Repository: MarketSquare/robotframework-robocop (python)

Issue:
SonarQube Error: Start pointer should be before end pointer – Invalid Robocop JSON report
When importing the Robocop JSON report into SonarQube via sonar.externalIssuesReportPaths, the scanner fails with the following error:
`ERROR Error during SonarScanner Engine execution
java.lang.IllegalArgumentException: Start pointer [line=1, lineOffset=1] should be before end pointer [line=1, lineOffset=1]`

The generated JSON contains ranges where the start position is equal to the end position:
"issues": [
        {
            "ruleId": "DOC04",
            "primaryLocation": {
                "message": "Missing documentation in resource file",
                "filePath": "keywords/tools/wait_until.resource",
                "textRange": {
                    "startLine": 1,
                    "startColumn": 1,
                    "endLine": 1,
                    "endColumn": 1
                }
            }
        },

Pull request description:
Fix positions in SonarQube report
Fix #1391 

Patch:
diff --git a/docs/releasenotes/6.1.1.rst b/docs/releasenotes/6.1.1.rst
index 5fcb2f9..39dc60f 100644
--- a/docs/releasenotes/6.1.1.rst
+++ b/docs/releasenotes/6.1.1.rst
@@ -37,3 +37,12 @@ Robocop format should now colorize output when displaying differences before and
 
 - ``--no-color`` in the command line
 - or ``color = false`` in ``tool.robocop.format`` section of the configuration file
+
+Invalid issue positions in SonarQube report (#1391)
+---------------------------------------------------
+
+Fixed issues with issue positions in SonarQube:
+
+- SonarQube doesn't accept identical start and end position. It was usually issue reported for whole file (
+  for example missing suite documentation). In such cases we are now only reporting startLine
+- column position was offset by 1 (Robocop use 1 as start position, where SonarQube uses 0)
diff --git a/src/robocop/linter/reports/sonarqube.py b/src/robocop/linter/reports/sonarqube.py
index e889722..f3da6d0 100644
--- a/src/robocop/linter/reports/sonarqube.py
+++ b/src/robocop/linter/reports/sonarqube.py
@@ -4,7 +4,7 @@ import robocop.linter.reports
 from robocop.config import Config, ConfigManager
 from robocop.files import get_relative_path
 from robocop.linter import sonar_qube
-from robocop.linter.diagnostics import Diagnostic, Diagnostics
+from robocop.linter.diagnostics import Diagnostic, Diagnostics, Range
 from robocop.linter.rules import Rule, RuleSeverity
 
 
@@ -51,18 +51,24 @@ class SonarQubeReport(robocop.linter.reports.JsonFileReport):
             **self.get_code_attributes(diagnostic.rule),
         }
 
+    @staticmethod
+    def get_sonar_qube_range(text_range: Range) -> dict:
+        if text_range.start == text_range.end:
+            return {"startLine": text_range.start.line}
+        return {
+            "startLine": text_range.start.line,
+            "startColumn": max(text_range.start.character - 1, 0),
+            "endLine": text_range.end.line,
+            "endColumn": max(text_range.end.character - 1, 0),
+        }
+
     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,
-                },
+                "textRange": self.get_sonar_qube_range(diagnostic.range),
             },
         }
 


Test patch:
diff --git a/tests/linter/reports/test_sonar_qube.py b/tests/linter/reports/test_sonar_qube.py
index f1de880..3a31774 100644
--- a/tests/linter/reports/test_sonar_qube.py
+++ b/tests/linter/reports/test_sonar_qube.py
@@ -41,7 +41,7 @@ class TestSonarQubeReport:
                     "primaryLocation": {
                         "filePath": "tests/atest/rules/comments/ignored-data/test.robot",
                         "message": "Some description",
-                        "textRange": {"endColumn": 10, "endLine": 50, "startColumn": 10, "startLine": 50},
+                        "textRange": {"startLine": 50},
                     },
                     "ruleId": "0101",
                 },
@@ -49,7 +49,7 @@ class TestSonarQubeReport:
                     "primaryLocation": {
                         "filePath": "tests/atest/rules/comments/ignored-data/test.robot",
                         "message": "Some description. Example",
-                        "textRange": {"endColumn": 10, "endLine": 51, "startColumn": 10, "startLine": 50},
+                        "textRange": {"endColumn": 9, "endLine": 51, "startColumn": 9, "startLine": 50},
                     },
                     "ruleId": "0902",
                 },
@@ -57,7 +57,7 @@ class TestSonarQubeReport:
                     "primaryLocation": {
                         "filePath": "tests/atest/rules/misc/empty-return/test.robot",
                         "message": "Some description. Example",
-                        "textRange": {"endColumn": 15, "endLine": 15, "startColumn": 10, "startLine": 11},
+                        "textRange": {"endColumn": 14, "endLine": 15, "startColumn": 9, "startLine": 11},
                     },
                     "ruleId": "0902",
                 },
@@ -65,7 +65,7 @@ class TestSonarQubeReport:
                     "primaryLocation": {
                         "filePath": "tests/atest/rules/misc/empty-return/test.robot",
                         "message": "Some description",
-                        "textRange": {"endColumn": 12, "endLine": 50, "startColumn": 10, "startLine": 50},
+                        "textRange": {"endColumn": 11, "endLine": 50, "startColumn": 9, "startLine": 50},
                     },
                     "ruleId": "0101",
                 },