##// END OF EJS Templates
import-checker: make it executable for convenience
Yuya Nishihara -
r26954:f804bf27 default
parent child Browse files
Show More
@@ -1,569 +1,571
1 #!/usr/bin/env python
2
1 import ast
3 import ast
2 import os
4 import os
3 import sys
5 import sys
4
6
5 # Import a minimal set of stdlib modules needed for list_stdlib_modules()
7 # Import a minimal set of stdlib modules needed for list_stdlib_modules()
6 # to work when run from a virtualenv. The modules were chosen empirically
8 # to work when run from a virtualenv. The modules were chosen empirically
7 # so that the return value matches the return value without virtualenv.
9 # so that the return value matches the return value without virtualenv.
8 import BaseHTTPServer
10 import BaseHTTPServer
9 import zlib
11 import zlib
10
12
11 # Whitelist of modules that symbols can be directly imported from.
13 # Whitelist of modules that symbols can be directly imported from.
12 allowsymbolimports = (
14 allowsymbolimports = (
13 '__future__',
15 '__future__',
14 'mercurial.i18n',
16 'mercurial.i18n',
15 'mercurial.node',
17 'mercurial.node',
16 )
18 )
17
19
18 # Modules that must be aliased because they are commonly confused with
20 # Modules that must be aliased because they are commonly confused with
19 # common variables and can create aliasing and readability issues.
21 # common variables and can create aliasing and readability issues.
20 requirealias = {
22 requirealias = {
21 'ui': 'uimod',
23 'ui': 'uimod',
22 }
24 }
23
25
24 def usingabsolute(root):
26 def usingabsolute(root):
25 """Whether absolute imports are being used."""
27 """Whether absolute imports are being used."""
26 if sys.version_info[0] >= 3:
28 if sys.version_info[0] >= 3:
27 return True
29 return True
28
30
29 for node in ast.walk(root):
31 for node in ast.walk(root):
30 if isinstance(node, ast.ImportFrom):
32 if isinstance(node, ast.ImportFrom):
31 if node.module == '__future__':
33 if node.module == '__future__':
32 for n in node.names:
34 for n in node.names:
33 if n.name == 'absolute_import':
35 if n.name == 'absolute_import':
34 return True
36 return True
35
37
36 return False
38 return False
37
39
38 def dotted_name_of_path(path, trimpure=False):
40 def dotted_name_of_path(path, trimpure=False):
39 """Given a relative path to a source file, return its dotted module name.
41 """Given a relative path to a source file, return its dotted module name.
40
42
41 >>> dotted_name_of_path('mercurial/error.py')
43 >>> dotted_name_of_path('mercurial/error.py')
42 'mercurial.error'
44 'mercurial.error'
43 >>> dotted_name_of_path('mercurial/pure/parsers.py', trimpure=True)
45 >>> dotted_name_of_path('mercurial/pure/parsers.py', trimpure=True)
44 'mercurial.parsers'
46 'mercurial.parsers'
45 >>> dotted_name_of_path('zlibmodule.so')
47 >>> dotted_name_of_path('zlibmodule.so')
46 'zlib'
48 'zlib'
47 """
49 """
48 parts = path.split('/')
50 parts = path.split('/')
49 parts[-1] = parts[-1].split('.', 1)[0] # remove .py and .so and .ARCH.so
51 parts[-1] = parts[-1].split('.', 1)[0] # remove .py and .so and .ARCH.so
50 if parts[-1].endswith('module'):
52 if parts[-1].endswith('module'):
51 parts[-1] = parts[-1][:-6]
53 parts[-1] = parts[-1][:-6]
52 if trimpure:
54 if trimpure:
53 return '.'.join(p for p in parts if p != 'pure')
55 return '.'.join(p for p in parts if p != 'pure')
54 return '.'.join(parts)
56 return '.'.join(parts)
55
57
56 def fromlocalfunc(modulename, localmods):
58 def fromlocalfunc(modulename, localmods):
57 """Get a function to examine which locally defined module the
59 """Get a function to examine which locally defined module the
58 target source imports via a specified name.
60 target source imports via a specified name.
59
61
60 `modulename` is an `dotted_name_of_path()`-ed source file path,
62 `modulename` is an `dotted_name_of_path()`-ed source file path,
61 which may have `.__init__` at the end of it, of the target source.
63 which may have `.__init__` at the end of it, of the target source.
62
64
63 `localmods` is a dict (or set), of which key is an absolute
65 `localmods` is a dict (or set), of which key is an absolute
64 `dotted_name_of_path()`-ed source file path of locally defined (=
66 `dotted_name_of_path()`-ed source file path of locally defined (=
65 Mercurial specific) modules.
67 Mercurial specific) modules.
66
68
67 This function assumes that module names not existing in
69 This function assumes that module names not existing in
68 `localmods` are from the Python standard library.
70 `localmods` are from the Python standard library.
69
71
70 This function returns the function, which takes `name` argument,
72 This function returns the function, which takes `name` argument,
71 and returns `(absname, dottedpath, hassubmod)` tuple if `name`
73 and returns `(absname, dottedpath, hassubmod)` tuple if `name`
72 matches against locally defined module. Otherwise, it returns
74 matches against locally defined module. Otherwise, it returns
73 False.
75 False.
74
76
75 It is assumed that `name` doesn't have `.__init__`.
77 It is assumed that `name` doesn't have `.__init__`.
76
78
77 `absname` is an absolute module name of specified `name`
79 `absname` is an absolute module name of specified `name`
78 (e.g. "hgext.convert"). This can be used to compose prefix for sub
80 (e.g. "hgext.convert"). This can be used to compose prefix for sub
79 modules or so.
81 modules or so.
80
82
81 `dottedpath` is a `dotted_name_of_path()`-ed source file path
83 `dottedpath` is a `dotted_name_of_path()`-ed source file path
82 (e.g. "hgext.convert.__init__") of `name`. This is used to look
84 (e.g. "hgext.convert.__init__") of `name`. This is used to look
83 module up in `localmods` again.
85 module up in `localmods` again.
84
86
85 `hassubmod` is whether it may have sub modules under it (for
87 `hassubmod` is whether it may have sub modules under it (for
86 convenient, even though this is also equivalent to "absname !=
88 convenient, even though this is also equivalent to "absname !=
87 dottednpath")
89 dottednpath")
88
90
89 >>> localmods = {'foo.__init__': True, 'foo.foo1': True,
91 >>> localmods = {'foo.__init__': True, 'foo.foo1': True,
90 ... 'foo.bar.__init__': True, 'foo.bar.bar1': True,
92 ... 'foo.bar.__init__': True, 'foo.bar.bar1': True,
91 ... 'baz.__init__': True, 'baz.baz1': True }
93 ... 'baz.__init__': True, 'baz.baz1': True }
92 >>> fromlocal = fromlocalfunc('foo.xxx', localmods)
94 >>> fromlocal = fromlocalfunc('foo.xxx', localmods)
93 >>> # relative
95 >>> # relative
94 >>> fromlocal('foo1')
96 >>> fromlocal('foo1')
95 ('foo.foo1', 'foo.foo1', False)
97 ('foo.foo1', 'foo.foo1', False)
96 >>> fromlocal('bar')
98 >>> fromlocal('bar')
97 ('foo.bar', 'foo.bar.__init__', True)
99 ('foo.bar', 'foo.bar.__init__', True)
98 >>> fromlocal('bar.bar1')
100 >>> fromlocal('bar.bar1')
99 ('foo.bar.bar1', 'foo.bar.bar1', False)
101 ('foo.bar.bar1', 'foo.bar.bar1', False)
100 >>> # absolute
102 >>> # absolute
101 >>> fromlocal('baz')
103 >>> fromlocal('baz')
102 ('baz', 'baz.__init__', True)
104 ('baz', 'baz.__init__', True)
103 >>> fromlocal('baz.baz1')
105 >>> fromlocal('baz.baz1')
104 ('baz.baz1', 'baz.baz1', False)
106 ('baz.baz1', 'baz.baz1', False)
105 >>> # unknown = maybe standard library
107 >>> # unknown = maybe standard library
106 >>> fromlocal('os')
108 >>> fromlocal('os')
107 False
109 False
108 >>> fromlocal(None, 1)
110 >>> fromlocal(None, 1)
109 ('foo', 'foo.__init__', True)
111 ('foo', 'foo.__init__', True)
110 >>> fromlocal2 = fromlocalfunc('foo.xxx.yyy', localmods)
112 >>> fromlocal2 = fromlocalfunc('foo.xxx.yyy', localmods)
111 >>> fromlocal2(None, 2)
113 >>> fromlocal2(None, 2)
112 ('foo', 'foo.__init__', True)
114 ('foo', 'foo.__init__', True)
113 """
115 """
114 prefix = '.'.join(modulename.split('.')[:-1])
116 prefix = '.'.join(modulename.split('.')[:-1])
115 if prefix:
117 if prefix:
116 prefix += '.'
118 prefix += '.'
117 def fromlocal(name, level=0):
119 def fromlocal(name, level=0):
118 # name is None when relative imports are used.
120 # name is None when relative imports are used.
119 if name is None:
121 if name is None:
120 # If relative imports are used, level must not be absolute.
122 # If relative imports are used, level must not be absolute.
121 assert level > 0
123 assert level > 0
122 candidates = ['.'.join(modulename.split('.')[:-level])]
124 candidates = ['.'.join(modulename.split('.')[:-level])]
123 else:
125 else:
124 # Check relative name first.
126 # Check relative name first.
125 candidates = [prefix + name, name]
127 candidates = [prefix + name, name]
126
128
127 for n in candidates:
129 for n in candidates:
128 if n in localmods:
130 if n in localmods:
129 return (n, n, False)
131 return (n, n, False)
130 dottedpath = n + '.__init__'
132 dottedpath = n + '.__init__'
131 if dottedpath in localmods:
133 if dottedpath in localmods:
132 return (n, dottedpath, True)
134 return (n, dottedpath, True)
133 return False
135 return False
134 return fromlocal
136 return fromlocal
135
137
136 def list_stdlib_modules():
138 def list_stdlib_modules():
137 """List the modules present in the stdlib.
139 """List the modules present in the stdlib.
138
140
139 >>> mods = set(list_stdlib_modules())
141 >>> mods = set(list_stdlib_modules())
140 >>> 'BaseHTTPServer' in mods
142 >>> 'BaseHTTPServer' in mods
141 True
143 True
142
144
143 os.path isn't really a module, so it's missing:
145 os.path isn't really a module, so it's missing:
144
146
145 >>> 'os.path' in mods
147 >>> 'os.path' in mods
146 False
148 False
147
149
148 sys requires special treatment, because it's baked into the
150 sys requires special treatment, because it's baked into the
149 interpreter, but it should still appear:
151 interpreter, but it should still appear:
150
152
151 >>> 'sys' in mods
153 >>> 'sys' in mods
152 True
154 True
153
155
154 >>> 'collections' in mods
156 >>> 'collections' in mods
155 True
157 True
156
158
157 >>> 'cStringIO' in mods
159 >>> 'cStringIO' in mods
158 True
160 True
159 """
161 """
160 for m in sys.builtin_module_names:
162 for m in sys.builtin_module_names:
161 yield m
163 yield m
162 # These modules only exist on windows, but we should always
164 # These modules only exist on windows, but we should always
163 # consider them stdlib.
165 # consider them stdlib.
164 for m in ['msvcrt', '_winreg']:
166 for m in ['msvcrt', '_winreg']:
165 yield m
167 yield m
166 # These get missed too
168 # These get missed too
167 for m in 'ctypes', 'email', 'multiprocessing':
169 for m in 'ctypes', 'email', 'multiprocessing':
168 yield m
170 yield m
169 yield 'builtins' # python3 only
171 yield 'builtins' # python3 only
170 for m in 'fcntl', 'grp', 'pwd', 'termios': # Unix only
172 for m in 'fcntl', 'grp', 'pwd', 'termios': # Unix only
171 yield m
173 yield m
172 stdlib_prefixes = set([sys.prefix, sys.exec_prefix])
174 stdlib_prefixes = set([sys.prefix, sys.exec_prefix])
173 # We need to supplement the list of prefixes for the search to work
175 # We need to supplement the list of prefixes for the search to work
174 # when run from within a virtualenv.
176 # when run from within a virtualenv.
175 for mod in (BaseHTTPServer, zlib):
177 for mod in (BaseHTTPServer, zlib):
176 try:
178 try:
177 # Not all module objects have a __file__ attribute.
179 # Not all module objects have a __file__ attribute.
178 filename = mod.__file__
180 filename = mod.__file__
179 except AttributeError:
181 except AttributeError:
180 continue
182 continue
181 dirname = os.path.dirname(filename)
183 dirname = os.path.dirname(filename)
182 for prefix in stdlib_prefixes:
184 for prefix in stdlib_prefixes:
183 if dirname.startswith(prefix):
185 if dirname.startswith(prefix):
184 # Then this directory is redundant.
186 # Then this directory is redundant.
185 break
187 break
186 else:
188 else:
187 stdlib_prefixes.add(dirname)
189 stdlib_prefixes.add(dirname)
188 for libpath in sys.path:
190 for libpath in sys.path:
189 # We want to walk everything in sys.path that starts with
191 # We want to walk everything in sys.path that starts with
190 # something in stdlib_prefixes. check-code suppressed because
192 # something in stdlib_prefixes. check-code suppressed because
191 # the ast module used by this script implies the availability
193 # the ast module used by this script implies the availability
192 # of any().
194 # of any().
193 if not any(libpath.startswith(p) for p in stdlib_prefixes): # no-py24
195 if not any(libpath.startswith(p) for p in stdlib_prefixes): # no-py24
194 continue
196 continue
195 for top, dirs, files in os.walk(libpath):
197 for top, dirs, files in os.walk(libpath):
196 for i, d in reversed(list(enumerate(dirs))):
198 for i, d in reversed(list(enumerate(dirs))):
197 if (not os.path.exists(os.path.join(top, d, '__init__.py'))
199 if (not os.path.exists(os.path.join(top, d, '__init__.py'))
198 or top == libpath and d in ('hgext', 'mercurial')):
200 or top == libpath and d in ('hgext', 'mercurial')):
199 del dirs[i]
201 del dirs[i]
200 for name in files:
202 for name in files:
201 if name == '__init__.py':
203 if name == '__init__.py':
202 continue
204 continue
203 if not name.endswith(('.py', '.so', '.pyc', '.pyo', '.pyd')):
205 if not name.endswith(('.py', '.so', '.pyc', '.pyo', '.pyd')):
204 continue
206 continue
205 full_path = os.path.join(top, name)
207 full_path = os.path.join(top, name)
206 rel_path = full_path[len(libpath) + 1:]
208 rel_path = full_path[len(libpath) + 1:]
207 mod = dotted_name_of_path(rel_path)
209 mod = dotted_name_of_path(rel_path)
208 yield mod
210 yield mod
209
211
210 stdlib_modules = set(list_stdlib_modules())
212 stdlib_modules = set(list_stdlib_modules())
211
213
212 def imported_modules(source, modulename, localmods, ignore_nested=False):
214 def imported_modules(source, modulename, localmods, ignore_nested=False):
213 """Given the source of a file as a string, yield the names
215 """Given the source of a file as a string, yield the names
214 imported by that file.
216 imported by that file.
215
217
216 Args:
218 Args:
217 source: The python source to examine as a string.
219 source: The python source to examine as a string.
218 modulename: of specified python source (may have `__init__`)
220 modulename: of specified python source (may have `__init__`)
219 localmods: dict of locally defined module names (may have `__init__`)
221 localmods: dict of locally defined module names (may have `__init__`)
220 ignore_nested: If true, import statements that do not start in
222 ignore_nested: If true, import statements that do not start in
221 column zero will be ignored.
223 column zero will be ignored.
222
224
223 Returns:
225 Returns:
224 A list of absolute module names imported by the given source.
226 A list of absolute module names imported by the given source.
225
227
226 >>> modulename = 'foo.xxx'
228 >>> modulename = 'foo.xxx'
227 >>> localmods = {'foo.__init__': True,
229 >>> localmods = {'foo.__init__': True,
228 ... 'foo.foo1': True, 'foo.foo2': True,
230 ... 'foo.foo1': True, 'foo.foo2': True,
229 ... 'foo.bar.__init__': True, 'foo.bar.bar1': True,
231 ... 'foo.bar.__init__': True, 'foo.bar.bar1': True,
230 ... 'baz.__init__': True, 'baz.baz1': True }
232 ... 'baz.__init__': True, 'baz.baz1': True }
231 >>> # standard library (= not locally defined ones)
233 >>> # standard library (= not locally defined ones)
232 >>> sorted(imported_modules(
234 >>> sorted(imported_modules(
233 ... 'from stdlib1 import foo, bar; import stdlib2',
235 ... 'from stdlib1 import foo, bar; import stdlib2',
234 ... modulename, localmods))
236 ... modulename, localmods))
235 []
237 []
236 >>> # relative importing
238 >>> # relative importing
237 >>> sorted(imported_modules(
239 >>> sorted(imported_modules(
238 ... 'import foo1; from bar import bar1',
240 ... 'import foo1; from bar import bar1',
239 ... modulename, localmods))
241 ... modulename, localmods))
240 ['foo.bar.__init__', 'foo.bar.bar1', 'foo.foo1']
242 ['foo.bar.__init__', 'foo.bar.bar1', 'foo.foo1']
241 >>> sorted(imported_modules(
243 >>> sorted(imported_modules(
242 ... 'from bar.bar1 import name1, name2, name3',
244 ... 'from bar.bar1 import name1, name2, name3',
243 ... modulename, localmods))
245 ... modulename, localmods))
244 ['foo.bar.bar1']
246 ['foo.bar.bar1']
245 >>> # absolute importing
247 >>> # absolute importing
246 >>> sorted(imported_modules(
248 >>> sorted(imported_modules(
247 ... 'from baz import baz1, name1',
249 ... 'from baz import baz1, name1',
248 ... modulename, localmods))
250 ... modulename, localmods))
249 ['baz.__init__', 'baz.baz1']
251 ['baz.__init__', 'baz.baz1']
250 >>> # mixed importing, even though it shouldn't be recommended
252 >>> # mixed importing, even though it shouldn't be recommended
251 >>> sorted(imported_modules(
253 >>> sorted(imported_modules(
252 ... 'import stdlib, foo1, baz',
254 ... 'import stdlib, foo1, baz',
253 ... modulename, localmods))
255 ... modulename, localmods))
254 ['baz.__init__', 'foo.foo1']
256 ['baz.__init__', 'foo.foo1']
255 >>> # ignore_nested
257 >>> # ignore_nested
256 >>> sorted(imported_modules(
258 >>> sorted(imported_modules(
257 ... '''import foo
259 ... '''import foo
258 ... def wat():
260 ... def wat():
259 ... import bar
261 ... import bar
260 ... ''', modulename, localmods))
262 ... ''', modulename, localmods))
261 ['foo.__init__', 'foo.bar.__init__']
263 ['foo.__init__', 'foo.bar.__init__']
262 >>> sorted(imported_modules(
264 >>> sorted(imported_modules(
263 ... '''import foo
265 ... '''import foo
264 ... def wat():
266 ... def wat():
265 ... import bar
267 ... import bar
266 ... ''', modulename, localmods, ignore_nested=True))
268 ... ''', modulename, localmods, ignore_nested=True))
267 ['foo.__init__']
269 ['foo.__init__']
268 """
270 """
269 fromlocal = fromlocalfunc(modulename, localmods)
271 fromlocal = fromlocalfunc(modulename, localmods)
270 for node in ast.walk(ast.parse(source)):
272 for node in ast.walk(ast.parse(source)):
271 if ignore_nested and getattr(node, 'col_offset', 0) > 0:
273 if ignore_nested and getattr(node, 'col_offset', 0) > 0:
272 continue
274 continue
273 if isinstance(node, ast.Import):
275 if isinstance(node, ast.Import):
274 for n in node.names:
276 for n in node.names:
275 found = fromlocal(n.name)
277 found = fromlocal(n.name)
276 if not found:
278 if not found:
277 # this should import standard library
279 # this should import standard library
278 continue
280 continue
279 yield found[1]
281 yield found[1]
280 elif isinstance(node, ast.ImportFrom):
282 elif isinstance(node, ast.ImportFrom):
281 found = fromlocal(node.module, node.level)
283 found = fromlocal(node.module, node.level)
282 if not found:
284 if not found:
283 # this should import standard library
285 # this should import standard library
284 continue
286 continue
285
287
286 absname, dottedpath, hassubmod = found
288 absname, dottedpath, hassubmod = found
287 yield dottedpath
289 yield dottedpath
288 if not hassubmod:
290 if not hassubmod:
289 # examination of "node.names" should be redundant
291 # examination of "node.names" should be redundant
290 # e.g.: from mercurial.node import nullid, nullrev
292 # e.g.: from mercurial.node import nullid, nullrev
291 continue
293 continue
292
294
293 prefix = absname + '.'
295 prefix = absname + '.'
294 for n in node.names:
296 for n in node.names:
295 found = fromlocal(prefix + n.name)
297 found = fromlocal(prefix + n.name)
296 if not found:
298 if not found:
297 # this should be a function or a property of "node.module"
299 # this should be a function or a property of "node.module"
298 continue
300 continue
299 yield found[1]
301 yield found[1]
300
302
301 def verify_import_convention(module, source):
303 def verify_import_convention(module, source):
302 """Verify imports match our established coding convention.
304 """Verify imports match our established coding convention.
303
305
304 We have 2 conventions: legacy and modern. The modern convention is in
306 We have 2 conventions: legacy and modern. The modern convention is in
305 effect when using absolute imports.
307 effect when using absolute imports.
306
308
307 The legacy convention only looks for mixed imports. The modern convention
309 The legacy convention only looks for mixed imports. The modern convention
308 is much more thorough.
310 is much more thorough.
309 """
311 """
310 root = ast.parse(source)
312 root = ast.parse(source)
311 absolute = usingabsolute(root)
313 absolute = usingabsolute(root)
312
314
313 if absolute:
315 if absolute:
314 return verify_modern_convention(module, root)
316 return verify_modern_convention(module, root)
315 else:
317 else:
316 return verify_stdlib_on_own_line(root)
318 return verify_stdlib_on_own_line(root)
317
319
318 def verify_modern_convention(module, root):
320 def verify_modern_convention(module, root):
319 """Verify a file conforms to the modern import convention rules.
321 """Verify a file conforms to the modern import convention rules.
320
322
321 The rules of the modern convention are:
323 The rules of the modern convention are:
322
324
323 * Ordering is stdlib followed by local imports. Each group is lexically
325 * Ordering is stdlib followed by local imports. Each group is lexically
324 sorted.
326 sorted.
325 * Importing multiple modules via "import X, Y" is not allowed: use
327 * Importing multiple modules via "import X, Y" is not allowed: use
326 separate import statements.
328 separate import statements.
327 * Importing multiple modules via "from X import ..." is allowed if using
329 * Importing multiple modules via "from X import ..." is allowed if using
328 parenthesis and one entry per line.
330 parenthesis and one entry per line.
329 * Only 1 relative import statement per import level ("from .", "from ..")
331 * Only 1 relative import statement per import level ("from .", "from ..")
330 is allowed.
332 is allowed.
331 * Relative imports from higher levels must occur before lower levels. e.g.
333 * Relative imports from higher levels must occur before lower levels. e.g.
332 "from .." must be before "from .".
334 "from .." must be before "from .".
333 * Imports from peer packages should use relative import (e.g. do not
335 * Imports from peer packages should use relative import (e.g. do not
334 "import mercurial.foo" from a "mercurial.*" module).
336 "import mercurial.foo" from a "mercurial.*" module).
335 * Symbols can only be imported from specific modules (see
337 * Symbols can only be imported from specific modules (see
336 `allowsymbolimports`). For other modules, first import the module then
338 `allowsymbolimports`). For other modules, first import the module then
337 assign the symbol to a module-level variable. In addition, these imports
339 assign the symbol to a module-level variable. In addition, these imports
338 must be performed before other relative imports. This rule only
340 must be performed before other relative imports. This rule only
339 applies to import statements outside of any blocks.
341 applies to import statements outside of any blocks.
340 * Relative imports from the standard library are not allowed.
342 * Relative imports from the standard library are not allowed.
341 * Certain modules must be aliased to alternate names to avoid aliasing
343 * Certain modules must be aliased to alternate names to avoid aliasing
342 and readability problems. See `requirealias`.
344 and readability problems. See `requirealias`.
343 """
345 """
344 topmodule = module.split('.')[0]
346 topmodule = module.split('.')[0]
345
347
346 # Whether a local/non-stdlib import has been performed.
348 # Whether a local/non-stdlib import has been performed.
347 seenlocal = False
349 seenlocal = False
348 # Whether a relative, non-symbol import has been seen.
350 # Whether a relative, non-symbol import has been seen.
349 seennonsymbolrelative = False
351 seennonsymbolrelative = False
350 # The last name to be imported (for sorting).
352 # The last name to be imported (for sorting).
351 lastname = None
353 lastname = None
352 # Relative import levels encountered so far.
354 # Relative import levels encountered so far.
353 seenlevels = set()
355 seenlevels = set()
354
356
355 for node in ast.walk(root):
357 for node in ast.walk(root):
356 if isinstance(node, ast.Import):
358 if isinstance(node, ast.Import):
357 # Disallow "import foo, bar" and require separate imports
359 # Disallow "import foo, bar" and require separate imports
358 # for each module.
360 # for each module.
359 if len(node.names) > 1:
361 if len(node.names) > 1:
360 yield 'multiple imported names: %s' % ', '.join(
362 yield 'multiple imported names: %s' % ', '.join(
361 n.name for n in node.names)
363 n.name for n in node.names)
362
364
363 name = node.names[0].name
365 name = node.names[0].name
364 asname = node.names[0].asname
366 asname = node.names[0].asname
365
367
366 # Ignore sorting rules on imports inside blocks.
368 # Ignore sorting rules on imports inside blocks.
367 if node.col_offset == 0:
369 if node.col_offset == 0:
368 if lastname and name < lastname:
370 if lastname and name < lastname:
369 yield 'imports not lexically sorted: %s < %s' % (
371 yield 'imports not lexically sorted: %s < %s' % (
370 name, lastname)
372 name, lastname)
371
373
372 lastname = name
374 lastname = name
373
375
374 # stdlib imports should be before local imports.
376 # stdlib imports should be before local imports.
375 stdlib = name in stdlib_modules
377 stdlib = name in stdlib_modules
376 if stdlib and seenlocal and node.col_offset == 0:
378 if stdlib and seenlocal and node.col_offset == 0:
377 yield 'stdlib import follows local import: %s' % name
379 yield 'stdlib import follows local import: %s' % name
378
380
379 if not stdlib:
381 if not stdlib:
380 seenlocal = True
382 seenlocal = True
381
383
382 # Import of sibling modules should use relative imports.
384 # Import of sibling modules should use relative imports.
383 topname = name.split('.')[0]
385 topname = name.split('.')[0]
384 if topname == topmodule:
386 if topname == topmodule:
385 yield 'import should be relative: %s' % name
387 yield 'import should be relative: %s' % name
386
388
387 if name in requirealias and asname != requirealias[name]:
389 if name in requirealias and asname != requirealias[name]:
388 yield '%s module must be "as" aliased to %s' % (
390 yield '%s module must be "as" aliased to %s' % (
389 name, requirealias[name])
391 name, requirealias[name])
390
392
391 elif isinstance(node, ast.ImportFrom):
393 elif isinstance(node, ast.ImportFrom):
392 # Resolve the full imported module name.
394 # Resolve the full imported module name.
393 if node.level > 0:
395 if node.level > 0:
394 fullname = '.'.join(module.split('.')[:-node.level])
396 fullname = '.'.join(module.split('.')[:-node.level])
395 if node.module:
397 if node.module:
396 fullname += '.%s' % node.module
398 fullname += '.%s' % node.module
397 else:
399 else:
398 assert node.module
400 assert node.module
399 fullname = node.module
401 fullname = node.module
400
402
401 topname = fullname.split('.')[0]
403 topname = fullname.split('.')[0]
402 if topname == topmodule:
404 if topname == topmodule:
403 yield 'import should be relative: %s' % fullname
405 yield 'import should be relative: %s' % fullname
404
406
405 # __future__ is special since it needs to come first and use
407 # __future__ is special since it needs to come first and use
406 # symbol import.
408 # symbol import.
407 if fullname != '__future__':
409 if fullname != '__future__':
408 if not fullname or fullname in stdlib_modules:
410 if not fullname or fullname in stdlib_modules:
409 yield 'relative import of stdlib module'
411 yield 'relative import of stdlib module'
410 else:
412 else:
411 seenlocal = True
413 seenlocal = True
412
414
413 # Direct symbol import is only allowed from certain modules and
415 # Direct symbol import is only allowed from certain modules and
414 # must occur before non-symbol imports.
416 # must occur before non-symbol imports.
415 if node.module and node.col_offset == 0:
417 if node.module and node.col_offset == 0:
416 if fullname not in allowsymbolimports:
418 if fullname not in allowsymbolimports:
417 yield 'direct symbol import from %s' % fullname
419 yield 'direct symbol import from %s' % fullname
418
420
419 if seennonsymbolrelative:
421 if seennonsymbolrelative:
420 yield ('symbol import follows non-symbol import: %s' %
422 yield ('symbol import follows non-symbol import: %s' %
421 fullname)
423 fullname)
422
424
423 if not node.module:
425 if not node.module:
424 assert node.level
426 assert node.level
425 seennonsymbolrelative = True
427 seennonsymbolrelative = True
426
428
427 # Only allow 1 group per level.
429 # Only allow 1 group per level.
428 if node.level in seenlevels and node.col_offset == 0:
430 if node.level in seenlevels and node.col_offset == 0:
429 yield 'multiple "from %s import" statements' % (
431 yield 'multiple "from %s import" statements' % (
430 '.' * node.level)
432 '.' * node.level)
431
433
432 # Higher-level groups come before lower-level groups.
434 # Higher-level groups come before lower-level groups.
433 if any(node.level > l for l in seenlevels):
435 if any(node.level > l for l in seenlevels):
434 yield 'higher-level import should come first: %s' % (
436 yield 'higher-level import should come first: %s' % (
435 fullname)
437 fullname)
436
438
437 seenlevels.add(node.level)
439 seenlevels.add(node.level)
438
440
439 # Entries in "from .X import ( ... )" lists must be lexically
441 # Entries in "from .X import ( ... )" lists must be lexically
440 # sorted.
442 # sorted.
441 lastentryname = None
443 lastentryname = None
442
444
443 for n in node.names:
445 for n in node.names:
444 if lastentryname and n.name < lastentryname:
446 if lastentryname and n.name < lastentryname:
445 yield 'imports from %s not lexically sorted: %s < %s' % (
447 yield 'imports from %s not lexically sorted: %s < %s' % (
446 fullname, n.name, lastentryname)
448 fullname, n.name, lastentryname)
447
449
448 lastentryname = n.name
450 lastentryname = n.name
449
451
450 if n.name in requirealias and n.asname != requirealias[n.name]:
452 if n.name in requirealias and n.asname != requirealias[n.name]:
451 yield '%s from %s must be "as" aliased to %s' % (
453 yield '%s from %s must be "as" aliased to %s' % (
452 n.name, fullname, requirealias[n.name])
454 n.name, fullname, requirealias[n.name])
453
455
454 def verify_stdlib_on_own_line(root):
456 def verify_stdlib_on_own_line(root):
455 """Given some python source, verify that stdlib imports are done
457 """Given some python source, verify that stdlib imports are done
456 in separate statements from relative local module imports.
458 in separate statements from relative local module imports.
457
459
458 Observing this limitation is important as it works around an
460 Observing this limitation is important as it works around an
459 annoying lib2to3 bug in relative import rewrites:
461 annoying lib2to3 bug in relative import rewrites:
460 http://bugs.python.org/issue19510.
462 http://bugs.python.org/issue19510.
461
463
462 >>> list(verify_stdlib_on_own_line(ast.parse('import sys, foo')))
464 >>> list(verify_stdlib_on_own_line(ast.parse('import sys, foo')))
463 ['mixed imports\\n stdlib: sys\\n relative: foo']
465 ['mixed imports\\n stdlib: sys\\n relative: foo']
464 >>> list(verify_stdlib_on_own_line(ast.parse('import sys, os')))
466 >>> list(verify_stdlib_on_own_line(ast.parse('import sys, os')))
465 []
467 []
466 >>> list(verify_stdlib_on_own_line(ast.parse('import foo, bar')))
468 >>> list(verify_stdlib_on_own_line(ast.parse('import foo, bar')))
467 []
469 []
468 """
470 """
469 for node in ast.walk(root):
471 for node in ast.walk(root):
470 if isinstance(node, ast.Import):
472 if isinstance(node, ast.Import):
471 from_stdlib = {False: [], True: []}
473 from_stdlib = {False: [], True: []}
472 for n in node.names:
474 for n in node.names:
473 from_stdlib[n.name in stdlib_modules].append(n.name)
475 from_stdlib[n.name in stdlib_modules].append(n.name)
474 if from_stdlib[True] and from_stdlib[False]:
476 if from_stdlib[True] and from_stdlib[False]:
475 yield ('mixed imports\n stdlib: %s\n relative: %s' %
477 yield ('mixed imports\n stdlib: %s\n relative: %s' %
476 (', '.join(sorted(from_stdlib[True])),
478 (', '.join(sorted(from_stdlib[True])),
477 ', '.join(sorted(from_stdlib[False]))))
479 ', '.join(sorted(from_stdlib[False]))))
478
480
479 class CircularImport(Exception):
481 class CircularImport(Exception):
480 pass
482 pass
481
483
482 def checkmod(mod, imports):
484 def checkmod(mod, imports):
483 shortest = {}
485 shortest = {}
484 visit = [[mod]]
486 visit = [[mod]]
485 while visit:
487 while visit:
486 path = visit.pop(0)
488 path = visit.pop(0)
487 for i in sorted(imports.get(path[-1], [])):
489 for i in sorted(imports.get(path[-1], [])):
488 if len(path) < shortest.get(i, 1000):
490 if len(path) < shortest.get(i, 1000):
489 shortest[i] = len(path)
491 shortest[i] = len(path)
490 if i in path:
492 if i in path:
491 if i == path[0]:
493 if i == path[0]:
492 raise CircularImport(path)
494 raise CircularImport(path)
493 continue
495 continue
494 visit.append(path + [i])
496 visit.append(path + [i])
495
497
496 def rotatecycle(cycle):
498 def rotatecycle(cycle):
497 """arrange a cycle so that the lexicographically first module listed first
499 """arrange a cycle so that the lexicographically first module listed first
498
500
499 >>> rotatecycle(['foo', 'bar'])
501 >>> rotatecycle(['foo', 'bar'])
500 ['bar', 'foo', 'bar']
502 ['bar', 'foo', 'bar']
501 """
503 """
502 lowest = min(cycle)
504 lowest = min(cycle)
503 idx = cycle.index(lowest)
505 idx = cycle.index(lowest)
504 return cycle[idx:] + cycle[:idx] + [lowest]
506 return cycle[idx:] + cycle[:idx] + [lowest]
505
507
506 def find_cycles(imports):
508 def find_cycles(imports):
507 """Find cycles in an already-loaded import graph.
509 """Find cycles in an already-loaded import graph.
508
510
509 All module names recorded in `imports` should be absolute one.
511 All module names recorded in `imports` should be absolute one.
510
512
511 >>> imports = {'top.foo': ['top.bar', 'os.path', 'top.qux'],
513 >>> imports = {'top.foo': ['top.bar', 'os.path', 'top.qux'],
512 ... 'top.bar': ['top.baz', 'sys'],
514 ... 'top.bar': ['top.baz', 'sys'],
513 ... 'top.baz': ['top.foo'],
515 ... 'top.baz': ['top.foo'],
514 ... 'top.qux': ['top.foo']}
516 ... 'top.qux': ['top.foo']}
515 >>> print '\\n'.join(sorted(find_cycles(imports)))
517 >>> print '\\n'.join(sorted(find_cycles(imports)))
516 top.bar -> top.baz -> top.foo -> top.bar
518 top.bar -> top.baz -> top.foo -> top.bar
517 top.foo -> top.qux -> top.foo
519 top.foo -> top.qux -> top.foo
518 """
520 """
519 cycles = set()
521 cycles = set()
520 for mod in sorted(imports.iterkeys()):
522 for mod in sorted(imports.iterkeys()):
521 try:
523 try:
522 checkmod(mod, imports)
524 checkmod(mod, imports)
523 except CircularImport as e:
525 except CircularImport as e:
524 cycle = e.args[0]
526 cycle = e.args[0]
525 cycles.add(" -> ".join(rotatecycle(cycle)))
527 cycles.add(" -> ".join(rotatecycle(cycle)))
526 return cycles
528 return cycles
527
529
528 def _cycle_sortkey(c):
530 def _cycle_sortkey(c):
529 return len(c), c
531 return len(c), c
530
532
531 def main(argv):
533 def main(argv):
532 if len(argv) < 2 or (argv[1] == '-' and len(argv) > 2):
534 if len(argv) < 2 or (argv[1] == '-' and len(argv) > 2):
533 print 'Usage: %s {-|file [file] [file] ...}'
535 print 'Usage: %s {-|file [file] [file] ...}'
534 return 1
536 return 1
535 if argv[1] == '-':
537 if argv[1] == '-':
536 argv = argv[:1]
538 argv = argv[:1]
537 argv.extend(l.rstrip() for l in sys.stdin.readlines())
539 argv.extend(l.rstrip() for l in sys.stdin.readlines())
538 localmods = {}
540 localmods = {}
539 used_imports = {}
541 used_imports = {}
540 any_errors = False
542 any_errors = False
541 for source_path in argv[1:]:
543 for source_path in argv[1:]:
542 modname = dotted_name_of_path(source_path, trimpure=True)
544 modname = dotted_name_of_path(source_path, trimpure=True)
543 localmods[modname] = source_path
545 localmods[modname] = source_path
544 for modname, source_path in sorted(localmods.iteritems()):
546 for modname, source_path in sorted(localmods.iteritems()):
545 f = open(source_path)
547 f = open(source_path)
546 src = f.read()
548 src = f.read()
547 used_imports[modname] = sorted(
549 used_imports[modname] = sorted(
548 imported_modules(src, modname, localmods, ignore_nested=True))
550 imported_modules(src, modname, localmods, ignore_nested=True))
549 for error in verify_import_convention(modname, src):
551 for error in verify_import_convention(modname, src):
550 any_errors = True
552 any_errors = True
551 print source_path, error
553 print source_path, error
552 f.close()
554 f.close()
553 cycles = find_cycles(used_imports)
555 cycles = find_cycles(used_imports)
554 if cycles:
556 if cycles:
555 firstmods = set()
557 firstmods = set()
556 for c in sorted(cycles, key=_cycle_sortkey):
558 for c in sorted(cycles, key=_cycle_sortkey):
557 first = c.split()[0]
559 first = c.split()[0]
558 # As a rough cut, ignore any cycle that starts with the
560 # As a rough cut, ignore any cycle that starts with the
559 # same module as some other cycle. Otherwise we see lots
561 # same module as some other cycle. Otherwise we see lots
560 # of cycles that are effectively duplicates.
562 # of cycles that are effectively duplicates.
561 if first in firstmods:
563 if first in firstmods:
562 continue
564 continue
563 print 'Import cycle:', c
565 print 'Import cycle:', c
564 firstmods.add(first)
566 firstmods.add(first)
565 any_errors = True
567 any_errors = True
566 return any_errors != 0
568 return any_errors != 0
567
569
568 if __name__ == '__main__':
570 if __name__ == '__main__':
569 sys.exit(int(main(sys.argv)))
571 sys.exit(int(main(sys.argv)))
General Comments 0
You need to be logged in to leave comments. Login now