Skip to content

Commit

Permalink
Prevent from * import * to be splitted
Browse files Browse the repository at this point in the history
Closes #7
  • Loading branch information
adhikasp committed Apr 18, 2017
1 parent 5a00d0d commit 23dd99f
Show file tree
Hide file tree
Showing 2 changed files with 144 additions and 13 deletions.
57 changes: 54 additions & 3 deletions autoflake.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@

import ast
import difflib
from collections import defaultdict
import io
import os
import re
Expand Down Expand Up @@ -105,6 +106,15 @@ def unused_import_line_numbers(messages):
if isinstance(message, pyflakes.messages.UnusedImport):
yield message.lineno

def unused_import_module_name(messages):
"""Yield line number and module name of unused imports."""
pattern = r'\'(.+?)\''
for message in messages:
if isinstance(message, pyflakes.messages.UnusedImport):
module_name = re.search(pattern, str(message))
module_name = module_name.group()[1:-1]
if (module_name):
yield (message.lineno, module_name)

def unused_variable_line_numbers(messages):
"""Yield line numbers of unused variables."""
Expand Down Expand Up @@ -203,13 +213,47 @@ def multiline_statement(line, previous_line=''):
return True


def filter_from_import(line, unused_module, debug=False):
"""
Parse and filter ``from something import a, b, c``
Return line without unused import modules, or `pass` if
all of the module in import is unused.
"""
(indentation, imports) = re.split(pattern=r'\bimport\b',
string=line, maxsplit=1)
base_module = re.search(pattern='from\s+([^ ]+)',
string=indentation).group(1)

# Create an imported module list with base module name
# ex ``from a import b, c as d`` -> ``['a.b', 'a.c as d']``
imports = re.split(pattern=r',', string=imports.strip())
imports = [base_module + '.' + x.strip() for x in imports]

# We compare full module name (``a.module`` not `module`) to
# guarantee the exact same module as detected from pyflakes.
filtered_imports = [x.replace(base_module + '.', '')
for x in imports if x not in unused_module]

# All of the import in this statement is unused
if not filtered_imports:
return get_indentation(line) + 'pass' + \
get_line_ending(line)

indentation += 'import '

return indentation + ', '.join(sorted(filtered_imports)) \
+ get_line_ending(line)


def break_up_import(line):
"""Return line with imports on separate lines."""
assert '\\' not in line
assert '(' not in line
assert ')' not in line
assert ';' not in line
assert '#' not in line
assert not re.match(line, r'^\s*from\s')

newline = get_line_ending(line)
if not newline:
Expand Down Expand Up @@ -238,6 +282,9 @@ def filter_code(source, additional_imports=None,

marked_import_line_numbers = frozenset(
unused_import_line_numbers(messages))
marked_unused_module = defaultdict(lambda: [])
for line_number, module_name in unused_import_module_name(messages):
marked_unused_module[line_number].append(module_name)

if remove_unused_variables:
marked_variable_line_numbers = frozenset(
Expand All @@ -253,6 +300,7 @@ def filter_code(source, additional_imports=None,
elif line_number in marked_import_line_numbers:
yield filter_unused_import(
line,
unused_module=marked_unused_module[line_number],
remove_all_unused_imports=remove_all_unused_imports,
imports=imports,
previous_line=previous_line)
Expand All @@ -264,13 +312,16 @@ def filter_code(source, additional_imports=None,
previous_line = line


def filter_unused_import(line, remove_all_unused_imports, imports,
previous_line=''):
def filter_unused_import(line, unused_module, remove_all_unused_imports,
imports, previous_line=''):
"""Return line if used, otherwise return None."""
if multiline_import(line, previous_line):
return line
elif ',' in line:
return break_up_import(line)
if re.match(r'^\s*from\s', line):
return filter_from_import(line, unused_module)
else:
return break_up_import(line)
else:
package = extract_package_name(line)
if not remove_all_unused_imports and package not in imports:
Expand Down
100 changes: 90 additions & 10 deletions test_autoflake.py
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,28 @@ def test_filter_code_with_not_from(self):
''.join(autoflake.filter_code("""\
import frommer
x = 1
""",
remove_all_unused_imports=True)))

def test_filter_code_with_used_from(self):
self.assertEqual(
"""\
import frommer
print(frommer)
""",
''.join(autoflake.filter_code("""\
import frommer
print(frommer)
""",
remove_all_unused_imports=True)))

def test_filter_code_with_ambiguous_from(self):
self.assertEqual(
"""\
pass
""",
''.join(autoflake.filter_code("""\
from frommy import abc, frommy, xyz
""",
remove_all_unused_imports=True)))

Expand Down Expand Up @@ -306,21 +328,35 @@ def test_break_up_import_with_indentation(self):
' import abc\n import math\n import subprocess\n',
autoflake.break_up_import(' import abc, subprocess, math\n'))

def test_break_up_import_with_from(self):
self.assertEqual(
"""\
from foo import abc
from foo import math
from foo import subprocess
""",
autoflake.break_up_import(
' from foo import abc, subprocess, math\n'))

def test_break_up_import_should_do_nothing_on_no_line_ending(self):
self.assertEqual(
'import abc, subprocess, math',
autoflake.break_up_import('import abc, subprocess, math'))

def test_filter_from_import_no_remove(self):
self.assertEqual(
"""\
from foo import abc, math, subprocess\n""",
autoflake.filter_from_import(
' from foo import abc, subprocess, math\n',
unused_module=[]))

def test_filter_from_import_remove_module(self):
self.assertEqual(
"""\
from foo import math, subprocess\n""",
autoflake.filter_from_import(
' from foo import abc, subprocess, math\n',
unused_module=['foo.abc']))

def test_filter_from_import_remove_all(self):
self.assertEqual(
' pass\n',
autoflake.filter_from_import(
' from foo import abc, subprocess, math\n',
unused_module=['foo.abc', 'foo.subprocess',
'foo.math']))

def test_filter_code_should_ignore_multiline_imports(self):
self.assertEqual(
r"""\
Expand Down Expand Up @@ -458,6 +494,50 @@ def test_fix_code_with_from_and_as(self):
from collections import defaultdict as abc, namedtuple as xyz
"""))

def test_fix_code_with_from_and_depth_module(self):
self.assertEqual(
"""\
from distutils.version import StrictVersion
StrictVersion('1.0.0')
""",
autoflake.fix_code("""\
from distutils.version import LooseVersion, StrictVersion
StrictVersion('1.0.0')
"""))

self.assertEqual(
"""\
from distutils.version import StrictVersion as version
version('1.0.0')
""",
autoflake.fix_code("""\
from distutils.version import LooseVersion, StrictVersion as version
version('1.0.0')
"""))

def test_fix_code_with_indented_from(self):
self.assertEqual(
"""\
def z():
from ctypes import POINTER, byref
POINTER, byref
""",
autoflake.fix_code("""\
def z():
from ctypes import c_short, c_uint, c_int, c_long, pointer, POINTER, byref
POINTER, byref
"""))

self.assertEqual(
"""\
def z():
pass
""",
autoflake.fix_code("""\
def z():
from ctypes import c_short, c_uint, c_int, c_long, pointer, POINTER, byref
"""))

def test_fix_code_with_empty_string(self):
self.assertEqual(
'',
Expand Down

0 comments on commit 23dd99f

Please sign in to comment.