summaryrefslogtreecommitdiff
path: root/src/mailman/testing
diff options
context:
space:
mode:
Diffstat (limited to 'src/mailman/testing')
-rw-r--r--src/mailman/testing/flake8.py88
1 files changed, 85 insertions, 3 deletions
diff --git a/src/mailman/testing/flake8.py b/src/mailman/testing/flake8.py
index d002e8b6c..df4e39c90 100644
--- a/src/mailman/testing/flake8.py
+++ b/src/mailman/testing/flake8.py
@@ -19,17 +19,39 @@
from ast import NodeVisitor
+from collections import namedtuple
+from enum import Enum
+
+
+class ImportType(Enum):
+ non_from = 0
+ from_import = 1
+
+
+ImportRecord = namedtuple('ImportRecord', 'itype lineno colno, module, names')
class ImportVisitor(NodeVisitor):
def __init__(self):
- self.last_import = None
+ self.imports = []
def visit_Import(self, node):
if node.col_offset != 0:
# Ignore nested imports.
return
-
+ names = [alias.name for alias in node.names]
+ self.imports.append(
+ ImportRecord(ImportType.non_from, node.lineno, node.col_offset,
+ None, names))
+
+ def visit_ImportFrom(self, node):
+ if node.col_offset != 0:
+ # Ignore nested imports.
+ return
+ names = [alias.name for alias in node.names]
+ self.imports.append(
+ ImportRecord(ImportType.from_import, node.lineno, node.col_offset,
+ node.module, names))
class ImportOrder:
@@ -40,5 +62,65 @@ class ImportOrder:
self.tree = tree
self.filename = filename
+ def _error(self, record, code, text):
+ return (record.lineno, record.colno,
+ '{} {}'.format(code, text), ImportOrder)
+
def run(self):
- print('Running', self.name, self.version)
+ visitor = ImportVisitor()
+ visitor.visit(self.tree)
+ last_import = None
+ for record in visitor.imports:
+ if last_import is None:
+ last_import = record
+ continue
+ if record.itype is ImportType.non_from:
+ if len(record.names) != 1:
+ yield self._error(record, 'B402',
+ 'Multiple names on non-from import')
+ if last_import.itype is ImportType.from_import:
+ yield self._error(record, 'B401',
+ 'Non-from import follows from-import')
+ if len(last_import.names[0]) > len(record.names[0]):
+ yield self._error(
+ record, 'B403',
+ 'Shorter non-from import follows longer')
+ # It's also possible that the imports are the same length, in
+ # which case they must be sorted alphabetically.
+ if (len(last_import.names[0]) == len(record.names[0]) and
+ last_import.names[0] > record.names[0]):
+ yield self._error(
+ record, 'B404',
+ 'Non-from imports not alphabetically sorted')
+ if last_import.lineno + 1 != record.lineno:
+ yield self._error(
+ record, 'B405',
+ 'Unexpected blank line since last non-from import')
+ else:
+ assert record.itype is ImportType.from_import
+ if (last_import.itype is ImportType.non_from and
+ record.lineno != last_import.lineno + 2):
+ yield self._error(
+ record, 'B406',
+ 'Expected one blank line since last non-from import')
+ if last_import.itype is ImportType.non_from:
+ last_import = record
+ continue
+ if last_import.module > record.module:
+ yield self._error(
+ record, 'B407',
+ 'From-imports not sorted alphabetically')
+ # All imports from the same module should show up in the same
+ # multiline import.
+ if last_import.module == record.module:
+ yield self._error(
+ record, 'B408',
+ 'Importing from same module on different lines')
+ # Check the sort order of the imported names.
+ if sorted(record.names) != record.names:
+ yield self._error(
+ record, 'B409',
+ 'Imported names are not sorted alphabetically')
+ # How to check for no blank lines between from imports?
+ # Update the last import.
+ last_import = record