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