##// END OF EJS Templates
import-checker: guess names of C extension modules...
Yuya Nishihara -
r32509:a025ec43 default
parent child Browse files
Show More
@@ -1,734 +1,744 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):
174 """Populate C extension modules based on pure modules"""
175 newlocalmods = set(localmods)
176 for n in localmods:
177 if n.startswith('mercurial.pure.'):
178 m = n[len('mercurial.pure.'):]
179 newlocalmods.add('mercurial.cext.' + m)
180 newlocalmods.add('mercurial.cffi._' + m)
181 return newlocalmods
182
173 def list_stdlib_modules():
183 def list_stdlib_modules():
174 """List the modules present in the stdlib.
184 """List the modules present in the stdlib.
175
185
176 >>> mods = set(list_stdlib_modules())
186 >>> mods = set(list_stdlib_modules())
177 >>> 'BaseHTTPServer' in mods
187 >>> 'BaseHTTPServer' in mods
178 True
188 True
179
189
180 os.path isn't really a module, so it's missing:
190 os.path isn't really a module, so it's missing:
181
191
182 >>> 'os.path' in mods
192 >>> 'os.path' in mods
183 False
193 False
184
194
185 sys requires special treatment, because it's baked into the
195 sys requires special treatment, because it's baked into the
186 interpreter, but it should still appear:
196 interpreter, but it should still appear:
187
197
188 >>> 'sys' in mods
198 >>> 'sys' in mods
189 True
199 True
190
200
191 >>> 'collections' in mods
201 >>> 'collections' in mods
192 True
202 True
193
203
194 >>> 'cStringIO' in mods
204 >>> 'cStringIO' in mods
195 True
205 True
196
206
197 >>> 'cffi' in mods
207 >>> 'cffi' in mods
198 True
208 True
199 """
209 """
200 for m in sys.builtin_module_names:
210 for m in sys.builtin_module_names:
201 yield m
211 yield m
202 # These modules only exist on windows, but we should always
212 # These modules only exist on windows, but we should always
203 # consider them stdlib.
213 # consider them stdlib.
204 for m in ['msvcrt', '_winreg']:
214 for m in ['msvcrt', '_winreg']:
205 yield m
215 yield m
206 yield 'builtins' # python3 only
216 yield 'builtins' # python3 only
207 for m in 'fcntl', 'grp', 'pwd', 'termios': # Unix only
217 for m in 'fcntl', 'grp', 'pwd', 'termios': # Unix only
208 yield m
218 yield m
209 for m in 'cPickle', 'datetime': # in Python (not C) on PyPy
219 for m in 'cPickle', 'datetime': # in Python (not C) on PyPy
210 yield m
220 yield m
211 for m in ['cffi']:
221 for m in ['cffi']:
212 yield m
222 yield m
213 stdlib_prefixes = {sys.prefix, sys.exec_prefix}
223 stdlib_prefixes = {sys.prefix, sys.exec_prefix}
214 # 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
215 # when run from within a virtualenv.
225 # when run from within a virtualenv.
216 for mod in (BaseHTTPServer, zlib):
226 for mod in (BaseHTTPServer, zlib):
217 try:
227 try:
218 # Not all module objects have a __file__ attribute.
228 # Not all module objects have a __file__ attribute.
219 filename = mod.__file__
229 filename = mod.__file__
220 except AttributeError:
230 except AttributeError:
221 continue
231 continue
222 dirname = os.path.dirname(filename)
232 dirname = os.path.dirname(filename)
223 for prefix in stdlib_prefixes:
233 for prefix in stdlib_prefixes:
224 if dirname.startswith(prefix):
234 if dirname.startswith(prefix):
225 # Then this directory is redundant.
235 # Then this directory is redundant.
226 break
236 break
227 else:
237 else:
228 stdlib_prefixes.add(dirname)
238 stdlib_prefixes.add(dirname)
229 for libpath in sys.path:
239 for libpath in sys.path:
230 # We want to walk everything in sys.path that starts with
240 # We want to walk everything in sys.path that starts with
231 # something in stdlib_prefixes.
241 # something in stdlib_prefixes.
232 if not any(libpath.startswith(p) for p in stdlib_prefixes):
242 if not any(libpath.startswith(p) for p in stdlib_prefixes):
233 continue
243 continue
234 for top, dirs, files in os.walk(libpath):
244 for top, dirs, files in os.walk(libpath):
235 for i, d in reversed(list(enumerate(dirs))):
245 for i, d in reversed(list(enumerate(dirs))):
236 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'))
237 or top == libpath and d in ('hgext', 'mercurial')):
247 or top == libpath and d in ('hgext', 'mercurial')):
238 del dirs[i]
248 del dirs[i]
239 for name in files:
249 for name in files:
240 if not name.endswith(('.py', '.so', '.pyc', '.pyo', '.pyd')):
250 if not name.endswith(('.py', '.so', '.pyc', '.pyo', '.pyd')):
241 continue
251 continue
242 if name.startswith('__init__.py'):
252 if name.startswith('__init__.py'):
243 full_path = top
253 full_path = top
244 else:
254 else:
245 full_path = os.path.join(top, name)
255 full_path = os.path.join(top, name)
246 rel_path = full_path[len(libpath) + 1:]
256 rel_path = full_path[len(libpath) + 1:]
247 mod = dotted_name_of_path(rel_path)
257 mod = dotted_name_of_path(rel_path)
248 yield mod
258 yield mod
249
259
250 stdlib_modules = set(list_stdlib_modules())
260 stdlib_modules = set(list_stdlib_modules())
251
261
252 def imported_modules(source, modulename, f, localmods, ignore_nested=False):
262 def imported_modules(source, modulename, f, localmods, ignore_nested=False):
253 """Given the source of a file as a string, yield the names
263 """Given the source of a file as a string, yield the names
254 imported by that file.
264 imported by that file.
255
265
256 Args:
266 Args:
257 source: The python source to examine as a string.
267 source: The python source to examine as a string.
258 modulename: of specified python source (may have `__init__`)
268 modulename: of specified python source (may have `__init__`)
259 localmods: set of locally defined module names (may have `__init__`)
269 localmods: set of locally defined module names (may have `__init__`)
260 ignore_nested: If true, import statements that do not start in
270 ignore_nested: If true, import statements that do not start in
261 column zero will be ignored.
271 column zero will be ignored.
262
272
263 Returns:
273 Returns:
264 A list of absolute module names imported by the given source.
274 A list of absolute module names imported by the given source.
265
275
266 >>> f = 'foo/xxx.py'
276 >>> f = 'foo/xxx.py'
267 >>> modulename = 'foo.xxx'
277 >>> modulename = 'foo.xxx'
268 >>> localmods = {'foo.__init__': True,
278 >>> localmods = {'foo.__init__': True,
269 ... 'foo.foo1': True, 'foo.foo2': True,
279 ... 'foo.foo1': True, 'foo.foo2': True,
270 ... 'foo.bar.__init__': True, 'foo.bar.bar1': True,
280 ... 'foo.bar.__init__': True, 'foo.bar.bar1': True,
271 ... 'baz.__init__': True, 'baz.baz1': True }
281 ... 'baz.__init__': True, 'baz.baz1': True }
272 >>> # standard library (= not locally defined ones)
282 >>> # standard library (= not locally defined ones)
273 >>> sorted(imported_modules(
283 >>> sorted(imported_modules(
274 ... 'from stdlib1 import foo, bar; import stdlib2',
284 ... 'from stdlib1 import foo, bar; import stdlib2',
275 ... modulename, f, localmods))
285 ... modulename, f, localmods))
276 []
286 []
277 >>> # relative importing
287 >>> # relative importing
278 >>> sorted(imported_modules(
288 >>> sorted(imported_modules(
279 ... 'import foo1; from bar import bar1',
289 ... 'import foo1; from bar import bar1',
280 ... modulename, f, localmods))
290 ... modulename, f, localmods))
281 ['foo.bar.bar1', 'foo.foo1']
291 ['foo.bar.bar1', 'foo.foo1']
282 >>> sorted(imported_modules(
292 >>> sorted(imported_modules(
283 ... 'from bar.bar1 import name1, name2, name3',
293 ... 'from bar.bar1 import name1, name2, name3',
284 ... modulename, f, localmods))
294 ... modulename, f, localmods))
285 ['foo.bar.bar1']
295 ['foo.bar.bar1']
286 >>> # absolute importing
296 >>> # absolute importing
287 >>> sorted(imported_modules(
297 >>> sorted(imported_modules(
288 ... 'from baz import baz1, name1',
298 ... 'from baz import baz1, name1',
289 ... modulename, f, localmods))
299 ... modulename, f, localmods))
290 ['baz.__init__', 'baz.baz1']
300 ['baz.__init__', 'baz.baz1']
291 >>> # mixed importing, even though it shouldn't be recommended
301 >>> # mixed importing, even though it shouldn't be recommended
292 >>> sorted(imported_modules(
302 >>> sorted(imported_modules(
293 ... 'import stdlib, foo1, baz',
303 ... 'import stdlib, foo1, baz',
294 ... modulename, f, localmods))
304 ... modulename, f, localmods))
295 ['baz.__init__', 'foo.foo1']
305 ['baz.__init__', 'foo.foo1']
296 >>> # ignore_nested
306 >>> # ignore_nested
297 >>> sorted(imported_modules(
307 >>> sorted(imported_modules(
298 ... '''import foo
308 ... '''import foo
299 ... def wat():
309 ... def wat():
300 ... import bar
310 ... import bar
301 ... ''', modulename, f, localmods))
311 ... ''', modulename, f, localmods))
302 ['foo.__init__', 'foo.bar.__init__']
312 ['foo.__init__', 'foo.bar.__init__']
303 >>> sorted(imported_modules(
313 >>> sorted(imported_modules(
304 ... '''import foo
314 ... '''import foo
305 ... def wat():
315 ... def wat():
306 ... import bar
316 ... import bar
307 ... ''', modulename, f, localmods, ignore_nested=True))
317 ... ''', modulename, f, localmods, ignore_nested=True))
308 ['foo.__init__']
318 ['foo.__init__']
309 """
319 """
310 fromlocal = fromlocalfunc(modulename, localmods)
320 fromlocal = fromlocalfunc(modulename, localmods)
311 for node in ast.walk(ast.parse(source, f)):
321 for node in ast.walk(ast.parse(source, f)):
312 if ignore_nested and getattr(node, 'col_offset', 0) > 0:
322 if ignore_nested and getattr(node, 'col_offset', 0) > 0:
313 continue
323 continue
314 if isinstance(node, ast.Import):
324 if isinstance(node, ast.Import):
315 for n in node.names:
325 for n in node.names:
316 found = fromlocal(n.name)
326 found = fromlocal(n.name)
317 if not found:
327 if not found:
318 # this should import standard library
328 # this should import standard library
319 continue
329 continue
320 yield found[1]
330 yield found[1]
321 elif isinstance(node, ast.ImportFrom):
331 elif isinstance(node, ast.ImportFrom):
322 found = fromlocal(node.module, node.level)
332 found = fromlocal(node.module, node.level)
323 if not found:
333 if not found:
324 # this should import standard library
334 # this should import standard library
325 continue
335 continue
326
336
327 absname, dottedpath, hassubmod = found
337 absname, dottedpath, hassubmod = found
328 if not hassubmod:
338 if not hassubmod:
329 # "dottedpath" is not a package; must be imported
339 # "dottedpath" is not a package; must be imported
330 yield dottedpath
340 yield dottedpath
331 # examination of "node.names" should be redundant
341 # examination of "node.names" should be redundant
332 # e.g.: from mercurial.node import nullid, nullrev
342 # e.g.: from mercurial.node import nullid, nullrev
333 continue
343 continue
334
344
335 modnotfound = False
345 modnotfound = False
336 prefix = absname + '.'
346 prefix = absname + '.'
337 for n in node.names:
347 for n in node.names:
338 found = fromlocal(prefix + n.name)
348 found = fromlocal(prefix + n.name)
339 if not found:
349 if not found:
340 # this should be a function or a property of "node.module"
350 # this should be a function or a property of "node.module"
341 modnotfound = True
351 modnotfound = True
342 continue
352 continue
343 yield found[1]
353 yield found[1]
344 if modnotfound:
354 if modnotfound:
345 # "dottedpath" is a package, but imported because of non-module
355 # "dottedpath" is a package, but imported because of non-module
346 # lookup
356 # lookup
347 yield dottedpath
357 yield dottedpath
348
358
349 def verify_import_convention(module, source, localmods):
359 def verify_import_convention(module, source, localmods):
350 """Verify imports match our established coding convention.
360 """Verify imports match our established coding convention.
351
361
352 We have 2 conventions: legacy and modern. The modern convention is in
362 We have 2 conventions: legacy and modern. The modern convention is in
353 effect when using absolute imports.
363 effect when using absolute imports.
354
364
355 The legacy convention only looks for mixed imports. The modern convention
365 The legacy convention only looks for mixed imports. The modern convention
356 is much more thorough.
366 is much more thorough.
357 """
367 """
358 root = ast.parse(source)
368 root = ast.parse(source)
359 absolute = usingabsolute(root)
369 absolute = usingabsolute(root)
360
370
361 if absolute:
371 if absolute:
362 return verify_modern_convention(module, root, localmods)
372 return verify_modern_convention(module, root, localmods)
363 else:
373 else:
364 return verify_stdlib_on_own_line(root)
374 return verify_stdlib_on_own_line(root)
365
375
366 def verify_modern_convention(module, root, localmods, root_col_offset=0):
376 def verify_modern_convention(module, root, localmods, root_col_offset=0):
367 """Verify a file conforms to the modern import convention rules.
377 """Verify a file conforms to the modern import convention rules.
368
378
369 The rules of the modern convention are:
379 The rules of the modern convention are:
370
380
371 * Ordering is stdlib followed by local imports. Each group is lexically
381 * Ordering is stdlib followed by local imports. Each group is lexically
372 sorted.
382 sorted.
373 * Importing multiple modules via "import X, Y" is not allowed: use
383 * Importing multiple modules via "import X, Y" is not allowed: use
374 separate import statements.
384 separate import statements.
375 * Importing multiple modules via "from X import ..." is allowed if using
385 * Importing multiple modules via "from X import ..." is allowed if using
376 parenthesis and one entry per line.
386 parenthesis and one entry per line.
377 * Only 1 relative import statement per import level ("from .", "from ..")
387 * Only 1 relative import statement per import level ("from .", "from ..")
378 is allowed.
388 is allowed.
379 * Relative imports from higher levels must occur before lower levels. e.g.
389 * Relative imports from higher levels must occur before lower levels. e.g.
380 "from .." must be before "from .".
390 "from .." must be before "from .".
381 * Imports from peer packages should use relative import (e.g. do not
391 * Imports from peer packages should use relative import (e.g. do not
382 "import mercurial.foo" from a "mercurial.*" module).
392 "import mercurial.foo" from a "mercurial.*" module).
383 * Symbols can only be imported from specific modules (see
393 * Symbols can only be imported from specific modules (see
384 `allowsymbolimports`). For other modules, first import the module then
394 `allowsymbolimports`). For other modules, first import the module then
385 assign the symbol to a module-level variable. In addition, these imports
395 assign the symbol to a module-level variable. In addition, these imports
386 must be performed before other local imports. This rule only
396 must be performed before other local imports. This rule only
387 applies to import statements outside of any blocks.
397 applies to import statements outside of any blocks.
388 * Relative imports from the standard library are not allowed.
398 * Relative imports from the standard library are not allowed.
389 * Certain modules must be aliased to alternate names to avoid aliasing
399 * Certain modules must be aliased to alternate names to avoid aliasing
390 and readability problems. See `requirealias`.
400 and readability problems. See `requirealias`.
391 """
401 """
392 topmodule = module.split('.')[0]
402 topmodule = module.split('.')[0]
393 fromlocal = fromlocalfunc(module, localmods)
403 fromlocal = fromlocalfunc(module, localmods)
394
404
395 # Whether a local/non-stdlib import has been performed.
405 # Whether a local/non-stdlib import has been performed.
396 seenlocal = None
406 seenlocal = None
397 # Whether a local/non-stdlib, non-symbol import has been seen.
407 # Whether a local/non-stdlib, non-symbol import has been seen.
398 seennonsymbollocal = False
408 seennonsymbollocal = False
399 # The last name to be imported (for sorting).
409 # The last name to be imported (for sorting).
400 lastname = None
410 lastname = None
401 laststdlib = None
411 laststdlib = None
402 # Relative import levels encountered so far.
412 # Relative import levels encountered so far.
403 seenlevels = set()
413 seenlevels = set()
404
414
405 for node, newscope in walklocal(root):
415 for node, newscope in walklocal(root):
406 def msg(fmt, *args):
416 def msg(fmt, *args):
407 return (fmt % args, node.lineno)
417 return (fmt % args, node.lineno)
408 if newscope:
418 if newscope:
409 # Check for local imports in function
419 # Check for local imports in function
410 for r in verify_modern_convention(module, node, localmods,
420 for r in verify_modern_convention(module, node, localmods,
411 node.col_offset + 4):
421 node.col_offset + 4):
412 yield r
422 yield r
413 elif isinstance(node, ast.Import):
423 elif isinstance(node, ast.Import):
414 # Disallow "import foo, bar" and require separate imports
424 # Disallow "import foo, bar" and require separate imports
415 # for each module.
425 # for each module.
416 if len(node.names) > 1:
426 if len(node.names) > 1:
417 yield msg('multiple imported names: %s',
427 yield msg('multiple imported names: %s',
418 ', '.join(n.name for n in node.names))
428 ', '.join(n.name for n in node.names))
419
429
420 name = node.names[0].name
430 name = node.names[0].name
421 asname = node.names[0].asname
431 asname = node.names[0].asname
422
432
423 stdlib = name in stdlib_modules
433 stdlib = name in stdlib_modules
424
434
425 # Ignore sorting rules on imports inside blocks.
435 # Ignore sorting rules on imports inside blocks.
426 if node.col_offset == root_col_offset:
436 if node.col_offset == root_col_offset:
427 if lastname and name < lastname and laststdlib == stdlib:
437 if lastname and name < lastname and laststdlib == stdlib:
428 yield msg('imports not lexically sorted: %s < %s',
438 yield msg('imports not lexically sorted: %s < %s',
429 name, lastname)
439 name, lastname)
430
440
431 lastname = name
441 lastname = name
432 laststdlib = stdlib
442 laststdlib = stdlib
433
443
434 # stdlib imports should be before local imports.
444 # stdlib imports should be before local imports.
435 if stdlib and seenlocal and node.col_offset == root_col_offset:
445 if stdlib and seenlocal and node.col_offset == root_col_offset:
436 yield msg('stdlib import "%s" follows local import: %s',
446 yield msg('stdlib import "%s" follows local import: %s',
437 name, seenlocal)
447 name, seenlocal)
438
448
439 if not stdlib:
449 if not stdlib:
440 seenlocal = name
450 seenlocal = name
441
451
442 # Import of sibling modules should use relative imports.
452 # Import of sibling modules should use relative imports.
443 topname = name.split('.')[0]
453 topname = name.split('.')[0]
444 if topname == topmodule:
454 if topname == topmodule:
445 yield msg('import should be relative: %s', name)
455 yield msg('import should be relative: %s', name)
446
456
447 if name in requirealias and asname != requirealias[name]:
457 if name in requirealias and asname != requirealias[name]:
448 yield msg('%s module must be "as" aliased to %s',
458 yield msg('%s module must be "as" aliased to %s',
449 name, requirealias[name])
459 name, requirealias[name])
450
460
451 elif isinstance(node, ast.ImportFrom):
461 elif isinstance(node, ast.ImportFrom):
452 # Resolve the full imported module name.
462 # Resolve the full imported module name.
453 if node.level > 0:
463 if node.level > 0:
454 fullname = '.'.join(module.split('.')[:-node.level])
464 fullname = '.'.join(module.split('.')[:-node.level])
455 if node.module:
465 if node.module:
456 fullname += '.%s' % node.module
466 fullname += '.%s' % node.module
457 else:
467 else:
458 assert node.module
468 assert node.module
459 fullname = node.module
469 fullname = node.module
460
470
461 topname = fullname.split('.')[0]
471 topname = fullname.split('.')[0]
462 if topname == topmodule:
472 if topname == topmodule:
463 yield msg('import should be relative: %s', fullname)
473 yield msg('import should be relative: %s', fullname)
464
474
465 # __future__ is special since it needs to come first and use
475 # __future__ is special since it needs to come first and use
466 # symbol import.
476 # symbol import.
467 if fullname != '__future__':
477 if fullname != '__future__':
468 if not fullname or fullname in stdlib_modules:
478 if not fullname or fullname in stdlib_modules:
469 yield msg('relative import of stdlib module')
479 yield msg('relative import of stdlib module')
470 else:
480 else:
471 seenlocal = fullname
481 seenlocal = fullname
472
482
473 # Direct symbol import is only allowed from certain modules and
483 # Direct symbol import is only allowed from certain modules and
474 # must occur before non-symbol imports.
484 # must occur before non-symbol imports.
475 found = fromlocal(node.module, node.level)
485 found = fromlocal(node.module, node.level)
476 if found and found[2]: # node.module is a package
486 if found and found[2]: # node.module is a package
477 prefix = found[0] + '.'
487 prefix = found[0] + '.'
478 symbols = (n.name for n in node.names
488 symbols = (n.name for n in node.names
479 if not fromlocal(prefix + n.name))
489 if not fromlocal(prefix + n.name))
480 else:
490 else:
481 symbols = (n.name for n in node.names)
491 symbols = (n.name for n in node.names)
482 symbols = [sym for sym in symbols if sym not in directsymbols]
492 symbols = [sym for sym in symbols if sym not in directsymbols]
483 if node.module and node.col_offset == root_col_offset:
493 if node.module and node.col_offset == root_col_offset:
484 if symbols and fullname not in allowsymbolimports:
494 if symbols and fullname not in allowsymbolimports:
485 yield msg('direct symbol import %s from %s',
495 yield msg('direct symbol import %s from %s',
486 ', '.join(symbols), fullname)
496 ', '.join(symbols), fullname)
487
497
488 if symbols and seennonsymbollocal:
498 if symbols and seennonsymbollocal:
489 yield msg('symbol import follows non-symbol import: %s',
499 yield msg('symbol import follows non-symbol import: %s',
490 fullname)
500 fullname)
491 if not symbols and fullname not in stdlib_modules:
501 if not symbols and fullname not in stdlib_modules:
492 seennonsymbollocal = True
502 seennonsymbollocal = True
493
503
494 if not node.module:
504 if not node.module:
495 assert node.level
505 assert node.level
496
506
497 # Only allow 1 group per level.
507 # Only allow 1 group per level.
498 if (node.level in seenlevels
508 if (node.level in seenlevels
499 and node.col_offset == root_col_offset):
509 and node.col_offset == root_col_offset):
500 yield msg('multiple "from %s import" statements',
510 yield msg('multiple "from %s import" statements',
501 '.' * node.level)
511 '.' * node.level)
502
512
503 # Higher-level groups come before lower-level groups.
513 # Higher-level groups come before lower-level groups.
504 if any(node.level > l for l in seenlevels):
514 if any(node.level > l for l in seenlevels):
505 yield msg('higher-level import should come first: %s',
515 yield msg('higher-level import should come first: %s',
506 fullname)
516 fullname)
507
517
508 seenlevels.add(node.level)
518 seenlevels.add(node.level)
509
519
510 # Entries in "from .X import ( ... )" lists must be lexically
520 # Entries in "from .X import ( ... )" lists must be lexically
511 # sorted.
521 # sorted.
512 lastentryname = None
522 lastentryname = None
513
523
514 for n in node.names:
524 for n in node.names:
515 if lastentryname and n.name < lastentryname:
525 if lastentryname and n.name < lastentryname:
516 yield msg('imports from %s not lexically sorted: %s < %s',
526 yield msg('imports from %s not lexically sorted: %s < %s',
517 fullname, n.name, lastentryname)
527 fullname, n.name, lastentryname)
518
528
519 lastentryname = n.name
529 lastentryname = n.name
520
530
521 if n.name in requirealias and n.asname != requirealias[n.name]:
531 if n.name in requirealias and n.asname != requirealias[n.name]:
522 yield msg('%s from %s must be "as" aliased to %s',
532 yield msg('%s from %s must be "as" aliased to %s',
523 n.name, fullname, requirealias[n.name])
533 n.name, fullname, requirealias[n.name])
524
534
525 def verify_stdlib_on_own_line(root):
535 def verify_stdlib_on_own_line(root):
526 """Given some python source, verify that stdlib imports are done
536 """Given some python source, verify that stdlib imports are done
527 in separate statements from relative local module imports.
537 in separate statements from relative local module imports.
528
538
529 >>> list(verify_stdlib_on_own_line(ast.parse('import sys, foo')))
539 >>> list(verify_stdlib_on_own_line(ast.parse('import sys, foo')))
530 [('mixed imports\\n stdlib: sys\\n relative: foo', 1)]
540 [('mixed imports\\n stdlib: sys\\n relative: foo', 1)]
531 >>> list(verify_stdlib_on_own_line(ast.parse('import sys, os')))
541 >>> list(verify_stdlib_on_own_line(ast.parse('import sys, os')))
532 []
542 []
533 >>> list(verify_stdlib_on_own_line(ast.parse('import foo, bar')))
543 >>> list(verify_stdlib_on_own_line(ast.parse('import foo, bar')))
534 []
544 []
535 """
545 """
536 for node in ast.walk(root):
546 for node in ast.walk(root):
537 if isinstance(node, ast.Import):
547 if isinstance(node, ast.Import):
538 from_stdlib = {False: [], True: []}
548 from_stdlib = {False: [], True: []}
539 for n in node.names:
549 for n in node.names:
540 from_stdlib[n.name in stdlib_modules].append(n.name)
550 from_stdlib[n.name in stdlib_modules].append(n.name)
541 if from_stdlib[True] and from_stdlib[False]:
551 if from_stdlib[True] and from_stdlib[False]:
542 yield ('mixed imports\n stdlib: %s\n relative: %s' %
552 yield ('mixed imports\n stdlib: %s\n relative: %s' %
543 (', '.join(sorted(from_stdlib[True])),
553 (', '.join(sorted(from_stdlib[True])),
544 ', '.join(sorted(from_stdlib[False]))), node.lineno)
554 ', '.join(sorted(from_stdlib[False]))), node.lineno)
545
555
546 class CircularImport(Exception):
556 class CircularImport(Exception):
547 pass
557 pass
548
558
549 def checkmod(mod, imports):
559 def checkmod(mod, imports):
550 shortest = {}
560 shortest = {}
551 visit = [[mod]]
561 visit = [[mod]]
552 while visit:
562 while visit:
553 path = visit.pop(0)
563 path = visit.pop(0)
554 for i in sorted(imports.get(path[-1], [])):
564 for i in sorted(imports.get(path[-1], [])):
555 if len(path) < shortest.get(i, 1000):
565 if len(path) < shortest.get(i, 1000):
556 shortest[i] = len(path)
566 shortest[i] = len(path)
557 if i in path:
567 if i in path:
558 if i == path[0]:
568 if i == path[0]:
559 raise CircularImport(path)
569 raise CircularImport(path)
560 continue
570 continue
561 visit.append(path + [i])
571 visit.append(path + [i])
562
572
563 def rotatecycle(cycle):
573 def rotatecycle(cycle):
564 """arrange a cycle so that the lexicographically first module listed first
574 """arrange a cycle so that the lexicographically first module listed first
565
575
566 >>> rotatecycle(['foo', 'bar'])
576 >>> rotatecycle(['foo', 'bar'])
567 ['bar', 'foo', 'bar']
577 ['bar', 'foo', 'bar']
568 """
578 """
569 lowest = min(cycle)
579 lowest = min(cycle)
570 idx = cycle.index(lowest)
580 idx = cycle.index(lowest)
571 return cycle[idx:] + cycle[:idx] + [lowest]
581 return cycle[idx:] + cycle[:idx] + [lowest]
572
582
573 def find_cycles(imports):
583 def find_cycles(imports):
574 """Find cycles in an already-loaded import graph.
584 """Find cycles in an already-loaded import graph.
575
585
576 All module names recorded in `imports` should be absolute one.
586 All module names recorded in `imports` should be absolute one.
577
587
578 >>> from __future__ import print_function
588 >>> from __future__ import print_function
579 >>> imports = {'top.foo': ['top.bar', 'os.path', 'top.qux'],
589 >>> imports = {'top.foo': ['top.bar', 'os.path', 'top.qux'],
580 ... 'top.bar': ['top.baz', 'sys'],
590 ... 'top.bar': ['top.baz', 'sys'],
581 ... 'top.baz': ['top.foo'],
591 ... 'top.baz': ['top.foo'],
582 ... 'top.qux': ['top.foo']}
592 ... 'top.qux': ['top.foo']}
583 >>> print('\\n'.join(sorted(find_cycles(imports))))
593 >>> print('\\n'.join(sorted(find_cycles(imports))))
584 top.bar -> top.baz -> top.foo -> top.bar
594 top.bar -> top.baz -> top.foo -> top.bar
585 top.foo -> top.qux -> top.foo
595 top.foo -> top.qux -> top.foo
586 """
596 """
587 cycles = set()
597 cycles = set()
588 for mod in sorted(imports.keys()):
598 for mod in sorted(imports.keys()):
589 try:
599 try:
590 checkmod(mod, imports)
600 checkmod(mod, imports)
591 except CircularImport as e:
601 except CircularImport as e:
592 cycle = e.args[0]
602 cycle = e.args[0]
593 cycles.add(" -> ".join(rotatecycle(cycle)))
603 cycles.add(" -> ".join(rotatecycle(cycle)))
594 return cycles
604 return cycles
595
605
596 def _cycle_sortkey(c):
606 def _cycle_sortkey(c):
597 return len(c), c
607 return len(c), c
598
608
599 def embedded(f, modname, src):
609 def embedded(f, modname, src):
600 """Extract embedded python code
610 """Extract embedded python code
601
611
602 >>> def test(fn, lines):
612 >>> def test(fn, lines):
603 ... for s, m, f, l in embedded(fn, "example", lines):
613 ... for s, m, f, l in embedded(fn, "example", lines):
604 ... print("%s %s %s" % (m, f, l))
614 ... print("%s %s %s" % (m, f, l))
605 ... print(repr(s))
615 ... print(repr(s))
606 >>> lines = [
616 >>> lines = [
607 ... 'comment',
617 ... 'comment',
608 ... ' >>> from __future__ import print_function',
618 ... ' >>> from __future__ import print_function',
609 ... " >>> ' multiline",
619 ... " >>> ' multiline",
610 ... " ... string'",
620 ... " ... string'",
611 ... ' ',
621 ... ' ',
612 ... 'comment',
622 ... 'comment',
613 ... ' $ cat > foo.py <<EOF',
623 ... ' $ cat > foo.py <<EOF',
614 ... ' > from __future__ import print_function',
624 ... ' > from __future__ import print_function',
615 ... ' > EOF',
625 ... ' > EOF',
616 ... ]
626 ... ]
617 >>> test("example.t", lines)
627 >>> test("example.t", lines)
618 example[2] doctest.py 2
628 example[2] doctest.py 2
619 "from __future__ import print_function\\n' multiline\\nstring'\\n"
629 "from __future__ import print_function\\n' multiline\\nstring'\\n"
620 example[7] foo.py 7
630 example[7] foo.py 7
621 'from __future__ import print_function\\n'
631 'from __future__ import print_function\\n'
622 """
632 """
623 inlinepython = 0
633 inlinepython = 0
624 shpython = 0
634 shpython = 0
625 script = []
635 script = []
626 prefix = 6
636 prefix = 6
627 t = ''
637 t = ''
628 n = 0
638 n = 0
629 for l in src:
639 for l in src:
630 n += 1
640 n += 1
631 if not l.endswith(b'\n'):
641 if not l.endswith(b'\n'):
632 l += b'\n'
642 l += b'\n'
633 if l.startswith(b' >>> '): # python inlines
643 if l.startswith(b' >>> '): # python inlines
634 if shpython:
644 if shpython:
635 print("%s:%d: Parse Error" % (f, n))
645 print("%s:%d: Parse Error" % (f, n))
636 if not inlinepython:
646 if not inlinepython:
637 # We've just entered a Python block.
647 # We've just entered a Python block.
638 inlinepython = n
648 inlinepython = n
639 t = 'doctest.py'
649 t = 'doctest.py'
640 script.append(l[prefix:])
650 script.append(l[prefix:])
641 continue
651 continue
642 if l.startswith(b' ... '): # python inlines
652 if l.startswith(b' ... '): # python inlines
643 script.append(l[prefix:])
653 script.append(l[prefix:])
644 continue
654 continue
645 cat = re.search(r"\$ \s*cat\s*>\s*(\S+\.py)\s*<<\s*EOF", l)
655 cat = re.search(r"\$ \s*cat\s*>\s*(\S+\.py)\s*<<\s*EOF", l)
646 if cat:
656 if cat:
647 if inlinepython:
657 if inlinepython:
648 yield ''.join(script), ("%s[%d]" %
658 yield ''.join(script), ("%s[%d]" %
649 (modname, inlinepython)), t, inlinepython
659 (modname, inlinepython)), t, inlinepython
650 script = []
660 script = []
651 inlinepython = 0
661 inlinepython = 0
652 shpython = n
662 shpython = n
653 t = cat.group(1)
663 t = cat.group(1)
654 continue
664 continue
655 if shpython and l.startswith(b' > '): # sh continuation
665 if shpython and l.startswith(b' > '): # sh continuation
656 if l == b' > EOF\n':
666 if l == b' > EOF\n':
657 yield ''.join(script), ("%s[%d]" %
667 yield ''.join(script), ("%s[%d]" %
658 (modname, shpython)), t, shpython
668 (modname, shpython)), t, shpython
659 script = []
669 script = []
660 shpython = 0
670 shpython = 0
661 else:
671 else:
662 script.append(l[4:])
672 script.append(l[4:])
663 continue
673 continue
664 if inlinepython and l == b' \n':
674 if inlinepython and l == b' \n':
665 yield ''.join(script), ("%s[%d]" %
675 yield ''.join(script), ("%s[%d]" %
666 (modname, inlinepython)), t, inlinepython
676 (modname, inlinepython)), t, inlinepython
667 script = []
677 script = []
668 inlinepython = 0
678 inlinepython = 0
669 continue
679 continue
670
680
671 def sources(f, modname):
681 def sources(f, modname):
672 """Yields possibly multiple sources from a filepath
682 """Yields possibly multiple sources from a filepath
673
683
674 input: filepath, modulename
684 input: filepath, modulename
675 yields: script(string), modulename, filepath, linenumber
685 yields: script(string), modulename, filepath, linenumber
676
686
677 For embedded scripts, the modulename and filepath will be different
687 For embedded scripts, the modulename and filepath will be different
678 from the function arguments. linenumber is an offset relative to
688 from the function arguments. linenumber is an offset relative to
679 the input file.
689 the input file.
680 """
690 """
681 py = False
691 py = False
682 if not f.endswith('.t'):
692 if not f.endswith('.t'):
683 with open(f) as src:
693 with open(f) as src:
684 yield src.read(), modname, f, 0
694 yield src.read(), modname, f, 0
685 py = True
695 py = True
686 if py or f.endswith('.t'):
696 if py or f.endswith('.t'):
687 with open(f) as src:
697 with open(f) as src:
688 for script, modname, t, line in embedded(f, modname, src):
698 for script, modname, t, line in embedded(f, modname, src):
689 yield script, modname, t, line
699 yield script, modname, t, line
690
700
691 def main(argv):
701 def main(argv):
692 if len(argv) < 2 or (argv[1] == '-' and len(argv) > 2):
702 if len(argv) < 2 or (argv[1] == '-' and len(argv) > 2):
693 print('Usage: %s {-|file [file] [file] ...}')
703 print('Usage: %s {-|file [file] [file] ...}')
694 return 1
704 return 1
695 if argv[1] == '-':
705 if argv[1] == '-':
696 argv = argv[:1]
706 argv = argv[:1]
697 argv.extend(l.rstrip() for l in sys.stdin.readlines())
707 argv.extend(l.rstrip() for l in sys.stdin.readlines())
698 localmodpaths = {}
708 localmodpaths = {}
699 used_imports = {}
709 used_imports = {}
700 any_errors = False
710 any_errors = False
701 for source_path in argv[1:]:
711 for source_path in argv[1:]:
702 modname = dotted_name_of_path(source_path)
712 modname = dotted_name_of_path(source_path)
703 localmodpaths[modname] = source_path
713 localmodpaths[modname] = source_path
704 localmods = set(localmodpaths)
714 localmods = populateextmods(localmodpaths)
705 for localmodname, source_path in sorted(localmodpaths.items()):
715 for localmodname, source_path in sorted(localmodpaths.items()):
706 for src, modname, name, line in sources(source_path, localmodname):
716 for src, modname, name, line in sources(source_path, localmodname):
707 try:
717 try:
708 used_imports[modname] = sorted(
718 used_imports[modname] = sorted(
709 imported_modules(src, modname, name, localmods,
719 imported_modules(src, modname, name, localmods,
710 ignore_nested=True))
720 ignore_nested=True))
711 for error, lineno in verify_import_convention(modname, src,
721 for error, lineno in verify_import_convention(modname, src,
712 localmods):
722 localmods):
713 any_errors = True
723 any_errors = True
714 print('%s:%d: %s' % (source_path, lineno + line, error))
724 print('%s:%d: %s' % (source_path, lineno + line, error))
715 except SyntaxError as e:
725 except SyntaxError as e:
716 print('%s:%d: SyntaxError: %s' %
726 print('%s:%d: SyntaxError: %s' %
717 (source_path, e.lineno + line, e))
727 (source_path, e.lineno + line, e))
718 cycles = find_cycles(used_imports)
728 cycles = find_cycles(used_imports)
719 if cycles:
729 if cycles:
720 firstmods = set()
730 firstmods = set()
721 for c in sorted(cycles, key=_cycle_sortkey):
731 for c in sorted(cycles, key=_cycle_sortkey):
722 first = c.split()[0]
732 first = c.split()[0]
723 # As a rough cut, ignore any cycle that starts with the
733 # As a rough cut, ignore any cycle that starts with the
724 # same module as some other cycle. Otherwise we see lots
734 # same module as some other cycle. Otherwise we see lots
725 # of cycles that are effectively duplicates.
735 # of cycles that are effectively duplicates.
726 if first in firstmods:
736 if first in firstmods:
727 continue
737 continue
728 print('Import cycle:', c)
738 print('Import cycle:', c)
729 firstmods.add(first)
739 firstmods.add(first)
730 any_errors = True
740 any_errors = True
731 return any_errors != 0
741 return any_errors != 0
732
742
733 if __name__ == '__main__':
743 if __name__ == '__main__':
734 sys.exit(int(main(sys.argv)))
744 sys.exit(int(main(sys.argv)))
General Comments 0
You need to be logged in to leave comments. Login now