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