From 6120764547e9226d7d4cf0e55867333c03299dff Mon Sep 17 00:00:00 2001 From: Sorin Sbarnea Date: Thu, 18 Feb 2021 09:56:12 +0000 Subject: [PATCH] Avoid same false positives with no-tabs rule As we identified some particular cases where use of tabs is perfectly justified, we add exceptions for these. Fixes: #1334 --- examples/playbooks/rule-no-tabs.yml | 10 +++++ .../rules/ComparisonToLiteralBoolRule.py | 2 +- src/ansiblelint/rules/NoTabsRule.py | 38 ++++++++++++++++++- .../rules/VariableHasSpacesRule.py | 2 +- src/ansiblelint/utils.py | 16 ++++---- test/TestUtils.py | 12 +++--- 6 files changed, 62 insertions(+), 18 deletions(-) create mode 100644 examples/playbooks/rule-no-tabs.yml diff --git a/examples/playbooks/rule-no-tabs.yml b/examples/playbooks/rule-no-tabs.yml new file mode 100644 index 0000000000..7612bc1f5f --- /dev/null +++ b/examples/playbooks/rule-no-tabs.yml @@ -0,0 +1,10 @@ +- hosts: localhost + tasks: + - name: should not trigger no-tabs rules + lineinfile: + path: some.txt + regexp: '^\t$' + line: 'string with \t inside' + - name: foo + debug: + msg: "Presence of \t should trigger no-tabs here." diff --git a/src/ansiblelint/rules/ComparisonToLiteralBoolRule.py b/src/ansiblelint/rules/ComparisonToLiteralBoolRule.py index 600ac6a7ff..840205800e 100644 --- a/src/ansiblelint/rules/ComparisonToLiteralBoolRule.py +++ b/src/ansiblelint/rules/ComparisonToLiteralBoolRule.py @@ -22,7 +22,7 @@ class ComparisonToLiteralBoolRule(AnsibleLintRule): literal_bool_compare = re.compile("[=!]= ?(True|true|False|false)") def matchtask(self, task: Dict[str, Any]) -> Union[bool, str]: - for k, v in nested_items(task): + for k, v, _ in nested_items(task): if k == 'when': if isinstance(v, str): if self.literal_bool_compare.search(v): diff --git a/src/ansiblelint/rules/NoTabsRule.py b/src/ansiblelint/rules/NoTabsRule.py index a44fc142a9..df6c684f53 100644 --- a/src/ansiblelint/rules/NoTabsRule.py +++ b/src/ansiblelint/rules/NoTabsRule.py @@ -1,5 +1,6 @@ # Copyright (c) 2016, Will Thames and contributors # Copyright (c) 2018, Ansible Project +import sys from typing import Any, Dict, Union from ansiblelint.rules import AnsibleLintRule @@ -13,11 +14,44 @@ class NoTabsRule(AnsibleLintRule): severity = 'LOW' tags = ['formatting'] version_added = 'v4.0.0' + allow_list = [ + ("lineinfile", "insertafter"), + ("lineinfile", "insertbefore"), + ("lineinfile", "regexp"), + ("lineinfile", "line"), + ] def matchtask(self, task: Dict[str, Any]) -> Union[bool, str]: - for k, v in nested_items(task): + for k, v, parent in nested_items(task): if isinstance(k, str) and '\t' in k: return True - if isinstance(v, str) and '\t' in v: + if (parent, k) not in self.allow_list and isinstance(v, str) and '\t' in v: return True return False + + +RULE_EXAMPLE = r'''--- +- hosts: localhost + tasks: + - name: should not trigger no-tabs rules + lineinfile: + path: some.txt + regexp: '^\t$' + line: 'string with \t inside' + - name: foo + debug: + msg: "Presence of \t should trigger no-tabs here." +''' + +# testing code to be loaded only with pytest or when executed the rule file +if "pytest" in sys.modules: + + import pytest + + @pytest.mark.parametrize('rule_runner', (NoTabsRule,), indirect=['rule_runner']) + def test_no_tabs_rule(rule_runner: "Any") -> None: + """Test rule matches.""" + results = rule_runner.run_playbook(RULE_EXAMPLE) + assert results[0].linenumber == 9 + assert results[0].message == NoTabsRule.shortdesc + assert len(results) == 1 diff --git a/src/ansiblelint/rules/VariableHasSpacesRule.py b/src/ansiblelint/rules/VariableHasSpacesRule.py index 75d12270f0..f424bd0b58 100644 --- a/src/ansiblelint/rules/VariableHasSpacesRule.py +++ b/src/ansiblelint/rules/VariableHasSpacesRule.py @@ -22,7 +22,7 @@ class VariableHasSpacesRule(AnsibleLintRule): exclude_json_re = re.compile(r"[^{]{'\w+': ?[^{]{.*?}}") def matchtask(self, task: Dict[str, Any]) -> Union[bool, str]: - for k, v in nested_items(task): + for k, v, _ in nested_items(task): if isinstance(v, str): cleaned = self.exclude_json_re.sub("", v) if bool(self.bracket_regex.search(cleaned)): diff --git a/src/ansiblelint/utils.py b/src/ansiblelint/utils.py index 137c881c78..2f5184fd41 100644 --- a/src/ansiblelint/utils.py +++ b/src/ansiblelint/utils.py @@ -855,16 +855,16 @@ def convert_to_boolean(value: Any) -> bool: def nested_items( - data: Union[Dict[Any, Any], List[Any]] -) -> Generator[Tuple[Any, Any], None, None]: + data: Union[Dict[Any, Any], List[Any]], parent: str = "" +) -> Generator[Tuple[Any, Any, str], None, None]: """Iterate a nested data structure.""" if isinstance(data, dict): for k, v in data.items(): - yield k, v - for k, v in nested_items(v): - yield k, v + yield k, v, parent + for k, v, p in nested_items(v, k): + yield k, v, p if isinstance(data, list): for item in data: - yield "list-item", item - for k, v in nested_items(item): - yield k, v + yield "list-item", item, parent + for k, v, p in nested_items(item): + yield k, v, p diff --git a/test/TestUtils.py b/test/TestUtils.py index 95d753290d..2d1951fb0b 100644 --- a/test/TestUtils.py +++ b/test/TestUtils.py @@ -435,11 +435,11 @@ def test_nested_items(): data = {"foo": "text", "bar": {"some": "text2"}, "fruits": ["apple", "orange"]} items = [ - ("foo", "text"), - ("bar", {"some": "text2"}), - ("some", "text2"), - ("fruits", ["apple", "orange"]), - ("list-item", "apple"), - ("list-item", "orange"), + ("foo", "text", ""), + ("bar", {"some": "text2"}, ""), + ("some", "text2", "bar"), + ("fruits", ["apple", "orange"], ""), + ("list-item", "apple", "fruits"), + ("list-item", "orange", "fruits"), ] assert list(utils.nested_items(data)) == items