Skip to content

Commit 673e5cf

Browse files
authored
feat(policy-devel): enhance lint error output (#2385)
Signed-off-by: Sylwester Piskozub <[email protected]>
1 parent eed41ef commit 673e5cf

File tree

1 file changed

+65
-2
lines changed
  • app/cli/internal/policydevel

1 file changed

+65
-2
lines changed

app/cli/internal/policydevel/lint.go

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import (
3232
"github.com/open-policy-agent/opa/v1/format"
3333
"github.com/styrainc/regal/pkg/config"
3434
"github.com/styrainc/regal/pkg/linter"
35+
"github.com/styrainc/regal/pkg/report"
3536
"github.com/styrainc/regal/pkg/rules"
3637
"gopkg.in/yaml.v3"
3738
)
@@ -289,13 +290,39 @@ func (p *PolicyToLint) runRegalLinter(filePath, content string) {
289290
return
290291
}
291292

292-
// Add any violations to the policy errors
293+
// Parse the Rego AST to map line numbers to rule names
294+
regoRuleMap := p.buildRegoRuleMap(content)
295+
296+
// Add violations to the policy errors
293297
for _, v := range report.Violations {
294-
errorStr := strings.ReplaceAll(v.Description, "`opa fmt`", "`--format`")
298+
errorStr := p.formatViolationError(v, regoRuleMap)
295299
p.AddError(filePath, errorStr, v.Location.Row)
296300
}
297301
}
298302

303+
// Creates a formatted error message from a Regal violation
304+
// Follows format <file>:<line>: [<ruleName>] <errorMsg> - <docLinks>
305+
func (p *PolicyToLint) formatViolationError(v report.Violation, regoRuleMap map[int]string) string {
306+
// Extract resources
307+
resources := make([]string, 0, len(v.RelatedResources))
308+
for _, r := range v.RelatedResources {
309+
resources = append(resources, r.Reference)
310+
}
311+
resourceStr := strings.Join(resources, ", ")
312+
313+
// Try to identify which Rego rule contains this violation
314+
regoRuleName, exists := regoRuleMap[v.Location.Row]
315+
if !exists {
316+
regoRuleName = ""
317+
} else {
318+
regoRuleName = fmt.Sprintf("[%s]", regoRuleName)
319+
}
320+
321+
// Format the error message
322+
lintError := fmt.Sprintf("%s: %s - %s", regoRuleName, v.Description, resourceStr)
323+
return strings.ReplaceAll(lintError, "`opa fmt`", "`--format`")
324+
}
325+
299326
// Attempts to load configuration in this order:
300327
// 1. User-specified config
301328
// 2. Default config
@@ -340,3 +367,39 @@ func (p *PolicyToLint) loadDefaultConfig() (*config.Config, error) {
340367

341368
return &cfg, nil
342369
}
370+
371+
// Creates a mapping from line numbers to Rego rule names
372+
func (p *PolicyToLint) buildRegoRuleMap(regoSrc string) map[int]string {
373+
ruleMap := make(map[int]string)
374+
375+
// Parse the Rego source into AST
376+
module, err := opaAst.ParseModule("", regoSrc)
377+
if err != nil {
378+
// Return empty map if parsing fails
379+
return ruleMap
380+
}
381+
382+
// Walk through the AST to find rule definitions
383+
for _, rule := range module.Rules {
384+
if rule.Location != nil {
385+
ruleName := string(rule.Head.Name)
386+
startLine := rule.Location.Row
387+
endLine := startLine
388+
389+
// Try to find the end line of the rule
390+
if len(rule.Body) > 0 {
391+
lastExpr := rule.Body[len(rule.Body)-1]
392+
if lastExpr.Location != nil {
393+
endLine = lastExpr.Location.Row
394+
}
395+
}
396+
397+
// Map all lines within this rule to the rule name
398+
for line := startLine; line <= endLine; line++ {
399+
ruleMap[line] = ruleName
400+
}
401+
}
402+
}
403+
404+
return ruleMap
405+
}

0 commit comments

Comments
 (0)