diff -r 5abba2c92da3 -r 1fa66d3ad28d contrib/import-checker.py --- a/contrib/import-checker.py Sun Nov 01 00:37:22 2015 +0900 +++ b/contrib/import-checker.py Sun Nov 01 17:42:03 2015 +0900 @@ -1,6 +1,7 @@ #!/usr/bin/env python import ast +import collections import os import sys @@ -37,6 +38,17 @@ return False +def walklocal(root): + """Recursively yield all descendant nodes but not in a different scope""" + todo = collections.deque(ast.iter_child_nodes(root)) + yield root, False + while todo: + node = todo.popleft() + newscope = isinstance(node, ast.FunctionDef) + if not newscope: + todo.extend(ast.iter_child_nodes(node)) + yield node, newscope + def dotted_name_of_path(path, trimpure=False): """Given a relative path to a source file, return its dotted module name. @@ -324,7 +336,7 @@ else: return verify_stdlib_on_own_line(root) -def verify_modern_convention(module, root): +def verify_modern_convention(module, root, root_col_offset=0): """Verify a file conforms to the modern import convention rules. The rules of the modern convention are: @@ -361,10 +373,15 @@ # Relative import levels encountered so far. seenlevels = set() - for node in ast.walk(root): + for node, newscope in walklocal(root): def msg(fmt, *args): return (fmt % args, node.lineno) - if isinstance(node, ast.Import): + if newscope: + # Check for local imports in function + for r in verify_modern_convention(module, node, + node.col_offset + 4): + yield r + elif isinstance(node, ast.Import): # Disallow "import foo, bar" and require separate imports # for each module. if len(node.names) > 1: @@ -375,7 +392,7 @@ asname = node.names[0].asname # Ignore sorting rules on imports inside blocks. - if node.col_offset == 0: + if node.col_offset == root_col_offset: if lastname and name < lastname: yield msg('imports not lexically sorted: %s < %s', name, lastname) @@ -384,7 +401,7 @@ # stdlib imports should be before local imports. stdlib = name in stdlib_modules - if stdlib and seenlocal and node.col_offset == 0: + if stdlib and seenlocal and node.col_offset == root_col_offset: yield msg('stdlib import follows local import: %s', name) if not stdlib: @@ -423,7 +440,7 @@ # Direct symbol import is only allowed from certain modules and # must occur before non-symbol imports. - if node.module and node.col_offset == 0: + if node.module and node.col_offset == root_col_offset: if fullname not in allowsymbolimports: yield msg('direct symbol import from %s', fullname) @@ -436,7 +453,8 @@ seennonsymbolrelative = True # Only allow 1 group per level. - if node.level in seenlevels and node.col_offset == 0: + if (node.level in seenlevels + and node.col_offset == root_col_offset): yield msg('multiple "from %s import" statements', '.' * node.level)