Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions isort/wrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
40 changes: 40 additions & 0 deletions tests/unit/test_regressions.py
Original file line number Diff line number Diff line change
Expand Up @@ -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},
):
Comment on lines +2145 to +2150

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just doing this with "force_single_line": True is enough. The rest is a bit overkill

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, "<test>", "exec")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's not do compile.

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
Loading