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