summaryrefslogtreecommitdiffstats
path: root/src/ansiblelint/rules/key_order.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/ansiblelint/rules/key_order.py')
-rw-r--r--src/ansiblelint/rules/key_order.py151
1 files changed, 151 insertions, 0 deletions
diff --git a/src/ansiblelint/rules/key_order.py b/src/ansiblelint/rules/key_order.py
new file mode 100644
index 0000000..897da64
--- /dev/null
+++ b/src/ansiblelint/rules/key_order.py
@@ -0,0 +1,151 @@
+"""All tasks should be have name come first."""
+from __future__ import annotations
+
+import functools
+import sys
+from typing import TYPE_CHECKING
+
+from ansiblelint.rules import AnsibleLintRule
+
+if TYPE_CHECKING:
+ from ansiblelint.errors import MatchError
+ from ansiblelint.file_utils import Lintable
+ from ansiblelint.utils import Task
+
+
+SORTER_TASKS = (
+ "name",
+ # "__module__",
+ # "action",
+ # "args",
+ None, # <-- None include all modules that not using action and *
+ # "when",
+ # "notify",
+ # "tags",
+ "block",
+ "rescue",
+ "always",
+)
+
+
+def get_property_sort_index(name: str) -> int:
+ """Return the index of the property in the sorter."""
+ a_index = -1
+ for i, v in enumerate(SORTER_TASKS):
+ if v == name:
+ return i
+ if v is None:
+ a_index = i
+ return a_index
+
+
+def task_property_sorter(property1: str, property2: str) -> int:
+ """Sort task properties based on SORTER."""
+ v_1 = get_property_sort_index(property1)
+ v_2 = get_property_sort_index(property2)
+ return (v_1 > v_2) - (v_1 < v_2)
+
+
+class KeyOrderRule(AnsibleLintRule):
+ """Ensure specific order of keys in mappings."""
+
+ id = "key-order"
+ shortdesc = __doc__
+ severity = "LOW"
+ tags = ["formatting"]
+ version_added = "v6.6.2"
+ needs_raw_task = True
+ _ids = {
+ "key-order[task]": "You can improve the task key order",
+ }
+
+ def matchtask(
+ self,
+ task: Task,
+ file: Lintable | None = None,
+ ) -> list[MatchError]:
+ result = []
+ raw_task = task["__raw_task__"]
+ keys = [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(
+ self.create_matcherror(
+ f"You can improve the task key order to: {', '.join(sorted_keys)}",
+ filename=file,
+ tag="key-order[task]",
+ ),
+ )
+ return result
+
+
+# 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
+
+ @pytest.mark.parametrize(
+ ("test_file", "failures"),
+ (
+ pytest.param("examples/playbooks/rule-key-order-pass.yml", 0, id="pass"),
+ pytest.param("examples/playbooks/rule-key-order-fail.yml", 6, id="fail"),
+ ),
+ )
+ def test_key_order_rule(
+ default_rules_collection: RulesCollection,
+ test_file: str,
+ failures: int,
+ ) -> None:
+ """Test rule matches."""
+ results = Runner(test_file, rules=default_rules_collection).run()
+ assert len(results) == failures
+ for result in results:
+ assert result.rule.id == "key-order"
+
+ @pytest.mark.parametrize(
+ ("properties", "expected"),
+ (
+ pytest.param([], []),
+ pytest.param(["block", "name"], ["name", "block"]),
+ pytest.param(
+ ["block", "name", "action", "..."],
+ ["name", "action", "...", "block"],
+ ),
+ ),
+ )
+ def test_key_order_property_sorter(
+ properties: list[str],
+ expected: list[str],
+ ) -> None:
+ """Test the task property sorter."""
+ result = sorted(properties, key=functools.cmp_to_key(task_property_sorter))
+ assert expected == result
+
+ @pytest.mark.parametrize(
+ ("key", "order"),
+ (
+ pytest.param("name", 0),
+ pytest.param("action", 1),
+ pytest.param("foobar", SORTER_TASKS.index(None)),
+ pytest.param("block", len(SORTER_TASKS) - 3),
+ pytest.param("rescue", len(SORTER_TASKS) - 2),
+ pytest.param("always", len(SORTER_TASKS) - 1),
+ ),
+ )
+ def test_key_order_property_sort_index(key: str, order: int) -> None:
+ """Test sorting index."""
+ assert get_property_sort_index(key) == order
+
+ @pytest.mark.parametrize(
+ ("prop1", "prop2", "result"),
+ (
+ pytest.param("name", "block", -1),
+ pytest.param("block", "name", 1),
+ pytest.param("block", "block", 0),
+ ),
+ )
+ def test_key_order_property_sortfunc(prop1: str, prop2: str, result: int) -> None:
+ """Test sorting function."""
+ assert task_property_sorter(prop1, prop2) == result