github.com/serversong/goreporter@v0.0.0-20200325104552-3cfaf44fd178/linters/simplecode/lint/lint.go (about)

     1  // Copyright (c) 2013 The Go Authors. All rights reserved.
     2  //
     3  // Use of this source code is governed by a BSD-style
     4  // license that can be found in the LICENSE file or at
     5  // https://developers.google.com/open-source/licenses/bsd.
     6  
     7  // Package lint provides the foundation for tools like gosimple.
     8  package lint // import "github.com/360EntSecGroup-Skylar/goreporter/linters/simplecode/lint"
     9  
    10  import (
    11  	"bytes"
    12  	"fmt"
    13  	"go/ast"
    14  	"go/constant"
    15  	"go/parser"
    16  	"go/printer"
    17  	"go/token"
    18  	"go/types"
    19  	"regexp"
    20  	"sort"
    21  	"strings"
    22  
    23  	"golang.org/x/tools/go/gcimporter"
    24  )
    25  
    26  type Func func(*File)
    27  
    28  // Problem represents a problem in some source code.
    29  type Problem struct {
    30  	Position   token.Position // position in source file
    31  	Text       string         // the prose that describes the problem
    32  	Link       string         // (optional) the link to the style guide for the problem
    33  	Confidence float64        // a value in (0,1] estimating the confidence in this problem's correctness
    34  	LineText   string         // the source line
    35  	Category   string         // a short name for the general category of the problem
    36  
    37  	// If the problem has a suggested fix (the minority case),
    38  	// ReplacementLine is a full replacement for the relevant line of the source file.
    39  	ReplacementLine string
    40  }
    41  
    42  func (p *Problem) String() string {
    43  	if p.Link != "" {
    44  		return p.Text + "\n\n" + p.Link
    45  	}
    46  	return p.Text
    47  }
    48  
    49  type ByPosition []Problem
    50  
    51  func (p ByPosition) Len() int      { return len(p) }
    52  func (p ByPosition) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
    53  
    54  func (p ByPosition) Less(i, j int) bool {
    55  	pi, pj := p[i].Position, p[j].Position
    56  
    57  	if pi.Filename != pj.Filename {
    58  		return pi.Filename < pj.Filename
    59  	}
    60  	if pi.Line != pj.Line {
    61  		return pi.Line < pj.Line
    62  	}
    63  	if pi.Column != pj.Column {
    64  		return pi.Column < pj.Column
    65  	}
    66  
    67  	return p[i].Text < p[j].Text
    68  }
    69  
    70  // A Linter lints Go source code.
    71  type Linter struct {
    72  	Funcs []Func
    73  }
    74  
    75  // Lint lints src.
    76  func (l *Linter) Lint(filename string, src []byte) ([]Problem, error) {
    77  	return l.LintFiles(map[string][]byte{filename: src})
    78  }
    79  
    80  // LintFiles lints a set of files of a single package.
    81  // The argument is a map of filename to source.
    82  func (l *Linter) LintFiles(files map[string][]byte) ([]Problem, error) {
    83  	if len(files) == 0 {
    84  		return nil, nil
    85  	}
    86  	pkg := &Pkg{
    87  		fset:  token.NewFileSet(),
    88  		files: make(map[string]*File),
    89  	}
    90  	var pkgName string
    91  	for filename, src := range files {
    92  		f, err := parser.ParseFile(pkg.fset, filename, src, parser.ParseComments)
    93  		if err != nil {
    94  			return nil, err
    95  		}
    96  		if pkgName == "" {
    97  			pkgName = f.Name.Name
    98  		} else if f.Name.Name != pkgName {
    99  			return nil, fmt.Errorf("%s is in package %s, not %s", filename, f.Name.Name, pkgName)
   100  		}
   101  		pkg.files[filename] = &File{
   102  			Pkg:      pkg,
   103  			File:     f,
   104  			Fset:     pkg.fset,
   105  			Src:      src,
   106  			Filename: filename,
   107  		}
   108  	}
   109  	return pkg.lint(l.Funcs), nil
   110  }
   111  
   112  // pkg represents a package being linted.
   113  type Pkg struct {
   114  	fset  *token.FileSet
   115  	files map[string]*File
   116  
   117  	TypesPkg  *types.Package
   118  	TypesInfo *types.Info
   119  
   120  	// main is whether this is a "main" package.
   121  	main bool
   122  
   123  	problems []Problem
   124  }
   125  
   126  func (p *Pkg) lint(linters []Func) []Problem {
   127  	if err := p.typeCheck(); err != nil {
   128  		/* TODO(dsymonds): Consider reporting these errors when golint operates on entire packages.
   129  		if e, ok := err.(types.Error); ok {
   130  			pos := p.fset.Position(e.Pos)
   131  			conf := 1.0
   132  			if strings.Contains(e.Msg, "can't find import: ") {
   133  				// Golint is probably being run in a context that doesn't support
   134  				// typechecking (e.g. package files aren't found), so don't warn about it.
   135  				conf = 0
   136  			}
   137  			if conf > 0 {
   138  				p.errorfAt(pos, conf, category("typechecking"), e.Msg)
   139  			}
   140  
   141  			// TODO(dsymonds): Abort if !e.Soft?
   142  		}
   143  		*/
   144  	}
   145  
   146  	p.main = p.IsMain()
   147  
   148  	for _, f := range p.files {
   149  		p.lintFile(f, linters)
   150  	}
   151  
   152  	sort.Sort(ByPosition(p.problems))
   153  
   154  	return p.problems
   155  }
   156  
   157  func (p *Pkg) lintFile(f *File, linters []Func) {
   158  	for _, fn := range linters {
   159  		fn(f)
   160  	}
   161  }
   162  
   163  // file represents a file being linted.
   164  type File struct {
   165  	Pkg      *Pkg
   166  	File     *ast.File
   167  	Fset     *token.FileSet
   168  	Src      []byte
   169  	Filename string
   170  }
   171  
   172  func (f *File) IsTest() bool { return strings.HasSuffix(f.Filename, "_test.go") }
   173  
   174  type Link string
   175  type Category string
   176  
   177  // The variadic arguments may start with link and category types,
   178  // and must end with a format string and any arguments.
   179  // It returns the new Problem.
   180  func (f *File) Errorf(n ast.Node, confidence float64, args ...interface{}) *Problem {
   181  	pos := f.Fset.Position(n.Pos())
   182  	if pos.Filename == "" {
   183  		pos.Filename = f.Filename
   184  	}
   185  	return f.Pkg.ErrorfAt(pos, confidence, args...)
   186  }
   187  
   188  func (p *Pkg) ErrorfAt(pos token.Position, confidence float64, args ...interface{}) *Problem {
   189  	problem := Problem{
   190  		Position:   pos,
   191  		Confidence: confidence,
   192  	}
   193  	if pos.Filename != "" {
   194  		// The file might not exist in our mapping if a //line directive was encountered.
   195  		if f, ok := p.files[pos.Filename]; ok {
   196  			problem.LineText = SrcLine(f.Src, pos)
   197  		}
   198  	}
   199  
   200  argLoop:
   201  	for len(args) > 1 { // always leave at least the format string in args
   202  		switch v := args[0].(type) {
   203  		case Link:
   204  			problem.Link = string(v)
   205  		case Category:
   206  			problem.Category = string(v)
   207  		default:
   208  			break argLoop
   209  		}
   210  		args = args[1:]
   211  	}
   212  
   213  	problem.Text = fmt.Sprintf(args[0].(string), args[1:]...)
   214  
   215  	p.problems = append(p.problems, problem)
   216  	return &p.problems[len(p.problems)-1]
   217  }
   218  
   219  var gcImporter = gcimporter.Import
   220  
   221  // importer implements go/types.Importer.
   222  // It also implements go/types.ImporterFrom, which was new in Go 1.6,
   223  // so vendoring will work.
   224  type importer struct {
   225  	impFn    func(packages map[string]*types.Package, path, srcDir string) (*types.Package, error)
   226  	packages map[string]*types.Package
   227  }
   228  
   229  func (i importer) Import(path string) (*types.Package, error) {
   230  	return i.impFn(i.packages, path, "")
   231  }
   232  
   233  // (importer).ImportFrom is in lint16.go.
   234  
   235  func (p *Pkg) typeCheck() error {
   236  	config := &types.Config{
   237  		// By setting a no-op error reporter, the type checker does as much work as possible.
   238  		Error: func(error) {},
   239  		Importer: importer{
   240  			impFn:    gcImporter,
   241  			packages: make(map[string]*types.Package),
   242  		},
   243  	}
   244  	info := &types.Info{
   245  		Types:  make(map[ast.Expr]types.TypeAndValue),
   246  		Defs:   make(map[*ast.Ident]types.Object),
   247  		Uses:   make(map[*ast.Ident]types.Object),
   248  		Scopes: make(map[ast.Node]*types.Scope),
   249  	}
   250  	var anyFile *File
   251  	var astFiles []*ast.File
   252  	for _, f := range p.files {
   253  		anyFile = f
   254  		astFiles = append(astFiles, f.File)
   255  	}
   256  	pkg, err := config.Check(anyFile.File.Name.Name, p.fset, astFiles, info)
   257  	// Remember the typechecking info, even if config.Check failed,
   258  	// since we will get partial information.
   259  	p.TypesPkg = pkg
   260  	p.TypesInfo = info
   261  	return err
   262  }
   263  
   264  func (p *Pkg) IsNamedType(typ types.Type, importPath, name string) bool {
   265  	n, ok := typ.(*types.Named)
   266  	if !ok {
   267  		return false
   268  	}
   269  	tn := n.Obj()
   270  	return tn != nil && tn.Pkg() != nil && tn.Pkg().Path() == importPath && tn.Name() == name
   271  }
   272  
   273  // scopeOf returns the tightest scope encompassing id.
   274  func (p *Pkg) ScopeOf(id *ast.Ident) *types.Scope {
   275  	var scope *types.Scope
   276  	if obj := p.TypesInfo.ObjectOf(id); obj != nil {
   277  		scope = obj.Parent()
   278  	}
   279  	if scope == p.TypesPkg.Scope() {
   280  		// We were given a top-level identifier.
   281  		// Use the file-level scope instead of the package-level scope.
   282  		pos := id.Pos()
   283  		for _, f := range p.files {
   284  			if f.File.Pos() <= pos && pos < f.File.End() {
   285  				scope = p.TypesInfo.Scopes[f.File]
   286  				break
   287  			}
   288  		}
   289  	}
   290  	return scope
   291  }
   292  
   293  func (p *Pkg) IsMain() bool {
   294  	for _, f := range p.files {
   295  		if f.IsMain() {
   296  			return true
   297  		}
   298  	}
   299  	return false
   300  }
   301  
   302  func (f *File) IsMain() bool {
   303  	return f.File.Name.Name == "main"
   304  }
   305  
   306  // exportedType reports whether typ is an exported type.
   307  // It is imprecise, and will err on the side of returning true,
   308  // such as for composite types.
   309  func ExportedType(typ types.Type) bool {
   310  	switch T := typ.(type) {
   311  	case *types.Named:
   312  		// Builtin types have no package.
   313  		return T.Obj().Pkg() == nil || T.Obj().Exported()
   314  	case *types.Map:
   315  		return ExportedType(T.Key()) && ExportedType(T.Elem())
   316  	case interface {
   317  		Elem() types.Type
   318  	}: // array, slice, pointer, chan
   319  		return ExportedType(T.Elem())
   320  	}
   321  	// Be conservative about other types, such as struct, interface, etc.
   322  	return true
   323  }
   324  
   325  func ReceiverType(fn *ast.FuncDecl) string {
   326  	switch e := fn.Recv.List[0].Type.(type) {
   327  	case *ast.Ident:
   328  		return e.Name
   329  	case *ast.StarExpr:
   330  		return e.X.(*ast.Ident).Name
   331  	}
   332  	panic(fmt.Sprintf("unknown method receiver AST node type %T", fn.Recv.List[0].Type))
   333  }
   334  
   335  func (f *File) Walk(fn func(ast.Node) bool) {
   336  	ast.Inspect(f.File, fn)
   337  }
   338  
   339  func (f *File) Render(x interface{}) string {
   340  	var buf bytes.Buffer
   341  	if err := printer.Fprint(&buf, f.Fset, x); err != nil {
   342  		panic(err)
   343  	}
   344  	return buf.String()
   345  }
   346  
   347  func (f *File) debugRender(x interface{}) string {
   348  	var buf bytes.Buffer
   349  	if err := ast.Fprint(&buf, f.Fset, x, nil); err != nil {
   350  		panic(err)
   351  	}
   352  	return buf.String()
   353  }
   354  
   355  func (f *File) RenderArgs(args []ast.Expr) string {
   356  	var ss []string
   357  	for _, arg := range args {
   358  		ss = append(ss, f.Render(arg))
   359  	}
   360  	return strings.Join(ss, ", ")
   361  }
   362  
   363  func IsIdent(expr ast.Expr, ident string) bool {
   364  	id, ok := expr.(*ast.Ident)
   365  	return ok && id.Name == ident
   366  }
   367  
   368  // isBlank returns whether id is the blank identifier "_".
   369  // If id == nil, the answer is false.
   370  func IsBlank(id ast.Expr) bool {
   371  	ident, ok := id.(*ast.Ident)
   372  	return ok && ident.Name == "_"
   373  }
   374  
   375  func IsPkgDot(expr ast.Expr, pkg, name string) bool {
   376  	sel, ok := expr.(*ast.SelectorExpr)
   377  	return ok && IsIdent(sel.X, pkg) && IsIdent(sel.Sel, name)
   378  }
   379  
   380  func IsZero(expr ast.Expr) bool {
   381  	lit, ok := expr.(*ast.BasicLit)
   382  	return ok && lit.Kind == token.INT && lit.Value == "0"
   383  }
   384  
   385  func IsOne(expr ast.Expr) bool {
   386  	lit, ok := expr.(*ast.BasicLit)
   387  	return ok && lit.Kind == token.INT && lit.Value == "1"
   388  }
   389  
   390  func IsNil(expr ast.Expr) bool {
   391  	// FIXME(dominikh): use type information
   392  	id, ok := expr.(*ast.Ident)
   393  	return ok && id.Name == "nil"
   394  }
   395  
   396  var basicTypeKinds = map[types.BasicKind]string{
   397  	types.UntypedBool:    "bool",
   398  	types.UntypedInt:     "int",
   399  	types.UntypedRune:    "rune",
   400  	types.UntypedFloat:   "float64",
   401  	types.UntypedComplex: "complex128",
   402  	types.UntypedString:  "string",
   403  }
   404  
   405  // isUntypedConst reports whether expr is an untyped constant,
   406  // and indicates what its default type is.
   407  // scope may be nil.
   408  func (f *File) IsUntypedConst(expr ast.Expr) (defType string, ok bool) {
   409  	// Re-evaluate expr outside of its context to see if it's untyped.
   410  	// (An expr evaluated within, for example, an assignment context will get the type of the LHS.)
   411  	exprStr := f.Render(expr)
   412  	tv, err := types.Eval(f.Fset, f.Pkg.TypesPkg, expr.Pos(), exprStr)
   413  	if err != nil {
   414  		return "", false
   415  	}
   416  	if b, ok := tv.Type.(*types.Basic); ok {
   417  		if dt, ok := basicTypeKinds[b.Kind()]; ok {
   418  			return dt, true
   419  		}
   420  	}
   421  
   422  	return "", false
   423  }
   424  
   425  // firstLineOf renders the given node and returns its first line.
   426  // It will also match the indentation of another node.
   427  func (f *File) FirstLineOf(node, match ast.Node) string {
   428  	line := f.Render(node)
   429  	if i := strings.Index(line, "\n"); i >= 0 {
   430  		line = line[:i]
   431  	}
   432  	return f.IndentOf(match) + line
   433  }
   434  
   435  func (f *File) IndentOf(node ast.Node) string {
   436  	line := SrcLine(f.Src, f.Fset.Position(node.Pos()))
   437  	for i, r := range line {
   438  		switch r {
   439  		case ' ', '\t':
   440  		default:
   441  			return line[:i]
   442  		}
   443  	}
   444  	return line // unusual or empty line
   445  }
   446  
   447  func (f *File) SrcLineWithMatch(node ast.Node, pattern string) (m []string) {
   448  	line := SrcLine(f.Src, f.Fset.Position(node.Pos()))
   449  	line = strings.TrimSuffix(line, "\n")
   450  	rx := regexp.MustCompile(pattern)
   451  	return rx.FindStringSubmatch(line)
   452  }
   453  
   454  // srcLine returns the complete line at p, including the terminating newline.
   455  func SrcLine(src []byte, p token.Position) string {
   456  	// Run to end of line in both directions if not at line start/end.
   457  	lo, hi := p.Offset, p.Offset+1
   458  	for lo > 0 && src[lo-1] != '\n' {
   459  		lo--
   460  	}
   461  	for hi < len(src) && src[hi-1] != '\n' {
   462  		hi++
   463  	}
   464  	return string(src[lo:hi])
   465  }
   466  
   467  func (f *File) BoolConst(expr ast.Expr) bool {
   468  	val := f.Pkg.TypesInfo.ObjectOf(expr.(*ast.Ident)).(*types.Const).Val()
   469  	return constant.BoolVal(val)
   470  }
   471  
   472  func (f *File) IsBoolConst(expr ast.Expr) bool {
   473  	// We explicitly don't support typed bools because more often than
   474  	// not, custom bool types are used as binary enums and the
   475  	// explicit comparison is desired.
   476  
   477  	ident, ok := expr.(*ast.Ident)
   478  	if !ok {
   479  		return false
   480  	}
   481  	obj := f.Pkg.TypesInfo.ObjectOf(ident)
   482  	c, ok := obj.(*types.Const)
   483  	if !ok {
   484  		return false
   485  	}
   486  	basic, ok := c.Type().(*types.Basic)
   487  	if !ok {
   488  		return false
   489  	}
   490  	if basic.Kind() != types.UntypedBool && basic.Kind() != types.Bool {
   491  		return false
   492  	}
   493  	return true
   494  }
   495  
   496  func ExprToInt(expr ast.Expr) (string, bool) {
   497  	switch y := expr.(type) {
   498  	case *ast.BasicLit:
   499  		if y.Kind != token.INT {
   500  			return "", false
   501  		}
   502  		return y.Value, true
   503  	case *ast.UnaryExpr:
   504  		if y.Op != token.SUB && y.Op != token.ADD {
   505  			return "", false
   506  		}
   507  		x, ok := y.X.(*ast.BasicLit)
   508  		if !ok {
   509  			return "", false
   510  		}
   511  		if x.Kind != token.INT {
   512  			return "", false
   513  		}
   514  		v := constant.MakeFromLiteral(x.Value, x.Kind, 0)
   515  		return constant.UnaryOp(y.Op, v, 0).String(), true
   516  	default:
   517  		return "", false
   518  	}
   519  }