Skip to content

Commit 1e412f8

Browse files
authored
Add ruff linter (#1979)
* Add ruff linter * Fix reported ruff issues * Add ruff config in pyproject.toml
1 parent f02a1ad commit 1e412f8

File tree

10 files changed

+162
-11
lines changed

10 files changed

+162
-11
lines changed

cli/config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
"""
2222
Exports SonarQube platform configuration as JSON
2323
"""
24-
from typing import TextIO, Optional
24+
from typing import TextIO
2525
from threading import Thread
2626
from queue import Queue
2727

conf/ruff2sonar.py

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
#!/usr/bin/env python3
2+
#
3+
# sonar-tools
4+
# Copyright (C) 2025 Olivier Korach
5+
# mailto:olivier.korach AT gmail DOT com
6+
#
7+
# This program is free software; you can redistribute it and/or
8+
# modify it under the terms of the GNU Lesser General Public
9+
# License as published by the Free Software Foundation; either
10+
# version 3 of the License, or (at your option) any later version.
11+
#
12+
# This program is distributed in the hope that it will be useful,
13+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15+
# Lesser General Public License for more details.
16+
#
17+
# You should have received a copy of the GNU Lesser General Public License
18+
# along with this program; if not, write to the Free Software Foundation,
19+
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20+
#
21+
"""
22+
23+
Converts Ruff report format to Sonar external issues format
24+
25+
"""
26+
import sys
27+
import json
28+
import re
29+
30+
TOOLNAME = "ruff"
31+
32+
TRIVY_TO_MQR_MAPPING = {"CRITICAL": "BLOCKER"}
33+
MAPPING = {"LOW": "MINOR", "MEDIUM": "MAJOR", "HIGH": "CRITICAL", "BLOCKER": "BLOCKER"}
34+
35+
36+
def main() -> None:
37+
"""Main script entry point"""
38+
rules_dict = {}
39+
issue_list = {}
40+
lines = sys.stdin.read().splitlines()
41+
i = 0
42+
nblines = len(lines)
43+
while i < nblines:
44+
line = lines[i]
45+
i += 1
46+
# Search for pattern like "F401 [*] `sys` imported but unused"
47+
if not (m := re.match(r"^([A-Za-z0-9]+) (\[\*\]) (.+)$", line)):
48+
continue
49+
rule_id = m.group(1)
50+
message = m.group(3)
51+
line = lines[i]
52+
i += 1
53+
# Search for pattern like " --> cli/cust_measures.py:28:8"
54+
if not (m := re.match(r"^\s*--> ([^:]+):(\d+):(\d+)$", line)):
55+
continue
56+
file_path = m.group(1)
57+
line_no = int(m.group(2))
58+
59+
# Search for " | ^^^" pattern"
60+
while i < nblines and not (m := re.match(r"^\s*\|\s(\s*)(\^+)", lines[i])):
61+
i += 1
62+
if not m:
63+
continue
64+
start_col = len(m.group(1))
65+
end_col = start_col + len(m.group(2))
66+
67+
sonar_issue = {
68+
"ruleId": f"{TOOLNAME}:{rule_id}",
69+
"effortMinutes": 5,
70+
"primaryLocation": {
71+
"message": message,
72+
"filePath": file_path,
73+
"textRange": {
74+
"startLine": line_no,
75+
"endLine": line_no,
76+
"startColumn": start_col,
77+
"endColumn": end_col,
78+
},
79+
},
80+
}
81+
82+
issue_list[f"{rule_id} - {message}"] = sonar_issue
83+
rules_dict[f"{TOOLNAME}:{rule_id}"] = {
84+
"id": f"{TOOLNAME}:{rule_id}",
85+
"name": f"{TOOLNAME}:{rule_id}",
86+
"description": message,
87+
"engineId": TOOLNAME,
88+
"type": "CODE_SMELL",
89+
"severity": "MAJOR",
90+
"cleanCodeAttribute": "LOGICAL",
91+
"impacts": [{"softwareQuality": "MAINTAINABILITY", "severity": "MEDIUM"}],
92+
}
93+
94+
external_issues = {"rules": list(rules_dict.values()), "issues": list(issue_list.values())}
95+
print(json.dumps(external_issues, indent=3, separators=(",", ": ")))
96+
97+
98+
if __name__ == "__main__":
99+
main()

conf/run_linters.sh

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,20 @@ pylintReport="$buildDir/pylint-report.out"
3131
flake8Report="$buildDir/flake8-report.out"
3232
shellcheckReport="$buildDir/external-issues-shellcheck.json"
3333
trivyReport="$buildDir/external-issues-trivy.json"
34+
ruffReport="$buildDir/external-issues-ruff.json"
3435
[ ! -d "$buildDir" ] && mkdir "$buildDir"
3536
# rm -rf -- ${buildDir:?"."}/* .coverage */__pycache__ */*.pyc # mediatools/__pycache__ tests/__pycache__
3637

38+
echo "===> Running ruff"
39+
rm -f "$ruffReport"
40+
ruff check . | tee "$buildDir/ruff-report.txt" | "$CONFDIR"/ruff2sonar.py >"$ruffReport"
41+
re=$?
42+
if [ "$re" == "32" ]; then
43+
>&2 echo "ERROR: pylint execution failed, errcode $re, aborting..."
44+
exit $re
45+
fi
46+
cat "$buildDir/ruff-report.txt"
47+
3748
echo "===> Running pylint"
3849
rm -f "$pylintReport"
3950
pylint --rcfile "$CONFDIR"/pylintrc "$ROOTDIR"/*.py "$ROOTDIR"/*/*.py -r n --msg-template="{path}:{line}: [{msg_id}({symbol}), {obj}] {msg}" | tee "$pylintReport"
@@ -46,19 +57,21 @@ fi
4657
echo "===> Running flake8"
4758
rm -f "$flake8Report"
4859
# See .flake8 file for settings
49-
flake8 --config "$CONFIG/.flake8" "$ROOTDIR" >"$flake8Report"
60+
flake8 --config "$CONFIG/.flake8" "$ROOTDIR" | tee "$flake8Report"
5061

5162
if [ "$localbuild" = "true" ]; then
5263
echo "===> Running shellcheck"
53-
shellcheck "$ROOTDIR"/*.sh "$ROOTDIR"/*/*.sh -s bash -f json | "$CONFDIR"/shellcheck2sonar.py >"$shellcheckReport"
64+
shellcheck "$ROOTDIR"/*.sh "$ROOTDIR"/*/*.sh -s bash -f json | tee "$buildDir/shellcheck-report.txt" | "$CONFDIR"/shellcheck2sonar.py >"$shellcheckReport"
5465
[ ! -s "$shellcheckReport" ] && rm -f "$shellcheckReport"
66+
cat "$buildDir/shellcheck-report.txt"
5567

5668
echo "===> Running checkov"
5769
checkov -d . --framework dockerfile -o sarif --output-file-path "$buildDir"
5870

5971
echo "===> Running trivy"
6072
"$CONFDIR"/build.sh docker
6173
trivy image -f json -o "$buildDir"/trivy_results.json olivierkorach/sonar-tools:latest
74+
cat "$buildDir"/trivy_results.json
6275
python3 "$CONFDIR"/trivy2sonar.py < "$buildDir"/trivy_results.json > "$trivyReport"
6376
[ ! -s "$trivyReport" ] && rm -f "$trivyReport"
6477
fi

migration/migration.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,9 @@
2121
"""
2222
Exports SonarQube platform configuration as JSON
2323
"""
24-
import sys
2524

2625
from cli import options, config
2726
from sonar import exceptions, errcodes, utilities, version
28-
import sonar.util.constants as c
2927
import sonar.logging as log
3028
from sonar import platform
3129

pyproject.toml.bak

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,48 @@ dependencies = [
2828
"jprops",
2929
"levenshtein",
3030
"PyYAML",
31-
]
31+
]
32+
33+
[tool.ruff]
34+
# Set the maximum line length to 150.
35+
line-length = 150
36+
37+
[tool.ruff.lint]
38+
# Add the `line-too-long` rule to the enforced rule set. By default, Ruff omits rules that
39+
# overlap with the use of a formatter, like Black, but we can override this behavior by
40+
# explicitly adding the rule.
41+
extend-select = ["E501"]
42+
select = [
43+
# pycodestyle
44+
"E",
45+
# Pyflakes
46+
"F",
47+
# pyupgrade
48+
"UP",
49+
# flake8-bugbear
50+
"B",
51+
# flake8-simplify
52+
"SIM",
53+
# isort
54+
"I",
55+
]
56+
57+
exclude = [
58+
".eggs",
59+
".git",
60+
61+
".mypy_cache",
62+
".pyenv",
63+
".pytest_cache",
64+
".ruff_cache",
65+
".svn",
66+
".tox",
67+
".venv",
68+
".vscode",
69+
"__pypackages__",
70+
"_build",
71+
"build",
72+
"dist",
73+
"node_modules",
74+
"site-packages"
75+
]

sonar/applications.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
"""
2424

2525
from __future__ import annotations
26-
from typing import Union, Optional
26+
from typing import Optional
2727
import re
2828
import json
2929
from datetime import datetime

sonar/branches.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@
3131
from sonar import platform
3232
from sonar.util import types, cache
3333
import sonar.logging as log
34-
import sonar.sqobject as sq
3534
from sonar import components, settings, exceptions, tasks
3635
from sonar import projects
3736
import sonar.utilities as util

sonar/measures.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
from __future__ import annotations
2323

2424
import json
25-
import re
2625
from http import HTTPStatus
2726
from requests import RequestException
2827
from sonar import metrics, exceptions, platform

sonar/metrics.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
import json
2525
from threading import Lock
2626

27-
import sonar.logging as log
2827
import sonar.platform as pf
2928
from sonar.util.types import ApiPayload
3029
from sonar.util import cache

sonar/qualityprofiles.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -849,7 +849,7 @@ def import_config(endpoint: pf.Platform, config_data: types.ObjectJsonRepr, key_
849849
qp = futures_map[future]
850850
try:
851851
_ = future.result(timeout=60)
852-
except TimeoutError as e:
852+
except TimeoutError:
853853
log.error(f"Importing {qp} timed out after 60 seconds for {str(future)}.")
854854
except Exception as e:
855855
log.error(f"Exception {str(e)} when importing {qp} or its chilren.")

0 commit comments

Comments
 (0)