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