diff --git a/autoflake.py b/autoflake.py index 95a8153..7e62e2b 100755 --- a/autoflake.py +++ b/autoflake.py @@ -386,7 +386,17 @@ def __init__( """Receive the same parameters as ``filter_unused_import``.""" self.remove: Iterable[str] = unused_module self.parenthesized: bool = "(" in line - self.from_, imports = self.IMPORT_RE.split(line, maxsplit=1) + split = self.IMPORT_RE.split(line, maxsplit=1) + if len(split) == 1: + # ``import`` is on a continuation line (e.g. ``from X \ + # import Y``). Treat the whole first line as the ``from`` + # fragment and leave imports empty; give_up ensures the statement + # is passed through unchanged. + self.from_, imports = line, "" + self.give_up_no_import_on_first_line: bool = True + else: + self.from_, imports = split + self.give_up_no_import_on_first_line = False match = self.BASE_RE.search(self.from_) self.base = match.group(1) if match else None self.give_up: bool = False @@ -401,6 +411,10 @@ def __init__( # Ignore tricky things like "try: \ import" ... self.give_up = True + if self.give_up_no_import_on_first_line: + # ``import`` is deferred to a continuation line; cannot filter. + self.give_up = True + self.analyze(line) PendingFix.__init__(self, imports) @@ -470,6 +484,10 @@ def __call__(self, line: str | None = None) -> PendingFix | str: if not self.is_over(line): return self if self.give_up: + if self.give_up_no_import_on_first_line: + # The ``import`` keyword was not on the first line; reconstruct + # verbatim without inserting a spurious ``import``. + return self.from_ + "".join(self.accumulator) return self.from_ + "import " + "".join(self.accumulator) return self.fix(self.accumulator) diff --git a/test_autoflake.py b/test_autoflake.py index eacb0d1..735cee3 100755 --- a/test_autoflake.py +++ b/test_autoflake.py @@ -717,6 +717,29 @@ def test_filter_code_multiline_from_imports(self) -> None: ), ) + def test_filter_code_backslash_continuation_import_on_second_line( + self, + ) -> None: + """Backslash-continuation where 'import' appears on the second line. + + ``from X \\ import A, B`` is a valid Python multiline + import. autoflake used to crash with ``ValueError: not enough values + to unpack`` because the regex split expected ``import`` on the first + line. The statement must be passed through unchanged (give_up path). + """ + source = "".join( + [ + "from ci.zimbeast.test_scripts.framework.test_interrupt.seq_lib \\\n", + " import IntTestSequence, send_int\n", + "\n", + "x = send_int(1)\n", + ] + ) + self.assertEqual( + source, + "".join(autoflake.filter_code(source, remove_all_unused_imports=True)), + ) + def test_filter_code_should_ignore_semicolons(self) -> None: self.assertEqual( r"""\