diff --git a/doc/whatsnew/fragments/7214.bugfix b/doc/whatsnew/fragments/7214.bugfix new file mode 100644 index 0000000000..c91805e4bc --- /dev/null +++ b/doc/whatsnew/fragments/7214.bugfix @@ -0,0 +1,3 @@ +Message send to reporter are now copied so a reporter cannot modify the message sent to other reporters. + +Closes #7214 diff --git a/pylint/reporters/multi_reporter.py b/pylint/reporters/multi_reporter.py index 45d3574456..8bf0828c4e 100644 --- a/pylint/reporters/multi_reporter.py +++ b/pylint/reporters/multi_reporter.py @@ -6,6 +6,7 @@ import os from collections.abc import Callable +from copy import copy from typing import TYPE_CHECKING, TextIO from pylint.message import Message @@ -77,7 +78,8 @@ def linter(self, value: PyLinter) -> None: def handle_message(self, msg: Message) -> None: """Handle a new message triggered on the current file.""" for rep in self._sub_reporters: - rep.handle_message(msg) + # We provide a copy so reporters can't modify message for others. + rep.handle_message(copy(msg)) def writeln(self, string: str = "") -> None: """Write a line in the output buffer.""" diff --git a/tests/reporters/unittest_reporting.py b/tests/reporters/unittest_reporting.py index f085466792..6b57972371 100644 --- a/tests/reporters/unittest_reporting.py +++ b/tests/reporters/unittest_reporting.py @@ -18,10 +18,12 @@ from _pytest.recwarn import WarningsRecorder from pylint import checkers +from pylint.interfaces import HIGH from pylint.lint import PyLinter +from pylint.message.message import Message from pylint.reporters import BaseReporter, MultiReporter from pylint.reporters.text import ParseableTextReporter, TextReporter -from pylint.typing import FileItem +from pylint.typing import FileItem, MessageLocationTuple if TYPE_CHECKING: from pylint.reporters.ureports.nodes import Section @@ -334,6 +336,50 @@ def test_multi_format_output(tmp_path: Path) -> None: ) +def test_multi_reporter_independant_messages() -> None: + """Messages should not be modified by multiple reporters""" + + check_message = "Not modified" + + class ReporterModify(BaseReporter): + def handle_message(self, msg: Message) -> None: + msg.msg = "Modified message" + + def writeln(self, string: str = "") -> None: + pass + + def _display(self, layout: Section) -> None: + pass + + class ReporterCheck(BaseReporter): + def handle_message(self, msg: Message) -> None: + assert ( + msg.msg == check_message + ), "Message object should not be changed by other reporters." + + def writeln(self, string: str = "") -> None: + pass + + def _display(self, layout: Section) -> None: + pass + + multi_reporter = MultiReporter([ReporterModify(), ReporterCheck()], lambda: None) + + message = Message( + symbol="missing-docstring", + msg_id="C0123", + location=MessageLocationTuple("abspath", "path", "module", "obj", 1, 2, 1, 3), + msg=check_message, + confidence=HIGH, + ) + + multi_reporter.handle_message(message) + + assert ( + message.msg == check_message + ), "Message object should not be changed by reporters." + + def test_display_results_is_renamed() -> None: class CustomReporter(TextReporter): def _display(self, layout: Section) -> None: