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