##// END OF EJS Templates
import-checker: reset context to verify convention in function scope...
Yuya Nishihara -
r26965:1fa66d3a default
parent child Browse files
Show More
@@ -1,580 +1,598
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2
2
3 import ast
3 import ast
4 import collections
4 import os
5 import os
5 import sys
6 import sys
6
7
7 # Import a minimal set of stdlib modules needed for list_stdlib_modules()
8 # Import a minimal set of stdlib modules needed for list_stdlib_modules()
8 # to work when run from a virtualenv. The modules were chosen empirically
9 # to work when run from a virtualenv. The modules were chosen empirically
9 # so that the return value matches the return value without virtualenv.
10 # so that the return value matches the return value without virtualenv.
10 import BaseHTTPServer
11 import BaseHTTPServer
11 import zlib
12 import zlib
12
13
13 # Whitelist of modules that symbols can be directly imported from.
14 # Whitelist of modules that symbols can be directly imported from.
14 allowsymbolimports = (
15 allowsymbolimports = (
15 '__future__',
16 '__future__',
16 'mercurial.i18n',
17 'mercurial.i18n',
17 'mercurial.node',
18 'mercurial.node',
18 )
19 )
19
20
20 # Modules that must be aliased because they are commonly confused with
21 # Modules that must be aliased because they are commonly confused with
21 # common variables and can create aliasing and readability issues.
22 # common variables and can create aliasing and readability issues.
22 requirealias = {
23 requirealias = {
23 'ui': 'uimod',
24 'ui': 'uimod',
24 }
25 }
25
26
26 def usingabsolute(root):
27 def usingabsolute(root):
27 """Whether absolute imports are being used."""
28 """Whether absolute imports are being used."""
28 if sys.version_info[0] >= 3:
29 if sys.version_info[0] >= 3:
29 return True
30 return True
30
31
31 for node in ast.walk(root):
32 for node in ast.walk(root):
32 if isinstance(node, ast.ImportFrom):
33 if isinstance(node, ast.ImportFrom):
33 if node.module == '__future__':
34 if node.module == '__future__':
34 for n in node.names:
35 for n in node.names:
35 if n.name == 'absolute_import':
36 if n.name == 'absolute_import':
36 return True
37 return True
37
38
38 return False
39 return False
39
40
41 def walklocal(root):
42 """Recursively yield all descendant nodes but not in a different scope"""
43 todo = collections.deque(ast.iter_child_nodes(root))
44 yield root, False
45 while todo:
46 node = todo.popleft()
47 newscope = isinstance(node, ast.FunctionDef)
48 if not newscope:
49 todo.extend(ast.iter_child_nodes(node))
50 yield node, newscope
51
40 def dotted_name_of_path(path, trimpure=False):
52 def dotted_name_of_path(path, trimpure=False):
41 """Given a relative path to a source file, return its dotted module name.
53 """Given a relative path to a source file, return its dotted module name.
42
54
43 >>> dotted_name_of_path('mercurial/error.py')
55 >>> dotted_name_of_path('mercurial/error.py')
44 'mercurial.error'
56 'mercurial.error'
45 >>> dotted_name_of_path('mercurial/pure/parsers.py', trimpure=True)
57 >>> dotted_name_of_path('mercurial/pure/parsers.py', trimpure=True)
46 'mercurial.parsers'
58 'mercurial.parsers'
47 >>> dotted_name_of_path('zlibmodule.so')
59 >>> dotted_name_of_path('zlibmodule.so')
48 'zlib'
60 'zlib'
49 """
61 """
50 parts = path.split('/')
62 parts = path.split('/')
51 parts[-1] = parts[-1].split('.', 1)[0] # remove .py and .so and .ARCH.so
63 parts[-1] = parts[-1].split('.', 1)[0] # remove .py and .so and .ARCH.so
52 if parts[-1].endswith('module'):
64 if parts[-1].endswith('module'):
53 parts[-1] = parts[-1][:-6]
65 parts[-1] = parts[-1][:-6]
54 if trimpure:
66 if trimpure:
55 return '.'.join(p for p in parts if p != 'pure')
67 return '.'.join(p for p in parts if p != 'pure')
56 return '.'.join(parts)
68 return '.'.join(parts)
57
69
58 def fromlocalfunc(modulename, localmods):
70 def fromlocalfunc(modulename, localmods):
59 """Get a function to examine which locally defined module the
71 """Get a function to examine which locally defined module the
60 target source imports via a specified name.
72 target source imports via a specified name.
61
73
62 `modulename` is an `dotted_name_of_path()`-ed source file path,
74 `modulename` is an `dotted_name_of_path()`-ed source file path,
63 which may have `.__init__` at the end of it, of the target source.
75 which may have `.__init__` at the end of it, of the target source.
64
76
65 `localmods` is a dict (or set), of which key is an absolute
77 `localmods` is a dict (or set), of which key is an absolute
66 `dotted_name_of_path()`-ed source file path of locally defined (=
78 `dotted_name_of_path()`-ed source file path of locally defined (=
67 Mercurial specific) modules.
79 Mercurial specific) modules.
68
80
69 This function assumes that module names not existing in
81 This function assumes that module names not existing in
70 `localmods` are from the Python standard library.
82 `localmods` are from the Python standard library.
71
83
72 This function returns the function, which takes `name` argument,
84 This function returns the function, which takes `name` argument,
73 and returns `(absname, dottedpath, hassubmod)` tuple if `name`
85 and returns `(absname, dottedpath, hassubmod)` tuple if `name`
74 matches against locally defined module. Otherwise, it returns
86 matches against locally defined module. Otherwise, it returns
75 False.
87 False.
76
88
77 It is assumed that `name` doesn't have `.__init__`.
89 It is assumed that `name` doesn't have `.__init__`.
78
90
79 `absname` is an absolute module name of specified `name`
91 `absname` is an absolute module name of specified `name`
80 (e.g. "hgext.convert"). This can be used to compose prefix for sub
92 (e.g. "hgext.convert"). This can be used to compose prefix for sub
81 modules or so.
93 modules or so.
82
94
83 `dottedpath` is a `dotted_name_of_path()`-ed source file path
95 `dottedpath` is a `dotted_name_of_path()`-ed source file path
84 (e.g. "hgext.convert.__init__") of `name`. This is used to look
96 (e.g. "hgext.convert.__init__") of `name`. This is used to look
85 module up in `localmods` again.
97 module up in `localmods` again.
86
98
87 `hassubmod` is whether it may have sub modules under it (for
99 `hassubmod` is whether it may have sub modules under it (for
88 convenient, even though this is also equivalent to "absname !=
100 convenient, even though this is also equivalent to "absname !=
89 dottednpath")
101 dottednpath")
90
102
91 >>> localmods = {'foo.__init__': True, 'foo.foo1': True,
103 >>> localmods = {'foo.__init__': True, 'foo.foo1': True,
92 ... 'foo.bar.__init__': True, 'foo.bar.bar1': True,
104 ... 'foo.bar.__init__': True, 'foo.bar.bar1': True,
93 ... 'baz.__init__': True, 'baz.baz1': True }
105 ... 'baz.__init__': True, 'baz.baz1': True }
94 >>> fromlocal = fromlocalfunc('foo.xxx', localmods)
106 >>> fromlocal = fromlocalfunc('foo.xxx', localmods)
95 >>> # relative
107 >>> # relative
96 >>> fromlocal('foo1')
108 >>> fromlocal('foo1')
97 ('foo.foo1', 'foo.foo1', False)
109 ('foo.foo1', 'foo.foo1', False)
98 >>> fromlocal('bar')
110 >>> fromlocal('bar')
99 ('foo.bar', 'foo.bar.__init__', True)
111 ('foo.bar', 'foo.bar.__init__', True)
100 >>> fromlocal('bar.bar1')
112 >>> fromlocal('bar.bar1')
101 ('foo.bar.bar1', 'foo.bar.bar1', False)
113 ('foo.bar.bar1', 'foo.bar.bar1', False)
102 >>> # absolute
114 >>> # absolute
103 >>> fromlocal('baz')
115 >>> fromlocal('baz')
104 ('baz', 'baz.__init__', True)
116 ('baz', 'baz.__init__', True)
105 >>> fromlocal('baz.baz1')
117 >>> fromlocal('baz.baz1')
106 ('baz.baz1', 'baz.baz1', False)
118 ('baz.baz1', 'baz.baz1', False)
107 >>> # unknown = maybe standard library
119 >>> # unknown = maybe standard library
108 >>> fromlocal('os')
120 >>> fromlocal('os')
109 False
121 False
110 >>> fromlocal(None, 1)
122 >>> fromlocal(None, 1)
111 ('foo', 'foo.__init__', True)
123 ('foo', 'foo.__init__', True)
112 >>> fromlocal2 = fromlocalfunc('foo.xxx.yyy', localmods)
124 >>> fromlocal2 = fromlocalfunc('foo.xxx.yyy', localmods)
113 >>> fromlocal2(None, 2)
125 >>> fromlocal2(None, 2)
114 ('foo', 'foo.__init__', True)
126 ('foo', 'foo.__init__', True)
115 """
127 """
116 prefix = '.'.join(modulename.split('.')[:-1])
128 prefix = '.'.join(modulename.split('.')[:-1])
117 if prefix:
129 if prefix:
118 prefix += '.'
130 prefix += '.'
119 def fromlocal(name, level=0):
131 def fromlocal(name, level=0):
120 # name is None when relative imports are used.
132 # name is None when relative imports are used.
121 if name is None:
133 if name is None:
122 # If relative imports are used, level must not be absolute.
134 # If relative imports are used, level must not be absolute.
123 assert level > 0
135 assert level > 0
124 candidates = ['.'.join(modulename.split('.')[:-level])]
136 candidates = ['.'.join(modulename.split('.')[:-level])]
125 else:
137 else:
126 # Check relative name first.
138 # Check relative name first.
127 candidates = [prefix + name, name]
139 candidates = [prefix + name, name]
128
140
129 for n in candidates:
141 for n in candidates:
130 if n in localmods:
142 if n in localmods:
131 return (n, n, False)
143 return (n, n, False)
132 dottedpath = n + '.__init__'
144 dottedpath = n + '.__init__'
133 if dottedpath in localmods:
145 if dottedpath in localmods:
134 return (n, dottedpath, True)
146 return (n, dottedpath, True)
135 return False
147 return False
136 return fromlocal
148 return fromlocal
137
149
138 def list_stdlib_modules():
150 def list_stdlib_modules():
139 """List the modules present in the stdlib.
151 """List the modules present in the stdlib.
140
152
141 >>> mods = set(list_stdlib_modules())
153 >>> mods = set(list_stdlib_modules())
142 >>> 'BaseHTTPServer' in mods
154 >>> 'BaseHTTPServer' in mods
143 True
155 True
144
156
145 os.path isn't really a module, so it's missing:
157 os.path isn't really a module, so it's missing:
146
158
147 >>> 'os.path' in mods
159 >>> 'os.path' in mods
148 False
160 False
149
161
150 sys requires special treatment, because it's baked into the
162 sys requires special treatment, because it's baked into the
151 interpreter, but it should still appear:
163 interpreter, but it should still appear:
152
164
153 >>> 'sys' in mods
165 >>> 'sys' in mods
154 True
166 True
155
167
156 >>> 'collections' in mods
168 >>> 'collections' in mods
157 True
169 True
158
170
159 >>> 'cStringIO' in mods
171 >>> 'cStringIO' in mods
160 True
172 True
161 """
173 """
162 for m in sys.builtin_module_names:
174 for m in sys.builtin_module_names:
163 yield m
175 yield m
164 # These modules only exist on windows, but we should always
176 # These modules only exist on windows, but we should always
165 # consider them stdlib.
177 # consider them stdlib.
166 for m in ['msvcrt', '_winreg']:
178 for m in ['msvcrt', '_winreg']:
167 yield m
179 yield m
168 # These get missed too
180 # These get missed too
169 for m in 'ctypes', 'email', 'multiprocessing':
181 for m in 'ctypes', 'email', 'multiprocessing':
170 yield m
182 yield m
171 yield 'builtins' # python3 only
183 yield 'builtins' # python3 only
172 for m in 'fcntl', 'grp', 'pwd', 'termios': # Unix only
184 for m in 'fcntl', 'grp', 'pwd', 'termios': # Unix only
173 yield m
185 yield m
174 stdlib_prefixes = set([sys.prefix, sys.exec_prefix])
186 stdlib_prefixes = set([sys.prefix, sys.exec_prefix])
175 # We need to supplement the list of prefixes for the search to work
187 # We need to supplement the list of prefixes for the search to work
176 # when run from within a virtualenv.
188 # when run from within a virtualenv.
177 for mod in (BaseHTTPServer, zlib):
189 for mod in (BaseHTTPServer, zlib):
178 try:
190 try:
179 # Not all module objects have a __file__ attribute.
191 # Not all module objects have a __file__ attribute.
180 filename = mod.__file__
192 filename = mod.__file__
181 except AttributeError:
193 except AttributeError:
182 continue
194 continue
183 dirname = os.path.dirname(filename)
195 dirname = os.path.dirname(filename)
184 for prefix in stdlib_prefixes:
196 for prefix in stdlib_prefixes:
185 if dirname.startswith(prefix):
197 if dirname.startswith(prefix):
186 # Then this directory is redundant.
198 # Then this directory is redundant.
187 break
199 break
188 else:
200 else:
189 stdlib_prefixes.add(dirname)
201 stdlib_prefixes.add(dirname)
190 for libpath in sys.path:
202 for libpath in sys.path:
191 # We want to walk everything in sys.path that starts with
203 # We want to walk everything in sys.path that starts with
192 # something in stdlib_prefixes. check-code suppressed because
204 # something in stdlib_prefixes. check-code suppressed because
193 # the ast module used by this script implies the availability
205 # the ast module used by this script implies the availability
194 # of any().
206 # of any().
195 if not any(libpath.startswith(p) for p in stdlib_prefixes): # no-py24
207 if not any(libpath.startswith(p) for p in stdlib_prefixes): # no-py24
196 continue
208 continue
197 for top, dirs, files in os.walk(libpath):
209 for top, dirs, files in os.walk(libpath):
198 for i, d in reversed(list(enumerate(dirs))):
210 for i, d in reversed(list(enumerate(dirs))):
199 if (not os.path.exists(os.path.join(top, d, '__init__.py'))
211 if (not os.path.exists(os.path.join(top, d, '__init__.py'))
200 or top == libpath and d in ('hgext', 'mercurial')):
212 or top == libpath and d in ('hgext', 'mercurial')):
201 del dirs[i]
213 del dirs[i]
202 for name in files:
214 for name in files:
203 if name == '__init__.py':
215 if name == '__init__.py':
204 continue
216 continue
205 if not name.endswith(('.py', '.so', '.pyc', '.pyo', '.pyd')):
217 if not name.endswith(('.py', '.so', '.pyc', '.pyo', '.pyd')):
206 continue
218 continue
207 full_path = os.path.join(top, name)
219 full_path = os.path.join(top, name)
208 rel_path = full_path[len(libpath) + 1:]
220 rel_path = full_path[len(libpath) + 1:]
209 mod = dotted_name_of_path(rel_path)
221 mod = dotted_name_of_path(rel_path)
210 yield mod
222 yield mod
211
223
212 stdlib_modules = set(list_stdlib_modules())
224 stdlib_modules = set(list_stdlib_modules())
213
225
214 def imported_modules(source, modulename, localmods, ignore_nested=False):
226 def imported_modules(source, modulename, localmods, ignore_nested=False):
215 """Given the source of a file as a string, yield the names
227 """Given the source of a file as a string, yield the names
216 imported by that file.
228 imported by that file.
217
229
218 Args:
230 Args:
219 source: The python source to examine as a string.
231 source: The python source to examine as a string.
220 modulename: of specified python source (may have `__init__`)
232 modulename: of specified python source (may have `__init__`)
221 localmods: dict of locally defined module names (may have `__init__`)
233 localmods: dict of locally defined module names (may have `__init__`)
222 ignore_nested: If true, import statements that do not start in
234 ignore_nested: If true, import statements that do not start in
223 column zero will be ignored.
235 column zero will be ignored.
224
236
225 Returns:
237 Returns:
226 A list of absolute module names imported by the given source.
238 A list of absolute module names imported by the given source.
227
239
228 >>> modulename = 'foo.xxx'
240 >>> modulename = 'foo.xxx'
229 >>> localmods = {'foo.__init__': True,
241 >>> localmods = {'foo.__init__': True,
230 ... 'foo.foo1': True, 'foo.foo2': True,
242 ... 'foo.foo1': True, 'foo.foo2': True,
231 ... 'foo.bar.__init__': True, 'foo.bar.bar1': True,
243 ... 'foo.bar.__init__': True, 'foo.bar.bar1': True,
232 ... 'baz.__init__': True, 'baz.baz1': True }
244 ... 'baz.__init__': True, 'baz.baz1': True }
233 >>> # standard library (= not locally defined ones)
245 >>> # standard library (= not locally defined ones)
234 >>> sorted(imported_modules(
246 >>> sorted(imported_modules(
235 ... 'from stdlib1 import foo, bar; import stdlib2',
247 ... 'from stdlib1 import foo, bar; import stdlib2',
236 ... modulename, localmods))
248 ... modulename, localmods))
237 []
249 []
238 >>> # relative importing
250 >>> # relative importing
239 >>> sorted(imported_modules(
251 >>> sorted(imported_modules(
240 ... 'import foo1; from bar import bar1',
252 ... 'import foo1; from bar import bar1',
241 ... modulename, localmods))
253 ... modulename, localmods))
242 ['foo.bar.bar1', 'foo.foo1']
254 ['foo.bar.bar1', 'foo.foo1']
243 >>> sorted(imported_modules(
255 >>> sorted(imported_modules(
244 ... 'from bar.bar1 import name1, name2, name3',
256 ... 'from bar.bar1 import name1, name2, name3',
245 ... modulename, localmods))
257 ... modulename, localmods))
246 ['foo.bar.bar1']
258 ['foo.bar.bar1']
247 >>> # absolute importing
259 >>> # absolute importing
248 >>> sorted(imported_modules(
260 >>> sorted(imported_modules(
249 ... 'from baz import baz1, name1',
261 ... 'from baz import baz1, name1',
250 ... modulename, localmods))
262 ... modulename, localmods))
251 ['baz.__init__', 'baz.baz1']
263 ['baz.__init__', 'baz.baz1']
252 >>> # mixed importing, even though it shouldn't be recommended
264 >>> # mixed importing, even though it shouldn't be recommended
253 >>> sorted(imported_modules(
265 >>> sorted(imported_modules(
254 ... 'import stdlib, foo1, baz',
266 ... 'import stdlib, foo1, baz',
255 ... modulename, localmods))
267 ... modulename, localmods))
256 ['baz.__init__', 'foo.foo1']
268 ['baz.__init__', 'foo.foo1']
257 >>> # ignore_nested
269 >>> # ignore_nested
258 >>> sorted(imported_modules(
270 >>> sorted(imported_modules(
259 ... '''import foo
271 ... '''import foo
260 ... def wat():
272 ... def wat():
261 ... import bar
273 ... import bar
262 ... ''', modulename, localmods))
274 ... ''', modulename, localmods))
263 ['foo.__init__', 'foo.bar.__init__']
275 ['foo.__init__', 'foo.bar.__init__']
264 >>> sorted(imported_modules(
276 >>> sorted(imported_modules(
265 ... '''import foo
277 ... '''import foo
266 ... def wat():
278 ... def wat():
267 ... import bar
279 ... import bar
268 ... ''', modulename, localmods, ignore_nested=True))
280 ... ''', modulename, localmods, ignore_nested=True))
269 ['foo.__init__']
281 ['foo.__init__']
270 """
282 """
271 fromlocal = fromlocalfunc(modulename, localmods)
283 fromlocal = fromlocalfunc(modulename, localmods)
272 for node in ast.walk(ast.parse(source)):
284 for node in ast.walk(ast.parse(source)):
273 if ignore_nested and getattr(node, 'col_offset', 0) > 0:
285 if ignore_nested and getattr(node, 'col_offset', 0) > 0:
274 continue
286 continue
275 if isinstance(node, ast.Import):
287 if isinstance(node, ast.Import):
276 for n in node.names:
288 for n in node.names:
277 found = fromlocal(n.name)
289 found = fromlocal(n.name)
278 if not found:
290 if not found:
279 # this should import standard library
291 # this should import standard library
280 continue
292 continue
281 yield found[1]
293 yield found[1]
282 elif isinstance(node, ast.ImportFrom):
294 elif isinstance(node, ast.ImportFrom):
283 found = fromlocal(node.module, node.level)
295 found = fromlocal(node.module, node.level)
284 if not found:
296 if not found:
285 # this should import standard library
297 # this should import standard library
286 continue
298 continue
287
299
288 absname, dottedpath, hassubmod = found
300 absname, dottedpath, hassubmod = found
289 if not hassubmod:
301 if not hassubmod:
290 # "dottedpath" is not a package; must be imported
302 # "dottedpath" is not a package; must be imported
291 yield dottedpath
303 yield dottedpath
292 # examination of "node.names" should be redundant
304 # examination of "node.names" should be redundant
293 # e.g.: from mercurial.node import nullid, nullrev
305 # e.g.: from mercurial.node import nullid, nullrev
294 continue
306 continue
295
307
296 modnotfound = False
308 modnotfound = False
297 prefix = absname + '.'
309 prefix = absname + '.'
298 for n in node.names:
310 for n in node.names:
299 found = fromlocal(prefix + n.name)
311 found = fromlocal(prefix + n.name)
300 if not found:
312 if not found:
301 # this should be a function or a property of "node.module"
313 # this should be a function or a property of "node.module"
302 modnotfound = True
314 modnotfound = True
303 continue
315 continue
304 yield found[1]
316 yield found[1]
305 if modnotfound:
317 if modnotfound:
306 # "dottedpath" is a package, but imported because of non-module
318 # "dottedpath" is a package, but imported because of non-module
307 # lookup
319 # lookup
308 yield dottedpath
320 yield dottedpath
309
321
310 def verify_import_convention(module, source):
322 def verify_import_convention(module, source):
311 """Verify imports match our established coding convention.
323 """Verify imports match our established coding convention.
312
324
313 We have 2 conventions: legacy and modern. The modern convention is in
325 We have 2 conventions: legacy and modern. The modern convention is in
314 effect when using absolute imports.
326 effect when using absolute imports.
315
327
316 The legacy convention only looks for mixed imports. The modern convention
328 The legacy convention only looks for mixed imports. The modern convention
317 is much more thorough.
329 is much more thorough.
318 """
330 """
319 root = ast.parse(source)
331 root = ast.parse(source)
320 absolute = usingabsolute(root)
332 absolute = usingabsolute(root)
321
333
322 if absolute:
334 if absolute:
323 return verify_modern_convention(module, root)
335 return verify_modern_convention(module, root)
324 else:
336 else:
325 return verify_stdlib_on_own_line(root)
337 return verify_stdlib_on_own_line(root)
326
338
327 def verify_modern_convention(module, root):
339 def verify_modern_convention(module, root, root_col_offset=0):
328 """Verify a file conforms to the modern import convention rules.
340 """Verify a file conforms to the modern import convention rules.
329
341
330 The rules of the modern convention are:
342 The rules of the modern convention are:
331
343
332 * Ordering is stdlib followed by local imports. Each group is lexically
344 * Ordering is stdlib followed by local imports. Each group is lexically
333 sorted.
345 sorted.
334 * Importing multiple modules via "import X, Y" is not allowed: use
346 * Importing multiple modules via "import X, Y" is not allowed: use
335 separate import statements.
347 separate import statements.
336 * Importing multiple modules via "from X import ..." is allowed if using
348 * Importing multiple modules via "from X import ..." is allowed if using
337 parenthesis and one entry per line.
349 parenthesis and one entry per line.
338 * Only 1 relative import statement per import level ("from .", "from ..")
350 * Only 1 relative import statement per import level ("from .", "from ..")
339 is allowed.
351 is allowed.
340 * Relative imports from higher levels must occur before lower levels. e.g.
352 * Relative imports from higher levels must occur before lower levels. e.g.
341 "from .." must be before "from .".
353 "from .." must be before "from .".
342 * Imports from peer packages should use relative import (e.g. do not
354 * Imports from peer packages should use relative import (e.g. do not
343 "import mercurial.foo" from a "mercurial.*" module).
355 "import mercurial.foo" from a "mercurial.*" module).
344 * Symbols can only be imported from specific modules (see
356 * Symbols can only be imported from specific modules (see
345 `allowsymbolimports`). For other modules, first import the module then
357 `allowsymbolimports`). For other modules, first import the module then
346 assign the symbol to a module-level variable. In addition, these imports
358 assign the symbol to a module-level variable. In addition, these imports
347 must be performed before other relative imports. This rule only
359 must be performed before other relative imports. This rule only
348 applies to import statements outside of any blocks.
360 applies to import statements outside of any blocks.
349 * Relative imports from the standard library are not allowed.
361 * Relative imports from the standard library are not allowed.
350 * Certain modules must be aliased to alternate names to avoid aliasing
362 * Certain modules must be aliased to alternate names to avoid aliasing
351 and readability problems. See `requirealias`.
363 and readability problems. See `requirealias`.
352 """
364 """
353 topmodule = module.split('.')[0]
365 topmodule = module.split('.')[0]
354
366
355 # Whether a local/non-stdlib import has been performed.
367 # Whether a local/non-stdlib import has been performed.
356 seenlocal = False
368 seenlocal = False
357 # Whether a relative, non-symbol import has been seen.
369 # Whether a relative, non-symbol import has been seen.
358 seennonsymbolrelative = False
370 seennonsymbolrelative = False
359 # The last name to be imported (for sorting).
371 # The last name to be imported (for sorting).
360 lastname = None
372 lastname = None
361 # Relative import levels encountered so far.
373 # Relative import levels encountered so far.
362 seenlevels = set()
374 seenlevels = set()
363
375
364 for node in ast.walk(root):
376 for node, newscope in walklocal(root):
365 def msg(fmt, *args):
377 def msg(fmt, *args):
366 return (fmt % args, node.lineno)
378 return (fmt % args, node.lineno)
367 if isinstance(node, ast.Import):
379 if newscope:
380 # Check for local imports in function
381 for r in verify_modern_convention(module, node,
382 node.col_offset + 4):
383 yield r
384 elif isinstance(node, ast.Import):
368 # Disallow "import foo, bar" and require separate imports
385 # Disallow "import foo, bar" and require separate imports
369 # for each module.
386 # for each module.
370 if len(node.names) > 1:
387 if len(node.names) > 1:
371 yield msg('multiple imported names: %s',
388 yield msg('multiple imported names: %s',
372 ', '.join(n.name for n in node.names))
389 ', '.join(n.name for n in node.names))
373
390
374 name = node.names[0].name
391 name = node.names[0].name
375 asname = node.names[0].asname
392 asname = node.names[0].asname
376
393
377 # Ignore sorting rules on imports inside blocks.
394 # Ignore sorting rules on imports inside blocks.
378 if node.col_offset == 0:
395 if node.col_offset == root_col_offset:
379 if lastname and name < lastname:
396 if lastname and name < lastname:
380 yield msg('imports not lexically sorted: %s < %s',
397 yield msg('imports not lexically sorted: %s < %s',
381 name, lastname)
398 name, lastname)
382
399
383 lastname = name
400 lastname = name
384
401
385 # stdlib imports should be before local imports.
402 # stdlib imports should be before local imports.
386 stdlib = name in stdlib_modules
403 stdlib = name in stdlib_modules
387 if stdlib and seenlocal and node.col_offset == 0:
404 if stdlib and seenlocal and node.col_offset == root_col_offset:
388 yield msg('stdlib import follows local import: %s', name)
405 yield msg('stdlib import follows local import: %s', name)
389
406
390 if not stdlib:
407 if not stdlib:
391 seenlocal = True
408 seenlocal = True
392
409
393 # Import of sibling modules should use relative imports.
410 # Import of sibling modules should use relative imports.
394 topname = name.split('.')[0]
411 topname = name.split('.')[0]
395 if topname == topmodule:
412 if topname == topmodule:
396 yield msg('import should be relative: %s', name)
413 yield msg('import should be relative: %s', name)
397
414
398 if name in requirealias and asname != requirealias[name]:
415 if name in requirealias and asname != requirealias[name]:
399 yield msg('%s module must be "as" aliased to %s',
416 yield msg('%s module must be "as" aliased to %s',
400 name, requirealias[name])
417 name, requirealias[name])
401
418
402 elif isinstance(node, ast.ImportFrom):
419 elif isinstance(node, ast.ImportFrom):
403 # Resolve the full imported module name.
420 # Resolve the full imported module name.
404 if node.level > 0:
421 if node.level > 0:
405 fullname = '.'.join(module.split('.')[:-node.level])
422 fullname = '.'.join(module.split('.')[:-node.level])
406 if node.module:
423 if node.module:
407 fullname += '.%s' % node.module
424 fullname += '.%s' % node.module
408 else:
425 else:
409 assert node.module
426 assert node.module
410 fullname = node.module
427 fullname = node.module
411
428
412 topname = fullname.split('.')[0]
429 topname = fullname.split('.')[0]
413 if topname == topmodule:
430 if topname == topmodule:
414 yield msg('import should be relative: %s', fullname)
431 yield msg('import should be relative: %s', fullname)
415
432
416 # __future__ is special since it needs to come first and use
433 # __future__ is special since it needs to come first and use
417 # symbol import.
434 # symbol import.
418 if fullname != '__future__':
435 if fullname != '__future__':
419 if not fullname or fullname in stdlib_modules:
436 if not fullname or fullname in stdlib_modules:
420 yield msg('relative import of stdlib module')
437 yield msg('relative import of stdlib module')
421 else:
438 else:
422 seenlocal = True
439 seenlocal = True
423
440
424 # Direct symbol import is only allowed from certain modules and
441 # Direct symbol import is only allowed from certain modules and
425 # must occur before non-symbol imports.
442 # must occur before non-symbol imports.
426 if node.module and node.col_offset == 0:
443 if node.module and node.col_offset == root_col_offset:
427 if fullname not in allowsymbolimports:
444 if fullname not in allowsymbolimports:
428 yield msg('direct symbol import from %s', fullname)
445 yield msg('direct symbol import from %s', fullname)
429
446
430 if seennonsymbolrelative:
447 if seennonsymbolrelative:
431 yield msg('symbol import follows non-symbol import: %s',
448 yield msg('symbol import follows non-symbol import: %s',
432 fullname)
449 fullname)
433
450
434 if not node.module:
451 if not node.module:
435 assert node.level
452 assert node.level
436 seennonsymbolrelative = True
453 seennonsymbolrelative = True
437
454
438 # Only allow 1 group per level.
455 # Only allow 1 group per level.
439 if node.level in seenlevels and node.col_offset == 0:
456 if (node.level in seenlevels
457 and node.col_offset == root_col_offset):
440 yield msg('multiple "from %s import" statements',
458 yield msg('multiple "from %s import" statements',
441 '.' * node.level)
459 '.' * node.level)
442
460
443 # Higher-level groups come before lower-level groups.
461 # Higher-level groups come before lower-level groups.
444 if any(node.level > l for l in seenlevels):
462 if any(node.level > l for l in seenlevels):
445 yield msg('higher-level import should come first: %s',
463 yield msg('higher-level import should come first: %s',
446 fullname)
464 fullname)
447
465
448 seenlevels.add(node.level)
466 seenlevels.add(node.level)
449
467
450 # Entries in "from .X import ( ... )" lists must be lexically
468 # Entries in "from .X import ( ... )" lists must be lexically
451 # sorted.
469 # sorted.
452 lastentryname = None
470 lastentryname = None
453
471
454 for n in node.names:
472 for n in node.names:
455 if lastentryname and n.name < lastentryname:
473 if lastentryname and n.name < lastentryname:
456 yield msg('imports from %s not lexically sorted: %s < %s',
474 yield msg('imports from %s not lexically sorted: %s < %s',
457 fullname, n.name, lastentryname)
475 fullname, n.name, lastentryname)
458
476
459 lastentryname = n.name
477 lastentryname = n.name
460
478
461 if n.name in requirealias and n.asname != requirealias[n.name]:
479 if n.name in requirealias and n.asname != requirealias[n.name]:
462 yield msg('%s from %s must be "as" aliased to %s',
480 yield msg('%s from %s must be "as" aliased to %s',
463 n.name, fullname, requirealias[n.name])
481 n.name, fullname, requirealias[n.name])
464
482
465 def verify_stdlib_on_own_line(root):
483 def verify_stdlib_on_own_line(root):
466 """Given some python source, verify that stdlib imports are done
484 """Given some python source, verify that stdlib imports are done
467 in separate statements from relative local module imports.
485 in separate statements from relative local module imports.
468
486
469 Observing this limitation is important as it works around an
487 Observing this limitation is important as it works around an
470 annoying lib2to3 bug in relative import rewrites:
488 annoying lib2to3 bug in relative import rewrites:
471 http://bugs.python.org/issue19510.
489 http://bugs.python.org/issue19510.
472
490
473 >>> list(verify_stdlib_on_own_line(ast.parse('import sys, foo')))
491 >>> list(verify_stdlib_on_own_line(ast.parse('import sys, foo')))
474 [('mixed imports\\n stdlib: sys\\n relative: foo', 1)]
492 [('mixed imports\\n stdlib: sys\\n relative: foo', 1)]
475 >>> list(verify_stdlib_on_own_line(ast.parse('import sys, os')))
493 >>> list(verify_stdlib_on_own_line(ast.parse('import sys, os')))
476 []
494 []
477 >>> list(verify_stdlib_on_own_line(ast.parse('import foo, bar')))
495 >>> list(verify_stdlib_on_own_line(ast.parse('import foo, bar')))
478 []
496 []
479 """
497 """
480 for node in ast.walk(root):
498 for node in ast.walk(root):
481 if isinstance(node, ast.Import):
499 if isinstance(node, ast.Import):
482 from_stdlib = {False: [], True: []}
500 from_stdlib = {False: [], True: []}
483 for n in node.names:
501 for n in node.names:
484 from_stdlib[n.name in stdlib_modules].append(n.name)
502 from_stdlib[n.name in stdlib_modules].append(n.name)
485 if from_stdlib[True] and from_stdlib[False]:
503 if from_stdlib[True] and from_stdlib[False]:
486 yield ('mixed imports\n stdlib: %s\n relative: %s' %
504 yield ('mixed imports\n stdlib: %s\n relative: %s' %
487 (', '.join(sorted(from_stdlib[True])),
505 (', '.join(sorted(from_stdlib[True])),
488 ', '.join(sorted(from_stdlib[False]))), node.lineno)
506 ', '.join(sorted(from_stdlib[False]))), node.lineno)
489
507
490 class CircularImport(Exception):
508 class CircularImport(Exception):
491 pass
509 pass
492
510
493 def checkmod(mod, imports):
511 def checkmod(mod, imports):
494 shortest = {}
512 shortest = {}
495 visit = [[mod]]
513 visit = [[mod]]
496 while visit:
514 while visit:
497 path = visit.pop(0)
515 path = visit.pop(0)
498 for i in sorted(imports.get(path[-1], [])):
516 for i in sorted(imports.get(path[-1], [])):
499 if len(path) < shortest.get(i, 1000):
517 if len(path) < shortest.get(i, 1000):
500 shortest[i] = len(path)
518 shortest[i] = len(path)
501 if i in path:
519 if i in path:
502 if i == path[0]:
520 if i == path[0]:
503 raise CircularImport(path)
521 raise CircularImport(path)
504 continue
522 continue
505 visit.append(path + [i])
523 visit.append(path + [i])
506
524
507 def rotatecycle(cycle):
525 def rotatecycle(cycle):
508 """arrange a cycle so that the lexicographically first module listed first
526 """arrange a cycle so that the lexicographically first module listed first
509
527
510 >>> rotatecycle(['foo', 'bar'])
528 >>> rotatecycle(['foo', 'bar'])
511 ['bar', 'foo', 'bar']
529 ['bar', 'foo', 'bar']
512 """
530 """
513 lowest = min(cycle)
531 lowest = min(cycle)
514 idx = cycle.index(lowest)
532 idx = cycle.index(lowest)
515 return cycle[idx:] + cycle[:idx] + [lowest]
533 return cycle[idx:] + cycle[:idx] + [lowest]
516
534
517 def find_cycles(imports):
535 def find_cycles(imports):
518 """Find cycles in an already-loaded import graph.
536 """Find cycles in an already-loaded import graph.
519
537
520 All module names recorded in `imports` should be absolute one.
538 All module names recorded in `imports` should be absolute one.
521
539
522 >>> imports = {'top.foo': ['top.bar', 'os.path', 'top.qux'],
540 >>> imports = {'top.foo': ['top.bar', 'os.path', 'top.qux'],
523 ... 'top.bar': ['top.baz', 'sys'],
541 ... 'top.bar': ['top.baz', 'sys'],
524 ... 'top.baz': ['top.foo'],
542 ... 'top.baz': ['top.foo'],
525 ... 'top.qux': ['top.foo']}
543 ... 'top.qux': ['top.foo']}
526 >>> print '\\n'.join(sorted(find_cycles(imports)))
544 >>> print '\\n'.join(sorted(find_cycles(imports)))
527 top.bar -> top.baz -> top.foo -> top.bar
545 top.bar -> top.baz -> top.foo -> top.bar
528 top.foo -> top.qux -> top.foo
546 top.foo -> top.qux -> top.foo
529 """
547 """
530 cycles = set()
548 cycles = set()
531 for mod in sorted(imports.iterkeys()):
549 for mod in sorted(imports.iterkeys()):
532 try:
550 try:
533 checkmod(mod, imports)
551 checkmod(mod, imports)
534 except CircularImport as e:
552 except CircularImport as e:
535 cycle = e.args[0]
553 cycle = e.args[0]
536 cycles.add(" -> ".join(rotatecycle(cycle)))
554 cycles.add(" -> ".join(rotatecycle(cycle)))
537 return cycles
555 return cycles
538
556
539 def _cycle_sortkey(c):
557 def _cycle_sortkey(c):
540 return len(c), c
558 return len(c), c
541
559
542 def main(argv):
560 def main(argv):
543 if len(argv) < 2 or (argv[1] == '-' and len(argv) > 2):
561 if len(argv) < 2 or (argv[1] == '-' and len(argv) > 2):
544 print 'Usage: %s {-|file [file] [file] ...}'
562 print 'Usage: %s {-|file [file] [file] ...}'
545 return 1
563 return 1
546 if argv[1] == '-':
564 if argv[1] == '-':
547 argv = argv[:1]
565 argv = argv[:1]
548 argv.extend(l.rstrip() for l in sys.stdin.readlines())
566 argv.extend(l.rstrip() for l in sys.stdin.readlines())
549 localmods = {}
567 localmods = {}
550 used_imports = {}
568 used_imports = {}
551 any_errors = False
569 any_errors = False
552 for source_path in argv[1:]:
570 for source_path in argv[1:]:
553 modname = dotted_name_of_path(source_path, trimpure=True)
571 modname = dotted_name_of_path(source_path, trimpure=True)
554 localmods[modname] = source_path
572 localmods[modname] = source_path
555 for modname, source_path in sorted(localmods.iteritems()):
573 for modname, source_path in sorted(localmods.iteritems()):
556 f = open(source_path)
574 f = open(source_path)
557 src = f.read()
575 src = f.read()
558 used_imports[modname] = sorted(
576 used_imports[modname] = sorted(
559 imported_modules(src, modname, localmods, ignore_nested=True))
577 imported_modules(src, modname, localmods, ignore_nested=True))
560 for error, lineno in verify_import_convention(modname, src):
578 for error, lineno in verify_import_convention(modname, src):
561 any_errors = True
579 any_errors = True
562 print '%s:%d: %s' % (source_path, lineno, error)
580 print '%s:%d: %s' % (source_path, lineno, error)
563 f.close()
581 f.close()
564 cycles = find_cycles(used_imports)
582 cycles = find_cycles(used_imports)
565 if cycles:
583 if cycles:
566 firstmods = set()
584 firstmods = set()
567 for c in sorted(cycles, key=_cycle_sortkey):
585 for c in sorted(cycles, key=_cycle_sortkey):
568 first = c.split()[0]
586 first = c.split()[0]
569 # As a rough cut, ignore any cycle that starts with the
587 # As a rough cut, ignore any cycle that starts with the
570 # same module as some other cycle. Otherwise we see lots
588 # same module as some other cycle. Otherwise we see lots
571 # of cycles that are effectively duplicates.
589 # of cycles that are effectively duplicates.
572 if first in firstmods:
590 if first in firstmods:
573 continue
591 continue
574 print 'Import cycle:', c
592 print 'Import cycle:', c
575 firstmods.add(first)
593 firstmods.add(first)
576 any_errors = True
594 any_errors = True
577 return any_errors != 0
595 return any_errors != 0
578
596
579 if __name__ == '__main__':
597 if __name__ == '__main__':
580 sys.exit(int(main(sys.argv)))
598 sys.exit(int(main(sys.argv)))
@@ -1,122 +1,135
1 #require test-repo
1 #require test-repo
2
2
3 $ import_checker="$TESTDIR"/../contrib/import-checker.py
3 $ import_checker="$TESTDIR"/../contrib/import-checker.py
4
4
5 Run the doctests from the import checker, and make sure
5 Run the doctests from the import checker, and make sure
6 it's working correctly.
6 it's working correctly.
7 $ TERM=dumb
7 $ TERM=dumb
8 $ export TERM
8 $ export TERM
9 $ python -m doctest $import_checker
9 $ python -m doctest $import_checker
10
10
11 Run additional tests for the import checker
11 Run additional tests for the import checker
12
12
13 $ mkdir testpackage
13 $ mkdir testpackage
14
14
15 $ cat > testpackage/multiple.py << EOF
15 $ cat > testpackage/multiple.py << EOF
16 > from __future__ import absolute_import
16 > from __future__ import absolute_import
17 > import os, sys
17 > import os, sys
18 > EOF
18 > EOF
19
19
20 $ cat > testpackage/unsorted.py << EOF
20 $ cat > testpackage/unsorted.py << EOF
21 > from __future__ import absolute_import
21 > from __future__ import absolute_import
22 > import sys
22 > import sys
23 > import os
23 > import os
24 > EOF
24 > EOF
25
25
26 $ cat > testpackage/stdafterlocal.py << EOF
26 $ cat > testpackage/stdafterlocal.py << EOF
27 > from __future__ import absolute_import
27 > from __future__ import absolute_import
28 > from . import unsorted
28 > from . import unsorted
29 > import os
29 > import os
30 > EOF
30 > EOF
31
31
32 $ cat > testpackage/requirerelative.py << EOF
32 $ cat > testpackage/requirerelative.py << EOF
33 > from __future__ import absolute_import
33 > from __future__ import absolute_import
34 > import testpackage.unsorted
34 > import testpackage.unsorted
35 > EOF
35 > EOF
36
36
37 $ cat > testpackage/importalias.py << EOF
37 $ cat > testpackage/importalias.py << EOF
38 > from __future__ import absolute_import
38 > from __future__ import absolute_import
39 > import ui
39 > import ui
40 > EOF
40 > EOF
41
41
42 $ cat > testpackage/relativestdlib.py << EOF
42 $ cat > testpackage/relativestdlib.py << EOF
43 > from __future__ import absolute_import
43 > from __future__ import absolute_import
44 > from .. import os
44 > from .. import os
45 > EOF
45 > EOF
46
46
47 $ cat > testpackage/symbolimport.py << EOF
47 $ cat > testpackage/symbolimport.py << EOF
48 > from __future__ import absolute_import
48 > from __future__ import absolute_import
49 > from .unsorted import foo
49 > from .unsorted import foo
50 > EOF
50 > EOF
51
51
52 $ cat > testpackage/latesymbolimport.py << EOF
52 $ cat > testpackage/latesymbolimport.py << EOF
53 > from __future__ import absolute_import
53 > from __future__ import absolute_import
54 > from . import unsorted
54 > from . import unsorted
55 > from mercurial.node import hex
55 > from mercurial.node import hex
56 > EOF
56 > EOF
57
57
58 $ cat > testpackage/multiplegroups.py << EOF
58 $ cat > testpackage/multiplegroups.py << EOF
59 > from __future__ import absolute_import
59 > from __future__ import absolute_import
60 > from . import unsorted
60 > from . import unsorted
61 > from . import more
61 > from . import more
62 > EOF
62 > EOF
63
63
64 $ mkdir testpackage/subpackage
64 $ mkdir testpackage/subpackage
65 $ cat > testpackage/subpackage/levelpriority.py << EOF
65 $ cat > testpackage/subpackage/levelpriority.py << EOF
66 > from __future__ import absolute_import
66 > from __future__ import absolute_import
67 > from . import foo
67 > from . import foo
68 > from .. import parent
68 > from .. import parent
69 > EOF
69 > EOF
70
70
71 $ touch testpackage/subpackage/foo.py
71 $ touch testpackage/subpackage/foo.py
72 $ cat > testpackage/subpackage/__init__.py << EOF
72 $ cat > testpackage/subpackage/__init__.py << EOF
73 > from __future__ import absolute_import
73 > from __future__ import absolute_import
74 > from . import levelpriority # should not cause cycle
74 > from . import levelpriority # should not cause cycle
75 > EOF
75 > EOF
76
76
77 $ cat > testpackage/subpackage/localimport.py << EOF
78 > from __future__ import absolute_import
79 > from . import foo
80 > def bar():
81 > # should not cause "higher-level import should come first"
82 > from .. import unsorted
83 > # but other errors should be detected
84 > from .. import more
85 > import testpackage.subpackage.levelpriority
86 > EOF
87
77 $ cat > testpackage/sortedentries.py << EOF
88 $ cat > testpackage/sortedentries.py << EOF
78 > from __future__ import absolute_import
89 > from __future__ import absolute_import
79 > from . import (
90 > from . import (
80 > foo,
91 > foo,
81 > bar,
92 > bar,
82 > )
93 > )
83 > EOF
94 > EOF
84
95
85 $ cat > testpackage/importfromalias.py << EOF
96 $ cat > testpackage/importfromalias.py << EOF
86 > from __future__ import absolute_import
97 > from __future__ import absolute_import
87 > from . import ui
98 > from . import ui
88 > EOF
99 > EOF
89
100
90 $ cat > testpackage/importfromrelative.py << EOF
101 $ cat > testpackage/importfromrelative.py << EOF
91 > from __future__ import absolute_import
102 > from __future__ import absolute_import
92 > from testpackage.unsorted import foo
103 > from testpackage.unsorted import foo
93 > EOF
104 > EOF
94
105
95 $ python "$import_checker" testpackage/*.py testpackage/subpackage/*.py
106 $ python "$import_checker" testpackage/*.py testpackage/subpackage/*.py
96 testpackage/importalias.py:2: ui module must be "as" aliased to uimod
107 testpackage/importalias.py:2: ui module must be "as" aliased to uimod
97 testpackage/importfromalias.py:2: ui from testpackage must be "as" aliased to uimod
108 testpackage/importfromalias.py:2: ui from testpackage must be "as" aliased to uimod
98 testpackage/importfromrelative.py:2: import should be relative: testpackage.unsorted
109 testpackage/importfromrelative.py:2: import should be relative: testpackage.unsorted
99 testpackage/importfromrelative.py:2: direct symbol import from testpackage.unsorted
110 testpackage/importfromrelative.py:2: direct symbol import from testpackage.unsorted
100 testpackage/latesymbolimport.py:3: symbol import follows non-symbol import: mercurial.node
111 testpackage/latesymbolimport.py:3: symbol import follows non-symbol import: mercurial.node
101 testpackage/multiple.py:2: multiple imported names: os, sys
112 testpackage/multiple.py:2: multiple imported names: os, sys
102 testpackage/multiplegroups.py:3: multiple "from . import" statements
113 testpackage/multiplegroups.py:3: multiple "from . import" statements
103 testpackage/relativestdlib.py:2: relative import of stdlib module
114 testpackage/relativestdlib.py:2: relative import of stdlib module
104 testpackage/requirerelative.py:2: import should be relative: testpackage.unsorted
115 testpackage/requirerelative.py:2: import should be relative: testpackage.unsorted
105 testpackage/sortedentries.py:2: imports from testpackage not lexically sorted: bar < foo
116 testpackage/sortedentries.py:2: imports from testpackage not lexically sorted: bar < foo
106 testpackage/stdafterlocal.py:3: stdlib import follows local import: os
117 testpackage/stdafterlocal.py:3: stdlib import follows local import: os
107 testpackage/subpackage/levelpriority.py:3: higher-level import should come first: testpackage
118 testpackage/subpackage/levelpriority.py:3: higher-level import should come first: testpackage
119 testpackage/subpackage/localimport.py:7: multiple "from .. import" statements
120 testpackage/subpackage/localimport.py:8: import should be relative: testpackage.subpackage.levelpriority
108 testpackage/symbolimport.py:2: direct symbol import from testpackage.unsorted
121 testpackage/symbolimport.py:2: direct symbol import from testpackage.unsorted
109 testpackage/unsorted.py:3: imports not lexically sorted: os < sys
122 testpackage/unsorted.py:3: imports not lexically sorted: os < sys
110 [1]
123 [1]
111
124
112 $ cd "$TESTDIR"/..
125 $ cd "$TESTDIR"/..
113
126
114 There are a handful of cases here that require renaming a module so it
127 There are a handful of cases here that require renaming a module so it
115 doesn't overlap with a stdlib module name. There are also some cycles
128 doesn't overlap with a stdlib module name. There are also some cycles
116 here that we should still endeavor to fix, and some cycles will be
129 here that we should still endeavor to fix, and some cycles will be
117 hidden by deduplication algorithm in the cycle detector, so fixing
130 hidden by deduplication algorithm in the cycle detector, so fixing
118 these may expose other cycles.
131 these may expose other cycles.
119
132
120 $ hg locate 'mercurial/**.py' 'hgext/**.py' | sed 's-\\-/-g' | python "$import_checker" -
133 $ hg locate 'mercurial/**.py' 'hgext/**.py' | sed 's-\\-/-g' | python "$import_checker" -
121 Import cycle: hgext.largefiles.basestore -> hgext.largefiles.localstore -> hgext.largefiles.basestore
134 Import cycle: hgext.largefiles.basestore -> hgext.largefiles.localstore -> hgext.largefiles.basestore
122 [1]
135 [1]
General Comments 0
You need to be logged in to leave comments. Login now