Skip to content

Bug 2957 #3076

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 63 commits into from
Nov 19, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
9b7d57d
Move the test about finding #pylint:diable at the end of a line in a …
hippo91 Jun 30, 2019
4eebb71
Merge branch 'master' into bug_2957
hippo91 Jul 6, 2019
97ce381
using split instead of search and then processing each match
hippo91 Jul 8, 2019
71a4847
Splitting check_line in two methods check_line_ending and check_line_…
hippo91 Jul 10, 2019
20773d1
Merge branch 'master' into bug_2957
hippo91 Aug 11, 2019
33174b5
Add a test that check the disable line-too-long comment works on mult…
hippo91 Aug 16, 2019
ef62924
Add a test that check the disable line-too-long comment works on mult…
hippo91 Aug 16, 2019
1441b9c
Reformat
hippo91 Aug 16, 2019
1d5ae3e
Reinforces the unit test to ensure only multiline strings are conside…
hippo91 Aug 16, 2019
de3e916
Add a comment
hippo91 Aug 16, 2019
e676dd4
Corrects the unit test
hippo91 Aug 16, 2019
39a7646
Creates the remove_pylint_option_from_lines function
hippo91 Aug 16, 2019
a131c52
Add the is_line_length_check_deactivated function
hippo91 Aug 16, 2019
3ba906b
Reformat.
hippo91 Aug 16, 2019
268e82c
Merge branch 'master' into bug_2957
hippo91 Aug 19, 2019
4fb1ba7
Create the function specific_split
hippo91 Aug 19, 2019
6700f01
Simplify the specific_split function
hippo91 Aug 23, 2019
612e6fc
Rename specific_split function into specific_splitlines and reformat it
hippo91 Aug 23, 2019
4b03198
Convert closures into methods or staticmethods. Clarifies the check_l…
hippo91 Aug 24, 2019
33f7e63
The is_line_check_deactivated method is changed into is_line_check_ac…
hippo91 Aug 24, 2019
142cf69
The incrementation of line counter is not made in check_line_ending m…
hippo91 Aug 24, 2019
76e5815
Reformat and add comment
hippo91 Aug 24, 2019
0bad8d9
Add an entry concerning line-too-long message emission and multilines
hippo91 Aug 24, 2019
716e16e
Format according to black
hippo91 Aug 24, 2019
0c19f31
Merge branch 'master' into bug_2957
hippo91 Aug 24, 2019
d90e6d6
Adds the import of List from typing module and corrects the return ty…
hippo91 Aug 24, 2019
eec5bc9
Change the way final newline are detected in the metod remove_pylint_…
hippo91 Aug 31, 2019
7dbd60d
Change the way final newline are appended in the metod remove_pylint_…
hippo91 Aug 31, 2019
8e4352f
A linesep is inserted at the end of line after removing the #pylint: …
hippo91 Aug 31, 2019
5f89d2c
Delete dead code.
hippo91 Sep 8, 2019
33a8327
Add a first draft of pragma_parser
hippo91 Sep 23, 2019
5a41243
First use of pragma_parser
hippo91 Oct 6, 2019
0421d3b
Merge branch 'master' into bug_2957
hippo91 Oct 6, 2019
bbde005
Modify the OPTION_RGX so that the first match group corresponds to #p…
hippo91 Oct 6, 2019
aea135b
Use the parse_pragma module to analyze the pylint options.
hippo91 Oct 6, 2019
bff9247
Use of specific matched group to parse pragma
hippo91 Oct 12, 2019
e62e366
Uses the new parse_pragma fonction intead of the old constants.OPTION…
hippo91 Oct 12, 2019
b1211bd
Adds a specific exception in case the pragma is not valid.
hippo91 Oct 12, 2019
f3048fb
Only specific keywords should be followed by assignment.
hippo91 Oct 13, 2019
f8b155a
Add unit tests for pragma_parser module
hippo91 Oct 19, 2019
e8d73f6
Adds multiple specific exceptions. Improve error detections.
hippo91 Oct 19, 2019
a15357d
Takes into account potential catching of PragmaParserException
hippo91 Oct 19, 2019
fe38cb3
bad-inline option should reflect a licit but bad build option
hippo91 Oct 20, 2019
f406ba8
The PragmaRepresenter can be emitted if messages is empty only for so…
hippo91 Oct 20, 2019
31fb88b
Rewrite partially process_tokens method in order to take into accound…
hippo91 Oct 20, 2019
cf54723
Add a fonction to test a simple pragma with keyword that does not req…
hippo91 Oct 20, 2019
b54f4bc
Deleting test that could not occur due to use of parse_pragma
hippo91 Oct 20, 2019
e2d4a1a
Move and rename the pragma parser unit tests
hippo91 Oct 20, 2019
3282848
Move and rename the pragma parser unit tests
hippo91 Oct 20, 2019
fe3bd4c
Reformatting according to black and sorting imports
hippo91 Oct 20, 2019
4c706e8
Merge branch 'master' into bug_2957
hippo91 Oct 20, 2019
f6c5b4c
Correcting errors shown by linters
hippo91 Nov 10, 2019
4e202cd
Reformatting according to black
hippo91 Nov 10, 2019
2515b19
Merge branch 'master' into bug_2957
hippo91 Nov 10, 2019
acd9019
Moving doc to 2.5 section
hippo91 Nov 11, 2019
80a5edb
Changing the type annotation to type comment to be compatible with py…
hippo91 Nov 11, 2019
7255800
Change the keyword collections into frozensets
hippo91 Nov 17, 2019
ad9a99e
Reduces the number of exceptions. Only two remain : UnRecognizedOptio…
hippo91 Nov 17, 2019
c874aec
Takes into account the change in the exceptions of the pragma_parser …
hippo91 Nov 17, 2019
e9c07b4
Formatting according to black
hippo91 Nov 17, 2019
fe01ce0
Merge branch 'master' into bug_2957
hippo91 Nov 17, 2019
6a88e79
The keywords are sorted according to their length.
hippo91 Nov 17, 2019
7f8e8d8
Merge branch 'master' into bug_2957
hippo91 Nov 18, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion doc/whatsnew/2.4.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
13 changes: 13 additions & 0 deletions doc/whatsnew/2.5.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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.``,
Expand Down
134 changes: 87 additions & 47 deletions pylint/checkers/format.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
import keyword
import tokenize
from functools import reduce # pylint: disable=redefined-builtin
from typing import List

from astroid import nodes

Expand All @@ -56,8 +57,9 @@
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 = [
Expand Down Expand Up @@ -1243,41 +1245,64 @@ def _check_multi_statement_line(self, node, line):
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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is fine, but I wonder if we should have a parsing utility for the pragmas, since the current approach might be brittle.

We could add a function in pylint.utils or somewhere that from a pragma string, returns what messages were used and the action of the pragma. Maybe something along the lines of:

>>> pragma = parse_pragma('# pylint: disable=line-too-long,no-member')
>>> pragma.action
'disable'
>>> pragma.messages
{'line-too-long', 'no-member'}
'''

In which case our code becomes:

pragma = parse_pragma(match_object.string)
if pragma.action == 'disable' and 'line-too-long' in pragma.messages:
...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@PCManticore it's a good idea. It could definitely improve the way we interact with pragmas.
I will give it at try right now.

"""
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",
Expand All @@ -1290,23 +1315,38 @@ def check_line(line, i):
"\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
Expand Down
18 changes: 14 additions & 4 deletions pylint/checkers/misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@
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):
Expand Down Expand Up @@ -138,11 +138,21 @@ def process_tokens(self, tokens):
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:
Expand Down
103 changes: 55 additions & 48 deletions pylint/lint.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,16 @@

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
Expand Down Expand Up @@ -803,50 +809,41 @@ def process_tokens(self, tokens):

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],
Expand All @@ -855,15 +852,25 @@ 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 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 ###################################################

Expand Down
Loading