summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLéni Gauffier~ <lenigauffier@gmail.com>2021-12-29 20:17:53 +0100
committerLéni Gauffier <lenigauffier@gmail.com>2022-03-13 00:23:41 +0100
commit8cde8b2864c0df1b98460f0dddcb47af0a7d346d (patch)
treec8347c20af8e169c0ce3e9f2e0aaed3ada0709f7
parent44e3b5d179da5033ce23f796b014e74f3a3259cd (diff)
downloadisort-8cde8b2864c0df1b98460f0dddcb47af0a7d346d.tar.gz
Add magic trailing comma option
-rw-r--r--docs/configuration/options.md11
-rw-r--r--docs/configuration/profiles.md1
-rw-r--r--isort/main.py6
-rw-r--r--isort/output.py14
-rw-r--r--isort/parse.py9
-rw-r--r--isort/settings.py1
-rw-r--r--isort/wrap.py17
-rw-r--r--tests/unit/test_isort.py16
-rw-r--r--tests/unit/test_parse.py1
-rw-r--r--tests/unit/test_wrap.py3
10 files changed, 71 insertions, 8 deletions
diff --git a/docs/configuration/options.md b/docs/configuration/options.md
index 527798a6..56239d6f 100644
--- a/docs/configuration/options.md
+++ b/docs/configuration/options.md
@@ -889,6 +889,17 @@ Includes a trailing comma on multi line imports that include parentheses.
- --tc
- --trailing-comma
+## Split on Trailing Comma
+
+Split imports list followed by a trailing comma into VERTICAL_HANGING_INDENT mode. This follows Black style magic comma.
+
+**Type:** Bool
+**Default:** `False`
+**Config default:** `false`
+**Python & Config File Name:** split_on_trailing_comma
+**CLI Flags:**
+
+- --split-on-trailing-comma
## From First
diff --git a/docs/configuration/profiles.md b/docs/configuration/profiles.md
index a8e09dd2..56b10757 100644
--- a/docs/configuration/profiles.md
+++ b/docs/configuration/profiles.md
@@ -16,6 +16,7 @@ To use any of the listed profiles, use `isort --profile PROFILE_NAME` from the c
- **use_parentheses**: `True`
- **ensure_newline_before_comments**: `True`
- **line_length**: `88`
+ - **split_on_trailing_comma**: `True`
#django
diff --git a/isort/main.py b/isort/main.py
index 40725a6a..de3cb1c0 100644
--- a/isort/main.py
+++ b/isort/main.py
@@ -714,6 +714,12 @@ def _build_arg_parser() -> argparse.ArgumentParser:
dest="star_first",
action="store_true",
)
+ output_group.add_argument(
+ "--split-on-trailing-comma",
+ help="Split imports list followed by a trailing comma into VERTICAL_HANGING_INDENT mode",
+ dest="split_on_trailing_comma",
+ action="store_true",
+ )
section_group.add_argument(
"--sd",
diff --git a/isort/output.py b/isort/output.py
index d049daf6..c59be936 100644
--- a/isort/output.py
+++ b/isort/output.py
@@ -505,7 +505,17 @@ def _with_from_imports(
):
do_multiline_reformat = True
- if do_multiline_reformat:
+ if config.split_on_trailing_comma and module in parsed.trailing_commas:
+ import_statement = wrap.import_statement(
+ import_start=import_start,
+ from_imports=from_import_section,
+ comments=comments,
+ line_separator=parsed.line_separator,
+ config=config,
+ explode=True,
+ )
+
+ elif do_multiline_reformat:
import_statement = wrap.import_statement(
import_start=import_start,
from_imports=from_import_section,
@@ -530,7 +540,7 @@ def _with_from_imports(
> config.line_length
):
import_statement = other_import_statement
- if not do_multiline_reformat and len(import_statement) > config.line_length:
+ elif len(import_statement) > config.line_length:
import_statement = wrap.line(import_statement, parsed.line_separator, config)
if import_statement:
diff --git a/isort/parse.py b/isort/parse.py
index 7fc6c8e6..c60938d8 100644
--- a/isort/parse.py
+++ b/isort/parse.py
@@ -2,7 +2,7 @@
from collections import OrderedDict, defaultdict
from functools import partial
from itertools import chain
-from typing import TYPE_CHECKING, Any, Dict, List, NamedTuple, Optional, Tuple
+from typing import TYPE_CHECKING, Any, Dict, List, NamedTuple, Optional, Set, Tuple
from warnings import warn
from . import place
@@ -138,6 +138,7 @@ class ParsedContent(NamedTuple):
line_separator: str
sections: Any
verbose_output: List[str]
+ trailing_commas: Set[str]
def file_contents(contents: str, config: Config = DEFAULT_CONFIG) -> ParsedContent:
@@ -176,6 +177,8 @@ def file_contents(contents: str, config: Config = DEFAULT_CONFIG) -> ParsedConte
"above": {"straight": {}, "from": {}},
}
+ trailing_commas: Set[str] = set()
+
index = 0
import_index = -1
in_quote = ""
@@ -515,6 +518,9 @@ def file_contents(contents: str, config: Config = DEFAULT_CONFIG) -> ParsedConte
if comments and attach_comments_to is not None:
attach_comments_to.extend(comments)
+
+ if "," in import_string.split(just_imports[-1])[-1]:
+ trailing_commas.add(import_from)
else:
if comments and attach_comments_to is not None:
attach_comments_to.extend(comments)
@@ -587,4 +593,5 @@ def file_contents(contents: str, config: Config = DEFAULT_CONFIG) -> ParsedConte
line_separator=line_separator,
sections=config.sections,
verbose_output=verbose_output,
+ trailing_commas=trailing_commas,
)
diff --git a/isort/settings.py b/isort/settings.py
index f13afade..f6fd6f51 100644
--- a/isort/settings.py
+++ b/isort/settings.py
@@ -243,6 +243,7 @@ class _Config:
format_success: str = "{success}: {message}"
sort_order: str = "natural"
sort_reexports: bool = False
+ split_on_trailing_comma: bool = False
def __post_init__(self) -> None:
py_version = self.py_version
diff --git a/isort/wrap.py b/isort/wrap.py
index 5fb4631f..eb4c2483 100644
--- a/isort/wrap.py
+++ b/isort/wrap.py
@@ -4,7 +4,7 @@ from typing import List, Optional, Sequence
from .settings import DEFAULT_CONFIG, Config
from .wrap_modes import WrapModes as Modes
-from .wrap_modes import formatter_from_string
+from .wrap_modes import formatter_from_string, vertical_hanging_indent
def import_statement(
@@ -14,12 +14,19 @@ def import_statement(
line_separator: str = "\n",
config: Config = DEFAULT_CONFIG,
multi_line_output: Optional[Modes] = None,
+ explode: bool = False,
) -> str:
"""Returns a multi-line wrapped form of the provided from import statement."""
- formatter = formatter_from_string((multi_line_output or config.multi_line_output).name)
+ if explode:
+ formatter = vertical_hanging_indent
+ line_length = 1
+ include_trailing_comma = True
+ else:
+ formatter = formatter_from_string((multi_line_output or config.multi_line_output).name)
+ line_length = config.wrap_length or config.line_length
+ include_trailing_comma = config.include_trailing_comma
dynamic_indent = " " * (len(import_start) + 1)
indent = config.indent
- line_length = config.wrap_length or config.line_length
statement = formatter(
statement=import_start,
imports=copy.copy(from_imports),
@@ -29,7 +36,7 @@ def import_statement(
comments=comments,
line_separator=line_separator,
comment_prefix=config.comment_prefix,
- include_trailing_comma=config.include_trailing_comma,
+ include_trailing_comma=include_trailing_comma,
remove_comments=config.ignore_comments,
)
if config.balanced_wrapping:
@@ -52,7 +59,7 @@ def import_statement(
comments=comments,
line_separator=line_separator,
comment_prefix=config.comment_prefix,
- include_trailing_comma=config.include_trailing_comma,
+ include_trailing_comma=include_trailing_comma,
remove_comments=config.ignore_comments,
)
lines = new_import_statement.split(line_separator)
diff --git a/tests/unit/test_isort.py b/tests/unit/test_isort.py
index 293b99be..4b86f7ec 100644
--- a/tests/unit/test_isort.py
+++ b/tests/unit/test_isort.py
@@ -5562,3 +5562,19 @@ def test_find_imports_in_stream() -> None:
test_input = NonSeekableTestStream("import m2\n" "import m1\n" "not_import = 7")
identified_imports = list(map(str, api.find_imports_in_stream(test_input)))
assert identified_imports == [":1 import m2", ":2 import m1"]
+
+
+def test_split_on_trailing_comma() -> None:
+ test_input = "from lib import (a, b, c,)"
+ expected_output = """from lib import (
+ a,
+ b,
+ c,
+)
+"""
+
+ output = isort.code(test_input, split_on_trailing_comma=True)
+ assert output == expected_output
+
+ output = isort.code(expected_output, split_on_trailing_comma=True)
+ assert output == expected_output
diff --git a/tests/unit/test_parse.py b/tests/unit/test_parse.py
index 0becac90..1a66a5cd 100644
--- a/tests/unit/test_parse.py
+++ b/tests/unit/test_parse.py
@@ -38,6 +38,7 @@ def test_file_contents():
_,
_,
_,
+ _,
) = parse.file_contents(TEST_CONTENTS, config=Config(default_section=""))
assert "\n".join(in_lines) == TEST_CONTENTS
assert "import" not in "\n".join(out_lines)
diff --git a/tests/unit/test_wrap.py b/tests/unit/test_wrap.py
index 2b8ec6fa..2e35951f 100644
--- a/tests/unit/test_wrap.py
+++ b/tests/unit/test_wrap.py
@@ -13,3 +13,6 @@ def test_import_statement():
== """from long_import (verylong, verylong, verylong, verylong, verylong, verylong,
verylong, verylong, verylong, verylong)"""
)
+ assert wrap.import_statement("from x import ", ["y", "z"], [], explode=True) == (
+ "from x import (\n y,\n z,\n)"
+ )