From ba233a0cbad76b4783a03893e7bf4716fbc0f0ec Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 26 Jun 2024 08:24:58 +0200 Subject: Merging upstream version 24.6.1. Signed-off-by: Daniel Baumann --- src/ansiblelint/rules/key_order.py | 78 ++++++++++++++++++++++++++++++++++---- 1 file changed, 71 insertions(+), 7 deletions(-) (limited to 'src/ansiblelint/rules/key_order.py') diff --git a/src/ansiblelint/rules/key_order.py b/src/ansiblelint/rules/key_order.py index 897da64..0c0a2f1 100644 --- a/src/ansiblelint/rules/key_order.py +++ b/src/ansiblelint/rules/key_order.py @@ -1,14 +1,19 @@ """All tasks should be have name come first.""" + from __future__ import annotations import functools import sys -from typing import TYPE_CHECKING +from dataclasses import dataclass +from typing import TYPE_CHECKING, Any -from ansiblelint.rules import AnsibleLintRule +from ansiblelint.constants import ANNOTATION_KEYS, LINE_NUMBER_KEY +from ansiblelint.errors import MatchError, RuleMatchTransformMeta +from ansiblelint.rules import AnsibleLintRule, TransformMixin if TYPE_CHECKING: - from ansiblelint.errors import MatchError + from ruamel.yaml.comments import CommentedMap, CommentedSeq + from ansiblelint.file_utils import Lintable from ansiblelint.utils import Task @@ -46,7 +51,21 @@ def task_property_sorter(property1: str, property2: str) -> int: return (v_1 > v_2) - (v_1 < v_2) -class KeyOrderRule(AnsibleLintRule): +@dataclass(frozen=True) +class KeyOrderTMeta(RuleMatchTransformMeta): + """Key Order transform metadata. + + :param fixed: tuple with updated key order + """ + + fixed: tuple[str | int, ...] + + def __str__(self) -> str: + """Return string representation.""" + return f"Fixed to {self.fixed}" + + +class KeyOrderRule(AnsibleLintRule, TransformMixin): """Ensure specific order of keys in mappings.""" id = "key-order" @@ -59,6 +78,25 @@ class KeyOrderRule(AnsibleLintRule): "key-order[task]": "You can improve the task key order", } + def matchplay(self, file: Lintable, data: dict[str, Any]) -> list[MatchError]: + """Return matches found for a specific play (entry in playbook).""" + result: list[MatchError] = [] + if file.kind != "playbook": + return result + keys = [str(key) for key, val in data.items() if key not in ANNOTATION_KEYS] + sorted_keys = sorted(keys, key=functools.cmp_to_key(task_property_sorter)) + if keys != sorted_keys: + result.append( + self.create_matcherror( + f"You can improve the play key order to: {', '.join(sorted_keys)}", + filename=file, + tag=f"{self.id}[play]", + lineno=data[LINE_NUMBER_KEY], + transform_meta=KeyOrderTMeta(fixed=tuple(sorted_keys)), + ), + ) + return result + def matchtask( self, task: Task, @@ -66,7 +104,7 @@ class KeyOrderRule(AnsibleLintRule): ) -> list[MatchError]: result = [] raw_task = task["__raw_task__"] - keys = [key for key in raw_task if not key.startswith("_")] + keys = [str(key) for key in raw_task if not key.startswith("_")] sorted_keys = sorted(keys, key=functools.cmp_to_key(task_property_sorter)) if keys != sorted_keys: result.append( @@ -74,17 +112,43 @@ class KeyOrderRule(AnsibleLintRule): f"You can improve the task key order to: {', '.join(sorted_keys)}", filename=file, tag="key-order[task]", + transform_meta=KeyOrderTMeta(fixed=tuple(sorted_keys)), ), ) return result + def transform( + self, + match: MatchError, + lintable: Lintable, + data: CommentedMap | CommentedSeq | str, + ) -> None: + if not isinstance(match.transform_meta, KeyOrderTMeta): + return + + if match.tag == f"{self.id}[play]": + play = self.seek(match.yaml_path, data) + for key in match.transform_meta.fixed: + # other transformation might change the key + if key in play: + play[key] = play.pop(key) + match.fixed = True + if match.tag == f"{self.id}[task]": + task = self.seek(match.yaml_path, data) + for key in match.transform_meta.fixed: + # other transformation might change the key + if key in task: + task[key] = task.pop(key) + match.fixed = True + # testing code to be loaded only with pytest or when executed the rule file if "pytest" in sys.modules: import pytest - from ansiblelint.rules import RulesCollection # pylint: disable=ungrouped-imports - from ansiblelint.runner import Runner # pylint: disable=ungrouped-imports + # pylint: disable=ungrouped-imports + from ansiblelint.rules import RulesCollection + from ansiblelint.runner import Runner @pytest.mark.parametrize( ("test_file", "failures"), -- cgit v1.2.3