Skip to content

Commit 568ceba

Browse files
authored
Merge pull request #344 from maggiemoss/preserve-inline-comments
Preserve inline comments
2 parents 6736c7a + 13d7dad commit 568ceba

File tree

6 files changed

+136
-4
lines changed

6 files changed

+136
-4
lines changed

docs/guide.rst

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -537,6 +537,57 @@ The following options are valid for the main ``tool.usort`` table:
537537
Whether to merge sequential imports from the same base module.
538538
See `Merging`_ for details on how this works.
539539

540+
.. attribute:: preserve_inline_comments
541+
:type: bool
542+
:value: false
543+
544+
Whether to preserve the placement of inline comments with their associated
545+
import items when sorting and merging imports.
546+
547+
When set to ``false`` (the default), inline comments from single-line imports
548+
are treated as import-level comments and will be placed at the end of the
549+
merged import statement::
550+
551+
# Before
552+
from foo import bar # comment1
553+
from foo import baz # comment2
554+
555+
# After (default behavior)
556+
from foo import bar, baz # comment1 # comment2
557+
558+
When set to ``true``, inline comments are associated with their specific
559+
import items and will remain attached to those items after sorting and
560+
merging::
561+
562+
# Before
563+
from foo import bar # comment1
564+
from foo import baz # comment2
565+
566+
# After (preserve_inline_comments = true)
567+
from foo import (
568+
bar, # comment1
569+
baz, # comment2
570+
)
571+
572+
This is particularly useful when using inline comments for directives like
573+
type checker annotations or linter suppressions that are specific to individual
574+
imports::
575+
576+
# Before
577+
from torch._C import _linalg # pyrefly:
578+
from torch._C import _LinAlgError as LinAlgError # pyrefly: ignore
579+
from torch._C import _add_docstr
580+
581+
# After (preserve_inline_comments = true)
582+
from torch._C import (
583+
_add_docstr,
584+
_linalg, # pyrefly:
585+
_LinAlgError as LinAlgError, # pyrefly: ignore
586+
)
587+
588+
Note that this option only affects inline comments on single-line imports.
589+
Comments on multi-line imports are always preserved with their associated items.
590+
540591
.. attribute:: excludes
541592
:type: List[str]
542593

usort/api.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
from .types import Result
1717
from .util import get_timings, timed, try_parse
1818

19-
2019
__all__ = ["usort_bytes", "usort_string", "usort_path", "usort_stdin"]
2120

2221

usort/config.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,9 @@ class Config:
6565
# Whether to merge imports when sorting
6666
merge_imports: bool = True
6767

68+
# Whether to preserve inline comments on individual imports when sorting
69+
preserve_inline_comments: bool = False
70+
6871
# gitignore-style filename patterns to exclude when sorting entire directories
6972
excludes: List[str] = field(default_factory=list)
7073

@@ -167,6 +170,8 @@ def update_from_config(self, toml_path: Path) -> None:
167170
self.magic_commas = bool(tbl["magic_commas"])
168171
if "merge_imports" in tbl:
169172
self.merge_imports = bool(tbl["merge_imports"])
173+
if "preserve_inline_comments" in tbl:
174+
self.preserve_inline_comments = bool(tbl["preserve_inline_comments"])
170175
if "excludes" in tbl:
171176
self.excludes = tbl["excludes"]
172177

usort/tests/cli.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -332,8 +332,8 @@ def test_format_latin_1(self) -> None:
332332
s = "\xb5"
333333
""".replace(
334334
b"\r", b""
335-
) # git on windows might make \r\n
336-
) as dtmp:
335+
)
336+
) as dtmp: # git on windows might make \r\n
337337
runner = CliRunner()
338338

339339
# Diff output is unicode

usort/tests/functional.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1140,6 +1140,58 @@ def test_sorting_with_extra_blank_lines(self) -> None:
11401140
""",
11411141
)
11421142

1143+
def test_preserve_inline_comments(self) -> None:
1144+
config = replace(DEFAULT_CONFIG, preserve_inline_comments=True)
1145+
self.assertUsortResult(
1146+
"""
1147+
from torch._C import _linalg # pyrefly:
1148+
from torch._C import (
1149+
_LinAlgError as LinAlgError, # pyrefly: ignore # missing-module-attribute
1150+
)
1151+
from torch._C import _add_docstr
1152+
""",
1153+
"""
1154+
from torch._C import (
1155+
_add_docstr,
1156+
_linalg, # pyrefly:
1157+
_LinAlgError as LinAlgError, # pyrefly: ignore # missing-module-attribute
1158+
)
1159+
""",
1160+
config,
1161+
)
1162+
1163+
def test_preserve_inline_comments_multiple_modules(self) -> None:
1164+
config = replace(DEFAULT_CONFIG, preserve_inline_comments=True)
1165+
self.assertUsortResult(
1166+
"""
1167+
from foo import bar # comment1
1168+
from foo import baz # comment2
1169+
from foo import alpha
1170+
""",
1171+
"""
1172+
from foo import (
1173+
alpha,
1174+
bar, # comment1
1175+
baz, # comment2
1176+
)
1177+
""",
1178+
config,
1179+
)
1180+
1181+
def test_preserve_inline_comments_disabled(self) -> None:
1182+
# Without preserve_inline_comments, comments become import-level
1183+
config = replace(DEFAULT_CONFIG, preserve_inline_comments=False)
1184+
self.assertUsortResult(
1185+
"""
1186+
from torch._C import _linalg # pyrefly:
1187+
from torch._C import _add_docstr
1188+
""",
1189+
"""
1190+
from torch._C import _add_docstr, _linalg # pyrefly:
1191+
""",
1192+
config,
1193+
)
1194+
11431195

11441196
if __name__ == "__main__":
11451197
unittest.main()

usort/translate.py

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,10 +201,27 @@ def import_from_node(node: cst.SimpleStatementLine, config: Config) -> SortableI
201201
if node.body[0].relative:
202202
stem = "." * len(node.body[0].relative) + stem
203203

204-
for name in node.body[0].names:
204+
# For single-line imports with preserve_inline_comments, attach trailing
205+
# comments to the specific item instead of the import level
206+
is_single_line = not node.body[0].lpar
207+
single_item_count = len(node.body[0].names)
208+
209+
for idx, name in enumerate(node.body[0].names):
205210
items.append(item_from_node(name, stem, comments.initial))
206211
comments.initial = []
207212

213+
# If preserving inline comments and this is a single-line import with
214+
# one item, move first_inline comments to the item's inline comments
215+
if (
216+
config.preserve_inline_comments
217+
and is_single_line
218+
and single_item_count == 1
219+
and idx == 0
220+
and comments.first_inline
221+
):
222+
items[-1].comments.inline.extend(comments.first_inline)
223+
comments.first_inline.clear()
224+
208225
else:
209226
raise TypeError
210227

@@ -240,6 +257,14 @@ def import_to_node(
240257
) -> cst.BaseStatement:
241258
if config.magic_commas and imp.stem and imp.trailing_comma:
242259
return import_to_node_multi(imp, module)
260+
261+
# If preserve_inline_comments is enabled and any item has inline comments,
262+
# use multi-line format to preserve them
263+
if config.preserve_inline_comments and imp.stem:
264+
has_item_comments = any(item.comments.inline for item in imp.items)
265+
if has_item_comments:
266+
return import_to_node_multi(imp, module)
267+
243268
node = import_to_node_single(imp, module)
244269
content = indent + render_node(node, module).rstrip()
245270
# basic imports can't be reflowed, so only deal with from-imports

0 commit comments

Comments
 (0)