1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
|
# Copyright (C) 2016 by the Free Software Foundation, Inc.
#
# This file is part of GNU Mailman.
#
# GNU Mailman is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option)
# any later version.
#
# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
# more details.
#
# You should have received a copy of the GNU General Public License along with
# GNU Mailman. If not, see <http://www.gnu.org/licenses/>.
"""Flake8 extensions for Mailman coding style."""
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')
NONFROM_FOLLOWS_FROM = 'B401 Non-from import follows from-import'
NONFROM_MULTIPLE_NAMES = 'B402 Multiple names on non-from import'
NONFROM_SHORTER_FOLLOWS = 'B403 Shorter non-from import follows longer'
NONFROM_ALPHA_UNSORTED = (
'B404 Same-length non-from imports not sorted alphabetically')
NONFROM_EXTRA_BLANK_LINE = (
'B405 Unexpected blank line since last non-from import')
NONFROM_DOTTED_UNSORTED = (
'B406 Dotted non-from import not sorted alphabetically')
FROMIMPORT_MISSING_BLANK_LINE = (
'B411 Expected one blank line since last non-from import')
FROMIMPORT_ALPHA_UNSORTED = 'B412 from-import not sorted alphabetically'
FROMIMPORT_MULTIPLE = 'B413 Multiple from-imports of same module'
FROMIMPORT_NAMES_UNSORTED = (
'B414 from-imported names are not sorted alphabetically')
class ImportVisitor(NodeVisitor):
def __init__(self):
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:
name = 'flufl-import-order'
version = '0.1'
def __init__(self, tree, filename):
self.tree = tree
self.filename = filename
def _error(self, record, error):
code, space, text = error.partition(' ')
return (record.lineno, record.colno,
'{} {}'.format(code, text), ImportOrder)
def run(self):
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, NONFROM_MULTIPLE_NAMES)
if last_import.itype is ImportType.from_import:
yield self._error(record, NONFROM_FOLLOWS_FROM)
# Shorter imports should always precede longer import *except*
# when they are dotted imports and everything but the last
# path component are the same. In that case, they should be
# sorted alphabetically.
last_name = last_import.names[0]
this_name = record.names[0]
if '.' in last_name and '.' in this_name:
last_parts = last_name.split('.')
this_parts = this_name.split('.')
if (last_parts[:-1] == this_parts[:-1] and
last_parts[-1] > this_parts[-1]):
yield self._error(record, NONFROM_DOTTED_UNSORTED)
elif len(last_name) > len(this_name):
yield self._error(record, NONFROM_SHORTER_FOLLOWS)
# 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, NONFROM_ALPHA_UNSORTED)
if last_import.lineno + 1 != record.lineno:
yield self._error(record, NONFROM_DOTTED_UNSORTED)
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, FROMIMPORT_MISSING_BLANK_LINE)
if last_import.itype is ImportType.non_from:
last_import = record
continue
if last_import.module > record.module:
yield self._error(record, FROMIMPORT_ALPHA_UNSORTED)
# All imports from the same module should show up in the same
# multiline import.
if last_import.module == record.module:
yield self._error(record, FROMIMPORT_MULTIPLE)
# Check the sort order of the imported names.
if sorted(record.names) != record.names:
yield self._error(record, FROMIMPORT_NAMES_UNSORTED)
# How to check for no blank lines between from imports?
# Update the last import.
last_import = record
|