Skip to content

Commit 5e953e3

Browse files
committed
Refactor line number identification
Related: AAP-41586
1 parent a531b81 commit 5e953e3

22 files changed

+176
-88
lines changed

conftest.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@ def pytest_configure(config: pytest.Config) -> None:
3131
if is_help_option_present(config):
3232
return
3333
if is_master(config):
34+
# linter should be able de detect and convert some deprecation warnings
35+
# into validation errors but during testing we disable this to avoid
36+
# unnecessary noise. Still, we might want to enable it for particular
37+
# tests, for testing our ability to detect deprecations.
38+
os.environ["ANSIBLE_DEPRECATION_WARNINGS"] = "False"
3439
# we need to be sure that we have the requirements installed as some tests
3540
# might depend on these. This approach is compatible with GHA caching.
3641
try:

examples/.no_collection_version/galaxy.yml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,7 @@ authors:
66
- John
77
description: your collection description
88
license:
9-
- GPL
10-
- Apache
9+
- GPL-3.0-or-later
1110

1211
dependencies: {}
1312
repository: http://example.com/repository

src/ansiblelint/constants.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ def main():
8989
"ansible-lint-config",
9090
"sanity-ignore-file", # tests/sanity/ignore file
9191
"plugin",
92+
"galaxy", # galaxy.yml
9293
"", # unknown file type
9394
]
9495

src/ansiblelint/rules/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,15 +76,20 @@ def create_matcherror(
7676
self,
7777
message: str = "",
7878
lineno: int = 1,
79+
column: int | None = None,
7980
details: str = "",
8081
filename: Lintable | None = None,
8182
tag: str = "",
8283
transform_meta: RuleMatchTransformMeta | None = None,
84+
data: Any | None = None,
8385
) -> MatchError:
8486
"""Instantiate a new MatchError."""
87+
if data is not None and lineno == 1 and column is None:
88+
lineno, column = ansiblelint.yaml_utils.get_line_column(data, line=lineno)
8589
match = MatchError(
8690
message=message,
8791
lineno=lineno,
92+
column=column,
8893
details=details,
8994
lintable=filename or Lintable(""),
9095
rule=copy.copy(self),

src/ansiblelint/rules/args.py

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -289,13 +289,26 @@ def test_args_module_fail(default_rules_collection: RulesCollection) -> None:
289289
results = Runner(success, rules=default_rules_collection).run()
290290
assert len(results) == 5
291291
assert results[0].tag == "args[module]"
292-
assert "missing required arguments" in results[0].message
292+
# First part of regex is for ansible-core up to 2.18, second part is for ansible-core 2.19+
293+
assert re.match(
294+
r"(missing required arguments|Unsupported parameters for \(basic.py\) module: foo)",
295+
results[0].message,
296+
)
293297
assert results[1].tag == "args[module]"
294-
assert "missing parameter(s) required by " in results[1].message
298+
assert re.match(
299+
r"(missing parameter\(s\) required by |Unsupported parameters for \(basic.py\) module: foo. Supported parameters include: fact_path)",
300+
results[1].message,
301+
)
295302
assert results[2].tag == "args[module]"
296-
assert "Unsupported parameters for" in results[2].message
303+
assert re.match(
304+
r"(Unsupported parameters for|missing parameter\(s\) required by 'enabled': name)",
305+
results[2].message,
306+
)
297307
assert results[3].tag == "args[module]"
298-
assert "Unsupported parameters for" in results[3].message
308+
assert re.match(
309+
r"(Unsupported parameters for|missing required arguments: repo)",
310+
results[3].message,
311+
)
299312
assert results[4].tag == "args[module]"
300313
assert "value of state must be one of" in results[4].message
301314

src/ansiblelint/rules/complexity.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
import sys
77
from typing import TYPE_CHECKING, Any
88

9-
from ansiblelint.constants import LINE_NUMBER_KEY
109
from ansiblelint.rules import AnsibleLintRule, RulesCollection
1110

1211
if TYPE_CHECKING:
@@ -40,9 +39,9 @@ def matchplay(self, file: Lintable, data: dict[str, Any]) -> list[MatchError]:
4039
results.append(
4140
self.create_matcherror(
4241
message=f"Maximum tasks allowed in a play is {self._collection.options.max_tasks}.",
43-
lineno=data[LINE_NUMBER_KEY],
4442
tag=f"{self.id}[play]",
4543
filename=file,
44+
data=data,
4645
),
4746
)
4847
return results

src/ansiblelint/rules/fqcn.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88

99
from ruamel.yaml.comments import CommentedSeq
1010

11-
from ansiblelint.constants import LINE_NUMBER_KEY
1211
from ansiblelint.rules import AnsibleLintRule, TransformMixin
1312
from ansiblelint.utils import load_plugin
1413

@@ -159,7 +158,7 @@ def matchtask(
159158
message=message,
160159
details=details,
161160
filename=file,
162-
lineno=task.line,
161+
data=module,
163162
tag="fqcn[action-core]",
164163
),
165164
)
@@ -169,7 +168,7 @@ def matchtask(
169168
message=f"Use FQCN for module actions, such `{self.module_aliases[module]}`.",
170169
details=f"Action `{module}` is not FQCN.",
171170
filename=file,
172-
lineno=task.line,
171+
data=module,
173172
tag="fqcn[action]",
174173
),
175174
)
@@ -183,7 +182,7 @@ def matchtask(
183182
self.create_matcherror(
184183
message=f"You should use canonical module name `{self.module_aliases[module]}` instead of `{module}`.",
185184
filename=file,
186-
lineno=task[LINE_NUMBER_KEY],
185+
data=module,
187186
tag="fqcn[canonical]",
188187
),
189188
)
@@ -219,7 +218,7 @@ def matchplay(self, file: Lintable, data: dict[str, Any]) -> list[MatchError]:
219218
return [
220219
self.create_matcherror(
221220
message="Avoid `collections` keyword by using FQCN for all plugins, modules, roles and playbooks.",
222-
lineno=data.get(LINE_NUMBER_KEY, 1),
221+
data=data,
223222
tag="fqcn[keyword]",
224223
filename=file,
225224
),

src/ansiblelint/rules/galaxy.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ class GalaxyRule(AnsibleLintRule):
4040

4141
def matchplay(self, file: Lintable, data: dict[str, Any]) -> list[MatchError]:
4242
"""Return matches found for a specific play (entry in playbook)."""
43-
if file.kind != "galaxy": # type: ignore[comparison-overlap]
43+
if file.kind != "galaxy":
4444
return []
4545

4646
# Defined by Automation Hub Team and Partner Engineering
@@ -161,7 +161,7 @@ def matchplay(self, file: Lintable, data: dict[str, Any]) -> list[MatchError]:
161161
results.append(
162162
self.create_matcherror(
163163
message="galaxy.yaml should have version tag.",
164-
lineno=data[LINE_NUMBER_KEY],
164+
data=data,
165165
tag="galaxy[version-missing]",
166166
filename=file,
167167
),

src/ansiblelint/rules/galaxy_version_incorrect.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,16 @@ class GalaxyVersionIncorrectRule(AnsibleLintRule):
2424

2525
def matchplay(self, file: Lintable, data: dict[str, Any]) -> list[MatchError]:
2626
"""Return matches found for a specific play (entry in playbook)."""
27-
if file.kind != "galaxy": # type: ignore[comparison-overlap]
27+
if file.kind != "galaxy":
2828
return []
2929

3030
results = []
3131
version = data.get("version")
32-
if Version(version) < Version("1.0.0"):
32+
if not version or Version(version) < Version("1.0.0"):
3333
results.append(
3434
self.create_matcherror(
3535
message="collection version should be greater than or equal to 1.0.0",
36-
lineno=version._line_number, # noqa: SLF001
36+
data=version,
3737
filename=file,
3838
),
3939
)

src/ansiblelint/rules/jinja.py

Lines changed: 29 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,14 @@
66
import os
77
import re
88
import sys
9+
from collections.abc import Mapping
910
from dataclasses import dataclass
1011
from pathlib import Path
1112
from typing import TYPE_CHECKING, NamedTuple
1213

1314
import black
1415
import jinja2
1516
from ansible.errors import AnsibleError, AnsibleFilterError, AnsibleParserError
16-
from ansible.parsing.yaml.objects import AnsibleUnicode
1717
from jinja2.exceptions import TemplateSyntaxError
1818

1919
from ansiblelint.errors import RuleMatchTransformMeta
@@ -22,6 +22,7 @@
2222
from ansiblelint.runner import get_matches
2323
from ansiblelint.skip_utils import get_rule_skips_from_line
2424
from ansiblelint.text import has_jinja
25+
from ansiblelint.types import AnsibleTemplateSyntaxError
2526
from ansiblelint.utils import ( # type: ignore[attr-defined]
2627
Templar,
2728
parse_yaml_from_file,
@@ -187,6 +188,13 @@ def matchtask(
187188
elif re.match(r"^lookup plugin (.*) not found$", exc.message):
188189
# lookup plugin 'template' not found
189190
bypass = True
191+
elif isinstance(
192+
orig_exc, AnsibleTemplateSyntaxError
193+
) and re.match(
194+
r"^Syntax error in template: No filter named '.*'.",
195+
exc.message,
196+
):
197+
bypass = True
190198

191199
# AnsibleError: template error while templating string: expected token ':', got '}'. String: {{ {{ '1' }} }}
192200
# AnsibleError: template error while templating string: unable to locate collection ansible.netcommon. String: Foo {{ buildset_registry.host | ipwrap }}
@@ -195,6 +203,7 @@ def matchtask(
195203
self.create_matcherror(
196204
message=str(exc),
197205
lineno=task.get_error_line(path),
206+
data=v,
198207
filename=file,
199208
tag=f"{self.id}[invalid]",
200209
),
@@ -214,6 +223,7 @@ def matchtask(
214223
reformatted=reformatted,
215224
),
216225
lineno=task.get_error_line(path),
226+
data=v,
217227
details=details,
218228
filename=file,
219229
tag=f"{self.id}[{tag}]",
@@ -237,10 +247,10 @@ def matchyaml(self, file: Lintable) -> list[MatchError]:
237247

238248
if str(file.kind) == "vars":
239249
data = parse_yaml_from_file(str(file.path))
240-
if not isinstance(data, dict):
250+
if not isinstance(data, Mapping):
241251
return results
242252
for key, v, _path in nested_items_path(data):
243-
if isinstance(v, AnsibleUnicode):
253+
if isinstance(v, str):
244254
reformatted, details, tag = self.check_whitespace(
245255
v,
246256
key=key,
@@ -254,7 +264,7 @@ def matchyaml(self, file: Lintable) -> list[MatchError]:
254264
value=v,
255265
reformatted=reformatted,
256266
),
257-
lineno=v.ansible_pos[1],
267+
data=v,
258268
details=details,
259269
filename=file,
260270
tag=f"{self.id}[{tag}]",
@@ -506,31 +516,23 @@ def blacken(text: str) -> str:
506516
from ansiblelint.runner import Runner
507517
from ansiblelint.transformer import Transformer
508518

509-
@pytest.fixture(name="error_expected_lines")
510-
def fixture_error_expected_lines() -> list[int]:
511-
"""Return list of expected error lines."""
512-
return [33, 36, 39, 42, 45, 48, 74]
513-
514-
# 21 68
515-
@pytest.fixture(name="lint_error_lines")
516-
def fixture_lint_error_lines() -> list[int]:
517-
"""Get VarHasSpacesRules linting results on test_playbook."""
519+
def test_jinja_spacing_playbook() -> None:
520+
"""Ensure that expected error lines are matching found linting error lines."""
521+
# list unexpected error lines or non-matching error lines
522+
lineno_list = [33, 36, 39, 42, 45, 48, 74]
523+
lintable = Lintable("examples/playbooks/jinja-spacing.yml")
518524
collection = RulesCollection()
519525
collection.register(JinjaRule())
520-
lintable = Lintable("examples/playbooks/jinja-spacing.yml")
521526
results = Runner(lintable, rules=collection).run()
522-
return [item.lineno for item in results]
527+
assert len(results) == len(lineno_list)
528+
for index, result in enumerate(results):
529+
assert result.tag == "jinja[spacing]"
530+
assert result.lineno == lineno_list[index]
523531

524-
def test_jinja_spacing_playbook(
525-
error_expected_lines: list[int],
526-
lint_error_lines: list[int],
527-
) -> None:
528-
"""Ensure that expected error lines are matching found linting error lines."""
529-
# list unexpected error lines or non-matching error lines
530-
error_lines_difference = list(
531-
set(error_expected_lines).symmetric_difference(set(lint_error_lines)),
532-
)
533-
assert len(error_lines_difference) == 0
532+
# error_lines_difference = list(
533+
# set(error_expected_lines).symmetric_difference(set(lint_error_lines)),
534+
# )
535+
# assert len(error_lines_difference) == 0
534536

535537
def test_jinja_spacing_vars() -> None:
536538
"""Ensure that expected error details are matching found linting error details."""
@@ -833,10 +835,10 @@ def test_jinja_invalid() -> None:
833835
assert len(errs) == 2
834836
assert errs[0].tag == "jinja[spacing]"
835837
assert errs[0].rule.id == "jinja"
836-
assert errs[0].lineno == 9
838+
assert errs[0].lineno in [7, 9] # 2.19 has better line identification
837839
assert errs[1].tag == "jinja[invalid]"
838840
assert errs[1].rule.id == "jinja"
839-
assert errs[1].lineno == 9
841+
assert errs[1].lineno in [9, 10] # 2.19 has better line identification
840842

841843
def test_jinja_valid() -> None:
842844
"""Tests our ability to parse jinja, even when variables may not be defined."""

0 commit comments

Comments
 (0)