Show More
@@ -1,6 +1,7 | |||||
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 | |||
@@ -37,6 +38,17 def usingabsolute(root): | |||||
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 | |||
@@ -324,7 +336,7 def verify_import_convention(module, sou | |||||
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: | |
@@ -361,10 +373,15 def verify_modern_convention(module, roo | |||||
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 |
|
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: | |
@@ -375,7 +392,7 def verify_modern_convention(module, roo | |||||
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 == |
|
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) | |
@@ -384,7 +401,7 def verify_modern_convention(module, roo | |||||
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 == |
|
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: | |
@@ -423,7 +440,7 def verify_modern_convention(module, roo | |||||
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 == |
|
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 | |||
@@ -436,7 +453,8 def verify_modern_convention(module, roo | |||||
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 |
|
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 |
@@ -74,6 +74,17 Run additional tests for the import chec | |||||
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 ( | |
@@ -105,6 +116,8 Run additional tests for the import chec | |||||
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] |
General Comments 0
You need to be logged in to leave comments.
Login now