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