Skip to content

Commit a6c3ce3

Browse files
ENT-13841: Fixed linting not working with namespaced bunde/body
Ticket: ENT-13841 Signed-off-by: Simon Halvorsen <simon.halvorsen@northern.tech>
1 parent 6b51e49 commit a6c3ce3

1 file changed

Lines changed: 92 additions & 29 deletions

File tree

src/cfengine_cli/lint.py

Lines changed: 92 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -32,33 +32,58 @@ class _State:
3232
block_type: str | None = None # "bundle" | "body" | "promise" | None
3333
promise_type: str | None = None # "vars" | "files" | "classes" | ... | None
3434
attribute_name: str | None = None # "if" | "string" | "slist" | ... | None
35+
namespace: str = "default" # "ns" | "default" | ... |
3536

3637
def update(self, node) -> "_State":
3738
"""Updates and returns the state that should apply to the children of `node`."""
39+
if node.type == "}":
40+
assert node.parent
41+
assert self.attribute_name == "list" or node.parent.type in [
42+
"bundle_block_body",
43+
"promise_block_body",
44+
"body_block_body",
45+
]
46+
if self.attribute_name != "list":
47+
self.block_type = None
48+
self.promise_type = None
49+
self.attribute_name = None
50+
return self
51+
if node.type == ";":
52+
self.attribute_name = None
53+
return self
3854
if node.type == "bundle_block":
39-
return _State(block_type="bundle")
55+
self.block_type = "bundle"
56+
return self
4057
if node.type == "body_block":
41-
return _State(block_type="body")
58+
self.block_type = "body"
59+
return self
4260
if node.type == "promise_block":
43-
return _State(block_type="promise")
61+
self.block_type = "promise"
62+
return self
4463
if node.type == "bundle_section":
45-
for child in node.children:
46-
if child.type == "promise_guard":
47-
return _State(
48-
block_type=self.block_type,
49-
promise_type=_text(child)[:-1], # strip trailing ':'
50-
)
51-
return _State(block_type=self.block_type)
64+
# A bundle_section is always: promise_guard, [promises], [class_guarded_promises...]
65+
# The promise_guard is guaranteed to exist by the grammar
66+
guard = next((c for c in node.children if c.type == "promise_guard"), None)
67+
if guard is None: # Should never happen
68+
print("ERROR: Bundle section without a promise guard")
69+
return self
70+
71+
self.promise_type = _text(guard)[:-1] # strip trailing ':'
72+
return self
5273
if node.type == "attribute":
5374
for child in node.children:
5475
if child.type == "attribute_name":
55-
return _State(
56-
block_type=self.block_type,
57-
promise_type=self.promise_type,
58-
attribute_name=_text(child),
59-
)
76+
self.attribute_name = _text(child)
77+
if self.attribute_name == "namespace":
78+
self.namespace = _text(child.next_named_sibling).strip("\"'")
79+
return self
6080
return self
6181

82+
@staticmethod
83+
def qualify(name: str, namespace: str) -> str:
84+
"""If name is already qualified (contains ':'), return as-is. Otherwise prepend namespace."""
85+
return name if ":" in name else f"{namespace}:{name}"
86+
6287

6388
def lint_cfbs_json(filename) -> int:
6489
assert os.path.isfile(filename)
@@ -184,7 +209,8 @@ def _node_checks(filename, lines, node, user_definition, strict, state: _State):
184209
if node.type == "calling_identifier":
185210
if (
186211
strict
187-
and _text(node) in user_definition.get("all_bundle_names", set())
212+
and state.qualify(_text(node), state.namespace)
213+
in user_definition.get("all_bundle_names", set())
188214
and state.promise_type in user_definition.get("custom_promise_types", set())
189215
):
190216
_highlight_range(node, lines)
@@ -193,11 +219,12 @@ def _node_checks(filename, lines, node, user_definition, strict, state: _State):
193219
)
194220
return 1
195221
if strict and (
196-
_text(node)
197-
not in BUILTIN_FUNCTIONS.union(
222+
state.qualify(_text(node), state.namespace)
223+
not in set.union(
198224
user_definition.get("all_bundle_names", set()),
199225
user_definition.get("all_body_names", set()),
200226
)
227+
and _text(node) not in BUILTIN_FUNCTIONS
201228
):
202229
_highlight_range(node, lines)
203230
print(
@@ -215,11 +242,9 @@ def _stateful_walk(
215242

216243
errors = _node_checks(filename, lines, node, user_definition, strict, state)
217244

218-
child_state = state.update(node)
245+
state.update(node)
219246
for child in node.children:
220-
errors += _stateful_walk(
221-
filename, lines, child, user_definition, strict, child_state
222-
)
247+
errors += _stateful_walk(filename, lines, child, user_definition, strict, state)
223248
return errors
224249

225250

@@ -239,18 +264,55 @@ def _walk(filename, lines, node, user_definition=None, strict=True) -> int:
239264
line = node.range.start_point[0] + 1
240265
column = node.range.start_point[1] + 1
241266

242-
return _stateful_walk(filename, lines, node, user_definition, strict)
267+
state = _State()
268+
ret = _stateful_walk(filename, lines, node, user_definition, strict, state=state)
269+
state = _State() # Clear state
270+
return ret
243271

244272

245273
def _parse_user_definition(filename, lines, root_node):
246-
promise_blocks = _find_node_type(filename, lines, root_node, "promise_block_name")
247-
bundle_blocks = _find_node_type(filename, lines, root_node, "bundle_block_name")
248-
body_blocks = _find_node_type(filename, lines, root_node, "body_block_name")
274+
ns = "default"
275+
promise_blocks = set()
276+
bundle_blocks = set()
277+
body_blocks = set()
278+
279+
for child in root_node.children:
280+
if child.type == "body_block":
281+
name_node = next(
282+
(c for c in child.named_children if c.type == "body_block_name"),
283+
None,
284+
)
285+
ns_attr = next(
286+
(
287+
c
288+
for c in _find_node_type(filename, lines, child, "attribute_name")
289+
if _text(c) == "namespace"
290+
),
291+
None,
292+
)
293+
if ns_attr is not None:
294+
ns = _text(ns_attr.next_named_sibling).strip("\"'")
295+
elif name_node is not None:
296+
body_blocks.add(_State.qualify(_text(name_node), ns))
297+
elif child.type == "bundle_block":
298+
name_node = next(
299+
(c for c in child.named_children if c.type == "bundle_block_name"),
300+
None,
301+
)
302+
if name_node is not None:
303+
bundle_blocks.add(_State.qualify(_text(name_node), ns))
304+
elif child.type == "promise_block":
305+
name_node = next(
306+
(c for c in child.named_children if c.type == "promise_block_name"),
307+
None,
308+
)
309+
if name_node is not None:
310+
promise_blocks.add(_text(name_node))
249311

250312
return {
251-
"custom_promise_types": {_text(x) for x in promise_blocks},
252-
"all_bundle_names": {_text(x) for x in bundle_blocks},
253-
"all_body_names": {_text(x) for x in body_blocks},
313+
"custom_promise_types": promise_blocks,
314+
"all_bundle_names": bundle_blocks,
315+
"all_body_names": body_blocks,
254316
}
255317

256318

@@ -323,6 +385,7 @@ def lint_policy_file(
323385
else:
324386
print(f"Error: Empty policy file '{filename}'")
325387
errors += 1
388+
print(filename)
326389
errors += _walk(filename, lines, root_node, user_definition, strict)
327390
if prefix:
328391
print(prefix, end="")

0 commit comments

Comments
 (0)