diff --git a/Makefile b/Makefile index d180a8cfff..0d93958c2d 100644 --- a/Makefile +++ b/Makefile @@ -304,6 +304,7 @@ contracts/test/prover/proofs/%.json: $(arbitrator_cases)/%.wasm $(arbitrator_pro # strategic rules to minimize dependency building .make/lint: $(DEP_PREDICATE) build-node-deps $(ORDER_ONLY_PREDICATE) .make + go run linter/koanf/koanf.go ./... go run linter/pointercheck/pointer.go ./... golangci-lint run --fix yarn --cwd contracts solhint diff --git a/linter/koanf/koanf.go b/linter/koanf/koanf.go new file mode 100644 index 0000000000..2127fb23b0 --- /dev/null +++ b/linter/koanf/koanf.go @@ -0,0 +1,100 @@ +package main + +import ( + "fmt" + "go/ast" + "go/token" + "reflect" + "strings" + + "github.com/fatih/structtag" + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/analysis/singlechecker" +) + +func New(conf any) ([]*analysis.Analyzer, error) { + return []*analysis.Analyzer{Analyzer}, nil +} + +var Analyzer = &analysis.Analyzer{ + Name: "koanfcheck", + Doc: "check for koanf misconfigurations", + Run: func(p *analysis.Pass) (interface{}, error) { return run(false, p) }, + ResultType: reflect.TypeOf(Result{}), +} + +var analyzerForTests = &analysis.Analyzer{ + Name: "testkoanfcheck", + Doc: "check for koanf misconfigurations (for tests)", + Run: func(p *analysis.Pass) (interface{}, error) { return run(true, p) }, + ResultType: reflect.TypeOf(Result{}), +} + +// koanfError indicates the position of an error in configuration. +type koanfError struct { + Pos token.Position + Message string +} + +// Result is returned from the checkStruct function, and holds all the +// configuration errors. +type Result struct { + Errors []koanfError +} + +func run(dryRun bool, pass *analysis.Pass) (interface{}, error) { + var ret Result + for _, f := range pass.Files { + ast.Inspect(f, func(node ast.Node) bool { + var res Result + switch v := node.(type) { + case *ast.StructType: + res = checkStruct(pass, v) + default: + } + for _, err := range res.Errors { + ret.Errors = append(ret.Errors, err) + if !dryRun { + pass.Report(analysis.Diagnostic{ + Pos: pass.Fset.File(f.Pos()).Pos(err.Pos.Offset), + Message: err.Message, + Category: "koanf", + }) + } + } + return true + }, + ) + } + return ret, nil +} + +func checkStruct(pass *analysis.Pass, s *ast.StructType) Result { + var res Result + for _, f := range s.Fields.List { + if f.Tag == nil { + continue + } + tags, err := structtag.Parse(strings.Trim((f.Tag.Value), "`")) + if err != nil { + continue + } + tag, err := tags.Get("koanf") + if err != nil { + continue + } + tagName := strings.ReplaceAll(tag.Name, "-", "") + fieldName := f.Names[0].Name + if !strings.EqualFold(tagName, fieldName) { + res.Errors = append(res.Errors, koanfError{ + Pos: pass.Fset.Position(f.Pos()), + Message: fmt.Sprintf("field name: %q doesn't match tag name: %q\n", fieldName, tagName), + }) + } + } + return res +} + +func main() { + singlechecker.Main(Analyzer) +} diff --git a/linter/koanf/koanf_test.go b/linter/koanf/koanf_test.go new file mode 100644 index 0000000000..2e3e68b0f4 --- /dev/null +++ b/linter/koanf/koanf_test.go @@ -0,0 +1,31 @@ +package main + +import ( + "os" + "path/filepath" + "testing" + + "golang.org/x/tools/go/analysis/analysistest" +) + +func TestAll(t *testing.T) { + wd, err := os.Getwd() + if err != nil { + t.Fatalf("Failed to get wd: %s", err) + } + testdata := filepath.Join(filepath.Dir(wd), "testdata") + res := analysistest.Run(t, testdata, analyzerForTests, "a") + if cnt := countErrors(res); cnt != 1 { + t.Errorf("analysistest.Run() got %v errors, expected 1", cnt) + } +} + +func countErrors(errs []*analysistest.Result) int { + cnt := 0 + for _, e := range errs { + if r, ok := e.Result.(Result); ok { + cnt += len(r.Errors) + } + } + return cnt +} diff --git a/linter/testdata/src/a/a.go b/linter/testdata/src/a/a.go new file mode 100644 index 0000000000..ddf77b6ed1 --- /dev/null +++ b/linter/testdata/src/a/a.go @@ -0,0 +1,11 @@ +package a + +type Config struct { + L2 int `koanf:"chain"` + LogLevel int `koanf:"log-level"` + LogType int `koanf:"log-type"` + Metrics int `koanf:"metrics"` + PProf int `koanf:"pprof"` + Node int `koanf:"node"` + Queue int `koanf:"queue"` +}