Skip to content

Commit e4de1cd

Browse files
oraNodpre-commit-ci[bot]ssbarnea
authored
Refactor no-loop-var-prefix rule (#2470)
* docs lint rule no-loop-var * chore: auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Refactor no-loop-var-prefix - rename rule to loop-var-prefix and keep alias for old name - make rule produce more detailed messages - add documentation to the rule Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Sorin Sbarnea <[email protected]>
1 parent 01d2e6d commit e4de1cd

File tree

7 files changed

+90
-38
lines changed

7 files changed

+90
-38
lines changed

examples/roles/fail_loop_var_prefix/tasks/main.yml

Lines changed: 0 additions & 24 deletions
This file was deleted.

examples/roles/loop_var_prefix/tasks/fail.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
2-
# 5 expected no-loop-var-prefix failures at 3, 9, 19, 26, 33
3-
- name: That should trigger no-loop-var-prefix
2+
# 5 expected loop-var-prefix failures at 3, 9, 19, 26, 33
3+
- name: That should trigger loop-var-prefix
44
ansible.builtin.debug:
55
var: item
66
loop:

examples/roles/loop_var_prefix/tasks/pass.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
# 0 expected no-loop-var-prefix failures
2+
# 0 expected loop-var-prefix failures
33
- name: That should pass
44
ansible.builtin.debug:
55
var: loop_var_prefix_item

src/ansiblelint/constants.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ def main():
102102
"git-latest": "latest[git]",
103103
"hg-latest": "latest[hg]",
104104
"no-jinja-nesting": "jinja[invalid]",
105+
"no-loop-var-prefix": "loop-var-prefix",
105106
}
106107

107108
PLAYBOOK_TASK_KEYWORDS = [
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# loop-var-prefix
2+
3+
This rule avoids conflicts with nested looping tasks by configuring a variable prefix with `loop_var`.
4+
Ansible sets `item` as the loop variable.
5+
You can use `loop_var` to specify a prefix for loop variables and ensure they are unique to each task.
6+
7+
This rule can produce the following messages:
8+
9+
- `[loop-var-prefix[missing]` - Replace unsafe implicit `item` loop variable by adding `loop_var: <loop_var_prefix>...`.
10+
- `[loop-var-prefix[wrong]` - Loop variable should start with <loop_var_prefix>
11+
12+
This is an opt-in rule.
13+
You must enable it in your Ansible-lint configuration as follows:
14+
15+
```yaml
16+
enable_list:
17+
- loop-var-prefix
18+
```
19+
20+
## Problematic Code
21+
22+
```yaml
23+
---
24+
- name: Example playbook
25+
hosts: localhost
26+
tasks:
27+
- name: Does not set a prefix for loop variables.
28+
ansible.builtin.debug:
29+
var: item
30+
loop:
31+
- foo
32+
- bar # <- These items do not have a unique prefix.
33+
- name: Sets
34+
ansible.builtin.debug:
35+
var: zz_item
36+
loop:
37+
- foo
38+
- bar
39+
loop_control:
40+
loop_var: zz_item # <- This prefix is not unique.
41+
```
42+
43+
## Correct Code
44+
45+
```yaml
46+
---
47+
- name: Example playbook
48+
hosts: localhost
49+
tasks:
50+
- name: Sets a unique prefix for loop variables.
51+
ansible.builtin.debug:
52+
var: zz_item
53+
loop:
54+
- foo
55+
- bar
56+
loop_control:
57+
loop_var: my_prefix # <- Specifies a unique prefix for loop variables.
58+
```

src/ansiblelint/rules/no_loop_var_prefix.py renamed to src/ansiblelint/rules/loop_var_prefix.py

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from typing import TYPE_CHECKING, Any
66

77
from ansiblelint.config import options
8+
from ansiblelint.errors import MatchError
89
from ansiblelint.rules import AnsibleLintRule
910
from ansiblelint.text import toidentifier
1011

@@ -15,7 +16,7 @@
1516
class RoleLoopVarPrefix(AnsibleLintRule):
1617
"""Role loop_var should use configured prefix."""
1718

18-
id = "no-loop-var-prefix"
19+
id = "loop-var-prefix"
1920
link = (
2021
"https://docs.ansible.com/ansible/latest/user_guide/"
2122
"playbooks_loops.html#defining-inner-and-outer-variable-names-with-loop-var"
@@ -30,12 +31,13 @@ class RoleLoopVarPrefix(AnsibleLintRule):
3031

3132
def matchtask(
3233
self, task: dict[str, Any], file: Lintable | None = None
33-
) -> bool | str:
34+
) -> list[MatchError]:
3435
"""Return matches for a task."""
3536
if not file or not file.role or not options.loop_var_prefix:
36-
return False
37+
return []
3738

3839
self.prefix = options.loop_var_prefix.format(role=toidentifier(file.role))
40+
# self.prefix becomes `loop_var_prefix_` because role name is loop_var_prefix
3941

4042
has_loop = "loop" in task
4143
for key in task.keys():
@@ -46,10 +48,25 @@ def matchtask(
4648
loop_control = task.get("loop_control", {})
4749
loop_var = loop_control.get("loop_var", "")
4850

49-
if not loop_var or not loop_var.startswith(self.prefix):
50-
return True
51-
52-
return False
51+
if loop_var:
52+
if not loop_var.startswith(self.prefix):
53+
return [
54+
self.create_matcherror(
55+
message=f"Loop variable should start with {self.prefix}",
56+
filename=file,
57+
tag="loop-var-prefix[wrong]",
58+
)
59+
]
60+
else:
61+
return [
62+
self.create_matcherror(
63+
message=f"Replace unsafe implicit `item` loop variable by adding `loop_var: {self.prefix}...`.",
64+
filename=file,
65+
tag="loop-var-prefix[missing]",
66+
)
67+
]
68+
69+
return []
5370

5471

5572
# testing code to be loaded only with pytest or when executed the rule file
@@ -71,13 +88,13 @@ def matchtask(
7188
),
7289
),
7390
)
74-
def test_no_loop_var_prefix(
91+
def test_loop_var_prefix(
7592
default_rules_collection: RulesCollection, test_file: str, failures: int
7693
) -> None:
7794
"""Test rule matches."""
7895
# Enable checking of loop variable prefixes in roles
7996
options.loop_var_prefix = "{role}_"
8097
results = Runner(test_file, rules=default_rules_collection).run()
81-
assert len(results) == failures
8298
for result in results:
83-
assert result.message == RoleLoopVarPrefix().shortdesc
99+
assert result.rule.id == RoleLoopVarPrefix().id
100+
assert len(results) == failures

src/ansiblelint/schemas/ansible-lint-config.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@
112112
"no-handler",
113113
"no-jinja-when",
114114
"no-log-password",
115-
"no-loop-var-prefix",
115+
"loop-var-prefix",
116116
"no-prompting",
117117
"no-relative-paths",
118118
"no-same-owner",

0 commit comments

Comments
 (0)