diff --git a/flake8/style_guide.py b/flake8/style_guide.py index 98e21c8..af68dce 100644 --- a/flake8/style_guide.py +++ b/flake8/style_guide.py @@ -1,9 +1,13 @@ """Implementation of the StyleGuide used by Flake8.""" import collections +import linecache import logging +import re import enum +from flake8 import utils + __all__ = ( 'StyleGuide', ) @@ -43,6 +47,20 @@ Error = collections.namedtuple('Error', ['code', class StyleGuide(object): """Manage a Flake8 user's style guide.""" + NOQA_INLINE_REGEXP = re.compile( + # We're looking for items that look like this: + # ``# noqa`` + # ``# noqa: E123`` + # ``# noqa: E123,W451,F921`` + # ``# NoQA: E123,W451,F921`` + # ``# NOQA: E123,W451,F921`` + # We do not care about the ``: `` that follows ``noqa`` + # We do not care about the casing of ``noqa`` + # We want a comma-separated list of errors + '# noqa(?:: )?(?P[A-Z0-9,]+)?$', + re.IGNORECASE + ) + def __init__(self, options, arguments, listener_trie, formatter): """Initialize our StyleGuide. @@ -109,6 +127,12 @@ class StyleGuide(object): # type: (Error) -> Decision """Determine if the error code should be reported or ignored. + This method only cares about the select and ignore rules as specified + by the user in their configuration files and command-line flags. + + This method does not look at whether the specific line is being + ignored in the file itself. + :param str code: The code for the check that has been run. """ @@ -135,11 +159,35 @@ class StyleGuide(object): LOG.debug('"%s" will be "%s"', code, decision) return decision + def is_inline_ignored(self, error): + """Determine if an comment has been added to ignore this line.""" + physical_line = linecache.getline(error.filename, error.line_number) + noqa_match = self.NOQA_INLINE_REGEXP.search(physical_line) + if noqa_match is None: + LOG.debug('%r is not inline ignored', error) + return False + + codes_str = noqa_match.groupdict()['codes'] + if codes_str is None: + LOG.debug('%r is ignored by a blanket ``# noqa``', error) + return True + + codes = set(utils.parse_comma_separated_list(codes_str)) + if error.code in codes: + LOG.debug('%r is ignored specifically inline with ``# noqa: %s``', + error, codes_str) + return True + + LOG.debug('%r is not ignored inline with ``# noqa: %s``', + error, codes_str) + return False + def handle_error(self, code, filename, line_number, column_number, text): # type: (str, str, int, int, str) -> NoneType """Handle an error reported by a check.""" error = Error(code, filename, line_number, column_number, text) - if self.should_report_error(error.code) is Decision.Selected: + if (self.should_report_error(error.code) is Decision.Selected and + self.is_inline_ignored(error) is False): self.formatter.handle(error) self.listener.notify(error.code, error) diff --git a/tests/unit/test_style_guide.py b/tests/unit/test_style_guide.py index f1e18b6..e02fdc7 100644 --- a/tests/unit/test_style_guide.py +++ b/tests/unit/test_style_guide.py @@ -142,7 +142,8 @@ def test_handle_error_notifies_listeners(select_list, ignore_list, error_code): listener_trie=listener_trie, formatter=formatter) - guide.handle_error(error_code, 'stdin', 1, 1, 'error found') + with mock.patch('linecache.getline', return_value=''): + guide.handle_error(error_code, 'stdin', 1, 1, 'error found') error = style_guide.Error(error_code, 'stdin', 1, 1, 'error found') listener_trie.notify.assert_called_once_with(error_code, error) formatter.handle.assert_called_once_with(error) @@ -170,6 +171,7 @@ def test_handle_error_does_not_notify_listeners(select_list, ignore_list, listener_trie=listener_trie, formatter=formatter) - guide.handle_error(error_code, 'stdin', 1, 1, 'error found') + with mock.patch('linecache.getline', return_value=''): + guide.handle_error(error_code, 'stdin', 1, 1, 'error found') assert listener_trie.notify.called is False assert formatter.handle.called is False