99import json
1010from typing import TYPE_CHECKING , Optional , TypedDict
1111
12- from pylint .interfaces import UNDEFINED
12+ from pylint .interfaces import CONFIDENCE_MAP , UNDEFINED
1313from pylint .message import Message
1414from pylint .reporters .base_reporter import BaseReporter
1515from pylint .typing import MessageLocationTuple
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+
114199def register (linter : PyLinter ) -> None :
115200 linter .register_reporter (JSONReporter )
201+ linter .register_reporter (JSON2Reporter )
0 commit comments