Skip to content

Commit 3525054

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 3525054

File tree

1 file changed

+95
-31
lines changed

1 file changed

+95
-31
lines changed

src/cfengine_cli/lint.py

Lines changed: 95 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -32,32 +32,59 @@ 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

36-
def update(self, node) -> "_State":
37+
def update(self, node):
3738
"""Updates and returns the state that should apply to the children of `node`."""
39+
if node.type == "}":
40+
assert node.parent
41+
assert node.parent.type in [
42+
"bundle_block_body",
43+
"promise_block_body",
44+
"body_block_body",
45+
"list",
46+
]
47+
if node.parent.type != "list":
48+
# We just ended a block
49+
self.block_type = None
50+
self.promise_type = None
51+
self.attribute_name = None
52+
return
53+
if node.type == ";":
54+
self.attribute_name = None
55+
return
3856
if node.type == "bundle_block":
39-
return _State(block_type="bundle")
57+
self.block_type = "bundle"
58+
return
4059
if node.type == "body_block":
41-
return _State(block_type="body")
60+
self.block_type = "body"
61+
return
4262
if node.type == "promise_block":
43-
return _State(block_type="promise")
63+
self.block_type = "promise"
64+
return
4465
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)
66+
# A bundle_section is always: promise_guard, [promises], [class_guarded_promises...]
67+
# The promise_guard is guaranteed to exist by the grammar
68+
guard = next((c for c in node.children if c.type == "promise_guard"), None)
69+
if guard is None: # Should never happen
70+
print("ERROR: Bundle section without a promise guard")
71+
return
72+
73+
self.promise_type = _text(guard)[:-1] # strip trailing ':'
74+
return
5275
if node.type == "attribute":
5376
for child in node.children:
5477
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-
)
60-
return self
78+
self.attribute_name = _text(child)
79+
if self.attribute_name == "namespace":
80+
self.namespace = _text(child.next_named_sibling).strip("\"'")
81+
return
82+
return
83+
84+
@staticmethod
85+
def qualify(name: str, namespace: str) -> str:
86+
"""If name is already qualified (contains ':'), return as-is. Otherwise prepend namespace."""
87+
return name if ":" in name else f"{namespace}:{name}"
6188

6289

6390
def lint_cfbs_json(filename) -> int:
@@ -184,7 +211,8 @@ def _node_checks(filename, lines, node, user_definition, strict, state: _State):
184211
if node.type == "calling_identifier":
185212
if (
186213
strict
187-
and _text(node) in user_definition.get("all_bundle_names", set())
214+
and state.qualify(_text(node), state.namespace)
215+
in user_definition.get("all_bundle_names", set())
188216
and state.promise_type in user_definition.get("custom_promise_types", set())
189217
):
190218
_highlight_range(node, lines)
@@ -193,11 +221,12 @@ def _node_checks(filename, lines, node, user_definition, strict, state: _State):
193221
)
194222
return 1
195223
if strict and (
196-
_text(node)
197-
not in BUILTIN_FUNCTIONS.union(
224+
state.qualify(_text(node), state.namespace)
225+
not in set.union(
198226
user_definition.get("all_bundle_names", set()),
199227
user_definition.get("all_body_names", set()),
200228
)
229+
and _text(node) not in BUILTIN_FUNCTIONS
201230
):
202231
_highlight_range(node, lines)
203232
print(
@@ -215,11 +244,9 @@ def _stateful_walk(
215244

216245
errors = _node_checks(filename, lines, node, user_definition, strict, state)
217246

218-
child_state = state.update(node)
247+
state.update(node)
219248
for child in node.children:
220-
errors += _stateful_walk(
221-
filename, lines, child, user_definition, strict, child_state
222-
)
249+
errors += _stateful_walk(filename, lines, child, user_definition, strict, state)
223250
return errors
224251

225252

@@ -239,18 +266,55 @@ def _walk(filename, lines, node, user_definition=None, strict=True) -> int:
239266
line = node.range.start_point[0] + 1
240267
column = node.range.start_point[1] + 1
241268

242-
return _stateful_walk(filename, lines, node, user_definition, strict)
269+
state = _State()
270+
ret = _stateful_walk(filename, lines, node, user_definition, strict, state=state)
271+
state = _State() # Clear state
272+
return ret
243273

244274

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

250314
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},
315+
"custom_promise_types": promise_blocks,
316+
"all_bundle_names": bundle_blocks,
317+
"all_body_names": body_blocks,
254318
}
255319

256320

0 commit comments

Comments
 (0)