diff options
author | hippo91 <guillaume.peillex@gmail.com> | 2019-11-19 09:16:54 +0100 |
---|---|---|
committer | Claudiu Popa <pcmanticore@gmail.com> | 2019-11-19 09:16:54 +0100 |
commit | 9bdae8b82fcc5b0592135cbf6bead7df360a6672 (patch) | |
tree | 823e86f1aa28d792d03bd963b52d1b9872d4ed9d | |
parent | 0ed3782d7a933b822d2ecf189fc24da0aaefbd6e (diff) | |
download | pylint-git-9bdae8b82fcc5b0592135cbf6bead7df360a6672.tar.gz |
Add support for disabling line-too-long for multilines strings
This commit adds support for disabling `line-too-long` messages
for multilines strings such as docstrings.
When a pylint disable pragma is present at the end of the docstring, it is taken
in account for the entire docstring.
Close #2957
-rw-r--r-- | ChangeLog | 5 | ||||
-rw-r--r-- | doc/whatsnew/2.4.rst | 2 | ||||
-rw-r--r-- | doc/whatsnew/2.5.rst | 13 | ||||
-rw-r--r-- | pylint/checkers/format.py | 134 | ||||
-rw-r--r-- | pylint/checkers/misc.py | 18 | ||||
-rw-r--r-- | pylint/lint.py | 103 | ||||
-rw-r--r-- | pylint/utils/pragma_parser.py | 134 | ||||
-rw-r--r-- | tests/functional/b/bad_inline_option.py | 4 | ||||
-rw-r--r-- | tests/functional/b/bad_inline_option.txt | 2 | ||||
-rw-r--r-- | tests/functional/l/line_too_long.py | 32 | ||||
-rw-r--r-- | tests/functional/l/line_too_long.txt | 5 | ||||
-rw-r--r-- | tests/test_pragma_parser.py | 94 |
12 files changed, 443 insertions, 103 deletions
@@ -7,6 +7,11 @@ What's New in Pylint 2.5.0? Release date: TBA +* Don't emit ``line-too-long`` for multilines when a + `pylint:disable=line-too-long` comment stands at their end + + Close #2957 + * Do not exempt bare except from ``undefined-variable`` and similar checks If a node was wrapped in a ``TryExcept``, ``pylint`` was taking a hint diff --git a/doc/whatsnew/2.4.rst b/doc/whatsnew/2.4.rst index 961a0b2d0..0dc289785 100644 --- a/doc/whatsnew/2.4.rst +++ b/doc/whatsnew/2.4.rst @@ -130,7 +130,7 @@ New checkers Other Changes ============= -* Don't emit ``protected-access`` when a single underscore prefixed attribute is used +* Don't emit ``protected-access`` when a single underscore prefixed attribute is used inside a special method Close #1802 diff --git a/doc/whatsnew/2.5.rst b/doc/whatsnew/2.5.rst index 615ae05f8..f73d3c2b6 100644 --- a/doc/whatsnew/2.5.rst +++ b/doc/whatsnew/2.5.rst @@ -23,6 +23,19 @@ New checkers Other Changes ============= +* Don't emit ``line-too-long`` for multilines when a + `pylint:disable=line-too-long` comment stands at their end. + + For example the following code will not trigger any ``line-too-long`` message:: + + def example(): + """ + This is a very very very long line within a docstring that should trigger a pylint C0301 error line-too-long + + Even spread on multiple lines, the disable command is still effective on very very very, maybe too much long docstring + """#pylint: disable=line-too-long + pass + * Configuration can be read from a setup.cfg or pyproject.toml file in the current directory. A setup.cfg must prepend pylintrc section names with ``pylint.``, diff --git a/pylint/checkers/format.py b/pylint/checkers/format.py index e297d3886..0463bcc91 100644 --- a/pylint/checkers/format.py +++ b/pylint/checkers/format.py @@ -46,6 +46,7 @@ Some parts of the process_token method is based from The Tab Nanny std module. import keyword import tokenize from functools import reduce # pylint: disable=redefined-builtin +from typing import List from astroid import nodes @@ -56,8 +57,9 @@ from pylint.checkers.utils import ( is_protocol_class, node_frame_class, ) -from pylint.constants import OPTION_RGX, WarningScope +from pylint.constants import WarningScope from pylint.interfaces import IAstroidChecker, IRawChecker, ITokenChecker +from pylint.utils.pragma_parser import OPTION_PO, PragmaParserError, parse_pragma _ASYNC_TOKEN = "async" _CONTINUATION_BLOCK_OPENERS = [ @@ -1243,41 +1245,64 @@ class FormatChecker(BaseTokenChecker): self.add_message("multiple-statements", node=node) self._visited_lines[line] = 2 - def check_lines(self, lines, i): - """check lines have less than a maximum number of characters + def check_line_ending(self, line: str, i: int) -> None: + """ + Check that the final newline is not missing and that there is no trailing whitespace. + """ + if not line.endswith("\n"): + self.add_message("missing-final-newline", line=i) + else: + # exclude \f (formfeed) from the rstrip + stripped_line = line.rstrip("\t\n\r\v ") + if not stripped_line and _EMPTY_LINE in self.config.no_space_check: + # allow empty lines + pass + elif line[len(stripped_line) :] not in ("\n", "\r\n"): + self.add_message( + "trailing-whitespace", line=i, col_offset=len(stripped_line) + ) + + def check_line_length(self, line: str, i: int) -> None: + """ + Check that the line length is less than the authorized value """ max_chars = self.config.max_line_length ignore_long_line = self.config.ignore_long_lines + line = line.rstrip() + if len(line) > max_chars and not ignore_long_line.search(line): + self.add_message("line-too-long", line=i, args=(len(line), max_chars)) - def check_line(line, i): - if not line.endswith("\n"): - self.add_message("missing-final-newline", line=i) - else: - # exclude \f (formfeed) from the rstrip - stripped_line = line.rstrip("\t\n\r\v ") - if not stripped_line and _EMPTY_LINE in self.config.no_space_check: - # allow empty lines - pass - elif line[len(stripped_line) :] not in ("\n", "\r\n"): - self.add_message( - "trailing-whitespace", line=i, col_offset=len(stripped_line) - ) - # Don't count excess whitespace in the line length. - line = stripped_line - mobj = OPTION_RGX.search(line) - if mobj and "=" in line: - front_of_equal, _, back_of_equal = mobj.group(1).partition("=") - if front_of_equal.strip() == "disable": - if "line-too-long" in { - _msg_id.strip() for _msg_id in back_of_equal.split(",") - }: - return None - line = line.rsplit("#", 1)[0].rstrip() - - if len(line) > max_chars and not ignore_long_line.search(line): - self.add_message("line-too-long", line=i, args=(len(line), max_chars)) - return i + 1 + @staticmethod + def remove_pylint_option_from_lines(options_pattern_obj) -> str: + """ + Remove the `# pylint ...` pattern from lines + """ + lines = options_pattern_obj.string + purged_lines = ( + lines[: options_pattern_obj.start(1)].rstrip() + + lines[options_pattern_obj.end(1) :] + ) + return purged_lines + @staticmethod + def is_line_length_check_activated(pylint_pattern_match_object) -> bool: + """ + Return true if the line length check is activated + """ + try: + for pragma in parse_pragma(pylint_pattern_match_object.group(2)): + if pragma.action == "disable" and "line-too-long" in pragma.messages: + return False + except PragmaParserError: + # Printing usefull informations dealing with this error is done in lint.py + pass + return True + + @staticmethod + def specific_splitlines(lines: str) -> List[str]: + """ + Split lines according to universal newlines except those in a specific sets + """ unsplit_ends = { "\v", "\x0b", @@ -1290,23 +1315,38 @@ class FormatChecker(BaseTokenChecker): "\u2028", "\u2029", } - unsplit = [] - for line in lines.splitlines(True): - if line[-1] in unsplit_ends: - unsplit.append(line) - continue - - if unsplit: - unsplit.append(line) - line = "".join(unsplit) - unsplit = [] - - i = check_line(line, i) - if i is None: - break + res = [] + buffer = "" + for atomic_line in lines.splitlines(True): + if atomic_line[-1] not in unsplit_ends: + res.append(buffer + atomic_line) + buffer = "" + else: + buffer += atomic_line + return res - if unsplit: - check_line("".join(unsplit), i) + def check_lines(self, lines: str, lineno: int) -> None: + """ + Check lines have : + - a final newline + - no trailing whitespace + - less than a maximum number of characters + """ + # By default, check the line length + check_l_length = True + + # Line length check may be deactivated through `pylint: disable` comment + mobj = OPTION_PO.search(lines) + if mobj: + check_l_length = self.is_line_length_check_activated(mobj) + # The 'pylint: disable whatever' should not be taken into account for line length count + lines = self.remove_pylint_option_from_lines(mobj) + + for line in self.specific_splitlines(lines): + if check_l_length: + self.check_line_length(line, lineno) + self.check_line_ending(line, lineno) + lineno += 1 def check_indent_level(self, string, expected, line_num): """return the indent level of the string diff --git a/pylint/checkers/misc.py b/pylint/checkers/misc.py index dcf7a3e15..18e532514 100644 --- a/pylint/checkers/misc.py +++ b/pylint/checkers/misc.py @@ -22,9 +22,9 @@ import re import tokenize from pylint.checkers import BaseChecker -from pylint.constants import OPTION_RGX from pylint.interfaces import IRawChecker, ITokenChecker from pylint.message import MessagesHandlerMixIn +from pylint.utils.pragma_parser import OPTION_PO, PragmaParserError, parse_pragma class ByIdManagedMessagesChecker(BaseChecker): @@ -138,11 +138,21 @@ class EncodingChecker(BaseChecker): comment_text = comment.string[1:].lstrip() # trim '#' and whitespaces # handle pylint disable clauses - disable_option_match = OPTION_RGX.search(comment_text) + disable_option_match = OPTION_PO.search(comment_text) if disable_option_match: try: - _, value = disable_option_match.group(1).split("=", 1) - values = [_val.strip().upper() for _val in value.split(",")] + values = [] + try: + for pragma_repr in ( + p_rep + for p_rep in parse_pragma(disable_option_match.group(2)) + if p_rep.action == "disable" + ): + values.extend(pragma_repr.messages) + except PragmaParserError: + # Printing usefull informations dealing with this error is done in lint.py + pass + values = [_val.upper() for _val in values] if set(values) & set(self.config.notes): continue except ValueError: diff --git a/pylint/lint.py b/pylint/lint.py index e490f55e2..5a7b3c052 100644 --- a/pylint/lint.py +++ b/pylint/lint.py @@ -76,10 +76,16 @@ from astroid.builder import AstroidBuilder 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 from pylint.message import Message, MessageDefinitionStore, MessagesHandlerMixIn from pylint.reporters.ureports import nodes as report_nodes from pylint.utils import ASTWalker, FileState, utils +from pylint.utils.pragma_parser import ( + OPTION_PO, + InvalidPragmaError, + UnRecognizedOptionError, + parse_pragma, +) try: import multiprocessing @@ -803,50 +809,41 @@ class PyLinter( if tok_type != tokenize.COMMENT: continue - match = OPTION_RGX.search(content) + match = OPTION_PO.search(content) if match is None: continue - first_group = match.group(1) - if ( - first_group.strip() == "disable-all" - or first_group.strip() == "skip-file" - ): - if first_group.strip() == "disable-all": - self.add_message( - "deprecated-pragma", - line=start[0], - args=("disable-all", "skip-file"), - ) - self.add_message("file-ignored", line=start[0]) - self._ignore_file = True - return try: - opt, value = first_group.split("=", 1) - except ValueError: - self.add_message( - "bad-inline-option", args=first_group.strip(), line=start[0] - ) - continue - opt = opt.strip() - if opt in self._options_methods or opt in self._bw_options_methods: - try: - meth = self._options_methods[opt] - except KeyError: - meth = self._bw_options_methods[opt] - # found a "(dis|en)able-msg" pragma deprecated suppression - self.add_message( - "deprecated-pragma", - line=start[0], - args=(opt, opt.replace("-msg", "")), - ) - 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] - + for pragma_repr in parse_pragma(match.group(2)): + if pragma_repr.action in ("disable-all", "skip-file"): + if pragma_repr.action == "disable-all": + self.add_message( + "deprecated-pragma", + line=start[0], + args=("disable-all", "skip-file"), + ) + self.add_message("file-ignored", line=start[0]) + self._ignore_file = True + return try: - if (opt, msgid) == ("disable", "all"): + meth = self._options_methods[pragma_repr.action] + except KeyError: + meth = self._bw_options_methods[pragma_repr.action] + # found a "(dis|en)able-msg" pragma deprecated suppression + self.add_message( + "deprecated-pragma", + line=start[0], + args=( + pragma_repr.action, + pragma_repr.action.replace("-msg", ""), + ), + ) + for msgid in pragma_repr.messages: + # Add the line where a control pragma was encountered. + if pragma_repr.action in control_pragmas: + self._pragma_lineno[msgid] = start[0] + + if (pragma_repr.action, msgid) == ("disable", "all"): self.add_message( "deprecated-pragma", line=start[0], @@ -855,15 +852,25 @@ class PyLinter( 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 we did not see a newline between the previous line and now, + # we saw a backslash so treat the two lines as one. + l_start = start[0] if not saw_newline: - meth(msgid, "module", start[0] - 1) - meth(msgid, "module", start[0]) - 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]) + l_start -= 1 + try: + meth(msgid, "module", l_start) + except exceptions.UnknownMessageError: + self.add_message( + "bad-option-value", args=msgid, line=start[0] + ) + except UnRecognizedOptionError as err: + self.add_message( + "unrecognized-inline-option", args=err.token, line=start[0] + ) + continue + except InvalidPragmaError as err: + self.add_message("bad-inline-option", args=err.token, line=start[0]) + continue # code checking methods ################################################### diff --git a/pylint/utils/pragma_parser.py b/pylint/utils/pragma_parser.py new file mode 100644 index 000000000..6ceefe031 --- /dev/null +++ b/pylint/utils/pragma_parser.py @@ -0,0 +1,134 @@ +# -*- coding: utf-8 -*- + +# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html +# For details: https://github.com/PyCQA/pylint/blob/master/COPYING + +import re +from collections import namedtuple +from typing import Generator, List + +# Allow stopping after the first semicolon/hash encountered, +# so that an option can be continued with the reasons +# why it is active or disabled. +OPTION_RGX = r""" + \s* # Any number of whithespace + \#? # One or zero hash + .* # Anything (as much as possible) + (\s* # Beginning of first matched group and any number of whitespaces + \# # Beginning of comment + .*? # Anything (as little as possible) + \bpylint: # pylint word and column + \s* # Any number of whitespaces + ([^;#\n]+)) # Anything except semicolon or hash or newline (it is the second matched group) + # and end of the first matched group + [;#]{0,1}""" # From 0 to 1 repetition of semicolon or hash +OPTION_PO = re.compile(OPTION_RGX, re.VERBOSE) + + +PragmaRepresenter = namedtuple("PragmaRepresenter", "action messages") + + +ATOMIC_KEYWORDS = frozenset(("disable-all", "skip-file")) +MESSAGE_KEYWORDS = frozenset(("disable-msg", "enable-msg", "disable", "enable")) +# sorted is necessary because sets are unordered collections and ALL_KEYWORDS +# string should not vary between executions +# reverse is necessary in order to have the longest keywords first, so that, for example, +# 'disable' string should not be matched instead of 'disable-all' +ALL_KEYWORDS = "|".join( + sorted(ATOMIC_KEYWORDS | MESSAGE_KEYWORDS, key=len, reverse=True) +) + + +TOKEN_SPECIFICATION = [ + ("KEYWORD", r"\b({:s})\b".format(ALL_KEYWORDS)), + ("MESSAGE_STRING", r"[A-Za-z\-]{2,}"), # Identifiers + ("ASSIGN", r"="), # Assignment operator + ("MESSAGE_NUMBER", r"[CREIWF]{1}\d*"), +] + +TOK_REGEX = "|".join( + "(?P<{:s}>{:s})".format(token_name, token_rgx) + for token_name, token_rgx in TOKEN_SPECIFICATION +) + + +def emit_pragma_representer(action, messages): + if not messages and action in MESSAGE_KEYWORDS: + raise InvalidPragmaError( + "The keyword is not followed by message identifier", action + ) + return PragmaRepresenter(action, messages) + + +class PragmaParserError(Exception): + """ + A class for exceptions thrown by pragma_parser module + """ + + def __init__(self, message, token): + """ + :args message: explain the reason why the exception has been thrown + :args token: token concerned by the exception + """ + self.message = message + self.token = token + super(PragmaParserError, self).__init__(self.message) + + +class UnRecognizedOptionError(PragmaParserError): + """ + Thrown in case the of a valid but unrecognized option + """ + + +class InvalidPragmaError(PragmaParserError): + """ + Thrown in case the pragma is invalid + """ + + +def parse_pragma(pylint_pragma: str) -> Generator[PragmaRepresenter, None, None]: + action = None + messages = [] # type: List[str] + assignment_required = False + previous_token = "" + + for mo in re.finditer(TOK_REGEX, pylint_pragma): + kind = mo.lastgroup + value = mo.group() + + if kind == "ASSIGN": + if not assignment_required: + if action: + # A keyword has been found previously but doesn't support assignement + raise UnRecognizedOptionError( + "The keyword doesn't support assignment", action + ) + if previous_token: + # Something found previously but not a known keyword + raise UnRecognizedOptionError( + "The keyword is unknown", previous_token + ) + # Nothing at all detected before this assignment + raise InvalidPragmaError("Missing keyword before assignment", "") + assignment_required = False + elif assignment_required: + raise InvalidPragmaError("The = sign is missing after the keyword", action) + elif kind == "KEYWORD": + if action: + yield emit_pragma_representer(action, messages) + action = value + messages = list() + assignment_required = action in MESSAGE_KEYWORDS + elif kind in ("MESSAGE_STRING", "MESSAGE_NUMBER"): + messages.append(value) + assignment_required = False + else: + raise RuntimeError("Token not recognized") + + previous_token = value + + if action: + yield emit_pragma_representer(action, messages) + else: + raise UnRecognizedOptionError("The keyword is unknown", previous_token) diff --git a/tests/functional/b/bad_inline_option.py b/tests/functional/b/bad_inline_option.py index be6e24081..ac6fc6642 100644 --- a/tests/functional/b/bad_inline_option.py +++ b/tests/functional/b/bad_inline_option.py @@ -1,5 +1,5 @@ """errors-only is not usable as an inline option""" # +1: [bad-inline-option] -# pylint: errors-only +# pylint: disable missing-docstring -CONST = "This is not a pylint: inline option." +CONST = "The assignment operator is missing inside the pylint inline option." diff --git a/tests/functional/b/bad_inline_option.txt b/tests/functional/b/bad_inline_option.txt index 91ac1af35..ea4758f94 100644 --- a/tests/functional/b/bad_inline_option.txt +++ b/tests/functional/b/bad_inline_option.txt @@ -1 +1 @@ -bad-inline-option:3::Unable to consider inline option 'errors-only' +bad-inline-option:3::Unable to consider inline option 'disable' diff --git a/tests/functional/l/line_too_long.py b/tests/functional/l/line_too_long.py index 60310b5a0..4481e842a 100644 --- a/tests/functional/l/line_too_long.py +++ b/tests/functional/l/line_too_long.py @@ -43,3 +43,35 @@ def func_with_long(parameter): aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbcccccccccccccccccccccccccccccccccccccccccccccccccccc
"""
return parameter
+
+
+# No line-too-long message should be emitted as the disable comment stands for all the multiline
+def issue_2957():
+ """
+ This is a very very very long line within a docstring that should trigger a pylint C0301 error line-too-long
+
+ Even spread on multiple lines, the disable command is still effective on very very very, maybe too much long docstring
+ """#pylint: disable=line-too-long
+ return True
+
+
+def issue_2957_bis():
+ # +3: [line-too-long]
+ # +4: [line-too-long]
+ """
+ This is a very very very long line within a docstring that should trigger a pylint C0301 error line-too-long
+
+ Even spread on multiple lines, the disable command is still effective on very very very, maybe too much long docstring
+ """
+ return True
+
+
+# +2: [line-too-long]
+# +2: [line-too-long]
+VAR = """A very very long, maybe too much, constant. Just here to trigger message emission and check everything is fine and nice"""
+"""But it is however too long, isn't it? I don't know what to say more here. I got a lack of imagination, just listening to music..."""
+
+
+# +1: [line-too-long]
+VAR_BIS = """A very very long, maybe too much, constant. Just here to trigger message emission and check everything is fine and nice"""
+"""But it is however too long, isn't it? I don't know what to say more here. I got a lack of imagination, just listening to music..."""#pylint: disable=line-too-long
diff --git a/tests/functional/l/line_too_long.txt b/tests/functional/l/line_too_long.txt index 22c218117..8ce703e80 100644 --- a/tests/functional/l/line_too_long.txt +++ b/tests/functional/l/line_too_long.txt @@ -4,3 +4,8 @@ line-too-long:18::Line too long (102/100) line-too-long:24::Line too long (109/100) line-too-long:27::Line too long (115/100) line-too-long:34::Line too long (105/100) +line-too-long:62::Line too long (112/100) +line-too-long:64::Line too long (122/100) +line-too-long:71::Line too long (131/100) +line-too-long:72::Line too long (135/100) +line-too-long:76::Line too long (135/100) diff --git a/tests/test_pragma_parser.py b/tests/test_pragma_parser.py new file mode 100644 index 000000000..48a435534 --- /dev/null +++ b/tests/test_pragma_parser.py @@ -0,0 +1,94 @@ +import pytest + +from pylint.utils.pragma_parser import ( + OPTION_PO, + InvalidPragmaError, + UnRecognizedOptionError, + parse_pragma, +) + + +def test_simple_pragma(): + comment = "#pylint: disable = missing-docstring" + match = OPTION_PO.search(comment) + for pragma_repr in parse_pragma(match.group(2)): + assert pragma_repr.action == "disable" + assert pragma_repr.messages == ["missing-docstring"] + + +def test_simple_pragma_no_messages(): + comment = "#pylint: skip-file" + match = OPTION_PO.search(comment) + for pragma_repr in parse_pragma(match.group(2)): + assert pragma_repr.action == "skip-file" + assert pragma_repr.messages == [] + + +def test_simple_pragma_multiple_messages(): + comment = "#pylint: disable = missing-docstring, invalid-name" + match = OPTION_PO.search(comment) + for pragma_repr in parse_pragma(match.group(2)): + assert pragma_repr.action == "disable" + assert pragma_repr.messages == ["missing-docstring", "invalid-name"] + + +def test_multiple_pragma_multiple_messages(): + comment = "#pylint: disable = missing-docstring, invalid-name, enable = R0202, no-self-use" + match = OPTION_PO.search(comment) + res = list(parse_pragma(match.group(2))) + assert res[0].action == "disable" + assert res[0].messages == ["missing-docstring", "invalid-name"] + assert res[1].action == "enable" + assert res[1].messages == ["R0202", "no-self-use"] + + +def test_missing_assignment(): + comment = "#pylint: disable missing-docstring" + match = OPTION_PO.search(comment) + with pytest.raises(InvalidPragmaError): + for pragma_repr in parse_pragma(match.group(2)): + pass + + +def test_missing_keyword(): + comment = "#pylint: = missing-docstring" + match = OPTION_PO.search(comment) + with pytest.raises(InvalidPragmaError): + for pragma_repr in parse_pragma(match.group(2)): + pass + + +def test_unsupported_assignment(): + comment = "#pylint: disable-all = missing-docstring" + match = OPTION_PO.search(comment) + with pytest.raises(UnRecognizedOptionError): + for pragma_repr in parse_pragma(match.group(2)): + pass + + +def test_unknown_keyword_with_messages(): + comment = "#pylint: unknown-keyword = missing-docstring" + match = OPTION_PO.search(comment) + with pytest.raises(UnRecognizedOptionError): + for pragma_repr in parse_pragma(match.group(2)): + pass + + +def test_unknown_keyword_without_messages(): + comment = "#pylint: unknown-keyword" + match = OPTION_PO.search(comment) + with pytest.raises(UnRecognizedOptionError): + for pragma_repr in parse_pragma(match.group(2)): + pass + + +def test_missing_message(): + comment = "#pylint: disable = " + match = OPTION_PO.search(comment) + with pytest.raises(InvalidPragmaError): + for pragma_repr in parse_pragma(match.group(2)): + pass + + +if __name__ == "__main__": + test_missing_message() |