Repository: MarketSquare/robotframework-robocop
Pull request #1033 (2023-12-29 14:08:18)

Separate deprecated rules when listing rules

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/MarketSquare/robotframework-robocop/pull/1033?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=MarketSquare) Report
Attention: `2 lines` in your changes are missing coverage. Please review.
> Comparison is base [(`9bd67cc`)](https://app.codecov.io/gh/MarketSquare/robotframework-robocop/commit/9bd67cc91cf28c8a90f21eea122558eddd0a4fb6?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=MarketSquare) 96.95% compared to head [(`5bd1125`)](https://app.codecov.io/gh/MarketSquare/robotframework-robocop/pull/1033?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=MarketSquare) 96.13%.

| [Files](https://app.codecov.io/gh/MarketSquare/robotframework-robocop/pull/1033?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=MarketSquare) | Patch % | Lines |
|---|---|---|
| [robocop/config.py](https://app.codecov.io/gh/MarketSquare/robotframework-robocop/pull/1033?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=MarketSquare#diff-cm9ib2NvcC9jb25maWcucHk=) | 0.00% | [1 Missing :warning: ](https://app.codecov.io/gh/MarketSquare/robotframework-robocop/pull/1033?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=MarketSquare) |
| [robocop/rules.py](https://app.codecov.io/gh/MarketSquare/robotframework-robocop/pull/1033?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=MarketSquare#diff-cm9ib2NvcC9ydWxlcy5weQ==) | 95.65% | [1 Missing :warning: ](https://app.codecov.io/gh/MarketSquare/robotframework-robocop/pull/1033?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=MarketSquare) |

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##           master    #1033      +/-   ##
==========================================
- Coverage   96.95%   96.13%   -0.83%     
==========================================
  Files          37       37              
  Lines        4602     4608       +6     
==========================================
- Hits         4462     4430      -32     
- Misses        140      178      +38     
```



</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/MarketSquare/robotframework-robocop/pull/1033?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=MarketSquare).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=MarketSquare).


Patch:
diff --git a/docs/releasenotes/unreleased/other.3.rst b/docs/releasenotes/unreleased/other.3.rst
index 79083b0..75f23d2 100644
--- a/docs/releasenotes/unreleased/other.3.rst
+++ b/docs/releasenotes/unreleased/other.3.rst
@@ -13,3 +13,7 @@ Deprecated rule implementation can be removed and only rule definition can stay.
 ``--exclude`` or ``--configure`` warning will be printed::
 
     Rule W1102 deprecated is deprecated. Remove it from your configuration.
+
+Deprecated rules can be now listed with ``DEPRECATED`` filter::
+
+    robocop --list DEPRECATED
diff --git a/robocop/config.py b/robocop/config.py
index f726e8c..2ea420e 100644
--- a/robocop/config.py
+++ b/robocop/config.py
@@ -54,7 +54,7 @@ class SetRuleThreshold(argparse.Action):
 
 class SetListOption(argparse.Action):
     def __call__(self, parser, namespace, values, option_string=None):
-        pattern = values if values else RuleFilter.EMPTY_PATTERN
+        pattern = values if values else RuleFilter.DEFAULT
         if "*" in pattern:
             pattern = translate_pattern(pattern)
         setattr(namespace, self.dest, pattern)
diff --git a/robocop/rules.py b/robocop/rules.py
index 435c3f6..f8f5cf5 100644
--- a/robocop/rules.py
+++ b/robocop/rules.py
@@ -97,27 +97,34 @@ class RuleSeverity(Enum):
 
 
 class RuleFilter:
-    EMPTY_PATTERN = "EMPTY_PATTERN"
-    ALL_PATTERN = "ALL"
-    ENABLED_PATTERN = "ENABLED"
-    DISABLED_PATTERN = "DISABLED"
-    COMMUNITY_PATTERN = "COMMUNITY"
+    DEFAULT = "DEFAULT"
+    ALL = "ALL"
+    ENABLED = "ENABLED"
+    DISABLED = "DISABLED"
+    COMMUNITY = "COMMUNITY"
+    DEPRECATED = "DEPRECATED"
 
     def get_filtered_rules(self, rules, pattern):
-        if pattern in (self.ENABLED_PATTERN, self.DISABLED_PATTERN):
+        if pattern in (self.ENABLED, self.DISABLED):
             rule_by_id = {
                 rule.rule_id: rule
                 for rule in rules.values()
-                if pattern.lower() in rule.get_enabled_status_desc() and not rule.community_rule
+                if pattern.lower() in rule.get_enabled_status_desc() and not rule.community_rule and not rule.deprecated
             }
-        elif pattern == self.COMMUNITY_PATTERN:
-            rule_by_id = {rule.rule_id: rule for rule in rules.values() if rule.community_rule}
-        elif pattern == self.EMPTY_PATTERN:
-            rule_by_id = {rule.rule_id: rule for rule in rules.values() if not rule.community_rule}
-        elif pattern == self.ALL_PATTERN:
-            rule_by_id = {rule.rule_id: rule for rule in rules.values()}
+        elif pattern == self.COMMUNITY:
+            rule_by_id = {rule.rule_id: rule for rule in rules.values() if rule.community_rule and not rule.deprecated}
+        elif pattern == self.DEFAULT:
+            rule_by_id = {
+                rule.rule_id: rule for rule in rules.values() if not rule.community_rule and not rule.deprecated
+            }
+        elif pattern == self.ALL:
+            rule_by_id = {rule.rule_id: rule for rule in rules.values() if not rule.deprecated}
+        elif pattern == self.DEPRECATED:
+            rule_by_id = {rule.rule_id: rule for rule in rules.values() if rule.deprecated}
         else:
-            rule_by_id = {rule.rule_id: rule for rule in rules.values() if rule.matches_pattern(pattern)}
+            rule_by_id = {
+                rule.rule_id: rule for rule in rules.values() if rule.matches_pattern(pattern) and not rule.deprecated
+            }
         return sorted(rule_by_id.values(), key=lambda x: int(x.rule_id))
 
 
@@ -391,10 +398,13 @@ class Rule:
         return f"Rule - {self.rule_id} [{self.severity}]: {self.name}: {self.msg} ({self.get_enabled_status_desc()})"
 
     def get_enabled_status_desc(self):
-        s = "enabled" if self.enabled else "disabled"
-        if not self.enabled and self.supported_version != "All":
-            s += f" - supported only for RF version {self.supported_version}"
-        return s
+        if self.deprecated:
+            return "deprecated"
+        if self.enabled:
+            return "enabled"
+        if self.supported_version != "All":
+            return f"disabled - supported only for RF version {self.supported_version}"
+        return "disabled"
 
     def configure(self, param, value):
         if param not in self.config:


Test patch:
diff --git a/tests/utest/test_listing_rules.py b/tests/utest/test_listing_rules.py
index 6c920e6..18ea9cf 100644
--- a/tests/utest/test_listing_rules.py
+++ b/tests/utest/test_listing_rules.py
@@ -98,6 +98,23 @@ def msg_disabled_for_4():
     }
 
 
+@pytest.fixture
+def deprecated_rule():
+    return {
+        "9991": Rule(
+            rule_id="9991", name="deprecated-rule", msg="Deprecated rule", severity=RuleSeverity.ERROR, deprecated=True
+        ),
+        "9992": Rule(
+            rule_id="9992",
+            name="deprecated-disabled-rule",
+            msg="Deprecated and disabled rule",
+            severity=RuleSeverity.INFO,
+            deprecated=True,
+            enabled=False,
+        ),
+    }
+
+
 def init_empty_checker(robocop_instance_pre_load, rule, exclude=False, **kwargs):
     checker = EmptyChecker()
     checker.rules = rule
@@ -111,10 +128,11 @@ def init_empty_checker(robocop_instance_pre_load, rule, exclude=False, **kwargs)
 
 
 class TestListingRules:
-    def test_list_rule(self, robocop_pre_load, msg_0101, community_rule, capsys):
-        robocop_pre_load.config.list = RuleFilter.EMPTY_PATTERN
+    def test_list_rule(self, robocop_pre_load, msg_0101, community_rule, deprecated_rule, capsys):
+        robocop_pre_load.config.list = RuleFilter.DEFAULT
         init_empty_checker(robocop_pre_load, msg_0101)
         init_empty_checker(robocop_pre_load, community_rule)
+        init_empty_checker(robocop_pre_load, deprecated_rule)
         with pytest.raises(SystemExit):
             robocop_pre_load.list_checkers()
         out, _ = capsys.readouterr()
@@ -149,7 +167,7 @@ class TestListingRules:
         )
 
     def test_list_filter_enabled(self, robocop_pre_load, msg_0101, msg_0102_0204, capsys):
-        robocop_pre_load.config.list = "ENABLED"
+        robocop_pre_load.config.list = RuleFilter.ENABLED
         init_empty_checker(robocop_pre_load, msg_0101)
         init_empty_checker(robocop_pre_load, msg_0102_0204, exclude=True)
         with pytest.raises(SystemExit):
@@ -164,9 +182,10 @@ class TestListingRules:
             "Visit https://robocop.readthedocs.io/en/stable/rules_list.html page for detailed documentation.\n"
         )
 
-    def test_list_filter_disabled(self, robocop_pre_load, msg_0101, msg_0102_0204, capsys):
-        robocop_pre_load.config.list = "DISABLED"
+    def test_list_filter_disabled(self, robocop_pre_load, msg_0101, msg_0102_0204, deprecated_rule, capsys):
+        robocop_pre_load.config.list = RuleFilter.DISABLED
         init_empty_checker(robocop_pre_load, msg_0101)
+        init_empty_checker(robocop_pre_load, deprecated_rule)
         init_empty_checker(robocop_pre_load, msg_0102_0204, exclude=True)
         with pytest.raises(SystemExit):
             robocop_pre_load.list_checkers()
@@ -181,6 +200,24 @@ class TestListingRules:
             "Visit https://robocop.readthedocs.io/en/stable/rules_list.html page for detailed documentation.\n"
         )
 
+    def test_list_filter_deprecated(self, robocop_pre_load, msg_0101, msg_0102_0204, deprecated_rule, capsys):
+        robocop_pre_load.config.list = RuleFilter.DEPRECATED
+        init_empty_checker(robocop_pre_load, msg_0101)
+        init_empty_checker(robocop_pre_load, deprecated_rule)
+        init_empty_checker(robocop_pre_load, msg_0102_0204, exclude=True)
+        with pytest.raises(SystemExit):
+            robocop_pre_load.list_checkers()
+        out, _ = capsys.readouterr()
+        assert (
+            out == "Rule - 9991 [E]: deprecated-rule: Deprecated rule (deprecated)\n"
+            "Rule - 9992 [I]: deprecated-disabled-rule: Deprecated and disabled rule (deprecated)\n\n"
+            "Altogether 2 rules with following severity:\n"
+            "    1 error rule,\n"
+            "    0 warning rules,\n"
+            "    1 info rule.\n\n"
+            "Visit https://robocop.readthedocs.io/en/stable/rules_list.html page for detailed documentation.\n"
+        )
+
     def test_list_reports(self, robocop_pre_load, msg_0101, capsys):
         robocop_pre_load.config.list_reports = True
         init_empty_checker(robocop_pre_load, msg_0101)
@@ -204,10 +241,11 @@ class TestListingRules:
         )
         assert all(msg in out for msg in exp_msg)
 
-    def test_list_filtered(self, robocop_pre_load, msg_0101, msg_0102_0204, capsys):
+    def test_list_filtered(self, robocop_pre_load, msg_0101, msg_0102_0204, deprecated_rule, capsys):
         robocop_pre_load.config.list = robocop.config.translate_pattern("01*")
         init_empty_checker(robocop_pre_load, msg_0102_0204, exclude=True)
         init_empty_checker(robocop_pre_load, msg_0101)
+        init_empty_checker(robocop_pre_load, deprecated_rule)
         with pytest.raises(SystemExit):
             robocop_pre_load.list_checkers()
         out, _ = capsys.readouterr()