1 // Copyright (c) 2013 The Go Authors. All rights reserved.
2 //
3 // Use of this source code is governed by a BSD-style
4 // license that can be found in the LICENSE file or at
5 // https://developers.google.com/open-source/licenses/bsd.
6 7 // Package lint contains a linter for Go source code.
8 package lint // import "golang.org/x/lint"
9 10 import (
11 "bufio"
12 "bytes"
13 "fmt"
14 "go/ast"
15 "go/parser"
16 "go/printer"
17 "go/token"
18 "go/types"
19 "regexp"
20 "sort"
21 "strconv"
22 "strings"
23 "unicode"
24 "unicode/utf8"
25 26 "golang.org/x/tools/go/ast/astutil"
27 "golang.org/x/tools/go/gcexportdata"
28 )
29 30 const styleGuideBase = "https://golang.org/wiki/CodeReviewComments"
31 32 // A Linter lints Go source code.
33 type Linter struct {
34 }
35 36 // Problem represents a problem in some source code.
37 type 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 50 func (p *Problem) String() string {
51 if p.Link != "" {
52 return p.Text + "\n\n" + p.Link
53 }
54 return p.Text
55 }
56 57 type byPosition []Problem
58 59 func (p byPosition) Len() int { return len(p) }
60 func (p byPosition) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
61 62 func (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.
79 func (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.
85 func (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 118 var (
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.
125 func 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.
137 type 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 152 func (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.
185 type file struct {
186 pkg *pkg
187 f *ast.File
188 fset *token.FileSet
189 src []byte
190 filename string
191 }
192 193 func (f *file) isTest() bool { return strings.HasSuffix(f.filename, "_test.go") }
194 195 func (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 215 type link string
216 type category string
217 218 // The variadic arguments may start with link and category types,
219 // and must end with a format string and any arguments.
220 // It returns the new Problem.
221 func (f *file) errorf(n ast.Node, confidence float64, args ...interface{}) *Problem {
222 pos := f.fset.Position(n.Pos())
223 if pos.Filename == "" {
224 pos.Filename = f.filename
225 }
226 return f.pkg.errorfAt(pos, confidence, args...)
227 }
228 229 func (p *pkg) errorfAt(pos token.Position, confidence float64, args ...interface{}) *Problem {
230 problem := Problem{
231 Position: pos,
232 Confidence: confidence,
233 }
234 if pos.Filename != "" {
235 // The file might not exist in our mapping if a //line directive was encountered.
236 if f, ok := p.files[pos.Filename]; ok {
237 problem.LineText = srcLine(f.src, pos)
238 }
239 }
240 241 argLoop:
242 for len(args) > 1 { // always leave at least the format string in args
243 switch v := args[0].(type) {
244 case link:
245 problem.Link = string(v)
246 case category:
247 problem.Category = string(v)
248 default:
249 break argLoop
250 }
251 args = args[1:]
252 }
253 254 problem.Text = fmt.Sprintf(args[0].(string), args[1:]...)
255 256 p.problems = append(p.problems, problem)
257 return &p.problems[len(p.problems)-1]
258 }
259 260 var newImporter = func(fset *token.FileSet) types.ImporterFrom {
261 return gcexportdata.NewImporter(fset, make(map[string]*types.Package))
262 }
263 264 func (p *pkg) typeCheck() error {
265 config := &types.Config{
266 // By setting a no-op error reporter, the type checker does as much work as possible.
267 Error: func(error) {},
268 Importer: newImporter(p.fset),
269 }
270 info := &types.Info{
271 Types: make(map[ast.Expr]types.TypeAndValue),
272 Defs: make(map[*ast.Ident]types.Object),
273 Uses: make(map[*ast.Ident]types.Object),
274 Scopes: make(map[ast.Node]*types.Scope),
275 }
276 var anyFile *file
277 var astFiles []*ast.File
278 for _, f := range p.files {
279 anyFile = f
280 astFiles = append(astFiles, f.f)
281 }
282 pkg, err := config.Check(anyFile.f.Name.Name, p.fset, astFiles, info)
283 // Remember the typechecking info, even if config.Check failed,
284 // since we will get partial information.
285 p.typesPkg = pkg
286 p.typesInfo = info
287 return err
288 }
289 290 func (p *pkg) typeOf(expr ast.Expr) types.Type {
291 if p.typesInfo == nil {
292 return nil
293 }
294 return p.typesInfo.TypeOf(expr)
295 }
296 297 func (p *pkg) isNamedType(typ types.Type, importPath, name string) bool {
298 n, ok := typ.(*types.Named)
299 if !ok {
300 return false
301 }
302 tn := n.Obj()
303 return tn != nil && tn.Pkg() != nil && tn.Pkg().Path() == importPath && tn.Name() == name
304 }
305 306 // scopeOf returns the tightest scope encompassing id.
307 func (p *pkg) scopeOf(id *ast.Ident) *types.Scope {
308 var scope *types.Scope
309 if obj := p.typesInfo.ObjectOf(id); obj != nil {
310 scope = obj.Parent()
311 }
312 if scope == p.typesPkg.Scope() {
313 // We were given a top-level identifier.
314 // Use the file-level scope instead of the package-level scope.
315 pos := id.Pos()
316 for _, f := range p.files {
317 if f.f.Pos() <= pos && pos < f.f.End() {
318 scope = p.typesInfo.Scopes[f.f]
319 break
320 }
321 }
322 }
323 return scope
324 }
325 326 func (p *pkg) scanSortable() {
327 p.sortable = make(map[string]bool)
328 329 // bitfield for which methods exist on each type.
330 const (
331 Len = 1 << iota
332 Less
333 Swap
334 )
335 nmap := map[string]int{"Len": Len, "Less": Less, "Swap": Swap}
336 has := make(map[string]int)
337 for _, f := range p.files {
338 f.walk(func(n ast.Node) bool {
339 fn, ok := n.(*ast.FuncDecl)
340 if !ok || fn.Recv == nil || len(fn.Recv.List) == 0 {
341 return true
342 }
343 // TODO(dsymonds): We could check the signature to be more precise.
344 recv := receiverType(fn)
345 if i, ok := nmap[fn.Name.Name]; ok {
346 has[recv] |= i
347 }
348 return false
349 })
350 }
351 for typ, ms := range has {
352 if ms == Len|Less|Swap {
353 p.sortable[typ] = true
354 }
355 }
356 }
357 358 func (p *pkg) isMain() bool {
359 for _, f := range p.files {
360 if f.isMain() {
361 return true
362 }
363 }
364 return false
365 }
366 367 func (f *file) isMain() bool {
368 if f.f.Name.Name == "main" {
369 return true
370 }
371 return false
372 }
373 374 // lintPackageComment checks package comments. It complains if
375 // there is no package comment, or if it is not of the right form.
376 // This has a notable false positive in that a package comment
377 // could rightfully appear in a different file of the same package,
378 // but that's not easy to fix since this linter is file-oriented.
379 func (f *file) lintPackageComment() {
380 if f.isTest() {
381 return
382 }
383 384 const ref = styleGuideBase + "#package-comments"
385 prefix := "Package " + f.f.Name.Name + " "
386 387 // Look for a detached package comment.
388 // First, scan for the last comment that occurs before the "package" keyword.
389 var lastCG *ast.CommentGroup
390 for _, cg := range f.f.Comments {
391 if cg.Pos() > f.f.Package {
392 // Gone past "package" keyword.
393 break
394 }
395 lastCG = cg
396 }
397 if lastCG != nil && strings.HasPrefix(lastCG.Text(), prefix) {
398 endPos := f.fset.Position(lastCG.End())
399 pkgPos := f.fset.Position(f.f.Package)
400 if endPos.Line+1 < pkgPos.Line {
401 // There isn't a great place to anchor this error;
402 // the start of the blank lines between the doc and the package statement
403 // is at least pointing at the location of the problem.
404 pos := token.Position{
405 Filename: endPos.Filename,
406 // Offset not set; it is non-trivial, and doesn't appear to be needed.
407 Line: endPos.Line + 1,
408 Column: 1,
409 }
410 f.pkg.errorfAt(pos, 0.9, link(ref), category("comments"), "package comment is detached; there should be no blank lines between it and the package statement")
411 return
412 }
413 }
414 415 if f.f.Doc == nil {
416 f.errorf(f.f, 0.2, link(ref), category("comments"), "should have a package comment, unless it's in another file for this package")
417 return
418 }
419 s := f.f.Doc.Text()
420 if ts := strings.TrimLeft(s, " \t"); ts != s {
421 f.errorf(f.f.Doc, 1, link(ref), category("comments"), "package comment should not have leading space")
422 s = ts
423 }
424 // Only non-main packages need to keep to this form.
425 if !f.pkg.main && !strings.HasPrefix(s, prefix) {
426 f.errorf(f.f.Doc, 1, link(ref), category("comments"), `package comment should be of the form "%s..."`, prefix)
427 }
428 }
429 430 // lintBlankImports complains if a non-main package has blank imports that are
431 // not documented.
432 func (f *file) lintBlankImports() {
433 // In package main and in tests, we don't complain about blank imports.
434 if f.pkg.main || f.isTest() {
435 return
436 }
437 438 // The first element of each contiguous group of blank imports should have
439 // an explanatory comment of some kind.
440 for i, imp := range f.f.Imports {
441 pos := f.fset.Position(imp.Pos())
442 443 if !isBlank(imp.Name) {
444 continue // Ignore non-blank imports.
445 }
446 if i > 0 {
447 prev := f.f.Imports[i-1]
448 prevPos := f.fset.Position(prev.Pos())
449 if isBlank(prev.Name) && prevPos.Line+1 == pos.Line {
450 continue // A subsequent blank in a group.
451 }
452 }
453 454 // This is the first blank import of a group.
455 if imp.Doc == nil && imp.Comment == nil {
456 ref := ""
457 f.errorf(imp, 1, link(ref), category("imports"), "a blank import should be only in a main or test package, or have a comment justifying it")
458 }
459 }
460 }
461 462 // lintImports examines import blocks.
463 func (f *file) lintImports() {
464 for i, is := range f.f.Imports {
465 _ = i
466 if is.Name != nil && is.Name.Name == "." && !f.isTest() {
467 f.errorf(is, 1, link(styleGuideBase+"#import-dot"), category("imports"), "should not use dot imports")
468 }
469 470 }
471 }
472 473 const docCommentsLink = styleGuideBase + "#doc-comments"
474 475 // lintExported examines the exported names.
476 // It complains if any required doc comments are missing,
477 // or if they are not of the right form. The exact rules are in
478 // lintFuncDoc, lintTypeDoc and lintValueSpecDoc; this function
479 // also tracks the GenDecl structure being traversed to permit
480 // doc comments for constants to be on top of the const block.
481 // It also complains if the names stutter when combined with
482 // the package name.
483 func (f *file) lintExported() {
484 if f.isTest() {
485 return
486 }
487 488 var lastGen *ast.GenDecl // last GenDecl entered.
489 490 // Set of GenDecls that have already had missing comments flagged.
491 genDeclMissingComments := make(map[*ast.GenDecl]bool)
492 493 f.walk(func(node ast.Node) bool {
494 switch v := node.(type) {
495 case *ast.GenDecl:
496 if v.Tok == token.IMPORT {
497 return false
498 }
499 // token.CONST, token.TYPE or token.VAR
500 lastGen = v
501 return true
502 case *ast.FuncDecl:
503 f.lintFuncDoc(v)
504 if v.Recv == nil {
505 // Only check for stutter on functions, not methods.
506 // Method names are not used package-qualified.
507 f.checkStutter(v.Name, "func")
508 }
509 // Don't proceed inside funcs.
510 return false
511 case *ast.TypeSpec:
512 // inside a GenDecl, which usually has the doc
513 doc := v.Doc
514 if doc == nil {
515 doc = lastGen.Doc
516 }
517 f.lintTypeDoc(v, doc)
518 f.checkStutter(v.Name, "type")
519 // Don't proceed inside types.
520 return false
521 case *ast.ValueSpec:
522 f.lintValueSpecDoc(v, lastGen, genDeclMissingComments)
523 return false
524 }
525 return true
526 })
527 }
528 529 var (
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.
537 var knownNameExceptions = map[string]bool{
538 "LastInsertId": true, // must match database/sql
539 "kWh": true,
540 }
541 542 func 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.
556 func (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.
700 func 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.
770 var 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.
814 func (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 836 var 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).
848 func (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).
888 func (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 938 func (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.
965 var 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.
981 func (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.
1025 func (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.
1056 func (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.
1097 func (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 1128 func 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.
1152 func (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".
1185 func (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--.
1218 func (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.
1246 func (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.
1273 func (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.
1311 func 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.
1329 var timeSuffixes = []string{
1330 "Sec", "Secs", "Seconds",
1331 "Msec", "Msecs",
1332 "Milli", "Millis", "Milliseconds",
1333 "Usec", "Usecs", "Microseconds",
1334 "MS", "Ms",
1335 }
1336 1337 func (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
1373 func (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.
1386 func (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.
1413 func (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.
1433 func (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.
1455 func 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 1468 func (f *file) walk(fn func(ast.Node) bool) {
1469 ast.Walk(walker(fn), f.f)
1470 }
1471 1472 func (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 1480 func (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.
1490 type walker func(ast.Node) bool
1491 1492 func (w walker) Visit(node ast.Node) ast.Visitor {
1493 if w(node) {
1494 return w
1495 }
1496 return nil
1497 }
1498 1499 func 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.
1506 func isBlank(id *ast.Ident) bool { return id != nil && id.Name == "_" }
1507 1508 func 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 1513 func isOne(expr ast.Expr) bool {
1514 lit, ok := expr.(*ast.BasicLit)
1515 return ok && lit.Kind == token.INT && lit.Value == "1"
1516 }
1517 1518 func 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 1532 var 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.
1544 func (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.
1563 func (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 1571 func (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 1583 func (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.
1591 func (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.
1605 func 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