Skip to content

Commit fd85e2d

Browse files
authored
Allow naming-convention rule to take list of names (not only a pattern) (#1911)
While the title of this PR is what we'll want in the release notes, this is 95% an internal change meant to improve Regal for ourselves. That a custom rule got better is mostly a happy coincidence :) Since we started working on Regal, we have learnt a lot about both OPA, the AST and of course our own domains — static analysis, linting and language servers. The terminology used in the project has grown organically along with that, and with time we've found a shared vocabulary for the project. As a mature project, there's however been no lack of code written before we knew better, and as this PR highlights there have been many places where names have been picked rather arbitrarly. Additionally, the lack of a documented vocabulary meant it wasn't accessible outside of our own internal know-how. This change aims to fix that by: 1. Documenting the terminlogy and vocabulary of Regal and our domain 2. Provide the means to help check how well we follow our stated naming conventions 3. Update our Rego code to comply with these naming conventions While having this goes a long way to unify our terminology, there are things that won't be caught by a simple "allow list" like this. As an example, our use of the name `ref` sometimes mean the "outer" type (the ref term), and sometimes it refers to only the the term slice of the ref (which should rather be called `terms`). That's out of scope for this PR, but could be something to look into later. As noted in the change, we will not enforce anything related to naming conventions, but occasionally check this and make fixes accordingly (and without ever getting in the way of anyone contributing to the project). Fixes #1493 Signed-off-by: Anders Eknert <anders.eknert@apple.com>
1 parent d661b5a commit fd85e2d

57 files changed

Lines changed: 670 additions & 405 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.regal/config.yaml

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,101 @@ rules:
1616
ignore:
1717
files:
1818
- "*_test.rego"
19+
naming-convention:
20+
# Regal naming conventions configuration. Note that this is disabled by default, and
21+
# is meant only to be run every once in a while to ensure we stick to our conventions.
22+
# These conventions are not static, and the correct "fix" to a naming violation may
23+
# very well be to update this configuration rather than the code.
24+
#
25+
# To check compliance:
26+
# regal lint --enable naming-convention bundle
27+
#
28+
# See naming.md for rationale and examples of these conventions.
29+
# NOTE: that this check is not automated, but intended to be used occasionally.
30+
level: ignore
31+
conventions:
32+
- pattern: '\b[a-z_]{5,}\b'
33+
names:
34+
# indices
35+
- i
36+
- j
37+
- k
38+
# count / length
39+
- n
40+
# rego types
41+
- arr
42+
- obj
43+
- set
44+
- str
45+
- num
46+
# imaginary 'types'
47+
- coll
48+
- seq
49+
- item
50+
# ast types
51+
- pkg
52+
- imp
53+
- ref
54+
- refs
55+
- var
56+
- vars
57+
- fun
58+
- arg
59+
- args
60+
- term
61+
- terms
62+
- expr
63+
- exprs
64+
- rule
65+
- head
66+
- body
67+
- call
68+
- comp
69+
- node
70+
# ast attributes and related
71+
- key
72+
- keys
73+
- val
74+
- vals
75+
- type
76+
# common
77+
- file
78+
- text
79+
- line
80+
- rows
81+
- cols
82+
- loc
83+
- end
84+
- col
85+
- row
86+
- agg
87+
- aggs
88+
- cfg
89+
- word
90+
- start
91+
- link
92+
- len
93+
- kind
94+
- rest
95+
- name
96+
- pos
97+
- sub
98+
- sup
99+
- last
100+
- next
101+
- lhs
102+
- rhs
103+
- path
104+
- url
105+
- uri
106+
- other
107+
- diff
108+
- dir
109+
targets:
110+
- var
111+
ignore:
112+
files:
113+
- "*_test.rego"
19114
narrow-argument:
20115
level: error
21116
exclude-args:

bundle/regal/ast/ast.rego

Lines changed: 34 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -102,9 +102,9 @@ rules := [rule |
102102
# description: all the test rules in the input AST
103103
tests := [rule |
104104
some rule in rules
105+
some term in rule.head.ref
105106

106-
some part in rule.head.ref
107-
startswith(part.value, "test_")
107+
startswith(term.value, "test_")
108108
]
109109

110110
# METADATA
@@ -212,59 +212,59 @@ function_calls[rule_index] contains call if {
212212

213213
# METADATA
214214
# description: |
215-
# true if both ref values (or "paths") are equal in type
215+
# true if both ref values (terms) are equal in type
216216
# and value for each path component, ignoring locations
217-
ref_value_equal(v1, v2) if {
218-
count(v1) == count(v2)
217+
ref_value_equal(terms, other) if {
218+
count(terms) == count(other)
219219

220-
every i, term in v1 {
221-
term.type == v2[i].type
222-
term.value == v2[i].value
220+
every i, term in terms {
221+
term.type == other[i].type
222+
term.value == other[i].value
223223
}
224224
}
225225

226226
# METADATA
227227
# description: |
228-
# returns a new ref value made by extending terms1 with terms2,
229-
# where the first term of terms2 transformed to string
230-
extend_ref_terms(terms1, terms2) := array.flatten([
231-
terms1,
232-
object.union(terms2[0], {"type": "string"}),
233-
array.slice(terms2, 1, 100),
228+
# returns a new terms array made by extending terms with other,
229+
# where the first term of other transformed to string
230+
extend_ref_terms(terms, other) := array.flatten([
231+
terms,
232+
object.union(other[0], {"type": "string"}),
233+
array.slice(other, 1, 100),
234234
])
235235

236236
# METADATA
237237
# description: |
238-
# true if all terms in `terms1` are also present in `terms2`
238+
# true if all terms in `terms` are also present in `other`
239239
# regardless of length, and ignoring locations
240-
is_terms_subset(terms1, terms2) if {
241-
count(terms1) <= count(terms2)
240+
is_terms_subset(terms, other) if {
241+
count(terms) <= count(other)
242242

243-
every i, term in terms1 {
244-
term.type == terms2[i].type
245-
term.value == terms2[i].value
243+
every i, term in terms {
244+
term.type == other[i].type
245+
term.value == other[i].value
246246
}
247247
}
248248

249249
# METADATA
250250
# description: returns the "path" string of any given ref value
251-
ref_to_string(ref) := concat("", array.flatten([ref[0].value, [_format_part(part) |
252-
some part in array.slice(ref, 1, 100)
251+
ref_to_string(ref) := concat("", array.flatten([ref[0].value, [_format_term(term) |
252+
some term in array.slice(ref, 1, 100)
253253

254-
not part.type in {"call", "ref", "templatestring"}
254+
not term.type in {"call", "ref", "templatestring"}
255255
]]))
256256

257-
_format_part(part) := concat("", [".", part.value]) if {
258-
part.type == "string"
259-
regex.match(`^[a-zA-Z_][a-zA-Z1-9_]*$`, part.value)
257+
_format_term(term) := concat("", [".", term.value]) if {
258+
term.type == "string"
259+
regex.match(`^[a-zA-Z_][a-zA-Z1-9_]*$`, term.value)
260260
} else := sprintf(
261261
{
262262
"string": `["%v"]`,
263263
"var": `[%v]`,
264264
"number": `[%d]`,
265265
"boolean": `[%v]`,
266-
}[part.type],
267-
[part.value],
266+
}[term.type],
267+
[term.value],
268268
)
269269

270270
# METADATA
@@ -275,9 +275,9 @@ _format_part(part) := concat("", [".", part.value]) if {
275275
# foo.bar[baz] -> foo.bar
276276
ref_static_to_string(ref) := ref_to_string(array.slice(ref, 0, first_non_static)) if {
277277
first_non_static := [i |
278-
some i, part in ref
278+
some i, term in ref
279279
i > 0
280-
part.type in {"call", "var", "ref", "templatestring"}
280+
term.type in {"call", "var", "ref", "templatestring"}
281281
][0]
282282
} else := ref_to_string(ref)
283283

@@ -369,11 +369,11 @@ is_chained_rule_body(rule, lines) if {
369369
# description: answers whether variable of `name` is found anywhere in provided rule `head`
370370
# scope: document
371371
var_in_head(head, name) if {
372-
some part in ["key", "value"]
373-
has_named_var(head[part], name)
372+
some type in ["key", "value"]
373+
has_named_var(head[type], name)
374374
} else if {
375-
some part in array.slice(head.ref, 1, 100)
376-
has_named_var(part, name)
375+
some term in array.slice(head.ref, 1, 100)
376+
has_named_var(term, name)
377377
}
378378

379379
# METADATA

bundle/regal/ast/comments.rego

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -80,23 +80,23 @@ comment_blocks(comments_decoded) := blocks if {
8080
some comment in row_partition
8181

8282
col := comment.location.col # regal ignore:comprehension-term-assignment
83-
partition := [c |
84-
some c in row_partition
85-
c.location.col == col
83+
partition := [partition_comment |
84+
some partition_comment in row_partition
85+
partition_comment.location.col == col
8686
]
8787
}
8888
]
8989
}
9090

91-
_splits(xs) := array.flatten([
91+
_splits(rows) := array.flatten([
9292
# -1 ++ [ all indices where there's a step larger than one ] ++ length of xs
9393
# the -1 is because we're adding +1 in array.slice
9494
-1,
9595
[i |
9696
some i in numbers.range(0, n - 1)
97-
xs[i + 1] != xs[i] + 1
97+
rows[i + 1] != rows[i] + 1
9898
],
9999
n,
100100
]) if {
101-
n := count(xs)
101+
n := count(rows)
102102
}

bundle/regal/ast/imports.rego

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,13 @@ resolved_imports[identifier] := paths[0] if {
4646
# returns true if provided path (like ["data", "foo", "bar"]) is in the
4747
# list of imports (which is commonly ast.imports)
4848
imports_has_path(imports, path) if {
49-
pv := imports[_].path.value
49+
n := count(path)
50+
terms := imports[_].path.value
5051

51-
count(pv) == count(path)
52+
count(terms) == n
5253

53-
every i, part in path {
54-
part == pv[i].value
54+
every i, term in path {
55+
term == terms[i].value
5556
}
5657
}
5758

@@ -62,11 +63,7 @@ imports_has_path(imports, path) if {
6263
imports_keyword(imports, keyword) if {
6364
capabilities.is_opa_v1
6465
input.regal.file.rego_version != "v0"
65-
} else if {
66-
pv := imports[_].path.value
67-
68-
_has_keyword([p.value | some p in pv], keyword)
69-
}
66+
} else if _has_keyword([term.value | term := imports[_].path.value[_]], keyword)
7067

7168
_imported_identifier(imp) := imp.alias
7269
_imported_identifier(imp) := regal.last(imp.path.value).value if not imp.alias

bundle/regal/ast/keywords.rego

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,19 @@ import data.regal.util
99
# METADATA
1010
# description: collects the `if` keyword. this isn't present in the AST, so we'll simply scan the input lines
1111
keywords[row] contains keyword if {
12-
some idx, line in input.regal.file.lines
12+
some i, line in input.regal.file.lines
1313

1414
col := indexof(line, " if ")
1515
col > 0
1616

17-
row := idx + 1
17+
row := i + 1
1818

1919
not row in _comment_row_index
2020

2121
keyword := {
2222
"name": "if",
2323
"location": {
24-
"row": idx + 1,
24+
"row": i + 1,
2525
"col": col + 2,
2626
},
2727
}

bundle/regal/ast/search.rego

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,8 @@ has_term_var(terms) if {
5656

5757
# converting to string until https://github.com/open-policy-agent/opa/issues/6736 is fixed
5858
_rule_index(rule) := rule_index_strings[i] if {
59-
some i, r in _rules
60-
r == rule
59+
some i
60+
rule == _rules[i]
6161
}
6262

6363
# hack to work around the different input models of linting vs. the lsp package.. we
@@ -119,9 +119,9 @@ found.vars[rule_index].ref contains term if {
119119
some rule_index, ref
120120
found.refs[rule_index][ref]
121121

122-
some x, term in ref.value
122+
some i, term in ref.value
123123

124-
x > 0
124+
i > 0
125125
term.type == "var"
126126
}
127127

@@ -165,9 +165,9 @@ found.vars[rule_index].somein contains var if {
165165

166166
arr := value[0].value
167167

168-
some var in array.flatten([_find_nested_vars(arr[1]), [v |
168+
some var in array.flatten([_find_nested_vars(arr[1]), [var |
169169
count(arr) == 4
170-
some v in _find_nested_vars(arr[2])
170+
some var in _find_nested_vars(arr[2])
171171
]])
172172
}
173173

bundle/regal/config/exclusion.rego

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,10 @@ excluded_file(category, title, file) if {
1616
# description: |
1717
# pattern_compiler transforms a glob pattern into a set of glob
1818
# patterns to make the combined set behave as .gitignore
19-
patterns_compiler(patterns) := {pat |
19+
patterns_compiler(patterns) := {compiled |
2020
some pattern in patterns
21-
some p in _leading_doublestar_pattern(_internal_slashes(pattern))
22-
some pat in _trailing_slash(p)
21+
some processed in _leading_doublestar_pattern(_internal_slashes(pattern))
22+
some compiled in _trailing_slash(processed)
2323
} if {
2424
patterns != []
2525
}

0 commit comments

Comments
 (0)