codesorter 0.2.3__tar.gz → 0.2.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {codesorter-0.2.3 → codesorter-0.2.4}/CHANGES.rst +19 -8
- {codesorter-0.2.3 → codesorter-0.2.4}/PKG-INFO +1 -1
- {codesorter-0.2.3 → codesorter-0.2.4}/codesorter/const.py +1 -1
- {codesorter-0.2.3 → codesorter-0.2.4}/codesorter/sort_code.py +52 -27
- codesorter-0.2.4/tests/test_files/barrier_input.py +11 -0
- codesorter-0.2.4/tests/test_files/barrier_output.py +11 -0
- {codesorter-0.2.3 → codesorter-0.2.4}/tests/test_sort_code.py +16 -0
- {codesorter-0.2.3 → codesorter-0.2.4}/.github/dependabot.yml +0 -0
- {codesorter-0.2.3 → codesorter-0.2.4}/.github/workflows/ci.yml +0 -0
- {codesorter-0.2.3 → codesorter-0.2.4}/.github/workflows/pre-commit_autoupdate.yml +0 -0
- {codesorter-0.2.3 → codesorter-0.2.4}/.github/workflows/prepare_release.yml +0 -0
- {codesorter-0.2.3 → codesorter-0.2.4}/.github/workflows/pypi.yml +0 -0
- {codesorter-0.2.3 → codesorter-0.2.4}/.github/workflows/scorecard.yml +0 -0
- {codesorter-0.2.3 → codesorter-0.2.4}/.github/workflows/stale_action.yml +0 -0
- {codesorter-0.2.3 → codesorter-0.2.4}/.github/workflows/tag_release.yml +0 -0
- {codesorter-0.2.3 → codesorter-0.2.4}/.gitignore +0 -0
- {codesorter-0.2.3 → codesorter-0.2.4}/.libcst.codemod.yaml +0 -0
- {codesorter-0.2.3 → codesorter-0.2.4}/.pre-commit-config.yaml +0 -0
- {codesorter-0.2.3 → codesorter-0.2.4}/.pre-commit-hooks.yaml +0 -0
- {codesorter-0.2.3 → codesorter-0.2.4}/.python-version +0 -0
- {codesorter-0.2.3 → codesorter-0.2.4}/.readthedocs.yaml +0 -0
- {codesorter-0.2.3 → codesorter-0.2.4}/LICENSE.txt +0 -0
- {codesorter-0.2.3 → codesorter-0.2.4}/README.rst +0 -0
- {codesorter-0.2.3 → codesorter-0.2.4}/codesorter/__init__.py +0 -0
- {codesorter-0.2.3 → codesorter-0.2.4}/codesorter/cli.py +0 -0
- {codesorter-0.2.3 → codesorter-0.2.4}/codesorter/py.typed +0 -0
- {codesorter-0.2.3 → codesorter-0.2.4}/docs/Makefile +0 -0
- {codesorter-0.2.3 → codesorter-0.2.4}/docs/code_overview/sort_code.rst +0 -0
- {codesorter-0.2.3 → codesorter-0.2.4}/docs/conf.py +0 -0
- {codesorter-0.2.3 → codesorter-0.2.4}/docs/contributing/pre_commit.rst +0 -0
- {codesorter-0.2.3 → codesorter-0.2.4}/docs/docutils.conf +0 -0
- {codesorter-0.2.3 → codesorter-0.2.4}/docs/genindex.rst +0 -0
- {codesorter-0.2.3 → codesorter-0.2.4}/docs/index.rst +0 -0
- {codesorter-0.2.3 → codesorter-0.2.4}/docs/make.bat +0 -0
- {codesorter-0.2.3 → codesorter-0.2.4}/docs/package_info/change_log.rst +0 -0
- {codesorter-0.2.3 → codesorter-0.2.4}/examples/after_example.py +0 -0
- {codesorter-0.2.3 → codesorter-0.2.4}/examples/before_example.py +0 -0
- {codesorter-0.2.3 → codesorter-0.2.4}/pyproject.toml +0 -0
- {codesorter-0.2.3 → codesorter-0.2.4}/tests/__init__.py +0 -0
- {codesorter-0.2.3 → codesorter-0.2.4}/tests/conftest.py +0 -0
- {codesorter-0.2.3 → codesorter-0.2.4}/tests/test_files/alias_function_input.py +0 -0
- {codesorter-0.2.3 → codesorter-0.2.4}/tests/test_files/alias_function_output.py +0 -0
- {codesorter-0.2.3 → codesorter-0.2.4}/tests/test_files/augmented_assignment_input.py +0 -0
- {codesorter-0.2.3 → codesorter-0.2.4}/tests/test_files/augmented_assignment_output.py +0 -0
- {codesorter-0.2.3 → codesorter-0.2.4}/tests/test_files/basic_function_input.py +0 -0
- {codesorter-0.2.3 → codesorter-0.2.4}/tests/test_files/basic_function_output.py +0 -0
- {codesorter-0.2.3 → codesorter-0.2.4}/tests/test_files/class_global_dependency_input.py +0 -0
- {codesorter-0.2.3 → codesorter-0.2.4}/tests/test_files/class_global_dependency_output.py +0 -0
- {codesorter-0.2.3 → codesorter-0.2.4}/tests/test_files/class_inheritance_input.py +0 -0
- {codesorter-0.2.3 → codesorter-0.2.4}/tests/test_files/class_inheritance_output.py +0 -0
- {codesorter-0.2.3 → codesorter-0.2.4}/tests/test_files/class_method_input.py +0 -0
- {codesorter-0.2.3 → codesorter-0.2.4}/tests/test_files/class_method_output.py +0 -0
- {codesorter-0.2.3 → codesorter-0.2.4}/tests/test_files/classmethod_input.py +0 -0
- {codesorter-0.2.3 → codesorter-0.2.4}/tests/test_files/classmethod_output.py +0 -0
- {codesorter-0.2.3 → codesorter-0.2.4}/tests/test_files/comprehension_dependency_input.py +0 -0
- {codesorter-0.2.3 → codesorter-0.2.4}/tests/test_files/comprehension_dependency_output.py +0 -0
- {codesorter-0.2.3 → codesorter-0.2.4}/tests/test_files/comprehensive_input.py +0 -0
- {codesorter-0.2.3 → codesorter-0.2.4}/tests/test_files/comprehensive_output.py +0 -0
- {codesorter-0.2.3 → codesorter-0.2.4}/tests/test_files/constant_ordering_input.py +0 -0
- {codesorter-0.2.3 → codesorter-0.2.4}/tests/test_files/constant_ordering_output.py +0 -0
- {codesorter-0.2.3 → codesorter-0.2.4}/tests/test_files/custom_decorators_input.py +0 -0
- {codesorter-0.2.3 → codesorter-0.2.4}/tests/test_files/custom_decorators_output.py +0 -0
- {codesorter-0.2.3 → codesorter-0.2.4}/tests/test_files/keyword_arguments_input.py +0 -0
- {codesorter-0.2.3 → codesorter-0.2.4}/tests/test_files/keyword_arguments_output.py +0 -0
- {codesorter-0.2.3 → codesorter-0.2.4}/tests/test_files/lazy_annotation_cycle_input.py +0 -0
- {codesorter-0.2.3 → codesorter-0.2.4}/tests/test_files/lazy_annotation_cycle_output.py +0 -0
- {codesorter-0.2.3 → codesorter-0.2.4}/tests/test_files/mixed_decorators_input.py +0 -0
- {codesorter-0.2.3 → codesorter-0.2.4}/tests/test_files/mixed_decorators_output.py +0 -0
- {codesorter-0.2.3 → codesorter-0.2.4}/tests/test_files/name_rebinding_input.py +0 -0
- {codesorter-0.2.3 → codesorter-0.2.4}/tests/test_files/name_rebinding_output.py +0 -0
- {codesorter-0.2.3 → codesorter-0.2.4}/tests/test_files/order_sensitive_input.py +0 -0
- {codesorter-0.2.3 → codesorter-0.2.4}/tests/test_files/order_sensitive_output.py +0 -0
- {codesorter-0.2.3 → codesorter-0.2.4}/tests/test_files/property_input.py +0 -0
- {codesorter-0.2.3 → codesorter-0.2.4}/tests/test_files/property_output.py +0 -0
- {codesorter-0.2.3 → codesorter-0.2.4}/tests/test_files/pytest_fixtures_input.py +0 -0
- {codesorter-0.2.3 → codesorter-0.2.4}/tests/test_files/pytest_fixtures_output.py +0 -0
- {codesorter-0.2.3 → codesorter-0.2.4}/tests/test_files/staticmethod_input.py +0 -0
- {codesorter-0.2.3 → codesorter-0.2.4}/tests/test_files/staticmethod_output.py +0 -0
- {codesorter-0.2.3 → codesorter-0.2.4}/tests/test_files/underscore_ordering_input.py +0 -0
- {codesorter-0.2.3 → codesorter-0.2.4}/tests/test_files/underscore_ordering_output.py +0 -0
- {codesorter-0.2.3 → codesorter-0.2.4}/uv.lock +0 -0
|
@@ -5,18 +5,19 @@
|
|
|
5
5
|
codesorter follows `semantic versioning <https://semver.org/>`_.
|
|
6
6
|
|
|
7
7
|
********************
|
|
8
|
-
0.2.
|
|
8
|
+
0.2.4 (2026/06/14)
|
|
9
9
|
********************
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
0.2.2 (2026/06/14)
|
|
13
|
-
********************
|
|
11
|
+
**Fixed**
|
|
14
12
|
|
|
15
|
-
|
|
13
|
+
- Never reorder a definition across a side-effecting statement. A bare statement such as
|
|
14
|
+
``sys.path.insert(0, str(REPO_ROOT))`` is now a barrier, and sorting happens only
|
|
15
|
+
within each segment between barriers, so a constant the statement uses is no longer
|
|
16
|
+
hoisted after it (which raised ``NameError``).
|
|
16
17
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
********************
|
|
19
|
+
0.2.3 (2026/06/14)
|
|
20
|
+
********************
|
|
20
21
|
|
|
21
22
|
**Fixed**
|
|
22
23
|
|
|
@@ -27,6 +28,16 @@ codesorter follows `semantic versioning <https://semver.org/>`_.
|
|
|
27
28
|
getter/setter/deleter groups, which carry no assignment, are still ordered by their
|
|
28
29
|
sort key.
|
|
29
30
|
|
|
31
|
+
********************
|
|
32
|
+
0.2.2 (2026/06/14)
|
|
33
|
+
********************
|
|
34
|
+
|
|
35
|
+
**Changed**
|
|
36
|
+
|
|
37
|
+
- Format reordered files with ``ruff`` by default instead of ``black``, so a downstream
|
|
38
|
+
project no longer needs a ``.libcst.codemod.yaml`` to use the ``codesorter`` CLI or
|
|
39
|
+
pre-commit hook. ``ruff`` must be importable on the ``PATH`` when sorting.
|
|
40
|
+
|
|
30
41
|
********************
|
|
31
42
|
0.2.1 (2026/06/14)
|
|
32
43
|
********************
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codesorter
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.4
|
|
4
4
|
Summary: A Python codemod that sorts and organizes code in your files.
|
|
5
5
|
Project-URL: Change Log, https://codesorter.readthedocs.io/en/latest/package_info/change_log.html
|
|
6
6
|
Project-URL: Documentation, https://codesorter.readthedocs.io/
|
|
@@ -488,6 +488,44 @@ class SortCodeCommand(VisitorBasedCodemodCommand, m.MatcherDecoratableTransforme
|
|
|
488
488
|
property_type,
|
|
489
489
|
)
|
|
490
490
|
|
|
491
|
+
def _reorder_segment(
|
|
492
|
+
self,
|
|
493
|
+
body: Sequence[cst.BaseStatement],
|
|
494
|
+
new_body: list[cst.BaseStatement],
|
|
495
|
+
*,
|
|
496
|
+
anchor_indexes: list[int],
|
|
497
|
+
trailers_of: dict[int, list[int]],
|
|
498
|
+
) -> None:
|
|
499
|
+
"""Reorder one barrier-free segment of sortable members in place within ``new_body``."""
|
|
500
|
+
if not anchor_indexes:
|
|
501
|
+
return
|
|
502
|
+
positions: list[int] = []
|
|
503
|
+
anchor_nodes: list[_Sortable] = []
|
|
504
|
+
trailers_by_anchor: dict[int, list[cst.BaseStatement]] = {}
|
|
505
|
+
for index in anchor_indexes:
|
|
506
|
+
trailer_indexes = sorted(trailers_of.get(index, []))
|
|
507
|
+
positions.append(index)
|
|
508
|
+
positions.extend(trailer_indexes)
|
|
509
|
+
anchor = cast("_Sortable", body[index])
|
|
510
|
+
anchor_nodes.append(anchor)
|
|
511
|
+
trailers_by_anchor[id(anchor)] = [body[trailer] for trailer in trailer_indexes]
|
|
512
|
+
# Blank-line separators are positional: the n-th anchor in the segment keeps the
|
|
513
|
+
# n-th separator. Trailers are excluded so they stay tight to their anchor.
|
|
514
|
+
anchor_separators = [_split_leading_lines(node)[0] for node in anchor_nodes]
|
|
515
|
+
trailer_ids = {id(trailer) for trailers in trailers_by_anchor.values() for trailer in trailers}
|
|
516
|
+
flattened: list[cst.BaseStatement] = []
|
|
517
|
+
for anchor in self._sorted_items(anchor_nodes):
|
|
518
|
+
flattened.append(anchor)
|
|
519
|
+
flattened.extend(trailers_by_anchor[id(anchor)])
|
|
520
|
+
anchor_index = 0
|
|
521
|
+
for position, member in zip(sorted(positions), flattened, strict=True):
|
|
522
|
+
if id(member) in trailer_ids:
|
|
523
|
+
new_body[position] = member
|
|
524
|
+
continue
|
|
525
|
+
_, attached = _split_leading_lines(member)
|
|
526
|
+
new_body[position] = member.with_changes(leading_lines=[*anchor_separators[anchor_index], *attached])
|
|
527
|
+
anchor_index += 1
|
|
528
|
+
|
|
491
529
|
def _resolve_dependents(self, node: _Sortable) -> None:
|
|
492
530
|
dependencies, _ = self._get_dependencies(node)
|
|
493
531
|
name = _sortable_name(node)
|
|
@@ -513,40 +551,27 @@ class SortCodeCommand(VisitorBasedCodemodCommand, m.MatcherDecoratableTransforme
|
|
|
513
551
|
Blank-line spacing stays with each slot while comment lines travel with their
|
|
514
552
|
statement, so reordering never shifts a blank line onto a different statement.
|
|
515
553
|
|
|
554
|
+
Sortable members are only reordered within a segment of consecutive sortable
|
|
555
|
+
members; any other statement (an import, a bare expression such as
|
|
556
|
+
``sys.path.insert(...)``, an ``if`` block) is a barrier that no definition may
|
|
557
|
+
cross, since the barrier may depend on a definition beside it.
|
|
558
|
+
|
|
516
559
|
"""
|
|
517
560
|
anchors = self._anchor_trailers(body, sort_constants=sort_constants)
|
|
518
561
|
trailers_of: defaultdict[int, list[int]] = defaultdict(list)
|
|
519
562
|
for trailer_index, anchor_index in anchors.items():
|
|
520
563
|
trailers_of[anchor_index].append(trailer_index)
|
|
521
|
-
positions: list[int] = []
|
|
522
|
-
anchor_nodes: list[_Sortable] = []
|
|
523
|
-
trailers_by_anchor: dict[int, list[cst.BaseStatement]] = {}
|
|
524
|
-
for index, member in enumerate(body):
|
|
525
|
-
if index in anchors or not self._is_sortable(member, sort_constants=sort_constants):
|
|
526
|
-
continue
|
|
527
|
-
trailer_indexes = sorted(trailers_of.get(index, []))
|
|
528
|
-
positions.append(index)
|
|
529
|
-
positions.extend(trailer_indexes)
|
|
530
|
-
anchor = cast("_Sortable", member)
|
|
531
|
-
anchor_nodes.append(anchor)
|
|
532
|
-
trailers_by_anchor[id(anchor)] = [body[trailer] for trailer in trailer_indexes]
|
|
533
|
-
# Blank-line separators are positional: the n-th anchor in the body keeps the
|
|
534
|
-
# n-th separator. Trailers are excluded so they stay tight to their anchor.
|
|
535
|
-
anchor_separators = [_split_leading_lines(node)[0] for node in anchor_nodes]
|
|
536
|
-
trailer_ids = {id(trailer) for trailers in trailers_by_anchor.values() for trailer in trailers}
|
|
537
|
-
flattened: list[cst.BaseStatement] = []
|
|
538
|
-
for anchor in self._sorted_items(anchor_nodes):
|
|
539
|
-
flattened.append(anchor)
|
|
540
|
-
flattened.extend(trailers_by_anchor[id(anchor)])
|
|
541
564
|
new_body = list(body)
|
|
542
|
-
|
|
543
|
-
for
|
|
544
|
-
if
|
|
545
|
-
|
|
565
|
+
segment: list[int] = []
|
|
566
|
+
for index, member in enumerate(body):
|
|
567
|
+
if index in anchors:
|
|
568
|
+
continue # a trailer, reordered together with its anchor
|
|
569
|
+
if self._is_sortable(member, sort_constants=sort_constants):
|
|
570
|
+
segment.append(index)
|
|
546
571
|
continue
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
572
|
+
self._reorder_segment(body, new_body, anchor_indexes=segment, trailers_of=trailers_of)
|
|
573
|
+
segment = []
|
|
574
|
+
self._reorder_segment(body, new_body, anchor_indexes=segment, trailers_of=trailers_of)
|
|
550
575
|
return new_body
|
|
551
576
|
|
|
552
577
|
def _sorted_items(
|
|
@@ -48,6 +48,22 @@ class TestSortCodeCommand:
|
|
|
48
48
|
|
|
49
49
|
assert expected_code == result.code
|
|
50
50
|
|
|
51
|
+
def test_barrier(self, test_files):
|
|
52
|
+
"""Test that definitions are not reordered across a side-effecting statement.
|
|
53
|
+
|
|
54
|
+
A bare statement such as ``sys.path.insert(0, str(REPO_ROOT))`` is a barrier:
|
|
55
|
+
the constants before it (which it may use) must stay before it, so sorting
|
|
56
|
+
happens only within each segment between barriers rather than across the whole
|
|
57
|
+
module.
|
|
58
|
+
|
|
59
|
+
"""
|
|
60
|
+
input_code, expected_code = test_files
|
|
61
|
+
context = CodemodContext()
|
|
62
|
+
command = SortCodeCommand(context)
|
|
63
|
+
result = command.transform_module(cst.parse_module(input_code))
|
|
64
|
+
|
|
65
|
+
assert expected_code == result.code
|
|
66
|
+
|
|
51
67
|
def test_basic_function(self, test_files):
|
|
52
68
|
"""Test that functions are sorted correctly."""
|
|
53
69
|
input_code, expected_code = test_files
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|