pikeman 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
+ }