Repository: MarketSquare/robotframework-robocop
Pull request #1334 (2025-03-18 12:16:10)

Unify exceptions under errors module
Merge linter and formatter exceptions into common module. Add more negative tests to increase coverage.


Patch:
diff --git a/src/robocop/config.py b/src/robocop/config.py
index 74bd937..97c6757 100644
--- a/src/robocop/config.py
+++ b/src/robocop/config.py
@@ -20,7 +20,7 @@ from robocop import errors, files
 from robocop.formatter import formatters
 from robocop.formatter.skip import SkipConfig
 from robocop.formatter.utils import misc  # TODO merge with linter misc
-from robocop.linter import exceptions, rules
+from robocop.linter import rules
 from robocop.linter.rules import BaseChecker, ProjectChecker, RuleSeverity
 from robocop.linter.utils.misc import compile_rule_pattern
 from robocop.linter.utils.version_matching import Version
@@ -279,9 +279,7 @@ class LinterConfig:
                 name, param_and_value = config.split(".", maxsplit=1)
                 param, value = param_and_value.split("=", maxsplit=1)
             except ValueError:
-                raise exceptions.ConfigGeneralError(
-                    f"Provided invalid config: '{config}' (general pattern: <rule/report>.<param>=<value>)"
-                ) from None
+                raise errors.InvalidConfigurationFormatError(config) from None
             if name in self._rules:
                 rule = self._rules[name]
                 if rule.deprecated:
@@ -289,7 +287,7 @@ class LinterConfig:
                 else:
                     rule.configure(param, value)
             # else:  TODO
-            #     raise exceptions.RuleOrReportDoesNotExist(name, self._rules)
+            #     raise errors.RuleOrReportDoesNotExist(name, self._rules)
 
     def split_inclusions_exclusions_into_patterns(self):
         if self.select:
@@ -308,7 +306,7 @@ class LinterConfig:
     #     def validate_rules_exists_and_not_deprecated(self, rules: dict[str, "Rule"]):
     #         for rule in chain(self.include, self.exclude):
     #             if rule not in rules:
-    #                 raise exceptions.RuleDoesNotExist(rule, rules) from None
+    #                 raise errors.RuleDoesNotExist(rule, rules) from None
     #             rule_def = rules[rule]
     #             if rule_def.deprecated:
     #                 print(rule_def.deprecation_warning)
@@ -442,7 +440,7 @@ class FormatterConfig:
             param, value = param_value.split("=", maxsplit=1)
             name, param, value = name.strip(), param.strip(), value.strip()
         except ValueError:
-            raise errors.InvalidParameterFormatError(configure) from None
+            raise errors.InvalidConfigurationFormatError(configure) from None
         return name, param, value
 
     @property
diff --git a/src/robocop/errors.py b/src/robocop/errors.py
index fbc2670..1c4d14a 100644
--- a/src/robocop/errors.py
+++ b/src/robocop/errors.py
@@ -1,5 +1,13 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
+
 import typer
 from rich.console import Console
+from robot.errors import DataError
+
+if TYPE_CHECKING:
+    from robocop.linter.rules import Rule
 
 
 class FatalError(typer.Exit):
@@ -9,10 +17,110 @@ class FatalError(typer.Exit):
         super().__init__(code=2)
 
 
-class RobocopFatalError(ValueError):
+class InvalidConfigurationError(FatalError):
+    pass
+
+
+class InvalidParameterValueError(InvalidConfigurationError):
+    def __init__(self, formatter, param, value, msg):
+        exc_msg = f"{formatter}: Invalid '{param}' parameter value: '{value}'. {msg}"
+        super().__init__(exc_msg)
+
+
+class InvalidParameterError(InvalidConfigurationError):
+    def __init__(self, formatter, similar):
+        super().__init__(
+            f"{formatter}: Failed to import. Verify if correct name or configuration was provided.{similar}"
+        )
+
+
+class ImportFormatterError(InvalidConfigurationError):
     pass
 
 
-class InvalidParameterFormatError(RobocopFatalError):
+class InvalidConfigurationFormatError(InvalidConfigurationError):
     def __init__(self, name: str):
-        super().__init__(f"{name}: Invalid parameter format. Pass parameters using Name.param_name=value syntax.")
+        super().__init__(f"'{name}': Invalid configuration format. Pass parameters using name.param_name=value syntax.")
+
+
+class ConfigurationError(FatalError):
+    pass
+
+
+class InvalidExternalCheckerError(FatalError):
+    def __init__(self, path):
+        msg = f'Fatal error: Failed to load external rules from file "{path}". Verify if the file exists'
+        super().__init__(msg)
+
+
+class RuleParamNotFoundError(FatalError):  # TODO, not used
+    def __init__(self, rule, param, checker):
+        super().__init__(
+            f"Rule `{rule.name}` in `{checker.__class__.__name__}` checker does not contain `{param}` param. "
+            f"Available params:\n    {rule.available_configurables()[1]}"
+        )
+
+
+class RuleParamFailedInitError(FatalError):
+    def __init__(self, param, value, err):
+        desc = f"    Parameter info: {param.desc}" if param.desc else ""
+        super().__init__(
+            f"Failed to configure param `{param.name}` with value `{value}`. Received error `{err}`.\n"
+            f"    Parameter type: {param.converter}\n" + desc
+        )
+
+
+class InvalidReportName(FatalError):
+    def __init__(self, report, reports):
+        from robocop.linter.utils import RecommendationFinder
+
+        report_names = sorted([*list(reports.keys()), "all"])
+        similar = RecommendationFinder().find_similar(report, report_names)
+        msg = f"Provided report '{report}' does not exist. {similar}"
+        super().__init__(msg)
+
+
+class RuleDoesNotExist(FatalError):  # not used atm
+    def __init__(self, rule: str, rules: dict[str, Rule]):
+        from robocop.linter.utils import RecommendationFinder
+
+        similar = RecommendationFinder().find_similar(rule, rules)
+        msg = f"Provided rule '{rule}' does not exist. {similar}"
+        super().__init__(msg)
+
+
+class RuleOrReportDoesNotExist(FatalError):  # not used atm
+    def __init__(self, rule: str, rules: dict[str, Rule]):
+        from robocop.linter.utils import RecommendationFinder
+
+        similar = RecommendationFinder().find_similar(rule, rules)
+        msg = f"Provided rule or report '{rule}' does not exist. {similar}"
+        super().__init__(msg)
+
+
+class RobotFrameworkParsingError(Exception):
+    def __init__(self):
+        msg = (
+            "Fatal exception occurred when using Robot Framework parsing module. "
+            "Consider updating Robot Framework to recent stable version."
+        )
+        super().__init__(msg)
+
+
+def handle_robot_errors(func):
+    """
+    Handle bugs in Robot Framework.
+
+    If the user uses older version of Robot Framework, it may fail while parsing the
+    source code due to bug that is already fixed in the more recent version.
+    """
+
+    def wrap_errors(*args, **kwargs):  # noqa: ANN202
+        try:
+            return func(*args, **kwargs)
+        except DataError:
+            raise
+        except Exception as err:
+            raise RobotFrameworkParsingError from err
+
+    return wrap_errors
diff --git a/src/robocop/formatter/exceptions.py b/src/robocop/formatter/exceptions.py
deleted file mode 100644
index 3b4b287..0000000
--- a/src/robocop/formatter/exceptions.py
+++ /dev/null
@@ -1,23 +0,0 @@
-from __future__ import annotations
-
-from robocop.errors import FatalError
-
-
-class RobocopConfigError(FatalError): ...
-
-
-class InvalidParameterValueError(RobocopConfigError):
-    def __init__(self, formatter, param, value, msg):
-        exc_msg = f"{formatter}: Invalid '{param}' parameter value: '{value}'. {msg}"
-        super().__init__(exc_msg)
-
-
-class InvalidParameterError(RobocopConfigError):
-    def __init__(self, formatter, similar):
-        super().__init__(
-            f"{formatter}: Failed to import. Verify if correct name or configuration was provided.{similar}"
-        )
-
-
-class ImportFormatterError(RobocopConfigError):
-    pass
diff --git a/src/robocop/formatter/formatters/AlignVariablesSection.py b/src/robocop/formatter/formatters/AlignVariablesSection.py
index 278e9cb..f8c2b77 100644
--- a/src/robocop/formatter/formatters/AlignVariablesSection.py
+++ b/src/robocop/formatter/formatters/AlignVariablesSection.py
@@ -5,8 +5,8 @@ from collections import defaultdict
 from robot.api.parsing import Token
 from robot.parsing.model import Statement
 
+from robocop.errors import InvalidParameterValueError
 from robocop.formatter.disablers import skip_section_if_disabled
-from robocop.formatter.exceptions import InvalidParameterValueError
 from robocop.formatter.formatters import Formatter
 from robocop.formatter.utils import misc
 
diff --git a/src/robocop/formatter/formatters/GenerateDocumentation.py b/src/robocop/formatter/formatters/GenerateDocumentation.py
index 35dd5e8..72f4d06 100644
--- a/src/robocop/formatter/formatters/GenerateDocumentation.py
+++ b/src/robocop/formatter/formatters/GenerateDocumentation.py
@@ -7,7 +7,7 @@ from jinja2 import Template
 from jinja2.exceptions import TemplateError
 from robot.api.parsing import Documentation, ModelVisitor, Token
 
-from robocop.formatter.exceptions import InvalidParameterValueError
+from robocop.errors import InvalidParameterValueError
 from robocop.formatter.formatters import Formatter
 
 GOOGLE_TEMPLATE = """    Short description.
diff --git a/src/robocop/formatter/formatters/IndentNestedKeywords.py b/src/robocop/formatter/formatters/IndentNestedKeywords.py
index 71f9ee8..c62ba7c 100644
--- a/src/robocop/formatter/formatters/IndentNestedKeywords.py
+++ b/src/robocop/formatter/formatters/IndentNestedKeywords.py
@@ -1,7 +1,7 @@
 from robot.api.parsing import Token
 
+from robocop.errors import InvalidParameterValueError
 from robocop.formatter.disablers import skip_if_disabled
-from robocop.formatter.exceptions import InvalidParameterValueError
 from robocop.formatter.formatters import Formatter
 from robocop.formatter.skip import Skip
 from robocop.formatter.utils import misc
diff --git a/src/robocop/formatter/formatters/MergeAndOrderSections.py b/src/robocop/formatter/formatters/MergeAndOrderSections.py
index 49bb7fd..bf9579b 100644
--- a/src/robocop/formatter/formatters/MergeAndOrderSections.py
+++ b/src/robocop/formatter/formatters/MergeAndOrderSections.py
@@ -1,7 +1,7 @@
 from robot.api.parsing import EmptyLine, SectionHeader, Token
 from robot.parsing.model.statements import Statement
 
-from robocop.formatter.exceptions import InvalidParameterValueError
+from robocop.errors import InvalidParameterValueError
 
 try:
     from robot.api.parsing import InlineIfHeader
diff --git a/src/robocop/formatter/formatters/NormalizeAssignments.py b/src/robocop/formatter/formatters/NormalizeAssignments.py
index 260a8da..56151fd 100644
--- a/src/robocop/formatter/formatters/NormalizeAssignments.py
+++ b/src/robocop/formatter/formatters/NormalizeAssignments.py
@@ -4,8 +4,8 @@ from collections import Counter
 
 from robot.api.parsing import Token, Variable
 
+from robocop.errors import InvalidParameterValueError
 from robocop.formatter.disablers import skip_if_disabled, skip_section_if_disabled
-from robocop.formatter.exceptions import InvalidParameterValueError
 from robocop.formatter.formatters import Formatter
 from robocop.formatter.skip import Skip
 
diff --git a/src/robocop/formatter/formatters/NormalizeTags.py b/src/robocop/formatter/formatters/NormalizeTags.py
index 8166d4f..884b145 100644
--- a/src/robocop/formatter/formatters/NormalizeTags.py
+++ b/src/robocop/formatter/formatters/NormalizeTags.py
@@ -1,7 +1,7 @@
 from robot.api.parsing import Token
 
+from robocop.errors import InvalidParameterValueError
 from robocop.formatter.disablers import skip_section_if_disabled
-from robocop.formatter.exceptions import InvalidParameterValueError
 from robocop.formatter.formatters import Formatter
 from robocop.formatter.utils import variable_matcher
 
diff --git a/src/robocop/formatter/formatters/OrderSettings.py b/src/robocop/formatter/formatters/OrderSettings.py
index 71f7625..4919377 100644
--- a/src/robocop/formatter/formatters/OrderSettings.py
+++ b/src/robocop/formatter/formatters/OrderSettings.py
@@ -1,7 +1,7 @@
 from robot.api.parsing import Comment, EmptyLine, Token
 
+from robocop.errors import InvalidConfigurationError, InvalidParameterValueError
 from robocop.formatter.disablers import skip_if_disabled, skip_section_if_disabled
-from robocop.formatter.exceptions import InvalidParameterValueError, RobocopConfigError
 from robocop.formatter.formatters import Formatter
 
 
@@ -19,7 +19,7 @@ class DuplicateInSettingsOrderError(InvalidParameterValueError):
         super().__init__(formatter, param_name, provided_order, msg)
 
 
-class SettingInBothOrdersError(RobocopConfigError):
+class SettingInBothOrdersError(InvalidConfigurationError):
     def __init__(self, formatter, first_order, second_order, duplicates):
         names = ",".join(setting.lower() for setting in duplicates)
         msg = (
diff --git a/src/robocop/formatter/formatters/OrderSettingsSection.py b/src/robocop/formatter/formatters/OrderSettingsSection.py
index 0ae091e..16f20b3 100644
--- a/src/robocop/formatter/formatters/OrderSettingsSection.py
+++ b/src/robocop/formatter/formatters/OrderSettingsSection.py
@@ -5,8 +5,8 @@ from collections import defaultdict
 from robot.api.parsing import Comment, EmptyLine, LibraryImport, Token
 from robot.libraries import STDLIBS
 
+from robocop.errors import InvalidParameterValueError
 from robocop.formatter.disablers import skip_section_if_disabled
-from robocop.formatter.exceptions import InvalidParameterValueError
 from robocop.formatter.formatters import Formatter
 
 
diff --git a/src/robocop/formatter/formatters/RemoveEmptySettings.py b/src/robocop/formatter/formatters/RemoveEmptySettings.py
index 759e612..65a0d2f 100644
--- a/src/robocop/formatter/formatters/RemoveEmptySettings.py
+++ b/src/robocop/formatter/formatters/RemoveEmptySettings.py
@@ -2,8 +2,8 @@ import ast
 
 from robot.api.parsing import Token
 
+from robocop.errors import InvalidParameterValueError
 from robocop.formatter.disablers import skip_section_if_disabled
-from robocop.formatter.exceptions import InvalidParameterValueError
 from robocop.formatter.formatters import Formatter
 
 
diff --git a/src/robocop/formatter/formatters/RenameKeywords.py b/src/robocop/formatter/formatters/RenameKeywords.py
index 00821ef..aedd735 100644
--- a/src/robocop/formatter/formatters/RenameKeywords.py
+++ b/src/robocop/formatter/formatters/RenameKeywords.py
@@ -4,8 +4,8 @@ import re
 
 from robot.api.parsing import Token
 
+from robocop.errors import InvalidParameterValueError
 from robocop.formatter.disablers import skip_if_disabled, skip_section_if_disabled
-from robocop.formatter.exceptions import InvalidParameterValueError
 from robocop.formatter.formatters import Formatter
 from robocop.formatter.utils import misc, variable_matcher
 from robocop.parsing.run_keywords import RUN_KEYWORDS
diff --git a/src/robocop/formatter/formatters/RenameVariables.py b/src/robocop/formatter/formatters/RenameVariables.py
index e48fb54..033f0c1 100644
--- a/src/robocop/formatter/formatters/RenameVariables.py
+++ b/src/robocop/formatter/formatters/RenameVariables.py
@@ -9,8 +9,8 @@ from robot.api.parsing import Arguments, Token
 from robot.errors import VariableError
 from robot.variables.search import search_variable
 
+from robocop.errors import InvalidParameterValueError
 from robocop.formatter.disablers import skip_if_disabled, skip_section_if_disabled
-from robocop.formatter.exceptions import InvalidParameterValueError
 from robocop.formatter.formatters import Formatter
 from robocop.formatter.utils import misc, variable_matcher
 
diff --git a/src/robocop/formatter/formatters/Translate.py b/src/robocop/formatter/formatters/Translate.py
index 0ca630c..d5c5c1c 100644
--- a/src/robocop/formatter/formatters/Translate.py
+++ b/src/robocop/formatter/formatters/Translate.py
@@ -13,8 +13,8 @@ try:
 except ImportError:  # RF < 6.1
     ImplicitCommentSection = None
 
+from robocop.errors import InvalidParameterValueError
 from robocop.formatter.disablers import skip_if_disabled, skip_section_if_disabled
-from robocop.formatter.exceptions import InvalidParameterValueError
 from robocop.formatter.formatters import Formatter
 
 
diff --git a/src/robocop/formatter/formatters/__init__.py b/src/robocop/formatter/formatters/__init__.py
index b8507e5..07bb95c 100644
--- a/src/robocop/formatter/formatters/__init__.py
+++ b/src/robocop/formatter/formatters/__init__.py
@@ -26,7 +26,7 @@ from robot.api.parsing import ModelTransformer
 from robot.errors import DataError
 from robot.utils.importer import Importer
 
-from robocop.formatter.exceptions import ImportFormatterError, InvalidParameterError
+from robocop.errors import ImportFormatterError, InvalidParameterError
 from robocop.formatter.skip import SKIP_OPTIONS, Skip, SkipConfig
 from robocop.formatter.utils import misc
 
@@ -166,8 +166,7 @@ def order_formatters(formatters, module):
     for name in formatters_list:
         if name not in formatters:
             raise ImportFormatterError(
-                f"Importing formatter '{name}' declared in TRANSFORMERS list failed. "
-                "Verify if correct name was provided."
+                f"Importing formatter '{name}' declared in FORMATTERS list failed. Verify if correct name was provided."
             ) from None
         ordered_formatters[name] = formatters[name]
     return ordered_formatters
diff --git a/src/robocop/formatter/formatters/aligners_core.py b/src/robocop/formatter/formatters/aligners_core.py
index 75f8b17..535fcc9 100644
--- a/src/robocop/formatter/formatters/aligners_core.py
+++ b/src/robocop/formatter/formatters/aligners_core.py
@@ -12,8 +12,8 @@ except ImportError:
 
 from typing import TYPE_CHECKING
 
+from robocop.errors import InvalidParameterValueError
 from robocop.formatter.disablers import skip_if_disabled
-from robocop.formatter.exceptions import InvalidParameterValueError
 from robocop.formatter.formatters import Formatter
 from robocop.formatter.utils import misc
 
diff --git a/src/robocop/linter/exceptions.py b/src/robocop/linter/exceptions.py
deleted file mode 100644
index 1c2266d..0000000
--- a/src/robocop/linter/exceptions.py
+++ /dev/null
@@ -1,104 +0,0 @@
-from typing import TYPE_CHECKING
-
-if TYPE_CHECKING:
-    from robocop.linter.rules import Rule
-
-import robot.errors
-
-from robocop.errors import FatalError
-
-
-class ConfigGeneralError(FatalError):
-    pass
-
-
-class InvalidExternalCheckerError(FatalError):
-    def __init__(self, path):
-        msg = f'Fatal error: Failed to load external rules from file "{path}". Verify if the file exists'
-        super().__init__(msg)
-
-
-class InvalidArgumentError(FatalError):
-    def __init__(self, msg):
-        super().__init__(f"Invalid configuration for Robocop:\n{msg}")
-
-
-class RuleNotFoundError(FatalError):
-    def __init__(self, rule, checker):
-        super().__init__(
-            f"{checker.__class__.__name__} checker does not contain rule `{rule}`. "
-            f"Available rules: {', '.join(checker.rules.keys())}"
-        )
-
-
-class RuleParamNotFoundError(FatalError):  # TODO
-    def __init__(self, rule, param, checker):
-        super().__init__(
-            f"Rule `{rule.name}` in `{checker.__class__.__name__}` checker does not contain `{param}` param. "
-            f"Available params:\n    {rule.available_configurables()[1]}"
-        )
-
-
-class RuleParamFailedInitError(FatalError):  # TODO
-    def __init__(self, param, value, err):
-        desc = f"    Parameter info: {param.desc}" if param.desc else ""
-        super().__init__(
-            f"Failed to configure param `{param.name}` with value `{value}`. Received error `{err}`.\n"
-            f"    Parameter type: {param.converter}\n" + desc
-        )
-
-
-class InvalidReportName(ConfigGeneralError):
-    def __init__(self, report, reports):
-        from robocop.linter.utils import RecommendationFinder
-
-        report_names = sorted([*list(reports.keys()), "all"])
-        similar = RecommendationFinder().find_similar(report, report_names)
-        msg = f"Provided report '{report}' does not exist. {similar}"
-        super().__init__(msg)
-
-
-class RuleDoesNotExist(ConfigGeneralError):
-    def __init__(self, rule: str, rules: dict[str, "Rule"]):
-        from robocop.linter.utils import RecommendationFinder
-
-        similar = RecommendationFinder().find_similar(rule, rules)
-        msg = f"Provided rule '{rule}' does not exist. {similar}"
-        super().__init__(msg)
-
-
-class RuleOrReportDoesNotExist(ConfigGeneralError):
-    def __init__(self, rule: str, rules: dict[str, "Rule"]):
-        from robocop.linter.utils import RecommendationFinder
-
-        similar = RecommendationFinder().find_similar(rule, rules)
-        msg = f"Provided rule or report '{rule}' does not exist. {similar}"
-        super().__init__(msg)
-
-
-class RobotFrameworkParsingError(Exception):
-    def __init__(self):
-        msg = (
-            "Fatal exception occurred when using Robot Framework parsing module. "
-            "Consider updating Robot Framework to recent stable version."
-        )
-        super().__init__(msg)
-
-
-def handle_robot_errors(func):
-    """
-    Handle bugs in Robot Framework.
-
-    If the user uses older version of Robot Framework, it may fail while parsing the
-    source code due to bug that is already fixed in the more recent version.
-    """
-
-    def wrap_errors(*args, **kwargs):  # noqa: ANN202
-        try:
-            return func(*args, **kwargs)
-        except robot.errors.DataError:
-            raise
-        except Exception as err:
-            raise RobotFrameworkParsingError from err
-
-    return wrap_errors
diff --git a/src/robocop/linter/reports/__init__.py b/src/robocop/linter/reports/__init__.py
index bfc854c..367c4fb 100644
--- a/src/robocop/linter/reports/__init__.py
+++ b/src/robocop/linter/reports/__init__.py
@@ -5,9 +5,8 @@ import json
 from pathlib import Path
 from typing import NoReturn
 
-import robocop.linter.exceptions
+from robocop import errors
 from robocop.config import Config
-from robocop.errors import FatalError
 from robocop.linter.rules import RobocopImporter
 from robocop.linter.utils.misc import get_robocop_cache_directory
 
@@ -31,9 +30,7 @@ class Report:
         self.config = config
 
     def configure(self, name: str, value: str) -> None:  # noqa: ARG002
-        raise robocop.linter.exceptions.ConfigGeneralError(
-            f"Provided param '{name}' for report '{self.name}' does not exist"
-        )
+        raise errors.ConfigurationError(f"Provided param '{name}' for report '{self.name}' does not exist")
 
     def generate_report(self, **kwargs) -> NoReturn:
         raise NotImplementedError
@@ -59,7 +56,7 @@ class JsonFileReport(Report):
             with open(output_path, "w") as fp:
                 json.dump(report, fp, indent=4)
         except OSError as err:
-            raise FatalError(f"Failed to write {report_type} report to {output_path}: {err}") from None
+            raise errors.FatalError(f"Failed to write {report_type} report to {output_path}: {err}") from None
         print(f"Generated {report_type} report at {self.output_path}")
 
 
@@ -114,7 +111,7 @@ def get_reports(config: Config):
                 if report_class.NO_ALL and name not in enabled_reports:
                     enabled_reports[name] = report_class
         elif report not in reports:
-            raise robocop.linter.exceptions.InvalidReportName(report, reports)
+            raise errors.InvalidReportName(report, reports)
         elif report not in enabled_reports:
             enabled_reports[report] = reports[report]
     for report, report_class in reports.items():
diff --git a/src/robocop/linter/reports/timestamp_report.py b/src/robocop/linter/reports/timestamp_report.py
index eeaafc3..2f5527c 100644
--- a/src/robocop/linter/reports/timestamp_report.py
+++ b/src/robocop/linter/reports/timestamp_report.py
@@ -3,8 +3,8 @@ from warnings import warn
 
 import pytz
 
-import robocop.linter.exceptions
 import robocop.linter.reports
+from robocop import errors
 from robocop.config import Config
 
 
@@ -82,9 +82,9 @@ class TimestampReport(robocop.linter.reports.Report):
             else:
                 timezone_code = pytz.timezone(self.timezone)
             return datetime.now(timezone_code).strftime(self.format)
-        except pytz.exceptions.UnknownTimeZoneError as err:
-            raise robocop.linter.exceptions.ConfigGeneralError(
+        except pytz.exceptions.UnknownTimeZoneError:
+            raise errors.ConfigurationError(
                 f"Provided timezone '{self.timezone}' for report '{self.name}' is not valid. "
                 "Use timezone names like `Europe\\Helsinki`."
                 "See: https://en.wikipedia.org/wiki/List_of_tz_database_time_zone"
-            ) from err
+            ) from None
diff --git a/src/robocop/linter/rules/__init__.py b/src/robocop/linter/rules/__init__.py
index 84e5646..2db167d 100644
--- a/src/robocop/linter/rules/__init__.py
+++ b/src/robocop/linter/rules/__init__.py
@@ -36,11 +36,8 @@ from typing import TYPE_CHECKING, Any, Callable, NoReturn
 
 from robot.utils import FileReader
 
-from robocop.linter import exceptions, sonar_qube
+from robocop import errors
 from robocop.linter.diagnostics import Diagnostic
-from robocop.linter.exceptions import (
-    InvalidExternalCheckerError,
-)
 from robocop.linter.utils.version_matching import Version, VersionSpecifier
 
 try:
@@ -55,6 +52,7 @@ if TYPE_CHECKING:
     from robot.parsing import File
 
     from robocop.config import LinterConfig
+    from robocop.linter import sonar_qube
 
 
 @total_ordering
@@ -133,7 +131,7 @@ class RuleSeverity(Enum):
                 # it will be reraised as RuleParamFailedInitError
                 raise ValueError(hint)
             # invalid severity threshold
-            raise exceptions.InvalidArgumentError(f"Invalid severity value '{value}'. {hint}") from None
+            raise errors.ConfigurationError(f"Invalid severity value '{value}'. {hint}") from None
         return severity
 
     def __str__(self):
@@ -229,7 +227,7 @@ class RuleParam:
         try:
             self._value = self.converter(value)
         except ValueError as err:
-            raise exceptions.RuleParamFailedInitError(self, value, str(err)) from None
+            raise errors.RuleParamFailedInitError(self, value, str(err)) from None
 
     @property
     def param_type(self):
@@ -286,7 +284,7 @@ class SeverityThreshold:
         if severity is None:
             severity_values = ", ".join(sev.value for sev in RuleSeverity)
             hint = f"Choose one from: {severity_values}."
-            raise exceptions.InvalidArgumentError(f"Invalid severity value '{value}'. {hint}") from None
+            raise errors.ConfigurationError(f"Invalid severity value '{value}'. {hint}") from None
         return severity
 
     def set_thresholds(self, value) -> None:
@@ -296,7 +294,7 @@ class SeverityThreshold:
             try:
                 sev, param_value = pair.split("=")
             except ValueError:
-                raise exceptions.InvalidArgumentError(
+                raise errors.ConfigurationError(
                     f"Invalid severity value '{value}'. "
                     f"It should be list of `severity=param_value` pairs, separated by `:`."
                 ) from None
@@ -471,7 +469,7 @@ class Rule:
     def configure(self, param: str, value: str) -> None:
         if param not in self.config:
             count, configurables_text = self.available_configurables()
-            raise exceptions.ConfigGeneralError(
+            raise errors.ConfigurationError(
                 f"Provided param '{param}' for rule '{self.name}' does not exist. "
                 f"Available configurable{'' if count == 1 else 's'} for this rule:\n"
                 f"    {configurables_text}"
@@ -686,7 +684,7 @@ class RobocopImporter:
                     yield from self._iter_imports(Path(mod.__file__))
                     yield mod
                 except ImportError:
-                    raise InvalidExternalCheckerError(path) from None
+                    raise errors.InvalidExternalCheckerError(path) from None
 
     @staticmethod
     def _import_module_from_file(file_path):
diff --git a/src/robocop/linter/runner.py b/src/robocop/linter/runner.py
index e8669d9..abb3dec 100644
--- a/src/robocop/linter/runner.py
+++ b/src/robocop/linter/runner.py
@@ -6,7 +6,8 @@ import typer
 from robot.api import get_init_model, get_model, get_resource_model
 from robot.errors import DataError
 
-from robocop.linter import exceptions, reports
+from robocop import errors
+from robocop.linter import reports
 from robocop.linter.diagnostics import Diagnostics
 from robocop.linter.reports import save_reports_result_to_cache
 from robocop.linter.utils.disablers import DisablersFinder
@@ -133,9 +134,7 @@ class RobocopLinter:
                 name, param_and_value = config.split(".", maxsplit=1)
                 param, value = param_and_value.split("=", maxsplit=1)
             except ValueError:
-                raise exceptions.ConfigGeneralError(
-                    f"Provided invalid config: '{config}' (general pattern: <rule/report>.<param>=<value>)"
-                ) from None
+                raise errors.InvalidConfigurationFormatError(config) from None
             if name in self.reports:
                 self.reports[name].configure(param, value)
 
diff --git a/src/robocop/linter/utils/file_types.py b/src/robocop/linter/utils/file_types.py
index 4cc8a69..6264c5f 100644
--- a/src/robocop/linter/utils/file_types.py
+++ b/src/robocop/linter/utils/file_types.py
@@ -12,7 +12,7 @@ from robot.api import get_init_model, get_model, get_resource_model
 from robot.errors import DataError
 from robot.utils.robotpath import find_file
 
-import robocop.linter.exceptions
+from robocop import errors
 from robocop.linter.utils.misc import rf_supports_lang
 
 if TYPE_CHECKING:
@@ -20,7 +20,7 @@ if TYPE_CHECKING:
     from robot.parsing.model.statements import ResourceImport
 
 
-@robocop.linter.exceptions.handle_robot_errors
+@errors.handle_robot_errors
 def get_resource_with_lang(get_resource_method: Callable, source: Path, lang: str | None) -> File:
     if rf_supports_lang():
         return get_resource_method(source, lang=lang)


Test patch:
diff --git a/src/robocop/formatter/formatters/RenameTestCases.py b/src/robocop/formatter/formatters/RenameTestCases.py
index 705cff6..363f7bf 100644
--- a/src/robocop/formatter/formatters/RenameTestCases.py
+++ b/src/robocop/formatter/formatters/RenameTestCases.py
@@ -4,8 +4,8 @@ import re
 
 from robot.api.parsing import Token
 
+from robocop.errors import InvalidParameterValueError
 from robocop.formatter.disablers import skip_if_disabled, skip_section_if_disabled
-from robocop.formatter.exceptions import InvalidParameterValueError
 from robocop.formatter.formatters import Formatter
 
 IGNORE_CHARS = {"(", "[", "{", "!", "?"}
diff --git a/tests/linter/cli/test_thresholds.py b/tests/linter/cli/test_thresholds.py
index 1b89b97..66b0f47 100644
--- a/tests/linter/cli/test_thresholds.py
+++ b/tests/linter/cli/test_thresholds.py
@@ -3,8 +3,8 @@ from unittest import mock
 
 import pytest
 
+from robocop import errors
 from robocop.config import RuleMatcher
-from robocop.linter import exceptions
 from robocop.linter.rules import Diagnostic, Rule, RuleParam, RuleSeverity, SeverityThreshold
 
 
@@ -87,18 +87,15 @@ class TestRuleSeverityThreshold:
         with mock.patch.dict(os.environ, {"COLUMNS": "200"}, clear=True):
             thresholds = SeverityThreshold("line_length")
             exp_error = (
-                "InvalidArgumentError: Invalid configuration for Robocop:\n"
-                "Invalid severity value 'error'. It should be list of `severity=param_value` pairs, separated by `:`.\n"
+                "ConfigurationError: Invalid severity value 'error'. "
+                "It should be list of `severity=param_value` pairs, separated by `:`.\n"
             )
-            with pytest.raises(exceptions.InvalidArgumentError):
+            with pytest.raises(errors.ConfigurationError):
                 thresholds.value = "error"
             _, err = capsys.readouterr()
             assert err == exp_error
-            exp_error = (
-                "InvalidArgumentError: Invalid configuration for Robocop:\n"
-                "Invalid severity value 'invalid'. Choose one from: I, W, E.\n"
-            )
-            with pytest.raises(exceptions.InvalidArgumentError):
+            exp_error = "ConfigurationError: Invalid severity value 'invalid'. Choose one from: I, W, E.\n"
+            with pytest.raises(errors.ConfigurationError):
                 thresholds.value = "invalid=100"
             _, err = capsys.readouterr()
             assert err == exp_error
diff --git a/tests/linter/reports/test_reports.py b/tests/linter/reports/test_reports.py
index 6ec3f56..c054cae 100644
--- a/tests/linter/reports/test_reports.py
+++ b/tests/linter/reports/test_reports.py
@@ -4,7 +4,7 @@ from pathlib import Path
 import platformdirs
 import pytest
 
-import robocop.linter.exceptions
+from robocop import errors
 from robocop.linter.reports import (
     ROBOCOP_CACHE_FILE,
     get_reports,
@@ -45,7 +45,7 @@ def test_get_reports_all(config):
 
 def test_get_unknown_report(config, capsys):
     config.linter.reports = ["all", "unknown"]
-    with pytest.raises(robocop.linter.exceptions.InvalidReportName):
+    with pytest.raises(errors.InvalidReportName):
         get_reports(config)
     _, err = capsys.readouterr()
     assert err == "InvalidReportName: Provided report 'unknown' does not exist. \n"
diff --git a/tests/linter/reports/test_timestamp_report.py b/tests/linter/reports/test_timestamp_report.py
index 820b936..898003b 100644
--- a/tests/linter/reports/test_timestamp_report.py
+++ b/tests/linter/reports/test_timestamp_report.py
@@ -2,7 +2,7 @@ import re
 
 import pytest
 
-from robocop.linter.exceptions import ConfigGeneralError
+from robocop.errors import ConfigurationError
 from robocop.linter.reports.timestamp_report import TimestampReport
 
 
@@ -35,14 +35,14 @@ class TestTimestampReport:
     )
     def test_timestamp_configure_invalid(self, name, value, expected, config):
         report = TimestampReport(config)
-        with pytest.raises(ConfigGeneralError) as err:
+        with pytest.raises(ConfigurationError) as err:
             report.configure(name, value)
         assert expected in str(err)
 
     def test_invalid_timestamp_report(self, config):
         report = TimestampReport(config)
         report.configure("timezone", "BAD")
-        with pytest.raises(ConfigGeneralError) as err:
+        with pytest.raises(ConfigurationError) as err:
             report.generate_report()
         assert "Provided timezone 'BAD' for report 'timestamp' is not valid." in str(err)
 
diff --git a/tests/linter/rules/arguments/argument_overwritten_before_usage/test_rule.py b/tests/linter/rules/arguments/argument_overwritten_before_usage/test_rule.py
index 3515441..ab51533 100644
--- a/tests/linter/rules/arguments/argument_overwritten_before_usage/test_rule.py
+++ b/tests/linter/rules/arguments/argument_overwritten_before_usage/test_rule.py
@@ -1,3 +1,5 @@
+import textwrap
+
 from tests.linter.utils import RuleAcceptance
 
 
@@ -36,3 +38,17 @@ class TestRuleAcceptance(RuleAcceptance):
             test_on_version=">=7",
             output_format="extended",
         )
+
+    def test_configuring_non_existing_param(self):
+        expected = textwrap.dedent(
+            """
+            ConfigurationError: Provided param 'unknown' for rule 'argument-overwritten-before-usage' does not exist. Available configurable for this rule:
+                severity = W
+                    type: parser
+                    info: Rule severity (E = Error, W = Warning, I = Info)
+            """  # noqa: E501
+        ).lstrip()
+        output = self.check_rule(
+            configure=["argument-overwritten-before-usage.unknown=value"], exit_code=2, compare_output=False
+        )
+        assert output == expected
diff --git a/tests/linter/rules/lengths/line_too_long/test_rule.py b/tests/linter/rules/lengths/line_too_long/test_rule.py
index aefa854..c1cefe8 100644
--- a/tests/linter/rules/lengths/line_too_long/test_rule.py
+++ b/tests/linter/rules/lengths/line_too_long/test_rule.py
@@ -1,3 +1,5 @@
+import textwrap
+
 from tests.linter.utils import RuleAcceptance
 
 
@@ -19,3 +21,35 @@ class TestRuleAcceptance(RuleAcceptance):
         self.check_rule(
             configure=["line-too-long.line_length=string"], expected_file="expected_exception.txt", exit_code=2
         )
+
+    def test_configuring_non_existing_param(self):
+        expected = textwrap.dedent(
+            """
+            ConfigurationError: Provided param 'unknown' for rule 'line-too-long' does not exist. Available configurables for this rule:
+                severity = W
+                    type: parser
+                    info: Rule severity (E = Error, W = Warning, I = Info)
+                severity_threshold
+                line_length = 120
+                    type: int
+                    info: number of characters allowed in line
+                ignore_pattern = re.compile('https?://\\\\S+')
+                    type: pattern_type
+                    info: ignore lines that contain configured pattern
+            """  # noqa: E501
+        ).lstrip()
+        output = self.check_rule(
+            src_files=["test.robot"], configure=["line-too-long.unknown=value"], exit_code=2, compare_output=False
+        )
+        assert output == expected
+
+    def test_configure_invalid_rule_severity(self):
+        expected = textwrap.dedent("""
+        RuleParamFailedInitError: Failed to configure param `severity` with value `value`. Received error `Choose one from: I, W, E.`.
+            Parameter type: <bound method RuleSeverity.parser of <enum 'RuleSeverity'>>
+            Parameter info: Rule severity (E = Error, W = Warning, I = Info)
+        """).lstrip()  # noqa: E501
+        output = self.check_rule(
+            src_files=["test.robot"], configure=["line-too-long.severity=value"], exit_code=2, compare_output=False
+        )
+        assert output == expected
diff --git a/tests/linter/utils/__init__.py b/tests/linter/utils/__init__.py
index 10fd08f..666e01c 100644
--- a/tests/linter/utils/__init__.py
+++ b/tests/linter/utils/__init__.py
@@ -97,13 +97,13 @@ class RuleAcceptance:
         output_format: str = "simple",
         deprecated: bool = False,
         exit_code: int | None = None,
+        compare_output: bool = True,
         **kwargs,
-    ):
+    ) -> str | None:
         if not self.enabled_in_version(test_on_version):
             pytest.skip(f"Test enabled only for RF {test_on_version}")
         test_data = self.test_class_dir
         sort_lines = output_format == "simple"
-        expected = load_expected_file(test_data, expected_file, sort_lines=sort_lines)
         issue_format = self.get_issue_format(issue_format)
         if select is None:
             select = [self.rule_name]
@@ -116,7 +116,7 @@ class RuleAcceptance:
         configure.append(f"print_issues.output_format={output_format}")
         with isolated_output() as output, working_directory(test_data):
             try:
-                with pytest.raises(click.exceptions.Exit) as excinfo:
+                with pytest.raises(click.exceptions.Exit) as exc_info:
                     check_files(
                         sources=paths,
                         select=select,
@@ -130,14 +130,18 @@ class RuleAcceptance:
             finally:
                 sys.stdout.flush()
                 result = get_result(output)
-                parsed_results = result.splitlines()
         if exit_code is not None:
-            assert excinfo.value.exit_code == exit_code
+            assert exc_info.value.exit_code == exit_code
+        if not compare_output:
+            return result
+        parsed_results = result.splitlines()
+        expected = load_expected_file(test_data, expected_file, sort_lines=sort_lines)
         actual = normalize_result(parsed_results, test_data, sort_lines=sort_lines)
         if deprecated:
             assert actual
             assert "No rule selected" in actual[0]
-        elif actual != expected:
+            return None
+        if actual != expected:
             error = "Actual issues are different than expected.\n"
             if sort_lines:
                 missing_expected = sorted(set(actual) - set(expected))
@@ -150,11 +154,12 @@ class RuleAcceptance:
                     present_in_expected = "\n    ".join(missing_actual)
                     error += f"Expected issues not found in actual:\n    {present_in_expected}"
             else:
-                # false alarm (rare occurence if OS have random file order), we don't sort at first to save time
+                # false alarm (rare occurrence if OS have random file order), we don't sort at first to save time
                 if sorted(actual) == sorted(expected):
-                    return
+                    return None
                 display_lines_diff(expected, actual)
             pytest.fail(error, pytrace=False)
+        return None
 
     def get_issue_format(self, issue_format):
         if issue_format == "default":
diff --git a/tests/test_cli.py b/tests/test_cli.py
index 621c013..8b98cd5 100644
--- a/tests/test_cli.py
+++ b/tests/test_cli.py
@@ -35,3 +35,10 @@ def test_print_docs_invalid():
     result = runner.invoke(app, ["docs", "idontexist"])
     assert result.exit_code == 2
     assert result.stdout == "There is no rule, formatter or a report with a 'idontexist' name.\n"
+
+
+def test_invalid_threshold():
+    runner = CliRunner()
+    result = runner.invoke(app, ["check", "--threshold", "unknown"])
+    assert result.exit_code == 2
+    assert result.stdout == "ConfigurationError: Invalid severity value 'unknown'. Choose one from: I, W, E.\n"