Skip to content

Commit 7264e33

Browse files
DanielNoordPierre-Sassoulasjacobtylerwalls
authored
Add a new json2 reporter (#8929)
Co-authored-by: Pierre Sassoulas <[email protected]> Co-authored-by: Jacob Walls <[email protected]>
1 parent f40e9ff commit 7264e33

File tree

12 files changed

+316
-76
lines changed

12 files changed

+316
-76
lines changed

.pyenchant_pylint_custom_dict.txt

+2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
abc
22
abcmeta
3+
abspath
34
abstractproperty
45
analyse
56
analysed
@@ -95,6 +96,7 @@ dirname
9596
docparams
9697
docstring
9798
docstrings
99+
dumpable
98100
dunder
99101
elif
100102
elif's

doc/user_guide/configuration/all-options.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ Standard Checkers
155155

156156
--output-format
157157
"""""""""""""""
158-
*Set the output format. Available formats are text, parseable, colorized, json and msvs (visual studio). You can also give a reporter class, e.g. mypackage.mymodule.MyReporterClass.*
158+
*Set the output format. Available formats are: text, parseable, colorized, json2 (improved json format), json (old json format) and msvs (visual studio). You can also give a reporter class, e.g. mypackage.mymodule.MyReporterClass.*
159159

160160
**Default:** ``text``
161161

doc/whatsnew/3/3.0/index.rst

+37
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,41 @@ The required ``astroid`` version is now 3.0.0. See the
2323
`astroid changelog <https://pylint.readthedocs.io/projects/astroid/en/latest/changelog.html#what-s-new-in-astroid-3-0-0>`_
2424
for additional fixes, features, and performance improvements applicable to pylint.
2525

26+
A new ``json2`` reporter has been added. It features an enriched output that is
27+
easier to parse and provides more info, here's a sample output.
28+
29+
.. code-block:: json
30+
31+
{
32+
"messages": [
33+
{
34+
"type": "convention",
35+
"symbol": "line-too-long",
36+
"message": "Line too long (1/2)",
37+
"messageId": "C0301",
38+
"confidence": "HIGH",
39+
"module": "0123",
40+
"obj": "",
41+
"line": 1,
42+
"column": 0,
43+
"endLine": 1,
44+
"endColumn": 4,
45+
"path": "0123",
46+
"absolutePath": "0123"
47+
}
48+
],
49+
"statistics": {
50+
"messageTypeCount": {
51+
"fatal": 0,
52+
"error": 0,
53+
"warning": 0,
54+
"refactor": 0,
55+
"convention": 1,
56+
"info": 0
57+
},
58+
"modulesLinted": 1,
59+
"score": 5.0
60+
}
61+
}
62+
2663
.. towncrier release notes start

doc/whatsnew/fragments/4741.feature

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
A new ``json2`` reporter has been added. It features a more enriched output that is
2+
easier to parse and provides more info.
3+
4+
Compared to ``json`` the only changes are that messages are now under the ``"messages"``
5+
key and that ``"message-id"`` now follows the camelCase convention and is renamed to
6+
``"messageId"``.
7+
The new reporter also reports the "score" of the modules you linted as defined by the
8+
``evaluation`` option and provides statistics about the modules you linted.
9+
10+
We encourage users to use the new reporter as the ``json`` reporter will no longer
11+
be maintained.
12+
13+
Closes #4741

pylint/interfaces.py

+1
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,4 @@ class Confidence(NamedTuple):
3535

3636
CONFIDENCE_LEVELS = [HIGH, CONTROL_FLOW, INFERENCE, INFERENCE_FAILURE, UNDEFINED]
3737
CONFIDENCE_LEVEL_NAMES = [i.name for i in CONFIDENCE_LEVELS]
38+
CONFIDENCE_MAP = {i.name: i for i in CONFIDENCE_LEVELS}

pylint/lint/base_options.py

+4-3
Original file line numberDiff line numberDiff line change
@@ -102,9 +102,10 @@ def _make_linter_options(linter: PyLinter) -> Options:
102102
"metavar": "<format>",
103103
"short": "f",
104104
"group": "Reports",
105-
"help": "Set the output format. Available formats are text,"
106-
" parseable, colorized, json and msvs (visual studio)."
107-
" You can also give a reporter class, e.g. mypackage.mymodule."
105+
"help": "Set the output format. Available formats are: text, "
106+
"parseable, colorized, json2 (improved json format), json "
107+
"(old json format) and msvs (visual studio). "
108+
"You can also give a reporter class, e.g. mypackage.mymodule."
108109
"MyReporterClass.",
109110
"kwargs": {"linter": linter},
110111
},

pylint/reporters/__init__.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from pylint import utils
1212
from pylint.reporters.base_reporter import BaseReporter
1313
from pylint.reporters.collecting_reporter import CollectingReporter
14-
from pylint.reporters.json_reporter import JSONReporter
14+
from pylint.reporters.json_reporter import JSON2Reporter, JSONReporter
1515
from pylint.reporters.multi_reporter import MultiReporter
1616
from pylint.reporters.reports_handler_mix_in import ReportsHandlerMixIn
1717

@@ -28,6 +28,7 @@ def initialize(linter: PyLinter) -> None:
2828
"BaseReporter",
2929
"ReportsHandlerMixIn",
3030
"JSONReporter",
31+
"JSON2Reporter",
3132
"CollectingReporter",
3233
"MultiReporter",
3334
]

pylint/reporters/json_reporter.py

+110-24
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import json
1010
from typing import TYPE_CHECKING, Optional, TypedDict
1111

12-
from pylint.interfaces import UNDEFINED
12+
from pylint.interfaces import CONFIDENCE_MAP, UNDEFINED
1313
from pylint.message import Message
1414
from pylint.reporters.base_reporter import BaseReporter
1515
from pylint.typing import MessageLocationTuple
@@ -37,8 +37,12 @@
3737
)
3838

3939

40-
class BaseJSONReporter(BaseReporter):
41-
"""Report messages and layouts in JSON."""
40+
class JSONReporter(BaseReporter):
41+
"""Report messages and layouts in JSON.
42+
43+
Consider using JSON2Reporter instead, as it is superior and this reporter
44+
is no longer maintained.
45+
"""
4246

4347
name = "json"
4448
extension = "json"
@@ -54,25 +58,6 @@ def display_reports(self, layout: Section) -> None:
5458
def _display(self, layout: Section) -> None:
5559
"""Do nothing."""
5660

57-
@staticmethod
58-
def serialize(message: Message) -> OldJsonExport:
59-
raise NotImplementedError
60-
61-
@staticmethod
62-
def deserialize(message_as_json: OldJsonExport) -> Message:
63-
raise NotImplementedError
64-
65-
66-
class JSONReporter(BaseJSONReporter):
67-
68-
"""
69-
TODO: 3.0: Remove this JSONReporter in favor of the new one handling abs-path
70-
and confidence.
71-
72-
TODO: 3.0: Add a new JSONReporter handling abs-path, confidence and scores.
73-
(Ultimately all other breaking change related to json for 3.0).
74-
"""
75-
7661
@staticmethod
7762
def serialize(message: Message) -> OldJsonExport:
7863
return {
@@ -96,7 +81,6 @@ def deserialize(message_as_json: OldJsonExport) -> Message:
9681
symbol=message_as_json["symbol"],
9782
msg=message_as_json["message"],
9883
location=MessageLocationTuple(
99-
# TODO: 3.0: Add abs-path and confidence in a new JSONReporter
10084
abspath=message_as_json["path"],
10185
path=message_as_json["path"],
10286
module=message_as_json["module"],
@@ -106,10 +90,112 @@ def deserialize(message_as_json: OldJsonExport) -> Message:
10690
end_line=message_as_json["endLine"],
10791
end_column=message_as_json["endColumn"],
10892
),
109-
# TODO: 3.0: Make confidence available in a new JSONReporter
11093
confidence=UNDEFINED,
11194
)
11295

11396

97+
class JSONMessage(TypedDict):
98+
type: str
99+
message: str
100+
messageId: str
101+
symbol: str
102+
confidence: str
103+
module: str
104+
path: str
105+
absolutePath: str
106+
line: int
107+
endLine: int | None
108+
column: int
109+
endColumn: int | None
110+
obj: str
111+
112+
113+
class JSON2Reporter(BaseReporter):
114+
name = "json2"
115+
extension = "json2"
116+
117+
def display_reports(self, layout: Section) -> None:
118+
"""Don't do anything in this reporter."""
119+
120+
def _display(self, layout: Section) -> None:
121+
"""Do nothing."""
122+
123+
def display_messages(self, layout: Section | None) -> None:
124+
"""Launch layouts display."""
125+
output = {
126+
"messages": [self.serialize(message) for message in self.messages],
127+
"statistics": self.serialize_stats(),
128+
}
129+
print(json.dumps(output, indent=4), file=self.out)
130+
131+
@staticmethod
132+
def serialize(message: Message) -> JSONMessage:
133+
return JSONMessage(
134+
type=message.category,
135+
symbol=message.symbol,
136+
message=message.msg or "",
137+
messageId=message.msg_id,
138+
confidence=message.confidence.name,
139+
module=message.module,
140+
obj=message.obj,
141+
line=message.line,
142+
column=message.column,
143+
endLine=message.end_line,
144+
endColumn=message.end_column,
145+
path=message.path,
146+
absolutePath=message.abspath,
147+
)
148+
149+
@staticmethod
150+
def deserialize(message_as_json: JSONMessage) -> Message:
151+
return Message(
152+
msg_id=message_as_json["messageId"],
153+
symbol=message_as_json["symbol"],
154+
msg=message_as_json["message"],
155+
location=MessageLocationTuple(
156+
abspath=message_as_json["absolutePath"],
157+
path=message_as_json["path"],
158+
module=message_as_json["module"],
159+
obj=message_as_json["obj"],
160+
line=message_as_json["line"],
161+
column=message_as_json["column"],
162+
end_line=message_as_json["endLine"],
163+
end_column=message_as_json["endColumn"],
164+
),
165+
confidence=CONFIDENCE_MAP[message_as_json["confidence"]],
166+
)
167+
168+
def serialize_stats(self) -> dict[str, str | int | dict[str, int]]:
169+
"""Serialize the linter stats into something JSON dumpable."""
170+
stats = self.linter.stats
171+
172+
counts_dict = {
173+
"fatal": stats.fatal,
174+
"error": stats.error,
175+
"warning": stats.warning,
176+
"refactor": stats.refactor,
177+
"convention": stats.convention,
178+
"info": stats.info,
179+
}
180+
181+
# Calculate score based on the evaluation option
182+
evaluation = self.linter.config.evaluation
183+
try:
184+
note: int = eval( # pylint: disable=eval-used
185+
evaluation, {}, {**counts_dict, "statement": stats.statement or 1}
186+
)
187+
except Exception as ex: # pylint: disable=broad-except
188+
score: str | int = f"An exception occurred while rating: {ex}"
189+
else:
190+
score = round(note, 2)
191+
192+
return {
193+
"messageTypeCount": counts_dict,
194+
"modulesLinted": len(stats.by_module),
195+
"score": score,
196+
}
197+
198+
114199
def register(linter: PyLinter) -> None:
115200
linter.register_reporter(JSONReporter)
201+
linter.register_reporter(JSON2Reporter)

pylint/testutils/_primer/primer_run_command.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,7 @@
1313

1414
from pylint.lint import Run
1515
from pylint.message import Message
16-
from pylint.reporters import JSONReporter
17-
from pylint.reporters.json_reporter import OldJsonExport
16+
from pylint.reporters.json_reporter import JSONReporter, OldJsonExport
1817
from pylint.testutils._primer.package_to_lint import PackageToLint
1918
from pylint.testutils._primer.primer_command import (
2019
PackageData,

0 commit comments

Comments
 (0)