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