Skip to content

Commit 8aae5b1

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 8aae5b1

File tree

1 file changed

+86
-29
lines changed

1 file changed

+86
-29
lines changed

src/cfengine_cli/lint.py

Lines changed: 86 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -32,33 +32,53 @@ 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+
if self.attribute_name == "slist":
41+
self.attribute_name = None
42+
else:
43+
self.block_type = None
44+
self.promise_type = None
45+
return self
46+
if node.type == ";":
47+
self.attribute_name = None
48+
return self
3849
if node.type == "bundle_block":
39-
return _State(block_type="bundle")
50+
self.block_type = "bundle"
51+
return self
4052
if node.type == "body_block":
41-
return _State(block_type="body")
53+
self.block_type = "body"
54+
return self
4255
if node.type == "promise_block":
43-
return _State(block_type="promise")
56+
self.block_type = "promise"
57+
return self
4458
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)
59+
# A bundle_section is always: promise_guard, [promises], [class_guarded_promises...]
60+
# The promise_guard is guaranteed to exist by the grammar
61+
guard = next((c for c in node.children if c.type == "promise_guard"), None)
62+
if guard is None: # Should never happen
63+
print("ERROR: Bundle section without a promise guard")
64+
return self
65+
66+
self.promise_type = _text(guard)[:-1] # strip trailing ':'
67+
return self
5268
if node.type == "attribute":
5369
for child in node.children:
5470
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-
)
71+
self.attribute_name = _text(child)
72+
if self.attribute_name == "namespace":
73+
self.namespace = _text(child.next_named_sibling).strip("\"'")
74+
return self
6075
return self
6176

77+
@staticmethod
78+
def qualify(name: str, namespace: str) -> str:
79+
"""If name is already qualified (contains ':'), return as-is. Otherwise prepend namespace."""
80+
return name if ":" in name else f"{namespace}:{name}"
81+
6282

6383
def lint_cfbs_json(filename) -> int:
6484
assert os.path.isfile(filename)
@@ -184,7 +204,8 @@ def _node_checks(filename, lines, node, user_definition, strict, state: _State):
184204
if node.type == "calling_identifier":
185205
if (
186206
strict
187-
and _text(node) in user_definition.get("all_bundle_names", set())
207+
and state.qualify(_text(node), state.namespace)
208+
in user_definition.get("all_bundle_names", set())
188209
and state.promise_type in user_definition.get("custom_promise_types", set())
189210
):
190211
_highlight_range(node, lines)
@@ -193,11 +214,12 @@ def _node_checks(filename, lines, node, user_definition, strict, state: _State):
193214
)
194215
return 1
195216
if strict and (
196-
_text(node)
197-
not in BUILTIN_FUNCTIONS.union(
217+
state.qualify(_text(node), state.namespace)
218+
not in set.union(
198219
user_definition.get("all_bundle_names", set()),
199220
user_definition.get("all_body_names", set()),
200221
)
222+
and _text(node) not in BUILTIN_FUNCTIONS
201223
):
202224
_highlight_range(node, lines)
203225
print(
@@ -215,11 +237,9 @@ def _stateful_walk(
215237

216238
errors = _node_checks(filename, lines, node, user_definition, strict, state)
217239

218-
child_state = state.update(node)
240+
state.update(node)
219241
for child in node.children:
220-
errors += _stateful_walk(
221-
filename, lines, child, user_definition, strict, child_state
222-
)
242+
errors += _stateful_walk(filename, lines, child, user_definition, strict, state)
223243
return errors
224244

225245

@@ -239,18 +259,55 @@ def _walk(filename, lines, node, user_definition=None, strict=True) -> int:
239259
line = node.range.start_point[0] + 1
240260
column = node.range.start_point[1] + 1
241261

242-
return _stateful_walk(filename, lines, node, user_definition, strict)
262+
state = _State()
263+
ret = _stateful_walk(filename, lines, node, user_definition, strict, state=state)
264+
state = _State() # Clear state
265+
return ret
243266

244267

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

250307
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},
308+
"custom_promise_types": promise_blocks,
309+
"all_bundle_names": bundle_blocks,
310+
"all_body_names": body_blocks,
254311
}
255312

256313

0 commit comments

Comments
 (0)