mirror of
https://github.com/PyCQA/flake8.git
synced 2026-03-29 10:36:53 +00:00
Add ability to disable qa for blocks of code
An ignore block can be defined with `# noqa: on` and `# noqa: off` comments. Ignore block behaviour should be the same as inline ignore.
This commit is contained in:
parent
e492aeb385
commit
6051897a91
4 changed files with 174 additions and 2 deletions
|
|
@ -40,6 +40,25 @@ NOQA_INLINE_REGEXP = re.compile(
|
|||
re.IGNORECASE,
|
||||
)
|
||||
|
||||
NOQA_BLOCK_REGEXP = re.compile(
|
||||
# We're looking for items that look like this:
|
||||
# ``# noqa: off``
|
||||
# ``# noqa:off``
|
||||
# ``# noqa: Off``
|
||||
# ``# noqa:Off``
|
||||
# ``# noqa: off E123``
|
||||
# ``# noqa: off E123,W451,F921``
|
||||
# ``# noqa:off E123,W451,F921``
|
||||
# ``# NoQA: off E123,W451,F921``
|
||||
# ``# NOQA: off E123,W451,F921``
|
||||
# ``# NOQA:off E123,W451,F921``
|
||||
# We do not care about the casing of ``noqa``
|
||||
# We want the desired block state
|
||||
# We want a comma-separated list of errors
|
||||
r"# noqa:[\s]?(?P<state>off|on)[\s]?(?P<codes>([A-Z]+[0-9]+(?:[,\s]+)?)+)?",
|
||||
re.IGNORECASE,
|
||||
)
|
||||
|
||||
NOQA_FILE = re.compile(r"\s*# flake8[:=]\s*noqa", re.I)
|
||||
|
||||
VALID_CODE_PREFIX = re.compile("^[A-Z]{1,3}[0-9]{0,3}$", re.ASCII)
|
||||
|
|
|
|||
|
|
@ -423,8 +423,13 @@ class StyleGuide:
|
|||
error_is_selected = (
|
||||
self.should_report_error(error.code) is Decision.Selected
|
||||
)
|
||||
is_not_inline_ignored = error.is_inline_ignored(disable_noqa) is False
|
||||
if error_is_selected and is_not_inline_ignored:
|
||||
is_ignored = (
|
||||
error.is_block_ignored(disable_noqa)
|
||||
or error.is_inline_ignored(disable_noqa)
|
||||
)
|
||||
is_not_ignored = is_ignored is False
|
||||
|
||||
if error_is_selected and is_not_ignored:
|
||||
self.formatter.handle(error)
|
||||
self.stats.record(error)
|
||||
return 1
|
||||
|
|
|
|||
|
|
@ -19,6 +19,45 @@ def _find_noqa(physical_line: str) -> Match[str] | None:
|
|||
return defaults.NOQA_INLINE_REGEXP.search(physical_line)
|
||||
|
||||
|
||||
@functools.lru_cache(maxsize=512)
|
||||
def _find_block_noqa(physical_line: str) -> Match[str] | None:
|
||||
return defaults.NOQA_BLOCK_REGEXP.search(physical_line)
|
||||
|
||||
|
||||
@functools.lru_cache(maxsize=8)
|
||||
def _noqa_block_ranges(filename: str) -> Match[str] | None:
|
||||
noqa_block_ranges = []
|
||||
next_expected_state = "on"
|
||||
current_block_start = None
|
||||
current_block_codes = None
|
||||
|
||||
enumerated_lines = enumerate(linecache.getlines(filename), start=1)
|
||||
|
||||
for line_no, physical_line in enumerated_lines:
|
||||
noqa_match = _find_block_noqa(physical_line)
|
||||
if noqa_match is None:
|
||||
continue
|
||||
|
||||
state = noqa_match.groupdict().get("state").lower()
|
||||
codes = noqa_match.groupdict().get("codes")
|
||||
|
||||
if state != next_expected_state:
|
||||
continue
|
||||
elif state == "on":
|
||||
next_expected_state = "off"
|
||||
current_block_start = line_no
|
||||
current_block_codes = codes
|
||||
elif state == "off":
|
||||
noqa_block_ranges.append((
|
||||
range(current_block_start, line_no), current_block_codes,
|
||||
))
|
||||
next_expected_state = "on"
|
||||
current_block_start = None
|
||||
current_block_codes = None
|
||||
|
||||
return tuple(noqa_block_ranges)
|
||||
|
||||
|
||||
class Violation(NamedTuple):
|
||||
"""Class representing a violation reported by Flake8."""
|
||||
|
||||
|
|
@ -29,6 +68,54 @@ class Violation(NamedTuple):
|
|||
text: str
|
||||
physical_line: str | None
|
||||
|
||||
def is_block_ignored(self, disable_noqa: bool) -> bool:
|
||||
"""Determine if line is between comments which define an ignore block.
|
||||
|
||||
:param disable_noqa:
|
||||
Whether or not users have provided ``--disable-noqa``.
|
||||
:returns:
|
||||
True if error is ignored by block, False otherwise.
|
||||
"""
|
||||
if disable_noqa:
|
||||
return False
|
||||
|
||||
filename = self.filename
|
||||
line_number = self.line_number
|
||||
|
||||
def in_range(block_range):
|
||||
return line_number in block_range
|
||||
|
||||
blocks_in_range = (
|
||||
(block_line_range, codes)
|
||||
for block_line_range, codes in _noqa_block_ranges(filename)
|
||||
for is_in_range, codes in [(in_range(block_line_range), codes)]
|
||||
if is_in_range
|
||||
)
|
||||
|
||||
block_line_range, codes_str = next(blocks_in_range, (None, None))
|
||||
|
||||
if block_line_range is None:
|
||||
LOG.debug("%r is not block ignored", self)
|
||||
return False
|
||||
|
||||
if codes_str is None:
|
||||
LOG.debug("%r is ignored by a blanket ``# noqa: on ... # noqa: off`` block", self)
|
||||
return True
|
||||
|
||||
codes = set(utils.parse_comma_separated_list(codes_str))
|
||||
if self.code in codes or self.code.startswith(tuple(codes)):
|
||||
LOG.debug(
|
||||
"%r is ignored specifically within ``# noqa: on %s ... # noqa: off`` block",
|
||||
self,
|
||||
codes_str,
|
||||
)
|
||||
return True
|
||||
|
||||
LOG.debug(
|
||||
"%r is not ignored within ``# noqa: on %s ... # noqa: off`` block", self, codes_str
|
||||
)
|
||||
return False
|
||||
|
||||
def is_inline_ignored(self, disable_noqa: bool) -> bool:
|
||||
"""Determine if a comment has been added to ignore this line.
|
||||
|
||||
|
|
|
|||
|
|
@ -51,3 +51,64 @@ def test_disable_is_inline_ignored():
|
|||
assert error.is_inline_ignored(True) is False
|
||||
|
||||
assert getline.called is False
|
||||
|
||||
|
||||
def _bi_tc(error_code, physical_line, block_start, filename, expected_result):
|
||||
if block_start is None:
|
||||
physical_lines = [physical_line]
|
||||
line_no = 1
|
||||
else:
|
||||
block_end = "# noqa: off"
|
||||
physical_lines = [block_start, physical_line, block_end]
|
||||
line_no = 2
|
||||
|
||||
return (error_code, filename, physical_lines, line_no, expected_result)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"error_code,filename,physical_lines,line_no,expected_result",
|
||||
[
|
||||
_bi_tc("E111", "a = 1", None, "filename-1.py", False),
|
||||
_bi_tc("E121", "a = 1", "# noqa: on E111", "filename-2.py", False),
|
||||
_bi_tc("E121", "a = 1", "# noqa: on E111,W123,F821", "filename-3.py", False),
|
||||
_bi_tc("E111", "a = 1", "# noqa: on E111,W123,F821", "filename-4.py", True),
|
||||
_bi_tc("W123", "a = 1", "# noqa: on E111,W123,F821", "filename-5.py", True),
|
||||
_bi_tc("W123", "a = 1", "# noqa: on E111, W123,F821", "filename-6.py", True),
|
||||
_bi_tc("E111", "a = 1", "# noqa: on E11,W123,F821", "filename-7.py", True),
|
||||
_bi_tc("E121", "a = 1", "# noqa:on E111,W123,F821", "filename-8.py", False),
|
||||
_bi_tc("E111", "a = 1", "# noqa:on E111,W123,F821", "filename-9.py", True),
|
||||
_bi_tc("W123", "a = 1", "# noqa:on E111,W123,F821", "filename-10.py", True),
|
||||
_bi_tc("W123", "a = 1", "# noqa:on E111, W123,F821", "filename-11.py", True),
|
||||
_bi_tc("E111", "a = 1", "# noqa:on E11,W123,F821", "filename-12.py", True),
|
||||
_bi_tc("E111", "a = 1", "# noqa: on - We do not care", "filename-13.py", True),
|
||||
_bi_tc("E111", "a = 1", "# noqa: on We do not care", "filename-14.py", True),
|
||||
_bi_tc("E111", "a = 1", "# noqa:On We do not care", "filename-15.py", True),
|
||||
_bi_tc("ABC123", "a = 1", "# noqa: on ABC123", "filename-16.py", True),
|
||||
_bi_tc("E111", "a = 1", "# noqa: on ABC123", "filename-17.py", False),
|
||||
_bi_tc("ABC123", "a = 1", "# noqa: on ABC124", "filename-18.py", False),
|
||||
_bi_tc("ABC123", "a = 1", "# noqa: ABC124", "filename-19.py", False),
|
||||
_bi_tc("ABC123", "a = 1", "# noqa: off ABC124", "filename-19.py", False),
|
||||
],
|
||||
)
|
||||
def test_is_block_ignored(
|
||||
error_code,
|
||||
filename,
|
||||
physical_lines,
|
||||
line_no,
|
||||
expected_result,
|
||||
):
|
||||
"""Verify that we detect block usage of ``# noqa: off/on``."""
|
||||
error = Violation(error_code, filename, line_no, 1, "error text", None)
|
||||
|
||||
with mock.patch("linecache.getlines", return_value=physical_lines):
|
||||
assert error.is_block_ignored(False) is expected_result
|
||||
|
||||
|
||||
def test_disable_is_block_ignored():
|
||||
"""Verify that is_block_ignored exits immediately if disabling NoQA."""
|
||||
error = Violation("E121", "filename.py", 1, 1, "error text", "line")
|
||||
|
||||
with mock.patch("linecache.getlines") as getlines:
|
||||
assert error.is_block_ignored(True) is False
|
||||
|
||||
assert getlines.called is False
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue