diff --git a/analyzer/analyzer.go b/analyzer/analyzer.go index 4ba1529b..3031f80e 100644 --- a/analyzer/analyzer.go +++ b/analyzer/analyzer.go @@ -99,7 +99,7 @@ func runAnalyzer(pass *analysis.Pass) (interface{}, error) { fullMessage := msg if printRuleLocation { fullMessage = fmt.Sprintf("%s: %s (%s:%d)", - info.Group, msg, filepath.Base(info.Group.Filename), info.Line) + info.Group.Name, msg, filepath.Base(info.Group.Filename), info.Line) } diag := analysis.Diagnostic{ Pos: n.Pos(), diff --git a/go.mod b/go.mod index 2d94ee79..65f019e0 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,6 @@ require ( github.com/go-toolsmith/astequal v1.0.0 github.com/google/go-cmp v0.5.2 github.com/quasilyte/go-ruleguard/dsl v0.3.2 - github.com/quasilyte/go-ruleguard/rules v0.0.0-20210203162857-b223e0831f88 + github.com/quasilyte/go-ruleguard/rules v0.0.0-20210428214800-545e0d2e0bf7 golang.org/x/tools v0.0.0-20201230224404-63754364767c ) diff --git a/go.sum b/go.sum index 264cbc3b..5da8236e 100644 --- a/go.sum +++ b/go.sum @@ -19,6 +19,8 @@ github.com/quasilyte/go-ruleguard/rules v0.0.0-20201231183845-9e62ed36efe1 h1:PX github.com/quasilyte/go-ruleguard/rules v0.0.0-20201231183845-9e62ed36efe1/go.mod h1:7JTjp89EGyU1d6XfBiXihJNG37wB2VRkd125Q1u7Plc= github.com/quasilyte/go-ruleguard/rules v0.0.0-20210203162857-b223e0831f88 h1:PeTrJiH/dSeruL/Z9Db39NRMwI/yoA3oHCdCkg+Wh8A= github.com/quasilyte/go-ruleguard/rules v0.0.0-20210203162857-b223e0831f88/go.mod h1:4cgAphtvu7Ftv7vOT2ZOYhC6CvBxZixcasr8qIOTA50= +github.com/quasilyte/go-ruleguard/rules v0.0.0-20210428214800-545e0d2e0bf7 h1:cRLFDAB53r5wIkxYvtQUMnn3+B09uZTAOPmefNfVk5I= +github.com/quasilyte/go-ruleguard/rules v0.0.0-20210428214800-545e0d2e0bf7/go.mod h1:4cgAphtvu7Ftv7vOT2ZOYhC6CvBxZixcasr8qIOTA50= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= diff --git a/ruleguard/engine.go b/ruleguard/engine.go index 66a4fd58..3e077bef 100644 --- a/ruleguard/engine.go +++ b/ruleguard/engine.go @@ -6,6 +6,7 @@ import ( "go/ast" "go/types" "io" + "sort" "strings" "sync" @@ -25,6 +26,17 @@ func newEngine() *engine { } } +func (e *engine) LoadedGroups() []GoRuleGroup { + result := make([]GoRuleGroup, 0, len(e.ruleSet.groups)) + for _, g := range e.ruleSet.groups { + result = append(result, *g) + } + sort.Slice(result, func(i, j int) bool { + return result[i].Name < result[j].Name + }) + return result +} + func (e *engine) Load(ctx *ParseContext, filename string, r io.Reader) error { config := rulesParserConfig{ state: e.state, diff --git a/ruleguard/gorule.go b/ruleguard/gorule.go index 35fdc801..d73842ea 100644 --- a/ruleguard/gorule.go +++ b/ruleguard/gorule.go @@ -3,7 +3,6 @@ package ruleguard import ( "fmt" "go/ast" - "go/token" "go/types" "regexp" @@ -15,7 +14,7 @@ import ( type goRuleSet struct { universal *scopedGoRuleSet - groups map[string]token.Position // To handle redefinitions + groups map[string]*GoRuleGroup // To handle redefinitions } type scopedGoRuleSet struct { @@ -32,7 +31,6 @@ type goCommentRule struct { type goRule struct { group *GoRuleGroup - filename string line int pat *gogrep.Pattern msg string @@ -108,18 +106,18 @@ func cloneRuleSet(rset *goRuleSet) *goRuleSet { func mergeRuleSets(toMerge []*goRuleSet) (*goRuleSet, error) { out := &goRuleSet{ universal: &scopedGoRuleSet{}, - groups: make(map[string]token.Position), + groups: make(map[string]*GoRuleGroup), } for _, x := range toMerge { out.universal = appendScopedRuleSet(out.universal, x.universal) - for group, pos := range x.groups { - if prevPos, ok := out.groups[group]; ok { - newRef := fmt.Sprintf("%s:%d", pos.Filename, pos.Line) - oldRef := fmt.Sprintf("%s:%d", prevPos.Filename, prevPos.Line) - return nil, fmt.Errorf("%s: redefinition of %s(), previously defined at %s", newRef, group, oldRef) + for groupName, group := range x.groups { + if prevGroup, ok := out.groups[groupName]; ok { + newRef := fmt.Sprintf("%s:%d", group.Filename, group.Line) + oldRef := fmt.Sprintf("%s:%d", prevGroup.Filename, prevGroup.Line) + return nil, fmt.Errorf("%s: redefinition of %s(), previously defined at %s", newRef, groupName, oldRef) } - out.groups[group] = pos + out.groups[groupName] = group } } diff --git a/ruleguard/parser.go b/ruleguard/parser.go index 538622af..daf1c882 100644 --- a/ruleguard/parser.go +++ b/ruleguard/parser.go @@ -86,7 +86,7 @@ func (p *rulesParser) ParseFile(filename string, r io.Reader) (*goRuleSet, error p.filename = filename p.res = &goRuleSet{ universal: &scopedGoRuleSet{}, - groups: make(map[string]token.Position), + groups: make(map[string]*GoRuleGroup), } parserFlags := parser.ParseComments @@ -304,7 +304,9 @@ func (p *rulesParser) parseRuleGroup(f *ast.FuncDecl) (err error) { matcher := params[0].Names[0].Name p.group = &GoRuleGroup{ - Name: f.Name.Name, + Line: p.ctx.Fset.Position(f.Name.Pos()).Line, + Filename: p.filename, + Name: f.Name.Name, } if p.prefix != "" { p.group.Name = p.prefix + "/" + p.group.Name @@ -321,10 +323,7 @@ func (p *rulesParser) parseRuleGroup(f *ast.FuncDecl) (err error) { if _, ok := p.res.groups[p.group.Name]; ok { panic(fmt.Sprintf("duplicated function %s after the typecheck", p.group)) // Should never happen } - p.res.groups[p.group.Name] = token.Position{ - Filename: p.filename, - Line: p.ctx.Fset.Position(f.Name.Pos()).Line, - } + p.res.groups[p.group.Name] = p.group p.itab.EnterScope() defer p.itab.LeaveScope() @@ -477,9 +476,8 @@ func (p *rulesParser) parseRule(matcher string, call *ast.CallExpr) error { } proto := goRule{ - filename: p.filename, - line: p.ctx.Fset.Position(origCall.Pos()).Line, - group: p.group, + line: p.ctx.Fset.Position(origCall.Pos()).Line, + group: p.group, } // AST patterns for Match() or regexp patterns for MatchComment(). diff --git a/ruleguard/ruleguard.go b/ruleguard/ruleguard.go index e51b53bc..ccc304e4 100644 --- a/ruleguard/ruleguard.go +++ b/ruleguard/ruleguard.go @@ -34,6 +34,11 @@ func (e *Engine) Load(ctx *ParseContext, filename string, r io.Reader) error { return e.impl.Load(ctx, filename, r) } +// LoadedGroups returns information about all currently loaded rule groups. +func (e *Engine) LoadedGroups() []GoRuleGroup { + return e.impl.LoadedGroups() +} + // Run executes all loaded rules on a given file. // Matched rules invoke `RunContext.Report()` method. // @@ -87,6 +92,13 @@ type GoRuleGroup struct { // Name is a function name associated with this rule group. Name string + // Pos is a location where this rule group was defined. + Pos token.Position + + // Line is a source code line number inside associated file. + // A pair of Filename:Line form a conventional location string. + Line int + // Filename is a file that defined this rule group. Filename string diff --git a/ruleguard/runner.go b/ruleguard/runner.go index 3efb5c3c..e6fc8c24 100644 --- a/ruleguard/runner.go +++ b/ruleguard/runner.go @@ -199,7 +199,7 @@ func (rr *rulesRunner) reject(rule goRule, reason string, m matchData) { pos := rr.ctx.Fset.Position(m.Node().Pos()) rr.ctx.DebugPrint(fmt.Sprintf("%s:%d: [%s:%d] rejected by %s", - pos.Filename, pos.Line, filepath.Base(rule.filename), rule.line, reason)) + pos.Filename, pos.Line, filepath.Base(rule.group.Filename), rule.line, reason)) values := make([]gogrep.CapturedNode, len(m.CaptureList())) copy(values, m.CaptureList())