summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAnthony Sottile <asottile@umich.edu>2018-12-10 17:47:05 +0000
committerAnthony Sottile <asottile@umich.edu>2018-12-10 17:47:05 +0000
commitcad4e5be6fd0bf025d2789f08fb6c9ba832e782f (patch)
tree5a987994369ff6ff22cc9191a4faec4fb5ad00c8
parent9a9237a3386acb4a28b3eeb237e24cce973f17f6 (diff)
parent2803d0a81045fb144cd874807809d5b6fcb91786 (diff)
downloadflake8-cad4e5be6fd0bf025d2789f08fb6c9ba832e782f.tar.gz
Merge branch 'physical-line-multiple' into 'master'
Allow physical checks to return multiple results See merge request pycqa/flake8!269
-rw-r--r--src/flake8/checker.py39
-rw-r--r--tests/integration/test_checker.py88
2 files changed, 109 insertions, 18 deletions
diff --git a/src/flake8/checker.py b/src/flake8/checker.py
index 8b1655b..b0e0644 100644
--- a/src/flake8/checker.py
+++ b/src/flake8/checker.py
@@ -541,21 +541,38 @@ class FileChecker(object):
self.processor.next_logical_line()
def run_physical_checks(self, physical_line, override_error_line=None):
- """Run all checks for a given physical line."""
+ """Run all checks for a given physical line.
+
+ A single physical check may return multiple errors.
+ """
for plugin in self.checks["physical_line_plugins"]:
self.processor.update_checker_state_for(plugin)
result = self.run_check(plugin, physical_line=physical_line)
- if result is not None:
- column_offset, text = result
- error_code = self.report(
- error_code=None,
- line_number=self.processor.line_number,
- column=column_offset,
- text=text,
- line=(override_error_line or physical_line),
- )
- self.processor.check_physical_error(error_code, physical_line)
+ if result is not None:
+ # This is a single result if first element is an int
+ column_offset = None
+ try:
+ column_offset = result[0]
+ except (IndexError, TypeError):
+ pass
+
+ if isinstance(column_offset, int):
+ # If we only have a single result, convert to a collection
+ result = (result,)
+
+ for result_single in result:
+ column_offset, text = result_single
+ error_code = self.report(
+ error_code=None,
+ line_number=self.processor.line_number,
+ column=column_offset,
+ text=text,
+ line=(override_error_line or physical_line),
+ )
+ self.processor.check_physical_error(
+ error_code, physical_line
+ )
def process_tokens(self):
"""Process tokens and trigger checks.
diff --git a/tests/integration/test_checker.py b/tests/integration/test_checker.py
index 3d10bfb..0a3703c 100644
--- a/tests/integration/test_checker.py
+++ b/tests/integration/test_checker.py
@@ -5,8 +5,17 @@ import pytest
from flake8 import checker
from flake8.plugins import manager
+PHYSICAL_LINE = "# Physical line content"
EXPECTED_REPORT = (1, 1, 'T000 Expected Message')
+EXPECTED_REPORT_PHYSICAL_LINE = (1, 'T000 Expected Message')
+EXPECTED_RESULT_PHYSICAL_LINE = (
+ 'T000',
+ 0,
+ 1,
+ 'Expected Message',
+ PHYSICAL_LINE,
+)
class PluginClass(object):
@@ -43,13 +52,48 @@ def plugin_func_list(tree):
return [EXPECTED_REPORT + (type(plugin_func_list), )]
-@pytest.mark.parametrize('plugin_target', [
- PluginClass,
- plugin_func_gen,
- plugin_func_list,
-])
-def test_handle_file_plugins(plugin_target):
- """Test the FileChecker class handling different file plugin types."""
+@plugin_func
+def plugin_func_physical_ret(physical_line):
+ """Expect report from a physical_line. Single return."""
+ return EXPECTED_REPORT_PHYSICAL_LINE
+
+
+@plugin_func
+def plugin_func_physical_none(physical_line):
+ """Expect report from a physical_line. No results."""
+ return None
+
+
+@plugin_func
+def plugin_func_physical_list_single(physical_line):
+ """Expect report from a physical_line. List of single result."""
+ return [EXPECTED_REPORT_PHYSICAL_LINE]
+
+
+@plugin_func
+def plugin_func_physical_list_multiple(physical_line):
+ """Expect report from a physical_line. List of multiple results."""
+ return [EXPECTED_REPORT_PHYSICAL_LINE] * 2
+
+
+@plugin_func
+def plugin_func_physical_gen_single(physical_line):
+ """Expect report from a physical_line. Generator of single result."""
+ yield EXPECTED_REPORT_PHYSICAL_LINE
+
+
+@plugin_func
+def plugin_func_physical_gen_multiple(physical_line):
+ """Expect report from a physical_line. Generator of multiple results."""
+ for _ in range(3):
+ yield EXPECTED_REPORT_PHYSICAL_LINE
+
+
+def mock_file_checker_with_plugin(plugin_target):
+ """Get a mock FileChecker class with plugin_target registered.
+
+ Useful as a starting point for mocking reports/results.
+ """
# Mock an entry point returning the plugin target
entry_point = mock.Mock(spec=['load'])
entry_point.name = plugin_target.name
@@ -67,6 +111,17 @@ def test_handle_file_plugins(plugin_target):
checks.to_dictionary(),
mock.MagicMock()
)
+ return file_checker
+
+
+@pytest.mark.parametrize('plugin_target', [
+ PluginClass,
+ plugin_func_gen,
+ plugin_func_list,
+])
+def test_handle_file_plugins(plugin_target):
+ """Test the FileChecker class handling different file plugin types."""
+ file_checker = mock_file_checker_with_plugin(plugin_target)
# Do not actually build an AST
file_checker.processor.build_ast = lambda: True
@@ -81,6 +136,25 @@ def test_handle_file_plugins(plugin_target):
text=EXPECTED_REPORT[2])
+@pytest.mark.parametrize('plugin_target,len_results', [
+ (plugin_func_physical_ret, 1),
+ (plugin_func_physical_none, 0),
+ (plugin_func_physical_list_single, 1),
+ (plugin_func_physical_list_multiple, 2),
+ (plugin_func_physical_gen_single, 1),
+ (plugin_func_physical_gen_multiple, 3),
+])
+def test_line_check_results(plugin_target, len_results):
+ """Test the FileChecker class handling results from line checks."""
+ file_checker = mock_file_checker_with_plugin(plugin_target)
+
+ # Results will be store in an internal array
+ file_checker.run_physical_checks(PHYSICAL_LINE)
+ assert file_checker.results == [
+ EXPECTED_RESULT_PHYSICAL_LINE
+ ] * len_results
+
+
PLACEHOLDER_CODE = 'some_line = "of" * code'