From b5d027cf4023ed1c13089fc1759b232fb97a5833 Mon Sep 17 00:00:00 2001 From: elara-leitstellentechnik Date: Tue, 4 Jun 2024 13:25:41 +0200 Subject: [PATCH] Respect yamllint 'document_start' rule when autofixing yaml (#4184) Co-authored-by: Jonas Switala --- .github/workflows/tox.yml | 2 +- src/ansiblelint/yaml_utils.py | 37 ++++++++++++++++++++++++----------- test/test_yaml_utils.py | 28 +++++++++++++++++++++++++- 3 files changed, 54 insertions(+), 13 deletions(-) diff --git a/.github/workflows/tox.yml b/.github/workflows/tox.yml index c7b3c47d18..77d546253e 100644 --- a/.github/workflows/tox.yml +++ b/.github/workflows/tox.yml @@ -72,7 +72,7 @@ jobs: env: # Number of expected test passes, safety measure for accidental skip of # tests. Update value if you add/remove tests. - PYTEST_REQPASS: 875 + PYTEST_REQPASS: 877 steps: - uses: actions/checkout@v4 with: diff --git a/src/ansiblelint/yaml_utils.py b/src/ansiblelint/yaml_utils.py index ca22303976..9f15f91fc5 100644 --- a/src/ansiblelint/yaml_utils.py +++ b/src/ansiblelint/yaml_utils.py @@ -820,6 +820,16 @@ def write_version_directive(self, version_text: Any) -> None: class FormattedYAML(YAML): """A YAML loader/dumper that handles ansible content better by default.""" + default_config = { + "explicit_start": True, + "explicit_end": False, + "width": 160, + "indent_sequences": True, + "preferred_quote": '"', + "min_spaces_inside": 0, + "max_spaces_inside": 1, + } + def __init__( # pylint: disable=too-many-arguments self, *, @@ -828,6 +838,7 @@ def __init__( # pylint: disable=too-many-arguments output: Any = None, plug_ins: list[str] | None = None, version: tuple[int, int] | None = None, + config: dict[str, bool | int | str] | None = None, ): """Return a configured ``ruamel.yaml.YAML`` instance. @@ -897,7 +908,8 @@ def __init__( # pylint: disable=too-many-arguments # NB: We ignore some mypy issues because ruamel.yaml typehints are not great. - config = self._defaults_from_yamllint_config() + if not config: + config = self._defaults_from_yamllint_config() # these settings are derived from yamllint config self.explicit_start: bool = config["explicit_start"] # type: ignore[assignment] @@ -950,15 +962,8 @@ def __init__( # pylint: disable=too-many-arguments @staticmethod def _defaults_from_yamllint_config() -> dict[str, bool | int | str]: """Extract FormattedYAML-relevant settings from yamllint config if possible.""" - config = { - "explicit_start": True, - "explicit_end": False, - "width": 160, - "indent_sequences": True, - "preferred_quote": '"', - "min_spaces_inside": 0, - "max_spaces_inside": 1, - } + config = FormattedYAML.default_config + for rule, rule_config in load_yamllint_config().rules.items(): if not rule_config: # rule disabled @@ -1062,6 +1067,7 @@ def dumps(self, data: Any) -> str: return self._post_process_yaml( text, strip_version_directive=strip_version_directive, + strip_explicit_start=not self.explicit_start, ) def _prevent_wrapping_flow_style(self, data: Any) -> None: @@ -1150,7 +1156,12 @@ def _pre_process_yaml(self, text: str) -> tuple[str, str | None]: return text, "".join(preamble_comments) or None @staticmethod - def _post_process_yaml(text: str, *, strip_version_directive: bool = False) -> str: + def _post_process_yaml( + text: str, + *, + strip_version_directive: bool = False, + strip_explicit_start: bool = False, + ) -> str: """Handle known issues with ruamel.yaml dumping. Make sure there's only one newline at the end of the file. @@ -1166,6 +1177,10 @@ def _post_process_yaml(text: str, *, strip_version_directive: bool = False) -> s if strip_version_directive and text.startswith("%YAML"): text = text.split("\n", 1)[1] + # remove explicit document start + if strip_explicit_start and text.startswith("---"): + text = text.split("\n", 1)[1] + text = text.rstrip("\n") + "\n" lines = text.splitlines(keepends=True) diff --git a/test/test_yaml_utils.py b/test/test_yaml_utils.py index 2e1a03acba..6f16c09a81 100644 --- a/test/test_yaml_utils.py +++ b/test/test_yaml_utils.py @@ -1,10 +1,11 @@ """Tests for yaml-related utility functions.""" +# pylint: disable=too-many-lines from __future__ import annotations from io import StringIO from pathlib import Path -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING, Any, cast import pytest from ruamel.yaml.main import YAML @@ -997,3 +998,28 @@ def test_yamllint_incompatible_config() -> None: with (cwd(Path("examples/yamllint/incompatible-config")),): config = ansiblelint.yaml_utils.load_yamllint_config() assert config.incompatible + + +@pytest.mark.parametrize( + ("yaml_version", "explicit_start"), + ( + pytest.param((1, 1), True), + pytest.param((1, 1), False), + ), +) +def test_document_start( + yaml_version: tuple[int, int] | None, + explicit_start: bool, +) -> None: + """Ensure the explicit_start config option from .yamllint is applied correctly.""" + config = ansiblelint.yaml_utils.FormattedYAML.default_config + config["explicit_start"] = explicit_start + + yaml = ansiblelint.yaml_utils.FormattedYAML( + version=yaml_version, + config=cast(dict[str, bool | int | str], config), + ) + assert ( + yaml.dumps(yaml.load(_SINGLE_QUOTE_WITHOUT_INDENTS)).startswith("---") + == explicit_start + )