Skip to content
Draft
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
54 changes: 50 additions & 4 deletions isort/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,38 @@
LITERAL_TYPE_MAPPING = {"(": "tuple", "[": "list", "{": "set"}


def _net_open_brackets(line: str) -> int:
"""Return the net number of open brackets in a line, correctly ignoring
brackets that appear inside string literals or comments.
"""
depth = 0
in_string = ""
i = 0
while i < len(line):
char = line[i]
if in_string:
if char == "\\" and len(in_string) == 1:
i += 1 # skip the escaped character
elif line[i : i + len(in_string)] == in_string:
i += len(in_string) - 1
in_string = ""
elif char in ('"', "'"):
triple = line[i : i + 3]
if triple in ('"""', "'''"):
in_string = triple
i += 2
else:
in_string = char
elif char == "#":
break # rest of the line is a comment
elif char in ("(", "[", "{"):
depth += 1
elif char in (")", "]", "}"):
depth -= 1
i += 1
return depth


# Ignore DeepSource cyclomatic complexity check for this function.
# skipcq: PY-R1000
def process(
Expand Down Expand Up @@ -78,6 +110,7 @@ def process(
lines_before: list[str] = []
is_reexport: bool = False
reexport_rollback: int = 0
reexport_bracket_depth: int = 0

if config.float_to_top:
new_input = ""
Expand Down Expand Up @@ -236,15 +269,30 @@ def process(
code_sorting_indent = line[: -len(line.lstrip())]
not_imports = True
elif config.sort_reexports and stripped_line.startswith("__all__"):
_, rhs = stripped_line.split("=")
_, rhs = stripped_line.split("=", 1)
code_sorting = LITERAL_TYPE_MAPPING.get(rhs.lstrip()[0], "tuple")
code_sorting_indent = line[: -len(line.lstrip())]
not_imports = True
code_sorting_section += line
reexport_rollback = len(line)
is_reexport = True
reexport_bracket_depth = _net_open_brackets(stripped_line)
elif code_sorting:
_process_section = False
if not stripped_line:
_process_section = True
elif is_reexport and reexport_bracket_depth <= 0:
# The __all__ assignment is complete; process it and let
# the current line be handled normally (don't set line = "")
_process_section = True
else:
code_sorting_section += line
line = ""
if is_reexport:
reexport_bracket_depth += _net_open_brackets(stripped_line)
if reexport_bracket_depth <= 0:
_process_section = True
if _process_section:
sorted_code = textwrap.indent(
isort.literal.assignment(
code_sorting_section,
Expand All @@ -271,9 +319,7 @@ def process(
code_sorting_section = ""
code_sorting_indent = ""
is_reexport = False
else:
code_sorting_section += line
line = ""
reexport_bracket_depth = 0
elif (
stripped_line in config.section_comments
or stripped_line in config.section_comments_end
Expand Down
2 changes: 1 addition & 1 deletion isort/literal.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ def assignment(code: str, sort_type: str, extension: str, config: Config = DEFAU
f"Defined sort types are {', '.join(type_mapping.keys())}."
)

variable_name, literal = code.split("=")
variable_name, literal = code.split("=", 1)
variable_name = variable_name.strip()
literal = literal.lstrip()
try:
Expand Down
36 changes: 36 additions & 0 deletions tests/unit/test_isort.py
Original file line number Diff line number Diff line change
Expand Up @@ -5719,6 +5719,42 @@ def test_reexport_multiline_long_rollback() -> None:
assert isort.code(test_input, config=Config(sort_reexports=True)) == expd_output


def test_reexport_multiline_no_trailing_blank_line() -> None:
"""Test that sort_reexports works when __all__ is followed immediately by
another assignment without a blank line (issue: too many values to unpack).
"""
test_input = """#!/usr/bin/env python
import importlib.metadata

__all__ = [
"FooType",
"BarType",
"some_method",
]
__version__ = importlib.metadata.version("my-package")
"""
expd_output = """#!/usr/bin/env python
import importlib.metadata

__all__ = ['BarType', 'FooType', 'some_method']
__version__ = importlib.metadata.version("my-package")
"""
assert isort.code(test_input, config=Config(sort_reexports=True)) == expd_output


def test_reexport_singleline_no_trailing_blank_line() -> None:
"""Test that sort_reexports works for single-line __all__ followed immediately
by another assignment without a blank line.
"""
test_input = """__all__ = ('foo', 'bar')
__version__ = '1.0'
"""
expd_output = """__all__ = ('bar', 'foo')
__version__ = '1.0'
"""
assert isort.code(test_input, config=Config(sort_reexports=True)) == expd_output


def test_noqa_multiline_hanging_indent() -> None:
test_input = (
"from aaaaaaa import bbbbbbbbbbbbbbbbb, ccccccccccccccccccc, dddddddddddddddd"
Expand Down
Loading