mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2024-12-24 09:20:31 +00:00
155 lines
3.5 KiB
Go
155 lines
3.5 KiB
Go
|
package checknoglobals
|
||
|
|
||
|
import (
|
||
|
"flag"
|
||
|
"fmt"
|
||
|
"go/ast"
|
||
|
"go/token"
|
||
|
"strings"
|
||
|
|
||
|
"golang.org/x/tools/go/analysis"
|
||
|
)
|
||
|
|
||
|
// allowedExpression is a struct representing packages and methods that will
|
||
|
// be an allowed combination to use as a global variable, f.ex. Name `regexp`
|
||
|
// and SelName `MustCompile`.
|
||
|
type allowedExpression struct {
|
||
|
Name string
|
||
|
SelName string
|
||
|
}
|
||
|
|
||
|
const Doc = `check that no global variables exist
|
||
|
|
||
|
This analyzer checks for global variables and errors on any found.
|
||
|
|
||
|
A global variable is a variable declared in package scope and that can be read
|
||
|
and written to by any function within the package. Global variables can cause
|
||
|
side effects which are difficult to keep track of. A code in one function may
|
||
|
change the variables state while another unrelated chunk of code may be
|
||
|
effected by it.`
|
||
|
|
||
|
// 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: "gochecknoglobals",
|
||
|
Doc: Doc,
|
||
|
Run: checkNoGlobals,
|
||
|
Flags: flags(),
|
||
|
RunDespiteErrors: true,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func flags() flag.FlagSet {
|
||
|
flags := flag.NewFlagSet("", flag.ExitOnError)
|
||
|
flags.Bool("t", false, "Include tests")
|
||
|
|
||
|
return *flags
|
||
|
}
|
||
|
|
||
|
func isAllowed(v ast.Node) bool {
|
||
|
switch i := v.(type) {
|
||
|
case *ast.Ident:
|
||
|
return i.Name == "_" || i.Name == "version" || looksLikeError(i)
|
||
|
case *ast.CallExpr:
|
||
|
if expr, ok := i.Fun.(*ast.SelectorExpr); ok {
|
||
|
return isAllowedSelectorExpression(expr)
|
||
|
}
|
||
|
case *ast.CompositeLit:
|
||
|
if expr, ok := i.Type.(*ast.SelectorExpr); ok {
|
||
|
return isAllowedSelectorExpression(expr)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
func isAllowedSelectorExpression(v *ast.SelectorExpr) bool {
|
||
|
x, ok := v.X.(*ast.Ident)
|
||
|
if !ok {
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
allowList := []allowedExpression{
|
||
|
{Name: "regexp", SelName: "MustCompile"},
|
||
|
}
|
||
|
|
||
|
for _, i := range allowList {
|
||
|
if x.Name == i.Name && v.Sel.Name == i.SelName {
|
||
|
return true
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
// looksLikeError returns true if the AST identifier starts
|
||
|
// with 'err' or 'Err', or false otherwise.
|
||
|
//
|
||
|
// TODO: https://github.com/leighmcculloch/gochecknoglobals/issues/5
|
||
|
func looksLikeError(i *ast.Ident) bool {
|
||
|
prefix := "err"
|
||
|
if i.IsExported() {
|
||
|
prefix = "Err"
|
||
|
}
|
||
|
return strings.HasPrefix(i.Name, prefix)
|
||
|
}
|
||
|
|
||
|
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)
|
||
|
onlyAllowedValues := false
|
||
|
|
||
|
for _, vn := range valueSpec.Values {
|
||
|
if isAllowed(vn) {
|
||
|
onlyAllowedValues = true
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
onlyAllowedValues = false
|
||
|
break
|
||
|
}
|
||
|
|
||
|
if onlyAllowedValues {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
for _, vn := range valueSpec.Names {
|
||
|
if isAllowed(vn) {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
message := fmt.Sprintf("%s is a global variable", vn.Name)
|
||
|
pass.Report(analysis.Diagnostic{
|
||
|
Pos: vn.Pos(),
|
||
|
Category: "global",
|
||
|
Message: message,
|
||
|
})
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return nil, nil
|
||
|
}
|