Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix re-export sorter #2065

Merged
merged 4 commits into from Jan 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
13 changes: 13 additions & 0 deletions docs/configuration/options.md
Expand Up @@ -231,6 +231,19 @@ Forces line endings to the specified value. If not set, values will be guessed p
- --le
- --line-ending

## Sort Re-exports

Specifies whether to sort re-exports (`__all__` collections) automatically.

**Type:** Bool
**Default:** `False`
**Config default:** `false`
**Python & Config File Name:** sort_reexports
**CLI Flags:**

- --srx
- --sort-reexports

## Sections

What sections isort should display imports for and in what order
Expand Down
31 changes: 15 additions & 16 deletions isort/core.py
Expand Up @@ -24,11 +24,7 @@
"# isort: unique-tuple",
"# isort: assignments",
)
LITERAL_TYPE_MAPPING = {
"(": "tuple",
"[": "list",
"{": "dict",
}
LITERAL_TYPE_MAPPING = {"(": "tuple", "[": "list", "{": "set"}


def process(
Expand Down Expand Up @@ -78,8 +74,8 @@ def process(
end_of_file: bool = False
verbose_output: List[str] = []
lines_before: List[str] = []
auto_reexporting: bool = False
line_index: int = 0
is_reexport: bool = False
sort_section_pointer: int = 0

if config.float_to_top:
new_input = ""
Expand Down Expand Up @@ -137,6 +133,8 @@ def process(
line_separator = "\n"

if code_sorting and code_sorting_section:
if is_reexport:
output_stream.seek(sort_section_pointer, 0)
sorted_code = textwrap.indent(
isort.literal.assignment(
code_sorting_section,
Expand All @@ -152,7 +150,7 @@ def process(
line_separator=line_separator,
ignore_whitespace=config.ignore_whitespace,
)
line_index += output_stream.write(sorted_code)
sort_section_pointer += output_stream.write(sorted_code)
else:
stripped_line = line.strip()
if stripped_line and not line_separator:
Expand Down Expand Up @@ -233,12 +231,13 @@ def process(
code_sorting_indent = line[: -len(line.lstrip())]
not_imports = True
elif config.sort_reexports and stripped_line.startswith("__all__"):
code_sorting = LITERAL_TYPE_MAPPING[stripped_line.split(" = ")[1][0]]
_, rhs = stripped_line.split("=")
code_sorting = LITERAL_TYPE_MAPPING.get(rhs.lstrip()[0], "tuple")
code_sorting_indent = line[: -len(line.lstrip())]
not_imports = True
code_sorting_section += line
auto_reexporting = True
line_index -= len(line) - 1
is_reexport = True
sort_section_pointer -= len(line)
elif code_sorting:
if not stripped_line:
sorted_code = textwrap.indent(
Expand All @@ -256,14 +255,14 @@ def process(
line_separator=line_separator,
ignore_whitespace=config.ignore_whitespace,
)
if auto_reexporting:
output_stream.seek(line_index, 0)
line_index += output_stream.write(sorted_code)
if is_reexport:
output_stream.seek(sort_section_pointer, 0)
sort_section_pointer += output_stream.write(sorted_code)
not_imports = True
code_sorting = False
code_sorting_section = ""
code_sorting_indent = ""
auto_reexporting = False
is_reexport = False
else:
code_sorting_section += line
line = ""
Expand Down Expand Up @@ -357,7 +356,7 @@ def process(
else:
not_imports = True

line_index += len(line)
sort_section_pointer += len(line)

if not_imports:

Expand Down
5 changes: 3 additions & 2 deletions isort/literal.py
Expand Up @@ -47,8 +47,9 @@ 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 = variable_name.lstrip()
variable_name, literal = code.split("=")
variable_name = variable_name.strip()
literal = literal.lstrip()
try:
value = ast.literal_eval(literal)
except Exception as error:
Expand Down
1 change: 1 addition & 0 deletions isort/main.py
Expand Up @@ -323,6 +323,7 @@ def _build_arg_parser() -> argparse.ArgumentParser:
help="Override the format used to print success.",
)
general_group.add_argument(
"--srx",
"--sort-reexports",
dest="sort_reexports",
action="store_true",
Expand Down
81 changes: 81 additions & 0 deletions tests/unit/test_isort.py
Expand Up @@ -5591,3 +5591,84 @@ def test_infinite_loop_in_unmatched_parenthesis() -> None:

# ensure other cases are handled correctly
assert isort.code(test_input) == "from os import path, walk\n"


def test_reexport() -> None:
test_input = """__all__ = ('foo', 'bar')
"""
expd_output = """__all__ = ('bar', 'foo')
"""
assert isort.code(test_input, config=Config(sort_reexports=True)) == expd_output


def test_reexport_leave_alone_if_not_enabled() -> None:
test_input = """__all__ = ('foo', 'bar')
"""
assert isort.code(test_input) == test_input


def test_reexport_multiline() -> None:
test_input = """__all__ = (
'foo',
'bar',
)
"""
expd_output = """__all__ = ('bar', 'foo')
"""
assert isort.code(test_input, config=Config(sort_reexports=True)) == expd_output


def test_reexport_list() -> None:
test_input = """__all__ = ['foo', 'bar']
"""
expd_output = """__all__ = ['bar', 'foo']
"""
assert isort.code(test_input, config=Config(sort_reexports=True)) == expd_output


def test_reexport_set() -> None:
test_input = """__all__ = {'foo', 'bar'}
"""
expd_output = """__all__ = {'bar', 'foo'}
"""
assert isort.code(test_input, config=Config(sort_reexports=True)) == expd_output


def test_reexport_bare() -> None:
test_input = """__all__ = 'foo', 'bar'
"""
expd_output = """__all__ = ('bar', 'foo')
"""
assert isort.code(test_input, config=Config(sort_reexports=True)) == expd_output


def test_reexport_no_spaces() -> None:
test_input = """__all__=('foo', 'bar')
"""
expd_output = """__all__ = ('bar', 'foo')
"""
assert isort.code(test_input, config=Config(sort_reexports=True)) == expd_output


def test_reexport_not_first_line() -> None:
test_input = """import random

__all__ = ('foo', 'bar')
"""
expd_output = """import random

__all__ = ('bar', 'foo')
"""
assert isort.code(test_input, config=Config(sort_reexports=True)) == expd_output


def test_reexport_not_last_line() -> None:
test_input = """__all__ = ('foo', 'bar')

meme = "rickroll"
"""
expd_output = """__all__ = ('bar', 'foo')

meme = "rickroll"
"""
assert isort.code(test_input, config=Config(sort_reexports=True)) == expd_output