diff --git a/checkers/ruleguard_checker.go b/checkers/ruleguard_checker.go new file mode 100644 index 000000000..4968a81ed --- /dev/null +++ b/checkers/ruleguard_checker.go @@ -0,0 +1,95 @@ +package checkers + +import ( + "bytes" + "go/ast" + "go/token" + "io/ioutil" + "log" + + "github.com/go-lintpack/lintpack" + "github.com/quasilyte/go-ruleguard/ruleguard" +) + +func init() { + var info lintpack.CheckerInfo + info.Name = "ruleguard" + info.Tags = []string{"style", "experimental"} + info.Params = lintpack.CheckerParams{ + "rules": { + Value: "", + Usage: "path to a gorules file", + }, + } + info.Summary = "Runs user-defined rules using ruleguard linter" + info.Details = "Reads a rules file and turns them into go-critic checkers." + info.Before = `N/A` + info.After = `N/A` + info.Note = "See https://github.com/quasilyte/go-ruleguard." + + collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker { + return newRuleguardChecker(&info, ctx) + }) +} + +func newRuleguardChecker(info *lintpack.CheckerInfo, ctx *lintpack.CheckerContext) *ruleguardChecker { + c := &ruleguardChecker{ctx: ctx} + rulesFilename := info.Params.String("rules") + if rulesFilename == "" { + return c + } + + // TODO(quasilyte): handle initialization errors better when we make + // a transition to the go/analysis framework. + // + // For now, we log error messages and return a ruleguard checker + // with an empty rules set. + + data, err := ioutil.ReadFile(rulesFilename) + if err != nil { + log.Printf("ruleguard init error: %+v", err) + return c + } + + fset := token.NewFileSet() + rset, err := ruleguard.ParseRules(rulesFilename, fset, bytes.NewReader(data)) + if err != nil { + log.Printf("ruleguard init error: %+v", err) + return c + } + + c.rset = rset + return c +} + +type ruleguardChecker struct { + ctx *lintpack.CheckerContext + + rset *ruleguard.GoRuleSet +} + +func (c *ruleguardChecker) WalkFile(f *ast.File) { + if c.rset == nil { + return + } + + ctx := &ruleguard.Context{ + Pkg: c.ctx.Pkg, + Types: c.ctx.TypesInfo, + Sizes: c.ctx.SizesInfo, + Fset: c.ctx.FileSet, + Report: func(n ast.Node, msg string, _ *ruleguard.Suggestion) { + // TODO(quasilyte): investigate whether we should add a rule name as + // a message prefix here. + c.ctx.Warn(n, msg) + }, + } + + err := ruleguard.RunRules(ctx, f, c.rset) + if err != nil { + // Normally this should never happen, but since + // we don't have a better mechanism to report errors, + // emit a warning. + c.ctx.Warn(f, "execution error: %v", err) + } +} diff --git a/checkers/testdata/_integration/ruleguard/f1.go b/checkers/testdata/_integration/ruleguard/f1.go new file mode 100644 index 000000000..7773e577a --- /dev/null +++ b/checkers/testdata/_integration/ruleguard/f1.go @@ -0,0 +1,10 @@ +package foo + +import "fmt" + +type myError error + +func _() { + var s1, s2 string + var _ = fmt.Sprintf("%s%s", s1, s2) +} diff --git a/checkers/testdata/_integration/ruleguard/linttest.golden b/checkers/testdata/_integration/ruleguard/linttest.golden new file mode 100644 index 000000000..360f7d5a2 --- /dev/null +++ b/checkers/testdata/_integration/ruleguard/linttest.golden @@ -0,0 +1,3 @@ +exit status 1 +./f1.go:5:1: ruleguard: error as an underlying type is probably a mistake +./f1.go:9:10: ruleguard: suggestion: s1+s2 \ No newline at end of file diff --git a/checkers/testdata/_integration/ruleguard/linttest.params b/checkers/testdata/_integration/ruleguard/linttest.params new file mode 100644 index 000000000..e8c6192c5 --- /dev/null +++ b/checkers/testdata/_integration/ruleguard/linttest.params @@ -0,0 +1 @@ +check -@ruleguard.rules ./rules.go -enable ruleguard ./... | linttest.golden diff --git a/checkers/testdata/_integration/ruleguard/rules.go b/checkers/testdata/_integration/ruleguard/rules.go new file mode 100644 index 000000000..b3fc23ef7 --- /dev/null +++ b/checkers/testdata/_integration/ruleguard/rules.go @@ -0,0 +1,15 @@ +// +build ignore + +package gorules + +import "github.com/quasilyte/go-ruleguard/dsl/fluent" + +func _(m fluent.Matcher) { + m.Match(`type $x error`). + Report(`error as an underlying type is probably a mistake`). + Suggest(`type $x struct { error }`) + + m.Match(`fmt.Sprintf("%s%s", $a, $b)`). + Where(m["a"].Type.Is(`string`) && m["b"].Type.Is(`string`)). + Suggest(`$a+$b`) +} diff --git a/go.mod b/go.mod index 2dd1983c2..6579fa4dd 100644 --- a/go.mod +++ b/go.mod @@ -14,5 +14,6 @@ require ( github.com/mattn/goveralls v0.0.2 github.com/pborman/uuid v1.2.0 // indirect github.com/quasilyte/go-consistent v0.0.0-20190521200055-c6f3937de18c + github.com/quasilyte/go-ruleguard v0.1.2-0.20200318202121-b00d7a75d3d8 golang.org/x/tools v0.0.0-20190521203540-521d6ed310dd ) diff --git a/go.sum b/go.sum index bb210d582..7bb61933c 100644 --- a/go.sum +++ b/go.sum @@ -35,6 +35,12 @@ github.com/pborman/uuid v1.2.0 h1:J7Q5mO4ysT1dv8hyrUGHb9+ooztCXu1D8MY8DZYsu3g= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/quasilyte/go-consistent v0.0.0-20190521200055-c6f3937de18c h1:JoUA0uz9U0FVFq5p4LjEq4C0VgQ0El320s3Ms0V4eww= github.com/quasilyte/go-consistent v0.0.0-20190521200055-c6f3937de18c/go.mod h1:5STLWrekHfjyYwxBRVRXNOSewLJ3PWfDJd1VyTS21fI= +github.com/quasilyte/go-ruleguard v0.1.1 h1:L+vAaUaPT/iI7p6vl1691zTC5EAAOZE+Fous1k6e9IE= +github.com/quasilyte/go-ruleguard v0.1.1/go.mod h1:CGFX09Ci3pq9QZdj86B+VGIdNj4VyCo2iPOGS9esB/k= +github.com/quasilyte/go-ruleguard v0.1.2-0.20200318181856-dc8aae974065 h1:JViY6/0dUURSKOyIRZZo1wSn5fbQzB8Y4uSIrV8gIT0= +github.com/quasilyte/go-ruleguard v0.1.2-0.20200318181856-dc8aae974065/go.mod h1:CGFX09Ci3pq9QZdj86B+VGIdNj4VyCo2iPOGS9esB/k= +github.com/quasilyte/go-ruleguard v0.1.2-0.20200318202121-b00d7a75d3d8 h1:DvnesvLtRPQOvaUbfXfh0tpMHg29by0H7F2U+QIkSu8= +github.com/quasilyte/go-ruleguard v0.1.2-0.20200318202121-b00d7a75d3d8/go.mod h1:CGFX09Ci3pq9QZdj86B+VGIdNj4VyCo2iPOGS9esB/k= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=