diff --git a/mypy_gitlab_code_quality/__init__.py b/mypy_gitlab_code_quality/__init__.py index 14bdbc9..300e4bd 100644 --- a/mypy_gitlab_code_quality/__init__.py +++ b/mypy_gitlab_code_quality/__init__.py @@ -35,7 +35,7 @@ class GitlabIssue(TypedDict): location: GitlabIssueLocation -def parse_issue(line: str) -> GitlabIssue | None: +def parse_issue(line: str, fingerprints: set[str] | None = None) -> GitlabIssue | None: if line.startswith("{"): try: match = json.loads(line) @@ -54,17 +54,38 @@ def parse_issue(line: str) -> GitlabIssue | None: ) if match is None: return None - fingerprint = hashlib.md5(line.encode("utf-8"), usedforsecurity=False).hexdigest() error_levels_table = {"error": Severity.major, "note": Severity.info} + + path = match["file"] + line_number = int(match["line"]) + error_level = match["severity"] + message = match["message"] + error_code = match["code"] + + if fingerprints is None: + fingerprints = set() + + def make_fingerprint(salt: str) -> str: + fingerprint_text = f"{salt}::{path}::{error_level}::{error_code}::{message}" + return hashlib.md5( + fingerprint_text.encode("utf-8"), + usedforsecurity=False, + ).hexdigest() + + fingerprint = make_fingerprint("") + while fingerprint in fingerprints: + fingerprint = make_fingerprint(fingerprint) + fingerprints.add(fingerprint) + return { - "description": match["message"], - "check_name": match["code"], + "description": message, + "check_name": error_code, "fingerprint": fingerprint, - "severity": error_levels_table.get(match["severity"], Severity.unknown), + "severity": error_levels_table.get(error_level, Severity.unknown), "location": { - "path": match["file"], + "path": path, "lines": { - "begin": int(match["line"]), + "begin": line_number, }, }, } @@ -91,7 +112,8 @@ def append_or_extend(issues: list[GitlabIssue], new: GitlabIssue) -> list[Gitlab def generate_report(lines: Iterable[str]) -> list[GitlabIssue]: - issues = filter(None, map(parse_issue, lines)) + fingerprints: set[str] = set() + issues = filter(None, (parse_issue(line, fingerprints) for line in lines)) return reduce(append_or_extend, issues, []) diff --git a/tests.py b/tests.py index f828397..b5e9064 100644 --- a/tests.py +++ b/tests.py @@ -1,3 +1,4 @@ +import json import unittest from mypy_gitlab_code_quality import Severity, parse_issue @@ -14,7 +15,7 @@ def test_line_number(self): def test_fingerprint(self): issue = parse_issue("module.py:2: error: Description") - self.assertEqual("a19285c6cdf4dafe237cc5d2de6c0308", issue["fingerprint"]) + self.assertEqual("d84c90ce1414af244070c71da60f2388", issue["fingerprint"]) def test_error_level_error(self): issue = parse_issue("module.py:2: error: Description") @@ -80,18 +81,21 @@ def test_line_number(self): self.assertEqual(2, issue["location"]["lines"]["begin"]) def test_fingerprint(self): - issue = parse_issue( - r"""{ - "file": "module.py", - "line": 2, - "column": 4, - "message": "Description", - "hint": null, - "code": "error-code", - "severity": "error" - }""" - ) - self.assertEqual("4455bb04f307121aa95a7b3725996837", issue["fingerprint"]) + fingerprints = set() + issue_dict = { + "file": "module.py", + "line": 2, + "column": 4, + "message": "Description", + "hint": None, + "code": "error-code", + "severity": "error", + } + issue = parse_issue(json.dumps(issue_dict), fingerprints) + issue_dict["line"] = 3 + other_issue = parse_issue(json.dumps(issue_dict), fingerprints) + self.assertEqual("59d5fc1777dba182a0bcac9dc1bd33a6", issue["fingerprint"]) + self.assertEqual("895f1dad0c7bb31d7bb4f792b2b16784", other_issue["fingerprint"]) def test_error_level_error(self): issue = parse_issue(