pax_global_header00006660000000000000000000000064130000074500014477gustar00rootroot0000000000000052 comment=3390df4df2787994aea98de825b964ac7944b817 golint-0.0+git20161013.3390df4/000077500000000000000000000000001300000745000152225ustar00rootroot00000000000000golint-0.0+git20161013.3390df4/.travis.yml000066400000000000000000000001401300000745000173260ustar00rootroot00000000000000language: go go: - 1.6 - 1.7 install: - go get -t -v ./... script: - go test -v ./... golint-0.0+git20161013.3390df4/CONTRIBUTING.md000066400000000000000000000006321300000745000174540ustar00rootroot00000000000000# Contributing to Golint ## Before filing an issue: ### Are you having trouble building golint? Check you have the latest version of its dependencies. Run ``` go get -u github.com/golang/lint ``` If you still have problems, consider searching for existing issues before filing a new issue. ## Before sending a pull request: Have you understood the purpose of golint? Make sure to carefully read `README`. golint-0.0+git20161013.3390df4/LICENSE000066400000000000000000000027071300000745000162350ustar00rootroot00000000000000Copyright (c) 2013 The Go Authors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Google Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. golint-0.0+git20161013.3390df4/README.md000066400000000000000000000060321300000745000165020ustar00rootroot00000000000000Golint is a linter for Go source code. [![Build Status](https://travis-ci.org/golang/lint.svg?branch=master)](https://travis-ci.org/golang/lint) ## Installation Golint requires Go 1.6 or later. go get -u github.com/golang/lint/golint ## Usage Invoke `golint` with one or more filenames, a directory, or a package named by its import path. Golint uses the same [import path syntax](https://golang.org/cmd/go/#hdr-Import_path_syntax) as the `go` command and therefore also supports relative import paths like `./...`. Additionally the `...` wildcard can be used as suffix on relative and absolute file paths to recurse into them. The output of this tool is a list of suggestions in Vim quickfix format, which is accepted by lots of different editors. ## Purpose Golint differs from gofmt. Gofmt reformats Go source code, whereas golint prints out style mistakes. Golint differs from govet. Govet is concerned with correctness, whereas golint is concerned with coding style. Golint is in use at Google, and it seeks to match the accepted style of the open source Go project. The suggestions made by golint are exactly that: suggestions. Golint is not perfect, and has both false positives and false negatives. Do not treat its output as a gold standard. We will not be adding pragmas or other knobs to suppress specific warnings, so do not expect or require code to be completely "lint-free". In short, this tool is not, and will never be, trustworthy enough for its suggestions to be enforced automatically, for example as part of a build process. Golint makes suggestions for many of the mechanically checkable items listed in [Effective Go](https://golang.org/doc/effective_go.html) and the [CodeReviewComments wiki page](https://golang.org/wiki/CodeReviewComments). If you find an established style that is frequently violated, and which you think golint could statically check, [file an issue](https://github.com/golang/lint/issues). ## Contributions Contributions to this project are welcome, though please send mail before starting work on anything major. Contributors retain their copyright, so we need you to fill out [a short form](https://developers.google.com/open-source/cla/individual) before we can accept your contribution. ## Vim Add this to your ~/.vimrc: set rtp+=$GOPATH/src/github.com/golang/lint/misc/vim If you have multiple entries in your GOPATH, replace `$GOPATH` with the right value. Running `:Lint` will run golint on the current file and populate the quickfix list. Optionally, add this to your `~/.vimrc` to automatically run `golint` on `:w` autocmd BufWritePost,FileWritePost *.go execute 'Lint' | cwindow ## Emacs Add this to your `.emacs` file: (add-to-list 'load-path (concat (getenv "GOPATH") "/src/github.com/golang/lint/misc/emacs")) (require 'golint) If you have multiple entries in your GOPATH, replace `$GOPATH` with the right value. Running M-x golint will run golint on the current file. For more usage, see [Compilation-Mode](http://www.gnu.org/software/emacs/manual/html_node/emacs/Compilation-Mode.html). golint-0.0+git20161013.3390df4/golint/000077500000000000000000000000001300000745000165165ustar00rootroot00000000000000golint-0.0+git20161013.3390df4/golint/golint.go000066400000000000000000000061261300000745000203460ustar00rootroot00000000000000// Copyright (c) 2013 The Go Authors. All rights reserved. // // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file or at // https://developers.google.com/open-source/licenses/bsd. // golint lints the Go source files named on its command line. package main import ( "flag" "fmt" "go/build" "io/ioutil" "os" "path/filepath" "strings" "github.com/golang/lint" ) var ( minConfidence = flag.Float64("min_confidence", 0.8, "minimum confidence of a problem to print it") setExitStatus = flag.Bool("set_exit_status", false, "set exit status to 1 if any issues are found") suggestions int ) func usage() { fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0]) fmt.Fprintf(os.Stderr, "\tgolint [flags] # runs on package in current directory\n") fmt.Fprintf(os.Stderr, "\tgolint [flags] package\n") fmt.Fprintf(os.Stderr, "\tgolint [flags] directory\n") fmt.Fprintf(os.Stderr, "\tgolint [flags] files... # must be a single package\n") fmt.Fprintf(os.Stderr, "Flags:\n") flag.PrintDefaults() } func main() { flag.Usage = usage flag.Parse() switch flag.NArg() { case 0: lintDir(".") case 1: arg := flag.Arg(0) if strings.HasSuffix(arg, "/...") && isDir(arg[:len(arg)-4]) { for _, dirname := range allPackagesInFS(arg) { lintDir(dirname) } } else if isDir(arg) { lintDir(arg) } else if exists(arg) { lintFiles(arg) } else { for _, pkgname := range importPaths([]string{arg}) { lintPackage(pkgname) } } default: lintFiles(flag.Args()...) } if *setExitStatus && suggestions > 0 { fmt.Fprintf(os.Stderr, "Found %d lint suggestions; failing.\n", suggestions) os.Exit(1) } } func isDir(filename string) bool { fi, err := os.Stat(filename) return err == nil && fi.IsDir() } func exists(filename string) bool { _, err := os.Stat(filename) return err == nil } func lintFiles(filenames ...string) { files := make(map[string][]byte) for _, filename := range filenames { src, err := ioutil.ReadFile(filename) if err != nil { fmt.Fprintln(os.Stderr, err) continue } files[filename] = src } l := new(lint.Linter) ps, err := l.LintFiles(files) if err != nil { fmt.Fprintf(os.Stderr, "%v\n", err) return } for _, p := range ps { if p.Confidence >= *minConfidence { fmt.Printf("%v: %s\n", p.Position, p.Text) suggestions++ } } } func lintDir(dirname string) { pkg, err := build.ImportDir(dirname, 0) lintImportedPackage(pkg, err) } func lintPackage(pkgname string) { pkg, err := build.Import(pkgname, ".", 0) lintImportedPackage(pkg, err) } func lintImportedPackage(pkg *build.Package, err error) { if err != nil { if _, nogo := err.(*build.NoGoError); nogo { // Don't complain if the failure is due to no Go source files. return } fmt.Fprintln(os.Stderr, err) return } var files []string files = append(files, pkg.GoFiles...) files = append(files, pkg.CgoFiles...) files = append(files, pkg.TestGoFiles...) if pkg.Dir != "." { for i, f := range files { files[i] = filepath.Join(pkg.Dir, f) } } // TODO(dsymonds): Do foo_test too (pkg.XTestGoFiles) lintFiles(files...) } golint-0.0+git20161013.3390df4/golint/import.go000066400000000000000000000204111300000745000203550ustar00rootroot00000000000000package main /* This file holds a direct copy of the import path matching code of https://github.com/golang/go/blob/master/src/cmd/go/main.go. It can be replaced when https://golang.org/issue/8768 is resolved. It has been updated to follow upstream changes in a few ways. */ import ( "fmt" "go/build" "log" "os" "path" "path/filepath" "regexp" "runtime" "strings" ) var buildContext = build.Default var ( goroot = filepath.Clean(runtime.GOROOT()) gorootSrc = filepath.Join(goroot, "src") ) // importPathsNoDotExpansion returns the import paths to use for the given // command line, but it does no ... expansion. func importPathsNoDotExpansion(args []string) []string { if len(args) == 0 { return []string{"."} } var out []string for _, a := range args { // Arguments are supposed to be import paths, but // as a courtesy to Windows developers, rewrite \ to / // in command-line arguments. Handles .\... and so on. if filepath.Separator == '\\' { a = strings.Replace(a, `\`, `/`, -1) } // Put argument in canonical form, but preserve leading ./. if strings.HasPrefix(a, "./") { a = "./" + path.Clean(a) if a == "./." { a = "." } } else { a = path.Clean(a) } if a == "all" || a == "std" { out = append(out, allPackages(a)...) continue } out = append(out, a) } return out } // importPaths returns the import paths to use for the given command line. func importPaths(args []string) []string { args = importPathsNoDotExpansion(args) var out []string for _, a := range args { if strings.Contains(a, "...") { if build.IsLocalImport(a) { out = append(out, allPackagesInFS(a)...) } else { out = append(out, allPackages(a)...) } continue } out = append(out, a) } return out } // matchPattern(pattern)(name) reports whether // name matches pattern. Pattern is a limited glob // pattern in which '...' means 'any string' and there // is no other special syntax. func matchPattern(pattern string) func(name string) bool { re := regexp.QuoteMeta(pattern) re = strings.Replace(re, `\.\.\.`, `.*`, -1) // Special case: foo/... matches foo too. if strings.HasSuffix(re, `/.*`) { re = re[:len(re)-len(`/.*`)] + `(/.*)?` } reg := regexp.MustCompile(`^` + re + `$`) return func(name string) bool { return reg.MatchString(name) } } // hasPathPrefix reports whether the path s begins with the // elements in prefix. func hasPathPrefix(s, prefix string) bool { switch { default: return false case len(s) == len(prefix): return s == prefix case len(s) > len(prefix): if prefix != "" && prefix[len(prefix)-1] == '/' { return strings.HasPrefix(s, prefix) } return s[len(prefix)] == '/' && s[:len(prefix)] == prefix } } // treeCanMatchPattern(pattern)(name) reports whether // name or children of name can possibly match pattern. // Pattern is the same limited glob accepted by matchPattern. func treeCanMatchPattern(pattern string) func(name string) bool { wildCard := false if i := strings.Index(pattern, "..."); i >= 0 { wildCard = true pattern = pattern[:i] } return func(name string) bool { return len(name) <= len(pattern) && hasPathPrefix(pattern, name) || wildCard && strings.HasPrefix(name, pattern) } } // allPackages returns all the packages that can be found // under the $GOPATH directories and $GOROOT matching pattern. // The pattern is either "all" (all packages), "std" (standard packages) // or a path including "...". func allPackages(pattern string) []string { pkgs := matchPackages(pattern) if len(pkgs) == 0 { fmt.Fprintf(os.Stderr, "warning: %q matched no packages\n", pattern) } return pkgs } func matchPackages(pattern string) []string { match := func(string) bool { return true } treeCanMatch := func(string) bool { return true } if pattern != "all" && pattern != "std" { match = matchPattern(pattern) treeCanMatch = treeCanMatchPattern(pattern) } have := map[string]bool{ "builtin": true, // ignore pseudo-package that exists only for documentation } if !buildContext.CgoEnabled { have["runtime/cgo"] = true // ignore during walk } var pkgs []string // Commands cmd := filepath.Join(goroot, "src/cmd") + string(filepath.Separator) filepath.Walk(cmd, func(path string, fi os.FileInfo, err error) error { if err != nil || !fi.IsDir() || path == cmd { return nil } name := path[len(cmd):] if !treeCanMatch(name) { return filepath.SkipDir } // Commands are all in cmd/, not in subdirectories. if strings.Contains(name, string(filepath.Separator)) { return filepath.SkipDir } // We use, e.g., cmd/gofmt as the pseudo import path for gofmt. name = "cmd/" + name if have[name] { return nil } have[name] = true if !match(name) { return nil } _, err = buildContext.ImportDir(path, 0) if err != nil { if _, noGo := err.(*build.NoGoError); !noGo { log.Print(err) } return nil } pkgs = append(pkgs, name) return nil }) for _, src := range buildContext.SrcDirs() { if (pattern == "std" || pattern == "cmd") && src != gorootSrc { continue } src = filepath.Clean(src) + string(filepath.Separator) root := src if pattern == "cmd" { root += "cmd" + string(filepath.Separator) } filepath.Walk(root, func(path string, fi os.FileInfo, err error) error { if err != nil || !fi.IsDir() || path == src { return nil } // Avoid .foo, _foo, and testdata directory trees. _, elem := filepath.Split(path) if strings.HasPrefix(elem, ".") || strings.HasPrefix(elem, "_") || elem == "testdata" { return filepath.SkipDir } name := filepath.ToSlash(path[len(src):]) if pattern == "std" && (strings.Contains(name, ".") || name == "cmd") { // The name "std" is only the standard library. // If the name is cmd, it's the root of the command tree. return filepath.SkipDir } if !treeCanMatch(name) { return filepath.SkipDir } if have[name] { return nil } have[name] = true if !match(name) { return nil } _, err = buildContext.ImportDir(path, 0) if err != nil { if _, noGo := err.(*build.NoGoError); noGo { return nil } } pkgs = append(pkgs, name) return nil }) } return pkgs } // allPackagesInFS is like allPackages but is passed a pattern // beginning ./ or ../, meaning it should scan the tree rooted // at the given directory. There are ... in the pattern too. func allPackagesInFS(pattern string) []string { pkgs := matchPackagesInFS(pattern) if len(pkgs) == 0 { fmt.Fprintf(os.Stderr, "warning: %q matched no packages\n", pattern) } return pkgs } func matchPackagesInFS(pattern string) []string { // Find directory to begin the scan. // Could be smarter but this one optimization // is enough for now, since ... is usually at the // end of a path. i := strings.Index(pattern, "...") dir, _ := path.Split(pattern[:i]) // pattern begins with ./ or ../. // path.Clean will discard the ./ but not the ../. // We need to preserve the ./ for pattern matching // and in the returned import paths. prefix := "" if strings.HasPrefix(pattern, "./") { prefix = "./" } match := matchPattern(pattern) var pkgs []string filepath.Walk(dir, func(path string, fi os.FileInfo, err error) error { if err != nil || !fi.IsDir() { return nil } if path == dir { // filepath.Walk starts at dir and recurses. For the recursive case, // the path is the result of filepath.Join, which calls filepath.Clean. // The initial case is not Cleaned, though, so we do this explicitly. // // This converts a path like "./io/" to "io". Without this step, running // "cd $GOROOT/src/pkg; go list ./io/..." would incorrectly skip the io // package, because prepending the prefix "./" to the unclean path would // result in "././io", and match("././io") returns false. path = filepath.Clean(path) } // Avoid .foo, _foo, and testdata directory trees, but do not avoid "." or "..". _, elem := filepath.Split(path) dot := strings.HasPrefix(elem, ".") && elem != "." && elem != ".." if dot || strings.HasPrefix(elem, "_") || elem == "testdata" { return filepath.SkipDir } name := prefix + filepath.ToSlash(path) if !match(name) { return nil } if _, err = build.ImportDir(path, 0); err != nil { if _, noGo := err.(*build.NoGoError); !noGo { log.Print(err) } return nil } pkgs = append(pkgs, name) return nil }) return pkgs } golint-0.0+git20161013.3390df4/lint.go000066400000000000000000001264631300000745000165330ustar00rootroot00000000000000// Copyright (c) 2013 The Go Authors. All rights reserved. // // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file or at // https://developers.google.com/open-source/licenses/bsd. // Package lint contains a linter for Go source code. package lint import ( "bytes" "fmt" "go/ast" "go/parser" "go/printer" "go/token" "go/types" "regexp" "sort" "strconv" "strings" "unicode" "unicode/utf8" "golang.org/x/tools/go/gcimporter15" ) const styleGuideBase = "https://golang.org/wiki/CodeReviewComments" // A Linter lints Go source code. type Linter struct { } // Problem represents a problem in some source code. type Problem struct { Position token.Position // position in source file Text string // the prose that describes the problem Link string // (optional) the link to the style guide for the problem Confidence float64 // a value in (0,1] estimating the confidence in this problem's correctness LineText string // the source line Category string // a short name for the general category of the problem // If the problem has a suggested fix (the minority case), // ReplacementLine is a full replacement for the relevant line of the source file. ReplacementLine string } func (p *Problem) String() string { if p.Link != "" { return p.Text + "\n\n" + p.Link } return p.Text } type byPosition []Problem func (p byPosition) Len() int { return len(p) } func (p byPosition) Swap(i, j int) { p[i], p[j] = p[j], p[i] } func (p byPosition) Less(i, j int) bool { pi, pj := p[i].Position, p[j].Position if pi.Filename != pj.Filename { return pi.Filename < pj.Filename } if pi.Line != pj.Line { return pi.Line < pj.Line } if pi.Column != pj.Column { return pi.Column < pj.Column } return p[i].Text < p[j].Text } // Lint lints src. func (l *Linter) Lint(filename string, src []byte) ([]Problem, error) { return l.LintFiles(map[string][]byte{filename: src}) } // LintFiles lints a set of files of a single package. // The argument is a map of filename to source. func (l *Linter) LintFiles(files map[string][]byte) ([]Problem, error) { if len(files) == 0 { return nil, nil } pkg := &pkg{ fset: token.NewFileSet(), files: make(map[string]*file), } var pkgName string for filename, src := range files { f, err := parser.ParseFile(pkg.fset, filename, src, parser.ParseComments) if err != nil { return nil, err } if pkgName == "" { pkgName = f.Name.Name } else if f.Name.Name != pkgName { return nil, fmt.Errorf("%s is in package %s, not %s", filename, f.Name.Name, pkgName) } pkg.files[filename] = &file{ pkg: pkg, f: f, fset: pkg.fset, src: src, filename: filename, } } return pkg.lint(), nil } // pkg represents a package being linted. type pkg struct { fset *token.FileSet files map[string]*file typesPkg *types.Package typesInfo *types.Info // sortable is the set of types in the package that implement sort.Interface. sortable map[string]bool // main is whether this is a "main" package. main bool problems []Problem } func (p *pkg) lint() []Problem { if err := p.typeCheck(); err != nil { /* TODO(dsymonds): Consider reporting these errors when golint operates on entire packages. if e, ok := err.(types.Error); ok { pos := p.fset.Position(e.Pos) conf := 1.0 if strings.Contains(e.Msg, "can't find import: ") { // Golint is probably being run in a context that doesn't support // typechecking (e.g. package files aren't found), so don't warn about it. conf = 0 } if conf > 0 { p.errorfAt(pos, conf, category("typechecking"), e.Msg) } // TODO(dsymonds): Abort if !e.Soft? } */ } p.scanSortable() p.main = p.isMain() for _, f := range p.files { f.lint() } sort.Sort(byPosition(p.problems)) return p.problems } // file represents a file being linted. type file struct { pkg *pkg f *ast.File fset *token.FileSet src []byte filename string } func (f *file) isTest() bool { return strings.HasSuffix(f.filename, "_test.go") } func (f *file) lint() { f.lintPackageComment() f.lintImports() f.lintBlankImports() f.lintExported() f.lintNames() f.lintVarDecls() f.lintElses() f.lintRanges() f.lintErrorf() f.lintErrors() f.lintErrorStrings() f.lintReceiverNames() f.lintIncDec() f.lintErrorReturn() f.lintUnexportedReturn() f.lintTimeNames() f.lintContextKeyTypes() f.lintContextArgs() } type link string type category string // The variadic arguments may start with link and category types, // and must end with a format string and any arguments. // It returns the new Problem. func (f *file) errorf(n ast.Node, confidence float64, args ...interface{}) *Problem { pos := f.fset.Position(n.Pos()) if pos.Filename == "" { pos.Filename = f.filename } return f.pkg.errorfAt(pos, confidence, args...) } func (p *pkg) errorfAt(pos token.Position, confidence float64, args ...interface{}) *Problem { problem := Problem{ Position: pos, Confidence: confidence, } if pos.Filename != "" { // The file might not exist in our mapping if a //line directive was encountered. if f, ok := p.files[pos.Filename]; ok { problem.LineText = srcLine(f.src, pos) } } argLoop: for len(args) > 1 { // always leave at least the format string in args switch v := args[0].(type) { case link: problem.Link = string(v) case category: problem.Category = string(v) default: break argLoop } args = args[1:] } problem.Text = fmt.Sprintf(args[0].(string), args[1:]...) p.problems = append(p.problems, problem) return &p.problems[len(p.problems)-1] } var gcImporter = gcimporter.Import // importer implements go/types.Importer{,From}. type importer struct { impFn func(packages map[string]*types.Package, path, srcDir string) (*types.Package, error) packages map[string]*types.Package } func (i importer) Import(path string) (*types.Package, error) { return i.impFn(i.packages, path, "") } func (i importer) ImportFrom(path, srcDir string, mode types.ImportMode) (*types.Package, error) { return i.impFn(i.packages, path, srcDir) } func (p *pkg) typeCheck() error { config := &types.Config{ // By setting a no-op error reporter, the type checker does as much work as possible. Error: func(error) {}, Importer: importer{ impFn: gcImporter, packages: make(map[string]*types.Package), }, } info := &types.Info{ Types: make(map[ast.Expr]types.TypeAndValue), Defs: make(map[*ast.Ident]types.Object), Uses: make(map[*ast.Ident]types.Object), Scopes: make(map[ast.Node]*types.Scope), } var anyFile *file var astFiles []*ast.File for _, f := range p.files { anyFile = f astFiles = append(astFiles, f.f) } pkg, err := config.Check(anyFile.f.Name.Name, p.fset, astFiles, info) // Remember the typechecking info, even if config.Check failed, // since we will get partial information. p.typesPkg = pkg p.typesInfo = info return err } func (p *pkg) typeOf(expr ast.Expr) types.Type { if p.typesInfo == nil { return nil } return p.typesInfo.TypeOf(expr) } func (p *pkg) isNamedType(typ types.Type, importPath, name string) bool { n, ok := typ.(*types.Named) if !ok { return false } tn := n.Obj() return tn != nil && tn.Pkg() != nil && tn.Pkg().Path() == importPath && tn.Name() == name } // scopeOf returns the tightest scope encompassing id. func (p *pkg) scopeOf(id *ast.Ident) *types.Scope { var scope *types.Scope if obj := p.typesInfo.ObjectOf(id); obj != nil { scope = obj.Parent() } if scope == p.typesPkg.Scope() { // We were given a top-level identifier. // Use the file-level scope instead of the package-level scope. pos := id.Pos() for _, f := range p.files { if f.f.Pos() <= pos && pos < f.f.End() { scope = p.typesInfo.Scopes[f.f] break } } } return scope } func (p *pkg) scanSortable() { p.sortable = make(map[string]bool) // bitfield for which methods exist on each type. const ( Len = 1 << iota Less Swap ) nmap := map[string]int{"Len": Len, "Less": Less, "Swap": Swap} has := make(map[string]int) for _, f := range p.files { f.walk(func(n ast.Node) bool { fn, ok := n.(*ast.FuncDecl) if !ok || fn.Recv == nil || len(fn.Recv.List) == 0 { return true } // TODO(dsymonds): We could check the signature to be more precise. recv := receiverType(fn) if i, ok := nmap[fn.Name.Name]; ok { has[recv] |= i } return false }) } for typ, ms := range has { if ms == Len|Less|Swap { p.sortable[typ] = true } } } func (p *pkg) isMain() bool { for _, f := range p.files { if f.isMain() { return true } } return false } func (f *file) isMain() bool { if f.f.Name.Name == "main" { return true } return false } // lintPackageComment checks package comments. It complains if // there is no package comment, or if it is not of the right form. // This has a notable false positive in that a package comment // could rightfully appear in a different file of the same package, // but that's not easy to fix since this linter is file-oriented. func (f *file) lintPackageComment() { if f.isTest() { return } const ref = styleGuideBase + "#package-comments" prefix := "Package " + f.f.Name.Name + " " // Look for a detached package comment. // First, scan for the last comment that occurs before the "package" keyword. var lastCG *ast.CommentGroup for _, cg := range f.f.Comments { if cg.Pos() > f.f.Package { // Gone past "package" keyword. break } lastCG = cg } if lastCG != nil && strings.HasPrefix(lastCG.Text(), prefix) { endPos := f.fset.Position(lastCG.End()) pkgPos := f.fset.Position(f.f.Package) if endPos.Line+1 < pkgPos.Line { // There isn't a great place to anchor this error; // the start of the blank lines between the doc and the package statement // is at least pointing at the location of the problem. pos := token.Position{ Filename: endPos.Filename, // Offset not set; it is non-trivial, and doesn't appear to be needed. Line: endPos.Line + 1, Column: 1, } 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") return } } if f.f.Doc == nil { f.errorf(f.f, 0.2, link(ref), category("comments"), "should have a package comment, unless it's in another file for this package") return } s := f.f.Doc.Text() if ts := strings.TrimLeft(s, " \t"); ts != s { f.errorf(f.f.Doc, 1, link(ref), category("comments"), "package comment should not have leading space") s = ts } // Only non-main packages need to keep to this form. if !f.pkg.main && !strings.HasPrefix(s, prefix) { f.errorf(f.f.Doc, 1, link(ref), category("comments"), `package comment should be of the form "%s..."`, prefix) } } // lintBlankImports complains if a non-main package has blank imports that are // not documented. func (f *file) lintBlankImports() { // In package main and in tests, we don't complain about blank imports. if f.pkg.main || f.isTest() { return } // The first element of each contiguous group of blank imports should have // an explanatory comment of some kind. for i, imp := range f.f.Imports { pos := f.fset.Position(imp.Pos()) if !isBlank(imp.Name) { continue // Ignore non-blank imports. } if i > 0 { prev := f.f.Imports[i-1] prevPos := f.fset.Position(prev.Pos()) if isBlank(prev.Name) && prevPos.Line+1 == pos.Line { continue // A subsequent blank in a group. } } // This is the first blank import of a group. if imp.Doc == nil && imp.Comment == nil { ref := "" 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") } } } // lintImports examines import blocks. func (f *file) lintImports() { for i, is := range f.f.Imports { _ = i if is.Name != nil && is.Name.Name == "." && !f.isTest() { f.errorf(is, 1, link(styleGuideBase+"#import-dot"), category("imports"), "should not use dot imports") } } } const docCommentsLink = styleGuideBase + "#doc-comments" // lintExported examines the exported names. // It complains if any required doc comments are missing, // or if they are not of the right form. The exact rules are in // lintFuncDoc, lintTypeDoc and lintValueSpecDoc; this function // also tracks the GenDecl structure being traversed to permit // doc comments for constants to be on top of the const block. // It also complains if the names stutter when combined with // the package name. func (f *file) lintExported() { if f.isTest() { return } var lastGen *ast.GenDecl // last GenDecl entered. // Set of GenDecls that have already had missing comments flagged. genDeclMissingComments := make(map[*ast.GenDecl]bool) f.walk(func(node ast.Node) bool { switch v := node.(type) { case *ast.GenDecl: if v.Tok == token.IMPORT { return false } // token.CONST, token.TYPE or token.VAR lastGen = v return true case *ast.FuncDecl: f.lintFuncDoc(v) if v.Recv == nil { // Only check for stutter on functions, not methods. // Method names are not used package-qualified. f.checkStutter(v.Name, "func") } // Don't proceed inside funcs. return false case *ast.TypeSpec: // inside a GenDecl, which usually has the doc doc := v.Doc if doc == nil { doc = lastGen.Doc } f.lintTypeDoc(v, doc) f.checkStutter(v.Name, "type") // Don't proceed inside types. return false case *ast.ValueSpec: f.lintValueSpecDoc(v, lastGen, genDeclMissingComments) return false } return true }) } var allCapsRE = regexp.MustCompile(`^[A-Z0-9_]+$`) // knownNameExceptions is a set of names that are known to be exempt from naming checks. // This is usually because they are constrained by having to match names in the // standard library. var knownNameExceptions = map[string]bool{ "LastInsertId": true, // must match database/sql "kWh": true, } // lintNames examines all names in the file. // It complains if any use underscores or incorrect known initialisms. func (f *file) lintNames() { // Package names need slightly different handling than other names. if strings.Contains(f.f.Name.Name, "_") && !strings.HasSuffix(f.f.Name.Name, "_test") { 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") } check := func(id *ast.Ident, thing string) { if id.Name == "_" { return } if knownNameExceptions[id.Name] { return } // Handle two common styles from other languages that don't belong in Go. if len(id.Name) >= 5 && allCapsRE.MatchString(id.Name) && strings.Contains(id.Name, "_") { f.errorf(id, 0.8, link(styleGuideBase+"#mixed-caps"), category("naming"), "don't use ALL_CAPS in Go names; use CamelCase") return } if len(id.Name) > 2 && id.Name[0] == 'k' && id.Name[1] >= 'A' && id.Name[1] <= 'Z' { should := string(id.Name[1]+'a'-'A') + id.Name[2:] 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) } should := lintName(id.Name) if id.Name == should { return } if len(id.Name) > 2 && strings.Contains(id.Name[1:], "_") { 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) return } f.errorf(id, 0.8, link(styleGuideBase+"#initialisms"), category("naming"), "%s %s should be %s", thing, id.Name, should) } checkList := func(fl *ast.FieldList, thing string) { if fl == nil { return } for _, f := range fl.List { for _, id := range f.Names { check(id, thing) } } } f.walk(func(node ast.Node) bool { switch v := node.(type) { case *ast.AssignStmt: if v.Tok == token.ASSIGN { return true } for _, exp := range v.Lhs { if id, ok := exp.(*ast.Ident); ok { check(id, "var") } } case *ast.FuncDecl: if f.isTest() && (strings.HasPrefix(v.Name.Name, "Example") || strings.HasPrefix(v.Name.Name, "Test") || strings.HasPrefix(v.Name.Name, "Benchmark")) { return true } thing := "func" if v.Recv != nil { thing = "method" } check(v.Name, thing) checkList(v.Type.Params, thing+" parameter") checkList(v.Type.Results, thing+" result") case *ast.GenDecl: if v.Tok == token.IMPORT { return true } var thing string switch v.Tok { case token.CONST: thing = "const" case token.TYPE: thing = "type" case token.VAR: thing = "var" } for _, spec := range v.Specs { switch s := spec.(type) { case *ast.TypeSpec: check(s.Name, thing) case *ast.ValueSpec: for _, id := range s.Names { check(id, thing) } } } case *ast.InterfaceType: // Do not check interface method names. // They are often constrainted by the method names of concrete types. for _, x := range v.Methods.List { ft, ok := x.Type.(*ast.FuncType) if !ok { // might be an embedded interface name continue } checkList(ft.Params, "interface method parameter") checkList(ft.Results, "interface method result") } case *ast.RangeStmt: if v.Tok == token.ASSIGN { return true } if id, ok := v.Key.(*ast.Ident); ok { check(id, "range var") } if id, ok := v.Value.(*ast.Ident); ok { check(id, "range var") } case *ast.StructType: for _, f := range v.Fields.List { for _, id := range f.Names { check(id, "struct field") } } } return true }) } // lintName returns a different name if it should be different. func lintName(name string) (should string) { // Fast path for simple cases: "_" and all lowercase. if name == "_" { return name } allLower := true for _, r := range name { if !unicode.IsLower(r) { allLower = false break } } if allLower { return name } // Split camelCase at any lower->upper transition, and split on underscores. // Check each word for common initialisms. runes := []rune(name) w, i := 0, 0 // index of start of word, scan for i+1 <= len(runes) { eow := false // whether we hit the end of a word if i+1 == len(runes) { eow = true } else if runes[i+1] == '_' { // underscore; shift the remainder forward over any run of underscores eow = true n := 1 for i+n+1 < len(runes) && runes[i+n+1] == '_' { n++ } // Leave at most one underscore if the underscore is between two digits if i+n+1 < len(runes) && unicode.IsDigit(runes[i]) && unicode.IsDigit(runes[i+n+1]) { n-- } copy(runes[i+1:], runes[i+n+1:]) runes = runes[:len(runes)-n] } else if unicode.IsLower(runes[i]) && !unicode.IsLower(runes[i+1]) { // lower->non-lower eow = true } i++ if !eow { continue } // [w,i) is a word. word := string(runes[w:i]) if u := strings.ToUpper(word); commonInitialisms[u] { // Keep consistent case, which is lowercase only at the start. if w == 0 && unicode.IsLower(runes[w]) { u = strings.ToLower(u) } // All the common initialisms are ASCII, // so we can replace the bytes exactly. copy(runes[w:], []rune(u)) } else if w > 0 && strings.ToLower(word) == word { // already all lowercase, and not the first word, so uppercase the first character. runes[w] = unicode.ToUpper(runes[w]) } w = i } return string(runes) } // commonInitialisms is a set of common initialisms. // Only add entries that are highly unlikely to be non-initialisms. // For instance, "ID" is fine (Freudian code is rare), but "AND" is not. var commonInitialisms = map[string]bool{ "ACL": true, "API": true, "ASCII": true, "CPU": true, "CSS": true, "DNS": true, "EOF": true, "GUID": true, "HTML": true, "HTTP": true, "HTTPS": true, "ID": true, "IP": true, "JSON": true, "LHS": true, "QPS": true, "RAM": true, "RHS": true, "RPC": true, "SLA": true, "SMTP": true, "SQL": true, "SSH": true, "TCP": true, "TLS": true, "TTL": true, "UDP": true, "UI": true, "UID": true, "UUID": true, "URI": true, "URL": true, "UTF8": true, "VM": true, "XML": true, "XMPP": true, "XSRF": true, "XSS": true, } // lintTypeDoc examines the doc comment on a type. // It complains if they are missing from an exported type, // or if they are not of the standard form. func (f *file) lintTypeDoc(t *ast.TypeSpec, doc *ast.CommentGroup) { if !ast.IsExported(t.Name.Name) { return } if doc == nil { f.errorf(t, 1, link(docCommentsLink), category("comments"), "exported type %v should have comment or be unexported", t.Name) return } s := doc.Text() articles := [...]string{"A", "An", "The"} for _, a := range articles { if strings.HasPrefix(s, a+" ") { s = s[len(a)+1:] break } } if !strings.HasPrefix(s, t.Name.Name+" ") { 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) } } var commonMethods = map[string]bool{ "Error": true, "Read": true, "ServeHTTP": true, "String": true, "Write": true, } // lintFuncDoc examines doc comments on functions and methods. // It complains if they are missing, or not of the right form. // It has specific exclusions for well-known methods (see commonMethods above). func (f *file) lintFuncDoc(fn *ast.FuncDecl) { if !ast.IsExported(fn.Name.Name) { // func is unexported return } kind := "function" name := fn.Name.Name if fn.Recv != nil && len(fn.Recv.List) > 0 { // method kind = "method" recv := receiverType(fn) if !ast.IsExported(recv) { // receiver is unexported return } if commonMethods[name] { return } switch name { case "Len", "Less", "Swap": if f.pkg.sortable[recv] { return } } name = recv + "." + name } if fn.Doc == nil { f.errorf(fn, 1, link(docCommentsLink), category("comments"), "exported %s %s should have comment or be unexported", kind, name) return } s := fn.Doc.Text() prefix := fn.Name.Name + " " if !strings.HasPrefix(s, prefix) { f.errorf(fn.Doc, 1, link(docCommentsLink), category("comments"), `comment on exported %s %s should be of the form "%s..."`, kind, name, prefix) } } // lintValueSpecDoc examines package-global variables and constants. // It complains if they are not individually declared, // or if they are not suitably documented in the right form (unless they are in a block that is commented). func (f *file) lintValueSpecDoc(vs *ast.ValueSpec, gd *ast.GenDecl, genDeclMissingComments map[*ast.GenDecl]bool) { kind := "var" if gd.Tok == token.CONST { kind = "const" } if len(vs.Names) > 1 { // Check that none are exported except for the first. for _, n := range vs.Names[1:] { if ast.IsExported(n.Name) { f.errorf(vs, 1, category("comments"), "exported %s %s should have its own declaration", kind, n.Name) return } } } // Only one name. name := vs.Names[0].Name if !ast.IsExported(name) { return } if vs.Doc == nil && gd.Doc == nil { if genDeclMissingComments[gd] { return } block := "" if kind == "const" && gd.Lparen.IsValid() { block = " (or a comment on this block)" } f.errorf(vs, 1, link(docCommentsLink), category("comments"), "exported %s %s should have comment%s or be unexported", kind, name, block) genDeclMissingComments[gd] = true return } // If this GenDecl has parens and a comment, we don't check its comment form. if gd.Lparen.IsValid() && gd.Doc != nil { return } // The relevant text to check will be on either vs.Doc or gd.Doc. // Use vs.Doc preferentially. doc := vs.Doc if doc == nil { doc = gd.Doc } prefix := name + " " if !strings.HasPrefix(doc.Text(), prefix) { f.errorf(doc, 1, link(docCommentsLink), category("comments"), `comment on exported %s %s should be of the form "%s..."`, kind, name, prefix) } } func (f *file) checkStutter(id *ast.Ident, thing string) { pkg, name := f.f.Name.Name, id.Name if !ast.IsExported(name) { // unexported name return } // A name stutters if the package name is a strict prefix // and the next character of the name starts a new word. if len(name) <= len(pkg) { // name is too short to stutter. // This permits the name to be the same as the package name. return } if !strings.EqualFold(pkg, name[:len(pkg)]) { return } // We can assume the name is well-formed UTF-8. // If the next rune after the package name is uppercase or an underscore // the it's starting a new word and thus this name stutters. rem := name[len(pkg):] if next, _ := utf8.DecodeRuneInString(rem); next == '_' || unicode.IsUpper(next) { 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) } } // zeroLiteral is a set of ast.BasicLit values that are zero values. // It is not exhaustive. var zeroLiteral = map[string]bool{ "false": true, // bool // runes `'\x00'`: true, `'\000'`: true, // strings `""`: true, "``": true, // numerics "0": true, "0.": true, "0.0": true, "0i": true, } // lintVarDecls examines variable declarations. It complains about declarations with // redundant LHS types that can be inferred from the RHS. func (f *file) lintVarDecls() { var lastGen *ast.GenDecl // last GenDecl entered. f.walk(func(node ast.Node) bool { switch v := node.(type) { case *ast.GenDecl: if v.Tok != token.CONST && v.Tok != token.VAR { return false } lastGen = v return true case *ast.ValueSpec: if lastGen.Tok == token.CONST { return false } if len(v.Names) > 1 || v.Type == nil || len(v.Values) == 0 { return false } rhs := v.Values[0] // An underscore var appears in a common idiom for compile-time interface satisfaction, // as in "var _ Interface = (*Concrete)(nil)". if isIdent(v.Names[0], "_") { return false } // If the RHS is a zero value, suggest dropping it. zero := false if lit, ok := rhs.(*ast.BasicLit); ok { zero = zeroLiteral[lit.Value] } else if isIdent(rhs, "nil") { zero = true } if zero { f.errorf(rhs, 0.9, category("zero-value"), "should drop = %s from declaration of var %s; it is the zero value", f.render(rhs), v.Names[0]) return false } lhsTyp := f.pkg.typeOf(v.Type) rhsTyp := f.pkg.typeOf(rhs) if !validType(lhsTyp) || !validType(rhsTyp) { // Type checking failed (often due to missing imports). return false } if !types.Identical(lhsTyp, rhsTyp) { // Assignment to a different type is not redundant. return false } // The next three conditions are for suppressing the warning in situations // where we were unable to typecheck. // If the LHS type is an interface, don't warn, since it is probably a // concrete type on the RHS. Note that our feeble lexical check here // will only pick up interface{} and other literal interface types; // that covers most of the cases we care to exclude right now. if _, ok := v.Type.(*ast.InterfaceType); ok { return false } // If the RHS is an untyped const, only warn if the LHS type is its default type. if defType, ok := f.isUntypedConst(rhs); ok && !isIdent(v.Type, defType) { return false } f.errorf(v.Type, 0.8, category("type-inference"), "should omit type %s from declaration of var %s; it will be inferred from the right-hand side", f.render(v.Type), v.Names[0]) return false } return true }) } func validType(T types.Type) bool { return T != nil && T != types.Typ[types.Invalid] && !strings.Contains(T.String(), "invalid type") // good but not foolproof } // lintElses examines else blocks. It complains about any else block whose if block ends in a return. func (f *file) lintElses() { // We don't want to flag if { } else if { } else { } constructions. // They will appear as an IfStmt whose Else field is also an IfStmt. // Record such a node so we ignore it when we visit it. ignore := make(map[*ast.IfStmt]bool) f.walk(func(node ast.Node) bool { ifStmt, ok := node.(*ast.IfStmt) if !ok || ifStmt.Else == nil { return true } if ignore[ifStmt] { return true } if elseif, ok := ifStmt.Else.(*ast.IfStmt); ok { ignore[elseif] = true return true } if _, ok := ifStmt.Else.(*ast.BlockStmt); !ok { // only care about elses without conditions return true } if len(ifStmt.Body.List) == 0 { return true } shortDecl := false // does the if statement have a ":=" initialization statement? if ifStmt.Init != nil { if as, ok := ifStmt.Init.(*ast.AssignStmt); ok && as.Tok == token.DEFINE { shortDecl = true } } lastStmt := ifStmt.Body.List[len(ifStmt.Body.List)-1] if _, ok := lastStmt.(*ast.ReturnStmt); ok { extra := "" if shortDecl { extra = " (move short variable declaration to its own line if necessary)" } 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) } return true }) } // lintRanges examines range clauses. It complains about redundant constructions. func (f *file) lintRanges() { f.walk(func(node ast.Node) bool { rs, ok := node.(*ast.RangeStmt) if !ok { return true } if rs.Value == nil { // for x = range m { ... } return true // single var form } if !isIdent(rs.Value, "_") { // for ?, y = range m { ... } return true } 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) newRS := *rs // shallow copy newRS.Value = nil p.ReplacementLine = f.firstLineOf(&newRS, rs) return true }) } // lintErrorf examines errors.New and testing.Error calls. It complains if its only argument is an fmt.Sprintf invocation. func (f *file) lintErrorf() { f.walk(func(node ast.Node) bool { ce, ok := node.(*ast.CallExpr) if !ok || len(ce.Args) != 1 { return true } isErrorsNew := isPkgDot(ce.Fun, "errors", "New") var isTestingError bool se, ok := ce.Fun.(*ast.SelectorExpr) if ok && se.Sel.Name == "Error" { if typ := f.pkg.typeOf(se.X); typ != nil { isTestingError = typ.String() == "*testing.T" } } if !isErrorsNew && !isTestingError { return true } arg := ce.Args[0] ce, ok = arg.(*ast.CallExpr) if !ok || !isPkgDot(ce.Fun, "fmt", "Sprintf") { return true } errorfPrefix := "fmt" if isTestingError { errorfPrefix = f.render(se.X) } p := f.errorf(node, 1, category("errors"), "should replace %s(fmt.Sprintf(...)) with %s.Errorf(...)", f.render(se), errorfPrefix) m := f.srcLineWithMatch(ce, `^(.*)`+f.render(se)+`\(fmt\.Sprintf\((.*)\)\)(.*)$`) if m != nil { p.ReplacementLine = m[1] + errorfPrefix + ".Errorf(" + m[2] + ")" + m[3] } return true }) } // lintErrors examines global error vars. It complains if they aren't named in the standard way. func (f *file) lintErrors() { for _, decl := range f.f.Decls { gd, ok := decl.(*ast.GenDecl) if !ok || gd.Tok != token.VAR { continue } for _, spec := range gd.Specs { spec := spec.(*ast.ValueSpec) if len(spec.Names) != 1 || len(spec.Values) != 1 { continue } ce, ok := spec.Values[0].(*ast.CallExpr) if !ok { continue } if !isPkgDot(ce.Fun, "errors", "New") && !isPkgDot(ce.Fun, "fmt", "Errorf") { continue } id := spec.Names[0] prefix := "err" if id.IsExported() { prefix = "Err" } if !strings.HasPrefix(id.Name, prefix) { f.errorf(id, 0.9, category("naming"), "error var %s should have name of the form %sFoo", id.Name, prefix) } } } } func lintCapAndPunct(s string) (isCap, isPunct bool) { first, firstN := utf8.DecodeRuneInString(s) last, _ := utf8.DecodeLastRuneInString(s) isPunct = last == '.' || last == ':' || last == '!' isCap = unicode.IsUpper(first) if isCap && len(s) > firstN { // Don't flag strings starting with something that looks like an initialism. if second, _ := utf8.DecodeRuneInString(s[firstN:]); unicode.IsUpper(second) { isCap = false } } return } // lintErrorStrings examines error strings. It complains if they are capitalized or end in punctuation. func (f *file) lintErrorStrings() { f.walk(func(node ast.Node) bool { ce, ok := node.(*ast.CallExpr) if !ok { return true } if !isPkgDot(ce.Fun, "errors", "New") && !isPkgDot(ce.Fun, "fmt", "Errorf") { return true } if len(ce.Args) < 1 { return true } str, ok := ce.Args[0].(*ast.BasicLit) if !ok || str.Kind != token.STRING { return true } s, _ := strconv.Unquote(str.Value) // can assume well-formed Go if s == "" { return true } isCap, isPunct := lintCapAndPunct(s) var msg string switch { case isCap && isPunct: msg = "error strings should not be capitalized and should not end with punctuation" case isCap: msg = "error strings should not be capitalized" case isPunct: msg = "error strings should not end with punctuation" default: return true } // People use proper nouns and exported Go identifiers in error strings, // so decrease the confidence of warnings for capitalization. conf := 0.8 if isCap { conf = 0.6 } f.errorf(str, conf, link(styleGuideBase+"#error-strings"), category("errors"), msg) return true }) } // lintReceiverNames examines receiver names. It complains about inconsistent // names used for the same type and names such as "this". func (f *file) lintReceiverNames() { typeReceiver := map[string]string{} f.walk(func(n ast.Node) bool { fn, ok := n.(*ast.FuncDecl) if !ok || fn.Recv == nil || len(fn.Recv.List) == 0 { return true } names := fn.Recv.List[0].Names if len(names) < 1 { return true } name := names[0].Name const ref = styleGuideBase + "#receiver-names" if name == "_" { f.errorf(n, 1, link(ref), category("naming"), `receiver name should not be an underscore`) return true } if name == "this" || name == "self" { 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"`) return true } recv := receiverType(fn) if prev, ok := typeReceiver[recv]; ok && prev != name { f.errorf(n, 1, link(ref), category("naming"), "receiver name %s should be consistent with previous receiver name %s for %s", name, prev, recv) return true } typeReceiver[recv] = name return true }) } // lintIncDec examines statements that increment or decrement a variable. // It complains if they don't use x++ or x--. func (f *file) lintIncDec() { f.walk(func(n ast.Node) bool { as, ok := n.(*ast.AssignStmt) if !ok { return true } if len(as.Lhs) != 1 { return true } if !isOne(as.Rhs[0]) { return true } var suffix string switch as.Tok { case token.ADD_ASSIGN: suffix = "++" case token.SUB_ASSIGN: suffix = "--" default: return true } f.errorf(as, 0.8, category("unary-op"), "should replace %s with %s%s", f.render(as), f.render(as.Lhs[0]), suffix) return true }) } // lintErrorReturn examines function declarations that return an error. // It complains if the error isn't the last parameter. func (f *file) lintErrorReturn() { f.walk(func(n ast.Node) bool { fn, ok := n.(*ast.FuncDecl) if !ok || fn.Type.Results == nil { return true } ret := fn.Type.Results.List if len(ret) <= 1 { return true } // An error return parameter should be the last parameter. // Flag any error parameters found before the last. for _, r := range ret[:len(ret)-1] { if isIdent(r.Type, "error") { f.errorf(fn, 0.9, category("arg-order"), "error should be the last type when returning multiple items") break // only flag one } } return true }) } // lintUnexportedReturn examines exported function declarations. // It complains if any return an unexported type. func (f *file) lintUnexportedReturn() { f.walk(func(n ast.Node) bool { fn, ok := n.(*ast.FuncDecl) if !ok { return true } if fn.Type.Results == nil { return false } if !fn.Name.IsExported() { return false } thing := "func" if fn.Recv != nil && len(fn.Recv.List) > 0 { thing = "method" if !ast.IsExported(receiverType(fn)) { // Don't report exported methods of unexported types, // such as private implementations of sort.Interface. return false } } for _, ret := range fn.Type.Results.List { typ := f.pkg.typeOf(ret.Type) if exportedType(typ) { continue } f.errorf(ret.Type, 0.8, category("unexported-type-in-api"), "exported %s %s returns unexported type %s, which can be annoying to use", thing, fn.Name.Name, typ) break // only flag one } return false }) } // exportedType reports whether typ is an exported type. // It is imprecise, and will err on the side of returning true, // such as for composite types. func exportedType(typ types.Type) bool { switch T := typ.(type) { case *types.Named: // Builtin types have no package. return T.Obj().Pkg() == nil || T.Obj().Exported() case *types.Map: return exportedType(T.Key()) && exportedType(T.Elem()) case interface { Elem() types.Type }: // array, slice, pointer, chan return exportedType(T.Elem()) } // Be conservative about other types, such as struct, interface, etc. return true } // timeSuffixes is a list of name suffixes that imply a time unit. // This is not an exhaustive list. var timeSuffixes = []string{ "Sec", "Secs", "Seconds", "Msec", "Msecs", "Milli", "Millis", "Milliseconds", "Usec", "Usecs", "Microseconds", "MS", "Ms", } func (f *file) lintTimeNames() { f.walk(func(node ast.Node) bool { v, ok := node.(*ast.ValueSpec) if !ok { return true } for _, name := range v.Names { origTyp := f.pkg.typeOf(name) // Look for time.Duration or *time.Duration; // the latter is common when using flag.Duration. typ := origTyp if pt, ok := typ.(*types.Pointer); ok { typ = pt.Elem() } if !f.pkg.isNamedType(typ, "time", "Duration") { continue } suffix := "" for _, suf := range timeSuffixes { if strings.HasSuffix(name.Name, suf) { suffix = suf break } } if suffix == "" { continue } f.errorf(v, 0.9, category("time"), "var %s is of type %v; don't use unit-specific suffix %q", name.Name, origTyp, suffix) } return true }) } // lintContextKeyTypes checks for call expressions to context.WithValue with // basic types used for the key argument. // See: https://golang.org/issue/17293 func (f *file) lintContextKeyTypes() { f.walk(func(node ast.Node) bool { switch node := node.(type) { case *ast.CallExpr: f.checkContextKeyType(node) } return true }) } // checkContextKeyType reports an error if the call expression calls // context.WithValue with a key argument of basic type. func (f *file) checkContextKeyType(x *ast.CallExpr) { sel, ok := x.Fun.(*ast.SelectorExpr) if !ok { return } pkg, ok := sel.X.(*ast.Ident) if !ok || pkg.Name != "context" { return } if sel.Sel.Name != "WithValue" { return } // key is second argument to context.WithValue if len(x.Args) != 3 { return } key := f.pkg.typesInfo.Types[x.Args[1]] if _, ok := key.Type.(*types.Basic); ok { f.errorf(x, 1.0, category("context"), fmt.Sprintf("should not use basic type %s as key in context.WithValue", key.Type)) } } // lintContextArgs examines function declarations that contain an // argument with a type of context.Context // It complains if that argument isn't the first parameter. func (f *file) lintContextArgs() { f.walk(func(n ast.Node) bool { fn, ok := n.(*ast.FuncDecl) if !ok || len(fn.Type.Params.List) <= 1 { return true } // A context.Context should be the first parameter of a function. // Flag any that show up after the first. for _, arg := range fn.Type.Params.List[1:] { if isPkgDot(arg.Type, "context", "Context") { f.errorf(fn, 0.9, link("https://golang.org/pkg/context/"), category("arg-order"), "context.Context should be the first parameter of a function") break // only flag one } } return true }) } // receiverType returns the named type of the method receiver, sans "*", // or "invalid-type" if fn.Recv is ill formed. func receiverType(fn *ast.FuncDecl) string { switch e := fn.Recv.List[0].Type.(type) { case *ast.Ident: return e.Name case *ast.StarExpr: if id, ok := e.X.(*ast.Ident); ok { return id.Name } } // The parser accepts much more than just the legal forms. return "invalid-type" } func (f *file) walk(fn func(ast.Node) bool) { ast.Walk(walker(fn), f.f) } func (f *file) render(x interface{}) string { var buf bytes.Buffer if err := printer.Fprint(&buf, f.fset, x); err != nil { panic(err) } return buf.String() } func (f *file) debugRender(x interface{}) string { var buf bytes.Buffer if err := ast.Fprint(&buf, f.fset, x, nil); err != nil { panic(err) } return buf.String() } // walker adapts a function to satisfy the ast.Visitor interface. // The function return whether the walk should proceed into the node's children. type walker func(ast.Node) bool func (w walker) Visit(node ast.Node) ast.Visitor { if w(node) { return w } return nil } func isIdent(expr ast.Expr, ident string) bool { id, ok := expr.(*ast.Ident) return ok && id.Name == ident } // isBlank returns whether id is the blank identifier "_". // If id == nil, the answer is false. func isBlank(id *ast.Ident) bool { return id != nil && id.Name == "_" } func isPkgDot(expr ast.Expr, pkg, name string) bool { sel, ok := expr.(*ast.SelectorExpr) return ok && isIdent(sel.X, pkg) && isIdent(sel.Sel, name) } func isZero(expr ast.Expr) bool { lit, ok := expr.(*ast.BasicLit) return ok && lit.Kind == token.INT && lit.Value == "0" } func isOne(expr ast.Expr) bool { lit, ok := expr.(*ast.BasicLit) return ok && lit.Kind == token.INT && lit.Value == "1" } var basicTypeKinds = map[types.BasicKind]string{ types.UntypedBool: "bool", types.UntypedInt: "int", types.UntypedRune: "rune", types.UntypedFloat: "float64", types.UntypedComplex: "complex128", types.UntypedString: "string", } // isUntypedConst reports whether expr is an untyped constant, // and indicates what its default type is. // scope may be nil. func (f *file) isUntypedConst(expr ast.Expr) (defType string, ok bool) { // Re-evaluate expr outside of its context to see if it's untyped. // (An expr evaluated within, for example, an assignment context will get the type of the LHS.) exprStr := f.render(expr) tv, err := types.Eval(f.fset, f.pkg.typesPkg, expr.Pos(), exprStr) if err != nil { return "", false } if b, ok := tv.Type.(*types.Basic); ok { if dt, ok := basicTypeKinds[b.Kind()]; ok { return dt, true } } return "", false } // firstLineOf renders the given node and returns its first line. // It will also match the indentation of another node. func (f *file) firstLineOf(node, match ast.Node) string { line := f.render(node) if i := strings.Index(line, "\n"); i >= 0 { line = line[:i] } return f.indentOf(match) + line } func (f *file) indentOf(node ast.Node) string { line := srcLine(f.src, f.fset.Position(node.Pos())) for i, r := range line { switch r { case ' ', '\t': default: return line[:i] } } return line // unusual or empty line } func (f *file) srcLineWithMatch(node ast.Node, pattern string) (m []string) { line := srcLine(f.src, f.fset.Position(node.Pos())) line = strings.TrimSuffix(line, "\n") rx := regexp.MustCompile(pattern) return rx.FindStringSubmatch(line) } // srcLine returns the complete line at p, including the terminating newline. func srcLine(src []byte, p token.Position) string { // Run to end of line in both directions if not at line start/end. lo, hi := p.Offset, p.Offset+1 for lo > 0 && src[lo-1] != '\n' { lo-- } for hi < len(src) && src[hi-1] != '\n' { hi++ } return string(src[lo:hi]) } golint-0.0+git20161013.3390df4/lint_test.go000066400000000000000000000166671300000745000175760ustar00rootroot00000000000000// Copyright (c) 2013 The Go Authors. All rights reserved. // // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file or at // https://developers.google.com/open-source/licenses/bsd. package lint import ( "bytes" "flag" "fmt" "go/ast" "go/parser" "go/printer" "go/token" "go/types" "io/ioutil" "path" "regexp" "strconv" "strings" "testing" ) var lintMatch = flag.String("lint.match", "", "restrict testdata matches to this pattern") func TestAll(t *testing.T) { l := new(Linter) rx, err := regexp.Compile(*lintMatch) if err != nil { t.Fatalf("Bad -lint.match value %q: %v", *lintMatch, err) } baseDir := "testdata" fis, err := ioutil.ReadDir(baseDir) if err != nil { t.Fatalf("ioutil.ReadDir: %v", err) } if len(fis) == 0 { t.Fatalf("no files in %v", baseDir) } for _, fi := range fis { if !rx.MatchString(fi.Name()) { continue } //t.Logf("Testing %s", fi.Name()) src, err := ioutil.ReadFile(path.Join(baseDir, fi.Name())) if err != nil { t.Fatalf("Failed reading %s: %v", fi.Name(), err) } ins := parseInstructions(t, fi.Name(), src) if ins == nil { t.Errorf("Test file %v does not have instructions", fi.Name()) continue } ps, err := l.Lint(fi.Name(), src) if err != nil { t.Errorf("Linting %s: %v", fi.Name(), err) continue } for _, in := range ins { ok := false for i, p := range ps { if p.Position.Line != in.Line { continue } if in.Match.MatchString(p.Text) { // check replacement if we are expecting one if in.Replacement != "" { // ignore any inline comments, since that would be recursive r := p.ReplacementLine if i := strings.Index(r, " //"); i >= 0 { r = r[:i] } if r != in.Replacement { t.Errorf("Lint failed at %s:%d; got replacement %q, want %q", fi.Name(), in.Line, r, in.Replacement) } } // remove this problem from ps copy(ps[i:], ps[i+1:]) ps = ps[:len(ps)-1] //t.Logf("/%v/ matched at %s:%d", in.Match, fi.Name(), in.Line) ok = true break } } if !ok { t.Errorf("Lint failed at %s:%d; /%v/ did not match", fi.Name(), in.Line, in.Match) } } for _, p := range ps { t.Errorf("Unexpected problem at %s:%d: %v", fi.Name(), p.Position.Line, p.Text) } } } type instruction struct { Line int // the line number this applies to Match *regexp.Regexp // what pattern to match Replacement string // what the suggested replacement line should be } // parseInstructions parses instructions from the comments in a Go source file. // It returns nil if none were parsed. func parseInstructions(t *testing.T, filename string, src []byte) []instruction { fset := token.NewFileSet() f, err := parser.ParseFile(fset, filename, src, parser.ParseComments) if err != nil { t.Fatalf("Test file %v does not parse: %v", filename, err) } var ins []instruction for _, cg := range f.Comments { ln := fset.Position(cg.Pos()).Line raw := cg.Text() for _, line := range strings.Split(raw, "\n") { if line == "" || strings.HasPrefix(line, "#") { continue } if line == "OK" && ins == nil { // so our return value will be non-nil ins = make([]instruction, 0) continue } if strings.Contains(line, "MATCH") { rx, err := extractPattern(line) if err != nil { t.Fatalf("At %v:%d: %v", filename, ln, err) } matchLine := ln if i := strings.Index(line, "MATCH:"); i >= 0 { // This is a match for a different line. lns := strings.TrimPrefix(line[i:], "MATCH:") lns = lns[:strings.Index(lns, " ")] matchLine, err = strconv.Atoi(lns) if err != nil { t.Fatalf("Bad match line number %q at %v:%d: %v", lns, filename, ln, err) } } var repl string if r, ok := extractReplacement(line); ok { repl = r } ins = append(ins, instruction{ Line: matchLine, Match: rx, Replacement: repl, }) } } } return ins } func extractPattern(line string) (*regexp.Regexp, error) { a, b := strings.Index(line, "/"), strings.LastIndex(line, "/") if a == -1 || a == b { return nil, fmt.Errorf("malformed match instruction %q", line) } pat := line[a+1 : b] rx, err := regexp.Compile(pat) if err != nil { return nil, fmt.Errorf("bad match pattern %q: %v", pat, err) } return rx, nil } func extractReplacement(line string) (string, bool) { // Look for this: / -> ` // (the end of a match and start of a backtick string), // and then the closing backtick. const start = "/ -> `" a, b := strings.Index(line, start), strings.LastIndex(line, "`") if a < 0 || a > b { return "", false } return line[a+len(start) : b], true } func render(fset *token.FileSet, x interface{}) string { var buf bytes.Buffer if err := printer.Fprint(&buf, fset, x); err != nil { panic(err) } return buf.String() } func TestLine(t *testing.T) { tests := []struct { src string offset int want string }{ {"single line file", 5, "single line file"}, {"single line file with newline\n", 5, "single line file with newline\n"}, {"first\nsecond\nthird\n", 2, "first\n"}, {"first\nsecond\nthird\n", 9, "second\n"}, {"first\nsecond\nthird\n", 14, "third\n"}, {"first\nsecond\nthird with no newline", 16, "third with no newline"}, {"first byte\n", 0, "first byte\n"}, } for _, test := range tests { got := srcLine([]byte(test.src), token.Position{Offset: test.offset}) if got != test.want { t.Errorf("srcLine(%q, offset=%d) = %q, want %q", test.src, test.offset, got, test.want) } } } func TestLintName(t *testing.T) { tests := []struct { name, want string }{ {"foo_bar", "fooBar"}, {"foo_bar_baz", "fooBarBaz"}, {"Foo_bar", "FooBar"}, {"foo_WiFi", "fooWiFi"}, {"id", "id"}, {"Id", "ID"}, {"foo_id", "fooID"}, {"fooId", "fooID"}, {"fooUid", "fooUID"}, {"idFoo", "idFoo"}, {"uidFoo", "uidFoo"}, {"midIdDle", "midIDDle"}, {"APIProxy", "APIProxy"}, {"ApiProxy", "APIProxy"}, {"apiProxy", "apiProxy"}, {"_Leading", "_Leading"}, {"___Leading", "_Leading"}, {"trailing_", "trailing"}, {"trailing___", "trailing"}, {"a_b", "aB"}, {"a__b", "aB"}, {"a___b", "aB"}, {"Rpc1150", "RPC1150"}, {"case3_1", "case3_1"}, {"case3__1", "case3_1"}, {"IEEE802_16bit", "IEEE802_16bit"}, {"IEEE802_16Bit", "IEEE802_16Bit"}, } for _, test := range tests { got := lintName(test.name) if got != test.want { t.Errorf("lintName(%q) = %q, want %q", test.name, got, test.want) } } } func TestExportedType(t *testing.T) { tests := []struct { typString string exp bool }{ {"int", true}, {"string", false}, // references the shadowed builtin "string" {"T", true}, {"t", false}, {"*T", true}, {"*t", false}, {"map[int]complex128", true}, } for _, test := range tests { src := `package foo; type T int; type t int; type string struct{}` fset := token.NewFileSet() file, err := parser.ParseFile(fset, "foo.go", src, 0) if err != nil { t.Fatalf("Parsing %q: %v", src, err) } // use the package name as package path config := &types.Config{} pkg, err := config.Check(file.Name.Name, fset, []*ast.File{file}, nil) if err != nil { t.Fatalf("Type checking %q: %v", src, err) } tv, err := types.Eval(fset, pkg, token.NoPos, test.typString) if err != nil { t.Errorf("types.Eval(%q): %v", test.typString, err) continue } if got := exportedType(tv.Type); got != test.exp { t.Errorf("exportedType(%v) = %t, want %t", tv.Type, got, test.exp) } } } golint-0.0+git20161013.3390df4/misc/000077500000000000000000000000001300000745000161555ustar00rootroot00000000000000golint-0.0+git20161013.3390df4/misc/emacs/000077500000000000000000000000001300000745000172455ustar00rootroot00000000000000golint-0.0+git20161013.3390df4/misc/emacs/golint.el000066400000000000000000000031051300000745000210620ustar00rootroot00000000000000;;; golint.el --- lint for the Go source code ;; Copyright 2013 The Go Authors. All rights reserved. ;; Use of this source code is governed by a BSD-style ;; license that can be found in the LICENSE file. ;; URL: https://github.com/golang/lint ;;; Commentary: ;; To install golint, add the following lines to your .emacs file: ;; (add-to-list 'load-path "PATH CONTAINING golint.el" t) ;; (require 'golint) ;; ;; After this, type M-x golint on Go source code. ;; ;; Usage: ;; C-x ` ;; Jump directly to the line in your code which caused the first message. ;; ;; For more usage, see Compilation-Mode: ;; http://www.gnu.org/software/emacs/manual/html_node/emacs/Compilation-Mode.html ;;; Code: (require 'compile) (defun go-lint-buffer-name (mode) "*Golint*") (defun golint-process-setup () "Setup compilation variables and buffer for `golint'." (run-hooks 'golint-setup-hook)) (define-compilation-mode golint-mode "golint" "Golint is a linter for Go source code." (set (make-local-variable 'compilation-scroll-output) nil) (set (make-local-variable 'compilation-disable-input) t) (set (make-local-variable 'compilation-process-setup-function) 'golint-process-setup) ) ;;;###autoload (defun golint () "Run golint on the current file and populate the fix list. Pressing C-x ` will jump directly to the line in your code which caused the first message." (interactive) (compilation-start (mapconcat #'shell-quote-argument (list "golint" (expand-file-name buffer-file-name)) " ") 'golint-mode)) (provide 'golint) ;;; golint.el ends here golint-0.0+git20161013.3390df4/misc/vim/000077500000000000000000000000001300000745000167505ustar00rootroot00000000000000golint-0.0+git20161013.3390df4/misc/vim/ftplugin/000077500000000000000000000000001300000745000206005ustar00rootroot00000000000000golint-0.0+git20161013.3390df4/misc/vim/ftplugin/go/000077500000000000000000000000001300000745000212055ustar00rootroot00000000000000golint-0.0+git20161013.3390df4/misc/vim/ftplugin/go/lint.vim000066400000000000000000000012131300000745000226650ustar00rootroot00000000000000" Copyright 2013 The Go Authors. All rights reserved. " Use of this source code is governed by a BSD-style " license that can be found in the LICENSE file. " " lint.vim: Vim command to lint Go files with golint. " " https://github.com/golang/lint " " This filetype plugin add a new commands for go buffers: " " :Lint " " Run golint for the current Go file. " if exists("b:did_ftplugin_go_lint") finish endif if !executable("golint") finish endif command! -buffer Lint call s:GoLint() function! s:GoLint() abort cexpr system('golint ' . shellescape(expand('%'))) endfunction let b:did_ftplugin_go_lint = 1 " vim:ts=4:sw=4:et golint-0.0+git20161013.3390df4/testdata/000077500000000000000000000000001300000745000170335ustar00rootroot00000000000000golint-0.0+git20161013.3390df4/testdata/4.go000066400000000000000000000020701300000745000175240ustar00rootroot00000000000000// Test that exported names have correct comments. // Package pkg does something. package pkg import "time" type T int // MATCH /exported type T.*should.*comment.*or.*unexport/ func (T) F() {} // MATCH /exported method T\.F.*should.*comment.*or.*unexport/ // this is a nice type. // MATCH /comment.*exported type U.*should.*form.*"U ..."/ type U string // this is a neat function. // MATCH /comment.*exported method U\.G.*should.*form.*"G ..."/ func (U) G() {} // A V is a string. type V string // V.H has a pointer receiver func (*V) H() {} // MATCH /exported method V\.H.*should.*comment.*or.*unexport/ var W = "foo" // MATCH /exported var W.*should.*comment.*or.*unexport/ const X = "bar" // MATCH /exported const X.*should.*comment.*or.*unexport/ var Y, Z int // MATCH /exported var Z.*own declaration/ // Location should be okay, since the other var name is an underscore. var Location, _ = time.LoadLocation("Europe/Istanbul") // not Constantinople // this is improperly documented // MATCH /comment.*const.*Thing.*form.*"Thing ..."/ const Thing = "wonderful" golint-0.0+git20161013.3390df4/testdata/5_test.go000066400000000000000000000003561300000745000205710ustar00rootroot00000000000000// This file ends in _test.go, so we should not warn about doc comments. // OK package pkg import "testing" type H int func TestSomething(t *testing.T) { } func TestSomething_suffix(t *testing.T) { } func ExampleBuffer_reader() { } golint-0.0+git20161013.3390df4/testdata/blank-import-lib.go000066400000000000000000000013201300000745000225210ustar00rootroot00000000000000// Test that blank imports in library packages are flagged. // Package foo ... package foo // The instructions need to go before the imports below so they will not be // mistaken for documentation. /* MATCH /blank import/ */ import _ "encoding/json" import ( "fmt" /* MATCH /blank import/ */ _ "os" /* MATCH /blank import/ */ _ "net/http" _ "path" ) import _ "encoding/base64" // Don't gripe about this import ( // Don't gripe about these next two lines. _ "compress/zlib" _ "syscall" /* MATCH /blank import/ */ _ "path/filepath" ) import ( "go/ast" _ "go/scanner" // Don't gripe about this or the following line. _ "go/token" ) var ( _ fmt.Stringer // for "fmt" _ ast.Node // for "go/ast" ) golint-0.0+git20161013.3390df4/testdata/blank-import-lib_test.go000066400000000000000000000006201300000745000235620ustar00rootroot00000000000000// Test that blank imports in test packages are not flagged. // OK // Package foo ... package foo // These are essentially the same imports as in the "library" package, but // these should not trigger the warning because this is a test. import _ "encoding/json" import ( "fmt" "testing" _ "os" _ "net/http" _ "path" ) var ( _ fmt.Stringer // for "fmt" _ testing.T // for "testing" ) golint-0.0+git20161013.3390df4/testdata/blank-import-main.go000066400000000000000000000002511300000745000227010ustar00rootroot00000000000000// Test that blank imports in package main are not flagged. // OK // Binary foo ... package main import _ "fmt" import ( "os" _ "path" ) var _ os.File // for "os" golint-0.0+git20161013.3390df4/testdata/broken.go000066400000000000000000000003301300000745000206360ustar00rootroot00000000000000// Test of code that is malformed, but accepted by go/parser. // See https://golang.org/issue/11271 for discussion. // OK // Package pkg ... package pkg // Foo is a method with a missing receiver. func () Foo() {} golint-0.0+git20161013.3390df4/testdata/common-methods.go000066400000000000000000000007661300000745000223240ustar00rootroot00000000000000// Test that we don't nag for comments on common methods. // OK // Package pkg ... package pkg import "net/http" // T is ... type T int func (T) Error() string { return "" } func (T) String() string { return "" } func (T) ServeHTTP(w http.ResponseWriter, r *http.Request) {} func (T) Read(p []byte) (n int, err error) { return 0, nil } func (T) Write(p []byte) (n int, err error) { return 0, nil } golint-0.0+git20161013.3390df4/testdata/const-block.go000066400000000000000000000014251300000745000216020ustar00rootroot00000000000000// Test for docs in const blocks // Package foo ... package foo const ( // Prefix for something. // MATCH /InlineWhatever.*form/ InlineWhatever = "blah" Whatsit = "missing_comment" // MATCH /Whatsit.*should have comment.*block/ // We should only warn once per block for missing comments, // but always complain about malformed comments. WhosYourDaddy = "another_missing_one" // Something // MATCH /WhatDoesHeDo.*form/ WhatDoesHeDo = "it's not a tumor!" ) // These shouldn't need doc comments. const ( Alpha = "a" Beta = "b" Gamma = "g" ) // The comment on the previous const block shouldn't flow through to here. const UndocAgain = 6 // MATCH /UndocAgain.*should have comment/ const ( SomeUndocumented = 7 // MATCH /SomeUndocumented.*should have comment.*block/ ) golint-0.0+git20161013.3390df4/testdata/context.go000066400000000000000000000011241300000745000210440ustar00rootroot00000000000000// Test that context.Context is the first arg to a function. // Package foo ... package foo import ( "context" ) // A proper context.Context location func x(ctx context.Context) { // ok } // A proper context.Context location func x(ctx context.Context, s string) { // ok } // An invalid context.Context location func y(s string, ctx context.Context) { // MATCH /context.Context should be the first parameter.*/ } // An invalid context.Context location with more than 2 args func y(s string, r int, ctx context.Context, x int) { // MATCH /context.Context should be the first parameter.*/ } golint-0.0+git20161013.3390df4/testdata/contextkeytypes.go000066400000000000000000000053151300000745000226500ustar00rootroot00000000000000// Package contextkeytypes verifies that correct types are used as keys in // calls to context.WithValue. package contextkeytypes import ( "context" "fmt" ) type ctxKey struct{} func contextKeyTypeTests() { fmt.Println() // not in package context context.TODO() // wrong function c := context.Background() // wrong function context.WithValue(c, "foo", "bar") // MATCH /should not use basic type( untyped|)? string as key in context.WithValue/ context.WithValue(c, true, "bar") // MATCH /should not use basic type( untyped|)? bool as key in context.WithValue/ context.WithValue(c, 1, "bar") // MATCH /should not use basic type( untyped|)? int as key in context.WithValue/ context.WithValue(c, int8(1), "bar") // MATCH /should not use basic type int8 as key in context.WithValue/ context.WithValue(c, int16(1), "bar") // MATCH /should not use basic type int16 as key in context.WithValue/ context.WithValue(c, int32(1), "bar") // MATCH /should not use basic type int32 as key in context.WithValue/ context.WithValue(c, rune(1), "bar") // MATCH /should not use basic type rune as key in context.WithValue/ context.WithValue(c, int64(1), "bar") // MATCH /should not use basic type int64 as key in context.WithValue/ context.WithValue(c, uint(1), "bar") // MATCH /should not use basic type uint as key in context.WithValue/ context.WithValue(c, uint8(1), "bar") // MATCH /should not use basic type uint8 as key in context.WithValue/ context.WithValue(c, byte(1), "bar") // MATCH /should not use basic type byte as key in context.WithValue/ context.WithValue(c, uint16(1), "bar") // MATCH /should not use basic type uint16 as key in context.WithValue/ context.WithValue(c, uint32(1), "bar") // MATCH /should not use basic type uint32 as key in context.WithValue/ context.WithValue(c, uint64(1), "bar") // MATCH /should not use basic type uint64 as key in context.WithValue/ context.WithValue(c, uintptr(1), "bar") // MATCH /should not use basic type uintptr as key in context.WithValue/ context.WithValue(c, float32(1.0), "bar") // MATCH /should not use basic type float32 as key in context.WithValue/ context.WithValue(c, float64(1.0), "bar") // MATCH /should not use basic type float64 as key in context.WithValue/ context.WithValue(c, complex64(1i), "bar") // MATCH /should not use basic type complex64 as key in context.WithValue/ context.WithValue(c, complex128(1i), "bar") // MATCH /should not use basic type complex128 as key in context.WithValue/ context.WithValue(c, ctxKey{}, "bar") // ok context.WithValue(c, &ctxKey{}, "bar") // ok } golint-0.0+git20161013.3390df4/testdata/else-multi.go000066400000000000000000000004401300000745000214400ustar00rootroot00000000000000// Test of return+else warning; should not trigger on multi-branch if/else. // OK // Package pkg ... package pkg import "log" func f(x int) bool { if x == 0 { log.Print("x is zero") } else if x > 0 { return true } else { log.Printf("non-positive x: %d", x) } return false } golint-0.0+git20161013.3390df4/testdata/else.go000066400000000000000000000006261300000745000203160ustar00rootroot00000000000000// Test of return+else warning. // Package pkg ... package pkg import "log" func f(x int) bool { if x > 0 { return true } else { // MATCH /if.*return.*else.*outdent/ log.Printf("non-positive x: %d", x) } return false } func g(f func() bool) string { if ok := f(); ok { return "it's okay" } else { // MATCH /if.*return.*else.*outdent.*short.*var.*declaration/ return "it's NOT okay!" } } golint-0.0+git20161013.3390df4/testdata/error-return.go000066400000000000000000000016141300000745000220320ustar00rootroot00000000000000// Test for returning errors. // Package foo ... package foo // Returns nothing func f() { // ok } // Check for a single error return func g() error { // ok return nil } // Check for a single other return type func h() int { // ok return 0 } // Check for multiple return but error at end. func i() (int, error) { // ok return 0, nil } // Check for multiple return but error at end with named variables. func j() (x int, err error) { // ok return 0, nil } // Check for error in the wrong location on 2 types func k() (error, int) { // MATCH /error should be the last type/ return nil, 0 } // Check for error in the wrong location for > 2 types func l() (int, error, int) { // MATCH /error should be the last type/ return 0, nil, 0 } // Check for error in the wrong location with named variables. func m() (x int, err error, y int) { // MATCH /error should be the last type/ return 0, nil, 0 } golint-0.0+git20161013.3390df4/testdata/errorf.go000066400000000000000000000015311300000745000206610ustar00rootroot00000000000000// Test for not using fmt.Errorf or testing.Errorf. // Package foo ... package foo import ( "errors" "fmt" "testing" ) func f(x int) error { if x > 10 { return errors.New(fmt.Sprintf("something %d", x)) // MATCH /should replace.*errors\.New\(fmt\.Sprintf\(\.\.\.\)\).*fmt\.Errorf\(\.\.\.\)/ -> ` return fmt.Errorf("something %d", x)` } if x > 5 { return errors.New(g("blah")) // ok } if x > 4 { return errors.New("something else") // ok } return nil } // TestF is a dummy test func TestF(t *testing.T) error { x := 1 if x > 10 { return t.Error(fmt.Sprintf("something %d", x)) // MATCH /should replace.*t\.Error\(fmt\.Sprintf\(\.\.\.\)\).*t\.Errorf\(\.\.\.\)/ } if x > 5 { return t.Error(g("blah")) // ok } if x > 4 { return t.Error("something else") // ok } return nil } func g(s string) string { return "prefix: " + s } golint-0.0+git20161013.3390df4/testdata/errors.go000066400000000000000000000014371300000745000207030ustar00rootroot00000000000000// Test for naming errors. // Package foo ... package foo import ( "errors" "fmt" ) var unexp = errors.New("some unexported error") // MATCH /error var.*unexp.*errFoo/ // Exp ... var Exp = errors.New("some exported error") // MATCH /error var.*Exp.*ErrFoo/ var ( e1 = fmt.Errorf("blah %d", 4) // MATCH /error var.*e1.*errFoo/ // E2 ... E2 = fmt.Errorf("blah %d", 5) // MATCH /error var.*E2.*ErrFoo/ ) func f() { var whatever = errors.New("ok") // ok _ = whatever } // Check for the error strings themselves. func g(x int) error { if x < 1 { return fmt.Errorf("This %d is too low", x) // MATCH /error strings.*not be capitalized/ } else if x == 0 { return fmt.Errorf("XML time") // ok } return errors.New(`too much stuff.`) // MATCH /error strings.*not end with punctuation/ } golint-0.0+git20161013.3390df4/testdata/import-dot.go000066400000000000000000000002121300000745000214530ustar00rootroot00000000000000// Test that dot imports are flagged. // Package pkg ... package pkg import . "fmt" // MATCH /dot import/ var _ Stringer // from "fmt" golint-0.0+git20161013.3390df4/testdata/inc.go000066400000000000000000000003061300000745000201320ustar00rootroot00000000000000// Test for use of x++ and x--. // Package pkg ... package pkg func addOne(x int) int { x += 1 // MATCH /x\+\+/ return x } func subOneInLoop(y int) { for ; y > 0; y -= 1 { // MATCH /y--/ } } golint-0.0+git20161013.3390df4/testdata/names.go000066400000000000000000000043631300000745000204730ustar00rootroot00000000000000// Test for name linting. // Package pkg_with_underscores ... package pkg_with_underscores // MATCH /underscore.*package name/ import ( "io" "net" net_http "net/http" // renamed deliberately "net/url" ) var var_name int // MATCH /underscore.*var.*var_name/ type t_wow struct { // MATCH /underscore.*type.*t_wow/ x_damn int // MATCH /underscore.*field.*x_damn/ Url *url.URL // MATCH /struct field.*Url.*URL/ } const fooId = "blah" // MATCH /fooId.*fooID/ func f_it() { // MATCH /underscore.*func.*f_it/ more_underscore := 4 // MATCH /underscore.*var.*more_underscore/ _ = more_underscore var err error if isEof := (err == io.EOF); isEof { // MATCH /var.*isEof.*isEOF/ more_underscore = 7 // should be okay } x := net_http.Request{} // should be okay _ = x var ips []net.IP for _, theIp := range ips { // MATCH /range var.*theIp.*theIP/ _ = theIp } switch myJson := g(); { // MATCH /var.*myJson.*myJSON/ default: _ = myJson } var y net_http.ResponseWriter // an interface switch tApi := y.(type) { // MATCH /var.*tApi.*tAPI/ default: _ = tApi } var c chan int select { case qId := <-c: // MATCH /var.*qId.*qID/ _ = qId } } // Common styles in other languages that don't belong in Go. const ( CPP_CONST = 1 // MATCH /ALL_CAPS.*CamelCase/ kLeadingKay = 2 // MATCH /k.*leadingKay/ HTML = 3 // okay; no underscore X509B = 4 // ditto ) func f(bad_name int) {} // MATCH /underscore.*func parameter.*bad_name/ func g() (no_way int) { return 0 } // MATCH /underscore.*func result.*no_way/ func (t *t_wow) f(more_under string) {} // MATCH /underscore.*method parameter.*more_under/ func (t *t_wow) g() (still_more string) { return "" } // MATCH /underscore.*method result.*still_more/ type i interface { CheckHtml() string // okay; interface method names are often constrained by the concrete types' method names F(foo_bar int) // MATCH /foo_bar.*fooBar/ } // All okay; underscore between digits const case1_1 = 1 type case2_1 struct { case2_2 int } func case3_1(case3_2 int) (case3_3 string) { case3_4 := 4 _ = case3_4 return "" } type t struct{} func (t) LastInsertId() (int64, error) { return 0, nil } // okay because it matches a known style violation golint-0.0+git20161013.3390df4/testdata/pkg-doc1.go000066400000000000000000000001341300000745000207650ustar00rootroot00000000000000// Test of missing package comment. package foo // MATCH /should.*package comment.*unless/ golint-0.0+git20161013.3390df4/testdata/pkg-doc2.go000066400000000000000000000002731300000745000207720ustar00rootroot00000000000000// Test of package comment in an incorrect form. // Some random package doc that isn't in the right form. // MATCH /package comment should.*form.*"Package testdata .*"/ package testdata golint-0.0+git20161013.3390df4/testdata/pkg-doc3.go000066400000000000000000000001301300000745000207630ustar00rootroot00000000000000// Test of block package comment. // OK /* Package foo is pretty sweet. */ package foo golint-0.0+git20161013.3390df4/testdata/pkg-doc4.go000066400000000000000000000002151300000745000207700ustar00rootroot00000000000000// Test of block package comment with leading space. /* Package foo is pretty sweet. MATCH /package comment.*leading space/ */ package foo golint-0.0+git20161013.3390df4/testdata/pkg-doc5.go000066400000000000000000000001761300000745000207770ustar00rootroot00000000000000// Test of detached package comment. /* Package foo is pretty sweet. */ package foo // MATCH:6 /package comment.*detached/ golint-0.0+git20161013.3390df4/testdata/pkg-main.go000066400000000000000000000001501300000745000210610ustar00rootroot00000000000000// Test of package comment for package main. // OK // This binary does something awesome. package main golint-0.0+git20161013.3390df4/testdata/range.go000066400000000000000000000010741300000745000204600ustar00rootroot00000000000000// Test for range construction. // Package foo ... package foo func f() { var m map[string]int // with := for x, _ := range m { // MATCH /should omit 2nd value.*range.*equivalent.*for x := range/ -> ` for x := range m {` _ = x } // with = var y string _ = y for y, _ = range m { // MATCH /should omit 2nd value.*range.*equivalent.*for y = range/ } // all OK: for x := range m { _ = x } for x, y := range m { _, _ = x, y } for _, y := range m { _ = y } var x int _ = x for y = range m { } for y, x = range m { } for _, x = range m { } } golint-0.0+git20161013.3390df4/testdata/receiver-names.go000066400000000000000000000015011300000745000222640ustar00rootroot00000000000000// Test for bad receiver names. // Package foo ... package foo type foo struct{} func (this foo) f1() { // MATCH /should be a reflection of its identity/ } func (self foo) f2() { // MATCH /should be a reflection of its identity/ } func (f foo) f3() { } func (foo) f4() { } type bar struct{} func (b bar) f1() { } func (b bar) f2() { } func (a bar) f3() { // MATCH /receiver name a should be consistent with previous receiver name b for bar/ } func (a *bar) f4() { // MATCH /receiver name a should be consistent with previous receiver name b for bar/ } func (b *bar) f5() { } func (bar) f6() { } func (_ *bar) f7() { // MATCH /receiver name should not be an underscore/ } type multiError struct{} func (me multiError) f8() { } // Regression test for a panic caused by ill-formed receiver type. func (recv []*x.y) f() golint-0.0+git20161013.3390df4/testdata/sort.go000066400000000000000000000010111300000745000203420ustar00rootroot00000000000000// Test that we don't ask for comments on sort.Interface methods. // Package pkg ... package pkg // T is ... type T []int // Len by itself should get documented. func (t T) Len() int { return len(t) } // MATCH /exported method T\.Len.*should.*comment/ // U is ... type U []int func (u U) Len() int { return len(u) } func (u U) Less(i, j int) bool { return u[i] < u[j] } func (u U) Swap(i, j int) { u[i], u[j] = u[j], u[i] } func (u U) Other() {} // MATCH /exported method U\.Other.*should.*comment/ golint-0.0+git20161013.3390df4/testdata/stutter.go000066400000000000000000000012051300000745000210720ustar00rootroot00000000000000// Test of stuttery names. // Package donut ... package donut // DonutMaker makes donuts. type DonutMaker struct{} // MATCH /donut\.DonutMaker.*stutter/ // DonutRank computes the ranking of a donut. func DonutRank(d Donut) int { // MATCH /donut\.DonutRank.*stutter/ return 0 } // Donut is a delicious treat. type Donut struct{} // ok because it is the whole name // Donuts are great, aren't they? type Donuts []Donut // ok because it didn't start a new word type donutGlaze int // ok because it is unexported // DonutMass reports the mass of a donut. func (d *Donut) DonutMass() (grams int) { // okay because it is a method return 38 } golint-0.0+git20161013.3390df4/testdata/time.go000066400000000000000000000004171300000745000203220ustar00rootroot00000000000000// Test of time suffixes. // Package foo ... package foo import ( "flag" "time" ) var rpcTimeoutMsec = flag.Duration("rpc_timeout", 100*time.Millisecond, "some flag") // MATCH /Msec.*\*time.Duration/ var timeoutSecs = 5 * time.Second // MATCH /Secs.*time.Duration/ golint-0.0+git20161013.3390df4/testdata/unexp-return.go000066400000000000000000000016101300000745000220340ustar00rootroot00000000000000// Test for unexported return types. // Package foo ... package foo import () type hidden struct{} // Exported returns a hidden type, which is annoying. func Exported() hidden { // MATCH /Exported.*unexported.*hidden/ return hidden{} } // ExpErr returns a builtin type. func ExpErr() error { // ok } func (hidden) ExpOnHidden() hidden { // ok } // T is another test type. type T struct{} // MethodOnT returns a hidden type, which is annoying. func (T) MethodOnT() hidden { // MATCH /method MethodOnT.*unexported.*hidden/ return hidden{} } // ExpT returns a T. func ExpT() T { // ok return T{} } func unexp() hidden { // ok return hidden{} } // This is slightly sneaky: we shadow the builtin "int" type. type int struct{} // ExportedIntReturner returns an unexported type from this package. func ExportedIntReturner() int { // MATCH /ExportedIntReturner.*unexported.*int/ return int{} } golint-0.0+git20161013.3390df4/testdata/var-decl.go000066400000000000000000000047311300000745000210640ustar00rootroot00000000000000// Test for redundant type declaration. // Package foo ... package foo import ( "fmt" "io" "net/http" "nosuchpkg" // export data unavailable "os" ) // Q is a test type. type Q bool var myInt int = 7 // MATCH /should.*int.*myInt.*inferred/ var mux *http.ServeMux = http.NewServeMux() // MATCH /should.*\*http\.ServeMux.*inferred/ var myZeroInt int = 0 // MATCH /should.*= 0.*myZeroInt.*zero value/ var myZeroFlt float32 = 0. // MATCH /should.*= 0\..*myZeroFlt.*zero value/ var myZeroF64 float64 = 0.0 // MATCH /should.*= 0\..*myZeroF64.*zero value/ var myZeroImg complex64 = 0i // MATCH /should.*= 0i.*myZeroImg.*zero value/ var myZeroStr string = "" // MATCH /should.*= "".*myZeroStr.*zero value/ var myZeroRaw string = `` // MATCH /should.*= ``.*myZeroRaw.*zero value/ var myZeroPtr *Q = nil // MATCH /should.*= nil.*myZeroPtr.*zero value/ var myZeroRune rune = '\x00' // MATCH /should.*= '\\x00'.*myZeroRune.*zero value/ var myZeroRune2 rune = '\000' // MATCH /should.*= '\\000'.*myZeroRune2.*zero value/ // No warning because there's no type on the LHS var x = 0 // This shouldn't get a warning because there's no initial values. var str fmt.Stringer // No warning because this is a const. const k uint64 = 7 const num = 123 // No warning because the var's RHS is known to be an untyped const. var flags uint32 = num // No warnings because the RHS is an ideal int, and the LHS is a different int type. var userID int64 = 1235 var negID int64 = -1 var parenID int64 = (17) var crazyID int64 = -(-(-(-9))) // Same, but for strings and floats. type stringT string type floatT float64 var stringV stringT = "abc" var floatV floatT = 123.45 // No warning because the LHS names an interface type. var data interface{} = googleIPs var googleIPs []int // No warning because it's a common idiom for interface satisfaction. var _ Server = (*serverImpl)(nil) // Server is a test type. type Server interface{} type serverImpl struct{} // LHS is a different type than the RHS. var myStringer fmt.Stringer = q(0) // LHS is a different type than the RHS. var out io.Writer = os.Stdout var out2 io.Writer = newWriter() // MATCH /should omit.*io\.Writer/ func newWriter() io.Writer { return nil } // We don't figure out the true types of LHS and RHS here, // so we suppress the check. var ni nosuchpkg.Interface = nosuchpkg.NewConcrete() var y string = q(1).String() // MATCH /should.*string/ type q int func (q) String() string { return "I'm a q" }