Repository: MarketSquare/robotframework-robocop
Pull request #1327 (2025-03-16 17:39:28)

Add Sonar Qube report
#759

Robocop is now able to produce reports in a format that can be exported to Sonar Qube: https://docs.sonarsource.com/sonarqube-server/latest/analyzing-source-code/importing-external-issues/generic-issue-import-format/

It requires to extend all rules definitions with 'SonarQubeAttributes`` class, If this class is missing (for example in custom rules), we use default values. Example class definition:

```
   sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
        clean_code=sonar_qube.CleanCodeAttribute.COMPLETE, issue_type=sonar_qube.SonarQubeIssueType.BUG
    )
```

I have assigned those attributes by 'gut feeling' as there were 159 rules to be updated. If users doesn't agree that particular rule belongs to particular code attribute or issue type, we can update them according to suggestions.

Additional rule attribute doesn't look like it affects performance, based on manual tests.


Patch:
diff --git a/docs/releasenotes/6.0.0a3.rst b/docs/releasenotes/6.0.0a3.rst
index 9c86a59..40b2dd5 100644
--- a/docs/releasenotes/6.0.0a3.rst
+++ b/docs/releasenotes/6.0.0a3.rst
@@ -695,15 +695,15 @@ It is ongoing process and there will be more updates in upcoming versions of Rob
 Integrations with other platforms
 ---------------------------------
 
-Robocop could be already integrated with different CI/CD platforms. It wasn't fully documented and sometimes it could
-require parsing output file. That's why we have decided to focus on improving how Robocop linting results can be
-integrated with other tools.
+Robocop could be already integrated with some CI/CD platforms. We made an active effort of documenting existing
+integrations and implementing it for missing, popular platforms such as Gitlab or Sonar Qube.
 
 Documentation is available at X # TODO .
 
 We are now supporting following integrations:
 
 - Gitlab Code Quality (with ``gitlab`` report) (#1116)
+- SonarQube (with ``sonarqube`` report) (#759)
 - pre-commit
 - ... and more - see the `Integrations` documentation
 
diff --git a/docs/source/integrations.rst b/docs/source/integrations.rst
index a85ce5b..671b837 100644
--- a/docs/source/integrations.rst
+++ b/docs/source/integrations.rst
@@ -51,4 +51,18 @@ You will need to attach this file to Gitlab artifacts::
         reports:
           codequality: robocop-code-quality.json
 
-See :ref:`gitlab` for more information about report and how to configure it.
+See :ref:`gitlab` for more information about the report and how to configure it.
+
+Sonar Qube
+----------
+
+Robocop results can be imported into Sonar Qube with `generic formatted result <https://docs.sonarsource.com/sonarqube-server/latest/analyzing-source-code/importing-external-issues/generic-issue-import-format/>`_ .
+
+Such report can be generated with ``sonarqube`` report::
+
+    robocop check --reports sonarqube
+
+By default it will produce ``robocop_sonar_qube.json`` file in the directory where Robocop was executed.
+You will need to attach this file in the CI/CD by defining analysis parameter ``sonar.externalIssuesReportPaths``.
+
+See :ref:`sonarqube` for more information about the report and how to configure it.
diff --git a/docs/source/reports.rst b/docs/source/reports.rst
index 9fb4f8a..6c3e558 100644
--- a/docs/source/reports.rst
+++ b/docs/source/reports.rst
@@ -260,3 +260,12 @@ Gitlab
 ------
 
 .. automodule:: robocop.linter.reports.gitlab.GitlabReport
+
+----
+
+.. _sonarqube:
+
+Sonar Qube
+----------
+
+.. automodule:: robocop.linter.reports.sonarqube.SonarQubeReport
diff --git a/src/robocop/linter/reports/gitlab.py b/src/robocop/linter/reports/gitlab.py
index 63c2595..efeb7bc 100644
--- a/src/robocop/linter/reports/gitlab.py
+++ b/src/robocop/linter/reports/gitlab.py
@@ -5,9 +5,8 @@ import json
 from pathlib import Path
 from typing import TYPE_CHECKING
 
-import typer
-
 import robocop.linter.reports
+from robocop.errors import FatalError
 from robocop.formatter.utils.misc import StatementLinesCollector
 from robocop.linter.rules import RuleSeverity
 
@@ -24,6 +23,9 @@ class GitlabReport(robocop.linter.reports.Report):
 
     Report that generates Gitlab Code Quality output file.
 
+    This report is not included in the default reports. The ``--reports all`` option will not enable this report.
+    You can still enable it using report name directly: ``--reports gitlab`` or ``--reports all,gitlab``.
+
     It allows to display issue information in the Gitlab, for example in the PR view.
     More information at https://docs.gitlab.com/ci/testing/code_quality/#code-quality-report-format .
 
@@ -57,15 +59,14 @@ class GitlabReport(robocop.linter.reports.Report):
             with open(output_path, "w") as fp:
                 json.dump(report, fp, indent=4)
         except OSError as err:
-            print(f"Failed to write Gitlab report to {output_path}: {err}")
-            raise typer.Exit(code=1) from None
+            raise FatalError(f"Failed to write Gitlab report to {output_path}: {err}") from None
         print(f"Generated Gitlab Code Quality report at {self.output_path}")
 
     def generate_gitlab_report(self, diagnostics: Diagnostics) -> list[dict]:
         report = []
         cwd = Path.cwd()
         for source, diag_by_source in diagnostics.diag_by_source.items():
-            source_rel = Path(source).relative_to(cwd).as_posix()
+            source_rel = str(Path(source).relative_to(cwd).as_posix())
             source_lines = None
             fingerprints = set()
             for diagnostic in diag_by_source:
@@ -74,7 +75,7 @@ class GitlabReport(robocop.linter.reports.Report):
                 content = self._get_line_content(diagnostic, source_lines)
                 unique_id = 0
                 while True:
-                    fingerprint = self.get_fingerprint(diagnostic, str(source_rel), content, unique_id)
+                    fingerprint = self.get_fingerprint(diagnostic, source_rel, content, unique_id)
                     if fingerprint not in fingerprints:
                         fingerprints.add(fingerprint)
                         break
@@ -85,7 +86,7 @@ class GitlabReport(robocop.linter.reports.Report):
                         "check_name": diagnostic.rule.name,
                         "fingerprint": fingerprint,
                         "severity": self.get_severity(diagnostic),
-                        "location": {"path": str(source_rel), "lines": {"begin": diagnostic.range.start.line}},
+                        "location": {"path": source_rel, "lines": {"begin": diagnostic.range.start.line}},
                     }
                 )
         return report
diff --git a/src/robocop/linter/reports/sarif_report.py b/src/robocop/linter/reports/sarif_report.py
index 50ddfae..332b76d 100644
--- a/src/robocop/linter/reports/sarif_report.py
+++ b/src/robocop/linter/reports/sarif_report.py
@@ -57,9 +57,9 @@ class SarifReport(robocop.linter.reports.Report):
             "name": rule.name,
             "helpUri": f"{ROBOCOP_RULES_URL.format(version=__version__)}#{rule.name}",
             "shortDescription": {"text": rule.message},
-            "fullDescription": {"text": rule.__doc__},
+            "fullDescription": {"text": rule.docs},
             "defaultConfiguration": {"level": self.map_severity_to_level(rule.default_severity)},
-            "help": {"text": rule.__doc__, "markdown": rule.__doc__},
+            "help": {"text": rule.docs, "markdown": rule.docs},
         }
 
     def generate_sarif_issues(self, diagnostics: Diagnostics, root: Path):
diff --git a/src/robocop/linter/reports/sonarqube.py b/src/robocop/linter/reports/sonarqube.py
new file mode 100644
index 0000000..832d456
--- /dev/null
+++ b/src/robocop/linter/reports/sonarqube.py
@@ -0,0 +1,120 @@
+import json
+from pathlib import Path
+
+import robocop.linter.reports
+from robocop.config import Config, ConfigManager
+from robocop.errors import FatalError
+from robocop.linter import sonar_qube
+from robocop.linter.diagnostics import Diagnostic, Diagnostics
+from robocop.linter.rules import Rule, RuleSeverity
+
+
+class SonarQubeReport(robocop.linter.reports.Report):
+    """
+    **Report name**: ``sonarqube``
+
+    Report that generates SonarQube report.
+
+    This report is not included in the default reports. The ``--reports all`` option will not enable this report.
+    You can still enable it using report name directly: ``--reports sonarqube`` or ``--reports all,sonarqube``.
+
+    Implements all mandatory field of Sonar Qube `Generic formatted issue report`. Each Robocop rule can be assigned
+    with Sonar Qube code attribute or issue type for that purpose. If rule doesn't have such attribute assigned,
+    ``cleanCodeAttribute`` will be set to ``CONVENTIONAL`` and ``issueType`` will be set to ``CODE SMELL``.
+    Severity is mapped in a following way:
+
+    - parsing errors and bugs as ``BLOCKER``
+    - errors as ``MAJOR``
+    - warnings as ``MINOR``
+    - infos as ``INFO``
+
+    You can configure output path. It's relative path to file that will be produced by the report::
+
+        robocop check --configure sonarqube.output_path=output/robocop_sonar_qube.json
+
+    Default path is ``robocop_sonar_qube.json`` .
+
+    """
+
+    NO_ALL = False
+
+    def __init__(self, config: Config):
+        self.name = "sonarqube"
+        self.description = "Generate SonarQube report"
+        self.output_path = "robocop_sonar_qube.json"
+        super().__init__(config)
+
+    def configure(self, name, value) -> None:
+        if name == "output_path":
+            self.output_path = value
+        else:
+            super().configure(name, value)
+
+    def get_rule_description(self, diagnostic: Diagnostic) -> dict:
+        return {
+            "id": diagnostic.rule.rule_id,
+            "name": diagnostic.rule.name,
+            "description": diagnostic.rule.docs,
+            "engineId": "robocop",
+            **self.get_code_attributes(diagnostic.rule),
+        }
+
+    def get_issue_description(self, diagnostic: Diagnostic, source_rel: str) -> dict:
+        return {
+            "ruleId": diagnostic.rule.rule_id,
+            "primaryLocation": {
+                "message": diagnostic.message,
+                "filePath": source_rel,
+                "textRange": {
+                    "startLine": diagnostic.range.start.line,
+                    "startColumn": diagnostic.range.start.character,
+                    "endLine": diagnostic.range.end.line,
+                    "endColumn": diagnostic.range.end.character,
+                },
+            },
+        }
+
+    @staticmethod
+    def map_severity(rule_severity: RuleSeverity) -> str:
+        return {
+            RuleSeverity.INFO: "INFO",
+            RuleSeverity.WARNING: "MINOR",
+            RuleSeverity.ERROR: "MAJOR",
+        }[rule_severity]
+
+    def get_code_attributes(self, rule: Rule) -> dict:
+        if rule.sonar_qube_attrs:
+            issue_type = rule.sonar_qube_attrs.issue_type.value
+            clean_code_attr = rule.sonar_qube_attrs.clean_code.value
+            if issue_type == sonar_qube.SonarQubeIssueType.BUG:
+                severity = "BLOCKER"
+            else:
+                severity = self.map_severity(rule.severity)
+        else:
+            issue_type = sonar_qube.SonarQubeIssueType.CODE_SMELL.value
+            clean_code_attr = sonar_qube.CleanCodeAttribute.CONVENTIONAL.value
+            severity = self.map_severity(rule.severity)
+        return {"type": issue_type, "cleanCodeAttribute": clean_code_attr, "severity": severity}
+
+    def generate_sonarqube_report(self, diagnostics: Diagnostics, root: Path) -> dict:
+        report = {"rules": [], "issues": []}
+        seen_rules = set()
+        for source, diag_by_source in diagnostics.diag_by_source.items():
+            source_rel = str(Path(source).relative_to(root).as_posix())
+            for diagnostic in diag_by_source:
+                if diagnostic.rule.rule_id not in seen_rules:
+                    seen_rules.add(diagnostic.rule.rule_id)
+                    report["rules"].append(self.get_rule_description(diagnostic))
+                report["issues"].append(self.get_issue_description(diagnostic, source_rel))
+        return report
+
+    def generate_report(self, diagnostics: Diagnostics, config_manager: ConfigManager, **kwargs) -> None:  # noqa: ARG002
+        report = self.generate_sonarqube_report(diagnostics, config_manager.root)
+        output_path = Path(self.output_path)
+        try:
+            output_path.parent.mkdir(exist_ok=True, parents=True)
+            with open(output_path, "w") as fp:
+                json.dump(report, fp, indent=4)
+        except OSError as err:
+            raise FatalError(f"Failed to write SonarQube report to {output_path}: {err}") from None
+        print(f"Generated SonarQube report in {self.output_path}")
diff --git a/src/robocop/linter/rules/__init__.py b/src/robocop/linter/rules/__init__.py
index df4ca80..eb2d326 100644
--- a/src/robocop/linter/rules/__init__.py
+++ b/src/robocop/linter/rules/__init__.py
@@ -36,7 +36,7 @@ from typing import TYPE_CHECKING, Any, Callable, NoReturn
 
 from robot.utils import FileReader
 
-from robocop.linter import exceptions
+from robocop.linter import exceptions, sonar_qube
 from robocop.linter.diagnostics import Diagnostic
 from robocop.linter.exceptions import (
     InvalidExternalCheckerError,
@@ -353,6 +353,10 @@ class Rule:
         enabled (bool): (class attribute) enable/disable rule by default using this parameter
         deprecated (bool): (class attribute) deprecated rule. If rule is used in configuration, it will issue a warning
         file_wide_rule (bool): (class attribute) If set, rule is reported for whole file
+        parameters: (class attribute) optional rule parameters
+        style_guide_ref (list of str): (class attribute) reference to Robot Framework Style Guide in form of
+        '#paragraph' strings
+        sonar_qube_attrs: (class attribute) optional SonarQube attributes used for SonarQube report
 
     """
 
@@ -369,6 +373,7 @@ class Rule:
     file_wide_rule: bool = False
     parameters: list[RuleParam] | None = None
     style_guide_ref: list[str] | None = None
+    sonar_qube_attrs: sonar_qube.SonarQubeAttributes | None = None
 
     def __init__(self):
         self.version_spec = VersionSpecifier(self.version) if self.version else None
diff --git a/src/robocop/linter/rules/arguments.py b/src/robocop/linter/rules/arguments.py
index fe35c86..53a6f08 100644
--- a/src/robocop/linter/rules/arguments.py
+++ b/src/robocop/linter/rules/arguments.py
@@ -1,3 +1,4 @@
+from robocop.linter import sonar_qube
 from robocop.linter.rules import Rule, RuleParam, RuleSeverity
 
 
@@ -23,6 +24,9 @@ class UnusedArgumentRule(Rule):
     message = "Keyword argument '{name}' is not used"
     severity = RuleSeverity.WARNING
     added_in_version = "3.2.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.CLEAR, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class ArgumentOverwrittenBeforeUsageRule(Rule):
@@ -42,6 +46,9 @@ class ArgumentOverwrittenBeforeUsageRule(Rule):
     message = "Keyword argument '{name}' is overwritten before usage"
     severity = RuleSeverity.WARNING
     added_in_version = "3.2.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.CLEAR, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class UndefinedArgumentDefaultRule(Rule):
@@ -68,6 +75,9 @@ class UndefinedArgumentDefaultRule(Rule):
     message = "Undefined argument default, use {arg_name}=${{EMPTY}} instead"
     severity = RuleSeverity.ERROR
     added_in_version = "5.7.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.CLEAR, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class UndefinedArgumentValueRule(Rule):
@@ -96,6 +106,9 @@ class UndefinedArgumentValueRule(Rule):
     message = "Undefined argument value, use {arg_name}=${{EMPTY}} instead"
     severity = RuleSeverity.ERROR
     added_in_version = "5.7.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.CLEAR, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class InvalidArgumentsRule(Rule):
@@ -125,6 +138,9 @@ class InvalidArgumentsRule(Rule):
     severity = RuleSeverity.ERROR
     version = ">=4.0"
     added_in_version = "1.11.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.COMPLETE, issue_type=sonar_qube.SonarQubeIssueType.BUG
+    )
 
 
 class DuplicatedArgumentRule(Rule):
@@ -146,6 +162,9 @@ class DuplicatedArgumentRule(Rule):
     message = "Argument name '{argument_name}' is already used"
     severity = RuleSeverity.ERROR
     added_in_version = "1.11.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.DISTINCT, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class ArgumentsPerLineRule(Rule):
@@ -184,4 +203,7 @@ class ArgumentsPerLineRule(Rule):
             desc="maximum number of arguments allowed in the continuation line",
         ),
     ]
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.FORMATTED, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
     # TODO flag to allow for [Arguments] multiple args ine one line, just not in other ...
diff --git a/src/robocop/linter/rules/comments.py b/src/robocop/linter/rules/comments.py
index bf6a6e9..21afce2 100644
--- a/src/robocop/linter/rules/comments.py
+++ b/src/robocop/linter/rules/comments.py
@@ -9,6 +9,7 @@ from typing import TYPE_CHECKING
 from robot.api import Token
 from robot.utils import FileReader
 
+from robocop.linter import sonar_qube
 from robocop.linter.rules import RawFileChecker, Rule, RuleParam, RuleSeverity, VisitorChecker
 from robocop.linter.utils import ROBOT_VERSION
 
@@ -59,6 +60,9 @@ class ToDoInCommentRule(Rule):
             desc="List of case-insensitive markers that violate the rule in comments.",
         )
     ]
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.COMPLETE, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class MissingSpaceAfterCommentRule(Rule):
@@ -106,6 +110,9 @@ class MissingSpaceAfterCommentRule(Rule):
     added_in_version = "1.0.0"
     parameters = [RuleParam(name="block", default="^###", converter=regex, desc="Block comment regex pattern.")]
     style_guide_ref = ["#comments"]
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.FORMATTED, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class InvalidCommentRule(Rule):
@@ -130,6 +137,9 @@ class InvalidCommentRule(Rule):
     severity = RuleSeverity.ERROR
     version = "<4.0"
     added_in_version = "1.0.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.COMPLETE, issue_type=sonar_qube.SonarQubeIssueType.BUG
+    )
 
 
 class IgnoredDataRule(Rule):
@@ -163,6 +173,9 @@ class IgnoredDataRule(Rule):
     message = "Ignored data found in file"
     severity = RuleSeverity.WARNING
     added_in_version = "1.3.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.CLEAR, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class BomEncodingRule(Rule):
@@ -180,6 +193,9 @@ class BomEncodingRule(Rule):
     file_wide_rule = True
     severity = RuleSeverity.WARNING
     added_in_version = "1.7.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.CLEAR, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class CommentChecker(VisitorChecker):
diff --git a/src/robocop/linter/rules/deprecated.py b/src/robocop/linter/rules/deprecated.py
index 96d22ad..48fa9fa 100644
--- a/src/robocop/linter/rules/deprecated.py
+++ b/src/robocop/linter/rules/deprecated.py
@@ -1,3 +1,4 @@
+from robocop.linter import sonar_qube
 from robocop.linter.rules import Rule, RuleSeverity
 
 
@@ -35,6 +36,9 @@ class IfCanBeUsedRule(Rule):
     severity = RuleSeverity.INFO
     version = "==4.*"
     added_in_version = "1.4.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.CONVENTIONAL, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class DeprecatedStatementRule(Rule):  # TODO: Split rule
@@ -52,6 +56,9 @@ class DeprecatedStatementRule(Rule):  # TODO: Split rule
     message = "'{statement_name}' is deprecated since Robot Framework version {version}, use '{alternative}' instead"
     severity = RuleSeverity.WARNING
     added_in_version = "2.0.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.CONVENTIONAL, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class DeprecatedWithNameRule(Rule):
@@ -79,6 +86,9 @@ class DeprecatedWithNameRule(Rule):
     severity = RuleSeverity.WARNING
     version = ">=6.0"
     added_in_version = "2.5.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.CONVENTIONAL, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class DeprecatedSingularHeaderRule(Rule):
@@ -106,6 +116,9 @@ class DeprecatedSingularHeaderRule(Rule):
     severity = RuleSeverity.WARNING
     version = ">=6.0"
     added_in_version = "2.6.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.CONVENTIONAL, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class ReplaceSetVariableWithVarRule(Rule):
@@ -143,6 +156,9 @@ class ReplaceSetVariableWithVarRule(Rule):
     severity = RuleSeverity.INFO
     version = ">=7.0"
     added_in_version = "5.0.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.CONVENTIONAL, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class ReplaceCreateWithVarRule(Rule):
@@ -174,3 +190,6 @@ class ReplaceCreateWithVarRule(Rule):
     severity = RuleSeverity.INFO
     version = ">=7.0"
     added_in_version = "5.0.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.CONVENTIONAL, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
diff --git a/src/robocop/linter/rules/documentation.py b/src/robocop/linter/rules/documentation.py
index a52c097..02e942e 100644
--- a/src/robocop/linter/rules/documentation.py
+++ b/src/robocop/linter/rules/documentation.py
@@ -7,6 +7,7 @@ from typing import TYPE_CHECKING
 
 from robot.parsing.model.statements import Documentation
 
+from robocop.linter import sonar_qube
 from robocop.linter.rules import Rule, RuleParam, RuleSeverity, VisitorChecker
 from robocop.linter.utils.misc import str2bool
 
@@ -36,6 +37,9 @@ class MissingDocKeywordRule(Rule):
     message = "Missing documentation in '{name}' keyword"
     severity = RuleSeverity.WARNING
     added_in_version = "1.0.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.CONVENTIONAL, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class MissingDocTestCaseRule(Rule):
@@ -63,7 +67,6 @@ class MissingDocTestCaseRule(Rule):
     message = "Missing documentation in '{name}' test case"
     severity = RuleSeverity.WARNING
     added_in_version = "1.0.0"
-
     parameters = [
         RuleParam(
             name="ignore_templated",
@@ -73,6 +76,9 @@ class MissingDocTestCaseRule(Rule):
             desc="whether templated tests should be documented or not",
         )
     ]
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.CONVENTIONAL, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class MissingDocTestSuiteRule(Rule):
@@ -92,6 +98,9 @@ class MissingDocTestSuiteRule(Rule):
     severity = RuleSeverity.WARNING
     file_wide_rule = True
     added_in_version = "1.0.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.CONVENTIONAL, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class MissingDocResourceFileRule(Rule):
@@ -111,6 +120,9 @@ class MissingDocResourceFileRule(Rule):
     severity = RuleSeverity.WARNING
     file_wide_rule = True
     added_in_version = "2.8.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.CONVENTIONAL, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class MissingDocumentationChecker(VisitorChecker):
diff --git a/src/robocop/linter/rules/duplications.py b/src/robocop/linter/rules/duplications.py
index 68ac8f2..a9d7a80 100644
--- a/src/robocop/linter/rules/duplications.py
+++ b/src/robocop/linter/rules/duplications.py
@@ -4,6 +4,7 @@ from collections import defaultdict
 
 from robot.api import Token
 
+from robocop.linter import sonar_qube
 from robocop.linter.rules import Rule, RuleSeverity, VisitorChecker, arguments, order, variables
 from robocop.linter.utils import get_errors, normalize_robot_name, normalize_robot_var_name
 
@@ -31,6 +32,9 @@ class DuplicatedTestCaseRule(Rule):
     message = "Multiple test cases with name '{name}' (first occurrence in line {first_occurrence_line})"
     severity = RuleSeverity.ERROR
     added_in_version = "1.0.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.DISTINCT, issue_type=sonar_qube.SonarQubeIssueType.BUG
+    )
 
 
 class DuplicatedKeywordRule(Rule):
@@ -59,6 +63,9 @@ class DuplicatedKeywordRule(Rule):
     message = "Multiple keywords with name '{name}' (first occurrence in line {first_occurrence_line})"
     severity = RuleSeverity.ERROR
     added_in_version = "1.0.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.DISTINCT, issue_type=sonar_qube.SonarQubeIssueType.BUG
+    )
 
 
 class DuplicatedVariableRule(Rule):
@@ -84,6 +91,9 @@ class DuplicatedVariableRule(Rule):
     )
     severity = RuleSeverity.ERROR
     added_in_version = "1.0.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.DISTINCT, issue_type=sonar_qube.SonarQubeIssueType.BUG
+    )
 
 
 class DuplicatedResourceRule(Rule):
@@ -106,6 +116,9 @@ class DuplicatedResourceRule(Rule):
     message = "Multiple resource imports with path '{name}' (first occurrence in line {first_occurrence_line})"
     severity = RuleSeverity.WARNING
     added_in_version = "1.0.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.DISTINCT, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class DuplicatedLibraryRule(Rule):
@@ -128,6 +141,9 @@ class DuplicatedLibraryRule(Rule):
     )
     severity = RuleSeverity.WARNING
     added_in_version = "1.0.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.DISTINCT, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class DuplicatedMetadataRule(Rule):
@@ -138,6 +154,9 @@ class DuplicatedMetadataRule(Rule):
     message = "Duplicated metadata '{name}' (first occurrence in line {first_occurrence_line})"
     severity = RuleSeverity.WARNING
     added_in_version = "1.0.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.DISTINCT, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class DuplicatedVariablesImportRule(Rule):
@@ -148,6 +167,9 @@ class DuplicatedVariablesImportRule(Rule):
     message = "Duplicated variables import with path '{name}' (first occurrence in line {first_occurrence_line})"
     severity = RuleSeverity.WARNING
     added_in_version = "1.0.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.DISTINCT, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class SectionAlreadyDefinedRule(Rule):
@@ -180,6 +202,9 @@ class SectionAlreadyDefinedRule(Rule):
     )
     severity = RuleSeverity.WARNING
     added_in_version = "1.0.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.DISTINCT, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class BothTestsAndTasksRule(Rule):
@@ -199,6 +224,9 @@ class BothTestsAndTasksRule(Rule):
     message = "Both Task(s) and Test Case(s) section headers defined in file"
     severity = RuleSeverity.ERROR
     added_in_version = "1.0.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.DISTINCT, issue_type=sonar_qube.SonarQubeIssueType.BUG
+    )
 
 
 class DuplicatedSettingRule(Rule):
@@ -219,6 +247,9 @@ class DuplicatedSettingRule(Rule):
     message = "{error_msg}"
     severity = RuleSeverity.WARNING
     added_in_version = "2.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.DISTINCT, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class DuplicationsChecker(VisitorChecker):
diff --git a/src/robocop/linter/rules/errors.py b/src/robocop/linter/rules/errors.py
index df1cfd1..f311157 100644
--- a/src/robocop/linter/rules/errors.py
+++ b/src/robocop/linter/rules/errors.py
@@ -9,6 +9,7 @@ try:
 except ImportError:
     If = None
 
+from robocop.linter import sonar_qube
 from robocop.linter.rules import Rule, RuleSeverity, VisitorChecker, arguments, whitespace
 from robocop.linter.utils import ROBOT_VERSION, find_robot_vars
 
@@ -19,6 +20,9 @@ class ParsingErrorRule(Rule):  # TODO docs
     message = "Robot Framework syntax error: {error_msg}"
     severity = RuleSeverity.ERROR
     added_in_version = "1.0.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.COMPLETE, issue_type=sonar_qube.SonarQubeIssueType.BUG
+    )
 
 
 class MissingKeywordNameRule(Rule):
@@ -39,6 +43,9 @@ class MissingKeywordNameRule(Rule):
     message = "Missing keyword name when calling some values"
     severity = RuleSeverity.ERROR
     added_in_version = "1.8.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.COMPLETE, issue_type=sonar_qube.SonarQubeIssueType.BUG
+    )
 
 
 class VariablesImportWithArgsRule(Rule):
@@ -59,6 +66,9 @@ class VariablesImportWithArgsRule(Rule):
     message = "YAML variable files do not take arguments"
     severity = RuleSeverity.ERROR
     added_in_version = "1.11.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.COMPLETE, issue_type=sonar_qube.SonarQubeIssueType.BUG
+    )
 
 
 class InvalidContinuationMarkRule(Rule):
@@ -79,6 +89,9 @@ class InvalidContinuationMarkRule(Rule):
     message = "Invalid continuation mark '{mark}'. It should be '...'"
     severity = RuleSeverity.ERROR
     added_in_version = "1.11.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.COMPLETE, issue_type=sonar_qube.SonarQubeIssueType.BUG
+    )
 
 
 class NonExistingSettingRule(Rule):
@@ -99,6 +112,9 @@ class NonExistingSettingRule(Rule):
     message = "{error_msg}"
     severity = RuleSeverity.ERROR
     added_in_version = "1.11.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.COMPLETE, issue_type=sonar_qube.SonarQubeIssueType.BUG
+    )
 
 
 class SettingNotSupportedRule(Rule):
@@ -134,6 +150,9 @@ class SettingNotSupportedRule(Rule):
     message = "Setting '[{setting_name}]' is not supported in {test_or_keyword}. Allowed are: {allowed_settings}"
     severity = RuleSeverity.ERROR
     added_in_version = "1.11.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.LOGICAL, issue_type=sonar_qube.SonarQubeIssueType.BUG
+    )
 
 
 class InvalidForLoopRule(Rule):
@@ -145,6 +164,9 @@ class InvalidForLoopRule(Rule):
     severity = RuleSeverity.ERROR
     version = ">=4.0"
     added_in_version = "1.0.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.COMPLETE, issue_type=sonar_qube.SonarQubeIssueType.BUG
+    )
 
 
 class InvalidIfRule(Rule):
@@ -156,6 +178,9 @@ class InvalidIfRule(Rule):
     severity = RuleSeverity.ERROR
     version = ">=4.0"
     added_in_version = "1.0.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.COMPLETE, issue_type=sonar_qube.SonarQubeIssueType.BUG
+    )
 
 
 class ReturnInTestCaseRule(Rule):
@@ -167,6 +192,9 @@ class ReturnInTestCaseRule(Rule):
     severity = RuleSeverity.ERROR
     version = ">=5.0"
     added_in_version = "2.0.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.COMPLETE, issue_type=sonar_qube.SonarQubeIssueType.BUG
+    )
 
 
 class InvalidSectionInResourceRule(Rule):
@@ -183,6 +211,9 @@ class InvalidSectionInResourceRule(Rule):
     message = "Resource file can't contain '{section_name}' section"
     severity = RuleSeverity.ERROR
     added_in_version = "3.1.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.COMPLETE, issue_type=sonar_qube.SonarQubeIssueType.BUG
+    )
 
 
 class InvalidSettingInResourceRule(Rule):
@@ -198,6 +229,9 @@ class InvalidSettingInResourceRule(Rule):
     message = "Settings section in resource file can't contain '{section_name}' setting"
     severity = RuleSeverity.ERROR
     added_in_version = "3.3.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.COMPLETE, issue_type=sonar_qube.SonarQubeIssueType.BUG
+    )
 
 
 class UnsupportedSettingInIniFileRule(Rule):
@@ -212,6 +246,9 @@ class UnsupportedSettingInIniFileRule(Rule):
     message = "Setting '{setting}' is not supported in initialization files"
     severity = RuleSeverity.ERROR
     added_in_version = "3.3.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.COMPLETE, issue_type=sonar_qube.SonarQubeIssueType.BUG
+    )
 
 
 class ParsingErrorChecker(VisitorChecker):
diff --git a/src/robocop/linter/rules/imports.py b/src/robocop/linter/rules/imports.py
index 129f394..78f6604 100644
--- a/src/robocop/linter/rules/imports.py
+++ b/src/robocop/linter/rules/imports.py
@@ -5,6 +5,7 @@ from typing import TYPE_CHECKING
 from robot.api import Token
 from robot.libraries import STDLIBS
 
+from robocop.linter import sonar_qube
 from robocop.linter.rules import Rule, RuleSeverity, VisitorChecker
 
 if TYPE_CHECKING:
@@ -32,6 +33,9 @@ class WrongImportOrderRule(Rule):
     message = "BuiltIn library import '{builtin_import}' should be placed before '{custom_import}'"
     severity = RuleSeverity.WARNING
     added_in_version = "1.7.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.CONVENTIONAL, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class BuiltinImportsNotSortedRule(Rule):
@@ -53,6 +57,9 @@ class BuiltinImportsNotSortedRule(Rule):
     message = "BuiltIn library import '{builtin_import}' should be placed before '{previous_builtin_import}'"
     severity = RuleSeverity.WARNING
     added_in_version = "5.2.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.CONVENTIONAL, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class NonBuiltinImportsNotSortedRule(Rule):
@@ -77,6 +84,9 @@ class NonBuiltinImportsNotSortedRule(Rule):
     severity = RuleSeverity.WARNING
     enabled = False
     added_in_version = "5.2.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.CONVENTIONAL, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class ResourcesImportsNotSortedRule(Rule):
@@ -100,6 +110,9 @@ class ResourcesImportsNotSortedRule(Rule):
     severity = RuleSeverity.WARNING
     enabled = False
     added_in_version = "5.2.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.CONVENTIONAL, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class SettingsOrderChecker(VisitorChecker):
diff --git a/src/robocop/linter/rules/keywords.py b/src/robocop/linter/rules/keywords.py
index ac0229b..34f933f 100644
--- a/src/robocop/linter/rules/keywords.py
+++ b/src/robocop/linter/rules/keywords.py
@@ -2,6 +2,7 @@ from robot.api import Token
 from robot.model import Keyword
 from robot.utils.robottime import timestr_to_secs
 
+from robocop.linter import sonar_qube
 from robocop.linter.rules import Rule, RuleParam, RuleSeverity, VisitorChecker
 from robocop.linter.utils.misc import normalize_robot_name
 from robocop.parsing.run_keywords import iterate_keyword_names
@@ -51,6 +52,9 @@ class SleepKeywordUsedRule(Rule):
         RuleParam(name="max_time", default=0, converter=timestr_to_secs, desc="Maximum amount of time allowed in Sleep")
     ]
     added_in_version = "5.0.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.CONVENTIONAL, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class NotAllowedKeywordRule(Rule):
@@ -91,6 +95,9 @@ class NotAllowedKeywordRule(Rule):
         )
     ]
     added_in_version = "5.1.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.CONVENTIONAL, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class NoEmbeddedKeywordArgumentsRule(Rule):
@@ -142,6 +149,9 @@ class NoEmbeddedKeywordArgumentsRule(Rule):
     severity = RuleSeverity.WARNING
     enabled = False
     added_in_version = "5.5.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.CONVENTIONAL, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class SleepKeywordUsedChecker(VisitorChecker):  # TODO: merge with a checker for keyword calls
diff --git a/src/robocop/linter/rules/lengths.py b/src/robocop/linter/rules/lengths.py
index a25e442..f13b536 100644
--- a/src/robocop/linter/rules/lengths.py
+++ b/src/robocop/linter/rules/lengths.py
@@ -25,6 +25,7 @@ try:  # RF7+
 except ImportError:
     Var = None
 
+from robocop.linter import sonar_qube
 from robocop.linter.rules import (
     RawFileChecker,
     Rule,
@@ -55,6 +56,9 @@ class TooLongKeywordRule(Rule):
     ]
     severity_threshold = SeverityThreshold("max_len", compare_method="greater", substitute_value="allowed_length")
     added_in_version = "1.0.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.FOCUSED, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class TooFewCallsInKeywordRule(Rule):
@@ -90,6 +94,9 @@ class TooFewCallsInKeywordRule(Rule):
     ]
     severity_threshold = SeverityThreshold("min_calls", compare_method="less", substitute_value="min_allowed_count")
     added_in_version = "1.0.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.MODULAR, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class TooManyCallsInKeywordRule(Rule):
@@ -108,6 +115,9 @@ class TooManyCallsInKeywordRule(Rule):
     ]
     severity_threshold = SeverityThreshold("max_calls", compare_method="greater", substitute_value="max_allowed_count")
     added_in_version = "1.0.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.FOCUSED, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class TooLongTestCaseRule(Rule):
@@ -127,6 +137,9 @@ class TooLongTestCaseRule(Rule):
     ]
     severity_threshold = SeverityThreshold("max_len", compare_method="greater", substitute_value="allowed_length")
     added_in_version = "1.0.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.MODULAR, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class TooManyCallsInTestCaseRule(Rule):
@@ -148,6 +161,9 @@ class TooManyCallsInTestCaseRule(Rule):
     ]
     severity_threshold = SeverityThreshold("max_calls", compare_method="greater", substitute_value="max_allowed_count")
     added_in_version = "1.0.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.MODULAR, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class FileTooLongRule(Rule):
@@ -163,6 +179,9 @@ class FileTooLongRule(Rule):
     ]
     severity_threshold = SeverityThreshold("max_lines", compare_method="greater", substitute_value="max_allowed_count")
     added_in_version = "1.0.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.FOCUSED, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class TooManyArgumentsRule(Rule):
@@ -176,6 +195,9 @@ class TooManyArgumentsRule(Rule):
     severity_threshold = SeverityThreshold("max_args", compare_method="greater", substitute_value="max_allowed_count")
     added_in_version = "1.0.0"
     style_guide_ref = ["#arguments"]
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.FOCUSED, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class LineTooLongRule(Rule):
@@ -207,6 +229,9 @@ class LineTooLongRule(Rule):
     severity_threshold = SeverityThreshold("line_length", substitute_value="allowed_length")
     added_in_version = "1.0.0"
     style_guide_ref = ["#line-length"]
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.FORMATTED, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class EmptySectionRule(Rule):
@@ -217,6 +242,9 @@ class EmptySectionRule(Rule):
     message = "Section '{section_name}' is empty"
     severity = RuleSeverity.WARNING
     added_in_version = "1.0.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.COMPLETE, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class NumberOfReturnedValuesRule(Rule):
@@ -233,6 +261,9 @@ class NumberOfReturnedValuesRule(Rule):
         "max_returns", compare_method="greater", substitute_value="max_allowed_count"
     )
     added_in_version = "1.0.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.FOCUSED, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class EmptyMetadataRule(Rule):
@@ -256,6 +287,9 @@ class EmptyMetadataRule(Rule):
     message = "Metadata settings does not have any value set"
     severity = RuleSeverity.WARNING
     added_in_version = "1.0.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.COMPLETE, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class EmptyDocumentationRule(Rule):
@@ -266,6 +300,9 @@ class EmptyDocumentationRule(Rule):
     message = "Documentation of {block_name} is empty"
     severity = RuleSeverity.WARNING
     added_in_version = "1.0.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.COMPLETE, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class EmptyForceTagsRule(Rule):  # TODO: Rename/deprecate and replace with Test Tags
@@ -276,6 +313,9 @@ class EmptyForceTagsRule(Rule):  # TODO: Rename/deprecate and replace with Test
     message = "Force Tags are empty"
     severity = RuleSeverity.WARNING
     added_in_version = "1.0.0"  # TODO: check for new settings, such as Keyword Tags
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.COMPLETE, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class EmptyDefaultTagsRule(Rule):
@@ -286,6 +326,9 @@ class EmptyDefaultTagsRule(Rule):
     message = "Default Tags are empty"
     severity = RuleSeverity.WARNING
     added_in_version = "1.0.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.COMPLETE, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class EmptyVariablesImport(Rule):
@@ -296,6 +339,9 @@ class EmptyVariablesImport(Rule):
     message = "Import variables path is empty"
     severity = RuleSeverity.ERROR
     added_in_version = "1.0.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.COMPLETE, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class EmptyResourceImport(Rule):
@@ -306,6 +352,9 @@ class EmptyResourceImport(Rule):
     message = "Import resource path is empty"
     severity = RuleSeverity.ERROR
     added_in_version = "1.0.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.COMPLETE, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class EmptyLibraryImport(Rule):
@@ -316,6 +365,9 @@ class EmptyLibraryImport(Rule):
     message = "Import library path is empty"
     severity = RuleSeverity.ERROR
     added_in_version = "1.0.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.COMPLETE, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class EmptySetupRule(Rule):
@@ -326,6 +378,9 @@ class EmptySetupRule(Rule):
     message = "Setup of {block_name} does not have any keywords"
     severity = RuleSeverity.ERROR
     added_in_version = "1.0.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.COMPLETE, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class EmptySuiteSetupRule(Rule):
@@ -336,6 +391,9 @@ class EmptySuiteSetupRule(Rule):
     message = "Suite Setup does not have any keywords"
     severity = RuleSeverity.ERROR
     added_in_version = "1.0.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.COMPLETE, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class EmptyTestSetupRule(Rule):
@@ -346,6 +404,9 @@ class EmptyTestSetupRule(Rule):
     message = "Test Setup does not have any keywords"
     severity = RuleSeverity.ERROR
     added_in_version = "1.0.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.COMPLETE, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class EmptyTeardownRule(Rule):
@@ -356,6 +417,9 @@ class EmptyTeardownRule(Rule):
     message = "Teardown of {block_name} does not have any keywords"
     severity = RuleSeverity.ERROR
     added_in_version = "1.0.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.COMPLETE, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class EmptySuiteTeardownRule(Rule):
@@ -366,6 +430,9 @@ class EmptySuiteTeardownRule(Rule):
     message = "Suite Teardown does not have any keywords"
     severity = RuleSeverity.ERROR
     added_in_version = "1.0.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.COMPLETE, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class EmptyTestTeardownRule(Rule):
@@ -376,6 +443,9 @@ class EmptyTestTeardownRule(Rule):
     message = "Test Teardown does not have any keywords"
     severity = RuleSeverity.ERROR
     added_in_version = "1.0.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.COMPLETE, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class EmptyTimeoutRule(Rule):
@@ -386,6 +456,9 @@ class EmptyTimeoutRule(Rule):
     message = "Timeout of {block_name} is empty"
     severity = RuleSeverity.WARNING
     added_in_version = "1.0.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.COMPLETE, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class EmptyTestTimeoutRule(Rule):
@@ -396,6 +469,9 @@ class EmptyTestTimeoutRule(Rule):
     message = "Test Timeout is empty"
     severity = RuleSeverity.WARNING
     added_in_version = "1.0.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.COMPLETE, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class EmptyArgumentsRule(Rule):
@@ -406,6 +482,9 @@ class EmptyArgumentsRule(Rule):
     message = "Arguments of {block_name} are empty"
     severity = RuleSeverity.ERROR
     added_in_version = "1.0.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.COMPLETE, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class TooManyTestCasesRule(Rule):
@@ -428,6 +507,9 @@ class TooManyTestCasesRule(Rule):
         "max_testcases or max_templated_testcases", substitute_value="max_allowed_count"
     )
     added_in_version = "1.10.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.MODULAR, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class TooFewCallsInTestCaseRule(Rule):
@@ -455,6 +537,9 @@ class TooFewCallsInTestCaseRule(Rule):
         ),
     ]
     added_in_version = "2.4.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.MODULAR, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class EmptyTestTemplateRule(Rule):
@@ -471,6 +556,9 @@ class EmptyTestTemplateRule(Rule):
     message = "Test Template is empty"
     severity = RuleSeverity.ERROR
     added_in_version = "3.1.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.COMPLETE, issue_type=sonar_qube.SonarQubeIssueType.BUG
+    )
 
 
 class EmptyTemplateRule(Rule):
@@ -500,6 +588,9 @@ class EmptyTemplateRule(Rule):
     message = "Template of {block_name} is empty. To overwrite suite Test Template use more explicit [Template]  NONE"
     severity = RuleSeverity.WARNING
     added_in_version = "3.1.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.CONVENTIONAL, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class EmptyKeywordTagsRule(Rule):
@@ -511,6 +602,9 @@ class EmptyKeywordTagsRule(Rule):
     severity = RuleSeverity.WARNING
     version = ">=6"
     added_in_version = "3.3.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.COMPLETE, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 def is_data_statement(node) -> bool:
diff --git a/src/robocop/linter/rules/misc.py b/src/robocop/linter/rules/misc.py
index b00a368..46667ab 100644
--- a/src/robocop/linter/rules/misc.py
+++ b/src/robocop/linter/rules/misc.py
@@ -20,6 +20,7 @@ try:
 except ImportError:
     InlineIfHeader, Break, Continue = None, None, None
 
+from robocop.linter import sonar_qube
 from robocop.linter.rules import (
     Rule,
     RuleParam,
@@ -79,6 +80,9 @@ class KeywordAfterReturnRule(Rule):
     message = "{error_msg}"
     severity = RuleSeverity.WARNING
     added_in_version = "1.0.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.CONVENTIONAL, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class EmptyReturnRule(Rule):
@@ -110,6 +114,9 @@ class EmptyReturnRule(Rule):
     message = "[Return] is empty"
     severity = RuleSeverity.WARNING
     added_in_version = "1.0.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.COMPLETE, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class NestedForLoopRule(Rule):
@@ -134,6 +141,9 @@ class NestedForLoopRule(Rule):
     severity = RuleSeverity.ERROR
     version = "<4.0"
     added_in_version = "1.0.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.CONVENTIONAL, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class InconsistentAssignmentRule(Rule):
@@ -211,6 +221,9 @@ class InconsistentAssignmentRule(Rule):
         ),
     ]
     added_in_version = "1.7.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.CONVENTIONAL, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class InconsistentAssignmentInVariablesRule(Rule):
@@ -269,6 +282,9 @@ class InconsistentAssignmentInVariablesRule(Rule):
         )
     ]
     added_in_version = "1.7.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.CONVENTIONAL, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class CanBeResourceFileRule(Rule):
@@ -284,6 +300,9 @@ class CanBeResourceFileRule(Rule):
     severity = RuleSeverity.INFO
     file_wide_rule = True
     added_in_version = "1.10.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.CONVENTIONAL, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class IfCanBeMergedRule(Rule):
@@ -327,6 +346,9 @@ class IfCanBeMergedRule(Rule):
     severity = RuleSeverity.INFO
     version = ">=4.0"
     added_in_version = "2.0.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.CONVENTIONAL, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class StatementOutsideLoopRule(Rule):
@@ -349,6 +371,9 @@ class StatementOutsideLoopRule(Rule):
     severity = RuleSeverity.ERROR
     version = ">=5.0"
     added_in_version = "2.0.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.CONVENTIONAL, issue_type=sonar_qube.SonarQubeIssueType.BUG
+    )
 
 
 class InlineIfCanBeUsedRule(Rule):
@@ -385,6 +410,9 @@ class InlineIfCanBeUsedRule(Rule):
     ]
     severity_threshold = SeverityThreshold("max_width", compare_method="less")
     added_in_version = "2.0.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.CONVENTIONAL, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class UnreachableCodeRule(Rule):
@@ -416,6 +444,9 @@ class UnreachableCodeRule(Rule):
     severity = RuleSeverity.WARNING
     version = ">=5.0"
     added_in_version = "3.1.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.LOGICAL, issue_type=sonar_qube.SonarQubeIssueType.BUG
+    )
 
 
 class MultilineInlineIfRule(Rule):
@@ -456,6 +487,9 @@ class MultilineInlineIfRule(Rule):
     severity = RuleSeverity.WARNING
     version = ">=5.0"
     added_in_version = "3.1.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.FORMATTED, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class UnnecessaryStringConversionRule(Rule):
@@ -555,6 +589,9 @@ class ExpressionCanBeSimplifiedRule(Rule):
     severity = RuleSeverity.INFO
     version = ">=4.0"
     added_in_version = "4.0.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.CONVENTIONAL, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class MisplacedNegativeConditionRule(Rule):
@@ -595,6 +632,9 @@ class MisplacedNegativeConditionRule(Rule):
     severity = RuleSeverity.INFO
     version = ">=4.0"
     added_in_version = "4.0.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.CONVENTIONAL, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class ReturnChecker(VisitorChecker):
diff --git a/src/robocop/linter/rules/naming.py b/src/robocop/linter/rules/naming.py
index d6cc395..c600f19 100644
--- a/src/robocop/linter/rules/naming.py
+++ b/src/robocop/linter/rules/naming.py
@@ -14,6 +14,7 @@ from robot.parsing.model.blocks import TestCaseSection
 from robot.parsing.model.statements import Arguments
 from robot.variables.search import search_variable
 
+from robocop.linter import sonar_qube
 from robocop.linter.rules import Rule, RuleParam, RuleSeverity, VisitorChecker, deprecated, variables
 from robocop.linter.utils import (
     ROBOT_VERSION,
@@ -61,6 +62,9 @@ class NotAllowedCharInNameRule(Rule):
         )
     ]
     added_in_version = "1.0.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.IDENTIFIABLE, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class WrongCaseInKeywordNameRule(Rule):
@@ -120,6 +124,9 @@ class WrongCaseInKeywordNameRule(Rule):
         ),
     ]
     added_in_version = "1.0.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.IDENTIFIABLE, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class KeywordNameIsReservedWordRule(Rule):
@@ -147,6 +154,9 @@ class KeywordNameIsReservedWordRule(Rule):
     message = "'{keyword_name}' is a reserved keyword{error_msg}"
     severity = RuleSeverity.ERROR
     added_in_version = "1.0.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.IDENTIFIABLE, issue_type=sonar_qube.SonarQubeIssueType.BUG
+    )
 
 
 class UnderscoreInKeywordNameRule(Rule):
@@ -170,6 +180,9 @@ class UnderscoreInKeywordNameRule(Rule):
     message = "Underscores in keyword name '{keyword_name}'"
     severity = RuleSeverity.WARNING
     added_in_version = "1.0.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.IDENTIFIABLE, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class SettingNameNotInTitleCaseRule(Rule):
@@ -204,6 +217,9 @@ class SettingNameNotInTitleCaseRule(Rule):
     message = "Setting name '{setting_name}' not in title or uppercase"
     severity = RuleSeverity.WARNING
     added_in_version = "1.0.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.IDENTIFIABLE, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class SectionNameInvalidRule(Rule):
@@ -229,6 +245,9 @@ class SectionNameInvalidRule(Rule):
     message = "Section name should be in format '{section_title_case}' or '{section_upper_case}'"  # TODO: rename
     severity = RuleSeverity.WARNING
     added_in_version = "1.0.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.IDENTIFIABLE, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class NotCapitalizedTestCaseTitleRule(Rule):
@@ -252,6 +271,9 @@ class NotCapitalizedTestCaseTitleRule(Rule):
     message = "Test case '{test_name}' title does not start with capital letter"
     severity = RuleSeverity.WARNING
     added_in_version = "1.4.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.IDENTIFIABLE, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class SectionVariableNotUppercaseRule(Rule):
@@ -276,6 +298,9 @@ class SectionVariableNotUppercaseRule(Rule):
     severity = RuleSeverity.WARNING
     added_in_version = "1.4.0"
     style_guide_ref = ["#variables-section", "#variable-scope-and-casing"]
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.IDENTIFIABLE, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class ElseNotUpperCaseRule(Rule):
@@ -313,6 +338,9 @@ class ElseNotUpperCaseRule(Rule):
     message = "ELSE and ELSE IF is not uppercase"
     severity = RuleSeverity.ERROR
     added_in_version = "1.5.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.IDENTIFIABLE, issue_type=sonar_qube.SonarQubeIssueType.BUG
+    )
 
 
 class KeywordNameIsEmptyRule(Rule):
@@ -332,6 +360,9 @@ class KeywordNameIsEmptyRule(Rule):
     message = "Keyword name is empty"
     severity = RuleSeverity.ERROR
     added_in_version = "1.8.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.IDENTIFIABLE, issue_type=sonar_qube.SonarQubeIssueType.BUG
+    )
 
 
 class TestCaseNameIsEmptyRule(Rule):
@@ -351,6 +382,9 @@ class TestCaseNameIsEmptyRule(Rule):
     message = "Test case name is empty"
     severity = RuleSeverity.ERROR
     added_in_version = "1.8.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.IDENTIFIABLE, issue_type=sonar_qube.SonarQubeIssueType.BUG
+    )
 
 
 class EmptyLibraryAliasRule(Rule):
@@ -376,6 +410,9 @@ class EmptyLibraryAliasRule(Rule):
     message = "Library alias is empty"
     severity = RuleSeverity.ERROR
     added_in_version = "1.10.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.COMPLETE, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class DuplicatedLibraryAliasRule(Rule):
@@ -395,6 +432,9 @@ class DuplicatedLibraryAliasRule(Rule):
     message = "Library alias is the same as original name"
     severity = RuleSeverity.WARNING
     added_in_version = "1.10.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.DISTINCT, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class BddWithoutKeywordCallRule(Rule):
@@ -429,6 +469,9 @@ class BddWithoutKeywordCallRule(Rule):
     message = "BDD reserved keyword '{keyword_name}' not followed by any keyword{error_msg}"
     severity = RuleSeverity.WARNING
     added_in_version = "1.11.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.COMPLETE, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class NotAllowedCharInFilenameRule(Rule):
@@ -459,6 +502,9 @@ class NotAllowedCharInFilenameRule(Rule):
         ),
     ]
     added_in_version = "2.1.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.IDENTIFIABLE, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class InvalidSectionRule(Rule):
@@ -484,6 +530,9 @@ class InvalidSectionRule(Rule):
     severity = RuleSeverity.ERROR
     version = ">=6.1"
     added_in_version = "3.2.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.IDENTIFIABLE, issue_type=sonar_qube.SonarQubeIssueType.BUG
+    )
 
 
 class MixedTaskTestSettingsRule(Rule):
@@ -502,6 +551,9 @@ class MixedTaskTestSettingsRule(Rule):
     message = "Use {task_or_test}-related setting '{setting}' if {tasks_or_tests} section is used"  # TODO: Rename
     severity = RuleSeverity.WARNING
     added_in_version = "3.3.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.CONVENTIONAL, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 SET_VARIABLE_VARIANTS = {
diff --git a/src/robocop/linter/rules/order.py b/src/robocop/linter/rules/order.py
index 9562780..162bf2d 100644
--- a/src/robocop/linter/rules/order.py
+++ b/src/robocop/linter/rules/order.py
@@ -3,6 +3,7 @@ from __future__ import annotations
 from robot.api import Token
 from robot.parsing.model.blocks import Keyword, TestCase
 
+from robocop.linter import sonar_qube
 from robocop.linter.rules import Rule, RuleParam, RuleSeverity, VisitorChecker
 
 
@@ -124,6 +125,9 @@ class TestCaseSectionOutOfOrderRule(Rule):
     ]
     added_in_version = "5.3.0"
     style_guide_ref = ["#test-cases-or-tasks"]
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.CONVENTIONAL, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class KeywordSectionOutOfOrderRule(Rule):
@@ -178,6 +182,9 @@ class KeywordSectionOutOfOrderRule(Rule):
     ]
     added_in_version = "5.3.0"
     style_guide_ref = ["#keyword"]
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.CONVENTIONAL, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class SectionOutOfOrderRule(Rule):
@@ -227,6 +234,9 @@ class SectionOutOfOrderRule(Rule):
         )
     ]
     style_guide_ref = ["#sections"]
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.CONVENTIONAL, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class TestAndKeywordOrderChecker(VisitorChecker):
diff --git a/src/robocop/linter/rules/spacing.py b/src/robocop/linter/rules/spacing.py
index 31f46ff..836513e 100644
--- a/src/robocop/linter/rules/spacing.py
+++ b/src/robocop/linter/rules/spacing.py
@@ -19,6 +19,7 @@ try:
 except ImportError:
     InlineIfHeader = None
 
+from robocop.linter import sonar_qube
 from robocop.linter.rules import RawFileChecker, Rule, RuleParam, RuleSeverity, SeverityThreshold, VisitorChecker
 from robocop.linter.utils import get_errors, get_section_name, str2bool, token_col
 from robocop.parsing.run_keywords import is_run_keyword
@@ -59,6 +60,9 @@ class TrailingWhitespaceRule(Rule):
     severity = RuleSeverity.WARNING
     added_in_version = "1.0.0"
     style_guide_ref = ["#trailing-whitespaces"]
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.FORMATTED, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class MissingTrailingBlankLineRule(Rule):
@@ -70,6 +74,9 @@ class MissingTrailingBlankLineRule(Rule):
     severity = RuleSeverity.WARNING
     added_in_version = "1.0.0"
     style_guide_ref = ["#spacing-after-sections"]
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.FORMATTED, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class EmptyLinesBetweenSectionsRule(Rule):
@@ -113,6 +120,9 @@ class EmptyLinesBetweenSectionsRule(Rule):
     ]
     added_in_version = "1.0.0"
     style_guide_ref = ["#spacing-after-sections"]
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.FORMATTED, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class EmptyLinesBetweenTestCasesRule(Rule):
@@ -156,6 +166,9 @@ class EmptyLinesBetweenTestCasesRule(Rule):
     ]
     added_in_version = "1.0.0"
     style_guide_ref = ["#spacing-after-test-cases-or-tasks"]
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.FORMATTED, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class EmptyLinesBetweenKeywordsRule(Rule):
@@ -199,6 +212,9 @@ class EmptyLinesBetweenKeywordsRule(Rule):
     ]
     added_in_version = "1.0.0"
     style_guide_ref = ["#spacing-after-keywords"]
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.FORMATTED, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class MixedTabsAndSpacesRule(Rule):
@@ -214,6 +230,9 @@ class MixedTabsAndSpacesRule(Rule):
     file_wide_rule = True
     severity = RuleSeverity.WARNING
     added_in_version = "1.1.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.FORMATTED, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class BadIndentRule(Rule):
@@ -262,6 +281,9 @@ class BadIndentRule(Rule):
         "#block-indentation",
         "#indentation-within-test-cases-tasks-and-keywords-section",
     ]
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.FORMATTED, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class EmptyLineAfterSectionRule(Rule):
@@ -298,6 +320,9 @@ class EmptyLineAfterSectionRule(Rule):
     severity_threshold = SeverityThreshold("empty_lines", substitute_value="allowed_empty_lines")
     added_in_version = "1.2.0"
     style_guide_ref = ["#spacing-after-the-section-header-line"]
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.FORMATTED, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class TooManyTrailingBlankLinesRule(Rule):
@@ -314,6 +339,9 @@ class TooManyTrailingBlankLinesRule(Rule):
     severity = RuleSeverity.WARNING
     added_in_version = "1.4.0"
     style_guide_ref = ["#spacing-after-sections"]
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.FORMATTED, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class MisalignedContinuationRule(Rule):
@@ -350,6 +378,9 @@ class MisalignedContinuationRule(Rule):
     severity = RuleSeverity.WARNING
     added_in_version = "1.6.0"
     style_guide_ref = ["#variables-section-line-continuation"]
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.FORMATTED, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class ConsecutiveEmptyLinesRule(Rule):
@@ -407,6 +438,9 @@ class ConsecutiveEmptyLinesRule(Rule):
         "#spacing-between-code-blocks-within-test-cases-or-tasks",
         "#spacing-between-code-blocks-within-keyword-calls",
     ]
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.FORMATTED, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class EmptyLinesInStatementRule(Rule):
@@ -443,6 +477,9 @@ class EmptyLinesInStatementRule(Rule):
     severity = RuleSeverity.WARNING
     added_in_version = "1.8.0"
     style_guide_ref = ["#spacing-of-line-continuations"]
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.FORMATTED, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class VariableNotLeftAlignedRule(Rule):
@@ -470,6 +507,9 @@ class VariableNotLeftAlignedRule(Rule):
     version = ">=4.0"
     added_in_version = "1.8.0"
     style_guide_ref = ["#indentation-within-variables-section"]
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.FORMATTED, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class MisalignedContinuationRowRule(Rule):
@@ -515,6 +555,9 @@ class MisalignedContinuationRowRule(Rule):
         ),
     ]
     added_in_version = "1.11.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.FORMATTED, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class SuiteSettingNotLeftAlignedRule(Rule):
@@ -544,6 +587,9 @@ class SuiteSettingNotLeftAlignedRule(Rule):
     version = ">=4.0"
     added_in_version = "2.4.0"
     style_guide_ref = ["#indentation-within-settings-section"]
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.FORMATTED, issue_type=sonar_qube.SonarQubeIssueType.BUG
+    )
 
 
 class BadBlockIndentRule(Rule):
@@ -581,6 +627,9 @@ class BadBlockIndentRule(Rule):
     severity = RuleSeverity.ERROR
     added_in_version = "3.0.0"
     style_guide_ref = ["#indentation"]
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.FORMATTED, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class FirstArgumentInNewLineRule(Rule):
@@ -609,6 +658,9 @@ class FirstArgumentInNewLineRule(Rule):
     message = "First argument: '{argument_name}' is not placed on the same line as [Arguments] setting"
     severity = RuleSeverity.WARNING
     added_in_version = "5.3.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.CONVENTIONAL, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class InvalidSpacingChecker(RawFileChecker):  # TODO merge, we can just use single RawFileChecker
diff --git a/src/robocop/linter/rules/tags.py b/src/robocop/linter/rules/tags.py
index b3b3307..51114c2 100644
--- a/src/robocop/linter/rules/tags.py
+++ b/src/robocop/linter/rules/tags.py
@@ -7,6 +7,7 @@ from typing import TYPE_CHECKING
 
 from robot.api import Token
 
+from robocop.linter import sonar_qube
 from robocop.linter.rules import Rule, RuleSeverity, VisitorChecker
 from robocop.linter.utils import variable_matcher
 
@@ -19,7 +20,7 @@ class TagWithSpaceRule(Rule):
     """
     Tag with space.
 
-    When including or excluding tags, it may leads to unexpected behavior. It's recommended to use short tag names
+    When including or excluding tags, it may lead to unexpected behavior. It's recommended to use short tag names
     without spaces.
 
     Example of rule violation::
@@ -35,6 +36,9 @@ class TagWithSpaceRule(Rule):
     message = "Tag '{tag}' contains spaces"
     severity = RuleSeverity.WARNING
     added_in_version = "1.0.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.CONVENTIONAL, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class TagWithOrAndRule(Rule):
@@ -61,6 +65,9 @@ class TagWithOrAndRule(Rule):
     message = "Tag '{tag}' with reserved word OR/AND"
     severity = RuleSeverity.INFO
     added_in_version = "1.0.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.CONVENTIONAL, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class TagWithReservedWordRule(Rule):
@@ -91,6 +98,9 @@ class TagWithReservedWordRule(Rule):
     message = "Tag '{tag}' prefixed with reserved word `robot:`"
     severity = RuleSeverity.WARNING
     added_in_version = "1.0.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.CONVENTIONAL, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class CouldBeTestTagsRule(Rule):
@@ -121,6 +131,9 @@ class CouldBeTestTagsRule(Rule):
     file_wide_rule = True
     severity = RuleSeverity.INFO
     added_in_version = "1.0.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.CONVENTIONAL, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class TagAlreadySetInTestTagsRule(Rule):
@@ -148,6 +161,9 @@ class TagAlreadySetInTestTagsRule(Rule):
     message = "Tag '{tag}' is already set by {test_force_tags} in suite settings"
     severity = RuleSeverity.INFO
     added_in_version = "1.0.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.DISTINCT, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class UnnecessaryDefaultTagsRule(Rule):
@@ -177,6 +193,9 @@ class UnnecessaryDefaultTagsRule(Rule):
     message = "Tags defined in Default Tags are always overwritten"
     severity = RuleSeverity.INFO
     added_in_version = "1.0.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.CONVENTIONAL, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class EmptyTagsRule(Rule):
@@ -193,6 +212,9 @@ class EmptyTagsRule(Rule):
     message = "[Tags] setting without values{optional_warning}"
     severity = RuleSeverity.WARNING
     added_in_version = "2.0.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.COMPLETE, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class DuplicatedTagsRule(Rule):
@@ -215,6 +237,9 @@ class DuplicatedTagsRule(Rule):
     message = "Multiple tags with name '{name}' (first occurrence at line {line} column {column})"
     severity = RuleSeverity.WARNING
     added_in_version = "2.0.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.DISTINCT, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class CouldBeKeywordTagsRule(Rule):
@@ -245,6 +270,9 @@ class CouldBeKeywordTagsRule(Rule):
     severity = RuleSeverity.INFO
     version = ">=6"
     added_in_version = "3.3.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.CONVENTIONAL, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class TagAlreadySetInKeywordTagsRule(Rule):
@@ -269,6 +297,9 @@ class TagAlreadySetInKeywordTagsRule(Rule):
     severity = RuleSeverity.INFO
     version = ">=6"
     added_in_version = "3.3.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.DISTINCT, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class TagNameChecker(VisitorChecker):
diff --git a/src/robocop/linter/rules/usage.py b/src/robocop/linter/rules/usage.py
index 4e3508d..9b91a8b 100644
--- a/src/robocop/linter/rules/usage.py
+++ b/src/robocop/linter/rules/usage.py
@@ -10,6 +10,7 @@ from robot.parsing.model.blocks import File, Keyword, Section
 from robot.parsing.model.statements import Tags
 from robot.running.arguments import EmbeddedArguments
 
+from robocop.linter import sonar_qube
 from robocop.linter.rules import ProjectChecker, Rule, RuleSeverity
 from robocop.linter.utils.misc import ROBOT_VERSION, normalize_robot_name
 from robocop.parsing.run_keywords import iterate_keyword_names
@@ -47,6 +48,9 @@ class UnusedKeywordRule(Rule):
     severity = RuleSeverity.INFO
     enabled = False
     added_in_version = "5.3.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.COMPLETE, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 if ROBOT_VERSION.major < 6:
diff --git a/src/robocop/linter/rules/variables.py b/src/robocop/linter/rules/variables.py
index ad25a42..578423f 100644
--- a/src/robocop/linter/rules/variables.py
+++ b/src/robocop/linter/rules/variables.py
@@ -1,3 +1,4 @@
+from robocop.linter import sonar_qube
 from robocop.linter.rules import Rule, RuleParam, RuleSeverity
 
 
@@ -63,6 +64,9 @@ class EmptyVariableRule(Rule):
         )
     ]
     added_in_version = "1.10.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.COMPLETE, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class UnusedVariableRule(Rule):
@@ -98,6 +102,9 @@ class UnusedVariableRule(Rule):
     message = "Variable '{name}' is assigned but not used"
     severity = RuleSeverity.INFO
     added_in_version = "3.2.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.CLEAR, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class VariableOverwrittenBeforeUsageRule(Rule):
@@ -127,6 +134,9 @@ class VariableOverwrittenBeforeUsageRule(Rule):
     message = "Local variable '{name}' is overwritten before usage"
     severity = RuleSeverity.WARNING
     added_in_version = "3.2.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.CLEAR, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class NoGlobalVariableRule(Rule):
@@ -182,6 +192,9 @@ class NoGlobalVariableRule(Rule):
     message = "Variable with global scope defined outside variables section"
     severity = RuleSeverity.WARNING
     added_in_version = "5.6.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.CONVENTIONAL, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class NoSuiteVariableRule(Rule):
@@ -231,6 +244,9 @@ class NoSuiteVariableRule(Rule):
     message = "Variable defined with suite scope"
     severity = RuleSeverity.WARNING
     added_in_version = "5.6.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.CONVENTIONAL, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class NoTestVariableRule(Rule):
@@ -280,6 +296,9 @@ class NoTestVariableRule(Rule):
     message = "Variable defined with test/task scope"
     severity = RuleSeverity.WARNING
     added_in_version = "5.6.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.CONVENTIONAL, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class NonLocalVariablesShouldBeUppercaseRule(Rule):
@@ -313,6 +332,9 @@ class NonLocalVariablesShouldBeUppercaseRule(Rule):
     message = "Non local variable is not uppercase"
     severity = RuleSeverity.WARNING
     added_in_version = "1.4.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.CONVENTIONAL, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class PossibleVariableOverwritingRule(Rule):
@@ -337,6 +359,9 @@ class PossibleVariableOverwritingRule(Rule):
     message = "Variable '{variable_name}' may overwrite similar variable inside '{block_name}' {block_type}"
     severity = RuleSeverity.INFO
     added_in_version = "1.10.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.CONVENTIONAL, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class HyphenInVariableNameRule(Rule):
@@ -370,6 +395,9 @@ class HyphenInVariableNameRule(Rule):
     message = "Hyphen in variable name '{variable_name}'"
     severity = RuleSeverity.INFO
     added_in_version = "1.10.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.CONVENTIONAL, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class InconsistentVariableNameRule(Rule):
@@ -411,6 +439,9 @@ class InconsistentVariableNameRule(Rule):
     message = "Variable '{name}' has inconsistent naming. First used as '{first_use}'"
     severity = RuleSeverity.WARNING
     added_in_version = "3.2.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.CONVENTIONAL, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class OverwritingReservedVariableRule(Rule):
@@ -429,6 +460,9 @@ class OverwritingReservedVariableRule(Rule):
     message = "{var_or_arg} '{variable_name}' overwrites reserved variable '{reserved_variable}'"
     severity = RuleSeverity.WARNING
     added_in_version = "3.2.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.CONVENTIONAL, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
 
 
 class DuplicatedAssignedVarNameRule(Rule):
@@ -455,3 +489,6 @@ class DuplicatedAssignedVarNameRule(Rule):
     message = "Assigned variable name '{variable_name}' is already used"
     severity = RuleSeverity.INFO
     added_in_version = "1.12.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.DISTINCT, issue_type=sonar_qube.SonarQubeIssueType.CODE_SMELL
+    )
diff --git a/src/robocop/linter/rules/whitespace.py b/src/robocop/linter/rules/whitespace.py
index 2ddb809..c8d0fae 100644
--- a/src/robocop/linter/rules/whitespace.py
+++ b/src/robocop/linter/rules/whitespace.py
@@ -5,6 +5,7 @@ Holds rules that are used outside spacing module for now - after redesign to sep
 can be moved here.
 """
 
+from robocop.linter import sonar_qube
 from robocop.linter.rules import Rule, RuleSeverity
 
 
@@ -47,6 +48,9 @@ class NotEnoughWhitespaceAfterSettingRule(Rule):
     message = "Not enough whitespace after '{setting_name}' setting"
     severity = RuleSeverity.ERROR
     added_in_version = "1.0.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.COMPLETE, issue_type=sonar_qube.SonarQubeIssueType.BUG
+    )
 
 
 class NotEnoughWhitespaceAfterNewlineMarkerRule(Rule):
@@ -76,6 +80,9 @@ class NotEnoughWhitespaceAfterNewlineMarkerRule(Rule):
     message = "Not enough whitespace after '...' marker"
     severity = RuleSeverity.ERROR
     added_in_version = "1.11.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.COMPLETE, issue_type=sonar_qube.SonarQubeIssueType.BUG
+    )
 
 
 class NotEnoughWhitespaceAfterVariableRule(Rule):
@@ -104,6 +111,9 @@ class NotEnoughWhitespaceAfterVariableRule(Rule):
     severity = RuleSeverity.ERROR
     version = ">=4.0"
     added_in_version = "1.11.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.COMPLETE, issue_type=sonar_qube.SonarQubeIssueType.BUG
+    )
 
 
 class NotEnoughWhitespaceAfterSuiteSettingRule(Rule):
@@ -135,3 +145,6 @@ class NotEnoughWhitespaceAfterSuiteSettingRule(Rule):
     message = "Not enough whitespace after '{setting_name}' setting"
     severity = RuleSeverity.ERROR
     added_in_version = "1.11.0"
+    sonar_qube_attrs = sonar_qube.SonarQubeAttributes(
+        clean_code=sonar_qube.CleanCodeAttribute.COMPLETE, issue_type=sonar_qube.SonarQubeIssueType.BUG
+    )
diff --git a/src/robocop/linter/sonar_qube.py b/src/robocop/linter/sonar_qube.py
new file mode 100644
index 0000000..a22fbec
--- /dev/null
+++ b/src/robocop/linter/sonar_qube.py
@@ -0,0 +1,38 @@
+"""SonarQube own attributes used in their reporting and dashboards."""
+
+from dataclasses import dataclass
+from enum import Enum
+
+
+class SonarQubeIssueType(Enum):
+    BUG = "BUG"
+    VULNERABILITY = "VULNERABILITY"
+    CODE_SMELL = "CODE_SMELL"
+
+
+class CleanCodeAttribute(Enum):
+    # Consistency
+    FORMATTED = "FORMATTED"  # The code presentation is systematic and regular
+    CONVENTIONAL = "CONVENTIONAL"  # Faced with equally good options, the code adheres to a single choice across
+    # all instances
+    IDENTIFIABLE = "IDENTIFIABLE"  # The names follow a regular structure based on language conventions
+    # Intentionality
+    CLEAR = "CLEAR"  # The code is self-explanatory, transparently communicating its functionality
+    LOGICAL = "LOGICAL"  # The code has well-formed and sound instructions that work together
+    COMPLETE = "COMPLETE"  # The code constructs are comprehensive and used adequately and thoroughly
+    EFFICIENT = "EFFICIENT"  # The code uses resources without needless waste
+    # Adaptability
+    FOCUSED = "FOCUSED"  # The code has a single, narrow, and specific scope
+    DISTINCT = "DISTINCT"  # The code procedures and data are unique and distinctive, without undue duplication
+    MODULAR = "MODULAR"  # The code has been organized and distributed to emphasize the separation between its parts
+    TESTED = "TESTED"  # The code has automated checks that provide confidence in the functionality
+    # Responsibility
+    LAWFUL = "LAWFUL"  # The code respects licensing and copyright regulation
+    TRUSTWORTHY = "TRUSTWORTHY"  # The code abstains from revealing or hard-coding private information
+    RESPECTFUL = "RESPECTFUL"  # The code refrains from using discriminatory and offensive language
+
+
+@dataclass
+class SonarQubeAttributes:
+    clean_code: CleanCodeAttribute
+    issue_type: SonarQubeIssueType


Test patch:
diff --git a/tests/linter/reports/test_sarif_report.py b/tests/linter/reports/test_sarif_report.py
index fa2c25b..8a01ef9 100644
--- a/tests/linter/reports/test_sarif_report.py
+++ b/tests/linter/reports/test_sarif_report.py
@@ -67,9 +67,9 @@ class TestSarifReport:
                                     "name": r.name,
                                     "helpUri": f"https://robocop.readthedocs.io/en/{__version__}/rules_list.html#{r.name}",
                                     "shortDescription": {"text": r.message},
-                                    "fullDescription": {"text": r.__doc__},
+                                    "fullDescription": {"text": r.docs},
                                     "defaultConfiguration": {"level": r.default_severity.name.lower()},
-                                    "help": {"text": r.__doc__, "markdown": r.__doc__},
+                                    "help": {"text": r.docs, "markdown": r.docs},
                                 }
                                 for r in (rule, rule2)
                             ],
diff --git a/tests/linter/reports/test_sonar_qube.py b/tests/linter/reports/test_sonar_qube.py
new file mode 100644
index 0000000..d3d7aa7
--- /dev/null
+++ b/tests/linter/reports/test_sonar_qube.py
@@ -0,0 +1,85 @@
+import json
+from pathlib import Path
+from unittest.mock import Mock
+
+from robocop.linter.diagnostics import Diagnostics
+from robocop.linter.reports.sonarqube import SonarQubeReport
+from tests.linter.reports import generate_issues
+
+
+class TestSonarQubeReport:
+    def test_sonarqube_report(self, rule, rule2, tmp_path, config):
+        output_file = tmp_path / "reports" / "sonarqube.json"
+        report = SonarQubeReport(config)
+        report.configure("output_path", output_file)
+
+        issues = generate_issues(rule, rule2)
+
+        expected_report = {
+            "rules": [
+                {
+                    "cleanCodeAttribute": "CONVENTIONAL",
+                    "description": "",
+                    "engineId": "robocop",
+                    "id": "0101",
+                    "name": "some-message",
+                    "severity": "MINOR",
+                    "type": "CODE_SMELL",
+                },
+                {
+                    "cleanCodeAttribute": "CONVENTIONAL",
+                    "description": "",
+                    "engineId": "robocop",
+                    "id": "0902",
+                    "name": "other-message",
+                    "severity": "MAJOR",
+                    "type": "CODE_SMELL",
+                },
+            ],
+            "issues": [
+                {
+                    "primaryLocation": {
+                        "filePath": "tests/atest/rules/comments/ignored-data/test.robot",
+                        "message": "Some description",
+                        "textRange": {"endColumn": 10, "endLine": 50, "startColumn": 10, "startLine": 50},
+                    },
+                    "ruleId": "0101",
+                },
+                {
+                    "primaryLocation": {
+                        "filePath": "tests/atest/rules/comments/ignored-data/test.robot",
+                        "message": "Some description. Example",
+                        "textRange": {"endColumn": 10, "endLine": 51, "startColumn": 10, "startLine": 50},
+                    },
+                    "ruleId": "0902",
+                },
+                {
+                    "primaryLocation": {
+                        "filePath": "tests/atest/rules/misc/empty-return/test.robot",
+                        "message": "Some description. Example",
+                        "textRange": {"endColumn": 15, "endLine": 15, "startColumn": 10, "startLine": 11},
+                    },
+                    "ruleId": "0902",
+                },
+                {
+                    "primaryLocation": {
+                        "filePath": "tests/atest/rules/misc/empty-return/test.robot",
+                        "message": "Some description",
+                        "textRange": {"endColumn": 12, "endLine": 50, "startColumn": 10, "startLine": 50},
+                    },
+                    "ruleId": "0101",
+                },
+            ],
+        }
+        config_manager = Mock()
+        config_manager.root = Path.cwd()
+        report.generate_report(Diagnostics(issues), config_manager)
+        with open(output_file) as fp:
+            sonarqube_report = json.load(fp)
+        assert expected_report == sonarqube_report
+
+    def test_configure_output_path(self, config):
+        output_path = "path/to/dir/file.json"
+        report = SonarQubeReport(config)
+        report.configure("output_path", output_path)
+        assert report.output_path == output_path