Fix an issue where the future import should be first.

This commit is contained in:
Dave Halter
2017-07-21 19:43:49 +02:00
parent 1df06025c2
commit 97b98a1da3
2 changed files with 61 additions and 6 deletions

View File

@@ -15,10 +15,43 @@ class CompressNormalizer(Normalizer):
return leaf.prefix + leaf.value return leaf.prefix + leaf.value
def is_future_import(from_import): def _iter_stmts(scope):
from_names = from_import.get_from_names() """
Iterates over all statements and splits up simple_stmt.
"""
for child in scope.children:
if child.type == 'simple_stmt':
for child2 in child.children:
if child2.type == 'newline' or child2 == ';':
continue
yield child2
else:
yield child
def _is_future_import(import_from):
if import_from.level != 0:
return False
from_names = import_from.get_from_names()
return [n.value for n in from_names] == ['__future__'] return [n.value for n in from_names] == ['__future__']
def _is_future_import_first(import_from):
"""
Checks if the import is the first statement of a file.
"""
found_docstring = False
for stmt in _iter_stmts(import_from.get_root_node()):
if stmt == import_from:
return True
if stmt.type == 'import_from' and _is_future_import(stmt):
continue
if stmt.type == 'string' and not found_docstring:
found_docstring = True
continue
return False
class Context(object): class Context(object):
def __init__(self, node, parent_context=None): def __init__(self, node, parent_context=None):
self.node = node self.node = node
@@ -92,8 +125,8 @@ class ErrorFinder(Normalizer):
yield yield
self._context = context self._context = context
return return
elif node.type == 'import_from' and node.level == 0 \ elif node.type == 'import_from' \
and is_future_import(node): and _is_future_import(node) and not _is_future_import_first(node):
message = "from __future__ imports must occur at the beginning of the file" message = "from __future__ imports must occur at the beginning of the file"
self._add_syntax_error(message, node) self._add_syntax_error(message, node)
elif node.type in _STAR_EXPR_PARENTS: elif node.type in _STAR_EXPR_PARENTS:

View File

@@ -10,8 +10,7 @@ import parso
from parso.python.normalizer import ErrorFinderConfig from parso.python.normalizer import ErrorFinderConfig
def _get_error_list(code, version=None): def _get_error_list(code, version=None):
grammar = parso.load_grammar(version=version) tree = parso.parse(code, version=version)
tree = grammar.parse(code)
config = ErrorFinderConfig() config = ErrorFinderConfig()
return list(tree._get_normalizer_issues(config)) return list(tree._get_normalizer_issues(config))
@@ -148,3 +147,26 @@ def test_statically_nested_blocks():
assert get_error(20) assert get_error(20)
assert get_error(20, add_func=True) assert get_error(20, add_func=True)
def test_future_import_first():
def is_issue(code, *args):
code = code % args
return bool(_get_error_list(code))
i1 = 'from __future__ import division'
i2 = 'from __future__ import absolute_import'
assert not is_issue(i1)
assert not is_issue(i1 + ';' + i2)
assert not is_issue(i1 + '\n' + i2)
assert not is_issue('"";' + i1)
assert not is_issue('"";' + i1)
assert not is_issue('""\n' + i1)
assert not is_issue('""\n%s\n%s', i1, i2)
assert not is_issue('""\n%s;%s', i1, i2)
assert not is_issue('"";%s;%s ', i1, i2)
assert not is_issue('"";%s\n%s ', i1, i2)
assert is_issue('1;' + i1)
assert is_issue('1\n' + i1)
assert is_issue('"";1\n' + i1)
assert is_issue('""\n%s\nfrom x import a\n%s', i1, i2)