diff --git a/isort/parse.py b/isort/parse.py index 5928b392..d3e16645 100644 --- a/isort/parse.py +++ b/isort/parse.py @@ -218,6 +218,19 @@ def _get_next_line() -> tuple[str, str | None]: ): nested_comments[stripped_line] = extra_line.comment + # If a semicolon was introduced by a backslash-continued line (i.e., the + # original statement had no semicolon but the merged import_string does), + # treat the entire multi-line construct as non-import code and output as-is. + # This mirrors what skip_line() does for single-line semicolons. + # Note: statement_index was set to `index` *after* its increment at the top + # of the outer while loop, so statement_index - 1 is the index of the first + # line of this statement in in_lines. + import_string_code = import_string.split("#")[0] + statement_code = statement.split("#")[0] + if ";" in import_string_code and ";" not in statement_code: + out_lines.extend(in_lines[statement_index - 1 : index]) + continue + if type_of_import == "from": import_string = normalize_from_import_string(import_string) if "import " not in import_string: diff --git a/tests/unit/test_regressions.py b/tests/unit/test_regressions.py index bd0e538d..487caee6 100644 --- a/tests/unit/test_regressions.py +++ b/tests/unit/test_regressions.py @@ -1985,3 +1985,16 @@ def test_comment_on_opening_line_of_aliased_import_does_not_move(): isort.code(short_line, profile="black") == "from mod import attr as alias # type: ignore[attr-defined] # My comment\n" ) + + +def test_semicolon_after_backslash_continuation_not_corrupted(): + """Ensure isort doesn't corrupt imports when a backslash-continued line ends with a + semicolon followed by additional code, e.g.: + + from os import \\ + name; print(name) + + See: https://github.com/PyCQA/isort/issues/2417 + """ + test_input = "from os import \\\n name; print(name)\n" + assert isort.code(test_input) == test_input