summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorhippo91 <guillaume.peillex@gmail.com>2019-11-19 09:16:54 +0100
committerClaudiu Popa <pcmanticore@gmail.com>2019-11-19 09:16:54 +0100
commit9bdae8b82fcc5b0592135cbf6bead7df360a6672 (patch)
tree823e86f1aa28d792d03bd963b52d1b9872d4ed9d
parent0ed3782d7a933b822d2ecf189fc24da0aaefbd6e (diff)
downloadpylint-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--ChangeLog5
-rw-r--r--doc/whatsnew/2.4.rst2
-rw-r--r--doc/whatsnew/2.5.rst13
-rw-r--r--pylint/checkers/format.py134
-rw-r--r--pylint/checkers/misc.py18
-rw-r--r--pylint/lint.py103
-rw-r--r--pylint/utils/pragma_parser.py134
-rw-r--r--tests/functional/b/bad_inline_option.py4
-rw-r--r--tests/functional/b/bad_inline_option.txt2
-rw-r--r--tests/functional/l/line_too_long.py32
-rw-r--r--tests/functional/l/line_too_long.txt5
-rw-r--r--tests/test_pragma_parser.py94
12 files changed, 443 insertions, 103 deletions
diff --git a/ChangeLog b/ChangeLog
index f549ba49a..8cfec00e6 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -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()