Skip to content

Commit b91260c

Browse files
authored
Merge pull request #86 from mxlint/feature/rules-validation
Check for rulenumber duplication because noqa works with rulenumbers
2 parents 6b5b157 + 21dd430 commit b91260c

File tree

2 files changed

+183
-0
lines changed

2 files changed

+183
-0
lines changed

lint/rules.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,30 @@ func TestAll(rulesPath string) error {
1919
return err
2020
}
2121

22+
// Check for duplicate rule numbers
23+
ruleNumberMap := make(map[string][]string)
24+
for _, rule := range allRules {
25+
if rule.RuleNumber != "" {
26+
ruleNumberMap[rule.RuleNumber] = append(ruleNumberMap[rule.RuleNumber], rule.Path)
27+
}
28+
}
29+
30+
// Report duplicates
31+
hasDuplicates := false
32+
for ruleNumber, paths := range ruleNumberMap {
33+
if len(paths) > 1 {
34+
hasDuplicates = true
35+
log.Errorf("Duplicate rule number '%s' found in the following rules:", ruleNumber)
36+
for _, path := range paths {
37+
log.Errorf(" - %s", path)
38+
}
39+
}
40+
}
41+
42+
if hasDuplicates {
43+
return fmt.Errorf("found duplicate rule numbers")
44+
}
45+
2246
for _, rule := range allRules {
2347
runTestCases(rule)
2448
}

lint/rules_test.go

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package lint
22

33
import (
4+
"os"
5+
"path/filepath"
46
"testing"
57
)
68

@@ -13,3 +15,160 @@ func TestAllRules(t *testing.T) {
1315
}
1416
})
1517
}
18+
19+
func TestDuplicateRuleNumbers(t *testing.T) {
20+
// Create a temporary directory for test rules
21+
tempDir := t.TempDir()
22+
23+
// Create two rules with the same rule number
24+
rule1Content := `# METADATA
25+
# scope: package
26+
# title: Test Rule 1
27+
# description: First test rule
28+
# custom:
29+
# category: Test
30+
# rulename: TestRule1
31+
# severity: LOW
32+
# rulenumber: 001_0001
33+
# remediation: Fix it
34+
# input: .*test\.yaml
35+
package app.test.rule1
36+
import rego.v1
37+
38+
default allow := false
39+
allow if count(errors) == 0
40+
errors contains "test error" if false
41+
`
42+
43+
rule2Content := `# METADATA
44+
# scope: package
45+
# title: Test Rule 2
46+
# description: Second test rule with same number
47+
# custom:
48+
# category: Test
49+
# rulename: TestRule2
50+
# severity: LOW
51+
# rulenumber: 001_0001
52+
# remediation: Fix it
53+
# input: .*test\.yaml
54+
package app.test.rule2
55+
import rego.v1
56+
57+
default allow := false
58+
allow if count(errors) == 0
59+
errors contains "test error" if false
60+
`
61+
62+
// Write the rule files
63+
rule1Path := filepath.Join(tempDir, "rule1.rego")
64+
rule2Path := filepath.Join(tempDir, "rule2.rego")
65+
66+
err := os.WriteFile(rule1Path, []byte(rule1Content), 0644)
67+
if err != nil {
68+
t.Fatalf("Failed to write rule1: %v", err)
69+
}
70+
71+
err = os.WriteFile(rule2Path, []byte(rule2Content), 0644)
72+
if err != nil {
73+
t.Fatalf("Failed to write rule2: %v", err)
74+
}
75+
76+
// Test that duplicate rule numbers are detected
77+
err = TestAll(tempDir)
78+
if err == nil {
79+
t.Error("Expected error for duplicate rule numbers, but got nil")
80+
}
81+
82+
if err != nil && err.Error() != "found duplicate rule numbers" {
83+
t.Errorf("Expected 'found duplicate rule numbers' error, got: %v", err)
84+
}
85+
}
86+
87+
func TestUniqueRuleNumbers(t *testing.T) {
88+
// Create a temporary directory for test rules
89+
tempDir := t.TempDir()
90+
91+
// Create two rules with different rule numbers
92+
rule1Content := `# METADATA
93+
# scope: package
94+
# title: Test Rule 1
95+
# description: First test rule
96+
# custom:
97+
# category: Test
98+
# rulename: TestRule1
99+
# severity: LOW
100+
# rulenumber: 001_0001
101+
# remediation: Fix it
102+
# input: .*test\.yaml
103+
package app.test.rule1
104+
import rego.v1
105+
106+
default allow := false
107+
allow if count(errors) == 0
108+
errors contains "test error" if false
109+
`
110+
111+
rule2Content := `# METADATA
112+
# scope: package
113+
# title: Test Rule 2
114+
# description: Second test rule with different number
115+
# custom:
116+
# category: Test
117+
# rulename: TestRule2
118+
# severity: LOW
119+
# rulenumber: 001_0002
120+
# remediation: Fix it
121+
# input: .*test\.yaml
122+
package app.test.rule2
123+
import rego.v1
124+
125+
default allow := false
126+
allow if count(errors) == 0
127+
errors contains "test error" if false
128+
`
129+
130+
// Write the rule files
131+
rule1Path := filepath.Join(tempDir, "rule1.rego")
132+
rule2Path := filepath.Join(tempDir, "rule2.rego")
133+
134+
// Create test files for the rules
135+
testFile1 := filepath.Join(tempDir, "rule1_test.yaml")
136+
testFile2 := filepath.Join(tempDir, "rule2_test.yaml")
137+
138+
testContent := `TestCases:
139+
- name: "test case"
140+
input:
141+
test: true
142+
allow: true
143+
`
144+
145+
err := os.WriteFile(rule1Path, []byte(rule1Content), 0644)
146+
if err != nil {
147+
t.Fatalf("Failed to write rule1: %v", err)
148+
}
149+
150+
err = os.WriteFile(rule2Path, []byte(rule2Content), 0644)
151+
if err != nil {
152+
t.Fatalf("Failed to write rule2: %v", err)
153+
}
154+
155+
err = os.WriteFile(testFile1, []byte(testContent), 0644)
156+
if err != nil {
157+
t.Fatalf("Failed to write test file 1: %v", err)
158+
}
159+
160+
err = os.WriteFile(testFile2, []byte(testContent), 0644)
161+
if err != nil {
162+
t.Fatalf("Failed to write test file 2: %v", err)
163+
}
164+
165+
// Test that unique rule numbers pass validation
166+
err = TestAll(tempDir)
167+
if err != nil {
168+
// We expect errors from running the actual tests, but not from duplicate rule numbers
169+
if err.Error() == "found duplicate rule numbers" {
170+
t.Errorf("Should not get duplicate rule numbers error with unique numbers: %v", err)
171+
}
172+
// Other errors from test execution are fine for this test
173+
}
174+
}

0 commit comments

Comments
 (0)