pikeman 0.0.1

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