Refactor Error formatting and handling

This allows us to handle --show-source in our formatters by default.

This also adds the physical line information to the Error class instead
of passing it to is_inline_ignored. This allows us to avoid using
linecache in our formatters.
This commit is contained in:
Ian Cordasco 2016-05-30 16:43:11 -05:00
parent 8300e0f97c
commit 467672fc5c
3 changed files with 54 additions and 17 deletions

View file

@ -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."""

View file

@ -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)

View file

@ -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)