From f3d7ce904316967f8a3b30ff9234628289a0fd77 Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Mon, 27 Jun 2022 15:31:30 +0200 Subject: [PATCH 1/4] [reporters] Move the reporter tests in 'tests.reporter' --- .../unittest_json_reporter.py} | 0 tests/{ => reporters}/unittest_reporting.py | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename tests/{unittest_reporters_json.py => reporters/unittest_json_reporter.py} (100%) rename tests/{ => reporters}/unittest_reporting.py (100%) diff --git a/tests/unittest_reporters_json.py b/tests/reporters/unittest_json_reporter.py similarity index 100% rename from tests/unittest_reporters_json.py rename to tests/reporters/unittest_json_reporter.py diff --git a/tests/unittest_reporting.py b/tests/reporters/unittest_reporting.py similarity index 100% rename from tests/unittest_reporting.py rename to tests/reporters/unittest_reporting.py From b784f4f2398864038b3bf26ade67c2c8ebf79b63 Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Mon, 27 Jun 2022 16:04:09 +0200 Subject: [PATCH 2/4] [JsonReporter] Add serialization functions to JsonReporter --- doc/conf.py | 2 +- pylint/reporters/json_reporter.py | 58 ++++++++++++++++------- tests/reporters/unittest_json_reporter.py | 37 +++++++++++++++ 3 files changed, 80 insertions(+), 17 deletions(-) diff --git a/doc/conf.py b/doc/conf.py index 3f0c1177b4..739ee312d3 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -25,7 +25,7 @@ # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. +# documentation root, use 'os.path.abspath' to make it absolute, like shown here. sys.path.append(os.path.abspath("exts")) # -- General configuration ----------------------------------------------------- diff --git a/pylint/reporters/json_reporter.py b/pylint/reporters/json_reporter.py index 8adb783027..59b596f3f9 100644 --- a/pylint/reporters/json_reporter.py +++ b/pylint/reporters/json_reporter.py @@ -9,7 +9,10 @@ import json from typing import TYPE_CHECKING +from pylint.interfaces import UNDEFINED +from pylint.message import Message from pylint.reporters.base_reporter import BaseReporter +from pylint.typing import MessageLocationTuple if TYPE_CHECKING: from pylint.lint.pylinter import PyLinter @@ -24,24 +27,47 @@ class JSONReporter(BaseReporter): def display_messages(self, layout: Section | None) -> None: """Launch layouts display.""" - json_dumpable = [ - { - "type": msg.category, - "module": msg.module, - "obj": msg.obj, - "line": msg.line, - "column": msg.column, - "endLine": msg.end_line, - "endColumn": msg.end_column, - "path": msg.path, - "symbol": msg.symbol, - "message": msg.msg or "", - "message-id": msg.msg_id, - } - for msg in self.messages - ] + json_dumpable = [self.serialize(message) for message in self.messages] print(json.dumps(json_dumpable, indent=4), file=self.out) + @staticmethod + def serialize(message: Message) -> dict[str, int | str | None]: + # TODO (3.0) add abs-path and confidence + return { + "type": message.category, + "module": message.module, + "obj": message.obj, + "line": message.line, + "column": message.column, + "endLine": message.end_line, + "endColumn": message.end_column, + "path": message.path, + "symbol": message.symbol, + "message": message.msg or "", + "message-id": message.msg_id, + } + + @staticmethod + def deserialize(message_as_json: dict) -> Message: + return Message( + msg_id=message_as_json["message-id"], + symbol=message_as_json["symbol"], + msg=message_as_json["message"], + location=MessageLocationTuple( + # TODO (3.0) abs-path is not available in json export + abspath=message_as_json["path"], + path=message_as_json["path"], + module=message_as_json["module"], + obj=message_as_json["obj"], + line=message_as_json["line"], + column=message_as_json["column"], + end_line=message_as_json["endLine"], + end_column=message_as_json["endColumn"], + ), + # TODO (3.0) confidence is not available in json export + confidence=UNDEFINED, + ) + def display_reports(self, layout: Section) -> None: """Don't do anything in this reporter.""" diff --git a/tests/reporters/unittest_json_reporter.py b/tests/reporters/unittest_json_reporter.py index 2a0843e7f2..fd29d8ea8a 100644 --- a/tests/reporters/unittest_json_reporter.py +++ b/tests/reporters/unittest_json_reporter.py @@ -10,10 +10,15 @@ from io import StringIO from typing import Any +import pytest + from pylint import checkers +from pylint.interfaces import UNDEFINED from pylint.lint import PyLinter +from pylint.message import Message from pylint.reporters import JSONReporter from pylint.reporters.ureports.nodes import EvaluationSection +from pylint.typing import MessageLocationTuple expected_score_message = "Expected score message" @@ -98,3 +103,35 @@ def get_linter_result(score: bool, message: dict[str, Any]) -> list[dict[str, An reporter.display_messages(None) report_result = json.loads(output.getvalue()) return report_result + + +@pytest.mark.parametrize( + "message", + [ + pytest.param( + Message( + msg_id="C0111", + symbol="missing-docstring", + location=MessageLocationTuple( + # abs-path and path must be equal because one of them is removed + # in the JsonReporter + abspath=__file__, + path=__file__, + module="unittest_json_reporter", + obj="obj", + line=1, + column=3, + end_line=3, + end_column=5, + ), + msg="This is the actual message", + confidence=UNDEFINED, + ), + id="everything-defined", + ) + ], +) +def test_serialize_deserialize(message): + # TODO (3.0): Add confidence handling, add path and abs path handling + json_message = JSONReporter.serialize(message) + assert message == JSONReporter.deserialize(json_message) From 777f847aeffbf20cff1db25a2a6b7c8ca12f6fa2 Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Mon, 27 Jun 2022 16:30:39 +0200 Subject: [PATCH 3/4] [pylint.message] Add a location attribute to 'pylint.Message' --- pylint/message/message.py | 13 +++++++++++++ pylint/reporters/json_reporter.py | 6 +++--- tests/message/unittest_message.py | 2 ++ tests/reporters/unittest_json_reporter.py | 2 +- 4 files changed, 19 insertions(+), 4 deletions(-) diff --git a/pylint/message/message.py b/pylint/message/message.py index 4efa3f1244..11961d9af9 100644 --- a/pylint/message/message.py +++ b/pylint/message/message.py @@ -77,3 +77,16 @@ def format(self, template: str) -> str: cf. https://docs.python.org/2/library/string.html#formatstrings """ return template.format(**asdict(self)) + + @property + def location(self) -> MessageLocationTuple: + return MessageLocationTuple( + self.abspath, + self.path, + self.module, + self.obj, + self.line, + self.column, + self.end_line, + self.end_column, + ) diff --git a/pylint/reporters/json_reporter.py b/pylint/reporters/json_reporter.py index 59b596f3f9..1d6d6ab86c 100644 --- a/pylint/reporters/json_reporter.py +++ b/pylint/reporters/json_reporter.py @@ -32,7 +32,7 @@ def display_messages(self, layout: Section | None) -> None: @staticmethod def serialize(message: Message) -> dict[str, int | str | None]: - # TODO (3.0) add abs-path and confidence + # TODO: 3.0: Add abs-path and confidence or a new JSONReporter return { "type": message.category, "module": message.module, @@ -54,7 +54,7 @@ def deserialize(message_as_json: dict) -> Message: symbol=message_as_json["symbol"], msg=message_as_json["message"], location=MessageLocationTuple( - # TODO (3.0) abs-path is not available in json export + # TODO: 3.0: Add abs-path and confidence or a new JSONReporter abspath=message_as_json["path"], path=message_as_json["path"], module=message_as_json["module"], @@ -64,7 +64,7 @@ def deserialize(message_as_json: dict) -> Message: end_line=message_as_json["endLine"], end_column=message_as_json["endColumn"], ), - # TODO (3.0) confidence is not available in json export + # TODO: 3.0: Make confidence available or a new JSONReporter confidence=UNDEFINED, ) diff --git a/tests/message/unittest_message.py b/tests/message/unittest_message.py index d0805e337d..edb803daf7 100644 --- a/tests/message/unittest_message.py +++ b/tests/message/unittest_message.py @@ -55,5 +55,7 @@ def build_message( ) e1234 = build_message(e1234_message_definition, e1234_location_values) w1234 = build_message(w1234_message_definition, w1234_location_values) + assert e1234.location == e1234_location_values + assert w1234.location == w1234_location_values assert e1234.format(template) == expected assert w1234.format(template) == "8:11:12: W1234: message (msg-symbol)" diff --git a/tests/reporters/unittest_json_reporter.py b/tests/reporters/unittest_json_reporter.py index fd29d8ea8a..90a67fcebd 100644 --- a/tests/reporters/unittest_json_reporter.py +++ b/tests/reporters/unittest_json_reporter.py @@ -132,6 +132,6 @@ def get_linter_result(score: bool, message: dict[str, Any]) -> list[dict[str, An ], ) def test_serialize_deserialize(message): - # TODO (3.0): Add confidence handling, add path and abs path handling + # TODO: 3.0: Add confidence handling, add path and abs path handling or a new JSONReporter json_message = JSONReporter.serialize(message) assert message == JSONReporter.deserialize(json_message) From 04643706022f76c83a762546167733c6ac5b776f Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Tue, 28 Jun 2022 09:30:01 +0200 Subject: [PATCH 4/4] [JsonReporter] Create a base JsonReporter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Daniƫl van Noord <13665637+DanielNoord@users.noreply.github.com> --- pylint/reporters/json_reporter.py | 68 +++++++++++++++++++++++++------ 1 file changed, 55 insertions(+), 13 deletions(-) diff --git a/pylint/reporters/json_reporter.py b/pylint/reporters/json_reporter.py index 1d6d6ab86c..c47aac5987 100644 --- a/pylint/reporters/json_reporter.py +++ b/pylint/reporters/json_reporter.py @@ -7,19 +7,43 @@ from __future__ import annotations import json -from typing import TYPE_CHECKING +import sys +from typing import TYPE_CHECKING, Optional from pylint.interfaces import UNDEFINED from pylint.message import Message from pylint.reporters.base_reporter import BaseReporter from pylint.typing import MessageLocationTuple +if sys.version_info >= (3, 8): + from typing import TypedDict +else: + from typing_extensions import TypedDict + if TYPE_CHECKING: from pylint.lint.pylinter import PyLinter from pylint.reporters.ureports.nodes import Section +# Since message-id is an invalid name we need to use the alternative syntax +OldJsonExport = TypedDict( + "OldJsonExport", + { + "type": str, + "module": str, + "obj": str, + "line": int, + "column": int, + "endLine": Optional[int], + "endColumn": Optional[int], + "path": str, + "symbol": str, + "message": str, + "message-id": str, + }, +) + -class JSONReporter(BaseReporter): +class BaseJSONReporter(BaseReporter): """Report messages and layouts in JSON.""" name = "json" @@ -30,9 +54,33 @@ def display_messages(self, layout: Section | None) -> None: json_dumpable = [self.serialize(message) for message in self.messages] print(json.dumps(json_dumpable, indent=4), file=self.out) + def display_reports(self, layout: Section) -> None: + """Don't do anything in this reporter.""" + + def _display(self, layout: Section) -> None: + """Do nothing.""" + + @staticmethod + def serialize(message: Message) -> OldJsonExport: + raise NotImplementedError + + @staticmethod + def deserialize(message_as_json: OldJsonExport) -> Message: + raise NotImplementedError + + +class JSONReporter(BaseJSONReporter): + + """ + TODO: 3.0: Remove this JSONReporter in favor of the new one handling abs-path + and confidence. + + TODO: 2.15: Add a new JSONReporter handling abs-path, confidence and scores. + (Ultimately all other breaking change related to json for 3.0). + """ + @staticmethod - def serialize(message: Message) -> dict[str, int | str | None]: - # TODO: 3.0: Add abs-path and confidence or a new JSONReporter + def serialize(message: Message) -> OldJsonExport: return { "type": message.category, "module": message.module, @@ -48,13 +96,13 @@ def serialize(message: Message) -> dict[str, int | str | None]: } @staticmethod - def deserialize(message_as_json: dict) -> Message: + def deserialize(message_as_json: OldJsonExport) -> Message: return Message( msg_id=message_as_json["message-id"], symbol=message_as_json["symbol"], msg=message_as_json["message"], location=MessageLocationTuple( - # TODO: 3.0: Add abs-path and confidence or a new JSONReporter + # TODO: 3.0: Add abs-path and confidence in a new JSONReporter abspath=message_as_json["path"], path=message_as_json["path"], module=message_as_json["module"], @@ -64,16 +112,10 @@ def deserialize(message_as_json: dict) -> Message: end_line=message_as_json["endLine"], end_column=message_as_json["endColumn"], ), - # TODO: 3.0: Make confidence available or a new JSONReporter + # TODO: 3.0: Make confidence available in a new JSONReporter confidence=UNDEFINED, ) - def display_reports(self, layout: Section) -> None: - """Don't do anything in this reporter.""" - - def _display(self, layout: Section) -> None: - """Do nothing.""" - def register(linter: PyLinter) -> None: linter.register_reporter(JSONReporter)