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
15 changes: 15 additions & 0 deletions isort/output.py
Original file line number Diff line number Diff line change
Expand Up @@ -550,6 +550,21 @@ def _with_from_imports(
comment = (
parsed.categorized_comments["nested"].get(module, {}).pop(from_import, None)
)
if (
comment is not None
and from_import in as_imports
and config.multi_line_output != wrap.Modes.NOQA # type: ignore[attr-defined] # noqa: E501

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.

This is a very verbose, AI-ey, comment which also refers to stuff that isn't happening (printing). Can you make it more to the point please?

):
# This name also carries an alias. The leading-alias loop above emits
# the plain name (with this comment) together with its alias on a later
# pass of the outer ``while``. Consuming it here would print only the
# plain name and silently drop the alias, so put the comment back and
# leave the name for that loop. (The NOQA wrap mode appends its own
# per-line suppression and keeps its existing handling.)
parsed.categorized_comments["nested"].setdefault(module, {})[
from_import
] = comment
continue
if comment is not None:
# If the comment is a noqa and hanging indent wrapping is used,
# keep the name in the main list and hoist the comment to the statement.
Expand Down
31 changes: 31 additions & 0 deletions tests/unit/test_regressions.py
Original file line number Diff line number Diff line change
Expand Up @@ -2117,3 +2117,34 @@ 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_isort_does_not_drop_aliased_import_when_plain_name_has_a_comment():
"""A name imported both plainly and with an alias must keep its alias when the plain
import carries a trailing comment and another member sorts ahead of it.

With default settings ``from x import m`` (commented) and ``from x import m as z`` are
combined into one module group. The aliased ``m as z`` is only emitted by the loop that
walks the *leading* aliased names, so it is reached only while ``m`` sits at the front of
the group. When a sibling such as ``aaa`` sorts before ``m`` that loop never sees ``m``;
the later "name has its own comment" pass then printed ``from x import m # c`` and
removed ``m`` from the group, so ``m as z`` was silently dropped (and the output no longer
re-sorted cleanly). The comment-bearing names that also have an alias must be left for the
alias loop instead of being consumed here.
"""
to_sort = "from x import aaa\nfrom x import m # c\nfrom x import m as z\n"
expected = "from x import aaa\nfrom x import m # c\nfrom x import m as z\n"

first_pass = isort.code(to_sort)
assert first_pass == expected
assert "m as z" in first_pass

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.

Suggested change
assert "m as z" in first_pass


# The result must already be a fixpoint - the dropped alias previously only surfaced on
# the second run.
assert isort.code(first_pass) == first_pass

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.

We can just use this and remove expected


# The same holds for a relative (local-folder) import.
relative = "from . import bar, one\nfrom . import one as zzz # NOQA\n"
relative_sorted = isort.code(relative)
assert "one as zzz" in relative_sorted
assert isort.code(relative_sorted) == relative_sorted
Loading