From 7c3533ca48e69394391945de1563ef7f639cd27d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Wed, 24 Nov 2021 14:35:27 +0100 Subject: Update reporters to (allow) use of ``end_line`` and ``end_column`` (#5372) * Update reporters to (allow) use end_line and end_column Co-authored-by: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Co-authored-by: Pierre Sassoulas --- ChangeLog | 5 ++++ doc/user_guide/output.rst | 7 +++++ doc/whatsnew/2.12.rst | 5 ++++ pylint/reporters/text.py | 30 +++++++++++++++++++-- tests/unittest_reporting.py | 66 +++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 111 insertions(+), 2 deletions(-) diff --git a/ChangeLog b/ChangeLog index b3c5fbcf7..9a20365af 100644 --- a/ChangeLog +++ b/ChangeLog @@ -15,6 +15,11 @@ Release date: TBA Closes #4982 +* Add ability to add ``end_line`` and ``end_column`` to the ``--msg-template`` option. + With the standard ``TextReporter`` this will add the line and column number of the + end of a node to the output of Pylint. If these numbers are unknown, they are represented + by an empty string. + * Introduced primer tests and a configuration tests framework. The helper classes available in ``pylint/testutil/`` are still unstable and might be modified in the near future. diff --git a/doc/user_guide/output.rst b/doc/user_guide/output.rst index 52959d3c7..1aa18f3b3 100644 --- a/doc/user_guide/output.rst +++ b/doc/user_guide/output.rst @@ -58,6 +58,10 @@ line line number column column number +end_line + line number of the end of the node +end_column + column number of the end of the node module module name obj @@ -94,6 +98,9 @@ A few other examples: The ``--msg-template`` option can only be combined with text-based reporters (``--output-format`` either unspecified or one of: parseable, colorized or msvs). If both ``--output-format`` and ``--msg-template`` are specified, the ``--msg-template`` option will take precedence over the default line format defined by the reporter class. +If ``end_line`` or ``end_column`` are ``None``, they will be represented as an empty string +by the default ``TextReporter``. + .. _Python new format syntax: https://docs.python.org/2/library/string.html#formatstrings Source code analysis section diff --git a/doc/whatsnew/2.12.rst b/doc/whatsnew/2.12.rst index dae98d485..473a3ba02 100644 --- a/doc/whatsnew/2.12.rst +++ b/doc/whatsnew/2.12.rst @@ -205,6 +205,11 @@ Other Changes Partially closes #5321 +* Add ability to add ``end_line`` and ``end_column`` to the ``--msg-template`` option. + With the standard ``TextReporter`` this will add the line and column number of the + end of a node to the output of Pylint. If these numbers are unknown, they are represented + by an empty string. + * Introduced primer tests and a configuration tests framework. The helper classes available in ``pylint/testutil/`` are still unstable and might be modified in the near future. diff --git a/pylint/reporters/text.py b/pylint/reporters/text.py index d4e7cf70c..b8de25dc0 100644 --- a/pylint/reporters/text.py +++ b/pylint/reporters/text.py @@ -24,6 +24,7 @@ :colorized: an ANSI colorized text reporter """ import os +import re import sys import warnings from typing import ( @@ -183,13 +184,38 @@ class TextReporter(BaseReporter): super().__init__(output) self._modules: Set[str] = set() self._template = self.line_format + self._fixed_template = self.line_format + """The output format template with any unrecognized arguments removed""" def on_set_current_module(self, module: str, filepath: Optional[str]) -> None: - self._template = str(self.linter.config.msg_template or self._template) + """Set the format template to be used and check for unrecognized arguments.""" + template = str(self.linter.config.msg_template or self._template) + + # Return early if the template is the same as the previous one + if template == self._template: + return + + # Set template to the currently selected template + self._template = template + + # Check to see if all parameters in the template are attributes of the Message + arguments = re.findall(r"\{(.+?)(:.*)?\}", template) + for argument in arguments: + if argument[0] not in Message._fields: + warnings.warn( + f"Don't recognize the argument '{argument[0]}' in the --msg-template. " + "Are you sure it is supported on the current version of pylint?" + ) + template = re.sub(r"\{" + argument[0] + r"(:.*?)?\}", "", template) + self._fixed_template = template def write_message(self, msg: Message) -> None: """Convenience method to write a formatted message with class default template""" - self.writeln(msg.format(self._template)) + self_dict = msg._asdict() + for key in ("end_line", "end_column"): + self_dict[key] = self_dict[key] or "" + + self.writeln(self._fixed_template.format(**self_dict)) def handle_message(self, msg: Message) -> None: """manage message of different type and in the context of path""" diff --git a/tests/unittest_reporting.py b/tests/unittest_reporting.py index 22fdc905c..3e6fdafd1 100644 --- a/tests/unittest_reporting.py +++ b/tests/unittest_reporting.py @@ -57,6 +57,72 @@ def test_template_option(linter): assert output.getvalue() == "************* Module 0123\nC0301:001\nC0301:002\n" +def test_template_option_default(linter) -> None: + """Test the default msg-template setting""" + output = StringIO() + linter.reporter.out = output + linter.open() + linter.set_current_module("my_module") + linter.add_message("C0301", line=1, args=(1, 2)) + linter.add_message("line-too-long", line=2, args=(3, 4)) + + out_lines = output.getvalue().split("\n") + assert out_lines[1] == "my_module:1:0: C0301: Line too long (1/2) (line-too-long)" + assert out_lines[2] == "my_module:2:0: C0301: Line too long (3/4) (line-too-long)" + + +def test_template_option_end_line(linter) -> None: + """Test the msg-template option with end_line and end_column""" + output = StringIO() + linter.reporter.out = output + linter.set_option( + "msg-template", + "{path}:{line}:{column}:{end_line}:{end_column}: {msg_id}: {msg} ({symbol})", + ) + linter.open() + linter.set_current_module("my_mod") + linter.add_message("C0301", line=1, args=(1, 2)) + linter.add_message( + "line-too-long", line=2, end_lineno=2, end_col_offset=4, args=(3, 4) + ) + + out_lines = output.getvalue().split("\n") + assert out_lines[1] == "my_mod:1:0::: C0301: Line too long (1/2) (line-too-long)" + assert out_lines[2] == "my_mod:2:0:2:4: C0301: Line too long (3/4) (line-too-long)" + + +def test_template_option_non_exisiting(linter) -> None: + """Test the msg-template option with a non exisiting options. + This makes sure that this option remains backwards compatible as new + parameters do not break on previous versions""" + output = StringIO() + linter.reporter.out = output + linter.set_option( + "msg-template", + "{path}:{line}:{a_new_option}:({a_second_new_option:03d})", + ) + linter.open() + with pytest.warns(UserWarning) as records: + linter.set_current_module("my_mod") + assert len(records) == 2 + assert ( + "Don't recognize the argument 'a_new_option'" in records[0].message.args[0] + ) + assert ( + "Don't recognize the argument 'a_second_new_option'" + in records[1].message.args[0] + ) + + linter.add_message("C0301", line=1, args=(1, 2)) + linter.add_message( + "line-too-long", line=2, end_lineno=2, end_col_offset=4, args=(3, 4) + ) + + out_lines = output.getvalue().split("\n") + assert out_lines[1] == "my_mod:1::()" + assert out_lines[2] == "my_mod:2::()" + + def test_deprecation_set_output(recwarn): """TODO remove in 3.0""" reporter = BaseReporter() -- cgit v1.2.1