gitee.com/lonely0422/gometalinter.git@v3.0.1-0.20190307123442-32416ab75314+incompatible/_linters/src/honnef.co/go/tools/stylecheck/lint.go (about) 1 package stylecheck // import "honnef.co/go/tools/stylecheck" 2 3 import ( 4 "fmt" 5 "go/ast" 6 "go/constant" 7 "go/token" 8 "go/types" 9 "strconv" 10 "strings" 11 "unicode" 12 "unicode/utf8" 13 14 "honnef.co/go/tools/lint" 15 . "honnef.co/go/tools/lint/lintdsl" 16 "honnef.co/go/tools/ssa" 17 18 "golang.org/x/tools/go/types/typeutil" 19 ) 20 21 type Checker struct { 22 CheckGenerated bool 23 } 24 25 func NewChecker() *Checker { 26 return &Checker{} 27 } 28 29 func (*Checker) Name() string { return "stylecheck" } 30 func (*Checker) Prefix() string { return "ST" } 31 func (c *Checker) Init(prog *lint.Program) {} 32 33 func (c *Checker) Checks() []lint.Check { 34 return []lint.Check{ 35 {ID: "ST1000", FilterGenerated: false, Fn: c.CheckPackageComment}, 36 {ID: "ST1001", FilterGenerated: true, Fn: c.CheckDotImports}, 37 // {ID: "ST1002", FilterGenerated: true, Fn: c.CheckBlankImports}, 38 {ID: "ST1003", FilterGenerated: true, Fn: c.CheckNames}, 39 // {ID: "ST1004", FilterGenerated: false, Fn: nil, }, 40 {ID: "ST1005", FilterGenerated: false, Fn: c.CheckErrorStrings}, 41 {ID: "ST1006", FilterGenerated: false, Fn: c.CheckReceiverNames}, 42 // {ID: "ST1007", FilterGenerated: true, Fn: c.CheckIncDec}, 43 {ID: "ST1008", FilterGenerated: false, Fn: c.CheckErrorReturn}, 44 // {ID: "ST1009", FilterGenerated: false, Fn: c.CheckUnexportedReturn}, 45 // {ID: "ST1010", FilterGenerated: false, Fn: c.CheckContextFirstArg}, 46 {ID: "ST1011", FilterGenerated: false, Fn: c.CheckTimeNames}, 47 {ID: "ST1012", FilterGenerated: false, Fn: c.CheckErrorVarNames}, 48 {ID: "ST1013", FilterGenerated: true, Fn: c.CheckHTTPStatusCodes}, 49 {ID: "ST1015", FilterGenerated: true, Fn: c.CheckDefaultCaseOrder}, 50 {ID: "ST1016", FilterGenerated: false, Fn: c.CheckReceiverNamesIdentical}, 51 {ID: "ST1017", FilterGenerated: true, Fn: c.CheckYodaConditions}, 52 } 53 } 54 55 func (c *Checker) CheckPackageComment(j *lint.Job) { 56 // - At least one file in a non-main package should have a package comment 57 // 58 // - The comment should be of the form 59 // "Package x ...". This has a slight potential for false 60 // positives, as multiple files can have package comments, in 61 // which case they get appended. But that doesn't happen a lot in 62 // the real world. 63 64 for _, pkg := range j.Program.InitialPackages { 65 if pkg.Name == "main" { 66 continue 67 } 68 hasDocs := false 69 for _, f := range pkg.Syntax { 70 if IsInTest(j, f) { 71 continue 72 } 73 if f.Doc != nil && len(f.Doc.List) > 0 { 74 hasDocs = true 75 prefix := "Package " + f.Name.Name + " " 76 if !strings.HasPrefix(strings.TrimSpace(f.Doc.Text()), prefix) { 77 j.Errorf(f.Doc, `package comment should be of the form "%s..."`, prefix) 78 } 79 f.Doc.Text() 80 } 81 } 82 83 if !hasDocs { 84 for _, f := range pkg.Syntax { 85 if IsInTest(j, f) { 86 continue 87 } 88 j.Errorf(f, "at least one file in a package should have a package comment") 89 } 90 } 91 } 92 } 93 94 func (c *Checker) CheckDotImports(j *lint.Job) { 95 for _, pkg := range j.Program.InitialPackages { 96 for _, f := range pkg.Syntax { 97 imports: 98 for _, imp := range f.Imports { 99 path := imp.Path.Value 100 path = path[1 : len(path)-1] 101 for _, w := range pkg.Config.DotImportWhitelist { 102 if w == path { 103 continue imports 104 } 105 } 106 107 if imp.Name != nil && imp.Name.Name == "." && !IsInTest(j, f) { 108 j.Errorf(imp, "should not use dot imports") 109 } 110 } 111 } 112 } 113 } 114 115 func (c *Checker) CheckBlankImports(j *lint.Job) { 116 fset := j.Program.Fset() 117 for _, f := range j.Program.Files { 118 if IsInMain(j, f) || IsInTest(j, f) { 119 continue 120 } 121 122 // Collect imports of the form `import _ "foo"`, i.e. with no 123 // parentheses, as their comment will be associated with the 124 // (paren-free) GenDecl, not the import spec itself. 125 // 126 // We don't directly process the GenDecl so that we can 127 // correctly handle the following: 128 // 129 // import _ "foo" 130 // import _ "bar" 131 // 132 // where only the first import should get flagged. 133 skip := map[ast.Spec]bool{} 134 ast.Inspect(f, func(node ast.Node) bool { 135 switch node := node.(type) { 136 case *ast.File: 137 return true 138 case *ast.GenDecl: 139 if node.Tok != token.IMPORT { 140 return false 141 } 142 if node.Lparen == token.NoPos && node.Doc != nil { 143 skip[node.Specs[0]] = true 144 } 145 return false 146 } 147 return false 148 }) 149 for i, imp := range f.Imports { 150 pos := fset.Position(imp.Pos()) 151 152 if !IsBlank(imp.Name) { 153 continue 154 } 155 // Only flag the first blank import in a group of imports, 156 // or don't flag any of them, if the first one is 157 // commented 158 if i > 0 { 159 prev := f.Imports[i-1] 160 prevPos := fset.Position(prev.Pos()) 161 if pos.Line-1 == prevPos.Line && IsBlank(prev.Name) { 162 continue 163 } 164 } 165 166 if imp.Doc == nil && imp.Comment == nil && !skip[imp] { 167 j.Errorf(imp, "a blank import should be only in a main or test package, or have a comment justifying it") 168 } 169 } 170 } 171 } 172 173 func (c *Checker) CheckIncDec(j *lint.Job) { 174 // TODO(dh): this can be noisy for function bodies that look like this: 175 // x += 3 176 // ... 177 // x += 2 178 // ... 179 // x += 1 180 fn := func(node ast.Node) bool { 181 assign, ok := node.(*ast.AssignStmt) 182 if !ok || (assign.Tok != token.ADD_ASSIGN && assign.Tok != token.SUB_ASSIGN) { 183 return true 184 } 185 if (len(assign.Lhs) != 1 || len(assign.Rhs) != 1) || 186 !IsIntLiteral(assign.Rhs[0], "1") { 187 return true 188 } 189 190 suffix := "" 191 switch assign.Tok { 192 case token.ADD_ASSIGN: 193 suffix = "++" 194 case token.SUB_ASSIGN: 195 suffix = "--" 196 } 197 198 j.Errorf(assign, "should replace %s with %s%s", Render(j, assign), Render(j, assign.Lhs[0]), suffix) 199 return true 200 } 201 for _, f := range j.Program.Files { 202 ast.Inspect(f, fn) 203 } 204 } 205 206 func (c *Checker) CheckErrorReturn(j *lint.Job) { 207 fnLoop: 208 for _, fn := range j.Program.InitialFunctions { 209 sig := fn.Type().(*types.Signature) 210 rets := sig.Results() 211 if rets == nil || rets.Len() < 2 { 212 continue 213 } 214 215 if rets.At(rets.Len()-1).Type() == types.Universe.Lookup("error").Type() { 216 // Last return type is error. If the function also returns 217 // errors in other positions, that's fine. 218 continue 219 } 220 for i := rets.Len() - 2; i >= 0; i-- { 221 if rets.At(i).Type() == types.Universe.Lookup("error").Type() { 222 j.Errorf(rets.At(i), "error should be returned as the last argument") 223 continue fnLoop 224 } 225 } 226 } 227 } 228 229 // CheckUnexportedReturn checks that exported functions on exported 230 // types do not return unexported types. 231 func (c *Checker) CheckUnexportedReturn(j *lint.Job) { 232 for _, fn := range j.Program.InitialFunctions { 233 if fn.Synthetic != "" || fn.Parent() != nil { 234 continue 235 } 236 if !ast.IsExported(fn.Name()) || IsInMain(j, fn) || IsInTest(j, fn) { 237 continue 238 } 239 sig := fn.Type().(*types.Signature) 240 if sig.Recv() != nil && !ast.IsExported(Dereference(sig.Recv().Type()).(*types.Named).Obj().Name()) { 241 continue 242 } 243 res := sig.Results() 244 for i := 0; i < res.Len(); i++ { 245 if named, ok := DereferenceR(res.At(i).Type()).(*types.Named); ok && 246 !ast.IsExported(named.Obj().Name()) && 247 named != types.Universe.Lookup("error").Type() { 248 j.Errorf(fn, "should not return unexported type") 249 } 250 } 251 } 252 } 253 254 func (c *Checker) CheckReceiverNames(j *lint.Job) { 255 for _, pkg := range j.Program.InitialPackages { 256 for _, m := range pkg.SSA.Members { 257 if T, ok := m.Object().(*types.TypeName); ok && !T.IsAlias() { 258 ms := typeutil.IntuitiveMethodSet(T.Type(), nil) 259 for _, sel := range ms { 260 fn := sel.Obj().(*types.Func) 261 recv := fn.Type().(*types.Signature).Recv() 262 if Dereference(recv.Type()) != T.Type() { 263 // skip embedded methods 264 continue 265 } 266 if recv.Name() == "self" || recv.Name() == "this" { 267 j.Errorf(recv, `receiver name should be a reflection of its identity; don't use generic names such as "this" or "self"`) 268 } 269 if recv.Name() == "_" { 270 j.Errorf(recv, "receiver name should not be an underscore, omit the name if it is unused") 271 } 272 } 273 } 274 } 275 } 276 } 277 278 func (c *Checker) CheckReceiverNamesIdentical(j *lint.Job) { 279 for _, pkg := range j.Program.InitialPackages { 280 for _, m := range pkg.SSA.Members { 281 names := map[string]int{} 282 283 var firstFn *types.Func 284 if T, ok := m.Object().(*types.TypeName); ok && !T.IsAlias() { 285 ms := typeutil.IntuitiveMethodSet(T.Type(), nil) 286 for _, sel := range ms { 287 fn := sel.Obj().(*types.Func) 288 recv := fn.Type().(*types.Signature).Recv() 289 if Dereference(recv.Type()) != T.Type() { 290 // skip embedded methods 291 continue 292 } 293 if firstFn == nil { 294 firstFn = fn 295 } 296 if recv.Name() != "" && recv.Name() != "_" { 297 names[recv.Name()]++ 298 } 299 } 300 } 301 302 if len(names) > 1 { 303 var seen []string 304 for name, count := range names { 305 seen = append(seen, fmt.Sprintf("%dx %q", count, name)) 306 } 307 308 j.Errorf(firstFn, "methods on the same type should have the same receiver name (seen %s)", strings.Join(seen, ", ")) 309 } 310 } 311 } 312 } 313 314 func (c *Checker) CheckContextFirstArg(j *lint.Job) { 315 // TODO(dh): this check doesn't apply to test helpers. Example from the stdlib: 316 // func helperCommandContext(t *testing.T, ctx context.Context, s ...string) (cmd *exec.Cmd) { 317 fnLoop: 318 for _, fn := range j.Program.InitialFunctions { 319 if fn.Synthetic != "" || fn.Parent() != nil { 320 continue 321 } 322 params := fn.Signature.Params() 323 if params.Len() < 2 { 324 continue 325 } 326 if types.TypeString(params.At(0).Type(), nil) == "context.Context" { 327 continue 328 } 329 for i := 1; i < params.Len(); i++ { 330 param := params.At(i) 331 if types.TypeString(param.Type(), nil) == "context.Context" { 332 j.Errorf(param, "context.Context should be the first argument of a function") 333 continue fnLoop 334 } 335 } 336 } 337 } 338 339 func (c *Checker) CheckErrorStrings(j *lint.Job) { 340 fnNames := map[*ssa.Package]map[string]bool{} 341 for _, fn := range j.Program.InitialFunctions { 342 m := fnNames[fn.Package()] 343 if m == nil { 344 m = map[string]bool{} 345 fnNames[fn.Package()] = m 346 } 347 m[fn.Name()] = true 348 } 349 350 for _, fn := range j.Program.InitialFunctions { 351 if IsInTest(j, fn) { 352 // We don't care about malformed error messages in tests; 353 // they're usually for direct human consumption, not part 354 // of an API 355 continue 356 } 357 for _, block := range fn.Blocks { 358 instrLoop: 359 for _, ins := range block.Instrs { 360 call, ok := ins.(*ssa.Call) 361 if !ok { 362 continue 363 } 364 if !IsCallTo(call.Common(), "errors.New") && !IsCallTo(call.Common(), "fmt.Errorf") { 365 continue 366 } 367 368 k, ok := call.Common().Args[0].(*ssa.Const) 369 if !ok { 370 continue 371 } 372 373 s := constant.StringVal(k.Value) 374 if len(s) == 0 { 375 continue 376 } 377 switch s[len(s)-1] { 378 case '.', ':', '!', '\n': 379 j.Errorf(call, "error strings should not end with punctuation or a newline") 380 } 381 idx := strings.IndexByte(s, ' ') 382 if idx == -1 { 383 // single word error message, probably not a real 384 // error but something used in tests or during 385 // debugging 386 continue 387 } 388 word := s[:idx] 389 first, n := utf8.DecodeRuneInString(word) 390 if !unicode.IsUpper(first) { 391 continue 392 } 393 for _, c := range word[n:] { 394 if unicode.IsUpper(c) { 395 // Word is probably an initialism or 396 // multi-word function name 397 continue instrLoop 398 } 399 } 400 401 word = strings.TrimRightFunc(word, func(r rune) bool { return unicode.IsPunct(r) }) 402 if fnNames[fn.Package()][word] { 403 // Word is probably the name of a function in this package 404 continue 405 } 406 // First word in error starts with a capital 407 // letter, and the word doesn't contain any other 408 // capitals, making it unlikely to be an 409 // initialism or multi-word function name. 410 // 411 // It could still be a proper noun, though. 412 413 j.Errorf(call, "error strings should not be capitalized") 414 } 415 } 416 } 417 } 418 419 func (c *Checker) CheckTimeNames(j *lint.Job) { 420 suffixes := []string{ 421 "Sec", "Secs", "Seconds", 422 "Msec", "Msecs", 423 "Milli", "Millis", "Milliseconds", 424 "Usec", "Usecs", "Microseconds", 425 "MS", "Ms", 426 } 427 fn := func(T types.Type, names []*ast.Ident) { 428 if !IsType(T, "time.Duration") && !IsType(T, "*time.Duration") { 429 return 430 } 431 for _, name := range names { 432 for _, suffix := range suffixes { 433 if strings.HasSuffix(name.Name, suffix) { 434 j.Errorf(name, "var %s is of type %v; don't use unit-specific suffix %q", name.Name, T, suffix) 435 break 436 } 437 } 438 } 439 } 440 for _, f := range j.Program.Files { 441 ast.Inspect(f, func(node ast.Node) bool { 442 switch node := node.(type) { 443 case *ast.ValueSpec: 444 T := TypeOf(j, node.Type) 445 fn(T, node.Names) 446 case *ast.FieldList: 447 for _, field := range node.List { 448 T := TypeOf(j, field.Type) 449 fn(T, field.Names) 450 } 451 } 452 return true 453 }) 454 } 455 } 456 457 func (c *Checker) CheckErrorVarNames(j *lint.Job) { 458 for _, f := range j.Program.Files { 459 for _, decl := range f.Decls { 460 gen, ok := decl.(*ast.GenDecl) 461 if !ok || gen.Tok != token.VAR { 462 continue 463 } 464 for _, spec := range gen.Specs { 465 spec := spec.(*ast.ValueSpec) 466 if len(spec.Names) != len(spec.Values) { 467 continue 468 } 469 470 for i, name := range spec.Names { 471 val := spec.Values[i] 472 if !IsCallToAST(j, val, "errors.New") && !IsCallToAST(j, val, "fmt.Errorf") { 473 continue 474 } 475 476 prefix := "err" 477 if name.IsExported() { 478 prefix = "Err" 479 } 480 if !strings.HasPrefix(name.Name, prefix) { 481 j.Errorf(name, "error var %s should have name of the form %sFoo", name.Name, prefix) 482 } 483 } 484 } 485 } 486 } 487 } 488 489 var httpStatusCodes = map[int]string{ 490 100: "StatusContinue", 491 101: "StatusSwitchingProtocols", 492 102: "StatusProcessing", 493 200: "StatusOK", 494 201: "StatusCreated", 495 202: "StatusAccepted", 496 203: "StatusNonAuthoritativeInfo", 497 204: "StatusNoContent", 498 205: "StatusResetContent", 499 206: "StatusPartialContent", 500 207: "StatusMultiStatus", 501 208: "StatusAlreadyReported", 502 226: "StatusIMUsed", 503 300: "StatusMultipleChoices", 504 301: "StatusMovedPermanently", 505 302: "StatusFound", 506 303: "StatusSeeOther", 507 304: "StatusNotModified", 508 305: "StatusUseProxy", 509 307: "StatusTemporaryRedirect", 510 308: "StatusPermanentRedirect", 511 400: "StatusBadRequest", 512 401: "StatusUnauthorized", 513 402: "StatusPaymentRequired", 514 403: "StatusForbidden", 515 404: "StatusNotFound", 516 405: "StatusMethodNotAllowed", 517 406: "StatusNotAcceptable", 518 407: "StatusProxyAuthRequired", 519 408: "StatusRequestTimeout", 520 409: "StatusConflict", 521 410: "StatusGone", 522 411: "StatusLengthRequired", 523 412: "StatusPreconditionFailed", 524 413: "StatusRequestEntityTooLarge", 525 414: "StatusRequestURITooLong", 526 415: "StatusUnsupportedMediaType", 527 416: "StatusRequestedRangeNotSatisfiable", 528 417: "StatusExpectationFailed", 529 418: "StatusTeapot", 530 422: "StatusUnprocessableEntity", 531 423: "StatusLocked", 532 424: "StatusFailedDependency", 533 426: "StatusUpgradeRequired", 534 428: "StatusPreconditionRequired", 535 429: "StatusTooManyRequests", 536 431: "StatusRequestHeaderFieldsTooLarge", 537 451: "StatusUnavailableForLegalReasons", 538 500: "StatusInternalServerError", 539 501: "StatusNotImplemented", 540 502: "StatusBadGateway", 541 503: "StatusServiceUnavailable", 542 504: "StatusGatewayTimeout", 543 505: "StatusHTTPVersionNotSupported", 544 506: "StatusVariantAlsoNegotiates", 545 507: "StatusInsufficientStorage", 546 508: "StatusLoopDetected", 547 510: "StatusNotExtended", 548 511: "StatusNetworkAuthenticationRequired", 549 } 550 551 func (c *Checker) CheckHTTPStatusCodes(j *lint.Job) { 552 for _, pkg := range j.Program.InitialPackages { 553 whitelist := map[string]bool{} 554 for _, code := range pkg.Config.HTTPStatusCodeWhitelist { 555 whitelist[code] = true 556 } 557 fn := func(node ast.Node) bool { 558 call, ok := node.(*ast.CallExpr) 559 if !ok { 560 return true 561 } 562 563 var arg int 564 switch CallNameAST(j, call) { 565 case "net/http.Error": 566 arg = 2 567 case "net/http.Redirect": 568 arg = 3 569 case "net/http.StatusText": 570 arg = 0 571 case "net/http.RedirectHandler": 572 arg = 1 573 default: 574 return true 575 } 576 lit, ok := call.Args[arg].(*ast.BasicLit) 577 if !ok { 578 return true 579 } 580 if whitelist[lit.Value] { 581 return true 582 } 583 584 n, err := strconv.Atoi(lit.Value) 585 if err != nil { 586 return true 587 } 588 s, ok := httpStatusCodes[n] 589 if !ok { 590 return true 591 } 592 j.Errorf(lit, "should use constant http.%s instead of numeric literal %d", s, n) 593 return true 594 } 595 for _, f := range pkg.Syntax { 596 ast.Inspect(f, fn) 597 } 598 } 599 } 600 601 func (c *Checker) CheckDefaultCaseOrder(j *lint.Job) { 602 fn := func(node ast.Node) bool { 603 stmt, ok := node.(*ast.SwitchStmt) 604 if !ok { 605 return true 606 } 607 list := stmt.Body.List 608 for i, c := range list { 609 if c.(*ast.CaseClause).List == nil && i != 0 && i != len(list)-1 { 610 j.Errorf(c, "default case should be first or last in switch statement") 611 break 612 } 613 } 614 return true 615 } 616 for _, f := range j.Program.Files { 617 ast.Inspect(f, fn) 618 } 619 } 620 621 func (c *Checker) CheckYodaConditions(j *lint.Job) { 622 fn := func(node ast.Node) bool { 623 cond, ok := node.(*ast.BinaryExpr) 624 if !ok { 625 return true 626 } 627 if cond.Op != token.EQL && cond.Op != token.NEQ { 628 return true 629 } 630 if _, ok := cond.X.(*ast.BasicLit); !ok { 631 return true 632 } 633 if _, ok := cond.Y.(*ast.BasicLit); ok { 634 // Don't flag lit == lit conditions, just in case 635 return true 636 } 637 j.Errorf(cond, "don't use Yoda conditions") 638 return true 639 } 640 for _, f := range j.Program.Files { 641 ast.Inspect(f, fn) 642 } 643 }