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