Skip to content

Commit 9746dc3

Browse files
authored
Rule: single-item-in (#1546)
Fixes #1516 Signed-off-by: Anders Eknert <anders@styra.com>
1 parent cb21bd4 commit 9746dc3

5 files changed

Lines changed: 162 additions & 0 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,7 @@ The following rules are currently available:
245245
| idiomatic | [no-defined-entrypoint](https://docs.styra.com/regal/rules/idiomatic/no-defined-entrypoint) | Missing entrypoint annotation |
246246
| idiomatic | [non-raw-regex-pattern](https://docs.styra.com/regal/rules/idiomatic/non-raw-regex-pattern) | Use raw strings for regex patterns |
247247
| idiomatic | [prefer-set-or-object-rule](https://docs.styra.com/regal/rules/idiomatic/prefer-set-or-object-rule) | Prefer set or object rule over comprehension |
248+
| idiomatic | [single-item-in](https://docs.styra.com/regal/rules/idiomatic/single-item-in) | Avoid `in` for single item collection |
248249
| idiomatic | [use-contains](https://docs.styra.com/regal/rules/idiomatic/use-contains) | Use the `contains` keyword |
249250
| idiomatic | [use-if](https://docs.styra.com/regal/rules/idiomatic/use-if) | Use the `if` keyword |
250251
| idiomatic | [use-in-operator](https://docs.styra.com/regal/rules/idiomatic/use-in-operator) | Use in to check for membership |

bundle/regal/config/provided/data.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,8 @@ rules:
9393
level: error
9494
prefer-set-or-object-rule:
9595
level: error
96+
single-item-in:
97+
level: error
9698
use-contains:
9799
level: error
98100
use-if:
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# METADATA
2+
# description: Avoid `in` for single item collection
3+
package regal.rules.idiomatic["single-item-in"]
4+
5+
import data.regal.ast
6+
import data.regal.result
7+
8+
report contains violation if {
9+
call := ast.found.calls[_][_]
10+
11+
call[0].value[0].value == "internal"
12+
call[0].value[1].value == "member_2"
13+
14+
call[2].type in {"array", "set", "object"}
15+
count(call[2].value) == 1
16+
17+
violation := result.fail(rego.metadata.chain(), result.location(call))
18+
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package regal.rules.idiomatic["single-item-in_test"]
2+
3+
import data.regal.ast
4+
import data.regal.config
5+
6+
import data.regal.rules.idiomatic["single-item-in"] as rule
7+
8+
test_fail_single_item_in_array if {
9+
r := rule.report with input as ast.policy("fail if 1 in [1]")
10+
11+
r == {{
12+
"category": "idiomatic",
13+
"description": "Avoid `in` for single item collection",
14+
"level": "error",
15+
"location": {
16+
"col": 11,
17+
"end": {
18+
"col": 13,
19+
"row": 3,
20+
},
21+
"file": "policy.rego",
22+
"row": 3,
23+
"text": "fail if 1 in [1]",
24+
},
25+
"related_resources": [{
26+
"description": "documentation",
27+
"ref": config.docs.resolve_url("$baseUrl/$category/single-item-in", "idiomatic"),
28+
}],
29+
"title": "single-item-in",
30+
}}
31+
}
32+
33+
test_fail_single_item_in_set if {
34+
r := rule.report with input as ast.policy("fail if 1 in {1}")
35+
36+
r == {{
37+
"category": "idiomatic",
38+
"description": "Avoid `in` for single item collection",
39+
"level": "error",
40+
"location": {
41+
"col": 11,
42+
"end": {
43+
"col": 13,
44+
"row": 3,
45+
},
46+
"file": "policy.rego",
47+
"row": 3,
48+
"text": "fail if 1 in {1}",
49+
},
50+
"related_resources": [{
51+
"description": "documentation",
52+
"ref": config.docs.resolve_url("$baseUrl/$category/single-item-in", "idiomatic"),
53+
}],
54+
"title": "single-item-in",
55+
}}
56+
}
57+
58+
test_fail_single_item_in_object if {
59+
r := rule.report with input as ast.policy(`fail if 1 in {"x": 1}`)
60+
61+
r == {{
62+
"category": "idiomatic",
63+
"description": "Avoid `in` for single item collection",
64+
"level": "error",
65+
"location": {
66+
"col": 11,
67+
"end": {
68+
"col": 13,
69+
"row": 3,
70+
},
71+
"file": "policy.rego",
72+
"row": 3,
73+
"text": `fail if 1 in {"x": 1}`,
74+
},
75+
"related_resources": [{
76+
"description": "documentation",
77+
"ref": config.docs.resolve_url("$baseUrl/$category/single-item-in", "idiomatic"),
78+
}],
79+
"title": "single-item-in",
80+
}}
81+
}
82+
83+
test_success_in_used_on_var if {
84+
r := rule.report with input as ast.policy(`success if 1 in var`)
85+
86+
r == set()
87+
}
88+
89+
test_success_some_in_used_on_var if {
90+
r := rule.report with input as ast.policy(`success if [y | some y in x]`)
91+
92+
r == set()
93+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# single-item-in
2+
3+
**Summary**: Avoid `in` for single item collection
4+
5+
**Category**: Idiomatic
6+
7+
**Avoid**
8+
```rego
9+
package policy
10+
11+
allow if input.role in {"admin"}
12+
```
13+
14+
**Prefer**
15+
```rego
16+
package policy
17+
18+
allow if input.role == "admin"
19+
```
20+
21+
## Rationale
22+
23+
Using `in` on a single-item collection (array, set or object) is a convoluted way of checking for equality. Better
24+
then to check for equality directly! Besides being more obvious, equality checks are also subject to rule indexing,
25+
whereas `in` checks currently aren't.
26+
27+
## Configuration Options
28+
29+
This linter rule provides the following configuration options:
30+
31+
```yaml
32+
rules:
33+
idiomatic:
34+
single-item-in:
35+
# one of "error", "warning", "ignore"
36+
level: error
37+
```
38+
39+
## Related Resources
40+
41+
- OPA Docs: [Use indexed statements](https://www.openpolicyagent.org/docs/latest/policy-performance/#use-indexed-statements)
42+
- GitHub: [Source Code](https://github.com/StyraInc/regal/blob/main/bundle/regal/rules/idiomatic/single-item-in/single_item_in.rego)
43+
44+
## Community
45+
46+
If you think you've found a problem with this rule or its documentation, would like to suggest improvements, new rules,
47+
or just talk about Regal in general, please join us in the `#regal` channel in the Styra Community
48+
[Slack](https://inviter.co/styra)!

0 commit comments

Comments
 (0)