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 }