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
21 changes: 16 additions & 5 deletions isort/output.py
Original file line number Diff line number Diff line change
Expand Up @@ -649,11 +649,20 @@ def _with_from_imports_for_module(
)
)
else:
# `combine_as_imports` with `force_single_line` stores the alias comment
# in `straight["module.base"]` (see parse.py). The combined
# `from_import` here is the alias string. For example, "the_function as some_function"
# so we need to look up via the base name.
base = from_import.split(" as ")[0] if " as " in from_import else from_import
per_alias_straight = parsed.categorized_comments["straight"].pop(
f"{module}.{base}", []
)
single_import_line = with_comments(
[
c
for c in (
*comments,
*per_alias_straight,
parsed.categorized_comments["nested"]
.get(module, {})
.pop(from_import, None),
Expand Down Expand Up @@ -754,12 +763,14 @@ def _with_from_imports_for_module(
):
from_import_section.append(from_imports.pop(0))

# If we are combining aliased imports we need to pop any associated comments.
if config.combine_as_imports:
comments = [
*comments,
*(parsed.categorized_comments["from"].pop(f"{module}.__combined_as__", ())),
]
combined_as_comments: list[str] = []
for imp in from_import_section:
base = imp.split(" as ")[0]
combined_as_comments.extend(
parsed.categorized_comments["straight"].pop(f"{module}.{base}", [])
)
comments = [*comments, *combined_as_comments]

grouped_from_import_statement = _build_grouped_from_imports(
config=config,
Expand Down
22 changes: 8 additions & 14 deletions isort/parse.py
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,6 @@ def _get_next_line() -> tuple[str, str | None]:
if "as" in just_imports and (just_imports.index("as") + 1) < len(just_imports):
straight_import = False
while "as" in just_imports:
nested_module = None
as_index = just_imports.index("as")
if type_of_import == "from":
nested_module = just_imports[as_index - 1]
Expand Down Expand Up @@ -297,21 +296,16 @@ def _get_next_line() -> tuple[str, str | None]:
as_map["straight"][module].append(as_name)

if comments and attach_comments_to is None:
if nested_module and config.combine_as_imports:
attach_comments_to = categorized_comments["from"].setdefault(
f"{top_level_module}.__combined_as__", []
if type_of_import == "from" or (
config.remove_redundant_aliases and as_name == module.split(".")[-1]
):
attach_comments_to = categorized_comments["straight"].setdefault(
module, []
)
else:
if type_of_import == "from" or (
config.remove_redundant_aliases and as_name == module.split(".")[-1]
):
attach_comments_to = categorized_comments["straight"].setdefault(
module, []
)
else:
attach_comments_to = categorized_comments["straight"].setdefault(
f"{module} as {as_name}", []
)
attach_comments_to = categorized_comments["straight"].setdefault(
f"{module} as {as_name}", []
)
del just_imports[as_index : as_index + 2]

if type_of_import == "from":
Expand Down
58 changes: 58 additions & 0 deletions tests/unit/test_regressions.py
Original file line number Diff line number Diff line change
Expand Up @@ -571,6 +571,64 @@ def test_combine_as_does_not_lose_comments_issue_1381():
assert "# type: ignore" in isort.code(test_input, combine_as_imports=True)


def test_combine_as_with_force_single_line_does_not_lose_comments_issue_2094():
"""Test to ensure isort doesn't lose trailing comments for aliased imports
when both combine_as_imports and force_single_line are enabled.
See: https://github.com/PyCQA/isort/issues/2094
"""
import re # noqa: PLC0415 # local import, consistent with test_sort_reexports_output_is_black_stable_issue_2280

test_input = """from other_module import other_function # type: ignore [import] # pylint: disable=no-name-in-module
from some_module import the_function as some_function # type: ignore
from some_other_module import another_function as yet_another_function # type: ignore [import] # pylint: disable=no-name-in-module
"""
output = isort.code(test_input, combine_as_imports=True, force_single_line=True)
joined = output.replace("\\\n", " ")
assert re.search(
r"^from other_module import\s+other_function\s+# type: ignore \[import\]\s+# pylint: disable=no-name-in-module$",
joined,
re.MULTILINE,
)
assert re.search(
r"^from some_module import the_function as some_function\s+# type: ignore$",
joined,
re.MULTILINE,
)
assert re.search(
r"^from some_other_module import\s+another_function as\s+yet_another_function\s+# type: ignore \[import\]\s+# pylint: disable=no-name-in-module$",
joined,
re.MULTILINE,
)
assert isort.code(output, combine_as_imports=True, force_single_line=True) == output


def test_combine_as_with_force_single_line_keeps_per_alias_comments_issue_2094():
"""Each alias from the same module keeps its own trailing comment.

Pins the per-base keying at the core of this fix: comments must not swap
or duplicate across aliases of one module.
See: https://github.com/PyCQA/isort/issues/2094
"""
import re # noqa: PLC0415 # local import, consistent with sibling test above

test_input = """from some_module import the_function as some_function # type: ignore
from some_module import other_function as other_alias # noqa: F401
"""
output = isort.code(test_input, combine_as_imports=True, force_single_line=True)
joined = output.replace("\\\n", " ")
assert re.search(
r"^from some_module import the_function as some_function\s+# type: ignore$",
joined,
re.MULTILINE,
)
assert re.search(
r"^from some_module import other_function as other_alias\s+# noqa: F401$",
joined,
re.MULTILINE,
)
assert isort.code(output, combine_as_imports=True, force_single_line=True) == output


def test_incorrect_grouping_when_comments_issue_1396():
"""Test to ensure isort groups import correct independent of the comments present.
See: https://github.com/pycqa/isort/issues/1396
Expand Down