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 }