st1012.go raw

   1  package st1012
   2  
   3  import (
   4  	"fmt"
   5  	"go/ast"
   6  	"go/token"
   7  	"strings"
   8  
   9  	"honnef.co/go/tools/analysis/code"
  10  	"honnef.co/go/tools/analysis/lint"
  11  	"honnef.co/go/tools/analysis/report"
  12  
  13  	"golang.org/x/tools/go/analysis"
  14  )
  15  
  16  var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
  17  	Analyzer: &analysis.Analyzer{
  18  		Name: "ST1012",
  19  		Run:  run,
  20  	},
  21  	Doc: &lint.RawDocumentation{
  22  		Title: `Poorly chosen name for error variable`,
  23  		Text: `Error variables that are part of an API should be called \'errFoo\' or
  24  \'ErrFoo\'.`,
  25  		Since:   "2019.1",
  26  		MergeIf: lint.MergeIfAny,
  27  	},
  28  })
  29  
  30  var Analyzer = SCAnalyzer.Analyzer
  31  
  32  func run(pass *analysis.Pass) (interface{}, error) {
  33  	for _, f := range pass.Files {
  34  		for _, decl := range f.Decls {
  35  			gen, ok := decl.(*ast.GenDecl)
  36  			if !ok || gen.Tok != token.VAR {
  37  				continue
  38  			}
  39  			for _, spec := range gen.Specs {
  40  				spec := spec.(*ast.ValueSpec)
  41  				if len(spec.Names) != len(spec.Values) {
  42  					continue
  43  				}
  44  
  45  				for i, name := range spec.Names {
  46  					val := spec.Values[i]
  47  					if !code.IsCallToAny(pass, val, "errors.New", "fmt.Errorf") {
  48  						continue
  49  					}
  50  
  51  					if pass.Pkg.Path() == "net/http" && strings.HasPrefix(name.Name, "http2err") {
  52  						// special case for internal variable names of
  53  						// bundled HTTP 2 code in net/http
  54  						continue
  55  					}
  56  					prefix := "err"
  57  					if name.IsExported() {
  58  						prefix = "Err"
  59  					}
  60  					if !strings.HasPrefix(name.Name, prefix) {
  61  						report.Report(pass, name, fmt.Sprintf("error var %s should have name of the form %sFoo", name.Name, prefix))
  62  					}
  63  				}
  64  			}
  65  		}
  66  	}
  67  	return nil, nil
  68  }
  69