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