Skip to content
Open
Show file tree
Hide file tree
Changes from 3 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
2 changes: 1 addition & 1 deletion isort/wrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ def import_statement(
"""Returns a multi-line wrapped form of the provided from import statement."""
if explode:
formatter = vertical_hanging_indent
line_length = 1
line_length = config.wrap_length or config.line_length
include_trailing_comma = True
else:
formatter = formatter_from_string((multi_line_output or config.multi_line_output).name)
Expand Down
27 changes: 26 additions & 1 deletion isort/wrap_modes.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,8 +173,33 @@ def vertical_hanging_indent(**interface: Any) -> str:
)
_imports = ("," + interface["line_separator"] + interface["indent"]).join(interface["imports"])
_comma_maybe = "," if interface["include_trailing_comma"] else ""
opening = f"{interface['statement']}({_line_with_comments}"
_is_functional_comment = any(
comment.strip().lower().startswith(("noqa", "type: ignore"))
for comment in (interface["comments"] or [])
)
if (
_line_with_comments
and len(opening) > interface["line_length"]
and not _is_functional_comment
):
_comment_on_own_line = interface["line_separator"].join(
isort.comments.add_to_line(
[single_comment],
interface["indent"],
removed=interface["remove_comments"],
comment_prefix=interface["comment_prefix"].lstrip(),
)
for single_comment in (interface["comments"] or [])
)
return (
f"{interface['statement']}({interface['line_separator']}"
f"{_comment_on_own_line}{interface['line_separator']}"
f"{interface['indent']}{_imports}{_comma_maybe}"
f"{interface['line_separator']})"
)
return (
f"{interface['statement']}({_line_with_comments}{interface['line_separator']}"
f"{opening}{interface['line_separator']}"
f"{interface['indent']}{_imports}{_comma_maybe}{interface['line_separator']})"
)

Expand Down
17 changes: 17 additions & 0 deletions tests/unit/test_regressions.py
Original file line number Diff line number Diff line change
Expand Up @@ -2489,3 +2489,20 @@ def test_add_import_keeps_a_prefixed_module_docstring_first_issue_1893():
docstring = f"{cased}{quote}module docstring\n{quote}\n"
source = docstring + "import a\n"
assert isort.code(source, add_imports=["import a"]) == source, cased + quote


def test_comments_should_cause_wrapping_on_long_lines_black_mode_issue_2124():
"""Ensure isort doesn't merge a long comment onto the opening line of a
multiline from import when using the black profile.
See: https://github.com/PyCQA/isort/issues/2124
"""
assert isort.code(
"""from os.path import (
join,
# this is a really really really really really really really really
# really really really really really really long comment
getsize,
)
""",
profile="black",
).startswith("from os.path import (\n # this is a really really")
17 changes: 17 additions & 0 deletions tests/unit/test_wrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,3 +93,20 @@ def test_star_import_wrapped_end_to_end() -> None:
source = "from very.very.very.very.very.very.very.very.very.long.line import *\n"
expected = "from very.very.very.very.very.very.very.very.very.long.line import \\\n *\n"
assert code(source, line_length=20, force_single_line=True) == expected


def test_import_statement_explode_long_comment_respects_line_length() -> None:
"""A long comment in explode mode is placed on its own line."""
result = wrap.import_statement(
import_start="from os.path import ",
from_imports=["getsize", "join"],
comments=[
" this is a really really really really really really really really"
" really really really really really really long comment"
],
config=Config(profile="black"),
explode=True,
)
lines = result.split("\n")
assert lines[0] == "from os.path import ("
assert "long comment" in lines[1]
102 changes: 102 additions & 0 deletions tests/unit/test_wrap_modes.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,108 @@ def test_vertical_grid_size_near_line_length(
)


def test_vertical_hanging_indent_long_comment_respects_line_length():
"""A long comment that exceeds line_length is placed on its own line."""
assert (
wrap_modes.vertical_hanging_indent(
statement="from os.path import ",
imports=["getsize", "join"],
white_space=" ",
indent=" ",
line_length=88,
comments=[
" this is a really really really really really really"
" really really really really really really long comment"
],
line_separator="\n",
comment_prefix=" #",
include_trailing_comma=True,
remove_comments=False,
)
== "from os.path import (\n"
" # this is a really really really really really really really really"
" really really really really long comment\n"
" getsize,\n"
" join,\n"
")"
)


def test_vertical_hanging_indent_short_comment_stays_on_opening_line():
"""A short comment that fits within line_length stays on the opening line."""
assert (
wrap_modes.vertical_hanging_indent(
statement="from os.path import ",
imports=["getsize", "join"],
white_space=" ",
indent=" ",
line_length=88,
comments=[" short comment"],
line_separator="\n",
comment_prefix=" #",
include_trailing_comma=True,
remove_comments=False,
)
== "from os.path import ( # short comment\n getsize,\n join,\n)"
)


def test_vertical_hanging_indent_non_directive_type_comment_moves_off():
"""A long comment containing 'type:' but not a 'type: ignore' directive moves off."""
assert (
wrap_modes.vertical_hanging_indent(
statement="from os.path import ",
imports=["getsize", "join"],
white_space=" ",
indent=" ",
line_length=88,
comments=[
" check type: int and more really really really really really really"
" really really really really really really long comment"
],
line_separator="\n",
comment_prefix=" #",
include_trailing_comma=True,
remove_comments=False,
)
== "from os.path import (\n"
" # check type: int and more really really really really really really really really"
" really really really really long comment\n"
" getsize,\n"
" join,\n"
")"
)


def test_vertical_hanging_indent_multi_fragment_comments_each_on_own_line():
"""Each comment fragment renders on its own # line to respect line_length."""
result = wrap_modes.vertical_hanging_indent(
statement="from os.path import ",
imports=["getsize", "join"],
white_space=" ",
indent=" ",
line_length=88,
comments=[
" this is a really really really really really really really really",
" really really really really really really long comment",
],
line_separator="\n",
comment_prefix=" #",
include_trailing_comma=True,
remove_comments=False,
)
assert result == (
"from os.path import (\n"
" # this is a really really really really really really really really\n"
" # really really really really really really long comment\n"
" getsize,\n"
" join,\n"
")"
)
for line in result.split("\n"):
assert len(line) <= 88, f"line exceeds 88: {line!r}"


# This test code was written by the `hypothesis.extra.ghostwriter` module
# and is provided under the Creative Commons Zero public domain dedication.

Expand Down