diff --git a/flake8/formatting/base.py b/flake8/formatting/base.py index e419470..ecc5973 100644 --- a/flake8/formatting/base.py +++ b/flake8/formatting/base.py @@ -55,8 +55,9 @@ class BaseFormatter(object): def handle(self, error): """Handle an error reported by Flake8. - This defaults to calling :meth:`format` and then :meth:`write`. To - extend how errors are handled, override this method. + This defaults to calling :meth:`format`, :meth:`format_source`, and + then :meth:`write`. To extend how errors are handled, override this + method. :param error: This will be an instance of :class:`~flake8.style_guide.Error`. @@ -64,7 +65,8 @@ class BaseFormatter(object): flake8.style_guide.Error """ line = self.format(error) - self.write(line) + source = self.format_source(error) + self.write(line, source) def format(self, error): """Format an error reported by Flake8. @@ -83,7 +85,26 @@ class BaseFormatter(object): raise NotImplementedError('Subclass of BaseFormatter did not implement' ' format.') - def write(self, line): + def format_source(self, error): + """Format the physical line generating the error. + + :param error: + This will be an instance of :class:`~flake8.style_guide.Error`. + :returns: + The formatted error string if the user wants to show the source. + If the user does not want to show the source, this will return + ``None``. + :rtype: + str + """ + if not self.options.show_source: + return None + pointer = (' ' * error.column_number) + '^' + # Physical lines have a newline at the end, no need to add an extra + # one + return error.physical_line + pointer + + def write(self, line, source): """Write the line either to the output file or stdout. This handles deciding whether to write to a file or print to standard @@ -94,9 +115,14 @@ class BaseFormatter(object): The formatted string to print or write. """ if self.output_fd is not None: - self.output_fd.write(line + self.newline) + write = self.output_fd.write + output_func = lambda line: write(line + self.newline) else: - print(line) + output_func = print + + output_func(line) + if source: + output_func(source) def stop(self): """Clean up after reporting is finished.""" diff --git a/flake8/style_guide.py b/flake8/style_guide.py index 2c18c9d..fd4692c 100644 --- a/flake8/style_guide.py +++ b/flake8/style_guide.py @@ -36,11 +36,17 @@ class Decision(enum.Enum): Selected = 'selected error' -Error = collections.namedtuple('Error', ['code', - 'filename', - 'line_number', - 'column_number', - 'text']) +Error = collections.namedtuple( + 'Error', + [ + 'code', + 'filename', + 'line_number', + 'column_number', + 'text', + 'physical_line', + ], +) class StyleGuide(object): @@ -157,9 +163,10 @@ class StyleGuide(object): LOG.debug('"%s" will be "%s"', code, decision) return decision - def is_inline_ignored(self, error, physical_line=None): + def is_inline_ignored(self, error): # type: (Error) -> bool """Determine if an comment has been added to ignore this line.""" + physical_line = error.physical_line # TODO(sigmavirus24): Determine how to handle stdin with linecache if self.options.disable_noqa: return False @@ -191,9 +198,10 @@ class StyleGuide(object): physical_line=None): # type: (str, str, int, int, str) -> NoneType """Handle an error reported by a check.""" - error = Error(code, filename, line_number, column_number, text) + error = Error(code, filename, line_number, column_number, text, + physical_line) if (self.should_report_error(error.code) is Decision.Selected and - self.is_inline_ignored(error, physical_line) is False): + 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 973281b..2b14721 100644 --- a/tests/unit/test_style_guide.py +++ b/tests/unit/test_style_guide.py @@ -134,7 +134,8 @@ def test_is_inline_ignored(error_code, physical_line, expected_result): guide = style_guide.StyleGuide(create_options(select=['E', 'W', 'F']), listener_trie=None, formatter=None) - error = style_guide.Error(error_code, 'filename.py', 1, 1, 'error text') + error = style_guide.Error(error_code, 'filename.py', 1, 1, 'error text', + physical_line) with mock.patch('linecache.getline', return_value=physical_line): assert guide.is_inline_ignored(error) is expected_result @@ -145,7 +146,8 @@ def test_disable_is_inline_ignored(): guide = style_guide.StyleGuide(create_options(disable_noqa=True), listener_trie=None, formatter=None) - error = style_guide.Error('E121', 'filename.py', 1, 1, 'error text') + error = style_guide.Error('E121', 'filename.py', 1, 1, 'error text', + 'line') with mock.patch('linecache.getline') as getline: assert guide.is_inline_ignored(error) is False @@ -171,7 +173,8 @@ def test_handle_error_notifies_listeners(select_list, ignore_list, error_code): 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') + error = style_guide.Error(error_code, 'stdin', 1, 1, 'error found', + None) listener_trie.notify.assert_called_once_with(error_code, error) formatter.handle.assert_called_once_with(error)