|
17 | 17 | from cfbs.validate import validate_config |
18 | 18 | from cfbs.cfbs_config import CFBSConfig |
19 | 19 |
|
| 20 | +DEPRECATED_PROMISE_TYPES = ["defaults", "guest_environments"] |
| 21 | +ALLOWED_BUNDLE_TYPES = ["agent", "common", "monitor", "server", "edit_line"] |
| 22 | + |
20 | 23 |
|
21 | 24 | def lint_cfbs_json(filename) -> int: |
22 | 25 | assert os.path.isfile(filename) |
@@ -72,34 +75,94 @@ def _text(node): |
72 | 75 | return node.text.decode() |
73 | 76 |
|
74 | 77 |
|
75 | | -def _walk(filename, lines, node) -> int: |
| 78 | +def _walk_generic(filename, lines, node, visitor): |
| 79 | + visitor(node) |
| 80 | + for node in node.children: |
| 81 | + _walk_generic(filename, lines, node, visitor) |
| 82 | + |
| 83 | + |
| 84 | +def _find_node_type(filename, lines, node, node_type): |
| 85 | + matches = [] |
| 86 | + visitor = lambda x: matches.extend([x] if x.type == node_type else []) |
| 87 | + _walk_generic(filename, lines, node, visitor) |
| 88 | + return matches |
| 89 | + |
| 90 | + |
| 91 | +def _find_nodes(filename, lines, node): |
| 92 | + matches = [] |
| 93 | + visitor = lambda x: matches.append(x) |
| 94 | + _walk_generic(filename, lines, node, visitor) |
| 95 | + return matches |
| 96 | + |
| 97 | + |
| 98 | +def _single_node_checks(filename, lines, node): |
| 99 | + """Things which can be checked by only looking at one node, |
| 100 | + not needing to recurse into children.""" |
76 | 101 | line = node.range.start_point[0] + 1 |
77 | | - column = node.range.start_point[1] |
78 | | - errors = 0 |
79 | | - # Checking for syntax errors (already detected by parser / grammar). |
80 | | - # These are represented in the syntax tree as special ERROR nodes. |
81 | | - if node.type == "ERROR": |
| 102 | + column = node.range.start_point[1] + 1 |
| 103 | + if node.type == "attribute_name" and _text(node) == "ifvarclass": |
82 | 104 | _highlight_range(node, lines) |
83 | | - print(f"Error: Syntax error at {filename}:{line}:{column}") |
84 | | - errors += 1 |
85 | | - |
86 | | - if node.type == "attribute_name": |
87 | | - if _text(node) == "ifvarclass": |
| 105 | + print( |
| 106 | + f"Deprecation: Use 'if' instead of 'ifvarclass' at {filename}:{line}:{column}" |
| 107 | + ) |
| 108 | + return 1 |
| 109 | + if node.type == "promise_guard": |
| 110 | + assert _text(node) and len(_text(node)) > 1 and _text(node)[-1] == ":" |
| 111 | + promise_type = _text(node)[0:-1] |
| 112 | + if promise_type in DEPRECATED_PROMISE_TYPES: |
| 113 | + _highlight_range(node, lines) |
| 114 | + print( |
| 115 | + f"Deprecation: Promise type '{promise_type}' is deprecated at {filename}:{line}:{column}" |
| 116 | + ) |
| 117 | + return 1 |
| 118 | + if node.type == "bundle_block_name": |
| 119 | + if _text(node) != _text(node).lower(): |
| 120 | + _highlight_range(node, lines) |
| 121 | + print( |
| 122 | + f"Convention: Bundle name should be lowercase at {filename}:{line}:{column}" |
| 123 | + ) |
| 124 | + return 1 |
| 125 | + if node.type == "promise_block_name": |
| 126 | + if _text(node) != _text(node).lower(): |
88 | 127 | _highlight_range(node, lines) |
89 | 128 | print( |
90 | | - f"Error: Use 'if' instead of 'ifvarclass' (deprecated) at {filename}:{line}:{column}" |
| 129 | + f"Convention: Promise type should be lowercase at {filename}:{line}:{column}" |
91 | 130 | ) |
92 | | - errors += 1 |
| 131 | + return 1 |
| 132 | + if node.type == "bundle_block_type": |
| 133 | + if _text(node) not in ALLOWED_BUNDLE_TYPES: |
| 134 | + _highlight_range(node, lines) |
| 135 | + print( |
| 136 | + f"Error: Bundle type must be one of ({', '.join(ALLOWED_BUNDLE_TYPES)}), not '{_text(node)}' at {filename}:{line}:{column}" |
| 137 | + ) |
| 138 | + return 1 |
| 139 | + return 0 |
93 | 140 |
|
94 | | - for node in node.children: |
95 | | - errors += _walk(filename, lines, node) |
| 141 | + |
| 142 | +def _walk(filename, lines, node) -> int: |
| 143 | + error_nodes = _find_node_type(filename, lines, node, "ERROR") |
| 144 | + if error_nodes: |
| 145 | + for node in error_nodes: |
| 146 | + line = node.range.start_point[0] + 1 |
| 147 | + column = node.range.start_point[1] + 1 |
| 148 | + _highlight_range(node, lines) |
| 149 | + print(f"Error: Syntax error at {filename}:{line}:{column}") |
| 150 | + return len(error_nodes) |
| 151 | + |
| 152 | + line = node.range.start_point[0] + 1 |
| 153 | + column = node.range.start_point[1] + 1 |
| 154 | + |
| 155 | + errors = 0 |
| 156 | + for node in _find_nodes(filename, lines, node): |
| 157 | + errors += _single_node_checks(filename, lines, node) |
96 | 158 |
|
97 | 159 | return errors |
98 | 160 |
|
99 | 161 |
|
100 | 162 | def lint_policy_file( |
101 | 163 | filename, original_filename=None, original_line=None, snippet=None, prefix=None |
102 | 164 | ): |
| 165 | + print(f"Linting: {filename}") |
103 | 166 | assert original_filename is None or type(original_filename) is str |
104 | 167 | assert original_line is None or type(original_line) is int |
105 | 168 | assert snippet is None or type(snippet) is int |
|
0 commit comments