├── .travis.yml ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── go.mod ├── go.sum ├── golint ├── golint.go ├── import.go └── importcomment.go ├── lint.go ├── lint_test.go ├── misc ├── emacs │ └── golint.el └── vim │ └── ftplugin │ └── go │ └── lint.vim └── testdata ├── 4.go ├── 5_test.go ├── blank-import-lib.go ├── blank-import-lib_test.go ├── blank-import-main.go ├── broken.go ├── common-methods.go ├── const-block.go ├── context.go ├── contextkeytypes.go ├── else-multi.go ├── else.go ├── error-return.go ├── errorf-custom.go ├── errorf.go ├── errors.go ├── import-dot.go ├── inc.go ├── names.go ├── pkg-caps.go ├── pkg-doc1.go ├── pkg-doc2.go ├── pkg-doc3.go ├── pkg-doc4.go ├── pkg-doc5.go ├── pkg-main.go ├── range.go ├── receiver-names.go ├── sort.go ├── stutter.go ├── time.go └── unexp-return.go /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: go 3 | go: 4 | - 1.10.x 5 | - 1.11.x 6 | - master 7 | 8 | go_import_path: golang.org/x/lint 9 | 10 | install: 11 | - go get -t -v ./... 12 | 13 | script: 14 | - go test -v -race ./... 15 | 16 | matrix: 17 | allow_failures: 18 | - go: master 19 | fast_finish: true 20 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Golint 2 | 3 | ## Before filing an issue: 4 | 5 | ### Are you having trouble building golint? 6 | 7 | Check you have the latest version of its dependencies. Run 8 | ``` 9 | go get -u golang.org/x/lint/golint 10 | ``` 11 | If you still have problems, consider searching for existing issues before filing a new issue. 12 | 13 | ## Before sending a pull request: 14 | 15 | Have you understood the purpose of golint? Make sure to carefully read `README`. 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 The Go Authors. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of Google Inc. nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **NOTE:** Golint is [deprecated and frozen](https://github.com/golang/go/issues/38968). 2 | There's no drop-in replacement for it, but tools such as [Staticcheck](https://staticcheck.io/) 3 | and `go vet` should be used instead. 4 | 5 | Golint is a linter for Go source code. 6 | 7 | [![Go Reference](https://pkg.go.dev/badge/golang.org/x/lint.svg)](https://pkg.go.dev/golang.org/x/lint) 8 | [![Build Status](https://travis-ci.org/golang/lint.svg?branch=master)](https://travis-ci.org/golang/lint) 9 | 10 | ## Installation 11 | 12 | Golint requires a 13 | [supported release of Go](https://golang.org/doc/devel/release.html#policy). 14 | 15 | go get -u golang.org/x/lint/golint 16 | 17 | To find out where `golint` was installed you can run `go list -f {{.Target}} golang.org/x/lint/golint`. For `golint` to be used globally add that directory to the `$PATH` environment setting. 18 | 19 | ## Usage 20 | 21 | Invoke `golint` with one or more filenames, directories, or packages named 22 | by its import path. Golint uses the same 23 | [import path syntax](https://golang.org/cmd/go/#hdr-Import_path_syntax) as 24 | the `go` command and therefore 25 | also supports relative import paths like `./...`. Additionally the `...` 26 | wildcard can be used as suffix on relative and absolute file paths to recurse 27 | into them. 28 | 29 | The output of this tool is a list of suggestions in Vim quickfix format, 30 | which is accepted by lots of different editors. 31 | 32 | ## Purpose 33 | 34 | Golint differs from gofmt. Gofmt reformats Go source code, whereas 35 | golint prints out style mistakes. 36 | 37 | Golint differs from govet. Govet is concerned with correctness, whereas 38 | golint is concerned with coding style. Golint is in use at Google, and it 39 | seeks to match the accepted style of the open source Go project. 40 | 41 | The suggestions made by golint are exactly that: suggestions. 42 | Golint is not perfect, and has both false positives and false negatives. 43 | Do not treat its output as a gold standard. We will not be adding pragmas 44 | or other knobs to suppress specific warnings, so do not expect or require 45 | code to be completely "lint-free". 46 | In short, this tool is not, and will never be, trustworthy enough for its 47 | suggestions to be enforced automatically, for example as part of a build process. 48 | Golint makes suggestions for many of the mechanically checkable items listed in 49 | [Effective Go](https://golang.org/doc/effective_go.html) and the 50 | [CodeReviewComments wiki page](https://golang.org/wiki/CodeReviewComments). 51 | 52 | ## Scope 53 | 54 | Golint is meant to carry out the stylistic conventions put forth in 55 | [Effective Go](https://golang.org/doc/effective_go.html) and 56 | [CodeReviewComments](https://golang.org/wiki/CodeReviewComments). 57 | Changes that are not aligned with those documents will not be considered. 58 | 59 | ## Contributions 60 | 61 | Contributions to this project are welcome provided they are [in scope](#scope), 62 | though please send mail before starting work on anything major. 63 | Contributors retain their copyright, so we need you to fill out 64 | [a short form](https://developers.google.com/open-source/cla/individual) 65 | before we can accept your contribution. 66 | 67 | ## Vim 68 | 69 | Add this to your ~/.vimrc: 70 | 71 | set rtp+=$GOPATH/src/golang.org/x/lint/misc/vim 72 | 73 | If you have multiple entries in your GOPATH, replace `$GOPATH` with the right value. 74 | 75 | Running `:Lint` will run golint on the current file and populate the quickfix list. 76 | 77 | Optionally, add this to your `~/.vimrc` to automatically run `golint` on `:w` 78 | 79 | autocmd BufWritePost,FileWritePost *.go execute 'Lint' | cwindow 80 | 81 | 82 | ## Emacs 83 | 84 | Add this to your `.emacs` file: 85 | 86 | (add-to-list 'load-path (concat (getenv "GOPATH") "/src/golang.org/x/lint/misc/emacs/")) 87 | (require 'golint) 88 | 89 | If you have multiple entries in your GOPATH, replace `$GOPATH` with the right value. 90 | 91 | Running M-x golint will run golint on the current file. 92 | 93 | For more usage, see [Compilation-Mode](http://www.gnu.org/software/emacs/manual/html_node/emacs/Compilation-Mode.html). 94 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module golang.org/x/lint 2 | 3 | go 1.11 4 | 5 | require golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7 6 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 2 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 3 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 4 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 5 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 6 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 7 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 8 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 9 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 10 | golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7 h1:EBZoQjiKKPaLbPrbpssUfuHtwM6KV/vb4U85g/cigFY= 11 | golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 12 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 13 | -------------------------------------------------------------------------------- /golint/golint.go: -------------------------------------------------------------------------------- 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 | // golint lints the Go source files named on its command line. 8 | package main 9 | 10 | import ( 11 | "flag" 12 | "fmt" 13 | "go/build" 14 | "io/ioutil" 15 | "os" 16 | "path/filepath" 17 | "strings" 18 | 19 | "golang.org/x/lint" 20 | ) 21 | 22 | var ( 23 | minConfidence = flag.Float64("min_confidence", 0.8, "minimum confidence of a problem to print it") 24 | setExitStatus = flag.Bool("set_exit_status", false, "set exit status to 1 if any issues are found") 25 | suggestions int 26 | ) 27 | 28 | func usage() { 29 | fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0]) 30 | fmt.Fprintf(os.Stderr, "\tgolint [flags] # runs on package in current directory\n") 31 | fmt.Fprintf(os.Stderr, "\tgolint [flags] [packages]\n") 32 | fmt.Fprintf(os.Stderr, "\tgolint [flags] [directories] # where a '/...' suffix includes all sub-directories\n") 33 | fmt.Fprintf(os.Stderr, "\tgolint [flags] [files] # all must belong to a single package\n") 34 | fmt.Fprintf(os.Stderr, "Flags:\n") 35 | flag.PrintDefaults() 36 | } 37 | 38 | func main() { 39 | flag.Usage = usage 40 | flag.Parse() 41 | 42 | if flag.NArg() == 0 { 43 | lintDir(".") 44 | } else { 45 | // dirsRun, filesRun, and pkgsRun indicate whether golint is applied to 46 | // directory, file or package targets. The distinction affects which 47 | // checks are run. It is no valid to mix target types. 48 | var dirsRun, filesRun, pkgsRun int 49 | var args []string 50 | for _, arg := range flag.Args() { 51 | if strings.HasSuffix(arg, "/...") && isDir(arg[:len(arg)-len("/...")]) { 52 | dirsRun = 1 53 | for _, dirname := range allPackagesInFS(arg) { 54 | args = append(args, dirname) 55 | } 56 | } else if isDir(arg) { 57 | dirsRun = 1 58 | args = append(args, arg) 59 | } else if exists(arg) { 60 | filesRun = 1 61 | args = append(args, arg) 62 | } else { 63 | pkgsRun = 1 64 | args = append(args, arg) 65 | } 66 | } 67 | 68 | if dirsRun+filesRun+pkgsRun != 1 { 69 | usage() 70 | os.Exit(2) 71 | } 72 | switch { 73 | case dirsRun == 1: 74 | for _, dir := range args { 75 | lintDir(dir) 76 | } 77 | case filesRun == 1: 78 | lintFiles(args...) 79 | case pkgsRun == 1: 80 | for _, pkg := range importPaths(args) { 81 | lintPackage(pkg) 82 | } 83 | } 84 | } 85 | 86 | if *setExitStatus && suggestions > 0 { 87 | fmt.Fprintf(os.Stderr, "Found %d lint suggestions; failing.\n", suggestions) 88 | os.Exit(1) 89 | } 90 | } 91 | 92 | func isDir(filename string) bool { 93 | fi, err := os.Stat(filename) 94 | return err == nil && fi.IsDir() 95 | } 96 | 97 | func exists(filename string) bool { 98 | _, err := os.Stat(filename) 99 | return err == nil 100 | } 101 | 102 | func lintFiles(filenames ...string) { 103 | files := make(map[string][]byte) 104 | for _, filename := range filenames { 105 | src, err := ioutil.ReadFile(filename) 106 | if err != nil { 107 | fmt.Fprintln(os.Stderr, err) 108 | continue 109 | } 110 | files[filename] = src 111 | } 112 | 113 | l := new(lint.Linter) 114 | ps, err := l.LintFiles(files) 115 | if err != nil { 116 | fmt.Fprintf(os.Stderr, "%v\n", err) 117 | return 118 | } 119 | for _, p := range ps { 120 | if p.Confidence >= *minConfidence { 121 | fmt.Printf("%v: %s\n", p.Position, p.Text) 122 | suggestions++ 123 | } 124 | } 125 | } 126 | 127 | func lintDir(dirname string) { 128 | pkg, err := build.ImportDir(dirname, 0) 129 | lintImportedPackage(pkg, err) 130 | } 131 | 132 | func lintPackage(pkgname string) { 133 | pkg, err := build.Import(pkgname, ".", 0) 134 | lintImportedPackage(pkg, err) 135 | } 136 | 137 | func lintImportedPackage(pkg *build.Package, err error) { 138 | if err != nil { 139 | if _, nogo := err.(*build.NoGoError); nogo { 140 | // Don't complain if the failure is due to no Go source files. 141 | return 142 | } 143 | fmt.Fprintln(os.Stderr, err) 144 | return 145 | } 146 | 147 | var files []string 148 | files = append(files, pkg.GoFiles...) 149 | files = append(files, pkg.CgoFiles...) 150 | files = append(files, pkg.TestGoFiles...) 151 | if pkg.Dir != "." { 152 | for i, f := range files { 153 | files[i] = filepath.Join(pkg.Dir, f) 154 | } 155 | } 156 | // TODO(dsymonds): Do foo_test too (pkg.XTestGoFiles) 157 | 158 | lintFiles(files...) 159 | } 160 | -------------------------------------------------------------------------------- /golint/import.go: -------------------------------------------------------------------------------- 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 | } 310 | -------------------------------------------------------------------------------- /golint/importcomment.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 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 | // +build go1.12 8 | 9 | // Require use of the correct import path only for Go 1.12+ users, so 10 | // any breakages coincide with people updating their CI configs or 11 | // whatnot. 12 | 13 | package main // import "golang.org/x/lint/golint" 14 | -------------------------------------------------------------------------------- /lint.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013 The Go Authors. All rights reserved. 2 | // 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file or at 5 | // https://developers.google.com/open-source/licenses/bsd. 6 | 7 | // Package lint contains a linter for Go source code. 8 | package lint // import "golang.org/x/lint" 9 | 10 | import ( 11 | "bufio" 12 | "bytes" 13 | "fmt" 14 | "go/ast" 15 | "go/parser" 16 | "go/printer" 17 | "go/token" 18 | "go/types" 19 | "regexp" 20 | "sort" 21 | "strconv" 22 | "strings" 23 | "unicode" 24 | "unicode/utf8" 25 | 26 | "golang.org/x/tools/go/ast/astutil" 27 | "golang.org/x/tools/go/gcexportdata" 28 | ) 29 | 30 | const styleGuideBase = "https://golang.org/wiki/CodeReviewComments" 31 | 32 | // A Linter lints Go source code. 33 | type Linter struct { 34 | } 35 | 36 | // Problem represents a problem in some source code. 37 | type Problem struct { 38 | Position token.Position // position in source file 39 | Text string // the prose that describes the problem 40 | Link string // (optional) the link to the style guide for the problem 41 | Confidence float64 // a value in (0,1] estimating the confidence in this problem's correctness 42 | LineText string // the source line 43 | Category string // a short name for the general category of the problem 44 | 45 | // If the problem has a suggested fix (the minority case), 46 | // ReplacementLine is a full replacement for the relevant line of the source file. 47 | ReplacementLine string 48 | } 49 | 50 | func (p *Problem) String() string { 51 | if p.Link != "" { 52 | return p.Text + "\n\n" + p.Link 53 | } 54 | return p.Text 55 | } 56 | 57 | type byPosition []Problem 58 | 59 | func (p byPosition) Len() int { return len(p) } 60 | func (p byPosition) Swap(i, j int) { p[i], p[j] = p[j], p[i] } 61 | 62 | func (p byPosition) Less(i, j int) bool { 63 | pi, pj := p[i].Position, p[j].Position 64 | 65 | if pi.Filename != pj.Filename { 66 | return pi.Filename < pj.Filename 67 | } 68 | if pi.Line != pj.Line { 69 | return pi.Line < pj.Line 70 | } 71 | if pi.Column != pj.Column { 72 | return pi.Column < pj.Column 73 | } 74 | 75 | return p[i].Text < p[j].Text 76 | } 77 | 78 | // Lint lints src. 79 | func (l *Linter) Lint(filename string, src []byte) ([]Problem, error) { 80 | return l.LintFiles(map[string][]byte{filename: src}) 81 | } 82 | 83 | // LintFiles lints a set of files of a single package. 84 | // The argument is a map of filename to source. 85 | func (l *Linter) LintFiles(files map[string][]byte) ([]Problem, error) { 86 | pkg := &pkg{ 87 | fset: token.NewFileSet(), 88 | files: make(map[string]*file), 89 | } 90 | var pkgName string 91 | for filename, src := range files { 92 | if isGenerated(src) { 93 | continue // See issue #239 94 | } 95 | f, err := parser.ParseFile(pkg.fset, filename, src, parser.ParseComments) 96 | if err != nil { 97 | return nil, err 98 | } 99 | if pkgName == "" { 100 | pkgName = f.Name.Name 101 | } else if f.Name.Name != pkgName { 102 | return nil, fmt.Errorf("%s is in package %s, not %s", filename, f.Name.Name, pkgName) 103 | } 104 | pkg.files[filename] = &file{ 105 | pkg: pkg, 106 | f: f, 107 | fset: pkg.fset, 108 | src: src, 109 | filename: filename, 110 | } 111 | } 112 | if len(pkg.files) == 0 { 113 | return nil, nil 114 | } 115 | return pkg.lint(), nil 116 | } 117 | 118 | var ( 119 | genHdr = []byte("// Code generated ") 120 | genFtr = []byte(" DO NOT EDIT.") 121 | ) 122 | 123 | // isGenerated reports whether the source file is generated code 124 | // according the rules from https://golang.org/s/generatedcode. 125 | func isGenerated(src []byte) bool { 126 | sc := bufio.NewScanner(bytes.NewReader(src)) 127 | for sc.Scan() { 128 | b := sc.Bytes() 129 | if bytes.HasPrefix(b, genHdr) && bytes.HasSuffix(b, genFtr) && len(b) >= len(genHdr)+len(genFtr) { 130 | return true 131 | } 132 | } 133 | return false 134 | } 135 | 136 | // pkg represents a package being linted. 137 | type pkg struct { 138 | fset *token.FileSet 139 | files map[string]*file 140 | 141 | typesPkg *types.Package 142 | typesInfo *types.Info 143 | 144 | // sortable is the set of types in the package that implement sort.Interface. 145 | sortable map[string]bool 146 | // main is whether this is a "main" package. 147 | main bool 148 | 149 | problems []Problem 150 | } 151 | 152 | func (p *pkg) lint() []Problem { 153 | if err := p.typeCheck(); err != nil { 154 | /* TODO(dsymonds): Consider reporting these errors when golint operates on entire packages. 155 | if e, ok := err.(types.Error); ok { 156 | pos := p.fset.Position(e.Pos) 157 | conf := 1.0 158 | if strings.Contains(e.Msg, "can't find import: ") { 159 | // Golint is probably being run in a context that doesn't support 160 | // typechecking (e.g. package files aren't found), so don't warn about it. 161 | conf = 0 162 | } 163 | if conf > 0 { 164 | p.errorfAt(pos, conf, category("typechecking"), e.Msg) 165 | } 166 | 167 | // TODO(dsymonds): Abort if !e.Soft? 168 | } 169 | */ 170 | } 171 | 172 | p.scanSortable() 173 | p.main = p.isMain() 174 | 175 | for _, f := range p.files { 176 | f.lint() 177 | } 178 | 179 | sort.Sort(byPosition(p.problems)) 180 | 181 | return p.problems 182 | } 183 | 184 | // file represents a file being linted. 185 | type file struct { 186 | pkg *pkg 187 | f *ast.File 188 | fset *token.FileSet 189 | src []byte 190 | filename string 191 | } 192 | 193 | func (f *file) isTest() bool { return strings.HasSuffix(f.filename, "_test.go") } 194 | 195 | func (f *file) lint() { 196 | f.lintPackageComment() 197 | f.lintImports() 198 | f.lintBlankImports() 199 | f.lintExported() 200 | f.lintNames() 201 | f.lintElses() 202 | f.lintRanges() 203 | f.lintErrorf() 204 | f.lintErrors() 205 | f.lintErrorStrings() 206 | f.lintReceiverNames() 207 | f.lintIncDec() 208 | f.lintErrorReturn() 209 | f.lintUnexportedReturn() 210 | f.lintTimeNames() 211 | f.lintContextKeyTypes() 212 | f.lintContextArgs() 213 | } 214 | 215 | type link string 216 | type category string 217 | 218 | // The variadic arguments may start with link and category types, 219 | // and must end with a format string and any arguments. 220 | // It returns the new Problem. 221 | func (f *file) errorf(n ast.Node, confidence float64, args ...interface{}) *Problem { 222 | pos := f.fset.Position(n.Pos()) 223 | if pos.Filename == "" { 224 | pos.Filename = f.filename 225 | } 226 | return f.pkg.errorfAt(pos, confidence, args...) 227 | } 228 | 229 | func (p *pkg) errorfAt(pos token.Position, confidence float64, args ...interface{}) *Problem { 230 | problem := Problem{ 231 | Position: pos, 232 | Confidence: confidence, 233 | } 234 | if pos.Filename != "" { 235 | // The file might not exist in our mapping if a //line directive was encountered. 236 | if f, ok := p.files[pos.Filename]; ok { 237 | problem.LineText = srcLine(f.src, pos) 238 | } 239 | } 240 | 241 | argLoop: 242 | for len(args) > 1 { // always leave at least the format string in args 243 | switch v := args[0].(type) { 244 | case link: 245 | problem.Link = string(v) 246 | case category: 247 | problem.Category = string(v) 248 | default: 249 | break argLoop 250 | } 251 | args = args[1:] 252 | } 253 | 254 | problem.Text = fmt.Sprintf(args[0].(string), args[1:]...) 255 | 256 | p.problems = append(p.problems, problem) 257 | return &p.problems[len(p.problems)-1] 258 | } 259 | 260 | var newImporter = func(fset *token.FileSet) types.ImporterFrom { 261 | return gcexportdata.NewImporter(fset, make(map[string]*types.Package)) 262 | } 263 | 264 | func (p *pkg) typeCheck() error { 265 | config := &types.Config{ 266 | // By setting a no-op error reporter, the type checker does as much work as possible. 267 | Error: func(error) {}, 268 | Importer: newImporter(p.fset), 269 | } 270 | info := &types.Info{ 271 | Types: make(map[ast.Expr]types.TypeAndValue), 272 | Defs: make(map[*ast.Ident]types.Object), 273 | Uses: make(map[*ast.Ident]types.Object), 274 | Scopes: make(map[ast.Node]*types.Scope), 275 | } 276 | var anyFile *file 277 | var astFiles []*ast.File 278 | for _, f := range p.files { 279 | anyFile = f 280 | astFiles = append(astFiles, f.f) 281 | } 282 | pkg, err := config.Check(anyFile.f.Name.Name, p.fset, astFiles, info) 283 | // Remember the typechecking info, even if config.Check failed, 284 | // since we will get partial information. 285 | p.typesPkg = pkg 286 | p.typesInfo = info 287 | return err 288 | } 289 | 290 | func (p *pkg) typeOf(expr ast.Expr) types.Type { 291 | if p.typesInfo == nil { 292 | return nil 293 | } 294 | return p.typesInfo.TypeOf(expr) 295 | } 296 | 297 | func (p *pkg) isNamedType(typ types.Type, importPath, name string) bool { 298 | n, ok := typ.(*types.Named) 299 | if !ok { 300 | return false 301 | } 302 | tn := n.Obj() 303 | return tn != nil && tn.Pkg() != nil && tn.Pkg().Path() == importPath && tn.Name() == name 304 | } 305 | 306 | // scopeOf returns the tightest scope encompassing id. 307 | func (p *pkg) scopeOf(id *ast.Ident) *types.Scope { 308 | var scope *types.Scope 309 | if obj := p.typesInfo.ObjectOf(id); obj != nil { 310 | scope = obj.Parent() 311 | } 312 | if scope == p.typesPkg.Scope() { 313 | // We were given a top-level identifier. 314 | // Use the file-level scope instead of the package-level scope. 315 | pos := id.Pos() 316 | for _, f := range p.files { 317 | if f.f.Pos() <= pos && pos < f.f.End() { 318 | scope = p.typesInfo.Scopes[f.f] 319 | break 320 | } 321 | } 322 | } 323 | return scope 324 | } 325 | 326 | func (p *pkg) scanSortable() { 327 | p.sortable = make(map[string]bool) 328 | 329 | // bitfield for which methods exist on each type. 330 | const ( 331 | Len = 1 << iota 332 | Less 333 | Swap 334 | ) 335 | nmap := map[string]int{"Len": Len, "Less": Less, "Swap": Swap} 336 | has := make(map[string]int) 337 | for _, f := range p.files { 338 | f.walk(func(n ast.Node) bool { 339 | fn, ok := n.(*ast.FuncDecl) 340 | if !ok || fn.Recv == nil || len(fn.Recv.List) == 0 { 341 | return true 342 | } 343 | // TODO(dsymonds): We could check the signature to be more precise. 344 | recv := receiverType(fn) 345 | if i, ok := nmap[fn.Name.Name]; ok { 346 | has[recv] |= i 347 | } 348 | return false 349 | }) 350 | } 351 | for typ, ms := range has { 352 | if ms == Len|Less|Swap { 353 | p.sortable[typ] = true 354 | } 355 | } 356 | } 357 | 358 | func (p *pkg) isMain() bool { 359 | for _, f := range p.files { 360 | if f.isMain() { 361 | return true 362 | } 363 | } 364 | return false 365 | } 366 | 367 | func (f *file) isMain() bool { 368 | if f.f.Name.Name == "main" { 369 | return true 370 | } 371 | return false 372 | } 373 | 374 | // lintPackageComment checks package comments. It complains if 375 | // there is no package comment, or if it is not of the right form. 376 | // This has a notable false positive in that a package comment 377 | // could rightfully appear in a different file of the same package, 378 | // but that's not easy to fix since this linter is file-oriented. 379 | func (f *file) lintPackageComment() { 380 | if f.isTest() { 381 | return 382 | } 383 | 384 | const ref = styleGuideBase + "#package-comments" 385 | prefix := "Package " + f.f.Name.Name + " " 386 | 387 | // Look for a detached package comment. 388 | // First, scan for the last comment that occurs before the "package" keyword. 389 | var lastCG *ast.CommentGroup 390 | for _, cg := range f.f.Comments { 391 | if cg.Pos() > f.f.Package { 392 | // Gone past "package" keyword. 393 | break 394 | } 395 | lastCG = cg 396 | } 397 | if lastCG != nil && strings.HasPrefix(lastCG.Text(), prefix) { 398 | endPos := f.fset.Position(lastCG.End()) 399 | pkgPos := f.fset.Position(f.f.Package) 400 | if endPos.Line+1 < pkgPos.Line { 401 | // There isn't a great place to anchor this error; 402 | // the start of the blank lines between the doc and the package statement 403 | // is at least pointing at the location of the problem. 404 | pos := token.Position{ 405 | Filename: endPos.Filename, 406 | // Offset not set; it is non-trivial, and doesn't appear to be needed. 407 | Line: endPos.Line + 1, 408 | Column: 1, 409 | } 410 | f.pkg.errorfAt(pos, 0.9, link(ref), category("comments"), "package comment is detached; there should be no blank lines between it and the package statement") 411 | return 412 | } 413 | } 414 | 415 | if f.f.Doc == nil { 416 | f.errorf(f.f, 0.2, link(ref), category("comments"), "should have a package comment, unless it's in another file for this package") 417 | return 418 | } 419 | s := f.f.Doc.Text() 420 | if ts := strings.TrimLeft(s, " \t"); ts != s { 421 | f.errorf(f.f.Doc, 1, link(ref), category("comments"), "package comment should not have leading space") 422 | s = ts 423 | } 424 | // Only non-main packages need to keep to this form. 425 | if !f.pkg.main && !strings.HasPrefix(s, prefix) { 426 | f.errorf(f.f.Doc, 1, link(ref), category("comments"), `package comment should be of the form "%s..."`, prefix) 427 | } 428 | } 429 | 430 | // lintBlankImports complains if a non-main package has blank imports that are 431 | // not documented. 432 | func (f *file) lintBlankImports() { 433 | // In package main and in tests, we don't complain about blank imports. 434 | if f.pkg.main || f.isTest() { 435 | return 436 | } 437 | 438 | // The first element of each contiguous group of blank imports should have 439 | // an explanatory comment of some kind. 440 | for i, imp := range f.f.Imports { 441 | pos := f.fset.Position(imp.Pos()) 442 | 443 | if !isBlank(imp.Name) { 444 | continue // Ignore non-blank imports. 445 | } 446 | if i > 0 { 447 | prev := f.f.Imports[i-1] 448 | prevPos := f.fset.Position(prev.Pos()) 449 | if isBlank(prev.Name) && prevPos.Line+1 == pos.Line { 450 | continue // A subsequent blank in a group. 451 | } 452 | } 453 | 454 | // This is the first blank import of a group. 455 | if imp.Doc == nil && imp.Comment == nil { 456 | ref := "" 457 | f.errorf(imp, 1, link(ref), category("imports"), "a blank import should be only in a main or test package, or have a comment justifying it") 458 | } 459 | } 460 | } 461 | 462 | // lintImports examines import blocks. 463 | func (f *file) lintImports() { 464 | for i, is := range f.f.Imports { 465 | _ = i 466 | if is.Name != nil && is.Name.Name == "." && !f.isTest() { 467 | f.errorf(is, 1, link(styleGuideBase+"#import-dot"), category("imports"), "should not use dot imports") 468 | } 469 | 470 | } 471 | } 472 | 473 | const docCommentsLink = styleGuideBase + "#doc-comments" 474 | 475 | // lintExported examines the exported names. 476 | // It complains if any required doc comments are missing, 477 | // or if they are not of the right form. The exact rules are in 478 | // lintFuncDoc, lintTypeDoc and lintValueSpecDoc; this function 479 | // also tracks the GenDecl structure being traversed to permit 480 | // doc comments for constants to be on top of the const block. 481 | // It also complains if the names stutter when combined with 482 | // the package name. 483 | func (f *file) lintExported() { 484 | if f.isTest() { 485 | return 486 | } 487 | 488 | var lastGen *ast.GenDecl // last GenDecl entered. 489 | 490 | // Set of GenDecls that have already had missing comments flagged. 491 | genDeclMissingComments := make(map[*ast.GenDecl]bool) 492 | 493 | f.walk(func(node ast.Node) bool { 494 | switch v := node.(type) { 495 | case *ast.GenDecl: 496 | if v.Tok == token.IMPORT { 497 | return false 498 | } 499 | // token.CONST, token.TYPE or token.VAR 500 | lastGen = v 501 | return true 502 | case *ast.FuncDecl: 503 | f.lintFuncDoc(v) 504 | if v.Recv == nil { 505 | // Only check for stutter on functions, not methods. 506 | // Method names are not used package-qualified. 507 | f.checkStutter(v.Name, "func") 508 | } 509 | // Don't proceed inside funcs. 510 | return false 511 | case *ast.TypeSpec: 512 | // inside a GenDecl, which usually has the doc 513 | doc := v.Doc 514 | if doc == nil { 515 | doc = lastGen.Doc 516 | } 517 | f.lintTypeDoc(v, doc) 518 | f.checkStutter(v.Name, "type") 519 | // Don't proceed inside types. 520 | return false 521 | case *ast.ValueSpec: 522 | f.lintValueSpecDoc(v, lastGen, genDeclMissingComments) 523 | return false 524 | } 525 | return true 526 | }) 527 | } 528 | 529 | var ( 530 | allCapsRE = regexp.MustCompile(`^[A-Z0-9_]+$`) 531 | anyCapsRE = regexp.MustCompile(`[A-Z]`) 532 | ) 533 | 534 | // knownNameExceptions is a set of names that are known to be exempt from naming checks. 535 | // This is usually because they are constrained by having to match names in the 536 | // standard library. 537 | var knownNameExceptions = map[string]bool{ 538 | "LastInsertId": true, // must match database/sql 539 | "kWh": true, 540 | } 541 | 542 | func isInTopLevel(f *ast.File, ident *ast.Ident) bool { 543 | path, _ := astutil.PathEnclosingInterval(f, ident.Pos(), ident.End()) 544 | for _, f := range path { 545 | switch f.(type) { 546 | case *ast.File, *ast.GenDecl, *ast.ValueSpec, *ast.Ident: 547 | continue 548 | } 549 | return false 550 | } 551 | return true 552 | } 553 | 554 | // lintNames examines all names in the file. 555 | // It complains if any use underscores or incorrect known initialisms. 556 | func (f *file) lintNames() { 557 | // Package names need slightly different handling than other names. 558 | if strings.Contains(f.f.Name.Name, "_") && !strings.HasSuffix(f.f.Name.Name, "_test") { 559 | f.errorf(f.f, 1, link("http://golang.org/doc/effective_go.html#package-names"), category("naming"), "don't use an underscore in package name") 560 | } 561 | if anyCapsRE.MatchString(f.f.Name.Name) { 562 | f.errorf(f.f, 1, link("http://golang.org/doc/effective_go.html#package-names"), category("mixed-caps"), "don't use MixedCaps in package name; %s should be %s", f.f.Name.Name, strings.ToLower(f.f.Name.Name)) 563 | } 564 | 565 | check := func(id *ast.Ident, thing string) { 566 | if id.Name == "_" { 567 | return 568 | } 569 | if knownNameExceptions[id.Name] { 570 | return 571 | } 572 | 573 | // Handle two common styles from other languages that don't belong in Go. 574 | if len(id.Name) >= 5 && allCapsRE.MatchString(id.Name) && strings.Contains(id.Name, "_") { 575 | capCount := 0 576 | for _, c := range id.Name { 577 | if 'A' <= c && c <= 'Z' { 578 | capCount++ 579 | } 580 | } 581 | if capCount >= 2 { 582 | f.errorf(id, 0.8, link(styleGuideBase+"#mixed-caps"), category("naming"), "don't use ALL_CAPS in Go names; use CamelCase") 583 | return 584 | } 585 | } 586 | if thing == "const" || (thing == "var" && isInTopLevel(f.f, id)) { 587 | if len(id.Name) > 2 && id.Name[0] == 'k' && id.Name[1] >= 'A' && id.Name[1] <= 'Z' { 588 | should := string(id.Name[1]+'a'-'A') + id.Name[2:] 589 | f.errorf(id, 0.8, link(styleGuideBase+"#mixed-caps"), category("naming"), "don't use leading k in Go names; %s %s should be %s", thing, id.Name, should) 590 | } 591 | } 592 | 593 | should := lintName(id.Name) 594 | if id.Name == should { 595 | return 596 | } 597 | 598 | if len(id.Name) > 2 && strings.Contains(id.Name[1:], "_") { 599 | f.errorf(id, 0.9, link("http://golang.org/doc/effective_go.html#mixed-caps"), category("naming"), "don't use underscores in Go names; %s %s should be %s", thing, id.Name, should) 600 | return 601 | } 602 | f.errorf(id, 0.8, link(styleGuideBase+"#initialisms"), category("naming"), "%s %s should be %s", thing, id.Name, should) 603 | } 604 | checkList := func(fl *ast.FieldList, thing string) { 605 | if fl == nil { 606 | return 607 | } 608 | for _, f := range fl.List { 609 | for _, id := range f.Names { 610 | check(id, thing) 611 | } 612 | } 613 | } 614 | f.walk(func(node ast.Node) bool { 615 | switch v := node.(type) { 616 | case *ast.AssignStmt: 617 | if v.Tok == token.ASSIGN { 618 | return true 619 | } 620 | for _, exp := range v.Lhs { 621 | if id, ok := exp.(*ast.Ident); ok { 622 | check(id, "var") 623 | } 624 | } 625 | case *ast.FuncDecl: 626 | if f.isTest() && (strings.HasPrefix(v.Name.Name, "Example") || strings.HasPrefix(v.Name.Name, "Test") || strings.HasPrefix(v.Name.Name, "Benchmark")) { 627 | return true 628 | } 629 | 630 | thing := "func" 631 | if v.Recv != nil { 632 | thing = "method" 633 | } 634 | 635 | // Exclude naming warnings for functions that are exported to C but 636 | // not exported in the Go API. 637 | // See https://github.com/golang/lint/issues/144. 638 | if ast.IsExported(v.Name.Name) || !isCgoExported(v) { 639 | check(v.Name, thing) 640 | } 641 | 642 | checkList(v.Type.Params, thing+" parameter") 643 | checkList(v.Type.Results, thing+" result") 644 | case *ast.GenDecl: 645 | if v.Tok == token.IMPORT { 646 | return true 647 | } 648 | var thing string 649 | switch v.Tok { 650 | case token.CONST: 651 | thing = "const" 652 | case token.TYPE: 653 | thing = "type" 654 | case token.VAR: 655 | thing = "var" 656 | } 657 | for _, spec := range v.Specs { 658 | switch s := spec.(type) { 659 | case *ast.TypeSpec: 660 | check(s.Name, thing) 661 | case *ast.ValueSpec: 662 | for _, id := range s.Names { 663 | check(id, thing) 664 | } 665 | } 666 | } 667 | case *ast.InterfaceType: 668 | // Do not check interface method names. 669 | // They are often constrainted by the method names of concrete types. 670 | for _, x := range v.Methods.List { 671 | ft, ok := x.Type.(*ast.FuncType) 672 | if !ok { // might be an embedded interface name 673 | continue 674 | } 675 | checkList(ft.Params, "interface method parameter") 676 | checkList(ft.Results, "interface method result") 677 | } 678 | case *ast.RangeStmt: 679 | if v.Tok == token.ASSIGN { 680 | return true 681 | } 682 | if id, ok := v.Key.(*ast.Ident); ok { 683 | check(id, "range var") 684 | } 685 | if id, ok := v.Value.(*ast.Ident); ok { 686 | check(id, "range var") 687 | } 688 | case *ast.StructType: 689 | for _, f := range v.Fields.List { 690 | for _, id := range f.Names { 691 | check(id, "struct field") 692 | } 693 | } 694 | } 695 | return true 696 | }) 697 | } 698 | 699 | // lintName returns a different name if it should be different. 700 | func lintName(name string) (should string) { 701 | // Fast path for simple cases: "_" and all lowercase. 702 | if name == "_" { 703 | return name 704 | } 705 | allLower := true 706 | for _, r := range name { 707 | if !unicode.IsLower(r) { 708 | allLower = false 709 | break 710 | } 711 | } 712 | if allLower { 713 | return name 714 | } 715 | 716 | // Split camelCase at any lower->upper transition, and split on underscores. 717 | // Check each word for common initialisms. 718 | runes := []rune(name) 719 | w, i := 0, 0 // index of start of word, scan 720 | for i+1 <= len(runes) { 721 | eow := false // whether we hit the end of a word 722 | if i+1 == len(runes) { 723 | eow = true 724 | } else if runes[i+1] == '_' { 725 | // underscore; shift the remainder forward over any run of underscores 726 | eow = true 727 | n := 1 728 | for i+n+1 < len(runes) && runes[i+n+1] == '_' { 729 | n++ 730 | } 731 | 732 | // Leave at most one underscore if the underscore is between two digits 733 | if i+n+1 < len(runes) && unicode.IsDigit(runes[i]) && unicode.IsDigit(runes[i+n+1]) { 734 | n-- 735 | } 736 | 737 | copy(runes[i+1:], runes[i+n+1:]) 738 | runes = runes[:len(runes)-n] 739 | } else if unicode.IsLower(runes[i]) && !unicode.IsLower(runes[i+1]) { 740 | // lower->non-lower 741 | eow = true 742 | } 743 | i++ 744 | if !eow { 745 | continue 746 | } 747 | 748 | // [w,i) is a word. 749 | word := string(runes[w:i]) 750 | if u := strings.ToUpper(word); commonInitialisms[u] { 751 | // Keep consistent case, which is lowercase only at the start. 752 | if w == 0 && unicode.IsLower(runes[w]) { 753 | u = strings.ToLower(u) 754 | } 755 | // All the common initialisms are ASCII, 756 | // so we can replace the bytes exactly. 757 | copy(runes[w:], []rune(u)) 758 | } else if w > 0 && strings.ToLower(word) == word { 759 | // already all lowercase, and not the first word, so uppercase the first character. 760 | runes[w] = unicode.ToUpper(runes[w]) 761 | } 762 | w = i 763 | } 764 | return string(runes) 765 | } 766 | 767 | // commonInitialisms is a set of common initialisms. 768 | // Only add entries that are highly unlikely to be non-initialisms. 769 | // For instance, "ID" is fine (Freudian code is rare), but "AND" is not. 770 | var commonInitialisms = map[string]bool{ 771 | "ACL": true, 772 | "API": true, 773 | "ASCII": true, 774 | "CPU": true, 775 | "CSS": true, 776 | "DNS": true, 777 | "EOF": true, 778 | "GUID": true, 779 | "HTML": true, 780 | "HTTP": true, 781 | "HTTPS": true, 782 | "ID": true, 783 | "IP": true, 784 | "JSON": true, 785 | "LHS": true, 786 | "QPS": true, 787 | "RAM": true, 788 | "RHS": true, 789 | "RPC": true, 790 | "SLA": true, 791 | "SMTP": true, 792 | "SQL": true, 793 | "SSH": true, 794 | "TCP": true, 795 | "TLS": true, 796 | "TTL": true, 797 | "UDP": true, 798 | "UI": true, 799 | "UID": true, 800 | "UUID": true, 801 | "URI": true, 802 | "URL": true, 803 | "UTF8": true, 804 | "VM": true, 805 | "XML": true, 806 | "XMPP": true, 807 | "XSRF": true, 808 | "XSS": true, 809 | } 810 | 811 | // lintTypeDoc examines the doc comment on a type. 812 | // It complains if they are missing from an exported type, 813 | // or if they are not of the standard form. 814 | func (f *file) lintTypeDoc(t *ast.TypeSpec, doc *ast.CommentGroup) { 815 | if !ast.IsExported(t.Name.Name) { 816 | return 817 | } 818 | if doc == nil { 819 | f.errorf(t, 1, link(docCommentsLink), category("comments"), "exported type %v should have comment or be unexported", t.Name) 820 | return 821 | } 822 | 823 | s := doc.Text() 824 | articles := [...]string{"A", "An", "The"} 825 | for _, a := range articles { 826 | if strings.HasPrefix(s, a+" ") { 827 | s = s[len(a)+1:] 828 | break 829 | } 830 | } 831 | if !strings.HasPrefix(s, t.Name.Name+" ") { 832 | f.errorf(doc, 1, link(docCommentsLink), category("comments"), `comment on exported type %v should be of the form "%v ..." (with optional leading article)`, t.Name, t.Name) 833 | } 834 | } 835 | 836 | var commonMethods = map[string]bool{ 837 | "Error": true, 838 | "Read": true, 839 | "ServeHTTP": true, 840 | "String": true, 841 | "Write": true, 842 | "Unwrap": true, 843 | } 844 | 845 | // lintFuncDoc examines doc comments on functions and methods. 846 | // It complains if they are missing, or not of the right form. 847 | // It has specific exclusions for well-known methods (see commonMethods above). 848 | func (f *file) lintFuncDoc(fn *ast.FuncDecl) { 849 | if !ast.IsExported(fn.Name.Name) { 850 | // func is unexported 851 | return 852 | } 853 | kind := "function" 854 | name := fn.Name.Name 855 | if fn.Recv != nil && len(fn.Recv.List) > 0 { 856 | // method 857 | kind = "method" 858 | recv := receiverType(fn) 859 | if !ast.IsExported(recv) { 860 | // receiver is unexported 861 | return 862 | } 863 | if commonMethods[name] { 864 | return 865 | } 866 | switch name { 867 | case "Len", "Less", "Swap": 868 | if f.pkg.sortable[recv] { 869 | return 870 | } 871 | } 872 | name = recv + "." + name 873 | } 874 | if fn.Doc == nil { 875 | f.errorf(fn, 1, link(docCommentsLink), category("comments"), "exported %s %s should have comment or be unexported", kind, name) 876 | return 877 | } 878 | s := fn.Doc.Text() 879 | prefix := fn.Name.Name + " " 880 | if !strings.HasPrefix(s, prefix) { 881 | f.errorf(fn.Doc, 1, link(docCommentsLink), category("comments"), `comment on exported %s %s should be of the form "%s..."`, kind, name, prefix) 882 | } 883 | } 884 | 885 | // lintValueSpecDoc examines package-global variables and constants. 886 | // It complains if they are not individually declared, 887 | // or if they are not suitably documented in the right form (unless they are in a block that is commented). 888 | func (f *file) lintValueSpecDoc(vs *ast.ValueSpec, gd *ast.GenDecl, genDeclMissingComments map[*ast.GenDecl]bool) { 889 | kind := "var" 890 | if gd.Tok == token.CONST { 891 | kind = "const" 892 | } 893 | 894 | if len(vs.Names) > 1 { 895 | // Check that none are exported except for the first. 896 | for _, n := range vs.Names[1:] { 897 | if ast.IsExported(n.Name) { 898 | f.errorf(vs, 1, category("comments"), "exported %s %s should have its own declaration", kind, n.Name) 899 | return 900 | } 901 | } 902 | } 903 | 904 | // Only one name. 905 | name := vs.Names[0].Name 906 | if !ast.IsExported(name) { 907 | return 908 | } 909 | 910 | if vs.Doc == nil && gd.Doc == nil { 911 | if genDeclMissingComments[gd] { 912 | return 913 | } 914 | block := "" 915 | if kind == "const" && gd.Lparen.IsValid() { 916 | block = " (or a comment on this block)" 917 | } 918 | f.errorf(vs, 1, link(docCommentsLink), category("comments"), "exported %s %s should have comment%s or be unexported", kind, name, block) 919 | genDeclMissingComments[gd] = true 920 | return 921 | } 922 | // If this GenDecl has parens and a comment, we don't check its comment form. 923 | if gd.Lparen.IsValid() && gd.Doc != nil { 924 | return 925 | } 926 | // The relevant text to check will be on either vs.Doc or gd.Doc. 927 | // Use vs.Doc preferentially. 928 | doc := vs.Doc 929 | if doc == nil { 930 | doc = gd.Doc 931 | } 932 | prefix := name + " " 933 | if !strings.HasPrefix(doc.Text(), prefix) { 934 | f.errorf(doc, 1, link(docCommentsLink), category("comments"), `comment on exported %s %s should be of the form "%s..."`, kind, name, prefix) 935 | } 936 | } 937 | 938 | func (f *file) checkStutter(id *ast.Ident, thing string) { 939 | pkg, name := f.f.Name.Name, id.Name 940 | if !ast.IsExported(name) { 941 | // unexported name 942 | return 943 | } 944 | // A name stutters if the package name is a strict prefix 945 | // and the next character of the name starts a new word. 946 | if len(name) <= len(pkg) { 947 | // name is too short to stutter. 948 | // This permits the name to be the same as the package name. 949 | return 950 | } 951 | if !strings.EqualFold(pkg, name[:len(pkg)]) { 952 | return 953 | } 954 | // We can assume the name is well-formed UTF-8. 955 | // If the next rune after the package name is uppercase or an underscore 956 | // the it's starting a new word and thus this name stutters. 957 | rem := name[len(pkg):] 958 | if next, _ := utf8.DecodeRuneInString(rem); next == '_' || unicode.IsUpper(next) { 959 | f.errorf(id, 0.8, link(styleGuideBase+"#package-names"), category("naming"), "%s name will be used as %s.%s by other packages, and that stutters; consider calling this %s", thing, pkg, name, rem) 960 | } 961 | } 962 | 963 | // zeroLiteral is a set of ast.BasicLit values that are zero values. 964 | // It is not exhaustive. 965 | var zeroLiteral = map[string]bool{ 966 | "false": true, // bool 967 | // runes 968 | `'\x00'`: true, 969 | `'\000'`: true, 970 | // strings 971 | `""`: true, 972 | "``": true, 973 | // numerics 974 | "0": true, 975 | "0.": true, 976 | "0.0": true, 977 | "0i": true, 978 | } 979 | 980 | // lintElses examines else blocks. It complains about any else block whose if block ends in a return. 981 | func (f *file) lintElses() { 982 | // We don't want to flag if { } else if { } else { } constructions. 983 | // They will appear as an IfStmt whose Else field is also an IfStmt. 984 | // Record such a node so we ignore it when we visit it. 985 | ignore := make(map[*ast.IfStmt]bool) 986 | 987 | f.walk(func(node ast.Node) bool { 988 | ifStmt, ok := node.(*ast.IfStmt) 989 | if !ok || ifStmt.Else == nil { 990 | return true 991 | } 992 | if elseif, ok := ifStmt.Else.(*ast.IfStmt); ok { 993 | ignore[elseif] = true 994 | return true 995 | } 996 | if ignore[ifStmt] { 997 | return true 998 | } 999 | if _, ok := ifStmt.Else.(*ast.BlockStmt); !ok { 1000 | // only care about elses without conditions 1001 | return true 1002 | } 1003 | if len(ifStmt.Body.List) == 0 { 1004 | return true 1005 | } 1006 | shortDecl := false // does the if statement have a ":=" initialization statement? 1007 | if ifStmt.Init != nil { 1008 | if as, ok := ifStmt.Init.(*ast.AssignStmt); ok && as.Tok == token.DEFINE { 1009 | shortDecl = true 1010 | } 1011 | } 1012 | lastStmt := ifStmt.Body.List[len(ifStmt.Body.List)-1] 1013 | if _, ok := lastStmt.(*ast.ReturnStmt); ok { 1014 | extra := "" 1015 | if shortDecl { 1016 | extra = " (move short variable declaration to its own line if necessary)" 1017 | } 1018 | f.errorf(ifStmt.Else, 1, link(styleGuideBase+"#indent-error-flow"), category("indent"), "if block ends with a return statement, so drop this else and outdent its block"+extra) 1019 | } 1020 | return true 1021 | }) 1022 | } 1023 | 1024 | // lintRanges examines range clauses. It complains about redundant constructions. 1025 | func (f *file) lintRanges() { 1026 | f.walk(func(node ast.Node) bool { 1027 | rs, ok := node.(*ast.RangeStmt) 1028 | if !ok { 1029 | return true 1030 | } 1031 | 1032 | if isIdent(rs.Key, "_") && (rs.Value == nil || isIdent(rs.Value, "_")) { 1033 | p := f.errorf(rs.Key, 1, category("range-loop"), "should omit values from range; this loop is equivalent to `for range ...`") 1034 | 1035 | newRS := *rs // shallow copy 1036 | newRS.Value = nil 1037 | newRS.Key = nil 1038 | p.ReplacementLine = f.firstLineOf(&newRS, rs) 1039 | 1040 | return true 1041 | } 1042 | 1043 | if isIdent(rs.Value, "_") { 1044 | p := f.errorf(rs.Value, 1, category("range-loop"), "should omit 2nd value from range; this loop is equivalent to `for %s %s range ...`", f.render(rs.Key), rs.Tok) 1045 | 1046 | newRS := *rs // shallow copy 1047 | newRS.Value = nil 1048 | p.ReplacementLine = f.firstLineOf(&newRS, rs) 1049 | } 1050 | 1051 | return true 1052 | }) 1053 | } 1054 | 1055 | // lintErrorf examines errors.New and testing.Error calls. It complains if its only argument is an fmt.Sprintf invocation. 1056 | func (f *file) lintErrorf() { 1057 | f.walk(func(node ast.Node) bool { 1058 | ce, ok := node.(*ast.CallExpr) 1059 | if !ok || len(ce.Args) != 1 { 1060 | return true 1061 | } 1062 | isErrorsNew := isPkgDot(ce.Fun, "errors", "New") 1063 | var isTestingError bool 1064 | se, ok := ce.Fun.(*ast.SelectorExpr) 1065 | if ok && se.Sel.Name == "Error" { 1066 | if typ := f.pkg.typeOf(se.X); typ != nil { 1067 | isTestingError = typ.String() == "*testing.T" 1068 | } 1069 | } 1070 | if !isErrorsNew && !isTestingError { 1071 | return true 1072 | } 1073 | if !f.imports("errors") { 1074 | return true 1075 | } 1076 | arg := ce.Args[0] 1077 | ce, ok = arg.(*ast.CallExpr) 1078 | if !ok || !isPkgDot(ce.Fun, "fmt", "Sprintf") { 1079 | return true 1080 | } 1081 | errorfPrefix := "fmt" 1082 | if isTestingError { 1083 | errorfPrefix = f.render(se.X) 1084 | } 1085 | p := f.errorf(node, 1, category("errors"), "should replace %s(fmt.Sprintf(...)) with %s.Errorf(...)", f.render(se), errorfPrefix) 1086 | 1087 | m := f.srcLineWithMatch(ce, `^(.*)`+f.render(se)+`\(fmt\.Sprintf\((.*)\)\)(.*)$`) 1088 | if m != nil { 1089 | p.ReplacementLine = m[1] + errorfPrefix + ".Errorf(" + m[2] + ")" + m[3] 1090 | } 1091 | 1092 | return true 1093 | }) 1094 | } 1095 | 1096 | // lintErrors examines global error vars. It complains if they aren't named in the standard way. 1097 | func (f *file) lintErrors() { 1098 | for _, decl := range f.f.Decls { 1099 | gd, ok := decl.(*ast.GenDecl) 1100 | if !ok || gd.Tok != token.VAR { 1101 | continue 1102 | } 1103 | for _, spec := range gd.Specs { 1104 | spec := spec.(*ast.ValueSpec) 1105 | if len(spec.Names) != 1 || len(spec.Values) != 1 { 1106 | continue 1107 | } 1108 | ce, ok := spec.Values[0].(*ast.CallExpr) 1109 | if !ok { 1110 | continue 1111 | } 1112 | if !isPkgDot(ce.Fun, "errors", "New") && !isPkgDot(ce.Fun, "fmt", "Errorf") { 1113 | continue 1114 | } 1115 | 1116 | id := spec.Names[0] 1117 | prefix := "err" 1118 | if id.IsExported() { 1119 | prefix = "Err" 1120 | } 1121 | if !strings.HasPrefix(id.Name, prefix) { 1122 | f.errorf(id, 0.9, category("naming"), "error var %s should have name of the form %sFoo", id.Name, prefix) 1123 | } 1124 | } 1125 | } 1126 | } 1127 | 1128 | func lintErrorString(s string) (isClean bool, conf float64) { 1129 | const basicConfidence = 0.8 1130 | const capConfidence = basicConfidence - 0.2 1131 | first, firstN := utf8.DecodeRuneInString(s) 1132 | last, _ := utf8.DecodeLastRuneInString(s) 1133 | if last == '.' || last == ':' || last == '!' || last == '\n' { 1134 | return false, basicConfidence 1135 | } 1136 | if unicode.IsUpper(first) { 1137 | // People use proper nouns and exported Go identifiers in error strings, 1138 | // so decrease the confidence of warnings for capitalization. 1139 | if len(s) <= firstN { 1140 | return false, capConfidence 1141 | } 1142 | // Flag strings starting with something that doesn't look like an initialism. 1143 | if second, _ := utf8.DecodeRuneInString(s[firstN:]); !unicode.IsUpper(second) { 1144 | return false, capConfidence 1145 | } 1146 | } 1147 | return true, 0 1148 | } 1149 | 1150 | // lintErrorStrings examines error strings. 1151 | // It complains if they are capitalized or end in punctuation or a newline. 1152 | func (f *file) lintErrorStrings() { 1153 | f.walk(func(node ast.Node) bool { 1154 | ce, ok := node.(*ast.CallExpr) 1155 | if !ok { 1156 | return true 1157 | } 1158 | if !isPkgDot(ce.Fun, "errors", "New") && !isPkgDot(ce.Fun, "fmt", "Errorf") { 1159 | return true 1160 | } 1161 | if len(ce.Args) < 1 { 1162 | return true 1163 | } 1164 | str, ok := ce.Args[0].(*ast.BasicLit) 1165 | if !ok || str.Kind != token.STRING { 1166 | return true 1167 | } 1168 | s, _ := strconv.Unquote(str.Value) // can assume well-formed Go 1169 | if s == "" { 1170 | return true 1171 | } 1172 | clean, conf := lintErrorString(s) 1173 | if clean { 1174 | return true 1175 | } 1176 | 1177 | f.errorf(str, conf, link(styleGuideBase+"#error-strings"), category("errors"), 1178 | "error strings should not be capitalized or end with punctuation or a newline") 1179 | return true 1180 | }) 1181 | } 1182 | 1183 | // lintReceiverNames examines receiver names. It complains about inconsistent 1184 | // names used for the same type and names such as "this". 1185 | func (f *file) lintReceiverNames() { 1186 | typeReceiver := map[string]string{} 1187 | f.walk(func(n ast.Node) bool { 1188 | fn, ok := n.(*ast.FuncDecl) 1189 | if !ok || fn.Recv == nil || len(fn.Recv.List) == 0 { 1190 | return true 1191 | } 1192 | names := fn.Recv.List[0].Names 1193 | if len(names) < 1 { 1194 | return true 1195 | } 1196 | name := names[0].Name 1197 | const ref = styleGuideBase + "#receiver-names" 1198 | if name == "_" { 1199 | f.errorf(n, 1, link(ref), category("naming"), `receiver name should not be an underscore, omit the name if it is unused`) 1200 | return true 1201 | } 1202 | if name == "this" || name == "self" { 1203 | f.errorf(n, 1, link(ref), category("naming"), `receiver name should be a reflection of its identity; don't use generic names such as "this" or "self"`) 1204 | return true 1205 | } 1206 | recv := receiverType(fn) 1207 | if prev, ok := typeReceiver[recv]; ok && prev != name { 1208 | f.errorf(n, 1, link(ref), category("naming"), "receiver name %s should be consistent with previous receiver name %s for %s", name, prev, recv) 1209 | return true 1210 | } 1211 | typeReceiver[recv] = name 1212 | return true 1213 | }) 1214 | } 1215 | 1216 | // lintIncDec examines statements that increment or decrement a variable. 1217 | // It complains if they don't use x++ or x--. 1218 | func (f *file) lintIncDec() { 1219 | f.walk(func(n ast.Node) bool { 1220 | as, ok := n.(*ast.AssignStmt) 1221 | if !ok { 1222 | return true 1223 | } 1224 | if len(as.Lhs) != 1 { 1225 | return true 1226 | } 1227 | if !isOne(as.Rhs[0]) { 1228 | return true 1229 | } 1230 | var suffix string 1231 | switch as.Tok { 1232 | case token.ADD_ASSIGN: 1233 | suffix = "++" 1234 | case token.SUB_ASSIGN: 1235 | suffix = "--" 1236 | default: 1237 | return true 1238 | } 1239 | f.errorf(as, 0.8, category("unary-op"), "should replace %s with %s%s", f.render(as), f.render(as.Lhs[0]), suffix) 1240 | return true 1241 | }) 1242 | } 1243 | 1244 | // lintErrorReturn examines function declarations that return an error. 1245 | // It complains if the error isn't the last parameter. 1246 | func (f *file) lintErrorReturn() { 1247 | f.walk(func(n ast.Node) bool { 1248 | fn, ok := n.(*ast.FuncDecl) 1249 | if !ok || fn.Type.Results == nil { 1250 | return true 1251 | } 1252 | ret := fn.Type.Results.List 1253 | if len(ret) <= 1 { 1254 | return true 1255 | } 1256 | if isIdent(ret[len(ret)-1].Type, "error") { 1257 | return true 1258 | } 1259 | // An error return parameter should be the last parameter. 1260 | // Flag any error parameters found before the last. 1261 | for _, r := range ret[:len(ret)-1] { 1262 | if isIdent(r.Type, "error") { 1263 | f.errorf(fn, 0.9, category("arg-order"), "error should be the last type when returning multiple items") 1264 | break // only flag one 1265 | } 1266 | } 1267 | return true 1268 | }) 1269 | } 1270 | 1271 | // lintUnexportedReturn examines exported function declarations. 1272 | // It complains if any return an unexported type. 1273 | func (f *file) lintUnexportedReturn() { 1274 | f.walk(func(n ast.Node) bool { 1275 | fn, ok := n.(*ast.FuncDecl) 1276 | if !ok { 1277 | return true 1278 | } 1279 | if fn.Type.Results == nil { 1280 | return false 1281 | } 1282 | if !fn.Name.IsExported() { 1283 | return false 1284 | } 1285 | thing := "func" 1286 | if fn.Recv != nil && len(fn.Recv.List) > 0 { 1287 | thing = "method" 1288 | if !ast.IsExported(receiverType(fn)) { 1289 | // Don't report exported methods of unexported types, 1290 | // such as private implementations of sort.Interface. 1291 | return false 1292 | } 1293 | } 1294 | for _, ret := range fn.Type.Results.List { 1295 | typ := f.pkg.typeOf(ret.Type) 1296 | if exportedType(typ) { 1297 | continue 1298 | } 1299 | f.errorf(ret.Type, 0.8, category("unexported-type-in-api"), 1300 | "exported %s %s returns unexported type %s, which can be annoying to use", 1301 | thing, fn.Name.Name, typ) 1302 | break // only flag one 1303 | } 1304 | return false 1305 | }) 1306 | } 1307 | 1308 | // exportedType reports whether typ is an exported type. 1309 | // It is imprecise, and will err on the side of returning true, 1310 | // such as for composite types. 1311 | func exportedType(typ types.Type) bool { 1312 | switch T := typ.(type) { 1313 | case *types.Named: 1314 | // Builtin types have no package. 1315 | return T.Obj().Pkg() == nil || T.Obj().Exported() 1316 | case *types.Map: 1317 | return exportedType(T.Key()) && exportedType(T.Elem()) 1318 | case interface { 1319 | Elem() types.Type 1320 | }: // array, slice, pointer, chan 1321 | return exportedType(T.Elem()) 1322 | } 1323 | // Be conservative about other types, such as struct, interface, etc. 1324 | return true 1325 | } 1326 | 1327 | // timeSuffixes is a list of name suffixes that imply a time unit. 1328 | // This is not an exhaustive list. 1329 | var timeSuffixes = []string{ 1330 | "Sec", "Secs", "Seconds", 1331 | "Msec", "Msecs", 1332 | "Milli", "Millis", "Milliseconds", 1333 | "Usec", "Usecs", "Microseconds", 1334 | "MS", "Ms", 1335 | } 1336 | 1337 | func (f *file) lintTimeNames() { 1338 | f.walk(func(node ast.Node) bool { 1339 | v, ok := node.(*ast.ValueSpec) 1340 | if !ok { 1341 | return true 1342 | } 1343 | for _, name := range v.Names { 1344 | origTyp := f.pkg.typeOf(name) 1345 | // Look for time.Duration or *time.Duration; 1346 | // the latter is common when using flag.Duration. 1347 | typ := origTyp 1348 | if pt, ok := typ.(*types.Pointer); ok { 1349 | typ = pt.Elem() 1350 | } 1351 | if !f.pkg.isNamedType(typ, "time", "Duration") { 1352 | continue 1353 | } 1354 | suffix := "" 1355 | for _, suf := range timeSuffixes { 1356 | if strings.HasSuffix(name.Name, suf) { 1357 | suffix = suf 1358 | break 1359 | } 1360 | } 1361 | if suffix == "" { 1362 | continue 1363 | } 1364 | f.errorf(v, 0.9, category("time"), "var %s is of type %v; don't use unit-specific suffix %q", name.Name, origTyp, suffix) 1365 | } 1366 | return true 1367 | }) 1368 | } 1369 | 1370 | // lintContextKeyTypes checks for call expressions to context.WithValue with 1371 | // basic types used for the key argument. 1372 | // See: https://golang.org/issue/17293 1373 | func (f *file) lintContextKeyTypes() { 1374 | f.walk(func(node ast.Node) bool { 1375 | switch node := node.(type) { 1376 | case *ast.CallExpr: 1377 | f.checkContextKeyType(node) 1378 | } 1379 | 1380 | return true 1381 | }) 1382 | } 1383 | 1384 | // checkContextKeyType reports an error if the call expression calls 1385 | // context.WithValue with a key argument of basic type. 1386 | func (f *file) checkContextKeyType(x *ast.CallExpr) { 1387 | sel, ok := x.Fun.(*ast.SelectorExpr) 1388 | if !ok { 1389 | return 1390 | } 1391 | pkg, ok := sel.X.(*ast.Ident) 1392 | if !ok || pkg.Name != "context" { 1393 | return 1394 | } 1395 | if sel.Sel.Name != "WithValue" { 1396 | return 1397 | } 1398 | 1399 | // key is second argument to context.WithValue 1400 | if len(x.Args) != 3 { 1401 | return 1402 | } 1403 | key := f.pkg.typesInfo.Types[x.Args[1]] 1404 | 1405 | if ktyp, ok := key.Type.(*types.Basic); ok && ktyp.Kind() != types.Invalid { 1406 | f.errorf(x, 1.0, category("context"), fmt.Sprintf("should not use basic type %s as key in context.WithValue", key.Type)) 1407 | } 1408 | } 1409 | 1410 | // lintContextArgs examines function declarations that contain an 1411 | // argument with a type of context.Context 1412 | // It complains if that argument isn't the first parameter. 1413 | func (f *file) lintContextArgs() { 1414 | f.walk(func(n ast.Node) bool { 1415 | fn, ok := n.(*ast.FuncDecl) 1416 | if !ok || len(fn.Type.Params.List) <= 1 { 1417 | return true 1418 | } 1419 | // A context.Context should be the first parameter of a function. 1420 | // Flag any that show up after the first. 1421 | for _, arg := range fn.Type.Params.List[1:] { 1422 | if isPkgDot(arg.Type, "context", "Context") { 1423 | f.errorf(fn, 0.9, link("https://golang.org/pkg/context/"), category("arg-order"), "context.Context should be the first parameter of a function") 1424 | break // only flag one 1425 | } 1426 | } 1427 | return true 1428 | }) 1429 | } 1430 | 1431 | // containsComments returns whether the interval [start, end) contains any 1432 | // comments without "// MATCH " prefix. 1433 | func (f *file) containsComments(start, end token.Pos) bool { 1434 | for _, cgroup := range f.f.Comments { 1435 | comments := cgroup.List 1436 | if comments[0].Slash >= end { 1437 | // All comments starting with this group are after end pos. 1438 | return false 1439 | } 1440 | if comments[len(comments)-1].Slash < start { 1441 | // Comments group ends before start pos. 1442 | continue 1443 | } 1444 | for _, c := range comments { 1445 | if start <= c.Slash && c.Slash < end && !strings.HasPrefix(c.Text, "// MATCH ") { 1446 | return true 1447 | } 1448 | } 1449 | } 1450 | return false 1451 | } 1452 | 1453 | // receiverType returns the named type of the method receiver, sans "*", 1454 | // or "invalid-type" if fn.Recv is ill formed. 1455 | func receiverType(fn *ast.FuncDecl) string { 1456 | switch e := fn.Recv.List[0].Type.(type) { 1457 | case *ast.Ident: 1458 | return e.Name 1459 | case *ast.StarExpr: 1460 | if id, ok := e.X.(*ast.Ident); ok { 1461 | return id.Name 1462 | } 1463 | } 1464 | // The parser accepts much more than just the legal forms. 1465 | return "invalid-type" 1466 | } 1467 | 1468 | func (f *file) walk(fn func(ast.Node) bool) { 1469 | ast.Walk(walker(fn), f.f) 1470 | } 1471 | 1472 | func (f *file) render(x interface{}) string { 1473 | var buf bytes.Buffer 1474 | if err := printer.Fprint(&buf, f.fset, x); err != nil { 1475 | panic(err) 1476 | } 1477 | return buf.String() 1478 | } 1479 | 1480 | func (f *file) debugRender(x interface{}) string { 1481 | var buf bytes.Buffer 1482 | if err := ast.Fprint(&buf, f.fset, x, nil); err != nil { 1483 | panic(err) 1484 | } 1485 | return buf.String() 1486 | } 1487 | 1488 | // walker adapts a function to satisfy the ast.Visitor interface. 1489 | // The function return whether the walk should proceed into the node's children. 1490 | type walker func(ast.Node) bool 1491 | 1492 | func (w walker) Visit(node ast.Node) ast.Visitor { 1493 | if w(node) { 1494 | return w 1495 | } 1496 | return nil 1497 | } 1498 | 1499 | func isIdent(expr ast.Expr, ident string) bool { 1500 | id, ok := expr.(*ast.Ident) 1501 | return ok && id.Name == ident 1502 | } 1503 | 1504 | // isBlank returns whether id is the blank identifier "_". 1505 | // If id == nil, the answer is false. 1506 | func isBlank(id *ast.Ident) bool { return id != nil && id.Name == "_" } 1507 | 1508 | func isPkgDot(expr ast.Expr, pkg, name string) bool { 1509 | sel, ok := expr.(*ast.SelectorExpr) 1510 | return ok && isIdent(sel.X, pkg) && isIdent(sel.Sel, name) 1511 | } 1512 | 1513 | func isOne(expr ast.Expr) bool { 1514 | lit, ok := expr.(*ast.BasicLit) 1515 | return ok && lit.Kind == token.INT && lit.Value == "1" 1516 | } 1517 | 1518 | func isCgoExported(f *ast.FuncDecl) bool { 1519 | if f.Recv != nil || f.Doc == nil { 1520 | return false 1521 | } 1522 | 1523 | cgoExport := regexp.MustCompile(fmt.Sprintf("(?m)^//export %s$", regexp.QuoteMeta(f.Name.Name))) 1524 | for _, c := range f.Doc.List { 1525 | if cgoExport.MatchString(c.Text) { 1526 | return true 1527 | } 1528 | } 1529 | return false 1530 | } 1531 | 1532 | var basicTypeKinds = map[types.BasicKind]string{ 1533 | types.UntypedBool: "bool", 1534 | types.UntypedInt: "int", 1535 | types.UntypedRune: "rune", 1536 | types.UntypedFloat: "float64", 1537 | types.UntypedComplex: "complex128", 1538 | types.UntypedString: "string", 1539 | } 1540 | 1541 | // isUntypedConst reports whether expr is an untyped constant, 1542 | // and indicates what its default type is. 1543 | // scope may be nil. 1544 | func (f *file) isUntypedConst(expr ast.Expr) (defType string, ok bool) { 1545 | // Re-evaluate expr outside of its context to see if it's untyped. 1546 | // (An expr evaluated within, for example, an assignment context will get the type of the LHS.) 1547 | exprStr := f.render(expr) 1548 | tv, err := types.Eval(f.fset, f.pkg.typesPkg, expr.Pos(), exprStr) 1549 | if err != nil { 1550 | return "", false 1551 | } 1552 | if b, ok := tv.Type.(*types.Basic); ok { 1553 | if dt, ok := basicTypeKinds[b.Kind()]; ok { 1554 | return dt, true 1555 | } 1556 | } 1557 | 1558 | return "", false 1559 | } 1560 | 1561 | // firstLineOf renders the given node and returns its first line. 1562 | // It will also match the indentation of another node. 1563 | func (f *file) firstLineOf(node, match ast.Node) string { 1564 | line := f.render(node) 1565 | if i := strings.Index(line, "\n"); i >= 0 { 1566 | line = line[:i] 1567 | } 1568 | return f.indentOf(match) + line 1569 | } 1570 | 1571 | func (f *file) indentOf(node ast.Node) string { 1572 | line := srcLine(f.src, f.fset.Position(node.Pos())) 1573 | for i, r := range line { 1574 | switch r { 1575 | case ' ', '\t': 1576 | default: 1577 | return line[:i] 1578 | } 1579 | } 1580 | return line // unusual or empty line 1581 | } 1582 | 1583 | func (f *file) srcLineWithMatch(node ast.Node, pattern string) (m []string) { 1584 | line := srcLine(f.src, f.fset.Position(node.Pos())) 1585 | line = strings.TrimSuffix(line, "\n") 1586 | rx := regexp.MustCompile(pattern) 1587 | return rx.FindStringSubmatch(line) 1588 | } 1589 | 1590 | // imports returns true if the current file imports the specified package path. 1591 | func (f *file) imports(importPath string) bool { 1592 | all := astutil.Imports(f.fset, f.f) 1593 | for _, p := range all { 1594 | for _, i := range p { 1595 | uq, err := strconv.Unquote(i.Path.Value) 1596 | if err == nil && importPath == uq { 1597 | return true 1598 | } 1599 | } 1600 | } 1601 | return false 1602 | } 1603 | 1604 | // srcLine returns the complete line at p, including the terminating newline. 1605 | func srcLine(src []byte, p token.Position) string { 1606 | // Run to end of line in both directions if not at line start/end. 1607 | lo, hi := p.Offset, p.Offset+1 1608 | for lo > 0 && src[lo-1] != '\n' { 1609 | lo-- 1610 | } 1611 | for hi < len(src) && src[hi-1] != '\n' { 1612 | hi++ 1613 | } 1614 | return string(src[lo:hi]) 1615 | } 1616 | -------------------------------------------------------------------------------- /lint_test.go: -------------------------------------------------------------------------------- 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 8 | 9 | import ( 10 | "bytes" 11 | "flag" 12 | "fmt" 13 | "go/ast" 14 | "go/parser" 15 | "go/printer" 16 | "go/token" 17 | "go/types" 18 | "io/ioutil" 19 | "path" 20 | "regexp" 21 | "strconv" 22 | "strings" 23 | "testing" 24 | ) 25 | 26 | var lintMatch = flag.String("lint.match", "", "restrict testdata matches to this pattern") 27 | 28 | func TestAll(t *testing.T) { 29 | l := new(Linter) 30 | rx, err := regexp.Compile(*lintMatch) 31 | if err != nil { 32 | t.Fatalf("Bad -lint.match value %q: %v", *lintMatch, err) 33 | } 34 | 35 | baseDir := "testdata" 36 | fis, err := ioutil.ReadDir(baseDir) 37 | if err != nil { 38 | t.Fatalf("ioutil.ReadDir: %v", err) 39 | } 40 | if len(fis) == 0 { 41 | t.Fatalf("no files in %v", baseDir) 42 | } 43 | for _, fi := range fis { 44 | if !rx.MatchString(fi.Name()) { 45 | continue 46 | } 47 | //t.Logf("Testing %s", fi.Name()) 48 | src, err := ioutil.ReadFile(path.Join(baseDir, fi.Name())) 49 | if err != nil { 50 | t.Fatalf("Failed reading %s: %v", fi.Name(), err) 51 | } 52 | 53 | ins := parseInstructions(t, fi.Name(), src) 54 | if ins == nil { 55 | t.Errorf("Test file %v does not have instructions", fi.Name()) 56 | continue 57 | } 58 | 59 | ps, err := l.Lint(fi.Name(), src) 60 | if err != nil { 61 | t.Errorf("Linting %s: %v", fi.Name(), err) 62 | continue 63 | } 64 | 65 | for _, in := range ins { 66 | ok := false 67 | for i, p := range ps { 68 | if p.Position.Line != in.Line { 69 | continue 70 | } 71 | if in.Match.MatchString(p.Text) { 72 | // check replacement if we are expecting one 73 | if in.Replacement != "" { 74 | // ignore any inline comments, since that would be recursive 75 | r := p.ReplacementLine 76 | if i := strings.Index(r, " //"); i >= 0 { 77 | r = r[:i] 78 | } 79 | if r != in.Replacement { 80 | t.Errorf("Lint failed at %s:%d; got replacement %q, want %q", fi.Name(), in.Line, r, in.Replacement) 81 | } 82 | } 83 | 84 | // remove this problem from ps 85 | copy(ps[i:], ps[i+1:]) 86 | ps = ps[:len(ps)-1] 87 | 88 | //t.Logf("/%v/ matched at %s:%d", in.Match, fi.Name(), in.Line) 89 | ok = true 90 | break 91 | } 92 | } 93 | if !ok { 94 | t.Errorf("Lint failed at %s:%d; /%v/ did not match", fi.Name(), in.Line, in.Match) 95 | } 96 | } 97 | for _, p := range ps { 98 | t.Errorf("Unexpected problem at %s:%d: %v", fi.Name(), p.Position.Line, p.Text) 99 | } 100 | } 101 | } 102 | 103 | type instruction struct { 104 | Line int // the line number this applies to 105 | Match *regexp.Regexp // what pattern to match 106 | Replacement string // what the suggested replacement line should be 107 | } 108 | 109 | // parseInstructions parses instructions from the comments in a Go source file. 110 | // It returns nil if none were parsed. 111 | func parseInstructions(t *testing.T, filename string, src []byte) []instruction { 112 | fset := token.NewFileSet() 113 | f, err := parser.ParseFile(fset, filename, src, parser.ParseComments) 114 | if err != nil { 115 | t.Fatalf("Test file %v does not parse: %v", filename, err) 116 | } 117 | var ins []instruction 118 | for _, cg := range f.Comments { 119 | ln := fset.Position(cg.Pos()).Line 120 | raw := cg.Text() 121 | for _, line := range strings.Split(raw, "\n") { 122 | if line == "" || strings.HasPrefix(line, "#") { 123 | continue 124 | } 125 | if line == "OK" && ins == nil { 126 | // so our return value will be non-nil 127 | ins = make([]instruction, 0) 128 | continue 129 | } 130 | if strings.Contains(line, "MATCH") { 131 | rx, err := extractPattern(line) 132 | if err != nil { 133 | t.Fatalf("At %v:%d: %v", filename, ln, err) 134 | } 135 | matchLine := ln 136 | if i := strings.Index(line, "MATCH:"); i >= 0 { 137 | // This is a match for a different line. 138 | lns := strings.TrimPrefix(line[i:], "MATCH:") 139 | lns = lns[:strings.Index(lns, " ")] 140 | matchLine, err = strconv.Atoi(lns) 141 | if err != nil { 142 | t.Fatalf("Bad match line number %q at %v:%d: %v", lns, filename, ln, err) 143 | } 144 | } 145 | var repl string 146 | if r, ok := extractReplacement(line); ok { 147 | repl = r 148 | } 149 | ins = append(ins, instruction{ 150 | Line: matchLine, 151 | Match: rx, 152 | Replacement: repl, 153 | }) 154 | } 155 | } 156 | } 157 | return ins 158 | } 159 | 160 | func extractPattern(line string) (*regexp.Regexp, error) { 161 | a, b := strings.Index(line, "/"), strings.LastIndex(line, "/") 162 | if a == -1 || a == b { 163 | return nil, fmt.Errorf("malformed match instruction %q", line) 164 | } 165 | pat := line[a+1 : b] 166 | rx, err := regexp.Compile(pat) 167 | if err != nil { 168 | return nil, fmt.Errorf("bad match pattern %q: %v", pat, err) 169 | } 170 | return rx, nil 171 | } 172 | 173 | func extractReplacement(line string) (string, bool) { 174 | // Look for this: / -> ` 175 | // (the end of a match and start of a backtick string), 176 | // and then the closing backtick. 177 | const start = "/ -> `" 178 | a, b := strings.Index(line, start), strings.LastIndex(line, "`") 179 | if a < 0 || a > b { 180 | return "", false 181 | } 182 | return line[a+len(start) : b], true 183 | } 184 | 185 | func render(fset *token.FileSet, x interface{}) string { 186 | var buf bytes.Buffer 187 | if err := printer.Fprint(&buf, fset, x); err != nil { 188 | panic(err) 189 | } 190 | return buf.String() 191 | } 192 | 193 | func TestLine(t *testing.T) { 194 | tests := []struct { 195 | src string 196 | offset int 197 | want string 198 | }{ 199 | {"single line file", 5, "single line file"}, 200 | {"single line file with newline\n", 5, "single line file with newline\n"}, 201 | {"first\nsecond\nthird\n", 2, "first\n"}, 202 | {"first\nsecond\nthird\n", 9, "second\n"}, 203 | {"first\nsecond\nthird\n", 14, "third\n"}, 204 | {"first\nsecond\nthird with no newline", 16, "third with no newline"}, 205 | {"first byte\n", 0, "first byte\n"}, 206 | } 207 | for _, test := range tests { 208 | got := srcLine([]byte(test.src), token.Position{Offset: test.offset}) 209 | if got != test.want { 210 | t.Errorf("srcLine(%q, offset=%d) = %q, want %q", test.src, test.offset, got, test.want) 211 | } 212 | } 213 | } 214 | 215 | func TestLintName(t *testing.T) { 216 | tests := []struct { 217 | name, want string 218 | }{ 219 | {"foo_bar", "fooBar"}, 220 | {"foo_bar_baz", "fooBarBaz"}, 221 | {"Foo_bar", "FooBar"}, 222 | {"foo_WiFi", "fooWiFi"}, 223 | {"id", "id"}, 224 | {"Id", "ID"}, 225 | {"foo_id", "fooID"}, 226 | {"fooId", "fooID"}, 227 | {"fooUid", "fooUID"}, 228 | {"idFoo", "idFoo"}, 229 | {"uidFoo", "uidFoo"}, 230 | {"midIdDle", "midIDDle"}, 231 | {"APIProxy", "APIProxy"}, 232 | {"ApiProxy", "APIProxy"}, 233 | {"apiProxy", "apiProxy"}, 234 | {"_Leading", "_Leading"}, 235 | {"___Leading", "_Leading"}, 236 | {"trailing_", "trailing"}, 237 | {"trailing___", "trailing"}, 238 | {"a_b", "aB"}, 239 | {"a__b", "aB"}, 240 | {"a___b", "aB"}, 241 | {"Rpc1150", "RPC1150"}, 242 | {"case3_1", "case3_1"}, 243 | {"case3__1", "case3_1"}, 244 | {"IEEE802_16bit", "IEEE802_16bit"}, 245 | {"IEEE802_16Bit", "IEEE802_16Bit"}, 246 | } 247 | for _, test := range tests { 248 | got := lintName(test.name) 249 | if got != test.want { 250 | t.Errorf("lintName(%q) = %q, want %q", test.name, got, test.want) 251 | } 252 | } 253 | } 254 | 255 | func TestExportedType(t *testing.T) { 256 | tests := []struct { 257 | typString string 258 | exp bool 259 | }{ 260 | {"int", true}, 261 | {"string", false}, // references the shadowed builtin "string" 262 | {"T", true}, 263 | {"t", false}, 264 | {"*T", true}, 265 | {"*t", false}, 266 | {"map[int]complex128", true}, 267 | } 268 | for _, test := range tests { 269 | src := `package foo; type T int; type t int; type string struct{}` 270 | fset := token.NewFileSet() 271 | file, err := parser.ParseFile(fset, "foo.go", src, 0) 272 | if err != nil { 273 | t.Fatalf("Parsing %q: %v", src, err) 274 | } 275 | // use the package name as package path 276 | config := &types.Config{} 277 | pkg, err := config.Check(file.Name.Name, fset, []*ast.File{file}, nil) 278 | if err != nil { 279 | t.Fatalf("Type checking %q: %v", src, err) 280 | } 281 | tv, err := types.Eval(fset, pkg, token.NoPos, test.typString) 282 | if err != nil { 283 | t.Errorf("types.Eval(%q): %v", test.typString, err) 284 | continue 285 | } 286 | if got := exportedType(tv.Type); got != test.exp { 287 | t.Errorf("exportedType(%v) = %t, want %t", tv.Type, got, test.exp) 288 | } 289 | } 290 | } 291 | 292 | func TestIsGenerated(t *testing.T) { 293 | tests := []struct { 294 | source string 295 | generated bool 296 | }{ 297 | {"// Code Generated by some tool. DO NOT EDIT.", false}, 298 | {"// Code generated by some tool. DO NOT EDIT.", true}, 299 | {"// Code generated by some tool. DO NOT EDIT", false}, 300 | {"// Code generated DO NOT EDIT.", true}, 301 | {"// Code generated DO NOT EDIT.", false}, 302 | {"\t\t// Code generated by some tool. DO NOT EDIT.\npackage foo\n", false}, 303 | {"// Code generated by some tool. DO NOT EDIT.\npackage foo\n", true}, 304 | {"package foo\n// Code generated by some tool. DO NOT EDIT.\ntype foo int\n", true}, 305 | {"package foo\n // Code generated by some tool. DO NOT EDIT.\ntype foo int\n", false}, 306 | {"package foo\n// Code generated by some tool. DO NOT EDIT. \ntype foo int\n", false}, 307 | {"package foo\ntype foo int\n// Code generated by some tool. DO NOT EDIT.\n", true}, 308 | {"package foo\ntype foo int\n// Code generated by some tool. DO NOT EDIT.", true}, 309 | } 310 | 311 | for i, test := range tests { 312 | got := isGenerated([]byte(test.source)) 313 | if got != test.generated { 314 | t.Errorf("test %d, isGenerated() = %v, want %v", i, got, test.generated) 315 | } 316 | } 317 | } 318 | -------------------------------------------------------------------------------- /misc/emacs/golint.el: -------------------------------------------------------------------------------- 1 | ;;; golint.el --- lint for the Go source code 2 | 3 | ;; Copyright 2013 The Go Authors. All rights reserved. 4 | 5 | ;; License: BSD-3-clause 6 | ;; URL: https://github.com/golang/lint 7 | 8 | ;;; Commentary: 9 | 10 | ;; To install golint, add the following lines to your .emacs file: 11 | ;; (add-to-list 'load-path "PATH CONTAINING golint.el" t) 12 | ;; (require 'golint) 13 | ;; 14 | ;; After this, type M-x golint on Go source code. 15 | ;; 16 | ;; Usage: 17 | ;; C-x ` 18 | ;; Jump directly to the line in your code which caused the first message. 19 | ;; 20 | ;; For more usage, see Compilation-Mode: 21 | ;; http://www.gnu.org/software/emacs/manual/html_node/emacs/Compilation-Mode.html 22 | 23 | ;;; Code: 24 | 25 | (require 'compile) 26 | 27 | (defun go-lint-buffer-name (mode) 28 | "*Golint*") 29 | 30 | (defun golint-process-setup () 31 | "Setup compilation variables and buffer for `golint'." 32 | (run-hooks 'golint-setup-hook)) 33 | 34 | (define-compilation-mode golint-mode "golint" 35 | "Golint is a linter for Go source code." 36 | (set (make-local-variable 'compilation-scroll-output) nil) 37 | (set (make-local-variable 'compilation-disable-input) t) 38 | (set (make-local-variable 'compilation-process-setup-function) 39 | 'golint-process-setup)) 40 | 41 | ;;;###autoload 42 | (defun golint () 43 | "Run golint on the current file and populate the fix list. 44 | Pressing \"C-x `\" jumps directly to the line in your code which 45 | caused the first message." 46 | (interactive) 47 | (compilation-start 48 | (mapconcat #'shell-quote-argument 49 | (list "golint" (expand-file-name buffer-file-name)) " ") 50 | 'golint-mode)) 51 | 52 | (provide 'golint) 53 | 54 | ;;; golint.el ends here 55 | -------------------------------------------------------------------------------- /misc/vim/ftplugin/go/lint.vim: -------------------------------------------------------------------------------- 1 | " Copyright 2013 The Go Authors. All rights reserved. 2 | " Use of this source code is governed by a BSD-style 3 | " license that can be found in the LICENSE file. 4 | " 5 | " lint.vim: Vim command to lint Go files with golint. 6 | " 7 | " https://github.com/golang/lint 8 | " 9 | " This filetype plugin add a new commands for go buffers: 10 | " 11 | " :Lint 12 | " 13 | " Run golint for the current Go file. 14 | " 15 | if exists("b:did_ftplugin_go_lint") 16 | finish 17 | endif 18 | 19 | if !executable("golint") 20 | finish 21 | endif 22 | 23 | command! -buffer Lint call s:GoLint() 24 | 25 | function! s:GoLint() abort 26 | cexpr system('golint ' . shellescape(expand('%'))) 27 | endfunction 28 | 29 | let b:did_ftplugin_go_lint = 1 30 | 31 | " vim:ts=4:sw=4:et 32 | -------------------------------------------------------------------------------- /testdata/4.go: -------------------------------------------------------------------------------- 1 | // Test that exported names have correct comments. 2 | 3 | // Package pkg does something. 4 | package pkg 5 | 6 | import "time" 7 | 8 | type T int // MATCH /exported type T.*should.*comment.*or.*unexport/ 9 | 10 | func (T) F() {} // MATCH /exported method T\.F.*should.*comment.*or.*unexport/ 11 | 12 | // this is a nice type. 13 | // MATCH /comment.*exported type U.*should.*form.*"U ..."/ 14 | type U string 15 | 16 | // this is a neat function. 17 | // MATCH /comment.*exported method U\.G.*should.*form.*"G ..."/ 18 | func (U) G() {} 19 | 20 | // A V is a string. 21 | type V string 22 | 23 | // V.H has a pointer receiver 24 | 25 | func (*V) H() {} // MATCH /exported method V\.H.*should.*comment.*or.*unexport/ 26 | 27 | var W = "foo" // MATCH /exported var W.*should.*comment.*or.*unexport/ 28 | 29 | const X = "bar" // MATCH /exported const X.*should.*comment.*or.*unexport/ 30 | 31 | var Y, Z int // MATCH /exported var Z.*own declaration/ 32 | 33 | // Location should be okay, since the other var name is an underscore. 34 | var Location, _ = time.LoadLocation("Europe/Istanbul") // not Constantinople 35 | 36 | // this is improperly documented 37 | // MATCH /comment.*const.*Thing.*form.*"Thing ..."/ 38 | const Thing = "wonderful" 39 | -------------------------------------------------------------------------------- /testdata/5_test.go: -------------------------------------------------------------------------------- 1 | // This file ends in _test.go, so we should not warn about doc comments. 2 | // OK 3 | 4 | package pkg 5 | 6 | import "testing" 7 | 8 | type H int 9 | 10 | func TestSomething(t *testing.T) { 11 | } 12 | 13 | func TestSomething_suffix(t *testing.T) { 14 | } 15 | 16 | func ExampleBuffer_reader() { 17 | } 18 | -------------------------------------------------------------------------------- /testdata/blank-import-lib.go: -------------------------------------------------------------------------------- 1 | // Test that blank imports in library packages are flagged. 2 | 3 | // Package foo ... 4 | package foo 5 | 6 | // The instructions need to go before the imports below so they will not be 7 | // mistaken for documentation. 8 | 9 | /* MATCH /blank import/ */ import _ "encoding/json" 10 | 11 | import ( 12 | "fmt" 13 | 14 | /* MATCH /blank import/ */ _ "os" 15 | 16 | /* MATCH /blank import/ */ _ "net/http" 17 | _ "path" 18 | ) 19 | 20 | import _ "encoding/base64" // Don't gripe about this 21 | 22 | import ( 23 | // Don't gripe about these next two lines. 24 | _ "compress/zlib" 25 | _ "syscall" 26 | 27 | /* MATCH /blank import/ */ _ "path/filepath" 28 | ) 29 | 30 | import ( 31 | "go/ast" 32 | _ "go/scanner" // Don't gripe about this or the following line. 33 | _ "go/token" 34 | ) 35 | 36 | var ( 37 | _ fmt.Stringer // for "fmt" 38 | _ ast.Node // for "go/ast" 39 | ) 40 | -------------------------------------------------------------------------------- /testdata/blank-import-lib_test.go: -------------------------------------------------------------------------------- 1 | // Test that blank imports in test packages are not flagged. 2 | // OK 3 | 4 | // Package foo ... 5 | package foo 6 | 7 | // These are essentially the same imports as in the "library" package, but 8 | // these should not trigger the warning because this is a test. 9 | 10 | import _ "encoding/json" 11 | 12 | import ( 13 | "fmt" 14 | "testing" 15 | 16 | _ "os" 17 | 18 | _ "net/http" 19 | _ "path" 20 | ) 21 | 22 | var ( 23 | _ fmt.Stringer // for "fmt" 24 | _ testing.T // for "testing" 25 | ) 26 | -------------------------------------------------------------------------------- /testdata/blank-import-main.go: -------------------------------------------------------------------------------- 1 | // Test that blank imports in package main are not flagged. 2 | // OK 3 | 4 | // Binary foo ... 5 | package main 6 | 7 | import _ "fmt" 8 | 9 | import ( 10 | "os" 11 | _ "path" 12 | ) 13 | 14 | var _ os.File // for "os" 15 | -------------------------------------------------------------------------------- /testdata/broken.go: -------------------------------------------------------------------------------- 1 | // Test of code that is malformed, but accepted by go/parser. 2 | // See https://golang.org/issue/11271 for discussion. 3 | // OK 4 | 5 | // Package pkg ... 6 | package pkg 7 | 8 | // Foo is a method with a missing receiver. 9 | func () Foo() {} 10 | -------------------------------------------------------------------------------- /testdata/common-methods.go: -------------------------------------------------------------------------------- 1 | // Test that we don't nag for comments on common methods. 2 | // OK 3 | 4 | // Package pkg ... 5 | package pkg 6 | 7 | import "net/http" 8 | 9 | // T is ... 10 | type T int 11 | 12 | func (T) Error() string { return "" } 13 | func (T) String() string { return "" } 14 | func (T) ServeHTTP(w http.ResponseWriter, r *http.Request) {} 15 | func (T) Read(p []byte) (n int, err error) { return 0, nil } 16 | func (T) Write(p []byte) (n int, err error) { return 0, nil } 17 | func (T) Unwrap(err error) error { return nil } 18 | -------------------------------------------------------------------------------- /testdata/const-block.go: -------------------------------------------------------------------------------- 1 | // Test for docs in const blocks 2 | 3 | // Package foo ... 4 | package foo 5 | 6 | const ( 7 | // Prefix for something. 8 | // MATCH /InlineWhatever.*form/ 9 | InlineWhatever = "blah" 10 | 11 | Whatsit = "missing_comment" // MATCH /Whatsit.*should have comment.*block/ 12 | 13 | // We should only warn once per block for missing comments, 14 | // but always complain about malformed comments. 15 | 16 | WhosYourDaddy = "another_missing_one" 17 | 18 | // Something 19 | // MATCH /WhatDoesHeDo.*form/ 20 | WhatDoesHeDo = "it's not a tumor!" 21 | ) 22 | 23 | // These shouldn't need doc comments. 24 | const ( 25 | Alpha = "a" 26 | Beta = "b" 27 | Gamma = "g" 28 | ) 29 | 30 | // The comment on the previous const block shouldn't flow through to here. 31 | 32 | const UndocAgain = 6 // MATCH /UndocAgain.*should have comment/ 33 | 34 | const ( 35 | SomeUndocumented = 7 // MATCH /SomeUndocumented.*should have comment.*block/ 36 | ) 37 | -------------------------------------------------------------------------------- /testdata/context.go: -------------------------------------------------------------------------------- 1 | // Test that context.Context is the first arg to a function. 2 | 3 | // Package foo ... 4 | package foo 5 | 6 | import ( 7 | "context" 8 | ) 9 | 10 | // A proper context.Context location 11 | func x(ctx context.Context) { // ok 12 | } 13 | 14 | // A proper context.Context location 15 | func x(ctx context.Context, s string) { // ok 16 | } 17 | 18 | // An invalid context.Context location 19 | func y(s string, ctx context.Context) { // MATCH /context.Context should be the first parameter.*/ 20 | } 21 | 22 | // An invalid context.Context location with more than 2 args 23 | func y(s string, r int, ctx context.Context, x int) { // MATCH /context.Context should be the first parameter.*/ 24 | } 25 | -------------------------------------------------------------------------------- /testdata/contextkeytypes.go: -------------------------------------------------------------------------------- 1 | // Package contextkeytypes verifies that correct types are used as keys in 2 | // calls to context.WithValue. 3 | package contextkeytypes 4 | 5 | import ( 6 | "context" 7 | "fmt" 8 | ) 9 | 10 | type ctxKey struct{} 11 | 12 | func contextKeyTypeTests() { 13 | fmt.Println() // not in package context 14 | context.TODO() // wrong function 15 | c := context.Background() // wrong function 16 | context.WithValue(c, "foo", "bar") // MATCH /should not use basic type( untyped|)? string as key in context.WithValue/ 17 | context.WithValue(c, true, "bar") // MATCH /should not use basic type( untyped|)? bool as key in context.WithValue/ 18 | context.WithValue(c, 1, "bar") // MATCH /should not use basic type( untyped|)? int as key in context.WithValue/ 19 | context.WithValue(c, int8(1), "bar") // MATCH /should not use basic type int8 as key in context.WithValue/ 20 | context.WithValue(c, int16(1), "bar") // MATCH /should not use basic type int16 as key in context.WithValue/ 21 | context.WithValue(c, int32(1), "bar") // MATCH /should not use basic type int32 as key in context.WithValue/ 22 | context.WithValue(c, rune(1), "bar") // MATCH /should not use basic type rune as key in context.WithValue/ 23 | context.WithValue(c, int64(1), "bar") // MATCH /should not use basic type int64 as key in context.WithValue/ 24 | context.WithValue(c, uint(1), "bar") // MATCH /should not use basic type uint as key in context.WithValue/ 25 | context.WithValue(c, uint8(1), "bar") // MATCH /should not use basic type uint8 as key in context.WithValue/ 26 | context.WithValue(c, byte(1), "bar") // MATCH /should not use basic type byte as key in context.WithValue/ 27 | context.WithValue(c, uint16(1), "bar") // MATCH /should not use basic type uint16 as key in context.WithValue/ 28 | context.WithValue(c, uint32(1), "bar") // MATCH /should not use basic type uint32 as key in context.WithValue/ 29 | context.WithValue(c, uint64(1), "bar") // MATCH /should not use basic type uint64 as key in context.WithValue/ 30 | context.WithValue(c, uintptr(1), "bar") // MATCH /should not use basic type uintptr as key in context.WithValue/ 31 | context.WithValue(c, float32(1.0), "bar") // MATCH /should not use basic type float32 as key in context.WithValue/ 32 | context.WithValue(c, float64(1.0), "bar") // MATCH /should not use basic type float64 as key in context.WithValue/ 33 | context.WithValue(c, complex64(1i), "bar") // MATCH /should not use basic type complex64 as key in context.WithValue/ 34 | context.WithValue(c, complex128(1i), "bar") // MATCH /should not use basic type complex128 as key in context.WithValue/ 35 | context.WithValue(c, ctxKey{}, "bar") // ok 36 | context.WithValue(c, &ctxKey{}, "bar") // ok 37 | context.WithValue(c, invalid{}, "bar") // ok 38 | } 39 | -------------------------------------------------------------------------------- /testdata/else-multi.go: -------------------------------------------------------------------------------- 1 | // Test of return+else warning; should not trigger on multi-branch if/else. 2 | // OK 3 | 4 | // Package pkg ... 5 | package pkg 6 | 7 | import "log" 8 | 9 | func f(x int) bool { 10 | if x == 0 { 11 | log.Print("x is zero") 12 | } else if x > 0 { 13 | return true 14 | } else { 15 | log.Printf("non-positive x: %d", x) 16 | } 17 | return false 18 | } 19 | 20 | func g(x int) int { 21 | if x == 0 { 22 | log.Print("x is zero") 23 | } else if x > 9 { 24 | return 2 25 | } else if x > 0 { 26 | return 1 27 | } else { 28 | log.Printf("non-positive x: %d", x) 29 | } 30 | return 0 31 | } 32 | 33 | func h(x int) int { 34 | if x == 0 { 35 | log.Print("x is zero") 36 | } else if x > 99 { 37 | return 3 38 | } else if x > 9 { 39 | return 2 40 | } else if x > 0 { 41 | return 1 42 | } else { 43 | log.Printf("non-positive x: %d", x) 44 | } 45 | return 0 46 | } 47 | -------------------------------------------------------------------------------- /testdata/else.go: -------------------------------------------------------------------------------- 1 | // Test of return+else warning. 2 | 3 | // Package pkg ... 4 | package pkg 5 | 6 | import "log" 7 | 8 | func f(x int) bool { 9 | if x > 0 { 10 | return true 11 | } else { // MATCH /if.*return.*else.*outdent/ 12 | log.Printf("non-positive x: %d", x) 13 | } 14 | return false 15 | } 16 | 17 | func g(f func() bool) string { 18 | if ok := f(); ok { 19 | return "it's okay" 20 | } else { // MATCH /if.*return.*else.*outdent.*short.*var.*declaration/ 21 | return "it's NOT okay!" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /testdata/error-return.go: -------------------------------------------------------------------------------- 1 | // Test for returning errors. 2 | 3 | // Package foo ... 4 | package foo 5 | 6 | // Returns nothing 7 | func f() { // ok 8 | } 9 | 10 | // Check for a single error return 11 | func g() error { // ok 12 | return nil 13 | } 14 | 15 | // Check for a single other return type 16 | func h() int { // ok 17 | return 0 18 | } 19 | 20 | // Check for multiple return but error at end. 21 | func i() (int, error) { // ok 22 | return 0, nil 23 | } 24 | 25 | // Check for multiple return but error at end with named variables. 26 | func j() (x int, err error) { // ok 27 | return 0, nil 28 | } 29 | 30 | // Check for error in the wrong location on 2 types 31 | func k() (error, int) { // MATCH /error should be the last type/ 32 | return nil, 0 33 | } 34 | 35 | // Check for error in the wrong location for > 2 types 36 | func l() (int, error, int) { // MATCH /error should be the last type/ 37 | return 0, nil, 0 38 | } 39 | 40 | // Check for error in the wrong location with named variables. 41 | func m() (x int, err error, y int) { // MATCH /error should be the last type/ 42 | return 0, nil, 0 43 | } 44 | 45 | // Check for multiple error returns but with errors at the end. 46 | func n() (int, error, error) { // OK 47 | return 0, nil, nil 48 | } 49 | 50 | // Check for multiple error returns mixed in order, but keeping one error at last position. 51 | func o() (int, error, int, error) { // OK 52 | return 0, nil, 0, nil 53 | } 54 | -------------------------------------------------------------------------------- /testdata/errorf-custom.go: -------------------------------------------------------------------------------- 1 | // Test for allowed errors.New(fmt.Sprintf()) when a custom errors package is imported. 2 | 3 | // Package foo ... 4 | package foo 5 | 6 | import ( 7 | "fmt" 8 | 9 | "github.com/pkg/errors" 10 | ) 11 | 12 | func f(x int) error { 13 | if x > 10 { 14 | return errors.New(fmt.Sprintf("something %d", x)) // OK 15 | } 16 | if x > 5 { 17 | return errors.New(g("blah")) // OK 18 | } 19 | if x > 4 { 20 | return errors.New("something else") // OK 21 | } 22 | return nil 23 | } 24 | 25 | func g(s string) string { return "prefix: " + s } 26 | -------------------------------------------------------------------------------- /testdata/errorf.go: -------------------------------------------------------------------------------- 1 | // Test for not using fmt.Errorf or testing.Errorf. 2 | 3 | // Package foo ... 4 | package foo 5 | 6 | import ( 7 | "errors" 8 | "fmt" 9 | "testing" 10 | ) 11 | 12 | func f(x int) error { 13 | if x > 10 { 14 | return errors.New(fmt.Sprintf("something %d", x)) // MATCH /should replace.*errors\.New\(fmt\.Sprintf\(\.\.\.\)\).*fmt\.Errorf\(\.\.\.\)/ -> ` return fmt.Errorf("something %d", x)` 15 | } 16 | if x > 5 { 17 | return errors.New(g("blah")) // ok 18 | } 19 | if x > 4 { 20 | return errors.New("something else") // ok 21 | } 22 | return nil 23 | } 24 | 25 | // TestF is a dummy test 26 | func TestF(t *testing.T) error { 27 | x := 1 28 | if x > 10 { 29 | return t.Error(fmt.Sprintf("something %d", x)) // MATCH /should replace.*t\.Error\(fmt\.Sprintf\(\.\.\.\)\).*t\.Errorf\(\.\.\.\)/ 30 | } 31 | if x > 5 { 32 | return t.Error(g("blah")) // ok 33 | } 34 | if x > 4 { 35 | return t.Error("something else") // ok 36 | } 37 | return nil 38 | } 39 | 40 | func g(s string) string { return "prefix: " + s } 41 | -------------------------------------------------------------------------------- /testdata/errors.go: -------------------------------------------------------------------------------- 1 | // Test for naming errors. 2 | 3 | // Package foo ... 4 | package foo 5 | 6 | import ( 7 | "errors" 8 | "fmt" 9 | ) 10 | 11 | var unexp = errors.New("some unexported error") // MATCH /error var.*unexp.*errFoo/ 12 | 13 | // Exp ... 14 | var Exp = errors.New("some exported error") // MATCH /error var.*Exp.*ErrFoo/ 15 | 16 | var ( 17 | e1 = fmt.Errorf("blah %d", 4) // MATCH /error var.*e1.*errFoo/ 18 | // E2 ... 19 | E2 = fmt.Errorf("blah %d", 5) // MATCH /error var.*E2.*ErrFoo/ 20 | ) 21 | 22 | func f() { 23 | var whatever = errors.New("ok") // ok 24 | _ = whatever 25 | } 26 | 27 | // Check for the error strings themselves. 28 | 29 | func g(x int) error { 30 | var err error 31 | err = fmt.Errorf("This %d is too low", x) // MATCH /error strings.*be capitalized/ 32 | err = fmt.Errorf("XML time") // ok 33 | err = fmt.Errorf("newlines are fun\n") // MATCH /error strings.*end with punctuation/ 34 | err = fmt.Errorf("Newlines are really fun\n") // MATCH /error strings.+not be capitalized/ 35 | err = errors.New(`too much stuff.`) // MATCH /error strings.*end with punctuation/ 36 | err = errors.New("This %d is too low", x) // MATCH /error strings.*be capitalized/ 37 | return err 38 | } 39 | -------------------------------------------------------------------------------- /testdata/import-dot.go: -------------------------------------------------------------------------------- 1 | // Test that dot imports are flagged. 2 | 3 | // Package pkg ... 4 | package pkg 5 | 6 | import . "fmt" // MATCH /dot import/ 7 | 8 | var _ Stringer // from "fmt" 9 | -------------------------------------------------------------------------------- /testdata/inc.go: -------------------------------------------------------------------------------- 1 | // Test for use of x++ and x--. 2 | 3 | // Package pkg ... 4 | package pkg 5 | 6 | func addOne(x int) int { 7 | x += 1 // MATCH /x\+\+/ 8 | return x 9 | } 10 | 11 | func subOneInLoop(y int) { 12 | for ; y > 0; y -= 1 { // MATCH /y--/ 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /testdata/names.go: -------------------------------------------------------------------------------- 1 | // Test for name linting. 2 | 3 | // Package pkg_with_underscores ... 4 | package pkg_with_underscores // MATCH /underscore.*package name/ 5 | 6 | import ( 7 | "io" 8 | "net" 9 | net_http "net/http" // renamed deliberately 10 | "net/url" 11 | ) 12 | 13 | import "C" 14 | 15 | var var_name int // MATCH /underscore.*var.*var_name/ 16 | 17 | type t_wow struct { // MATCH /underscore.*type.*t_wow/ 18 | x_damn int // MATCH /underscore.*field.*x_damn/ 19 | Url *url.URL // MATCH /struct field.*Url.*URL/ 20 | } 21 | 22 | const fooId = "blah" // MATCH /fooId.*fooID/ 23 | 24 | func f_it() { // MATCH /underscore.*func.*f_it/ 25 | more_underscore := 4 // MATCH /underscore.*var.*more_underscore/ 26 | _ = more_underscore 27 | var err error 28 | if isEof := (err == io.EOF); isEof { // MATCH /var.*isEof.*isEOF/ 29 | more_underscore = 7 // should be okay 30 | } 31 | 32 | x := net_http.Request{} // should be okay 33 | _ = x 34 | 35 | var ips []net.IP 36 | for _, theIp := range ips { // MATCH /range var.*theIp.*theIP/ 37 | _ = theIp 38 | } 39 | 40 | switch myJson := g(); { // MATCH /var.*myJson.*myJSON/ 41 | default: 42 | _ = myJson 43 | } 44 | var y net_http.ResponseWriter // an interface 45 | switch tApi := y.(type) { // MATCH /var.*tApi.*tAPI/ 46 | default: 47 | _ = tApi 48 | } 49 | 50 | var c chan int 51 | select { 52 | case qId := <-c: // MATCH /var.*qId.*qID/ 53 | _ = qId 54 | } 55 | } 56 | 57 | // Common styles in other languages that don't belong in Go. 58 | const ( 59 | CPP_CONST = 1 // MATCH /ALL_CAPS.*CamelCase/ 60 | kLeadingKay = 2 // MATCH /k.*leadingKay/ 61 | 62 | HTML = 3 // okay; no underscore 63 | X509B = 4 // ditto 64 | V1_10_5 = 5 // okay; fewer than two uppercase letters 65 | ) 66 | 67 | var kVarsAreSometimesUsedAsConstants = 0 // MATCH /k.*varsAreSometimesUsedAsConstants/ 68 | var ( 69 | kVarsAreSometimesUsedAsConstants2 = 0 // MATCH /k.*varsAreSometimesUsedAsConstants2/ 70 | ) 71 | 72 | var kThisIsNotOkay = struct { // MATCH /k.*thisIsNotOkay/ 73 | kThisIsOkay bool 74 | }{} 75 | 76 | func kThisIsOkay() { // this is okay because this is a function name 77 | var kThisIsAlsoOkay = 1 // this is okay because this is a non-top-level variable 78 | _ = kThisIsAlsoOkay 79 | const kThisIsNotOkay = 2 // MATCH /k.*thisIsNotOkay/ 80 | } 81 | 82 | var anotherFunctionScope = func() { 83 | var kThisIsOkay = 1 // this is okay because this is a non-top-level variable 84 | _ = kThisIsOkay 85 | const kThisIsNotOkay = 2 // MATCH /k.*thisIsNotOkay/} 86 | } 87 | 88 | func f(bad_name int) {} // MATCH /underscore.*func parameter.*bad_name/ 89 | func g() (no_way int) { return 0 } // MATCH /underscore.*func result.*no_way/ 90 | func (t *t_wow) f(more_under string) {} // MATCH /underscore.*method parameter.*more_under/ 91 | func (t *t_wow) g() (still_more string) { return "" } // MATCH /underscore.*method result.*still_more/ 92 | 93 | type i interface { 94 | CheckHtml() string // okay; interface method names are often constrained by the concrete types' method names 95 | 96 | F(foo_bar int) // MATCH /foo_bar.*fooBar/ 97 | } 98 | 99 | // All okay; underscore between digits 100 | const case1_1 = 1 101 | 102 | type case2_1 struct { 103 | case2_2 int 104 | } 105 | 106 | func case3_1(case3_2 int) (case3_3 string) { 107 | case3_4 := 4 108 | _ = case3_4 109 | 110 | return "" 111 | } 112 | 113 | type t struct{} 114 | 115 | func (t) LastInsertId() (int64, error) { return 0, nil } // okay because it matches a known style violation 116 | 117 | //export exported_to_c 118 | func exported_to_c() {} // okay: https://github.com/golang/lint/issues/144 119 | 120 | //export exported_to_c_with_arg 121 | func exported_to_c_with_arg(but_use_go_param_names int) // MATCH /underscore.*func parameter.*but_use_go_param_names/ 122 | 123 | // This is an exported C function with a leading doc comment. 124 | // 125 | //export exported_to_c_with_comment 126 | func exported_to_c_with_comment() {} // okay: https://github.com/golang/lint/issues/144 127 | 128 | //export maybe_exported_to_CPlusPlusWithCamelCase 129 | func maybe_exported_to_CPlusPlusWithCamelCase() {} // okay: https://github.com/golang/lint/issues/144 130 | 131 | // WhyAreYouUsingCapitalLetters_InACFunctionName is a Go-exported function that 132 | // is also exported to C as a name with underscores. 133 | // 134 | // Don't do that. If you want to use a C-style name for a C export, make it 135 | // lower-case and leave it out of the Go-exported API. 136 | // 137 | //export WhyAreYouUsingCapitalLetters_InACFunctionName 138 | func WhyAreYouUsingCapitalLetters_InACFunctionName() {} // MATCH /underscore.*func.*Why.*CFunctionName/ 139 | -------------------------------------------------------------------------------- /testdata/pkg-caps.go: -------------------------------------------------------------------------------- 1 | // MixedCaps package name 2 | 3 | // Package PkgName ... 4 | package PkgName // MATCH /don't use MixedCaps in package name/ 5 | -------------------------------------------------------------------------------- /testdata/pkg-doc1.go: -------------------------------------------------------------------------------- 1 | // Test of missing package comment. 2 | 3 | package foo // MATCH /should.*package comment.*unless/ 4 | -------------------------------------------------------------------------------- /testdata/pkg-doc2.go: -------------------------------------------------------------------------------- 1 | // Test of package comment in an incorrect form. 2 | 3 | // Some random package doc that isn't in the right form. 4 | // MATCH /package comment should.*form.*"Package testdata .*"/ 5 | package testdata 6 | -------------------------------------------------------------------------------- /testdata/pkg-doc3.go: -------------------------------------------------------------------------------- 1 | // Test of block package comment. 2 | // OK 3 | 4 | /* 5 | Package foo is pretty sweet. 6 | */ 7 | package foo 8 | -------------------------------------------------------------------------------- /testdata/pkg-doc4.go: -------------------------------------------------------------------------------- 1 | // Test of block package comment with leading space. 2 | 3 | /* 4 | Package foo is pretty sweet. 5 | MATCH /package comment.*leading space/ 6 | */ 7 | package foo 8 | -------------------------------------------------------------------------------- /testdata/pkg-doc5.go: -------------------------------------------------------------------------------- 1 | // Test of detached package comment. 2 | 3 | /* 4 | Package foo is pretty sweet. 5 | */ 6 | 7 | package foo 8 | 9 | // MATCH:6 /package comment.*detached/ 10 | -------------------------------------------------------------------------------- /testdata/pkg-main.go: -------------------------------------------------------------------------------- 1 | // Test of package comment for package main. 2 | // OK 3 | 4 | // This binary does something awesome. 5 | package main 6 | -------------------------------------------------------------------------------- /testdata/range.go: -------------------------------------------------------------------------------- 1 | // Test for range construction. 2 | 3 | // Package foo ... 4 | package foo 5 | 6 | func f() { 7 | var m map[string]int 8 | 9 | // with := 10 | for x, _ := range m { // MATCH /should omit 2nd value.*range.*equivalent.*for x := range/ -> ` for x := range m {` 11 | _ = x 12 | } 13 | // with = 14 | var y string 15 | _ = y 16 | for y, _ = range m { // MATCH /should omit 2nd value.*range.*equivalent.*for y = range/ 17 | } 18 | 19 | for _ = range m { // MATCH /should omit values.*range.*equivalent.*for range/ 20 | } 21 | 22 | for _, _ = range m { // MATCH /should omit values.*range.*equivalent.*for range/ 23 | } 24 | 25 | // all OK: 26 | for x := range m { 27 | _ = x 28 | } 29 | for x, y := range m { 30 | _, _ = x, y 31 | } 32 | for _, y := range m { 33 | _ = y 34 | } 35 | var x int 36 | _ = x 37 | for y = range m { 38 | } 39 | for y, x = range m { 40 | } 41 | for _, x = range m { 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /testdata/receiver-names.go: -------------------------------------------------------------------------------- 1 | // Test for bad receiver names. 2 | 3 | // Package foo ... 4 | package foo 5 | 6 | type foo struct{} 7 | 8 | func (this foo) f1() { // MATCH /should be a reflection of its identity/ 9 | } 10 | 11 | func (self foo) f2() { // MATCH /should be a reflection of its identity/ 12 | } 13 | 14 | func (f foo) f3() { 15 | } 16 | 17 | func (foo) f4() { 18 | } 19 | 20 | type bar struct{} 21 | 22 | func (b bar) f1() { 23 | } 24 | 25 | func (b bar) f2() { 26 | } 27 | 28 | func (a bar) f3() { // MATCH /receiver name a should be consistent with previous receiver name b for bar/ 29 | } 30 | 31 | func (a *bar) f4() { // MATCH /receiver name a should be consistent with previous receiver name b for bar/ 32 | } 33 | 34 | func (b *bar) f5() { 35 | } 36 | 37 | func (bar) f6() { 38 | } 39 | 40 | func (_ *bar) f7() { // MATCH /receiver name should not be an underscore/ 41 | } 42 | 43 | type multiError struct{} 44 | 45 | func (me multiError) f8() { 46 | } 47 | 48 | // Regression test for a panic caused by ill-formed receiver type. 49 | func (recv []*x.y) f() 50 | -------------------------------------------------------------------------------- /testdata/sort.go: -------------------------------------------------------------------------------- 1 | // Test that we don't ask for comments on sort.Interface methods. 2 | 3 | // Package pkg ... 4 | package pkg 5 | 6 | // T is ... 7 | type T []int 8 | 9 | // Len by itself should get documented. 10 | 11 | func (t T) Len() int { return len(t) } // MATCH /exported method T\.Len.*should.*comment/ 12 | 13 | // U is ... 14 | type U []int 15 | 16 | func (u U) Len() int { return len(u) } 17 | func (u U) Less(i, j int) bool { return u[i] < u[j] } 18 | func (u U) Swap(i, j int) { u[i], u[j] = u[j], u[i] } 19 | 20 | func (u U) Other() {} // MATCH /exported method U\.Other.*should.*comment/ 21 | -------------------------------------------------------------------------------- /testdata/stutter.go: -------------------------------------------------------------------------------- 1 | // Test of stuttery names. 2 | 3 | // Package donut ... 4 | package donut 5 | 6 | // DonutMaker makes donuts. 7 | type DonutMaker struct{} // MATCH /donut\.DonutMaker.*stutter/ 8 | 9 | // DonutRank computes the ranking of a donut. 10 | func DonutRank(d Donut) int { // MATCH /donut\.DonutRank.*stutter/ 11 | return 0 12 | } 13 | 14 | // Donut is a delicious treat. 15 | type Donut struct{} // ok because it is the whole name 16 | 17 | // Donuts are great, aren't they? 18 | type Donuts []Donut // ok because it didn't start a new word 19 | 20 | type donutGlaze int // ok because it is unexported 21 | 22 | // DonutMass reports the mass of a donut. 23 | func (d *Donut) DonutMass() (grams int) { // okay because it is a method 24 | return 38 25 | } 26 | -------------------------------------------------------------------------------- /testdata/time.go: -------------------------------------------------------------------------------- 1 | // Test of time suffixes. 2 | 3 | // Package foo ... 4 | package foo 5 | 6 | import ( 7 | "flag" 8 | "time" 9 | ) 10 | 11 | var rpcTimeoutMsec = flag.Duration("rpc_timeout", 100*time.Millisecond, "some flag") // MATCH /Msec.*\*time.Duration/ 12 | 13 | var timeoutSecs = 5 * time.Second // MATCH /Secs.*time.Duration/ 14 | -------------------------------------------------------------------------------- /testdata/unexp-return.go: -------------------------------------------------------------------------------- 1 | // Test for unexported return types. 2 | 3 | // Package foo ... 4 | package foo 5 | 6 | import () 7 | 8 | type hidden struct{} 9 | 10 | // Exported returns a hidden type, which is annoying. 11 | func Exported() hidden { // MATCH /Exported.*unexported.*hidden/ 12 | return hidden{} 13 | } 14 | 15 | // ExpErr returns a builtin type. 16 | func ExpErr() error { // ok 17 | } 18 | 19 | func (hidden) ExpOnHidden() hidden { // ok 20 | } 21 | 22 | // T is another test type. 23 | type T struct{} 24 | 25 | // MethodOnT returns a hidden type, which is annoying. 26 | func (T) MethodOnT() hidden { // MATCH /method MethodOnT.*unexported.*hidden/ 27 | return hidden{} 28 | } 29 | 30 | // ExpT returns a T. 31 | func ExpT() T { // ok 32 | return T{} 33 | } 34 | 35 | func unexp() hidden { // ok 36 | return hidden{} 37 | } 38 | 39 | // This is slightly sneaky: we shadow the builtin "int" type. 40 | 41 | type int struct{} 42 | 43 | // ExportedIntReturner returns an unexported type from this package. 44 | func ExportedIntReturner() int { // MATCH /ExportedIntReturner.*unexported.*int/ 45 | return int{} 46 | } 47 | --------------------------------------------------------------------------------