From 934e0f575a4f6a46ad5b315692ef175d6a7e96be Mon Sep 17 00:00:00 2001 From: Tino Wagner Date: Wed, 12 Mar 2025 15:56:18 +0100 Subject: [PATCH] Stabilize the fingerprint Remove the line number from fingerprint. If the same fingerprint is already used for another issue in the same file, generate another one. This is more stable compared to the line number itself, which changes whenever code is added or removed above the issue. --- mypy_gitlab_code_quality/__init__.py | 38 ++++++++++++++++++++++------ tests.py | 30 ++++++++++++---------- 2 files changed, 47 insertions(+), 21 deletions(-) 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(