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