Skip to content

Commit

Permalink
Merge pull request #1817 from OffchainLabs/koanf
Browse files Browse the repository at this point in the history
Introduce koanf linter that checks that field names match koanf tags
  • Loading branch information
anodar authored Sep 1, 2023
2 parents 8252c29 + a645527 commit beea4fb
Show file tree
Hide file tree
Showing 4 changed files with 143 additions and 0 deletions.
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
100 changes: 100 additions & 0 deletions linter/koanf/koanf.go
Original file line number Diff line number Diff line change
@@ -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)
}
31 changes: 31 additions & 0 deletions linter/koanf/koanf_test.go
Original file line number Diff line number Diff line change
@@ -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
}
11 changes: 11 additions & 0 deletions linter/testdata/src/a/a.go
Original file line number Diff line number Diff line change
@@ -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"`
}

0 comments on commit beea4fb

Please sign in to comment.