Skip to content

Commit dbad057

Browse files
authored
Merge pull request #33 from SimonThalvorsen/ENT-13074
ENT-13074: Added linter errors for unknown calls (bundle/body/func)
2 parents c6b0123 + 7905b8b commit dbad057

File tree

3 files changed

+291
-62
lines changed

3 files changed

+291
-62
lines changed

src/cfengine_cli/lint.py

Lines changed: 51 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -18,41 +18,12 @@
1818
from cfbs.validate import validate_config
1919
from cfbs.cfbs_config import CFBSConfig
2020
from cfbs.utils import find
21-
22-
DEPRECATED_PROMISE_TYPES = ["defaults", "guest_environments"]
23-
ALLOWED_BUNDLE_TYPES = ["agent", "common", "monitor", "server", "edit_line", "edit_xml"]
24-
BUILTIN_PROMISE_TYPES = {
25-
"access",
26-
"build_xpath",
27-
"classes",
28-
"commands",
29-
"databases",
30-
"defaults",
31-
"delete_attribute",
32-
"delete_lines",
33-
"delete_text",
34-
"delete_tree",
35-
"field_edits",
36-
"files",
37-
"guest_environments",
38-
"insert_lines",
39-
"insert_text",
40-
"insert_tree",
41-
"measurements",
42-
"meta",
43-
"methods",
44-
"packages",
45-
"processes",
46-
"replace_patterns",
47-
"reports",
48-
"roles",
49-
"services",
50-
"set_attribute",
51-
"set_text",
52-
"storage",
53-
"users",
54-
"vars",
55-
}
21+
from cfengine_cli.policy_language import (
22+
DEPRECATED_PROMISE_TYPES,
23+
ALLOWED_BUNDLE_TYPES,
24+
BUILTIN_PROMISE_TYPES,
25+
BUILTIN_FUNCTIONS,
26+
)
5627

5728

5829
def lint_cfbs_json(filename) -> int:
@@ -129,7 +100,7 @@ def _find_nodes(filename, lines, node):
129100
return matches
130101

131102

132-
def _single_node_checks(filename, lines, node, custom_promise_types, strict):
103+
def _single_node_checks(filename, lines, node, user_definition, strict):
133104
"""Things which can be checked by only looking at one node,
134105
not needing to recurse into children."""
135106
line = node.range.start_point[0] + 1
@@ -150,7 +121,12 @@ def _single_node_checks(filename, lines, node, custom_promise_types, strict):
150121
)
151122
return 1
152123
if strict and (
153-
(promise_type not in BUILTIN_PROMISE_TYPES.union(custom_promise_types))
124+
(
125+
promise_type
126+
not in BUILTIN_PROMISE_TYPES.union(
127+
user_definition.get("custom_promise_types", set())
128+
)
129+
)
154130
):
155131
_highlight_range(node, lines)
156132
print(
@@ -179,13 +155,25 @@ def _single_node_checks(filename, lines, node, custom_promise_types, strict):
179155
f"Error: Bundle type must be one of ({', '.join(ALLOWED_BUNDLE_TYPES)}), not '{_text(node)}' at {filename}:{line}:{column}"
180156
)
181157
return 1
182-
158+
if node.type == "calling_identifier":
159+
if strict and (
160+
_text(node)
161+
not in BUILTIN_FUNCTIONS.union(
162+
user_definition.get("all_bundle_names", set()),
163+
user_definition.get("all_body_names", set()),
164+
)
165+
):
166+
_highlight_range(node, lines)
167+
print(
168+
f"Error: Call to unknown function / bundle / body '{_text(node)}' at at {filename}:{line}:{column}"
169+
)
170+
return 1
183171
return 0
184172

185173

186-
def _walk(filename, lines, node, custom_promise_types=None, strict=True) -> int:
187-
if custom_promise_types is None:
188-
custom_promise_types = set()
174+
def _walk(filename, lines, node, user_definition=None, strict=True) -> int:
175+
if user_definition is None:
176+
user_definition = {}
189177

190178
error_nodes = _find_node_type(filename, lines, node, "ERROR")
191179
if error_nodes:
@@ -201,18 +189,21 @@ def _walk(filename, lines, node, custom_promise_types=None, strict=True) -> int:
201189

202190
errors = 0
203191
for node in _find_nodes(filename, lines, node):
204-
errors += _single_node_checks(
205-
filename, lines, node, custom_promise_types, strict
206-
)
192+
errors += _single_node_checks(filename, lines, node, user_definition, strict)
207193

208194
return errors
209195

210196

211-
def _parse_custom_types(filename, lines, root_node):
212-
ret = set()
197+
def _parse_user_definition(filename, lines, root_node):
213198
promise_blocks = _find_node_type(filename, lines, root_node, "promise_block_name")
214-
ret.update(_text(x) for x in promise_blocks)
215-
return ret
199+
bundle_blocks = _find_node_type(filename, lines, root_node, "bundle_block_name")
200+
body_blocks = _find_node_type(filename, lines, root_node, "body_block_name")
201+
202+
return {
203+
"custom_promise_types": {_text(x) for x in promise_blocks},
204+
"all_bundle_names": {_text(x) for x in bundle_blocks},
205+
"all_body_names": {_text(x) for x in body_blocks},
206+
}
216207

217208

218209
def _parse_policy_file(filename):
@@ -234,7 +225,7 @@ def lint_policy_file(
234225
original_line=None,
235226
snippet=None,
236227
prefix=None,
237-
custom_promise_types=None,
228+
user_definition=None,
238229
strict=True,
239230
):
240231
assert original_filename is None or type(original_filename) is str
@@ -251,8 +242,8 @@ def lint_policy_file(
251242
assert os.path.isfile(filename)
252243
assert filename.endswith((".cf", ".cfengine3", ".cf3", ".cf.sub"))
253244

254-
if custom_promise_types is None:
255-
custom_promise_types = set()
245+
if user_definition is None:
246+
user_definition = {}
256247

257248
tree, lines, original_data = _parse_policy_file(filename)
258249
root_node = tree.root_node
@@ -284,7 +275,7 @@ def lint_policy_file(
284275
else:
285276
print(f"Error: Empty policy file '{filename}'")
286277
errors += 1
287-
errors += _walk(filename, lines, root_node, custom_promise_types, strict)
278+
errors += _walk(filename, lines, root_node, user_definition, strict)
288279
if prefix:
289280
print(prefix, end="")
290281
if errors == 0:
@@ -323,34 +314,33 @@ def lint_folder(folder, strict=True):
323314
else:
324315
errors += lint_single_file(filename)
325316

326-
custom_promise_types = set()
317+
user_definition = {}
327318

328319
# First pass: Gather custom types
329320
for filename in policy_files if strict else []:
330321
tree, lines, _ = _parse_policy_file(filename)
331322
if tree.root_node.type == "source_file":
332-
custom_promise_types.update(
333-
_parse_custom_types(filename, lines, tree.root_node)
334-
)
323+
for key, val in _parse_user_definition(
324+
filename, lines, tree.root_node
325+
).items():
326+
user_definition[key] = user_definition.get(key, set()).union(val)
335327

336328
# Second pass: lint all policy files
337329
for filename in policy_files:
338330
errors += lint_policy_file(
339-
filename, custom_promise_types=custom_promise_types, strict=strict
331+
filename, user_definition=user_definition, strict=strict
340332
)
341333
return errors
342334

343335

344-
def lint_single_file(file, custom_promise_types=None, strict=True):
336+
def lint_single_file(file, user_definition=None, strict=True):
345337
assert os.path.isfile(file)
346338
if file.endswith("/cfbs.json"):
347339
return lint_cfbs_json(file)
348340
if file.endswith(".json"):
349341
return lint_json(file)
350342
assert file.endswith(".cf")
351-
return lint_policy_file(
352-
file, custom_promise_types=custom_promise_types, strict=strict
353-
)
343+
return lint_policy_file(file, user_definition=user_definition, strict=strict)
354344

355345

356346
def lint_single_arg(arg, strict=True):

src/cfengine_cli/main.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ def _get_arg_parser():
5656
"--strict",
5757
type=str,
5858
default="yes",
59-
help="Strict mode. Default=yes, checks for undefined promisetypes",
59+
help="Strict mode. Default=yes, checks for undefined promise types, bundles, bodies, functions",
6060
)
6161
lnt.add_argument("files", nargs="*", help="Files to format")
6262
subp.add_parser(

0 commit comments

Comments
 (0)