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} 843 844// lintFuncDoc examines doc comments on functions and methods. 845// It complains if they are missing, or not of the right form. 846// It has specific exclusions for well-known methods (see commonMethods above). 847func (f *file) lintFuncDoc(fn *ast.FuncDecl) { 848 if !ast.IsExported(fn.Name.Name) { 849 // func is unexported 850 return 851 } 852 kind := "function" 853 name := fn.Name.Name 854 if fn.Recv != nil && len(fn.Recv.List) > 0 { 855 // method 856 kind = "method" 857 recv := receiverType(fn) 858 if !ast.IsExported(recv) { 859 // receiver is unexported 860 return 861 } 862 if commonMethods[name] { 863 return 864 } 865 switch name { 866 case "Len", "Less", "Swap": 867 if f.pkg.sortable[recv] { 868 return 869 } 870 } 871 name = recv + "." + name 872 } 873 if fn.Doc == nil { 874 f.errorf(fn, 1, link(docCommentsLink), category("comments"), "exported %s %s should have comment or be unexported", kind, name) 875 return 876 } 877 s := fn.Doc.Text() 878 prefix := fn.Name.Name + " " 879 if !strings.HasPrefix(s, prefix) { 880 f.errorf(fn.Doc, 1, link(docCommentsLink), category("comments"), `comment on exported %s %s should be of the form "%s..."`, kind, name, prefix) 881 } 882} 883 884// lintValueSpecDoc examines package-global variables and constants. 885// It complains if they are not individually declared, 886// or if they are not suitably documented in the right form (unless they are in a block that is commented). 887func (f *file) lintValueSpecDoc(vs *ast.ValueSpec, gd *ast.GenDecl, genDeclMissingComments map[*ast.GenDecl]bool) { 888 kind := "var" 889 if gd.Tok == token.CONST { 890 kind = "const" 891 } 892 893 if len(vs.Names) > 1 { 894 // Check that none are exported except for the first. 895 for _, n := range vs.Names[1:] { 896 if ast.IsExported(n.Name) { 897 f.errorf(vs, 1, category("comments"), "exported %s %s should have its own declaration", kind, n.Name) 898 return 899 } 900 } 901 } 902 903 // Only one name. 904 name := vs.Names[0].Name 905 if !ast.IsExported(name) { 906 return 907 } 908 909 if vs.Doc == nil && gd.Doc == nil { 910 if genDeclMissingComments[gd] { 911 return 912 } 913 block := "" 914 if kind == "const" && gd.Lparen.IsValid() { 915 block = " (or a comment on this block)" 916 } 917 f.errorf(vs, 1, link(docCommentsLink), category("comments"), "exported %s %s should have comment%s or be unexported", kind, name, block) 918 genDeclMissingComments[gd] = true 919 return 920 } 921 // If this GenDecl has parens and a comment, we don't check its comment form. 922 if gd.Lparen.IsValid() && gd.Doc != nil { 923 return 924 } 925 // The relevant text to check will be on either vs.Doc or gd.Doc. 926 // Use vs.Doc preferentially. 927 doc := vs.Doc 928 if doc == nil { 929 doc = gd.Doc 930 } 931 prefix := name + " " 932 if !strings.HasPrefix(doc.Text(), prefix) { 933 f.errorf(doc, 1, link(docCommentsLink), category("comments"), `comment on exported %s %s should be of the form "%s..."`, kind, name, prefix) 934 } 935} 936 937func (f *file) checkStutter(id *ast.Ident, thing string) { 938 pkg, name := f.f.Name.Name, id.Name 939 if !ast.IsExported(name) { 940 // unexported name 941 return 942 } 943 // A name stutters if the package name is a strict prefix 944 // and the next character of the name starts a new word. 945 if len(name) <= len(pkg) { 946 // name is too short to stutter. 947 // This permits the name to be the same as the package name. 948 return 949 } 950 if !strings.EqualFold(pkg, name[:len(pkg)]) { 951 return 952 } 953 // We can assume the name is well-formed UTF-8. 954 // If the next rune after the package name is uppercase or an underscore 955 // the it's starting a new word and thus this name stutters. 956 rem := name[len(pkg):] 957 if next, _ := utf8.DecodeRuneInString(rem); next == '_' || unicode.IsUpper(next) { 958 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) 959 } 960} 961 962// zeroLiteral is a set of ast.BasicLit values that are zero values. 963// It is not exhaustive. 964var zeroLiteral = map[string]bool{ 965 "false": true, // bool 966 // runes 967 `'\x00'`: true, 968 `'\000'`: true, 969 // strings 970 `""`: true, 971 "``": true, 972 // numerics 973 "0": true, 974 "0.": true, 975 "0.0": true, 976 "0i": true, 977} 978 979// lintElses examines else blocks. It complains about any else block whose if block ends in a return. 980func (f *file) lintElses() { 981 // We don't want to flag if { } else if { } else { } constructions. 982 // They will appear as an IfStmt whose Else field is also an IfStmt. 983 // Record such a node so we ignore it when we visit it. 984 ignore := make(map[*ast.IfStmt]bool) 985 986 f.walk(func(node ast.Node) bool { 987 ifStmt, ok := node.(*ast.IfStmt) 988 if !ok || ifStmt.Else == nil { 989 return true 990 } 991 if elseif, ok := ifStmt.Else.(*ast.IfStmt); ok { 992 ignore[elseif] = true 993 return true 994 } 995 if ignore[ifStmt] { 996 return true 997 } 998 if _, ok := ifStmt.Else.(*ast.BlockStmt); !ok { 999 // only care about elses without conditions 1000 return true 1001 } 1002 if len(ifStmt.Body.List) == 0 { 1003 return true 1004 } 1005 shortDecl := false // does the if statement have a ":=" initialization statement? 1006 if ifStmt.Init != nil { 1007 if as, ok := ifStmt.Init.(*ast.AssignStmt); ok && as.Tok == token.DEFINE { 1008 shortDecl = true 1009 } 1010 } 1011 lastStmt := ifStmt.Body.List[len(ifStmt.Body.List)-1] 1012 if _, ok := lastStmt.(*ast.ReturnStmt); ok { 1013 extra := "" 1014 if shortDecl { 1015 extra = " (move short variable declaration to its own line if necessary)" 1016 } 1017 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) 1018 } 1019 return true 1020 }) 1021} 1022 1023// lintRanges examines range clauses. It complains about redundant constructions. 1024func (f *file) lintRanges() { 1025 f.walk(func(node ast.Node) bool { 1026 rs, ok := node.(*ast.RangeStmt) 1027 if !ok { 1028 return true 1029 } 1030 1031 if isIdent(rs.Key, "_") && (rs.Value == nil || isIdent(rs.Value, "_")) { 1032 p := f.errorf(rs.Key, 1, category("range-loop"), "should omit values from range; this loop is equivalent to `for range ...`") 1033 1034 newRS := *rs // shallow copy 1035 newRS.Value = nil 1036 newRS.Key = nil 1037 p.ReplacementLine = f.firstLineOf(&newRS, rs) 1038 1039 return true 1040 } 1041 1042 if isIdent(rs.Value, "_") { 1043 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) 1044 1045 newRS := *rs // shallow copy 1046 newRS.Value = nil 1047 p.ReplacementLine = f.firstLineOf(&newRS, rs) 1048 } 1049 1050 return true 1051 }) 1052} 1053 1054// lintErrorf examines errors.New and testing.Error calls. It complains if its only argument is an fmt.Sprintf invocation. 1055func (f *file) lintErrorf() { 1056 f.walk(func(node ast.Node) bool { 1057 ce, ok := node.(*ast.CallExpr) 1058 if !ok || len(ce.Args) != 1 { 1059 return true 1060 } 1061 isErrorsNew := isPkgDot(ce.Fun, "errors", "New") 1062 var isTestingError bool 1063 se, ok := ce.Fun.(*ast.SelectorExpr) 1064 if ok && se.Sel.Name == "Error" { 1065 if typ := f.pkg.typeOf(se.X); typ != nil { 1066 isTestingError = typ.String() == "*testing.T" 1067 } 1068 } 1069 if !isErrorsNew && !isTestingError { 1070 return true 1071 } 1072 if !f.imports("errors") { 1073 return true 1074 } 1075 arg := ce.Args[0] 1076 ce, ok = arg.(*ast.CallExpr) 1077 if !ok || !isPkgDot(ce.Fun, "fmt", "Sprintf") { 1078 return true 1079 } 1080 errorfPrefix := "fmt" 1081 if isTestingError { 1082 errorfPrefix = f.render(se.X) 1083 } 1084 p := f.errorf(node, 1, category("errors"), "should replace %s(fmt.Sprintf(...)) with %s.Errorf(...)", f.render(se), errorfPrefix) 1085 1086 m := f.srcLineWithMatch(ce, `^(.*)`+f.render(se)+`\(fmt\.Sprintf\((.*)\)\)(.*)$`) 1087 if m != nil { 1088 p.ReplacementLine = m[1] + errorfPrefix + ".Errorf(" + m[2] + ")" + m[3] 1089 } 1090 1091 return true 1092 }) 1093} 1094 1095// lintErrors examines global error vars. It complains if they aren't named in the standard way. 1096func (f *file) lintErrors() { 1097 for _, decl := range f.f.Decls { 1098 gd, ok := decl.(*ast.GenDecl) 1099 if !ok || gd.Tok != token.VAR { 1100 continue 1101 } 1102 for _, spec := range gd.Specs { 1103 spec := spec.(*ast.ValueSpec) 1104 if len(spec.Names) != 1 || len(spec.Values) != 1 { 1105 continue 1106 } 1107 ce, ok := spec.Values[0].(*ast.CallExpr) 1108 if !ok { 1109 continue 1110 } 1111 if !isPkgDot(ce.Fun, "errors", "New") && !isPkgDot(ce.Fun, "fmt", "Errorf") { 1112 continue 1113 } 1114 1115 id := spec.Names[0] 1116 prefix := "err" 1117 if id.IsExported() { 1118 prefix = "Err" 1119 } 1120 if !strings.HasPrefix(id.Name, prefix) { 1121 f.errorf(id, 0.9, category("naming"), "error var %s should have name of the form %sFoo", id.Name, prefix) 1122 } 1123 } 1124 } 1125} 1126 1127func lintErrorString(s string) (isClean bool, conf float64) { 1128 const basicConfidence = 0.8 1129 const capConfidence = basicConfidence - 0.2 1130 first, firstN := utf8.DecodeRuneInString(s) 1131 last, _ := utf8.DecodeLastRuneInString(s) 1132 if last == '.' || last == ':' || last == '!' || last == '\n' { 1133 return false, basicConfidence 1134 } 1135 if unicode.IsUpper(first) { 1136 // People use proper nouns and exported Go identifiers in error strings, 1137 // so decrease the confidence of warnings for capitalization. 1138 if len(s) <= firstN { 1139 return false, capConfidence 1140 } 1141 // Flag strings starting with something that doesn't look like an initialism. 1142 if second, _ := utf8.DecodeRuneInString(s[firstN:]); !unicode.IsUpper(second) { 1143 return false, capConfidence 1144 } 1145 } 1146 return true, 0 1147} 1148 1149// lintErrorStrings examines error strings. 1150// It complains if they are capitalized or end in punctuation or a newline. 1151func (f *file) lintErrorStrings() { 1152 f.walk(func(node ast.Node) bool { 1153 ce, ok := node.(*ast.CallExpr) 1154 if !ok { 1155 return true 1156 } 1157 if !isPkgDot(ce.Fun, "errors", "New") && !isPkgDot(ce.Fun, "fmt", "Errorf") { 1158 return true 1159 } 1160 if len(ce.Args) < 1 { 1161 return true 1162 } 1163 str, ok := ce.Args[0].(*ast.BasicLit) 1164 if !ok || str.Kind != token.STRING { 1165 return true 1166 } 1167 s, _ := strconv.Unquote(str.Value) // can assume well-formed Go 1168 if s == "" { 1169 return true 1170 } 1171 clean, conf := lintErrorString(s) 1172 if clean { 1173 return true 1174 } 1175 1176 f.errorf(str, conf, link(styleGuideBase+"#error-strings"), category("errors"), 1177 "error strings should not be capitalized or end with punctuation or a newline") 1178 return true 1179 }) 1180} 1181 1182// lintReceiverNames examines receiver names. It complains about inconsistent 1183// names used for the same type and names such as "this". 1184func (f *file) lintReceiverNames() { 1185 typeReceiver := map[string]string{} 1186 f.walk(func(n ast.Node) bool { 1187 fn, ok := n.(*ast.FuncDecl) 1188 if !ok || fn.Recv == nil || len(fn.Recv.List) == 0 { 1189 return true 1190 } 1191 names := fn.Recv.List[0].Names 1192 if len(names) < 1 { 1193 return true 1194 } 1195 name := names[0].Name 1196 const ref = styleGuideBase + "#receiver-names" 1197 if name == "_" { 1198 f.errorf(n, 1, link(ref), category("naming"), `receiver name should not be an underscore, omit the name if it is unused`) 1199 return true 1200 } 1201 if name == "this" || name == "self" { 1202 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"`) 1203 return true 1204 } 1205 recv := receiverType(fn) 1206 if prev, ok := typeReceiver[recv]; ok && prev != name { 1207 f.errorf(n, 1, link(ref), category("naming"), "receiver name %s should be consistent with previous receiver name %s for %s", name, prev, recv) 1208 return true 1209 } 1210 typeReceiver[recv] = name 1211 return true 1212 }) 1213} 1214 1215// lintIncDec examines statements that increment or decrement a variable. 1216// It complains if they don't use x++ or x--. 1217func (f *file) lintIncDec() { 1218 f.walk(func(n ast.Node) bool { 1219 as, ok := n.(*ast.AssignStmt) 1220 if !ok { 1221 return true 1222 } 1223 if len(as.Lhs) != 1 { 1224 return true 1225 } 1226 if !isOne(as.Rhs[0]) { 1227 return true 1228 } 1229 var suffix string 1230 switch as.Tok { 1231 case token.ADD_ASSIGN: 1232 suffix = "++" 1233 case token.SUB_ASSIGN: 1234 suffix = "--" 1235 default: 1236 return true 1237 } 1238 f.errorf(as, 0.8, category("unary-op"), "should replace %s with %s%s", f.render(as), f.render(as.Lhs[0]), suffix) 1239 return true 1240 }) 1241} 1242 1243// lintErrorReturn examines function declarations that return an error. 1244// It complains if the error isn't the last parameter. 1245func (f *file) lintErrorReturn() { 1246 f.walk(func(n ast.Node) bool { 1247 fn, ok := n.(*ast.FuncDecl) 1248 if !ok || fn.Type.Results == nil { 1249 return true 1250 } 1251 ret := fn.Type.Results.List 1252 if len(ret) <= 1 { 1253 return true 1254 } 1255 if isIdent(ret[len(ret)-1].Type, "error") { 1256 return true 1257 } 1258 // An error return parameter should be the last parameter. 1259 // Flag any error parameters found before the last. 1260 for _, r := range ret[:len(ret)-1] { 1261 if isIdent(r.Type, "error") { 1262 f.errorf(fn, 0.9, category("arg-order"), "error should be the last type when returning multiple items") 1263 break // only flag one 1264 } 1265 } 1266 return true 1267 }) 1268} 1269 1270// lintUnexportedReturn examines exported function declarations. 1271// It complains if any return an unexported type. 1272func (f *file) lintUnexportedReturn() { 1273 f.walk(func(n ast.Node) bool { 1274 fn, ok := n.(*ast.FuncDecl) 1275 if !ok { 1276 return true 1277 } 1278 if fn.Type.Results == nil { 1279 return false 1280 } 1281 if !fn.Name.IsExported() { 1282 return false 1283 } 1284 thing := "func" 1285 if fn.Recv != nil && len(fn.Recv.List) > 0 { 1286 thing = "method" 1287 if !ast.IsExported(receiverType(fn)) { 1288 // Don't report exported methods of unexported types, 1289 // such as private implementations of sort.Interface. 1290 return false 1291 } 1292 } 1293 for _, ret := range fn.Type.Results.List { 1294 typ := f.pkg.typeOf(ret.Type) 1295 if exportedType(typ) { 1296 continue 1297 } 1298 f.errorf(ret.Type, 0.8, category("unexported-type-in-api"), 1299 "exported %s %s returns unexported type %s, which can be annoying to use", 1300 thing, fn.Name.Name, typ) 1301 break // only flag one 1302 } 1303 return false 1304 }) 1305} 1306 1307// exportedType reports whether typ is an exported type. 1308// It is imprecise, and will err on the side of returning true, 1309// such as for composite types. 1310func exportedType(typ types.Type) bool { 1311 switch T := typ.(type) { 1312 case *types.Named: 1313 // Builtin types have no package. 1314 return T.Obj().Pkg() == nil || T.Obj().Exported() 1315 case *types.Map: 1316 return exportedType(T.Key()) && exportedType(T.Elem()) 1317 case interface { 1318 Elem() types.Type 1319 }: // array, slice, pointer, chan 1320 return exportedType(T.Elem()) 1321 } 1322 // Be conservative about other types, such as struct, interface, etc. 1323 return true 1324} 1325 1326// timeSuffixes is a list of name suffixes that imply a time unit. 1327// This is not an exhaustive list. 1328var timeSuffixes = []string{ 1329 "Sec", "Secs", "Seconds", 1330 "Msec", "Msecs", 1331 "Milli", "Millis", "Milliseconds", 1332 "Usec", "Usecs", "Microseconds", 1333 "MS", "Ms", 1334} 1335 1336func (f *file) lintTimeNames() { 1337 f.walk(func(node ast.Node) bool { 1338 v, ok := node.(*ast.ValueSpec) 1339 if !ok { 1340 return true 1341 } 1342 for _, name := range v.Names { 1343 origTyp := f.pkg.typeOf(name) 1344 // Look for time.Duration or *time.Duration; 1345 // the latter is common when using flag.Duration. 1346 typ := origTyp 1347 if pt, ok := typ.(*types.Pointer); ok { 1348 typ = pt.Elem() 1349 } 1350 if !f.pkg.isNamedType(typ, "time", "Duration") { 1351 continue 1352 } 1353 suffix := "" 1354 for _, suf := range timeSuffixes { 1355 if strings.HasSuffix(name.Name, suf) { 1356 suffix = suf 1357 break 1358 } 1359 } 1360 if suffix == "" { 1361 continue 1362 } 1363 f.errorf(v, 0.9, category("time"), "var %s is of type %v; don't use unit-specific suffix %q", name.Name, origTyp, suffix) 1364 } 1365 return true 1366 }) 1367} 1368 1369// lintContextKeyTypes checks for call expressions to context.WithValue with 1370// basic types used for the key argument. 1371// See: https://golang.org/issue/17293 1372func (f *file) lintContextKeyTypes() { 1373 f.walk(func(node ast.Node) bool { 1374 switch node := node.(type) { 1375 case *ast.CallExpr: 1376 f.checkContextKeyType(node) 1377 } 1378 1379 return true 1380 }) 1381} 1382 1383// checkContextKeyType reports an error if the call expression calls 1384// context.WithValue with a key argument of basic type. 1385func (f *file) checkContextKeyType(x *ast.CallExpr) { 1386 sel, ok := x.Fun.(*ast.SelectorExpr) 1387 if !ok { 1388 return 1389 } 1390 pkg, ok := sel.X.(*ast.Ident) 1391 if !ok || pkg.Name != "context" { 1392 return 1393 } 1394 if sel.Sel.Name != "WithValue" { 1395 return 1396 } 1397 1398 // key is second argument to context.WithValue 1399 if len(x.Args) != 3 { 1400 return 1401 } 1402 key := f.pkg.typesInfo.Types[x.Args[1]] 1403 1404 if ktyp, ok := key.Type.(*types.Basic); ok && ktyp.Kind() != types.Invalid { 1405 f.errorf(x, 1.0, category("context"), fmt.Sprintf("should not use basic type %s as key in context.WithValue", key.Type)) 1406 } 1407} 1408 1409// lintContextArgs examines function declarations that contain an 1410// argument with a type of context.Context 1411// It complains if that argument isn't the first parameter. 1412func (f *file) lintContextArgs() { 1413 f.walk(func(n ast.Node) bool { 1414 fn, ok := n.(*ast.FuncDecl) 1415 if !ok || len(fn.Type.Params.List) <= 1 { 1416 return true 1417 } 1418 // A context.Context should be the first parameter of a function. 1419 // Flag any that show up after the first. 1420 for _, arg := range fn.Type.Params.List[1:] { 1421 if isPkgDot(arg.Type, "context", "Context") { 1422 f.errorf(fn, 0.9, link("https://golang.org/pkg/context/"), category("arg-order"), "context.Context should be the first parameter of a function") 1423 break // only flag one 1424 } 1425 } 1426 return true 1427 }) 1428} 1429 1430// containsComments returns whether the interval [start, end) contains any 1431// comments without "// MATCH " prefix. 1432func (f *file) containsComments(start, end token.Pos) bool { 1433 for _, cgroup := range f.f.Comments { 1434 comments := cgroup.List 1435 if comments[0].Slash >= end { 1436 // All comments starting with this group are after end pos. 1437 return false 1438 } 1439 if comments[len(comments)-1].Slash < start { 1440 // Comments group ends before start pos. 1441 continue 1442 } 1443 for _, c := range comments { 1444 if start <= c.Slash && c.Slash < end && !strings.HasPrefix(c.Text, "// MATCH ") { 1445 return true 1446 } 1447 } 1448 } 1449 return false 1450} 1451 1452// receiverType returns the named type of the method receiver, sans "*", 1453// or "invalid-type" if fn.Recv is ill formed. 1454func receiverType(fn *ast.FuncDecl) string { 1455 switch e := fn.Recv.List[0].Type.(type) { 1456 case *ast.Ident: 1457 return e.Name 1458 case *ast.StarExpr: 1459 if id, ok := e.X.(*ast.Ident); ok { 1460 return id.Name 1461 } 1462 } 1463 // The parser accepts much more than just the legal forms. 1464 return "invalid-type" 1465} 1466 1467func (f *file) walk(fn func(ast.Node) bool) { 1468 ast.Walk(walker(fn), f.f) 1469} 1470 1471func (f *file) render(x interface{}) string { 1472 var buf bytes.Buffer 1473 if err := printer.Fprint(&buf, f.fset, x); err != nil { 1474 panic(err) 1475 } 1476 return buf.String() 1477} 1478 1479func (f *file) debugRender(x interface{}) string { 1480 var buf bytes.Buffer 1481 if err := ast.Fprint(&buf, f.fset, x, nil); err != nil { 1482 panic(err) 1483 } 1484 return buf.String() 1485} 1486 1487// walker adapts a function to satisfy the ast.Visitor interface. 1488// The function return whether the walk should proceed into the node's children. 1489type walker func(ast.Node) bool 1490 1491func (w walker) Visit(node ast.Node) ast.Visitor { 1492 if w(node) { 1493 return w 1494 } 1495 return nil 1496} 1497 1498func isIdent(expr ast.Expr, ident string) bool { 1499 id, ok := expr.(*ast.Ident) 1500 return ok && id.Name == ident 1501} 1502 1503// isBlank returns whether id is the blank identifier "_". 1504// If id == nil, the answer is false. 1505func isBlank(id *ast.Ident) bool { return id != nil && id.Name == "_" } 1506 1507func isPkgDot(expr ast.Expr, pkg, name string) bool { 1508 sel, ok := expr.(*ast.SelectorExpr) 1509 return ok && isIdent(sel.X, pkg) && isIdent(sel.Sel, name) 1510} 1511 1512func isOne(expr ast.Expr) bool { 1513 lit, ok := expr.(*ast.BasicLit) 1514 return ok && lit.Kind == token.INT && lit.Value == "1" 1515} 1516 1517func isCgoExported(f *ast.FuncDecl) bool { 1518 if f.Recv != nil || f.Doc == nil { 1519 return false 1520 } 1521 1522 cgoExport := regexp.MustCompile(fmt.Sprintf("(?m)^//export %s$", regexp.QuoteMeta(f.Name.Name))) 1523 for _, c := range f.Doc.List { 1524 if cgoExport.MatchString(c.Text) { 1525 return true 1526 } 1527 } 1528 return false 1529} 1530 1531var basicTypeKinds = map[types.BasicKind]string{ 1532 types.UntypedBool: "bool", 1533 types.UntypedInt: "int", 1534 types.UntypedRune: "rune", 1535 types.UntypedFloat: "float64", 1536 types.UntypedComplex: "complex128", 1537 types.UntypedString: "string", 1538} 1539 1540// isUntypedConst reports whether expr is an untyped constant, 1541// and indicates what its default type is. 1542// scope may be nil. 1543func (f *file) isUntypedConst(expr ast.Expr) (defType string, ok bool) { 1544 // Re-evaluate expr outside of its context to see if it's untyped. 1545 // (An expr evaluated within, for example, an assignment context will get the type of the LHS.) 1546 exprStr := f.render(expr) 1547 tv, err := types.Eval(f.fset, f.pkg.typesPkg, expr.Pos(), exprStr) 1548 if err != nil { 1549 return "", false 1550 } 1551 if b, ok := tv.Type.(*types.Basic); ok { 1552 if dt, ok := basicTypeKinds[b.Kind()]; ok { 1553 return dt, true 1554 } 1555 } 1556 1557 return "", false 1558} 1559 1560// firstLineOf renders the given node and returns its first line. 1561// It will also match the indentation of another node. 1562func (f *file) firstLineOf(node, match ast.Node) string { 1563 line := f.render(node) 1564 if i := strings.Index(line, "\n"); i >= 0 { 1565 line = line[:i] 1566 } 1567 return f.indentOf(match) + line 1568} 1569 1570func (f *file) indentOf(node ast.Node) string { 1571 line := srcLine(f.src, f.fset.Position(node.Pos())) 1572 for i, r := range line { 1573 switch r { 1574 case ' ', '\t': 1575 default: 1576 return line[:i] 1577 } 1578 } 1579 return line // unusual or empty line 1580} 1581 1582func (f *file) srcLineWithMatch(node ast.Node, pattern string) (m []string) { 1583 line := srcLine(f.src, f.fset.Position(node.Pos())) 1584 line = strings.TrimSuffix(line, "\n") 1585 rx := regexp.MustCompile(pattern) 1586 return rx.FindStringSubmatch(line) 1587} 1588 1589// imports returns true if the current file imports the specified package path. 1590func (f *file) imports(importPath string) bool { 1591 all := astutil.Imports(f.fset, f.f) 1592 for _, p := range all { 1593 for _, i := range p { 1594 uq, err := strconv.Unquote(i.Path.Value) 1595 if err == nil && importPath == uq { 1596 return true 1597 } 1598 } 1599 } 1600 return false 1601} 1602 1603// srcLine returns the complete line at p, including the terminating newline. 1604func srcLine(src []byte, p token.Position) string { 1605 // Run to end of line in both directions if not at line start/end. 1606 lo, hi := p.Offset, p.Offset+1 1607 for lo > 0 && src[lo-1] != '\n' { 1608 lo-- 1609 } 1610 for hi < len(src) && src[hi-1] != '\n' { 1611 hi++ 1612 } 1613 return string(src[lo:hi]) 1614} 1615