diff --git a/robocop/checkers/comments.py b/robocop/checkers/comments.py index 8d999829..9db7da3d 100644 --- a/robocop/checkers/comments.py +++ b/robocop/checkers/comments.py @@ -7,7 +7,7 @@ from robot.utils import FileReader from robocop.checkers import RawFileChecker, VisitorChecker -from robocop.rules import Rule, RuleParam, RuleSeverity +from robocop.rules import DefaultRule, RuleParam, RuleSeverity from robocop.utils import ROBOT_VERSION @@ -22,7 +22,7 @@ def regex(value): RULE_CATEGORY_ID = "07" rules = { - "0701": Rule( + "0701": DefaultRule( RuleParam( name="markers", default="todo,fixme", @@ -49,7 +49,7 @@ def regex(value): """, added_in_version="1.0.0", ), - "0702": Rule( + "0702": DefaultRule( RuleParam( name="block", default="^###", @@ -87,7 +87,7 @@ def regex(value): """, added_in_version="1.0.0", ), - "0703": Rule( + "0703": DefaultRule( rule_id="0703", name="invalid-comment", msg="Invalid comment. '#' needs to be first character in the cell. " @@ -107,7 +107,7 @@ def regex(value): """, added_in_version="1.0.0", ), - "0704": Rule( + "0704": DefaultRule( rule_id="0704", name="ignored-data", msg="Ignored data found in file", @@ -136,7 +136,7 @@ def regex(value): """, added_in_version="1.3.0", ), - "0705": Rule( + "0705": DefaultRule( rule_id="0705", name="bom-encoding-in-file", msg="This file contains BOM (Byte Order Mark) encoding not supported by Robot Framework", diff --git a/robocop/checkers/community_rules/keywords.py b/robocop/checkers/community_rules/keywords.py index ee0c69bb..38699d5b 100644 --- a/robocop/checkers/community_rules/keywords.py +++ b/robocop/checkers/community_rules/keywords.py @@ -3,7 +3,7 @@ from robot.utils.robottime import timestr_to_secs from robocop.checkers import VisitorChecker -from robocop.rules import Rule, RuleParam, RuleSeverity +from robocop.rules import CommunityRule, RuleParam, RuleSeverity from robocop.utils.misc import normalize_robot_name from robocop.utils.run_keywords import iterate_keyword_names @@ -17,7 +17,7 @@ def comma_separated_list(value: str) -> set[str]: rules = { - "10001": Rule( + "10001": CommunityRule( RuleParam( name="max_time", default=0, converter=timestr_to_secs, desc="Maximum amount of time allowed in Sleep" ), @@ -53,7 +53,7 @@ def comma_separated_list(value: str) -> set[str]: """, ), - "10002": Rule( + "10002": CommunityRule( RuleParam( name="keywords", default=None, @@ -87,7 +87,7 @@ def comma_separated_list(value: str) -> set[str]: """, ), - "10003": Rule( + "10003": CommunityRule( rule_id="10003", name="no-embedded-keyword-arguments", msg="Not allowed embedded arguments {{ arguments }} found in keyword '{{ keyword }}'", diff --git a/robocop/checkers/community_rules/misc.py b/robocop/checkers/community_rules/misc.py index a824cbaf..9e003ace 100644 --- a/robocop/checkers/community_rules/misc.py +++ b/robocop/checkers/community_rules/misc.py @@ -2,13 +2,13 @@ from robot.libraries import STDLIBS from robocop.checkers import VisitorChecker -from robocop.rules import Rule, RuleSeverity +from robocop.rules import CommunityRule, RuleSeverity RULE_CATEGORY_ID = "01" rules = { - "10101": Rule( + "10101": CommunityRule( rule_id="10101", name="non-builtin-imports-not-sorted", msg="Non builtin library import '{{ custom_import }}' should be placed before '{{ previous_custom_import }}'", @@ -25,7 +25,7 @@ """, ), - "10102": Rule( + "10102": CommunityRule( rule_id="10102", name="resources-imports-not-sorted", msg="Resource import '{{ resource_import }}' should be placed before '{{ previous_resource_import }}'", diff --git a/robocop/checkers/community_rules/usage.py b/robocop/checkers/community_rules/usage.py index 370dfea8..24c93f21 100644 --- a/robocop/checkers/community_rules/usage.py +++ b/robocop/checkers/community_rules/usage.py @@ -11,14 +11,14 @@ from robot.running.arguments import EmbeddedArguments from robocop.checkers import ProjectChecker -from robocop.rules import Message, Rule, RuleSeverity +from robocop.rules import CommunityRule, Message, RuleSeverity from robocop.utils.misc import ROBOT_VERSION, normalize_robot_name from robocop.utils.run_keywords import iterate_keyword_names RULE_CATEGORY_ID = "01" rules = { - "10101": Rule( + "10101": CommunityRule( rule_id="10101", name="unused-keyword", msg="Keyword '{{ keyword_name }}' is not used", diff --git a/robocop/checkers/documentation.py b/robocop/checkers/documentation.py index 3d05e689..cf1f633a 100644 --- a/robocop/checkers/documentation.py +++ b/robocop/checkers/documentation.py @@ -5,13 +5,13 @@ from robot.parsing.model.statements import Documentation from robocop.checkers import VisitorChecker -from robocop.rules import Rule, RuleParam, RuleSeverity +from robocop.rules import DefaultRule, RuleParam, RuleSeverity from robocop.utils.misc import str2bool RULE_CATEGORY_ID = "02" rules = { - "0201": Rule( + "0201": DefaultRule( rule_id="0201", name="missing-doc-keyword", msg="Missing documentation in '{{ name }}' keyword", @@ -30,7 +30,7 @@ Other Step """, ), - "0202": Rule( + "0202": DefaultRule( RuleParam( name="ignore_templated", default="True", @@ -59,7 +59,7 @@ Possible values are: ``Yes`` / ``1`` / ``True`` (default) or ``No`` / ``False`` / ``0``. """, ), - "0203": Rule( + "0203": DefaultRule( rule_id="0203", name="missing-doc-suite", msg="Missing documentation in suite", @@ -72,7 +72,7 @@ Documentation Suite documentation """, ), - "0204": Rule( + "0204": DefaultRule( rule_id="0204", name="missing-doc-resource-file", msg="Missing documentation in resource file", diff --git a/robocop/checkers/duplications.py b/robocop/checkers/duplications.py index 01d6a6b1..51433f00 100644 --- a/robocop/checkers/duplications.py +++ b/robocop/checkers/duplications.py @@ -5,7 +5,7 @@ from robot.api import Token from robocop.checkers import VisitorChecker -from robocop.rules import Rule, RuleParam, RuleSeverity +from robocop.rules import DefaultRule, RuleParam, RuleSeverity from robocop.utils import get_errors, normalize_robot_name, normalize_robot_var_name @@ -33,7 +33,7 @@ def configure_sections_order(value): RULE_CATEGORY_ID = "08" rules = { - "0801": Rule( + "0801": DefaultRule( rule_id="0801", name="duplicated-test-case", msg="Multiple test cases with name '{{ name }}' (first occurrence in line {{ first_occurrence_line }})", @@ -52,7 +52,7 @@ def configure_sections_order(value): """, added_in_version="1.0.0", ), - "0802": Rule( + "0802": DefaultRule( rule_id="0802", name="duplicated-keyword", msg="Multiple keywords with name '{{ name }}' (first occurrence in line {{ first_occurrence_line }})", @@ -75,7 +75,7 @@ def configure_sections_order(value): """, added_in_version="1.0.0", ), - "0803": Rule( + "0803": DefaultRule( rule_id="0803", name="duplicated-variable", msg="Multiple variables with name '{{ name }}' in Variables section (first occurrence in line " @@ -96,14 +96,14 @@ def configure_sections_order(value): """, added_in_version="1.0.0", ), - "0804": Rule( + "0804": DefaultRule( rule_id="0804", name="duplicated-resource", msg="Multiple resource imports with path '{{ name }}' (first occurrence in line {{ first_occurrence_line }})", severity=RuleSeverity.WARNING, added_in_version="1.0.0", ), - "0805": Rule( + "0805": DefaultRule( rule_id="0805", name="duplicated-library", msg="Multiple library imports with name '{{ name }}' and identical arguments (first occurrence in line " @@ -119,21 +119,21 @@ def configure_sections_order(value): """, added_in_version="1.0.0", ), - "0806": Rule( + "0806": DefaultRule( rule_id="0806", name="duplicated-metadata", msg="Duplicated metadata '{{ name }}' (first occurrence in line {{ first_occurrence_line }})", severity=RuleSeverity.WARNING, added_in_version="1.0.0", ), - "0807": Rule( + "0807": DefaultRule( rule_id="0807", name="duplicated-variables-import", msg="Duplicated variables import with path '{{ name }}' (first occurrence in line {{ first_occurrence_line }})", severity=RuleSeverity.WARNING, added_in_version="1.0.0", ), - "0808": Rule( + "0808": DefaultRule( rule_id="0808", name="section-already-defined", msg="'{{ section_name }}' section header already defined in file (first occurrence in line " @@ -160,7 +160,7 @@ def configure_sections_order(value): """, added_in_version="1.0.0", ), - "0809": Rule( + "0809": DefaultRule( RuleParam( name="sections_order", default="settings,variables,testcases,keywords", @@ -194,7 +194,7 @@ def configure_sections_order(value): """, added_in_version="1.0.0", ), - "0810": Rule( + "0810": DefaultRule( rule_id="0810", name="both-tests-and-tasks", msg="Both Task(s) and Test Case(s) section headers defined in file", @@ -209,7 +209,7 @@ def configure_sections_order(value): """, added_in_version="1.0.0", ), - "0811": Rule( + "0811": DefaultRule( rule_id="0811", name="duplicated-argument-name", msg="Argument name '{{ argument_name }}' is already used", @@ -226,7 +226,7 @@ def configure_sections_order(value): """, added_in_version="1.11.0", ), - "0812": Rule( + "0812": DefaultRule( rule_id="0812", name="duplicated-assigned-var-name", msg="Assigned variable name '{{ variable_name }}' is already used", @@ -250,7 +250,7 @@ def configure_sections_order(value): """, added_in_version="1.12.0", ), - "0813": Rule( + "0813": DefaultRule( rule_id="0813", name="duplicated-setting", msg="{{ error_msg }}", diff --git a/robocop/checkers/errors.py b/robocop/checkers/errors.py index 840f5240..df391a33 100644 --- a/robocop/checkers/errors.py +++ b/robocop/checkers/errors.py @@ -10,20 +10,20 @@ If = None from robocop.checkers import VisitorChecker -from robocop.rules import Rule, RuleSeverity +from robocop.rules import DefaultRule, RuleSeverity from robocop.utils import ROBOT_VERSION, find_robot_vars RULE_CATEGORY_ID = "04" rules = { - "0401": Rule( + "0401": DefaultRule( rule_id="0401", name="parsing-error", msg="Robot Framework syntax error: {{ error_msg }}", severity=RuleSeverity.ERROR, added_in_version="1.0.0", ), - "0402": Rule( + "0402": DefaultRule( rule_id="0402", name="not-enough-whitespace-after-setting", msg="Provide at least two spaces after '{{ setting_name }}' setting", @@ -45,7 +45,7 @@ """, added_in_version="1.0.0", ), - "0403": Rule( + "0403": DefaultRule( rule_id="0403", name="missing-keyword-name", msg="Missing keyword name when calling some values", @@ -61,7 +61,7 @@ """, added_in_version="1.8.0", ), - "0404": Rule( + "0404": DefaultRule( rule_id="0404", name="variables-import-with-args", msg="YAML variable files do not take arguments", @@ -77,7 +77,7 @@ """, added_in_version="1.11.0", ), - "0405": Rule( + "0405": DefaultRule( rule_id="0405", name="invalid-continuation-mark", msg="Invalid continuation mark '{{ mark }}'. It should be '...'", @@ -94,7 +94,7 @@ added_in_version="1.11.0", ), # there is not-enough-whitespace-after-newline-marker for keyword calls already - "0406": Rule( + "0406": DefaultRule( rule_id="0406", name="not-enough-whitespace-after-newline-marker", msg="Provide at least two spaces after '...' marker", @@ -109,7 +109,7 @@ """, added_in_version="1.11.0", ), - "0407": Rule( + "0407": DefaultRule( rule_id="0407", name="invalid-argument", msg="{{ error_msg }}", @@ -132,7 +132,7 @@ """, added_in_version="1.11.0", ), - "0408": Rule( + "0408": DefaultRule( rule_id="0408", name="non-existing-setting", msg="{{ error_msg }}", @@ -150,7 +150,7 @@ """, added_in_version="1.11.0", ), - "0409": Rule( + "0409": DefaultRule( rule_id="0409", name="setting-not-supported", msg="Setting '[{{ setting_name }}]' is not supported in {{ test_or_keyword }}. " @@ -178,7 +178,7 @@ """, added_in_version="1.11.0", ), - "0410": Rule( + "0410": DefaultRule( rule_id="0410", name="not-enough-whitespace-after-variable", msg="Provide at least two spaces after '{{ variable_name }}' variable name", @@ -193,7 +193,7 @@ """, added_in_version="1.11.0", ), - "0411": Rule( + "0411": DefaultRule( rule_id="0411", name="not-enough-whitespace-after-suite-setting", msg="Provide at least two spaces after '{{ setting_name }}' setting", @@ -210,7 +210,7 @@ """, added_in_version="1.11.0", ), - "0412": Rule( + "0412": DefaultRule( rule_id="0412", name="invalid-for-loop", msg="Invalid for loop syntax: {{ error_msg }}", @@ -218,7 +218,7 @@ version=">=4.0", added_in_version="1.11.0", ), - "0413": Rule( + "0413": DefaultRule( rule_id="0413", name="invalid-if", msg="Invalid IF syntax: {{ error_msg }}", @@ -226,7 +226,7 @@ version=">=4.0", added_in_version="1.11.0", ), - "0414": Rule( + "0414": DefaultRule( rule_id="0414", name="return-in-test-case", msg="RETURN can only be used inside a user keyword", @@ -234,7 +234,7 @@ version=">=5.0", added_in_version="2.0.0", ), - "0415": Rule( + "0415": DefaultRule( rule_id="0415", name="invalid-section-in-resource", msg="Resource file can't contain '{{ section_name }}' section", @@ -245,7 +245,7 @@ severity=RuleSeverity.ERROR, added_in_version="3.1.0", ), - "0416": Rule( + "0416": DefaultRule( rule_id="0416", name="invalid-setting-in-resource", msg="Settings section in resource file can't contain '{{ section_name }}' setting", @@ -256,7 +256,7 @@ severity=RuleSeverity.ERROR, added_in_version="3.3.0", ), - "0417": Rule( + "0417": DefaultRule( rule_id="0417", name="unsupported-setting-in-init-file", msg="Setting '{{ setting }}' is not supported in initialization files", diff --git a/robocop/checkers/lengths.py b/robocop/checkers/lengths.py index ffcf6ba6..8228846f 100644 --- a/robocop/checkers/lengths.py +++ b/robocop/checkers/lengths.py @@ -24,14 +24,14 @@ Var = None from robocop.checkers import RawFileChecker, VisitorChecker -from robocop.rules import Rule, RuleParam, RuleSeverity, SeverityThreshold +from robocop.rules import DefaultRule, RuleParam, RuleSeverity, SeverityThreshold from robocop.utils import get_section_name, normalize_robot_name, pattern_type, str2bool from robocop.utils.misc import RETURN_CLASSES RULE_CATEGORY_ID = "05" rules = { - "0501": Rule( + "0501": DefaultRule( RuleParam(name="max_len", default=40, converter=int, desc="number of lines allowed in a keyword"), RuleParam(name="ignore_docs", default=False, converter=str2bool, show_type="bool", desc="Ignore documentation"), SeverityThreshold("max_len", compare_method="greater", substitute_value="allowed_length"), @@ -41,7 +41,7 @@ severity=RuleSeverity.WARNING, added_in_version="1.0.0", ), - "0502": Rule( + "0502": DefaultRule( RuleParam(name="min_calls", default=1, converter=int, desc="number of keyword calls required in a keyword"), SeverityThreshold("min_calls", compare_method="less", substitute_value="min_allowed_count"), rule_id="0502", @@ -50,7 +50,7 @@ severity=RuleSeverity.WARNING, added_in_version="1.0.0", ), - "0503": Rule( + "0503": DefaultRule( RuleParam(name="max_calls", default=10, converter=int, desc="number of keyword calls allowed in a keyword"), SeverityThreshold("max_calls", compare_method="greater", substitute_value="max_allowed_count"), rule_id="0503", @@ -59,7 +59,7 @@ severity=RuleSeverity.WARNING, added_in_version="1.0.0", ), - "0504": Rule( + "0504": DefaultRule( RuleParam(name="max_len", default=20, converter=int, desc="number of lines allowed in a test case"), RuleParam(name="ignore_docs", default=False, converter=str2bool, show_type="bool", desc="Ignore documentation"), SeverityThreshold("max_len", compare_method="greater", substitute_value="allowed_length"), @@ -69,7 +69,7 @@ severity=RuleSeverity.WARNING, added_in_version="1.0.0", ), - "0505": Rule( + "0505": DefaultRule( RuleParam(name="max_calls", default=10, converter=int, desc="number of keyword calls allowed in a test case"), RuleParam( name="ignore_templated", default=False, converter=str2bool, show_type="bool", desc="Ignore templated tests" @@ -82,7 +82,7 @@ severity=RuleSeverity.WARNING, added_in_version="1.0.0", ), - "0506": Rule( + "0506": DefaultRule( RuleParam(name="max_lines", default=400, converter=int, desc="number of lines allowed in a file"), SeverityThreshold("max_lines", compare_method="greater", substitute_value="max_allowed_count"), rule_id="0506", @@ -91,7 +91,7 @@ severity=RuleSeverity.WARNING, added_in_version="1.0.0", ), - "0507": Rule( + "0507": DefaultRule( RuleParam(name="max_args", default=5, converter=int, desc="number of lines allowed in a file"), SeverityThreshold("max_args", compare_method="greater", substitute_value="max_allowed_count"), rule_id="0507", @@ -100,7 +100,7 @@ severity=RuleSeverity.WARNING, added_in_version="1.0.0", ), - "0508": Rule( + "0508": DefaultRule( RuleParam(name="line_length", default=120, converter=int, desc="number of characters allowed in line"), RuleParam( name="ignore_pattern", @@ -124,14 +124,14 @@ """, added_in_version="1.0.0", ), - "0509": Rule( + "0509": DefaultRule( rule_id="0509", name="empty-section", msg="Section '{{ section_name }}' is empty", severity=RuleSeverity.WARNING, added_in_version="1.0.0", ), - "0510": Rule( + "0510": DefaultRule( RuleParam( name="max_returns", default=4, converter=int, desc="allowed number of returned values from a keyword" ), @@ -142,119 +142,119 @@ severity=RuleSeverity.WARNING, added_in_version="1.0.0", ), - "0511": Rule( + "0511": DefaultRule( rule_id="0511", name="empty-metadata", msg="Metadata settings does not have any value set", severity=RuleSeverity.WARNING, added_in_version="1.0.0", ), - "0512": Rule( + "0512": DefaultRule( rule_id="0512", name="empty-documentation", msg="Documentation of {{ block_name }} is empty", severity=RuleSeverity.WARNING, added_in_version="1.0.0", ), - "0513": Rule( + "0513": DefaultRule( rule_id="0513", name="empty-force-tags", msg="Force Tags are empty", severity=RuleSeverity.WARNING, added_in_version="1.0.0", ), - "0514": Rule( + "0514": DefaultRule( rule_id="0514", name="empty-default-tags", msg="Default Tags are empty", severity=RuleSeverity.WARNING, added_in_version="1.0.0", ), - "0515": Rule( + "0515": DefaultRule( rule_id="0515", name="empty-variables-import", msg="Import variables path is empty", severity=RuleSeverity.ERROR, added_in_version="1.0.0", ), - "0516": Rule( + "0516": DefaultRule( rule_id="0516", name="empty-resource-import", msg="Import resource path is empty", severity=RuleSeverity.ERROR, added_in_version="1.0.0", ), - "0517": Rule( + "0517": DefaultRule( rule_id="0517", name="empty-library-import", msg="Import library path is empty", severity=RuleSeverity.ERROR, added_in_version="1.0.0", ), - "0518": Rule( + "0518": DefaultRule( rule_id="0518", name="empty-setup", msg="Setup of {{ block_name }} does not have any keywords", severity=RuleSeverity.ERROR, added_in_version="1.0.0", ), - "0519": Rule( + "0519": DefaultRule( rule_id="0519", name="empty-suite-setup", msg="Suite Setup does not have any keywords", severity=RuleSeverity.ERROR, added_in_version="1.0.0", ), - "0520": Rule( + "0520": DefaultRule( rule_id="0520", name="empty-test-setup", msg="Test Setup does not have any keywords", severity=RuleSeverity.ERROR, added_in_version="1.0.0", ), - "0521": Rule( + "0521": DefaultRule( rule_id="0521", name="empty-teardown", msg="Teardown of {{ block_name }} does not have any keywords", severity=RuleSeverity.ERROR, added_in_version="1.0.0", ), - "0522": Rule( + "0522": DefaultRule( rule_id="0522", name="empty-suite-teardown", msg="Suite Teardown does not have any keywords", severity=RuleSeverity.ERROR, added_in_version="1.0.0", ), - "0523": Rule( + "0523": DefaultRule( rule_id="0523", name="empty-test-teardown", msg="Test Teardown does not have any keywords", severity=RuleSeverity.ERROR, added_in_version="1.0.0", ), - "0524": Rule( + "0524": DefaultRule( rule_id="0524", name="empty-timeout", msg="Timeout of {{ block_name }} is empty", severity=RuleSeverity.WARNING, added_in_version="1.0.0", ), - "0525": Rule( + "0525": DefaultRule( rule_id="0525", name="empty-test-timeout", msg="Test Timeout is empty", severity=RuleSeverity.WARNING, added_in_version="1.0.0", ), - "0526": Rule( + "0526": DefaultRule( rule_id="0526", name="empty-arguments", msg="Arguments of {{ block_name }} are empty", severity=RuleSeverity.ERROR, added_in_version="1.0.0", ), - "0527": Rule( + "0527": DefaultRule( RuleParam(name="max_testcases", default=50, converter=int, desc="number of test cases allowed in a suite"), RuleParam( name="max_templated_testcases", @@ -269,7 +269,7 @@ severity=RuleSeverity.WARNING, added_in_version="1.10.0", ), - "0528": Rule( + "0528": DefaultRule( RuleParam(name="min_calls", default=1, converter=int, desc="number of keyword calls required in a test case"), RuleParam( name="ignore_templated", default=False, converter=str2bool, show_type="bool", desc="Ignore templated tests" @@ -290,7 +290,7 @@ severity=RuleSeverity.ERROR, added_in_version="2.4.0", ), - "0529": Rule( + "0529": DefaultRule( rule_id="0529", name="empty-test-template", msg="Test Template is empty", @@ -302,7 +302,7 @@ severity=RuleSeverity.ERROR, added_in_version="3.1.0", ), - "0530": Rule( + "0530": DefaultRule( rule_id="0530", name="empty-template", msg="Template of {{ block_name }} is empty. " @@ -328,7 +328,7 @@ severity=RuleSeverity.WARNING, added_in_version="3.1.0", ), - "0531": Rule( + "0531": DefaultRule( rule_id="0531", name="empty-keyword-tags", msg="Keyword Tags are empty", @@ -336,7 +336,7 @@ version=">=6", added_in_version="3.3.0", ), - "0532": Rule( + "0532": DefaultRule( RuleParam( name="max_args", default=1, @@ -359,7 +359,7 @@ [Arguments] ${first_arg ... ${second_arg} ${third_arg}=default - Good |:white_check_mark:| :: + Good |:white_check_mark:| :: .. code-block:: none diff --git a/robocop/checkers/misc.py b/robocop/checkers/misc.py index 8a3ec848..77374596 100644 --- a/robocop/checkers/misc.py +++ b/robocop/checkers/misc.py @@ -23,7 +23,7 @@ InlineIfHeader, Break, Continue = None, None, None from robocop.checkers import VisitorChecker -from robocop.rules import Rule, RuleParam, RuleSeverity, SeverityThreshold +from robocop.rules import DefaultRule, RuleParam, RuleSeverity, SeverityThreshold from robocop.utils import ( ROBOT_VERSION, AssignmentTypeDetector, @@ -51,7 +51,7 @@ def comma_separated_list(value: str) -> list[str]: rules = { - "0901": Rule( + "0901": DefaultRule( rule_id="0901", name="keyword-after-return", msg="{{ error_msg }}", @@ -78,7 +78,7 @@ def comma_separated_list(value: str) -> list[str]: """, added_in_version="1.0.0", ), - "0903": Rule( + "0903": DefaultRule( rule_id="0903", name="empty-return", msg="[Return] is empty", @@ -89,7 +89,7 @@ def comma_separated_list(value: str) -> list[str]: """, added_in_version="1.0.0", ), - "0907": Rule( + "0907": DefaultRule( rule_id="0907", name="nested-for-loop", msg="Nested for loops are not supported. You can use keyword with for loop instead", @@ -107,7 +107,7 @@ def comma_separated_list(value: str) -> list[str]: """, added_in_version="1.0.0", ), - "0908": Rule( + "0908": DefaultRule( rule_id="0908", name="if-can-be-used", msg="'{{ run_keyword }}' can be replaced with IF block since Robot Framework 4.0", @@ -118,7 +118,7 @@ def comma_separated_list(value: str) -> list[str]: """, added_in_version="1.4.0", ), - "0909": Rule( + "0909": DefaultRule( RuleParam( name="assignment_sign_type", default="autodetect", @@ -157,7 +157,7 @@ def comma_separated_list(value: str) -> list[str]: """, added_in_version="1.7.0", ), - "0910": Rule( + "0910": DefaultRule( RuleParam( name="assignment_sign_type", default="autodetect", @@ -194,7 +194,7 @@ def comma_separated_list(value: str) -> list[str]: """, added_in_version="1.7.0", ), - "0911": Rule( + "0911": DefaultRule( rule_id="0911", name="wrong-import-order", msg="BuiltIn library import '{{ builtin_import }}' should be placed before '{{ custom_import }}'", @@ -210,7 +210,7 @@ def comma_separated_list(value: str) -> list[str]: """, added_in_version="1.7.0", ), - "0912": Rule( + "0912": DefaultRule( RuleParam( name="variable_source", default="section,var", @@ -245,7 +245,7 @@ def comma_separated_list(value: str) -> list[str]: """, added_in_version="1.10.0", ), - "0913": Rule( + "0913": DefaultRule( rule_id="0913", name="can-be-resource-file", msg="No tests in '{{ file_name }}' file, consider renaming to '{{ file_name_stem }}.resource'", @@ -255,7 +255,7 @@ def comma_separated_list(value: str) -> list[str]: """, added_in_version="1.10.0", ), - "0914": Rule( + "0914": DefaultRule( rule_id="0914", name="if-can-be-merged", msg="IF statement can be merged with previous IF (defined in line {{ line }})", @@ -290,7 +290,7 @@ def comma_separated_list(value: str) -> list[str]: """, added_in_version="2.0.0", ), - "0915": Rule( + "0915": DefaultRule( rule_id="0915", name="statement-outside-loop", msg="{{ name }} {{ statement_type }} used outside a loop", @@ -308,7 +308,7 @@ def comma_separated_list(value: str) -> list[str]: """, added_in_version="2.0.0", ), - "0916": Rule( + "0916": DefaultRule( RuleParam( name="max_width", default=80, @@ -337,7 +337,7 @@ def comma_separated_list(value: str) -> list[str]: """, added_in_version="2.0.0", ), - "0917": Rule( + "0917": DefaultRule( rule_id="0917", name="unreachable-code", msg="Unreachable code after {{ statement }} statement", @@ -363,7 +363,7 @@ def comma_separated_list(value: str) -> list[str]: """, added_in_version="3.1.0", ), - "0918": Rule( + "0918": DefaultRule( rule_id="0918", name="multiline-inline-if", msg="Avoid splitting inline IF to multiple lines", @@ -392,7 +392,7 @@ def comma_separated_list(value: str) -> list[str]: """, added_in_version="3.1.0", ), - "0919": Rule( + "0919": DefaultRule( rule_id="0919", name="unused-argument", msg="Keyword argument '{{ name }}' is not used", @@ -414,7 +414,7 @@ def comma_separated_list(value: str) -> list[str]: """, added_in_version="3.2.0", ), - "0920": Rule( + "0920": DefaultRule( rule_id="0920", name="unused-variable", msg="Variable '{{ name }}' is assigned but not used", @@ -445,7 +445,7 @@ def comma_separated_list(value: str) -> list[str]: """, added_in_version="3.2.0", ), - "0921": Rule( + "0921": DefaultRule( rule_id="0921", name="argument-overwritten-before-usage", msg="Keyword argument '{{ name }}' is overwritten before usage", @@ -461,7 +461,7 @@ def comma_separated_list(value: str) -> list[str]: """, added_in_version="3.2.0", ), - "0922": Rule( + "0922": DefaultRule( rule_id="0922", name="variable-overwritten-before-usage", msg="Local variable '{{ name }}' is overwritten before usage", @@ -486,7 +486,7 @@ def comma_separated_list(value: str) -> list[str]: """, added_in_version="3.2.0", ), - "0923": Rule( + "0923": DefaultRule( rule_id="0923", name="unnecessary-string-conversion", msg="Variable '{{ name }}' in '{{ block_name }}' condition has unnecessary string conversion", @@ -532,7 +532,7 @@ def comma_separated_list(value: str) -> list[str]: """, added_in_version="4.0.0", ), - "0924": Rule( + "0924": DefaultRule( rule_id="0924", name="expression-can-be-simplified", msg="'{{ block_name }}' condition can be simplified", @@ -574,7 +574,7 @@ def comma_separated_list(value: str) -> list[str]: """, added_in_version="4.0.0", ), - "0925": Rule( + "0925": DefaultRule( rule_id="0925", name="misplaced-negative-condition", msg="'{{ block_name }}' condition '{{ original_condition }}' can be rewritten to '{{ proposed_condition }}'", @@ -612,7 +612,7 @@ def comma_separated_list(value: str) -> list[str]: """, added_in_version="4.0.0", ), - "0926": Rule( + "0926": DefaultRule( rule_id="0926", name="builtin-imports-not-sorted", msg="BuiltIn library import '{{ builtin_import }}' should be placed before '{{ previous_builtin_import }}'", @@ -627,7 +627,7 @@ def comma_separated_list(value: str) -> list[str]: """, ), - "0927": Rule( + "0927": DefaultRule( RuleParam( name="sections_order", default="documentation,tags,timeout,setup,template,keyword,teardown", @@ -663,7 +663,7 @@ def comma_separated_list(value: str) -> list[str]: Keyword1 """, ), - "0928": Rule( + "0928": DefaultRule( RuleParam( name="sections_order", default="documentation,tags,arguments,timeout,setup,keyword,teardown", @@ -699,7 +699,7 @@ def comma_separated_list(value: str) -> list[str]: Keyword1 """, ), - "0929": Rule( + "0929": DefaultRule( rule_id="0929", name="no-global-variable", msg="Don't set global variables outside the variables section", @@ -750,7 +750,7 @@ def comma_separated_list(value: str) -> list[str]: makes code needlessly hard to understand. """, ), - "0930": Rule( + "0930": DefaultRule( rule_id="0930", name="no-suite-variable", msg="Don't use suite variables", @@ -797,7 +797,7 @@ def comma_separated_list(value: str) -> list[str]: makes code needlessly hard to understand. """, ), - "0931": Rule( + "0931": DefaultRule( rule_id="0931", name="no-test-variable", msg="Don't use test/task variables", diff --git a/robocop/checkers/naming.py b/robocop/checkers/naming.py index 600d714c..aa6a32a0 100644 --- a/robocop/checkers/naming.py +++ b/robocop/checkers/naming.py @@ -15,7 +15,7 @@ from robot.variables.search import search_variable from robocop.checkers import VisitorChecker -from robocop.rules import Rule, RuleParam, RuleSeverity +from robocop.rules import DefaultRule, RuleParam, RuleSeverity from robocop.utils import ( ROBOT_VERSION, find_robot_vars, @@ -36,7 +36,7 @@ RULE_CATEGORY_ID = "03" rules = { - "0301": Rule( + "0301": DefaultRule( RuleParam( name="pattern", default=re.compile(r"[\.\?]"), @@ -59,7 +59,7 @@ would report any occurrence of ``@[`` characters. """, ), - "0302": Rule( + "0302": DefaultRule( RuleParam( name="convention", default="each_word_capitalized", @@ -110,7 +110,7 @@ Go To robocop.readthedocs.io Page """, ), - "0303": Rule( + "0303": DefaultRule( rule_id="0303", name="keyword-name-is-reserved-word", msg="'{{ keyword_name }}' is a reserved keyword{{ error_msg }}", @@ -132,7 +132,7 @@ - FINALLY """, ), - "0305": Rule( + "0305": DefaultRule( rule_id="0305", name="underscore-in-keyword-name", msg="Underscores in keyword name '{{ keyword_name }}' can be replaced with spaces", @@ -152,7 +152,7 @@ Keyword Without Underscores """, ), - "0306": Rule( + "0306": DefaultRule( rule_id="0306", name="setting-name-not-in-title-case", msg="Setting name '{{ setting_name }}' should use title or upper case", @@ -180,7 +180,7 @@ Step """, ), - "0307": Rule( + "0307": DefaultRule( rule_id="0307", name="section-name-invalid", msg="Section name should be in format '{{ section_title_case }}' or '{{ section_upper_case }}'", @@ -198,7 +198,7 @@ """, ), - "0308": Rule( + "0308": DefaultRule( rule_id="0308", name="not-capitalized-test-case-title", msg="Test case '{{ test_name }}' title should start with capital letter", @@ -216,14 +216,14 @@ validate user details """, ), - "0309": Rule( + "0309": DefaultRule( rule_id="0309", name="section-variable-not-uppercase", msg="Section variable '{{ variable_name }}' name should be uppercase", severity=RuleSeverity.WARNING, added_in_version="1.4.0", ), - "0310": Rule( + "0310": DefaultRule( rule_id="0310", name="non-local-variables-should-be-uppercase", msg="Test, suite and global variables should be uppercase", @@ -249,7 +249,7 @@ Set Global Variable ${my_var${NESTED}} 1 """, ), - "0311": Rule( + "0311": DefaultRule( rule_id="0311", name="else-not-upper-case", msg="ELSE and ELSE IF should be upper case", @@ -281,7 +281,7 @@ RETURN Cold """, ), - "0312": Rule( + "0312": DefaultRule( rule_id="0312", name="keyword-name-is-empty", msg="Keyword name should not be empty", @@ -295,7 +295,7 @@ Log To Console hi """, ), - "0313": Rule( + "0313": DefaultRule( rule_id="0313", name="test-case-name-is-empty", msg="Test case name should not be empty", @@ -309,7 +309,7 @@ Log To Console hello """, ), - "0314": Rule( + "0314": DefaultRule( rule_id="0314", name="empty-library-alias", msg="Library alias should not be empty", @@ -329,7 +329,7 @@ Library CustomLibrary AS """, ), - "0315": Rule( + "0315": DefaultRule( rule_id="0315", name="duplicated-library-alias", msg="Library alias should not be the same as original name", @@ -343,7 +343,7 @@ Library CustomLibrary AS Custom Library # same as library name (spaces are ignored) """, ), - "0316": Rule( + "0316": DefaultRule( rule_id="0316", name="possible-variable-overwriting", msg="Variable '{{ variable_name }}' may overwrite similar variable inside '{{ block_name }}' {{ block_type }}. " @@ -364,7 +364,7 @@ underscores and whitespaces are ignored. """, ), - "0317": Rule( + "0317": DefaultRule( rule_id="0317", name="hyphen-in-variable-name", msg="Use underscore in variable name '{{ variable_name }}' instead of hyphens to " @@ -386,7 +386,7 @@ ${var2} Set Variable ${${var}_${var2}} """, ), - "0318": Rule( + "0318": DefaultRule( rule_id="0318", name="bdd-without-keyword-call", msg="BDD reserved keyword '{{ keyword_name }}' not followed by any keyword{{ error_msg }}", @@ -411,7 +411,7 @@ Since those words are used for BDD style, it's also recommended not to use them within the user keyword name. """, ), - "0319": Rule( + "0319": DefaultRule( rule_id="0319", name="deprecated-statement", msg="'{{ statement_name }}' is deprecated since Robot Framework version " @@ -424,7 +424,7 @@ For example, ``Run Keyword`` and ``Continue For Loop`` keywords or ``[Return]`` setting. """, ), - "0320": Rule( + "0320": DefaultRule( RuleParam( name="pattern", default=re.compile(r"[\.\?]"), @@ -447,7 +447,7 @@ pattern would report any occurrence of ``@[`` characters. """, ), - "0321": Rule( + "0321": DefaultRule( rule_id="0321", name="deprecated-with-name", msg=( @@ -472,7 +472,7 @@ Library Collections AS AliasedName """, ), - "0322": Rule( + "0322": DefaultRule( rule_id="0322", name="deprecated-singular-header", msg="'{{ singular_header }}' singular header form is deprecated since RF 6.0 and " @@ -485,7 +485,7 @@ is available at https://github.com/robotframework/robotframework/issues/4431 """, ), - "0323": Rule( + "0323": DefaultRule( rule_id="0323", name="inconsistent-variable-name", msg="Variable '{{ name }}' has inconsistent naming. First used as '{{ first_use }}'", @@ -509,7 +509,7 @@ END """, ), - "0324": Rule( + "0324": DefaultRule( rule_id="0324", name="overwriting-reserved-variable", msg="{{ var_or_arg }} '{{ variable_name }}' overwrites reserved variable '{{ reserved_variable }}'", @@ -522,7 +522,7 @@ `Robot Framework User Guide `_ """, ), - "0325": Rule( + "0325": DefaultRule( rule_id="0325", name="invalid-section", msg="Invalid section '{{ invalid_section }}'. Consider using --language parameter if the file is defined with different language", @@ -542,7 +542,7 @@ Log Błąd dostępu """, ), - "0326": Rule( + "0326": DefaultRule( rule_id="0326", name="mixed-task-test-settings", msg="Use {{ task_or_test }}-related setting '{{ setting }}' if {{ tasks_or_tests }} section is used", @@ -555,7 +555,7 @@ Similarly, use test-related settings when using ``*** Test Cases ***`` section. """, ), - "0327": Rule( + "0327": DefaultRule( rule_id="0327", name="replace-set-variable-with-var", msg="{{ set_variable_keyword }} can be replaced with VAR", @@ -588,7 +588,7 @@ """, ), - "0328": Rule( + "0328": DefaultRule( rule_id="0328", name="replace-create-with-var", msg="{{ create_keyword }} can be replaced with VAR", diff --git a/robocop/checkers/spacing.py b/robocop/checkers/spacing.py index 3dfd61cc..446f71bf 100644 --- a/robocop/checkers/spacing.py +++ b/robocop/checkers/spacing.py @@ -17,28 +17,28 @@ InlineIfHeader = None from robocop.checkers import RawFileChecker, VisitorChecker -from robocop.rules import Rule, RuleParam, RuleSeverity, SeverityThreshold +from robocop.rules import DefaultRule, RuleParam, RuleSeverity, SeverityThreshold from robocop.utils import get_errors, get_section_name, str2bool, token_col from robocop.utils.run_keywords import is_run_keyword RULE_CATEGORY_ID = "10" rules = { - "1001": Rule( + "1001": DefaultRule( rule_id="1001", name="trailing-whitespace", msg="Trailing whitespace at the end of line", severity=RuleSeverity.WARNING, added_in_version="1.0.0", ), - "1002": Rule( + "1002": DefaultRule( rule_id="1002", name="missing-trailing-blank-line", msg="Missing trailing blank line at the end of file", severity=RuleSeverity.WARNING, added_in_version="1.0.0", ), - "1003": Rule( + "1003": DefaultRule( RuleParam( name="empty_lines", default=2, @@ -51,7 +51,7 @@ severity=RuleSeverity.WARNING, added_in_version="1.0.0", ), - "1004": Rule( + "1004": DefaultRule( RuleParam( name="empty_lines", default=1, @@ -64,7 +64,7 @@ severity=RuleSeverity.WARNING, added_in_version="1.0.0", ), - "1005": Rule( + "1005": DefaultRule( RuleParam( name="empty_lines", default=1, @@ -77,14 +77,14 @@ severity=RuleSeverity.WARNING, added_in_version="1.0.0", ), - "1006": Rule( + "1006": DefaultRule( rule_id="1006", name="mixed-tabs-and-spaces", msg="Inconsistent use of tabs and spaces in file", severity=RuleSeverity.WARNING, added_in_version="1.1.0", ), - "1008": Rule( + "1008": DefaultRule( RuleParam( name="indent", default=-1, @@ -113,7 +113,7 @@ """, added_in_version="3.0.0", ), - "1009": Rule( + "1009": DefaultRule( RuleParam( name="empty_lines", default=0, @@ -137,7 +137,7 @@ """, added_in_version="1.2.0", ), - "1010": Rule( + "1010": DefaultRule( rule_id="1010", name="too-many-trailing-blank-lines", msg="Too many blank lines at the end of file", @@ -145,7 +145,7 @@ docs="""There should be exactly one blank line at the end of the file""", added_in_version="1.4.0", ), - "1011": Rule( + "1011": DefaultRule( rule_id="1011", name="misaligned-continuation", msg="Continuation marker should be aligned with starting row", @@ -164,7 +164,7 @@ """, added_in_version="1.6.0", ), - "1012": Rule( + "1012": DefaultRule( RuleParam( name="empty_lines", default=1, @@ -196,7 +196,7 @@ """, added_in_version="1.8.0", ), - "1013": Rule( + "1013": DefaultRule( rule_id="1013", name="empty-lines-in-statement", msg="Multi-line statement with empty lines", @@ -212,7 +212,7 @@ """, added_in_version="1.8.0", ), - "1014": Rule( + "1014": DefaultRule( rule_id="1014", name="variable-should-be-left-aligned", msg="Variable in Variable section should be left aligned", @@ -228,7 +228,7 @@ """, added_in_version="1.8.0", ), - "1015": Rule( + "1015": DefaultRule( RuleParam(name="ignore_docs", default=True, converter=str2bool, show_type="bool", desc="Ignore documentation"), RuleParam( name="ignore_run_keywords", default=False, converter=str2bool, show_type="bool", desc="Ignore run keywords" @@ -253,7 +253,7 @@ """, added_in_version="1.11.0", ), - "1016": Rule( + "1016": DefaultRule( rule_id="1016", name="suite-setting-should-be-left-aligned", msg="Setting in Settings section should be left aligned", @@ -270,7 +270,7 @@ """, added_in_version="2.4.0", ), - "1017": Rule( + "1017": DefaultRule( rule_id="1017", name="bad-block-indent", msg="Indent expected. Provide 2 or more spaces of indentation for statements inside block", @@ -289,7 +289,7 @@ """, added_in_version="3.0.0", ), - "1018": Rule( + "1018": DefaultRule( rule_id="1018", name="first-argument-in-new-line", msg="First argument: '{{ argument_name }}' should be placed on the same line as [Arguments] setting", diff --git a/robocop/checkers/tags.py b/robocop/checkers/tags.py index 9c892fcc..67b69f73 100644 --- a/robocop/checkers/tags.py +++ b/robocop/checkers/tags.py @@ -5,13 +5,13 @@ from robot.api import Token from robocop.checkers import VisitorChecker -from robocop.rules import Rule, RuleSeverity +from robocop.rules import DefaultRule, RuleSeverity from robocop.utils import variable_matcher RULE_CATEGORY_ID = "06" rules = { - "0601": Rule( + "0601": DefaultRule( rule_id="0601", name="tag-with-space", msg="Tag '{{ tag }}' should not contain spaces", @@ -25,7 +25,7 @@ """, added_in_version="1.0.0", ), - "0602": Rule( + "0602": DefaultRule( rule_id="0602", name="tag-with-or-and", msg="Tag '{{ tag }}' with reserved word OR/AND." @@ -47,7 +47,7 @@ """, added_in_version="1.0.0", ), - "0603": Rule( + "0603": DefaultRule( rule_id="0603", name="tag-with-reserved-word", msg="Tag '{{ tag }}' prefixed with reserved word `robot:`", @@ -72,7 +72,7 @@ """, added_in_version="1.0.0", ), - "0605": Rule( + "0605": DefaultRule( rule_id="0605", name="could-be-test-tags", msg="All tests in suite share these tags: '{{ tags }}'. " @@ -96,7 +96,7 @@ """, added_in_version="1.0.0", ), - "0606": Rule( + "0606": DefaultRule( rule_id="0606", name="tag-already-set-in-test-tags", msg="Tag '{{ tag }}' is already set by {{ test_force_tags }} in suite settings", @@ -118,7 +118,7 @@ """, added_in_version="1.0.0", ), - "0607": Rule( + "0607": DefaultRule( rule_id="0607", name="unnecessary-default-tags", msg="Tags defined in Default Tags are always overwritten", @@ -142,7 +142,7 @@ """, added_in_version="1.0.0", ), - "0608": Rule( + "0608": DefaultRule( rule_id="0608", name="empty-tags", msg="[Tags] setting without values{{ optional_warning }}", @@ -153,7 +153,7 @@ """, added_in_version="2.0.0", ), - "0609": Rule( + "0609": DefaultRule( rule_id="0609", name="duplicated-tags", msg="Multiple tags with name '{{ name }}' (first occurrence at line {{ line }} column {{ column }})", @@ -170,7 +170,7 @@ """, added_in_version="2.0.0", ), - "0610": Rule( + "0610": DefaultRule( rule_id="0610", name="could-be-keyword-tags", msg="All keywords in suite share these tags: '{{ tags }}'. " @@ -193,7 +193,7 @@ """, added_in_version="3.3.0", ), - "0611": Rule( + "0611": DefaultRule( rule_id="0611", name="tag-already-set-in-keyword-tags", msg="Tag '{{ tag }}' is already set by {{ keyword_tags }} in suite settings", diff --git a/robocop/reports/sarif_report.py b/robocop/reports/sarif_report.py index d5ac03ab..ae38a864 100644 --- a/robocop/reports/sarif_report.py +++ b/robocop/reports/sarif_report.py @@ -3,7 +3,6 @@ import robocop.reports from robocop.rules import Message -from robocop.utils.misc import ROBOCOP_RULES_URL from robocop.version import __version__ @@ -53,7 +52,7 @@ def get_rule_desc(self, rule): return { "id": rule.rule_id, "name": rule.name, - "helpUri": f"{ROBOCOP_RULES_URL.format(version=__version__)}#{rule.name}", + "helpUri": rule.help_url or "", "shortDescription": {"text": rule.msg}, "fullDescription": {"text": rule.docs}, "defaultConfiguration": {"level": self.map_severity_to_level(rule.default_severity)}, diff --git a/robocop/rules.py b/robocop/rules.py index 2fcacc05..9cc11bc0 100644 --- a/robocop/rules.py +++ b/robocop/rules.py @@ -39,6 +39,7 @@ from robocop.utils import ROBOT_VERSION from robocop.utils.misc import str2bool from robocop.utils.version_matching import VersionSpecifier +from robocop.version import __version__ if TYPE_CHECKING: from re import Pattern @@ -298,6 +299,7 @@ def __init__( added_in_version: str | None = None, enabled: bool = True, deprecated: bool = False, + help_url: str | None = None, ): """ :param params: RuleParam() or SeverityThreshold() instances @@ -311,6 +313,7 @@ def __init__( :param added_in_version: Version of the Robocop when the Rule was created :param enabled: Enable/disable rule by default using this parameter :param deprecated: Deprecate rule. If rule is used in configuration, it will issue a warning. + :param help_url: URL to rule documentation or other help resource. """ self.rule_id = rule_id self.name = name @@ -342,6 +345,7 @@ def __init__( self.added_in_version = added_in_version self.community_rule = False self.category_id = None + self.help_url = help_url @property def severity(self): @@ -469,6 +473,26 @@ def matches_pattern(self, pattern: str | Pattern): return pattern.match(self.name) or pattern.match(self.rule_id) +class DefaultRule(Rule): + @property + def help_url(self) -> str: + return f"https://robocop.readthedocs.io/en/{__version__}/rules_list.html#{self.name}" + + @help_url.setter + def help_url(self, _): + pass + + +class CommunityRule(Rule): + @property + def help_url(self) -> str: + return f"https://robocop.readthedocs.io/en/{__version__}/community_rules.html#{self.name}" + + @help_url.setter + def help_url(self, _): + pass + + class Message: def __init__( self, @@ -487,6 +511,7 @@ def __init__( self.enabled = rule.enabled self.rule_id = rule.rule_id self.name = rule.name + self.help_url = rule.help_url self.severity = self.get_severity(overwrite_severity, rule, sev_threshold_value) self.desc = msg self.source = source diff --git a/robocop/utils/misc.py b/robocop/utils/misc.py index 268e18b7..59ed9852 100644 --- a/robocop/utils/misc.py +++ b/robocop/utils/misc.py @@ -24,7 +24,6 @@ from robocop.utils.variable_matcher import VariableMatches from robocop.utils.version_matching import Version -from robocop.version import __version__ ROBOT_VERSION = Version(RF_VERSION) ROBOT_WITH_LANG = Version("6.0") @@ -104,8 +103,9 @@ def token_col(node, *token_type) -> int: def issues_to_lsp_diagnostic(issues) -> list[dict]: - return [ - { + diagnostics = [] + for issue in issues: + diagnostic = { "range": { "start": { "line": max(0, issue.line - 1), @@ -120,10 +120,14 @@ def issues_to_lsp_diagnostic(issues) -> list[dict]: "code": issue.rule_id, "source": "robocop", "message": issue.desc, - "codeDescription": {"href": f"{ROBOCOP_RULES_URL.format(version=__version__)}#{issue.name}"}, } - for issue in issues - ] + + if issue.help_url: + diagnostic["codeDescription"] = {"href": issue.help_url} + + diagnostics.append(diagnostic) + + return diagnostics def str2bool(v): diff --git a/tests/utest/reports/conftest.py b/tests/utest/reports/conftest.py index b9d6572b..ecc23eaf 100644 --- a/tests/utest/reports/conftest.py +++ b/tests/utest/reports/conftest.py @@ -1,11 +1,11 @@ import pytest -from robocop.rules import Rule, RuleParam, RuleSeverity +from robocop.rules import CommunityRule, DefaultRule, Rule, RuleParam, RuleSeverity @pytest.fixture def rule(): - return Rule( + return DefaultRule( RuleParam(name="param_name", converter=int, default=1, desc=""), rule_id="0101", name="some-message", @@ -16,7 +16,7 @@ def rule(): @pytest.fixture def rule2(): - return Rule( + return DefaultRule( rule_id="0902", name="other-message", msg="Some description. Example::\n", @@ -25,8 +25,28 @@ def rule2(): @pytest.fixture -def error_msg(): +def community_rule(): + return CommunityRule( + rule_id="10001", + name="some-community-rule", + msg="An amazing description. Example::\n", + severity=RuleSeverity.INFO, + ) + + +@pytest.fixture +def custom_rule(): return Rule( + rule_id="CUSTOM01", + name="some-custom-rule", + msg="A rule made outside Robocop. It has no help URL", + severity=RuleSeverity.ERROR, + ) + + +@pytest.fixture +def error_msg(): + return DefaultRule( RuleParam(name="param_name", converter=int, default=1, desc=""), rule_id="0101", name="error-message", @@ -37,7 +57,7 @@ def error_msg(): @pytest.fixture def warning_msg(): - return Rule( + return DefaultRule( RuleParam(name="param_name", converter=int, default=1, desc=""), rule_id="0102", name="warning-message", @@ -48,7 +68,7 @@ def warning_msg(): @pytest.fixture def info_msg(): - return Rule( + return DefaultRule( RuleParam(name="param_name", converter=int, default=1, desc=""), rule_id="0103", name="info-message", diff --git a/tests/utest/reports/test_sarif_report.py b/tests/utest/reports/test_sarif_report.py index 5fb2a0ee..3746ca69 100644 --- a/tests/utest/reports/test_sarif_report.py +++ b/tests/utest/reports/test_sarif_report.py @@ -23,9 +23,9 @@ def test_configure_filename(self): assert report.report_filename == filename @pytest.mark.parametrize("compare_runs", [True, False]) - def test_sarif_report(self, rule, rule2, compare_runs, tmp_path): + def test_sarif_report(self, rule, rule2, community_rule, custom_rule, compare_runs, tmp_path): config = Config(from_cli=False) - rules = {m.rule_id: m for m in (rule, rule2)} + rules = {m.rule_id: m for m in (rule, rule2, community_rule, custom_rule)} source1_rel = "tests/atest/rules/comments/ignored-data/test.robot" source2_rel = "tests/atest/rules/misc/empty-return/test.robot" source1 = str(config.root / source1_rel) @@ -49,13 +49,21 @@ def test_sarif_report(self, rule, rule2, compare_runs, tmp_path): (rule2, source1, 50, 51, 10, None), (rule, source2, 50, None, 10, 12), (rule2, source2, 11, 15, 10, 15), + (community_rule, source2, 50, None, 10, None), + (custom_rule, source2, 50, None, 10, None), ] ] + def severity_to_sarif(severity: str) -> str: + severity = severity.lower() + if severity == "info": + return "note" + return severity + def get_expected_result(message, level, source): return { "ruleId": message.rule_id, - "level": level, + "level": severity_to_sarif(level), "message": {"text": message.desc}, "locations": [ { @@ -86,13 +94,13 @@ def get_expected_result(message, level, source): { "id": r.rule_id, "name": r.name, - "helpUri": f"https://robocop.readthedocs.io/en/{__version__}/rules_list.html#{r.name}", + "helpUri": r.help_url or "", "shortDescription": {"text": r.msg}, "fullDescription": {"text": r.docs}, - "defaultConfiguration": {"level": r.default_severity.name.lower()}, + "defaultConfiguration": {"level": severity_to_sarif(r.default_severity.name)}, "help": {"text": r.docs, "markdown": r.docs}, } - for r in (rule, rule2) + for r in (rule, rule2, community_rule, custom_rule) ], } }, @@ -102,6 +110,8 @@ def get_expected_result(message, level, source): get_expected_result(issues[1], "error", source1_rel), get_expected_result(issues[2], "warning", source2_rel), get_expected_result(issues[3], "error", source2_rel), + get_expected_result(issues[4], "info", source2_rel), + get_expected_result(issues[5], "error", source2_rel), ], } ], diff --git a/tests/utest/test_api.py b/tests/utest/test_api.py index a3415dae..c532c44b 100644 --- a/tests/utest/test_api.py +++ b/tests/utest/test_api.py @@ -8,7 +8,6 @@ from robocop.exceptions import InvalidArgumentError from robocop.rules import Message, Rule, RuleParam, RuleSeverity from robocop.utils import issues_to_lsp_diagnostic -from robocop.version import __version__ @pytest.fixture @@ -19,6 +18,17 @@ def rule(): name="some-message", msg="Some description", severity=RuleSeverity.WARNING, + help_url="https://fake.com/rules-docs", + ) + + +@pytest.fixture +def rule_without_help(): + return Rule( + rule_id="0102", + name="another-message", + msg="Another description", + severity=RuleSeverity.INFO, ) @@ -86,7 +96,7 @@ def test_invalid_config(self): Config(root=config_path) assert r"Invalid configuration for Robocop:\nunrecognized arguments: --some" in str(exception) - def test_lsp_diagnostic(self, rule): + def test_lsp_diagnostic(self, rule, rule_without_help): issues = [ Message( rule=rule, @@ -99,8 +109,8 @@ def test_lsp_diagnostic(self, rule): end_col=50, ), Message( - rule=rule, - msg=rule.get_message(), + rule=rule_without_help, + msg=rule_without_help.get_message(), source=r"C:\directory\file.robot", node=None, lineno=1, @@ -120,7 +130,7 @@ def test_lsp_diagnostic(self, rule): "source": "robocop", "message": "Some description", "codeDescription": { - "href": f"https://robocop.readthedocs.io/en/{__version__}/rules_list.html#some-message", + "href": "https://fake.com/rules-docs", }, }, { @@ -128,13 +138,10 @@ def test_lsp_diagnostic(self, rule): "start": {"line": 0, "character": 0}, "end": {"line": 0, "character": 0}, }, - "severity": 2, - "code": "0101", + "severity": 3, + "code": "0102", "source": "robocop", - "message": "Some description", - "codeDescription": { - "href": f"https://robocop.readthedocs.io/en/{__version__}/rules_list.html#some-message", - }, + "message": "Another description", }, ] diagnostic = issues_to_lsp_diagnostic(issues) diff --git a/tests/utest/test_community_rule.py b/tests/utest/test_community_rule.py new file mode 100644 index 00000000..27819ce6 --- /dev/null +++ b/tests/utest/test_community_rule.py @@ -0,0 +1,24 @@ +from robocop import __version__ +from robocop.rules import CommunityRule, Rule, RuleSeverity + + +class TestDefaultRule: + def test_instance_of_rule(self): + r = CommunityRule( + rule_id="000001", + name="amazing-rule", + msg="Amazing error message", + severity=RuleSeverity.WARNING, + ) + + assert isinstance(r, Rule) + + def test_has_help_url(self): + r = CommunityRule( + rule_id="000001", + name="amazing-rule", + msg="Amazing error message", + severity=RuleSeverity.WARNING, + ) + + assert r.help_url == f"https://robocop.readthedocs.io/en/{__version__}/community_rules.html#amazing-rule" diff --git a/tests/utest/test_default_rule.py b/tests/utest/test_default_rule.py new file mode 100644 index 00000000..cbc13f05 --- /dev/null +++ b/tests/utest/test_default_rule.py @@ -0,0 +1,24 @@ +from robocop import __version__ +from robocop.rules import DefaultRule, Rule, RuleSeverity + + +class TestDefaultRule: + def test_instance_of_rule(self): + r = DefaultRule( + rule_id="0001", + name="amazing-rule", + msg="Amazing error message", + severity=RuleSeverity.WARNING, + ) + + assert isinstance(r, Rule) + + def test_has_help_url(self): + r = DefaultRule( + rule_id="0001", + name="amazing-rule", + msg="Amazing error message", + severity=RuleSeverity.WARNING, + ) + + assert r.help_url == f"https://robocop.readthedocs.io/en/{__version__}/rules_list.html#amazing-rule"