From 0d83e5bcc94cf81a69e9fef2bccaac8ce6c0a7e8 Mon Sep 17 00:00:00 2001 From: Labib-Bin-Salam Date: Mon, 15 Jun 2026 20:15:52 +0100 Subject: [PATCH] Fix over-long wildcard imports being rewritten into invalid Python A wildcard import such as `from x.y.z import *` cannot be wrapped: parenthesising a star (`from x.y.z import (*)`) is a SyntaxError, and the only over-long part, the dotted module path, cannot be split either. When such a line exceeded the configured length, `wrap.line()` tried each of its splitters in turn. The `import ` splitter failed to match because its `\bimport \b` pattern has no word boundary between the trailing space and `*`, so isort fell through to the `.` splitter and shredded the module path, producing invalid Python such as: from very.very.( line import *.long.very.very, ) Bail out of wrapping for star imports and leave the statement untouched, matching Black. Adds a regression test covering force_single_line, combine_star and trailing-comment cases. Closes #2267 --- isort/wrap.py | 9 ++++++++ tests/unit/test_regressions.py | 40 ++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/isort/wrap.py b/isort/wrap.py index 4d1e7391..ef341c39 100644 --- a/isort/wrap.py +++ b/isort/wrap.py @@ -76,6 +76,15 @@ def line(content: str, line_separator: str, config: Config = DEFAULT_CONFIG) -> comment = None if "#" in content: line_without_comment, comment = content.split("#", 1) + # A wildcard (star) import such as ``from x.y.z import *`` cannot be + # wrapped: parenthesising a star (``from x.y.z import (*)``) is a + # SyntaxError, and the only over-long part - the dotted module path - + # cannot be split either. Without this guard the ``import `` splitter + # below fails to match (there is no word boundary between the space and + # ``*``) and isort falls through to the ``.`` splitter, mangling the + # module path into invalid Python. See issue #2267. + if line_without_comment.rstrip().endswith("import *"): + return content for splitter in ("import ", "cimport ", ".", "as "): exp = r"\b" + re.escape(splitter) + r"\b" if re.search(exp, line_without_comment) and not line_without_comment.strip().startswith( diff --git a/tests/unit/test_regressions.py b/tests/unit/test_regressions.py index f843ff84..3d3d7ac6 100644 --- a/tests/unit/test_regressions.py +++ b/tests/unit/test_regressions.py @@ -2117,3 +2117,43 @@ def test_noqa_wrap_mode_does_not_accumulate_spaces_with_as_import(): first_pass, multi_line_output=7, force_single_line=True, line_length=40 ) assert second_pass == first_pass + + +def test_long_star_import_should_not_be_corrupted_issue_2267(): + """Ensure an over-long wildcard import is left intact instead of being mangled. + + A wildcard import such as ``from x.y.z import *`` cannot be wrapped - parenthesising a + star (``from x.y.z import (*)``) is a SyntaxError and the dotted module path cannot be + split. When the line exceeded the configured length and ``force_single_line`` was set, + isort used to fall through to its ``.`` splitter and rewrite the statement into invalid + Python, e.g.:: + + from very.very.( + line import *.long.very.very, + ) + + as reported in issue #2267: https://github.com/PyCQA/isort/issues/2267 + + The statement must be returned unchanged (matching Black) and must remain valid, + parseable Python across repeated runs. + """ + test_input = ( + "from very.very.very.very.very.very.very.very.very.very." + "very.very.very.very.very.very.very.very.long.line import *\n" + ) + + for kwargs in ( + {"profile": "black", "force_single_line": True}, + {"force_single_line": True}, + {"profile": "black"}, + {"profile": "black", "force_single_line": True, "combine_star": True}, + ): + first_pass = isort.code(test_input, **kwargs) + assert first_pass == test_input, f"corrupted with {kwargs}" + # Must be valid Python and idempotent. + compile(first_pass, "", "exec") + assert isort.code(first_pass, **kwargs) == first_pass, f"not idempotent with {kwargs}" + + # A trailing comment on the wildcard import must be preserved, not dropped. + with_comment = test_input.rstrip("\n") + " # noqa: F403\n" + assert isort.code(with_comment, profile="black", force_single_line=True) == with_comment