diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index bb5511ce4f..451afb3887 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -17,6 +17,7 @@ exclude: > examples/broken/encoding.yml| examples/broken/encoding.j2| examples/broken/yaml-with-tabs/invalid-due-tabs.yaml| + examples/broken/load-failure-invalid.yml| examples/playbooks/collections/.*| examples/playbooks/vars/empty.transformed.yml| examples/playbooks/vars/empty.yml| diff --git a/examples/broken/load-failure-invalid.yml b/examples/broken/load-failure-invalid.yml new file mode 100644 index 0000000000..3f09b8707a --- /dev/null +++ b/examples/broken/load-failure-invalid.yml @@ -0,0 +1,5 @@ +--- +- aaa: 1 + bbb: 2 + c: 3 +foo: bar diff --git a/src/ansiblelint/runner.py b/src/ansiblelint/runner.py index ca21fc2d12..ff3f4f02e3 100644 --- a/src/ansiblelint/runner.py +++ b/src/ansiblelint/runner.py @@ -23,6 +23,9 @@ from ansible.parsing.splitter import split_args from ansible.plugins.loader import add_all_plugin_dirs from ansible_compat.runtime import AnsibleWarning +from ruamel.yaml.parser import ParserError as RuamelParserError +from yaml.parser import ParserError +from yaml.scanner import ScannerError import ansiblelint.skip_utils import ansiblelint.utils @@ -213,14 +216,30 @@ def _run(self) -> list[MatchError]: self.lintables.remove(lintable) continue if isinstance(lintable.data, States) and lintable.exc: + line = 1 + column = None + detail = "" + sub_tag = "" lintable.exc.__class__.__name__.lower() + message = None + if lintable.exc.__cause__ and isinstance(lintable.exc.__cause__, ScannerError | ParserError | RuamelParserError): + sub_tag = "yaml" + if isinstance(lintable.exc.args, tuple): + message = lintable.exc.args[0] + detail = lintable.exc.__cause__.problem + if lintable.exc.__cause__.problem_mark: + line = lintable.exc.__cause__.problem_mark.line + 1 + column = lintable.exc.__cause__.problem_mark.column + 1 + matches.append( MatchError( lintable=lintable, - message=str(lintable.exc), - details=str(lintable.exc.__cause__), + message=message or str(lintable.exc), + details=detail or str(lintable.exc.__cause__), rule=self.rules["load-failure"], - tag=f"load-failure[{lintable.exc.__class__.__name__.lower()}]", + lineno=line, + column=column, + tag=f"load-failure[{sub_tag or lintable.exc.__class__.__name__.lower()}]", ), ) lintable.stop_processing = True diff --git a/src/ansiblelint/schemas/__store__.json b/src/ansiblelint/schemas/__store__.json index 70eb8c45df..8f760fd387 100644 --- a/src/ansiblelint/schemas/__store__.json +++ b/src/ansiblelint/schemas/__store__.json @@ -24,7 +24,7 @@ "url": "https://raw.githubusercontent.com/ansible/ansible-lint/main/src/ansiblelint/schemas/inventory.json" }, "meta": { - "etag": "ba724ad96ccb630237b908377c31796a1208265731b4b8a8fa91ffb7e52c611b", + "etag": "50a33e03b769d4574f20f192ce37dcb694361a8b30e030b2c1274f393e6595e4", "url": "https://raw.githubusercontent.com/ansible/ansible-lint/main/src/ansiblelint/schemas/meta.json" }, "meta-runtime": { diff --git a/src/ansiblelint/utils.py b/src/ansiblelint/utils.py index 0ada7af206..ee32dd0d6c 100644 --- a/src/ansiblelint/utils.py +++ b/src/ansiblelint/utils.py @@ -1160,8 +1160,8 @@ def construct_mapping( yaml.constructor.ConstructorError, ruamel.yaml.parser.ParserError, ) as exc: - msg = f"Failed to load YAML file: {lintable.path}" - raise RuntimeError(msg) from exc + msg = "Failed to load YAML file" + raise RuntimeError(msg, lintable.path) from exc if len(result) == 0: return None # empty documents diff --git a/test/test_runner.py b/test/test_runner.py index aa76b65a66..91644cf282 100644 --- a/test/test_runner.py +++ b/test/test_runner.py @@ -241,6 +241,25 @@ def test_runner_not_found(default_rules_collection: RulesCollection) -> None: assert result[0].tag == "load-failure[not-found]" +def test_runner_load_failure_yaml(default_rules_collection: RulesCollection) -> None: + """Ensure load-failure[yaml] work as expected.""" + checked_files: set[Lintable] = set() + + filename = Path("examples/broken/load-failure-invalid.yml").resolve() + runner = Runner( + filename, + rules=default_rules_collection, + verbosity=0, + checked_files=checked_files, + ) + result = runner.run() + assert len(runner.checked_files) == 1 + assert len(result) == 1 + assert result[0].tag == "load-failure[yaml]" + assert result[0].lineno == 5 + assert result[0].column == 1 + + def test_runner_tmp_file( tmp_path: Path, default_rules_collection: RulesCollection, diff --git a/tox.ini b/tox.ini index 1157a35291..a08999f250 100644 --- a/tox.ini +++ b/tox.ini @@ -56,7 +56,7 @@ set_env = PIP_CONSTRAINT = {tox_root}/.config/constraints.txt PIP_DISABLE_PIP_VERSION_CHECK = 1 PRE_COMMIT_COLOR = always - PYTEST_REQPASS = 908 + PYTEST_REQPASS = 909 UV_CONSTRAINT = {tox_root}/.config/constraints.txt deps, devel, hook, lint, pkg, pre, py310, schemas: PIP_CONSTRAINT = /dev/null deps, devel, hook, lint, pkg, pre, py310, schemas: UV_CONSTRAINT = /dev/null