From f3c671d11c0a02c6f8d4b98a6e87d6ad7ee3ffc6 Mon Sep 17 00:00:00 2001 From: Konstantin Kubatkin Date: Fri, 19 Jun 2026 15:45:53 +0300 Subject: [PATCH 1/6] feat: Added SARIF formatter --- prospector/formatters/__init__.py | 3 +- prospector/formatters/sarif.py | 63 +++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 prospector/formatters/sarif.py diff --git a/prospector/formatters/__init__.py b/prospector/formatters/__init__.py index 0cfa985b..16592afd 100644 --- a/prospector/formatters/__init__.py +++ b/prospector/formatters/__init__.py @@ -1,4 +1,4 @@ -from . import emacs, gitlab, grouped, json, pylint, pylint_parseable, text, vscode, xunit, yaml +from . import emacs, gitlab, grouped, json, pylint, pylint_parseable, sarif, text, vscode, xunit, yaml from .base import Formatter __all__ = ("FORMATTERS", "Formatter") @@ -15,4 +15,5 @@ "pylint_parseable": pylint_parseable.PylintParseableFormatter, "xunit": xunit.XunitFormatter, "vscode": vscode.VSCodeFormatter, + "sarif": sarif.SarifFormatter, } diff --git a/prospector/formatters/sarif.py b/prospector/formatters/sarif.py new file mode 100644 index 00000000..6214da01 --- /dev/null +++ b/prospector/formatters/sarif.py @@ -0,0 +1,63 @@ +import importlib +import json +from typing import Any + +from prospector.formatters.base import Formatter + +__all__ = ("SarifFormatter",) + +_VERSION = importlib.metadata.version("prospector") + + +class SarifFormatter(Formatter): + """ + This formatter outputs messages in the SARIF format. + https://www.oasis-open.org/committees/sarif/charter.php + """ + def render(self, summary: bool = True, messages: bool = True, profile: bool = False) -> str: + output: list[dict[str, Any]] = [] + + if messages: + for message in sorted(self.messages): + output.append( + { + "ruleId": message.code, + "level": "warning", + "message": { + "text": f"{message.source}[{message.code}]: {message.message.strip()}" + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": str(self._make_path(message.location)) + } + }, + "region": { + "startLine": message.location.line, + "endLine": message.location.line_end, + "startColumn": message.location.character, + "endColumn": message.location.character_end + } + } + ] + } + ) + + sarif_skeleton = { + "version": "2.1.0", + "$schema": "https://schemastore.azurewebsites.net/schemas/json/sarif-2.1.0.json", + "runs": [ + { + "tool": { + "driver": { + "name": "Prospector", + "informationUri": "https://github.com/prospector-dev/prospector", + "version": _VERSION + } + } + } + ] + } + + return json.dumps(sarif_skeleton, indent=2) From 7bb45b6ef53d6aab7714114975b951412b811bbc Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 19 Jun 2026 12:53:25 +0000 Subject: [PATCH 2/6] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- prospector/formatters/sarif.py | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/prospector/formatters/sarif.py b/prospector/formatters/sarif.py index 6214da01..2960f82b 100644 --- a/prospector/formatters/sarif.py +++ b/prospector/formatters/sarif.py @@ -14,6 +14,7 @@ class SarifFormatter(Formatter): This formatter outputs messages in the SARIF format. https://www.oasis-open.org/committees/sarif/charter.php """ + def render(self, summary: bool = True, messages: bool = True, profile: bool = False) -> str: output: list[dict[str, Any]] = [] @@ -23,24 +24,20 @@ def render(self, summary: bool = True, messages: bool = True, profile: bool = Fa { "ruleId": message.code, "level": "warning", - "message": { - "text": f"{message.source}[{message.code}]: {message.message.strip()}" - }, + "message": {"text": f"{message.source}[{message.code}]: {message.message.strip()}"}, "locations": [ { "physicalLocation": { - "artifactLocation": { - "uri": str(self._make_path(message.location)) - } + "artifactLocation": {"uri": str(self._make_path(message.location))} }, "region": { "startLine": message.location.line, "endLine": message.location.line_end, "startColumn": message.location.character, - "endColumn": message.location.character_end - } + "endColumn": message.location.character_end, + }, } - ] + ], } ) @@ -53,11 +50,11 @@ def render(self, summary: bool = True, messages: bool = True, profile: bool = Fa "driver": { "name": "Prospector", "informationUri": "https://github.com/prospector-dev/prospector", - "version": _VERSION + "version": _VERSION, } } } - ] + ], } return json.dumps(sarif_skeleton, indent=2) From 475674cb189fe2a36004285c2bb086b9dbe33b30 Mon Sep 17 00:00:00 2001 From: Konstantin Kubatkin Date: Fri, 19 Jun 2026 16:07:24 +0300 Subject: [PATCH 3/6] feat: Updated usage.rst --- docs/usage.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/usage.rst b/docs/usage.rst index 18fff34f..4ddb7359 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -4,6 +4,7 @@ Command Line Usage .. _issue_16: https://github.com/PyCQA/prospector/issues/16 .. _vscode_python_plugin: https://marketplace.visualstudio.com/items?itemName=donjayamanne.python .. _gitlab_code_climate_report_format: https://docs.gitlab.com/ci/testing/code_quality/#code-quality-report-format +.. _sarif_report_format: https://www.oasis-open.org/committees/sarif/charter.php The simplest way to run prospector is from the project root with no arguments. It will try to figure everything else out itself and provide sensible defaults:: @@ -39,6 +40,8 @@ The default output format of ``prospector`` is designed to be human readable. Yo +----------------------+----------------------------------------------------------------------------+ | ``gitlab`` | | Support for `gitlab_code_climate_report_format`_ | +----------------------+----------------------------------------------------------------------------+ +| ``sarif`` | | Support for `sarif_report_format`_ | ++----------------------+----------------------------------------------------------------------------+ | ``grouped`` | | Similar to ``text``, but groups all message on the same line together | | | | rather than having a separate entry per message. | +----------------------+----------------------------------------------------------------------------+ From c61937c6a63a67136edcf9c0eef8332e1c6698ad Mon Sep 17 00:00:00 2001 From: Konstantin Kubatkin Date: Fri, 19 Jun 2026 17:53:35 +0300 Subject: [PATCH 4/6] fix: Fixed empty results --- prospector/formatters/sarif.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/prospector/formatters/sarif.py b/prospector/formatters/sarif.py index 2960f82b..fd73d27a 100644 --- a/prospector/formatters/sarif.py +++ b/prospector/formatters/sarif.py @@ -52,7 +52,8 @@ def render(self, summary: bool = True, messages: bool = True, profile: bool = Fa "informationUri": "https://github.com/prospector-dev/prospector", "version": _VERSION, } - } + }, + "results": output, } ], } From 7a05cff7c1a450827cb943c04b6b5bd497d450dc Mon Sep 17 00:00:00 2001 From: Konstantin Kubatkin Date: Fri, 19 Jun 2026 18:29:17 +0300 Subject: [PATCH 5/6] fix: Fixed usage.rst formatting --- docs/usage.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/usage.rst b/docs/usage.rst index 4ddb7359..8e33da3c 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -40,7 +40,7 @@ The default output format of ``prospector`` is designed to be human readable. Yo +----------------------+----------------------------------------------------------------------------+ | ``gitlab`` | | Support for `gitlab_code_climate_report_format`_ | +----------------------+----------------------------------------------------------------------------+ -| ``sarif`` | | Support for `sarif_report_format`_ | +| ``sarif`` | | Support for `sarif_report_format`_ | +----------------------+----------------------------------------------------------------------------+ | ``grouped`` | | Similar to ``text``, but groups all message on the same line together | | | | rather than having a separate entry per message. | From e18c214985cd8c52fd0dd7da4d29b87aff5fb517 Mon Sep 17 00:00:00 2001 From: Konstantin Kubatkin Date: Fri, 19 Jun 2026 20:09:52 +0300 Subject: [PATCH 6/6] fix: Updated the structure of the resulting JSON to match the schema --- prospector/formatters/sarif.py | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/prospector/formatters/sarif.py b/prospector/formatters/sarif.py index fd73d27a..d871288a 100644 --- a/prospector/formatters/sarif.py +++ b/prospector/formatters/sarif.py @@ -16,11 +16,21 @@ class SarifFormatter(Formatter): """ def render(self, summary: bool = True, messages: bool = True, profile: bool = False) -> str: - output: list[dict[str, Any]] = [] + results: list[dict[str, Any]] = [] if messages: for message in sorted(self.messages): - output.append( + region: dict[str, int] = {} + if message.location.line: + region["startLine"] = message.location.line + if message.location.line_end: + region["endLine"] = message.location.line_end + if message.location.character: + region["startColumn"] = message.location.character + if message.location.character_end: + region["endColumn"] = message.location.character_end + + results.append( { "ruleId": message.code, "level": "warning", @@ -28,22 +38,17 @@ def render(self, summary: bool = True, messages: bool = True, profile: bool = Fa "locations": [ { "physicalLocation": { - "artifactLocation": {"uri": str(self._make_path(message.location))} - }, - "region": { - "startLine": message.location.line, - "endLine": message.location.line_end, - "startColumn": message.location.character, - "endColumn": message.location.character_end, - }, + "artifactLocation": {"uri": str(self._make_path(message.location))}, + "region": region, + } } ], } ) - sarif_skeleton = { + output = { "version": "2.1.0", - "$schema": "https://schemastore.azurewebsites.net/schemas/json/sarif-2.1.0.json", + "$schema": "https://json.schemastore.org/sarif-2.1.0.json", "runs": [ { "tool": { @@ -53,9 +58,9 @@ def render(self, summary: bool = True, messages: bool = True, profile: bool = Fa "version": _VERSION, } }, - "results": output, + "results": results, } ], } - return json.dumps(sarif_skeleton, indent=2) + return json.dumps(output, indent=2)