Skip to content

Commit 712b530

Browse files
authored
Fix missing MatchErrors due to hash collisions (#4307)
1 parent cd3dbd0 commit 712b530

File tree

5 files changed

+56
-20
lines changed

5 files changed

+56
-20
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
---
2+
CamelCaseIsBad: bar
3+
ALL_CAPS_ARE_BAD_TOO: bar
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
---
2+
CamelCaseIsBad: foo
3+
ALL_CAPS_ARE_BAD_TOO: foo

src/ansiblelint/rules/var_naming.py

+43-12
Original file line numberDiff line numberDiff line change
@@ -117,13 +117,15 @@ def get_var_naming_matcherror(
117117
ident: str,
118118
*,
119119
prefix: Prefix | None = None,
120+
file: Lintable,
120121
) -> MatchError | None:
121122
"""Return a MatchError if the variable name is not valid, otherwise None."""
122123
if not isinstance(ident, str): # pragma: no cover
123124
return MatchError(
124125
tag="var-naming[non-string]",
125126
message="Variables names must be strings.",
126127
rule=self,
128+
lintable=file,
127129
)
128130

129131
if ident in ANNOTATION_KEYS or ident in self.allowed_special_names:
@@ -136,27 +138,31 @@ def get_var_naming_matcherror(
136138
tag="var-naming[non-ascii]",
137139
message=f"Variables names must be ASCII. ({ident})",
138140
rule=self,
141+
lintable=file,
139142
)
140143

141144
if keyword.iskeyword(ident):
142145
return MatchError(
143146
tag="var-naming[no-keyword]",
144147
message=f"Variables names must not be Python keywords. ({ident})",
145148
rule=self,
149+
lintable=file,
146150
)
147151

148152
if ident in self.reserved_names:
149153
return MatchError(
150154
tag="var-naming[no-reserved]",
151155
message=f"Variables names must not be Ansible reserved names. ({ident})",
152156
rule=self,
157+
lintable=file,
153158
)
154159

155160
if ident in self.read_only_names:
156161
return MatchError(
157162
tag="var-naming[read-only]",
158163
message=f"This special variable is read-only. ({ident})",
159164
rule=self,
165+
lintable=file,
160166
)
161167

162168
# We want to allow use of jinja2 templating for variable names
@@ -165,6 +171,7 @@ def get_var_naming_matcherror(
165171
tag="var-naming[no-jinja]",
166172
message="Variables names must not contain jinja2 templating.",
167173
rule=self,
174+
lintable=file,
168175
)
169176

170177
if not bool(self.re_pattern.match(ident)) and (
@@ -174,6 +181,7 @@ def get_var_naming_matcherror(
174181
tag="var-naming[pattern]",
175182
message=f"Variables names should match {self.re_pattern_str} regex. ({ident})",
176183
rule=self,
184+
lintable=file,
177185
)
178186

179187
if (
@@ -186,6 +194,7 @@ def get_var_naming_matcherror(
186194
tag="var-naming[no-role-prefix]",
187195
message=f"Variables names from within roles should use {prefix.value}_ as a prefix.",
188196
rule=self,
197+
lintable=file,
189198
)
190199
return None
191200

@@ -199,9 +208,8 @@ def matchplay(self, file: Lintable, data: dict[str, Any]) -> list[MatchError]:
199208
# If the Play uses the 'vars' section to set variables
200209
our_vars = data.get("vars", {})
201210
for key in our_vars:
202-
match_error = self.get_var_naming_matcherror(key)
211+
match_error = self.get_var_naming_matcherror(key, file=file)
203212
if match_error:
204-
match_error.filename = str(file.path)
205213
match_error.lineno = (
206214
key.ansible_pos[1]
207215
if isinstance(key, AnsibleUnicode)
@@ -219,9 +227,9 @@ def matchplay(self, file: Lintable, data: dict[str, Any]) -> list[MatchError]:
219227
match_error = self.get_var_naming_matcherror(
220228
key,
221229
prefix=prefix,
230+
file=file,
222231
)
223232
if match_error:
224-
match_error.filename = str(file.path)
225233
match_error.message += f" (vars: {key})"
226234
match_error.lineno = (
227235
key.ansible_pos[1]
@@ -235,9 +243,9 @@ def matchplay(self, file: Lintable, data: dict[str, Any]) -> list[MatchError]:
235243
match_error = self.get_var_naming_matcherror(
236244
key,
237245
prefix=prefix,
246+
file=file,
238247
)
239248
if match_error:
240-
match_error.filename = str(file.path)
241249
match_error.message += f" (vars: {key})"
242250
match_error.lineno = (
243251
key.ansible_pos[1]
@@ -266,7 +274,6 @@ def matchtask(
266274
"""Return matches for task based variables."""
267275
results = []
268276
prefix = Prefix()
269-
filename = "" if file is None else str(file.path)
270277
if file and file.parent and file.parent.kind == "role":
271278
prefix = Prefix(file.parent.path.name)
272279
ansible_module = task["action"]["__ansible_module__"]
@@ -283,9 +290,9 @@ def matchtask(
283290
match_error = self.get_var_naming_matcherror(
284291
key,
285292
prefix=prefix,
293+
file=file or Lintable(""),
286294
)
287295
if match_error:
288-
match_error.filename = filename
289296
match_error.lineno = our_vars[LINE_NUMBER_KEY]
290297
match_error.message += f" (vars: {key})"
291298
results.append(match_error)
@@ -298,9 +305,12 @@ def matchtask(
298305
and x != "cacheable",
299306
task["action"].keys(),
300307
):
301-
match_error = self.get_var_naming_matcherror(key, prefix=prefix)
308+
match_error = self.get_var_naming_matcherror(
309+
key,
310+
prefix=prefix,
311+
file=file or Lintable(""),
312+
)
302313
if match_error:
303-
match_error.filename = filename
304314
match_error.lineno = task["action"][LINE_NUMBER_KEY]
305315
match_error.message += f" (set_fact: {key})"
306316
results.append(match_error)
@@ -311,10 +321,10 @@ def matchtask(
311321
match_error = self.get_var_naming_matcherror(
312322
registered_var,
313323
prefix=prefix,
324+
file=file or Lintable(""),
314325
)
315326
if match_error:
316327
match_error.message += f" (register: {registered_var})"
317-
match_error.filename = filename
318328
match_error.lineno = task[LINE_NUMBER_KEY]
319329
results.append(match_error)
320330

@@ -325,15 +335,17 @@ def matchyaml(self, file: Lintable) -> list[MatchError]:
325335
results: list[MatchError] = []
326336
raw_results: list[MatchError] = []
327337
meta_data: dict[AnsibleUnicode, Any] = {}
328-
filename = "" if file is None else str(file.path)
329338

330339
if str(file.kind) == "vars" and file.data:
331340
meta_data = parse_yaml_from_file(str(file.path))
332341
for key in meta_data:
333342
prefix = Prefix(file.role) if file.role else Prefix()
334-
match_error = self.get_var_naming_matcherror(key, prefix=prefix)
343+
match_error = self.get_var_naming_matcherror(
344+
key,
345+
prefix=prefix,
346+
file=file,
347+
)
335348
if match_error:
336-
match_error.filename = filename
337349
match_error.lineno = key.ansible_pos[1]
338350
match_error.message += f" (vars: {key})"
339351
raw_results.append(match_error)
@@ -413,6 +425,25 @@ def test_invalid_var_name_varsfile(
413425
assert result.tag == expected_errors[idx][0]
414426
assert result.lineno == expected_errors[idx][1]
415427

428+
def test_invalid_vars_diff_files(
429+
default_rules_collection: RulesCollection,
430+
) -> None:
431+
"""Test rule matches."""
432+
results = Runner(
433+
Lintable("examples/playbooks/vars/rule_var_naming_fails_files"),
434+
rules=default_rules_collection,
435+
).run()
436+
expected_errors = (
437+
("var-naming[pattern]", 2),
438+
("var-naming[pattern]", 3),
439+
("var-naming[pattern]", 2),
440+
("var-naming[pattern]", 3),
441+
)
442+
assert len(results) == len(expected_errors)
443+
for idx, result in enumerate(results):
444+
assert result.tag == expected_errors[idx][0]
445+
assert result.lineno == expected_errors[idx][1]
446+
416447
def test_var_naming_with_role_prefix(
417448
default_rules_collection: RulesCollection,
418449
) -> None:

src/ansiblelint/utils.py

+6-7
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,7 @@ def taskshandlers_children(
358358
raise MatchError(
359359
message="A malformed block was encountered while loading a block.",
360360
rule=RuntimeErrorRule(),
361+
lintable=lintable,
361362
)
362363
for task_handler in v:
363364
# ignore empty tasks, `-`
@@ -408,23 +409,21 @@ def _validate_task_handler_action_for_role(self, th_action: dict[str, Any]) -> N
408409
"""Verify that the task handler action is valid for role include."""
409410
module = th_action["__ansible_module__"]
410411

412+
lintable = Lintable(
413+
self.rules.options.lintables[0] if self.rules.options.lintables else ".",
414+
)
411415
if "name" not in th_action:
412416
raise MatchError(
413417
message=f"Failed to find required 'name' key in {module!s}",
414418
rule=self.rules.rules[0],
415-
lintable=Lintable(
416-
(
417-
self.rules.options.lintables[0]
418-
if self.rules.options.lintables
419-
else "."
420-
),
421-
),
419+
lintable=lintable,
422420
)
423421

424422
if not isinstance(th_action["name"], str):
425423
raise MatchError(
426424
message=f"Value assigned to 'name' key on '{module!s}' is not a string.",
427425
rule=self.rules.rules[1],
426+
lintable=lintable,
428427
)
429428

430429
def roles_children(

tox.ini

+1-1
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ setenv =
7474
PRE_COMMIT_COLOR = always
7575
# Number of expected test passes, safety measure for accidental skip of
7676
# tests. Update value if you add/remove tests. (tox-extra)
77-
PYTEST_REQPASS = 893
77+
PYTEST_REQPASS = 894
7878
FORCE_COLOR = 1
7979
pre: PIP_PRE = 1
8080
allowlist_externals =

0 commit comments

Comments
 (0)