aboutsummaryrefslogtreecommitdiff
path: root/vendor/golang.org/x/lint/lint.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/golang.org/x/lint/lint.go')
-rw-r--r--vendor/golang.org/x/lint/lint.go1614
1 files changed, 1614 insertions, 0 deletions
diff --git a/vendor/golang.org/x/lint/lint.go b/vendor/golang.org/x/lint/lint.go
new file mode 100644
index 0000000..532a75a
--- /dev/null
+++ b/vendor/golang.org/x/lint/lint.go
@@ -0,0 +1,1614 @@
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}