From 7c24b62308db5232fba525fcce399daf00a30dba Mon Sep 17 00:00:00 2001 From: Ashley Whetter Date: Tue, 29 Oct 2019 16:00:32 -0700 Subject: [PATCH] Support `# noqa: ...` pragmas Closes #2493 --- ChangeLog | 5 +++ doc/user_guide/message-control.rst | 14 ++++++++ doc/whatsnew/2.5.rst | 6 ++++ pylint/constants.py | 1 + pylint/lint.py | 37 ++++++++++++++++---- tests/functional/n/noqa_pragma.py | 13 +++++++ tests/functional/p/pragma_after_backslash.py | 6 ++++ 7 files changed, 75 insertions(+), 7 deletions(-) create mode 100644 tests/functional/n/noqa_pragma.py diff --git a/ChangeLog b/ChangeLog index f99b8b859e..843b46b648 100644 --- a/ChangeLog +++ b/ChangeLog @@ -33,6 +33,11 @@ Release date: TBA * Allow parallel linting when run under Prospector +* ``# noqa: ...` pragmas are now supported as an alternative to + ``# pylint: disable=...``. + + Closes #2493 + What's New in Pylint 2.4.3? =========================== diff --git a/doc/user_guide/message-control.rst b/doc/user_guide/message-control.rst index 82a8ec804b..8d93d9fdb1 100644 --- a/doc/user_guide/message-control.rst +++ b/doc/user_guide/message-control.rst @@ -1,3 +1,5 @@ +.. _messages-control: + Messages control ================ @@ -25,6 +27,18 @@ For all of these controls, ``pylint`` accepts the following values: * All the checks with ``all`` +.. note:: + + ``pylint`` also supports using the ``# noqa: ...`` pragma + as an alternative to ``# pylint: disable=...``. + Note that other linters may not be able to parse pragmas that use + symbolic messages. + Therefore is it recommended to use numerical IDs inside ``# noqa`` pragmas + when using ``pylint`` with another linter. + Disabling ``use-symbolic-message-instead`` may be needed in this case. + + Bare ``# noqa`` pragmas without a list of messages to disable is not supported. + Block disables -------------- diff --git a/doc/whatsnew/2.5.rst b/doc/whatsnew/2.5.rst index afd2dda873..1fa09f161c 100644 --- a/doc/whatsnew/2.5.rst +++ b/doc/whatsnew/2.5.rst @@ -32,3 +32,9 @@ Other Changes These files can also be passed in on the command line. * Mutable ``collections.*`` are now flagged as dangerous defaults. + +* ``# noqa: ...` pragmas are now supported as an alternative to + ``# pylint: disable=...``. + See :ref:`messages-control` for detailed usage. + + Closes #2493 diff --git a/pylint/constants.py b/pylint/constants.py index 852fc15108..63efaa6b9a 100644 --- a/pylint/constants.py +++ b/pylint/constants.py @@ -9,6 +9,7 @@ # so that an option can be continued with the reasons # why it is active or disabled. OPTION_RGX = re.compile(r"\s*#.*\bpylint:\s*([^;#]+)[;#]{0,1}") +NOQA_RGX = re.compile(r"# noqa:[\s]?([^;#]+)[;#]{0,1}", re.IGNORECASE) PY_EXTS = (".py", ".pyc", ".pyo", ".pyw", ".so", ".dll") diff --git a/pylint/lint.py b/pylint/lint.py index 1556bfb259..14e69ada0a 100644 --- a/pylint/lint.py +++ b/pylint/lint.py @@ -68,6 +68,7 @@ import traceback import warnings from io import TextIOWrapper +from typing import List import astroid from astroid import modutils @@ -76,7 +77,7 @@ from pylint import __pkginfo__, checkers, config, exceptions, interfaces, reporters from pylint.__pkginfo__ import version -from pylint.constants import MAIN_CHECKER_NAME, MSG_TYPES, OPTION_RGX +from pylint.constants import MAIN_CHECKER_NAME, MSG_TYPES, NOQA_RGX, OPTION_RGX from pylint.message import Message, MessageDefinitionStore, MessagesHandlerMixIn from pylint.reporters.ureports import nodes as report_nodes from pylint.utils import ASTWalker, FileState, utils @@ -806,6 +807,14 @@ def process_tokens(self, tokens): if tok_type != tokenize.COMMENT: continue + + # If we did not see a newline between the previous line and now, + # we saw a backslash so treat the two lines as one. + line_nos = [start[0]] + if not saw_newline: + line_nos.append(start[0] - 1) + self._process_noqa(content, line_nos) + match = OPTION_RGX.search(content) if match is None: continue @@ -846,7 +855,7 @@ def process_tokens(self, tokens): for msgid in utils._splitstrip(value): # Add the line where a control pragma was encountered. if opt in control_pragmas: - self._pragma_lineno[msgid] = start[0] + self._pragma_lineno[msgid] = line_nos[-1] try: if (opt, msgid) == ("disable", "all"): @@ -858,16 +867,30 @@ def process_tokens(self, tokens): self.add_message("file-ignored", line=start[0]) self._ignore_file = True return - # If we did not see a newline between the previous line and now, - # we saw a backslash so treat the two lines as one. - if not saw_newline: - meth(msgid, "module", start[0] - 1) - meth(msgid, "module", start[0]) + + for line_no in line_nos: + meth(msgid, "module", line_no) except exceptions.UnknownMessageError: self.add_message("bad-option-value", args=msgid, line=start[0]) else: self.add_message("unrecognized-inline-option", args=opt, line=start[0]) + def _process_noqa(self, content: str, line_nos: List[int]) -> None: + """Find and process any supported noqa pragmas. + + :param content: The comment to parse (including the leading "#"). + :param line_nos: The line numbers of the lines that the pragma affects. + """ + match = NOQA_RGX.search(content) + if not match: + return + + for msgid in utils._splitstrip(match.group(1)): + for line_no in line_nos: + self._pragma_lineno[msgid] = line_no + # Ignore unknown messages because this message may not be ours + self.disable(msgid, "module", line_no, ignore_unknown=True) + # code checking methods ################################################### def get_checkers(self): diff --git a/tests/functional/n/noqa_pragma.py b/tests/functional/n/noqa_pragma.py new file mode 100644 index 0000000000..f680bff6c6 --- /dev/null +++ b/tests/functional/n/noqa_pragma.py @@ -0,0 +1,13 @@ +"""Test that a noqa pragma has an effect.""" +# pylint: disable=too-few-public-methods + +class Foo: + """block-disable test""" + + def meth3(self): + """test one line disabling""" + print(self.bla) # noqa: E1101 + + print(self.bla) # noqa: no-member + + self.thing = self.bla # noqa: E1101, attribute-defined-outside-init diff --git a/tests/functional/p/pragma_after_backslash.py b/tests/functional/p/pragma_after_backslash.py index c506f6c9da..fc06611b17 100644 --- a/tests/functional/p/pragma_after_backslash.py +++ b/tests/functional/p/pragma_after_backslash.py @@ -8,3 +8,9 @@ def meth3(self): """test one line disabling""" print(self.bla) \ # pylint: disable=E1101 + + print(self.bla \ + + self.bla) # pylint: disable=E1101 + + print(self.bla) \ + # noqa: E1101