diff --git a/.gitignore b/.gitignore index 71394018..3dea2851 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ .build-*/ README.html __pycache__ +.mypy_cache/ build .coverage coverage.xml diff --git a/autopep8.py b/autopep8.py index 572e4109..a23ffc97 100755 --- a/autopep8.py +++ b/autopep8.py @@ -48,6 +48,7 @@ class documentation for more information. import fnmatch import inspect import io +import itertools import keyword import locale import os @@ -1422,19 +1423,30 @@ def is_string_literal(line): return line and (line[0] == '"' or line[0] == "'") def is_future_import(line): - try: - nodes = ast.parse(line) - except SyntaxError: - return False + nodes = ast.parse(line) for n in nodes.body: if isinstance(n, ast.ImportFrom) and n.module == '__future__': return True return False + def has_future_import(source): + offset = 0 + line = '' + for _, next_line in source: + for line_part in next_line.strip().splitlines(True): + line = line + line_part + try: + return is_future_import(line), offset + except SyntaxError: + continue + offset += 1 + return False, offset + allowed_try_keywords = ('try', 'except', 'else', 'finally') in_docstring = False docstring_kind = '"""' - for cnt, line in enumerate(source): + source_stream = iter(enumerate(source)) + for cnt, line in source_stream: if not in_docstring: m = DOCSTRING_START_REGEX.match(line.lstrip()) if m is not None: @@ -1454,9 +1466,19 @@ def is_future_import(line): elif line.startswith('#'): continue - if line.startswith('import ') or line.startswith('from '): - if cnt == import_line_index or is_future_import(line): + if line.startswith('import '): + if cnt == import_line_index: + continue + return cnt + elif line.startswith('from '): + if cnt == import_line_index: continue + hit, offset = has_future_import( + itertools.chain([(cnt, line)], source_stream) + ) + if hit: + # move to the back + return cnt + offset + 1 return cnt elif pycodestyle.DUNDER_REGEX.match(line): continue diff --git a/test/test_autopep8.py b/test/test_autopep8.py index 59a9084b..401a22e2 100755 --- a/test/test_autopep8.py +++ b/test/test_autopep8.py @@ -2442,6 +2442,56 @@ def test_e402_with_future_import(self): with autopep8_context(line) as result: self.assertEqual(fixed, result) + def test_e401_with_multiline_from_import(self): + line = """\ +from os import ( + chroot +) +def f(): + pass +from a import b +from b import c +from c import d +""" + fixed = """\ +from a import b +from c import d +from b import c +from os import ( + chroot +) + + +def f(): + pass +""" + with autopep8_context(line) as result: + self.assertEqual(fixed, result) + + def test_e402_with_multiline_from_future_import(self): + line = """\ +from __future__ import ( + absolute_import, + print_function +) +def f(): + pass +import os +""" + fixed = """\ +from __future__ import ( + absolute_import, + print_function +) +import os + + +def f(): + pass +""" + with autopep8_context(line) as result: + self.assertEqual(fixed, result) + def test_e402_with_module_doc(self): line1 = '"""\nmodule doc\n"""\na = 1\nimport os\n' fixed1 = '"""\nmodule doc\n"""\nimport os\na = 1\n'