package noglobals
import (
"flag"
"fmt"
"go/ast"
"go/token"
"strings"
"golang.org/x/tools/go/analysis"
)
const Doc = `check that no global variables exist
This analyzer checks for global variables and errors on any found.
A global variable is an exported variable declared in package scope, and so
has global visibility and unlimited coupling.`
// Analyzer provides an Analyzer that checks that there are no global
// variables, except for errors and variables containing regular
// expressions.
func Analyzer() *analysis.Analyzer {
return &analysis.Analyzer{
Name: "noglobals",
Doc: Doc,
Run: checkNoGlobals,
Flags: flags(),
RunDespiteErrors: true,
}
}
func flags() flag.FlagSet {
flags := flag.NewFlagSet("", flag.ExitOnError)
flags.Bool("t", false, "include tests (files that end with _test.go)")
flags.Bool("err", true, "allow global variables that look like errors")
return *flags
}
func checkNoGlobals(pass *analysis.Pass) (interface{}, error) {
includeTests := pass.Analyzer.Flags.Lookup("t").Value.(flag.Getter).Get().(bool)
for _, file := range pass.Files {
filename := pass.Fset.Position(file.Pos()).Filename
if !strings.HasSuffix(filename, ".go") {
continue
}
if !includeTests && strings.HasSuffix(filename, "_test.go") {
continue
}
for _, decl := range file.Decls {
genDecl, ok := decl.(*ast.GenDecl)
if !ok {
continue
}
if genDecl.Tok != token.VAR {
continue
}
for _, spec := range genDecl.Specs {
valueSpec := spec.(*ast.ValueSpec)
for _, vn := range valueSpec.Names {
if !vn.IsExported() {
continue
}
if strings.HasPrefix(vn.Name, "Err") {
continue
}
pass.Report(analysis.Diagnostic{
Pos: vn.Pos(),
End: vn.End(),
Category: "global",
Message: fmt.Sprintf("%s is a global variable", vn.Name),
})
}
}
}
}
return nil, nil
}