gopkg.in/alecthomas/gometalinter.v3@v3.0.0/_linters/src/github.com/golang/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 contains a linter for Go source code. 8 package lint 9 10 import ( 11 "bufio" 12 "bytes" 13 "fmt" 14 "go/ast" 15 "go/parser" 16 "go/printer" 17 "go/token" 18 "go/types" 19 "regexp" 20 "sort" 21 "strconv" 22 "strings" 23 "unicode" 24 "unicode/utf8" 25 26 "golang.org/x/tools/go/gcexportdata" 27 ) 28 29 const styleGuideBase = "https://golang.org/wiki/CodeReviewComments" 30 31 // A Linter lints Go source code. 32 type Linter struct { 33 } 34 35 // Problem represents a problem in some source code. 36 type Problem struct { 37 Position token.Position // position in source file 38 Text string // the prose that describes the problem 39 Link string // (optional) the link to the style guide for the problem 40 Confidence float64 // a value in (0,1] estimating the confidence in this problem's correctness 41 LineText string // the source line 42 Category string // a short name for the general category of the problem 43 44 // If the problem has a suggested fix (the minority case), 45 // ReplacementLine is a full replacement for the relevant line of the source file. 46 ReplacementLine string 47 } 48 49 func (p *Problem) String() string { 50 if p.Link != "" { 51 return p.Text + "\n\n" + p.Link 52 } 53 return p.Text 54 } 55 56 type byPosition []Problem 57 58 func (p byPosition) Len() int { return len(p) } 59 func (p byPosition) Swap(i, j int) { p[i], p[j] = p[j], p[i] } 60 61 func (p byPosition) Less(i, j int) bool { 62 pi, pj := p[i].Position, p[j].Position 63 64 if pi.Filename != pj.Filename { 65 return pi.Filename < pj.Filename 66 } 67 if pi.Line != pj.Line { 68 return pi.Line < pj.Line 69 } 70 if pi.Column != pj.Column { 71 return pi.Column < pj.Column 72 } 73 74 return p[i].Text < p[j].Text 75 } 76 77 // Lint lints src. 78 func (l *Linter) Lint(filename string, src []byte) ([]Problem, error) { 79 return l.LintFiles(map[string][]byte{filename: src}) 80 } 81 82 // LintFiles lints a set of files of a single package. 83 // The argument is a map of filename to source. 84 func (l *Linter) LintFiles(files map[string][]byte) ([]Problem, error) { 85 pkg := &pkg{ 86 fset: token.NewFileSet(), 87 files: make(map[string]*file), 88 } 89 var pkgName string 90 for filename, src := range files { 91 if isGenerated(src) { 92 continue // See issue #239 93 } 94 f, err := parser.ParseFile(pkg.fset, filename, src, parser.ParseComments) 95 if err != nil { 96 return nil, err 97 } 98 if pkgName == "" { 99 pkgName = f.Name.Name 100 } else if f.Name.Name != pkgName { 101 return nil, fmt.Errorf("%s is in package %s, not %s", filename, f.Name.Name, pkgName) 102 } 103 pkg.files[filename] = &file{ 104 pkg: pkg, 105 f: f, 106 fset: pkg.fset, 107 src: src, 108 filename: filename, 109 } 110 } 111 if len(pkg.files) == 0 { 112 return nil, nil 113 } 114 return pkg.lint(), nil 115 } 116 117 var ( 118 genHdr = []byte("// Code generated ") 119 genFtr = []byte(" DO NOT EDIT.") 120 ) 121 122 // isGenerated reports whether the source file is generated code 123 // according the rules from https://golang.org/s/generatedcode. 124 func isGenerated(src []byte) bool { 125 sc := bufio.NewScanner(bytes.NewReader(src)) 126 for sc.Scan() { 127 b := sc.Bytes() 128 if bytes.HasPrefix(b, genHdr) && bytes.HasSuffix(b, genFtr) && len(b) >= len(genHdr)+len(genFtr) { 129 return true 130 } 131 } 132 return false 133 } 134 135 // pkg represents a package being linted. 136 type pkg struct { 137 fset *token.FileSet 138 files map[string]*file 139 140 typesPkg *types.Package 141 typesInfo *types.Info 142 143 // sortable is the set of types in the package that implement sort.Interface. 144 sortable map[string]bool 145 // main is whether this is a "main" package. 146 main bool 147 148 problems []Problem 149 } 150 151 func (p *pkg) lint() []Problem { 152 if err := p.typeCheck(); err != nil { 153 /* TODO(dsymonds): Consider reporting these errors when golint operates on entire packages. 154 if e, ok := err.(types.Error); ok { 155 pos := p.fset.Position(e.Pos) 156 conf := 1.0 157 if strings.Contains(e.Msg, "can't find import: ") { 158 // Golint is probably being run in a context that doesn't support 159 // typechecking (e.g. package files aren't found), so don't warn about it. 160 conf = 0 161 } 162 if conf > 0 { 163 p.errorfAt(pos, conf, category("typechecking"), e.Msg) 164 } 165 166 // TODO(dsymonds): Abort if !e.Soft? 167 } 168 */ 169 } 170 171 p.scanSortable() 172 p.main = p.isMain() 173 174 for _, f := range p.files { 175 f.lint() 176 } 177 178 sort.Sort(byPosition(p.problems)) 179 180 return p.problems 181 } 182 183 // file represents a file being linted. 184 type file struct { 185 pkg *pkg 186 f *ast.File 187 fset *token.FileSet 188 src []byte 189 filename string 190 } 191 192 func (f *file) isTest() bool { return strings.HasSuffix(f.filename, "_test.go") } 193 194 func (f *file) lint() { 195 f.lintPackageComment() 196 f.lintImports() 197 f.lintBlankImports() 198 f.lintExported() 199 f.lintNames() 200 f.lintVarDecls() 201 f.lintElses() 202 f.lintRanges() 203 f.lintErrorf() 204 f.lintErrors() 205 f.lintErrorStrings() 206 f.lintReceiverNames() 207 f.lintIncDec() 208 f.lintErrorReturn() 209 f.lintUnexportedReturn() 210 f.lintTimeNames() 211 f.lintContextKeyTypes() 212 f.lintContextArgs() 213 } 214 215 type link string 216 type category string 217 218 // The variadic arguments may start with link and category types, 219 // and must end with a format string and any arguments. 220 // It returns the new Problem. 221 func (f *file) errorf(n ast.Node, confidence float64, args ...interface{}) *Problem { 222 pos := f.fset.Position(n.Pos()) 223 if pos.Filename == "" { 224 pos.Filename = f.filename 225 } 226 return f.pkg.errorfAt(pos, confidence, args...) 227 } 228 229 func (p *pkg) errorfAt(pos token.Position, confidence float64, args ...interface{}) *Problem { 230 problem := Problem{ 231 Position: pos, 232 Confidence: confidence, 233 } 234 if pos.Filename != "" { 235 // The file might not exist in our mapping if a //line directive was encountered. 236 if f, ok := p.files[pos.Filename]; ok { 237 problem.LineText = srcLine(f.src, pos) 238 } 239 } 240 241 argLoop: 242 for len(args) > 1 { // always leave at least the format string in args 243 switch v := args[0].(type) { 244 case link: 245 problem.Link = string(v) 246 case category: 247 problem.Category = string(v) 248 default: 249 break argLoop 250 } 251 args = args[1:] 252 } 253 254 problem.Text = fmt.Sprintf(args[0].(string), args[1:]...) 255 256 p.problems = append(p.problems, problem) 257 return &p.problems[len(p.problems)-1] 258 } 259 260 var newImporter = func(fset *token.FileSet) types.ImporterFrom { 261 return gcexportdata.NewImporter(fset, make(map[string]*types.Package)) 262 } 263 264 func (p *pkg) typeCheck() error { 265 config := &types.Config{ 266 // By setting a no-op error reporter, the type checker does as much work as possible. 267 Error: func(error) {}, 268 Importer: newImporter(p.fset), 269 } 270 info := &types.Info{ 271 Types: make(map[ast.Expr]types.TypeAndValue), 272 Defs: make(map[*ast.Ident]types.Object), 273 Uses: make(map[*ast.Ident]types.Object), 274 Scopes: make(map[ast.Node]*types.Scope), 275 } 276 var anyFile *file 277 var astFiles []*ast.File 278 for _, f := range p.files { 279 anyFile = f 280 astFiles = append(astFiles, f.f) 281 } 282 pkg, err := config.Check(anyFile.f.Name.Name, p.fset, astFiles, info) 283 // Remember the typechecking info, even if config.Check failed, 284 // since we will get partial information. 285 p.typesPkg = pkg 286 p.typesInfo = info 287 return err 288 } 289 290 func (p *pkg) typeOf(expr ast.Expr) types.Type { 291 if p.typesInfo == nil { 292 return nil 293 } 294 return p.typesInfo.TypeOf(expr) 295 } 296 297 func (p *pkg) isNamedType(typ types.Type, importPath, name string) bool { 298 n, ok := typ.(*types.Named) 299 if !ok { 300 return false 301 } 302 tn := n.Obj() 303 return tn != nil && tn.Pkg() != nil && tn.Pkg().Path() == importPath && tn.Name() == name 304 } 305 306 // scopeOf returns the tightest scope encompassing id. 307 func (p *pkg) scopeOf(id *ast.Ident) *types.Scope { 308 var scope *types.Scope 309 if obj := p.typesInfo.ObjectOf(id); obj != nil { 310 scope = obj.Parent() 311 } 312 if scope == p.typesPkg.Scope() { 313 // We were given a top-level identifier. 314 // Use the file-level scope instead of the package-level scope. 315 pos := id.Pos() 316 for _, f := range p.files { 317 if f.f.Pos() <= pos && pos < f.f.End() { 318 scope = p.typesInfo.Scopes[f.f] 319 break 320 } 321 } 322 } 323 return scope 324 } 325 326 func (p *pkg) scanSortable() { 327 p.sortable = make(map[string]bool) 328 329 // bitfield for which methods exist on each type. 330 const ( 331 Len = 1 << iota 332 Less 333 Swap 334 ) 335 nmap := map[string]int{"Len": Len, "Less": Less, "Swap": Swap} 336 has := make(map[string]int) 337 for _, f := range p.files { 338 f.walk(func(n ast.Node) bool { 339 fn, ok := n.(*ast.FuncDecl) 340 if !ok || fn.Recv == nil || len(fn.Recv.List) == 0 { 341 return true 342 } 343 // TODO(dsymonds): We could check the signature to be more precise. 344 recv := receiverType(fn) 345 if i, ok := nmap[fn.Name.Name]; ok { 346 has[recv] |= i 347 } 348 return false 349 }) 350 } 351 for typ, ms := range has { 352 if ms == Len|Less|Swap { 353 p.sortable[typ] = true 354 } 355 } 356 } 357 358 func (p *pkg) isMain() bool { 359 for _, f := range p.files { 360 if f.isMain() { 361 return true 362 } 363 } 364 return false 365 } 366 367 func (f *file) isMain() bool { 368 if f.f.Name.Name == "main" { 369 return true 370 } 371 return false 372 } 373 374 // lintPackageComment checks package comments. It complains if 375 // there is no package comment, or if it is not of the right form. 376 // This has a notable false positive in that a package comment 377 // could rightfully appear in a different file of the same package, 378 // but that's not easy to fix since this linter is file-oriented. 379 func (f *file) lintPackageComment() { 380 if f.isTest() { 381 return 382 } 383 384 const ref = styleGuideBase + "#package-comments" 385 prefix := "Package " + f.f.Name.Name + " " 386 387 // Look for a detached package comment. 388 // First, scan for the last comment that occurs before the "package" keyword. 389 var lastCG *ast.CommentGroup 390 for _, cg := range f.f.Comments { 391 if cg.Pos() > f.f.Package { 392 // Gone past "package" keyword. 393 break 394 } 395 lastCG = cg 396 } 397 if lastCG != nil && strings.HasPrefix(lastCG.Text(), prefix) { 398 endPos := f.fset.Position(lastCG.End()) 399 pkgPos := f.fset.Position(f.f.Package) 400 if endPos.Line+1 < pkgPos.Line { 401 // There isn't a great place to anchor this error; 402 // the start of the blank lines between the doc and the package statement 403 // is at least pointing at the location of the problem. 404 pos := token.Position{ 405 Filename: endPos.Filename, 406 // Offset not set; it is non-trivial, and doesn't appear to be needed. 407 Line: endPos.Line + 1, 408 Column: 1, 409 } 410 f.pkg.errorfAt(pos, 0.9, link(ref), category("comments"), "package comment is detached; there should be no blank lines between it and the package statement") 411 return 412 } 413 } 414 415 if f.f.Doc == nil { 416 f.errorf(f.f, 0.2, link(ref), category("comments"), "should have a package comment, unless it's in another file for this package") 417 return 418 } 419 s := f.f.Doc.Text() 420 if ts := strings.TrimLeft(s, " \t"); ts != s { 421 f.errorf(f.f.Doc, 1, link(ref), category("comments"), "package comment should not have leading space") 422 s = ts 423 } 424 // Only non-main packages need to keep to this form. 425 if !f.pkg.main && !strings.HasPrefix(s, prefix) { 426 f.errorf(f.f.Doc, 1, link(ref), category("comments"), `package comment should be of the form "%s..."`, prefix) 427 } 428 } 429 430 // lintBlankImports complains if a non-main package has blank imports that are 431 // not documented. 432 func (f *file) lintBlankImports() { 433 // In package main and in tests, we don't complain about blank imports. 434 if f.pkg.main || f.isTest() { 435 return 436 } 437 438 // The first element of each contiguous group of blank imports should have 439 // an explanatory comment of some kind. 440 for i, imp := range f.f.Imports { 441 pos := f.fset.Position(imp.Pos()) 442 443 if !isBlank(imp.Name) { 444 continue // Ignore non-blank imports. 445 } 446 if i > 0 { 447 prev := f.f.Imports[i-1] 448 prevPos := f.fset.Position(prev.Pos()) 449 if isBlank(prev.Name) && prevPos.Line+1 == pos.Line { 450 continue // A subsequent blank in a group. 451 } 452 } 453 454 // This is the first blank import of a group. 455 if imp.Doc == nil && imp.Comment == nil { 456 ref := "" 457 f.errorf(imp, 1, link(ref), category("imports"), "a blank import should be only in a main or test package, or have a comment justifying it") 458 } 459 } 460 } 461 462 // lintImports examines import blocks. 463 func (f *file) lintImports() { 464 for i, is := range f.f.Imports { 465 _ = i 466 if is.Name != nil && is.Name.Name == "." && !f.isTest() { 467 f.errorf(is, 1, link(styleGuideBase+"#import-dot"), category("imports"), "should not use dot imports") 468 } 469 470 } 471 } 472 473 const docCommentsLink = styleGuideBase + "#doc-comments" 474 475 // lintExported examines the exported names. 476 // It complains if any required doc comments are missing, 477 // or if they are not of the right form. The exact rules are in 478 // lintFuncDoc, lintTypeDoc and lintValueSpecDoc; this function 479 // also tracks the GenDecl structure being traversed to permit 480 // doc comments for constants to be on top of the const block. 481 // It also complains if the names stutter when combined with 482 // the package name. 483 func (f *file) lintExported() { 484 if f.isTest() { 485 return 486 } 487 488 var lastGen *ast.GenDecl // last GenDecl entered. 489 490 // Set of GenDecls that have already had missing comments flagged. 491 genDeclMissingComments := make(map[*ast.GenDecl]bool) 492 493 f.walk(func(node ast.Node) bool { 494 switch v := node.(type) { 495 case *ast.GenDecl: 496 if v.Tok == token.IMPORT { 497 return false 498 } 499 // token.CONST, token.TYPE or token.VAR 500 lastGen = v 501 return true 502 case *ast.FuncDecl: 503 f.lintFuncDoc(v) 504 if v.Recv == nil { 505 // Only check for stutter on functions, not methods. 506 // Method names are not used package-qualified. 507 f.checkStutter(v.Name, "func") 508 } 509 // Don't proceed inside funcs. 510 return false 511 case *ast.TypeSpec: 512 // inside a GenDecl, which usually has the doc 513 doc := v.Doc 514 if doc == nil { 515 doc = lastGen.Doc 516 } 517 f.lintTypeDoc(v, doc) 518 f.checkStutter(v.Name, "type") 519 // Don't proceed inside types. 520 return false 521 case *ast.ValueSpec: 522 f.lintValueSpecDoc(v, lastGen, genDeclMissingComments) 523 return false 524 } 525 return true 526 }) 527 } 528 529 var allCapsRE = regexp.MustCompile(`^[A-Z0-9_]+$`) 530 531 // knownNameExceptions is a set of names that are known to be exempt from naming checks. 532 // This is usually because they are constrained by having to match names in the 533 // standard library. 534 var knownNameExceptions = map[string]bool{ 535 "LastInsertId": true, // must match database/sql 536 "kWh": true, 537 } 538 539 // lintNames examines all names in the file. 540 // It complains if any use underscores or incorrect known initialisms. 541 func (f *file) lintNames() { 542 // Package names need slightly different handling than other names. 543 if strings.Contains(f.f.Name.Name, "_") && !strings.HasSuffix(f.f.Name.Name, "_test") { 544 f.errorf(f.f, 1, link("http://golang.org/doc/effective_go.html#package-names"), category("naming"), "don't use an underscore in package name") 545 } 546 547 check := func(id *ast.Ident, thing string) { 548 if id.Name == "_" { 549 return 550 } 551 if knownNameExceptions[id.Name] { 552 return 553 } 554 555 // Handle two common styles from other languages that don't belong in Go. 556 if len(id.Name) >= 5 && allCapsRE.MatchString(id.Name) && strings.Contains(id.Name, "_") { 557 f.errorf(id, 0.8, link(styleGuideBase+"#mixed-caps"), category("naming"), "don't use ALL_CAPS in Go names; use CamelCase") 558 return 559 } 560 if len(id.Name) > 2 && id.Name[0] == 'k' && id.Name[1] >= 'A' && id.Name[1] <= 'Z' { 561 should := string(id.Name[1]+'a'-'A') + id.Name[2:] 562 f.errorf(id, 0.8, link(styleGuideBase+"#mixed-caps"), category("naming"), "don't use leading k in Go names; %s %s should be %s", thing, id.Name, should) 563 } 564 565 should := lintName(id.Name) 566 if id.Name == should { 567 return 568 } 569 570 if len(id.Name) > 2 && strings.Contains(id.Name[1:], "_") { 571 f.errorf(id, 0.9, link("http://golang.org/doc/effective_go.html#mixed-caps"), category("naming"), "don't use underscores in Go names; %s %s should be %s", thing, id.Name, should) 572 return 573 } 574 f.errorf(id, 0.8, link(styleGuideBase+"#initialisms"), category("naming"), "%s %s should be %s", thing, id.Name, should) 575 } 576 checkList := func(fl *ast.FieldList, thing string) { 577 if fl == nil { 578 return 579 } 580 for _, f := range fl.List { 581 for _, id := range f.Names { 582 check(id, thing) 583 } 584 } 585 } 586 f.walk(func(node ast.Node) bool { 587 switch v := node.(type) { 588 case *ast.AssignStmt: 589 if v.Tok == token.ASSIGN { 590 return true 591 } 592 for _, exp := range v.Lhs { 593 if id, ok := exp.(*ast.Ident); ok { 594 check(id, "var") 595 } 596 } 597 case *ast.FuncDecl: 598 if f.isTest() && (strings.HasPrefix(v.Name.Name, "Example") || strings.HasPrefix(v.Name.Name, "Test") || strings.HasPrefix(v.Name.Name, "Benchmark")) { 599 return true 600 } 601 602 thing := "func" 603 if v.Recv != nil { 604 thing = "method" 605 } 606 607 // Exclude naming warnings for functions that are exported to C but 608 // not exported in the Go API. 609 // See https://github.com/golang/lint/issues/144. 610 if ast.IsExported(v.Name.Name) || !isCgoExported(v) { 611 check(v.Name, thing) 612 } 613 614 checkList(v.Type.Params, thing+" parameter") 615 checkList(v.Type.Results, thing+" result") 616 case *ast.GenDecl: 617 if v.Tok == token.IMPORT { 618 return true 619 } 620 var thing string 621 switch v.Tok { 622 case token.CONST: 623 thing = "const" 624 case token.TYPE: 625 thing = "type" 626 case token.VAR: 627 thing = "var" 628 } 629 for _, spec := range v.Specs { 630 switch s := spec.(type) { 631 case *ast.TypeSpec: 632 check(s.Name, thing) 633 case *ast.ValueSpec: 634 for _, id := range s.Names { 635 check(id, thing) 636 } 637 } 638 } 639 case *ast.InterfaceType: 640 // Do not check interface method names. 641 // They are often constrainted by the method names of concrete types. 642 for _, x := range v.Methods.List { 643 ft, ok := x.Type.(*ast.FuncType) 644 if !ok { // might be an embedded interface name 645 continue 646 } 647 checkList(ft.Params, "interface method parameter") 648 checkList(ft.Results, "interface method result") 649 } 650 case *ast.RangeStmt: 651 if v.Tok == token.ASSIGN { 652 return true 653 } 654 if id, ok := v.Key.(*ast.Ident); ok { 655 check(id, "range var") 656 } 657 if id, ok := v.Value.(*ast.Ident); ok { 658 check(id, "range var") 659 } 660 case *ast.StructType: 661 for _, f := range v.Fields.List { 662 for _, id := range f.Names { 663 check(id, "struct field") 664 } 665 } 666 } 667 return true 668 }) 669 } 670 671 // lintName returns a different name if it should be different. 672 func lintName(name string) (should string) { 673 // Fast path for simple cases: "_" and all lowercase. 674 if name == "_" { 675 return name 676 } 677 allLower := true 678 for _, r := range name { 679 if !unicode.IsLower(r) { 680 allLower = false 681 break 682 } 683 } 684 if allLower { 685 return name 686 } 687 688 // Split camelCase at any lower->upper transition, and split on underscores. 689 // Check each word for common initialisms. 690 runes := []rune(name) 691 w, i := 0, 0 // index of start of word, scan 692 for i+1 <= len(runes) { 693 eow := false // whether we hit the end of a word 694 if i+1 == len(runes) { 695 eow = true 696 } else if runes[i+1] == '_' { 697 // underscore; shift the remainder forward over any run of underscores 698 eow = true 699 n := 1 700 for i+n+1 < len(runes) && runes[i+n+1] == '_' { 701 n++ 702 } 703 704 // Leave at most one underscore if the underscore is between two digits 705 if i+n+1 < len(runes) && unicode.IsDigit(runes[i]) && unicode.IsDigit(runes[i+n+1]) { 706 n-- 707 } 708 709 copy(runes[i+1:], runes[i+n+1:]) 710 runes = runes[:len(runes)-n] 711 } else if unicode.IsLower(runes[i]) && !unicode.IsLower(runes[i+1]) { 712 // lower->non-lower 713 eow = true 714 } 715 i++ 716 if !eow { 717 continue 718 } 719 720 // [w,i) is a word. 721 word := string(runes[w:i]) 722 if u := strings.ToUpper(word); commonInitialisms[u] { 723 // Keep consistent case, which is lowercase only at the start. 724 if w == 0 && unicode.IsLower(runes[w]) { 725 u = strings.ToLower(u) 726 } 727 // All the common initialisms are ASCII, 728 // so we can replace the bytes exactly. 729 copy(runes[w:], []rune(u)) 730 } else if w > 0 && strings.ToLower(word) == word { 731 // already all lowercase, and not the first word, so uppercase the first character. 732 runes[w] = unicode.ToUpper(runes[w]) 733 } 734 w = i 735 } 736 return string(runes) 737 } 738 739 // commonInitialisms is a set of common initialisms. 740 // Only add entries that are highly unlikely to be non-initialisms. 741 // For instance, "ID" is fine (Freudian code is rare), but "AND" is not. 742 var commonInitialisms = map[string]bool{ 743 "ACL": true, 744 "API": true, 745 "ASCII": true, 746 "CPU": true, 747 "CSS": true, 748 "DNS": true, 749 "EOF": true, 750 "GUID": true, 751 "HTML": true, 752 "HTTP": true, 753 "HTTPS": true, 754 "ID": true, 755 "IP": true, 756 "JSON": true, 757 "LHS": true, 758 "QPS": true, 759 "RAM": true, 760 "RHS": true, 761 "RPC": true, 762 "SLA": true, 763 "SMTP": true, 764 "SQL": true, 765 "SSH": true, 766 "TCP": true, 767 "TLS": true, 768 "TTL": true, 769 "UDP": true, 770 "UI": true, 771 "UID": true, 772 "UUID": true, 773 "URI": true, 774 "URL": true, 775 "UTF8": true, 776 "VM": true, 777 "XML": true, 778 "XMPP": true, 779 "XSRF": true, 780 "XSS": true, 781 } 782 783 // lintTypeDoc examines the doc comment on a type. 784 // It complains if they are missing from an exported type, 785 // or if they are not of the standard form. 786 func (f *file) lintTypeDoc(t *ast.TypeSpec, doc *ast.CommentGroup) { 787 if !ast.IsExported(t.Name.Name) { 788 return 789 } 790 if doc == nil { 791 f.errorf(t, 1, link(docCommentsLink), category("comments"), "exported type %v should have comment or be unexported", t.Name) 792 return 793 } 794 795 s := doc.Text() 796 articles := [...]string{"A", "An", "The"} 797 for _, a := range articles { 798 if strings.HasPrefix(s, a+" ") { 799 s = s[len(a)+1:] 800 break 801 } 802 } 803 if !strings.HasPrefix(s, t.Name.Name+" ") { 804 f.errorf(doc, 1, link(docCommentsLink), category("comments"), `comment on exported type %v should be of the form "%v ..." (with optional leading article)`, t.Name, t.Name) 805 } 806 } 807 808 var commonMethods = map[string]bool{ 809 "Error": true, 810 "Read": true, 811 "ServeHTTP": true, 812 "String": true, 813 "Write": true, 814 } 815 816 // lintFuncDoc examines doc comments on functions and methods. 817 // It complains if they are missing, or not of the right form. 818 // It has specific exclusions for well-known methods (see commonMethods above). 819 func (f *file) lintFuncDoc(fn *ast.FuncDecl) { 820 if !ast.IsExported(fn.Name.Name) { 821 // func is unexported 822 return 823 } 824 kind := "function" 825 name := fn.Name.Name 826 if fn.Recv != nil && len(fn.Recv.List) > 0 { 827 // method 828 kind = "method" 829 recv := receiverType(fn) 830 if !ast.IsExported(recv) { 831 // receiver is unexported 832 return 833 } 834 if commonMethods[name] { 835 return 836 } 837 switch name { 838 case "Len", "Less", "Swap": 839 if f.pkg.sortable[recv] { 840 return 841 } 842 } 843 name = recv + "." + name 844 } 845 if fn.Doc == nil { 846 f.errorf(fn, 1, link(docCommentsLink), category("comments"), "exported %s %s should have comment or be unexported", kind, name) 847 return 848 } 849 s := fn.Doc.Text() 850 prefix := fn.Name.Name + " " 851 if !strings.HasPrefix(s, prefix) { 852 f.errorf(fn.Doc, 1, link(docCommentsLink), category("comments"), `comment on exported %s %s should be of the form "%s..."`, kind, name, prefix) 853 } 854 } 855 856 // lintValueSpecDoc examines package-global variables and constants. 857 // It complains if they are not individually declared, 858 // or if they are not suitably documented in the right form (unless they are in a block that is commented). 859 func (f *file) lintValueSpecDoc(vs *ast.ValueSpec, gd *ast.GenDecl, genDeclMissingComments map[*ast.GenDecl]bool) { 860 kind := "var" 861 if gd.Tok == token.CONST { 862 kind = "const" 863 } 864 865 if len(vs.Names) > 1 { 866 // Check that none are exported except for the first. 867 for _, n := range vs.Names[1:] { 868 if ast.IsExported(n.Name) { 869 f.errorf(vs, 1, category("comments"), "exported %s %s should have its own declaration", kind, n.Name) 870 return 871 } 872 } 873 } 874 875 // Only one name. 876 name := vs.Names[0].Name 877 if !ast.IsExported(name) { 878 return 879 } 880 881 if vs.Doc == nil && gd.Doc == nil { 882 if genDeclMissingComments[gd] { 883 return 884 } 885 block := "" 886 if kind == "const" && gd.Lparen.IsValid() { 887 block = " (or a comment on this block)" 888 } 889 f.errorf(vs, 1, link(docCommentsLink), category("comments"), "exported %s %s should have comment%s or be unexported", kind, name, block) 890 genDeclMissingComments[gd] = true 891 return 892 } 893 // If this GenDecl has parens and a comment, we don't check its comment form. 894 if gd.Lparen.IsValid() && gd.Doc != nil { 895 return 896 } 897 // The relevant text to check will be on either vs.Doc or gd.Doc. 898 // Use vs.Doc preferentially. 899 doc := vs.Doc 900 if doc == nil { 901 doc = gd.Doc 902 } 903 prefix := name + " " 904 if !strings.HasPrefix(doc.Text(), prefix) { 905 f.errorf(doc, 1, link(docCommentsLink), category("comments"), `comment on exported %s %s should be of the form "%s..."`, kind, name, prefix) 906 } 907 } 908 909 func (f *file) checkStutter(id *ast.Ident, thing string) { 910 pkg, name := f.f.Name.Name, id.Name 911 if !ast.IsExported(name) { 912 // unexported name 913 return 914 } 915 // A name stutters if the package name is a strict prefix 916 // and the next character of the name starts a new word. 917 if len(name) <= len(pkg) { 918 // name is too short to stutter. 919 // This permits the name to be the same as the package name. 920 return 921 } 922 if !strings.EqualFold(pkg, name[:len(pkg)]) { 923 return 924 } 925 // We can assume the name is well-formed UTF-8. 926 // If the next rune after the package name is uppercase or an underscore 927 // the it's starting a new word and thus this name stutters. 928 rem := name[len(pkg):] 929 if next, _ := utf8.DecodeRuneInString(rem); next == '_' || unicode.IsUpper(next) { 930 f.errorf(id, 0.8, link(styleGuideBase+"#package-names"), category("naming"), "%s name will be used as %s.%s by other packages, and that stutters; consider calling this %s", thing, pkg, name, rem) 931 } 932 } 933 934 // zeroLiteral is a set of ast.BasicLit values that are zero values. 935 // It is not exhaustive. 936 var zeroLiteral = map[string]bool{ 937 "false": true, // bool 938 // runes 939 `'\x00'`: true, 940 `'\000'`: true, 941 // strings 942 `""`: true, 943 "``": true, 944 // numerics 945 "0": true, 946 "0.": true, 947 "0.0": true, 948 "0i": true, 949 } 950 951 // lintVarDecls examines variable declarations. It complains about declarations with 952 // redundant LHS types that can be inferred from the RHS. 953 func (f *file) lintVarDecls() { 954 var lastGen *ast.GenDecl // last GenDecl entered. 955 956 f.walk(func(node ast.Node) bool { 957 switch v := node.(type) { 958 case *ast.GenDecl: 959 if v.Tok != token.CONST && v.Tok != token.VAR { 960 return false 961 } 962 lastGen = v 963 return true 964 case *ast.ValueSpec: 965 if lastGen.Tok == token.CONST { 966 return false 967 } 968 if len(v.Names) > 1 || v.Type == nil || len(v.Values) == 0 { 969 return false 970 } 971 rhs := v.Values[0] 972 // An underscore var appears in a common idiom for compile-time interface satisfaction, 973 // as in "var _ Interface = (*Concrete)(nil)". 974 if isIdent(v.Names[0], "_") { 975 return false 976 } 977 // If the RHS is a zero value, suggest dropping it. 978 zero := false 979 if lit, ok := rhs.(*ast.BasicLit); ok { 980 zero = zeroLiteral[lit.Value] 981 } else if isIdent(rhs, "nil") { 982 zero = true 983 } 984 if zero { 985 f.errorf(rhs, 0.9, category("zero-value"), "should drop = %s from declaration of var %s; it is the zero value", f.render(rhs), v.Names[0]) 986 return false 987 } 988 lhsTyp := f.pkg.typeOf(v.Type) 989 rhsTyp := f.pkg.typeOf(rhs) 990 991 if !validType(lhsTyp) || !validType(rhsTyp) { 992 // Type checking failed (often due to missing imports). 993 return false 994 } 995 996 if !types.Identical(lhsTyp, rhsTyp) { 997 // Assignment to a different type is not redundant. 998 return false 999 } 1000 1001 // The next three conditions are for suppressing the warning in situations 1002 // where we were unable to typecheck. 1003 1004 // If the LHS type is an interface, don't warn, since it is probably a 1005 // concrete type on the RHS. Note that our feeble lexical check here 1006 // will only pick up interface{} and other literal interface types; 1007 // that covers most of the cases we care to exclude right now. 1008 if _, ok := v.Type.(*ast.InterfaceType); ok { 1009 return false 1010 } 1011 // If the RHS is an untyped const, only warn if the LHS type is its default type. 1012 if defType, ok := f.isUntypedConst(rhs); ok && !isIdent(v.Type, defType) { 1013 return false 1014 } 1015 1016 f.errorf(v.Type, 0.8, category("type-inference"), "should omit type %s from declaration of var %s; it will be inferred from the right-hand side", f.render(v.Type), v.Names[0]) 1017 return false 1018 } 1019 return true 1020 }) 1021 } 1022 1023 func validType(T types.Type) bool { 1024 return T != nil && 1025 T != types.Typ[types.Invalid] && 1026 !strings.Contains(T.String(), "invalid type") // good but not foolproof 1027 } 1028 1029 // lintElses examines else blocks. It complains about any else block whose if block ends in a return. 1030 func (f *file) lintElses() { 1031 // We don't want to flag if { } else if { } else { } constructions. 1032 // They will appear as an IfStmt whose Else field is also an IfStmt. 1033 // Record such a node so we ignore it when we visit it. 1034 ignore := make(map[*ast.IfStmt]bool) 1035 1036 f.walk(func(node ast.Node) bool { 1037 ifStmt, ok := node.(*ast.IfStmt) 1038 if !ok || ifStmt.Else == nil { 1039 return true 1040 } 1041 if ignore[ifStmt] { 1042 return true 1043 } 1044 if elseif, ok := ifStmt.Else.(*ast.IfStmt); ok { 1045 ignore[elseif] = true 1046 return true 1047 } 1048 if _, ok := ifStmt.Else.(*ast.BlockStmt); !ok { 1049 // only care about elses without conditions 1050 return true 1051 } 1052 if len(ifStmt.Body.List) == 0 { 1053 return true 1054 } 1055 shortDecl := false // does the if statement have a ":=" initialization statement? 1056 if ifStmt.Init != nil { 1057 if as, ok := ifStmt.Init.(*ast.AssignStmt); ok && as.Tok == token.DEFINE { 1058 shortDecl = true 1059 } 1060 } 1061 lastStmt := ifStmt.Body.List[len(ifStmt.Body.List)-1] 1062 if _, ok := lastStmt.(*ast.ReturnStmt); ok { 1063 extra := "" 1064 if shortDecl { 1065 extra = " (move short variable declaration to its own line if necessary)" 1066 } 1067 f.errorf(ifStmt.Else, 1, link(styleGuideBase+"#indent-error-flow"), category("indent"), "if block ends with a return statement, so drop this else and outdent its block"+extra) 1068 } 1069 return true 1070 }) 1071 } 1072 1073 // lintRanges examines range clauses. It complains about redundant constructions. 1074 func (f *file) lintRanges() { 1075 f.walk(func(node ast.Node) bool { 1076 rs, ok := node.(*ast.RangeStmt) 1077 if !ok { 1078 return true 1079 } 1080 if rs.Value == nil { 1081 // for x = range m { ... } 1082 return true // single var form 1083 } 1084 if !isIdent(rs.Value, "_") { 1085 // for ?, y = range m { ... } 1086 return true 1087 } 1088 1089 p := f.errorf(rs.Value, 1, category("range-loop"), "should omit 2nd value from range; this loop is equivalent to `for %s %s range ...`", f.render(rs.Key), rs.Tok) 1090 1091 newRS := *rs // shallow copy 1092 newRS.Value = nil 1093 p.ReplacementLine = f.firstLineOf(&newRS, rs) 1094 1095 return true 1096 }) 1097 } 1098 1099 // lintErrorf examines errors.New and testing.Error calls. It complains if its only argument is an fmt.Sprintf invocation. 1100 func (f *file) lintErrorf() { 1101 f.walk(func(node ast.Node) bool { 1102 ce, ok := node.(*ast.CallExpr) 1103 if !ok || len(ce.Args) != 1 { 1104 return true 1105 } 1106 isErrorsNew := isPkgDot(ce.Fun, "errors", "New") 1107 var isTestingError bool 1108 se, ok := ce.Fun.(*ast.SelectorExpr) 1109 if ok && se.Sel.Name == "Error" { 1110 if typ := f.pkg.typeOf(se.X); typ != nil { 1111 isTestingError = typ.String() == "*testing.T" 1112 } 1113 } 1114 if !isErrorsNew && !isTestingError { 1115 return true 1116 } 1117 arg := ce.Args[0] 1118 ce, ok = arg.(*ast.CallExpr) 1119 if !ok || !isPkgDot(ce.Fun, "fmt", "Sprintf") { 1120 return true 1121 } 1122 errorfPrefix := "fmt" 1123 if isTestingError { 1124 errorfPrefix = f.render(se.X) 1125 } 1126 p := f.errorf(node, 1, category("errors"), "should replace %s(fmt.Sprintf(...)) with %s.Errorf(...)", f.render(se), errorfPrefix) 1127 1128 m := f.srcLineWithMatch(ce, `^(.*)`+f.render(se)+`\(fmt\.Sprintf\((.*)\)\)(.*)$`) 1129 if m != nil { 1130 p.ReplacementLine = m[1] + errorfPrefix + ".Errorf(" + m[2] + ")" + m[3] 1131 } 1132 1133 return true 1134 }) 1135 } 1136 1137 // lintErrors examines global error vars. It complains if they aren't named in the standard way. 1138 func (f *file) lintErrors() { 1139 for _, decl := range f.f.Decls { 1140 gd, ok := decl.(*ast.GenDecl) 1141 if !ok || gd.Tok != token.VAR { 1142 continue 1143 } 1144 for _, spec := range gd.Specs { 1145 spec := spec.(*ast.ValueSpec) 1146 if len(spec.Names) != 1 || len(spec.Values) != 1 { 1147 continue 1148 } 1149 ce, ok := spec.Values[0].(*ast.CallExpr) 1150 if !ok { 1151 continue 1152 } 1153 if !isPkgDot(ce.Fun, "errors", "New") && !isPkgDot(ce.Fun, "fmt", "Errorf") { 1154 continue 1155 } 1156 1157 id := spec.Names[0] 1158 prefix := "err" 1159 if id.IsExported() { 1160 prefix = "Err" 1161 } 1162 if !strings.HasPrefix(id.Name, prefix) { 1163 f.errorf(id, 0.9, category("naming"), "error var %s should have name of the form %sFoo", id.Name, prefix) 1164 } 1165 } 1166 } 1167 } 1168 1169 func lintErrorString(s string) (isClean bool, conf float64) { 1170 const basicConfidence = 0.8 1171 const capConfidence = basicConfidence - 0.2 1172 first, firstN := utf8.DecodeRuneInString(s) 1173 last, _ := utf8.DecodeLastRuneInString(s) 1174 if last == '.' || last == ':' || last == '!' || last == '\n' { 1175 return false, basicConfidence 1176 } 1177 if unicode.IsUpper(first) { 1178 // People use proper nouns and exported Go identifiers in error strings, 1179 // so decrease the confidence of warnings for capitalization. 1180 if len(s) <= firstN { 1181 return false, capConfidence 1182 } 1183 // Flag strings starting with something that doesn't look like an initialism. 1184 if second, _ := utf8.DecodeRuneInString(s[firstN:]); !unicode.IsUpper(second) { 1185 return false, capConfidence 1186 } 1187 } 1188 return true, 0 1189 } 1190 1191 // lintErrorStrings examines error strings. 1192 // It complains if they are capitalized or end in punctuation or a newline. 1193 func (f *file) lintErrorStrings() { 1194 f.walk(func(node ast.Node) bool { 1195 ce, ok := node.(*ast.CallExpr) 1196 if !ok { 1197 return true 1198 } 1199 if !isPkgDot(ce.Fun, "errors", "New") && !isPkgDot(ce.Fun, "fmt", "Errorf") { 1200 return true 1201 } 1202 if len(ce.Args) < 1 { 1203 return true 1204 } 1205 str, ok := ce.Args[0].(*ast.BasicLit) 1206 if !ok || str.Kind != token.STRING { 1207 return true 1208 } 1209 s, _ := strconv.Unquote(str.Value) // can assume well-formed Go 1210 if s == "" { 1211 return true 1212 } 1213 clean, conf := lintErrorString(s) 1214 if clean { 1215 return true 1216 } 1217 1218 f.errorf(str, conf, link(styleGuideBase+"#error-strings"), category("errors"), 1219 "error strings should not be capitalized or end with punctuation or a newline") 1220 return true 1221 }) 1222 } 1223 1224 // lintReceiverNames examines receiver names. It complains about inconsistent 1225 // names used for the same type and names such as "this". 1226 func (f *file) lintReceiverNames() { 1227 typeReceiver := map[string]string{} 1228 f.walk(func(n ast.Node) bool { 1229 fn, ok := n.(*ast.FuncDecl) 1230 if !ok || fn.Recv == nil || len(fn.Recv.List) == 0 { 1231 return true 1232 } 1233 names := fn.Recv.List[0].Names 1234 if len(names) < 1 { 1235 return true 1236 } 1237 name := names[0].Name 1238 const ref = styleGuideBase + "#receiver-names" 1239 if name == "_" { 1240 f.errorf(n, 1, link(ref), category("naming"), `receiver name should not be an underscore`) 1241 return true 1242 } 1243 if name == "this" || name == "self" { 1244 f.errorf(n, 1, link(ref), category("naming"), `receiver name should be a reflection of its identity; don't use generic names such as "this" or "self"`) 1245 return true 1246 } 1247 recv := receiverType(fn) 1248 if prev, ok := typeReceiver[recv]; ok && prev != name { 1249 f.errorf(n, 1, link(ref), category("naming"), "receiver name %s should be consistent with previous receiver name %s for %s", name, prev, recv) 1250 return true 1251 } 1252 typeReceiver[recv] = name 1253 return true 1254 }) 1255 } 1256 1257 // lintIncDec examines statements that increment or decrement a variable. 1258 // It complains if they don't use x++ or x--. 1259 func (f *file) lintIncDec() { 1260 f.walk(func(n ast.Node) bool { 1261 as, ok := n.(*ast.AssignStmt) 1262 if !ok { 1263 return true 1264 } 1265 if len(as.Lhs) != 1 { 1266 return true 1267 } 1268 if !isOne(as.Rhs[0]) { 1269 return true 1270 } 1271 var suffix string 1272 switch as.Tok { 1273 case token.ADD_ASSIGN: 1274 suffix = "++" 1275 case token.SUB_ASSIGN: 1276 suffix = "--" 1277 default: 1278 return true 1279 } 1280 f.errorf(as, 0.8, category("unary-op"), "should replace %s with %s%s", f.render(as), f.render(as.Lhs[0]), suffix) 1281 return true 1282 }) 1283 } 1284 1285 // lintErrorReturn examines function declarations that return an error. 1286 // It complains if the error isn't the last parameter. 1287 func (f *file) lintErrorReturn() { 1288 f.walk(func(n ast.Node) bool { 1289 fn, ok := n.(*ast.FuncDecl) 1290 if !ok || fn.Type.Results == nil { 1291 return true 1292 } 1293 ret := fn.Type.Results.List 1294 if len(ret) <= 1 { 1295 return true 1296 } 1297 // An error return parameter should be the last parameter. 1298 // Flag any error parameters found before the last. 1299 for _, r := range ret[:len(ret)-1] { 1300 if isIdent(r.Type, "error") { 1301 f.errorf(fn, 0.9, category("arg-order"), "error should be the last type when returning multiple items") 1302 break // only flag one 1303 } 1304 } 1305 return true 1306 }) 1307 } 1308 1309 // lintUnexportedReturn examines exported function declarations. 1310 // It complains if any return an unexported type. 1311 func (f *file) lintUnexportedReturn() { 1312 f.walk(func(n ast.Node) bool { 1313 fn, ok := n.(*ast.FuncDecl) 1314 if !ok { 1315 return true 1316 } 1317 if fn.Type.Results == nil { 1318 return false 1319 } 1320 if !fn.Name.IsExported() { 1321 return false 1322 } 1323 thing := "func" 1324 if fn.Recv != nil && len(fn.Recv.List) > 0 { 1325 thing = "method" 1326 if !ast.IsExported(receiverType(fn)) { 1327 // Don't report exported methods of unexported types, 1328 // such as private implementations of sort.Interface. 1329 return false 1330 } 1331 } 1332 for _, ret := range fn.Type.Results.List { 1333 typ := f.pkg.typeOf(ret.Type) 1334 if exportedType(typ) { 1335 continue 1336 } 1337 f.errorf(ret.Type, 0.8, category("unexported-type-in-api"), 1338 "exported %s %s returns unexported type %s, which can be annoying to use", 1339 thing, fn.Name.Name, typ) 1340 break // only flag one 1341 } 1342 return false 1343 }) 1344 } 1345 1346 // exportedType reports whether typ is an exported type. 1347 // It is imprecise, and will err on the side of returning true, 1348 // such as for composite types. 1349 func exportedType(typ types.Type) bool { 1350 switch T := typ.(type) { 1351 case *types.Named: 1352 // Builtin types have no package. 1353 return T.Obj().Pkg() == nil || T.Obj().Exported() 1354 case *types.Map: 1355 return exportedType(T.Key()) && exportedType(T.Elem()) 1356 case interface { 1357 Elem() types.Type 1358 }: // array, slice, pointer, chan 1359 return exportedType(T.Elem()) 1360 } 1361 // Be conservative about other types, such as struct, interface, etc. 1362 return true 1363 } 1364 1365 // timeSuffixes is a list of name suffixes that imply a time unit. 1366 // This is not an exhaustive list. 1367 var timeSuffixes = []string{ 1368 "Sec", "Secs", "Seconds", 1369 "Msec", "Msecs", 1370 "Milli", "Millis", "Milliseconds", 1371 "Usec", "Usecs", "Microseconds", 1372 "MS", "Ms", 1373 } 1374 1375 func (f *file) lintTimeNames() { 1376 f.walk(func(node ast.Node) bool { 1377 v, ok := node.(*ast.ValueSpec) 1378 if !ok { 1379 return true 1380 } 1381 for _, name := range v.Names { 1382 origTyp := f.pkg.typeOf(name) 1383 // Look for time.Duration or *time.Duration; 1384 // the latter is common when using flag.Duration. 1385 typ := origTyp 1386 if pt, ok := typ.(*types.Pointer); ok { 1387 typ = pt.Elem() 1388 } 1389 if !f.pkg.isNamedType(typ, "time", "Duration") { 1390 continue 1391 } 1392 suffix := "" 1393 for _, suf := range timeSuffixes { 1394 if strings.HasSuffix(name.Name, suf) { 1395 suffix = suf 1396 break 1397 } 1398 } 1399 if suffix == "" { 1400 continue 1401 } 1402 f.errorf(v, 0.9, category("time"), "var %s is of type %v; don't use unit-specific suffix %q", name.Name, origTyp, suffix) 1403 } 1404 return true 1405 }) 1406 } 1407 1408 // lintContextKeyTypes checks for call expressions to context.WithValue with 1409 // basic types used for the key argument. 1410 // See: https://golang.org/issue/17293 1411 func (f *file) lintContextKeyTypes() { 1412 f.walk(func(node ast.Node) bool { 1413 switch node := node.(type) { 1414 case *ast.CallExpr: 1415 f.checkContextKeyType(node) 1416 } 1417 1418 return true 1419 }) 1420 } 1421 1422 // checkContextKeyType reports an error if the call expression calls 1423 // context.WithValue with a key argument of basic type. 1424 func (f *file) checkContextKeyType(x *ast.CallExpr) { 1425 sel, ok := x.Fun.(*ast.SelectorExpr) 1426 if !ok { 1427 return 1428 } 1429 pkg, ok := sel.X.(*ast.Ident) 1430 if !ok || pkg.Name != "context" { 1431 return 1432 } 1433 if sel.Sel.Name != "WithValue" { 1434 return 1435 } 1436 1437 // key is second argument to context.WithValue 1438 if len(x.Args) != 3 { 1439 return 1440 } 1441 key := f.pkg.typesInfo.Types[x.Args[1]] 1442 1443 if ktyp, ok := key.Type.(*types.Basic); ok && ktyp.Kind() != types.Invalid { 1444 f.errorf(x, 1.0, category("context"), fmt.Sprintf("should not use basic type %s as key in context.WithValue", key.Type)) 1445 } 1446 } 1447 1448 // lintContextArgs examines function declarations that contain an 1449 // argument with a type of context.Context 1450 // It complains if that argument isn't the first parameter. 1451 func (f *file) lintContextArgs() { 1452 f.walk(func(n ast.Node) bool { 1453 fn, ok := n.(*ast.FuncDecl) 1454 if !ok || len(fn.Type.Params.List) <= 1 { 1455 return true 1456 } 1457 // A context.Context should be the first parameter of a function. 1458 // Flag any that show up after the first. 1459 for _, arg := range fn.Type.Params.List[1:] { 1460 if isPkgDot(arg.Type, "context", "Context") { 1461 f.errorf(fn, 0.9, link("https://golang.org/pkg/context/"), category("arg-order"), "context.Context should be the first parameter of a function") 1462 break // only flag one 1463 } 1464 } 1465 return true 1466 }) 1467 } 1468 1469 // receiverType returns the named type of the method receiver, sans "*", 1470 // or "invalid-type" if fn.Recv is ill formed. 1471 func receiverType(fn *ast.FuncDecl) string { 1472 switch e := fn.Recv.List[0].Type.(type) { 1473 case *ast.Ident: 1474 return e.Name 1475 case *ast.StarExpr: 1476 if id, ok := e.X.(*ast.Ident); ok { 1477 return id.Name 1478 } 1479 } 1480 // The parser accepts much more than just the legal forms. 1481 return "invalid-type" 1482 } 1483 1484 func (f *file) walk(fn func(ast.Node) bool) { 1485 ast.Walk(walker(fn), f.f) 1486 } 1487 1488 func (f *file) render(x interface{}) string { 1489 var buf bytes.Buffer 1490 if err := printer.Fprint(&buf, f.fset, x); err != nil { 1491 panic(err) 1492 } 1493 return buf.String() 1494 } 1495 1496 func (f *file) debugRender(x interface{}) string { 1497 var buf bytes.Buffer 1498 if err := ast.Fprint(&buf, f.fset, x, nil); err != nil { 1499 panic(err) 1500 } 1501 return buf.String() 1502 } 1503 1504 // walker adapts a function to satisfy the ast.Visitor interface. 1505 // The function return whether the walk should proceed into the node's children. 1506 type walker func(ast.Node) bool 1507 1508 func (w walker) Visit(node ast.Node) ast.Visitor { 1509 if w(node) { 1510 return w 1511 } 1512 return nil 1513 } 1514 1515 func isIdent(expr ast.Expr, ident string) bool { 1516 id, ok := expr.(*ast.Ident) 1517 return ok && id.Name == ident 1518 } 1519 1520 // isBlank returns whether id is the blank identifier "_". 1521 // If id == nil, the answer is false. 1522 func isBlank(id *ast.Ident) bool { return id != nil && id.Name == "_" } 1523 1524 func isPkgDot(expr ast.Expr, pkg, name string) bool { 1525 sel, ok := expr.(*ast.SelectorExpr) 1526 return ok && isIdent(sel.X, pkg) && isIdent(sel.Sel, name) 1527 } 1528 1529 func isZero(expr ast.Expr) bool { 1530 lit, ok := expr.(*ast.BasicLit) 1531 return ok && lit.Kind == token.INT && lit.Value == "0" 1532 } 1533 1534 func isOne(expr ast.Expr) bool { 1535 lit, ok := expr.(*ast.BasicLit) 1536 return ok && lit.Kind == token.INT && lit.Value == "1" 1537 } 1538 1539 func isCgoExported(f *ast.FuncDecl) bool { 1540 if f.Recv != nil || f.Doc == nil { 1541 return false 1542 } 1543 1544 cgoExport := regexp.MustCompile(fmt.Sprintf("(?m)^//export %s$", regexp.QuoteMeta(f.Name.Name))) 1545 for _, c := range f.Doc.List { 1546 if cgoExport.MatchString(c.Text) { 1547 return true 1548 } 1549 } 1550 return false 1551 } 1552 1553 var basicTypeKinds = map[types.BasicKind]string{ 1554 types.UntypedBool: "bool", 1555 types.UntypedInt: "int", 1556 types.UntypedRune: "rune", 1557 types.UntypedFloat: "float64", 1558 types.UntypedComplex: "complex128", 1559 types.UntypedString: "string", 1560 } 1561 1562 // isUntypedConst reports whether expr is an untyped constant, 1563 // and indicates what its default type is. 1564 // scope may be nil. 1565 func (f *file) isUntypedConst(expr ast.Expr) (defType string, ok bool) { 1566 // Re-evaluate expr outside of its context to see if it's untyped. 1567 // (An expr evaluated within, for example, an assignment context will get the type of the LHS.) 1568 exprStr := f.render(expr) 1569 tv, err := types.Eval(f.fset, f.pkg.typesPkg, expr.Pos(), exprStr) 1570 if err != nil { 1571 return "", false 1572 } 1573 if b, ok := tv.Type.(*types.Basic); ok { 1574 if dt, ok := basicTypeKinds[b.Kind()]; ok { 1575 return dt, true 1576 } 1577 } 1578 1579 return "", false 1580 } 1581 1582 // firstLineOf renders the given node and returns its first line. 1583 // It will also match the indentation of another node. 1584 func (f *file) firstLineOf(node, match ast.Node) string { 1585 line := f.render(node) 1586 if i := strings.Index(line, "\n"); i >= 0 { 1587 line = line[:i] 1588 } 1589 return f.indentOf(match) + line 1590 } 1591 1592 func (f *file) indentOf(node ast.Node) string { 1593 line := srcLine(f.src, f.fset.Position(node.Pos())) 1594 for i, r := range line { 1595 switch r { 1596 case ' ', '\t': 1597 default: 1598 return line[:i] 1599 } 1600 } 1601 return line // unusual or empty line 1602 } 1603 1604 func (f *file) srcLineWithMatch(node ast.Node, pattern string) (m []string) { 1605 line := srcLine(f.src, f.fset.Position(node.Pos())) 1606 line = strings.TrimSuffix(line, "\n") 1607 rx := regexp.MustCompile(pattern) 1608 return rx.FindStringSubmatch(line) 1609 } 1610 1611 // srcLine returns the complete line at p, including the terminating newline. 1612 func srcLine(src []byte, p token.Position) string { 1613 // Run to end of line in both directions if not at line start/end. 1614 lo, hi := p.Offset, p.Offset+1 1615 for lo > 0 && src[lo-1] != '\n' { 1616 lo-- 1617 } 1618 for hi < len(src) && src[hi-1] != '\n' { 1619 hi++ 1620 } 1621 return string(src[lo:hi]) 1622 }