diff options
author | Léni Gauffier~ <lenigauffier@gmail.com> | 2021-12-29 20:17:53 +0100 |
---|---|---|
committer | Léni Gauffier <lenigauffier@gmail.com> | 2022-03-13 00:23:41 +0100 |
commit | 8cde8b2864c0df1b98460f0dddcb47af0a7d346d (patch) | |
tree | c8347c20af8e169c0ce3e9f2e0aaed3ada0709f7 | |
parent | 44e3b5d179da5033ce23f796b014e74f3a3259cd (diff) | |
download | isort-8cde8b2864c0df1b98460f0dddcb47af0a7d346d.tar.gz |
Add magic trailing comma option
-rw-r--r-- | docs/configuration/options.md | 11 | ||||
-rw-r--r-- | docs/configuration/profiles.md | 1 | ||||
-rw-r--r-- | isort/main.py | 6 | ||||
-rw-r--r-- | isort/output.py | 14 | ||||
-rw-r--r-- | isort/parse.py | 9 | ||||
-rw-r--r-- | isort/settings.py | 1 | ||||
-rw-r--r-- | isort/wrap.py | 17 | ||||
-rw-r--r-- | tests/unit/test_isort.py | 16 | ||||
-rw-r--r-- | tests/unit/test_parse.py | 1 | ||||
-rw-r--r-- | tests/unit/test_wrap.py | 3 |
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)" + ) |