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