Repository: MarketSquare/robotframework-robocop (python)

Issue:
List only enabled rules
As rule list is quite long already and growing I'd like a way of listing only the rules I have enabled.

It is quite impossible to spot only the enabled rules from all the all rules.


Pull request description:
Filter --list/--list-configurables by status
Closes #764 

PR based on #774

Patch:
diff --git a/docs/cli_options.rst b/docs/cli_options.rst
index c8c3904..55ed243 100644
--- a/docs/cli_options.rst
+++ b/docs/cli_options.rst
@@ -50,9 +50,13 @@ and you will be shown with the following output:
                             -c line-too-long:line_length:150
                             --configure 0101:severity:E
       -l [PATTERN], --list [PATTERN]
-                            List all available rules. You can use optional PATTERN argument.
+                            List all available rules. You can use optional PATTERN argument to match rule names
+                            (for example --list *doc*).
+                            PATTERN can be also ENABLED/DISABLED keyword to list only enabled/disabled rules.
       -lc [PATTERN], --list-configurables [PATTERN]
-                            List all available rules with configurable parameters. You can use optional PATTERN argument.
+                            List all available rules with configurable parameters.
+                            You can use optional PATTERN argument to match rule names (for example --list *doc*).
+                            PATTERN can be also ENABLED/DISABLED keyword to list only enabled/disabled rules.
       -lr, --list-reports   List all available reports.
       -o PATH, --output PATH
                             Path to output file.
diff --git a/docs/releasenotes/2.8.0.rst b/docs/releasenotes/2.8.0.rst
index bef8754..eb8c3c4 100644
--- a/docs/releasenotes/2.8.0.rst
+++ b/docs/releasenotes/2.8.0.rst
@@ -33,6 +33,25 @@ It means that argument file can have paths relative to the argument file locatio
     --output robocop_output.txt # it will now refer to config/robocop_output.txt
     --ext-rules robocop/rules.py # it will now refer to config/robocop/rules.py
 
+List only enabled or disabled rules #764
+------------------------------------------
+
+Robocop can list all rules with ``--list`` option (or ``--list-configurables`` to only list rules that can be
+configured). Our every growing list of rules made the output of this command hard to read. That's why it is now
+possible to filter rules with ``ENABLED`` or ``DISABLED`` special patterns::
+
+    robocop --list ENABLED
+    robocop -i *doc* -i line-too-long --list ENABLED
+    robocop --exclude empty-lines-between-test-cases --list DISABLED
+    robocop --list-configurables ENABLED
+
+The new feature still coexist with existing feature that allows to filter list output using pattern::
+
+    robocop --list *line*
+
+However ``ENABLED``/``DISABLED`` patterns are more useful when you want to list only the rules that are enabled using
+your configuration.
+
 Bugs
 -----
 
diff --git a/docs/user_guide.rst b/docs/user_guide.rst
index 2594154..787fdf2 100644
--- a/docs/user_guide.rst
+++ b/docs/user_guide.rst
@@ -104,7 +104,7 @@ To get list of available rules (with enabled/disabled status) use ``-l / --list`
 
 ..  code-block:: none
 
-    robocop --list
+    > robocop --list
     Rule - 0201 [W]: missing-doc-keyword: Missing documentation in '{{ name }}' keyword (enabled)
     Rule - 0202 [W]: missing-doc-test-case: Missing documentation in '{{ name }}' test case (enabled)
     Rule - 0203 [W]: missing-doc-suite: Missing documentation in suite (enabled)
@@ -114,7 +114,7 @@ If some of the rules are disabled from CLI it will be reflected in the output:
 
 ..  code-block:: none
 
-    robocop --exclude 02* --list
+    > robocop --exclude 02* --list
     Rule - 0201 [W]: missing-doc-keyword: Missing documentation in '{{ name }}' keyword (disabled)
     Rule - 0202 [W]: missing-doc-test-case: Missing documentation in '{{ name }}' test case (disabled)
     Rule - 0203 [W]: missing-doc-suite: Missing documentation in suite (disabled)
@@ -125,7 +125,7 @@ Rules list can be filtered out by glob pattern:
 
 ..  code-block:: none
 
-    robocop --list tag*
+    > robocop --list tag*
     Rule - 0601 [W]: tag-with-space: Tag '{{ tag }}' should not contain spaces (enabled)
     Rule - 0602 [I]: tag-with-or-and: Tag '{{ tag }}' with reserved word OR/AND. Hint: make sure to include this tag using lowercase name to avoid issues (enabled)
     Rule - 0603 [W]: tag-with-reserved-word: Tag '{{ tag }}' prefixed with reserved word `robot:` (enabled)
@@ -142,6 +142,23 @@ Use ``-lc \ --list-configurables`` argument to list rules together with availabl
                 type: int
                 info: number of empty lines required between sections
 
+
+To list only enabled or disabled rules:
+
+..  code-block:: none
+
+    > robocop -i tag-with* --list ENABLED
+    Rule - 0601 [W]: tag-with-space: Tag '{{ tag }}' should not contain spaces (enabled)
+    Rule - 0602 [I]: tag-with-or-and: Tag '{{ tag }}' with reserved word OR/AND. Hint: make sure to include this tag using lowercase name to avoid issues (enabled)
+    Rule - 0603 [W]: tag-with-reserved-word: Tag '{{ tag }}' prefixed with reserved word `robot:` (enabled)
+
+    > robocop -e inconsistent-assignment-in-variables --list-configurables DISABLED
+    Rule - 0910 [W]: inconsistent-assignment-in-variables: The assignment sign is not consistent inside the variables section. Expected '{{ expected_sign }}' but got '{{ actual_sign }}' instead (disabled)
+        assignment_sign_type = autodetect
+            type: parse_assignment_sign_type
+            info: possible values: 'autodetect' (default), 'none' (''), 'equal_sign' ('=') or space_and_equal_sign (' =')
+
+
 Ignoring file
 -------------
 Path matching glob pattern can be ignored (or *skipped* during scan). You can pass list of patterns::
diff --git a/robocop/config.py b/robocop/config.py
index 0dc497e..5044f51 100644
--- a/robocop/config.py
+++ b/robocop/config.py
@@ -310,7 +310,9 @@ class Config:
             const="",
             default=self.list,
             metavar="PATTERN",
-            help="List all available rules. You can use optional PATTERN argument.",
+            help="List all available rules. You can use optional PATTERN argument to match rule names "
+            "(for example --list *doc*). "
+            "PATTERN can be also ENABLED/DISABLED keyword to list only enabled/disabled rules.",
         )
         optional.add_argument(
             "-lc",
@@ -320,7 +322,9 @@ class Config:
             const="",
             default=self.list_configurables,
             metavar="PATTERN",
-            help="List all available rules with configurable parameters. You can use optional PATTERN argument.",
+            help="List all available rules with configurable parameters. You can use optional PATTERN argument "
+            "to match rule names (for example --list *doc*). "
+            "PATTERN can be also ENABLED/DISABLED keyword to list only enabled/disabled rules.",
         )
         optional.add_argument(
             "-lr",
diff --git a/robocop/run.py b/robocop/run.py
index 408ad88..2a4aa06 100644
--- a/robocop/run.py
+++ b/robocop/run.py
@@ -177,7 +177,12 @@ class Robocop:
                 "\n    E / error\n    W / warning\n    I / info\n"
             )
         pattern = self.config.list if self.config.list else self.config.list_configurables
-        rule_by_id = {rule.rule_id: rule for rule in self.rules.values() if rule.matches_pattern(pattern)}
+        if pattern in ("ENABLED", "DISABLED"):
+            rule_by_id = {
+                rule.rule_id: rule for rule in self.rules.values() if pattern.lower() in rule.get_enabled_status_desc()
+            }
+        else:
+            rule_by_id = {rule.rule_id: rule for rule in self.rules.values() if rule.matches_pattern(pattern)}
         rule_by_id = sorted(rule_by_id.values(), key=lambda x: x.rule_id)
         severity_counter = Counter({"E": 0, "W": 0, "I": 0})
         for rule in rule_by_id:


Test patch:
diff --git a/tests/utest/test_listing_rules.py b/tests/utest/test_listing_rules.py
index 827f299..2041d2f 100644
--- a/tests/utest/test_listing_rules.py
+++ b/tests/utest/test_listing_rules.py
@@ -1,9 +1,9 @@
 import pytest
 
 import robocop.config
-from robocop.utils import ROBOT_VERSION
 from robocop.checkers import VisitorChecker
 from robocop.rules import Rule, RuleParam, RuleSeverity
+from robocop.utils import ROBOT_VERSION
 
 
 class EmptyChecker(VisitorChecker):
@@ -135,6 +135,39 @@ class TestListingRules:
             "Visit https://robocop.readthedocs.io/en/stable/rules.html page for detailed documentation.\n"
         )
 
+    def test_list_filter_enabled(self, robocop_pre_load, msg_0101, msg_0102_0204, capsys):
+        robocop_pre_load.config.list = "ENABLED"
+        init_empty_checker(robocop_pre_load, msg_0101)
+        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 - 0101 [W]: some-message: Some description (enabled)\n\n"
+            "Altogether 1 rule with following severity:\n"
+            "    0 error rules,\n"
+            "    1 warning rule,\n"
+            "    0 info rules.\n\n"
+            "Visit https://robocop.readthedocs.io/en/stable/rules.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"
+        init_empty_checker(robocop_pre_load, msg_0101)
+        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 - 0102 [E]: other-message: this is description (disabled)\n"
+            "Rule - 0204 [I]: another-message: Message with meaning 4 (disabled)\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.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)