Skip to content

fix: preserve trailing comments when sorting literals - #2647

Open
Manny7717 wants to merge 1 commit into
PyCQA:mainfrom
Manny7717:fix/trailing-comment-in-literal-sort
Open

fix: preserve trailing comments when sorting literals#2647
Manny7717 wants to merge 1 commit into
PyCQA:mainfrom
Manny7717:fix/trailing-comment-in-literal-sort

Conversation

@Manny7717

Copy link
Copy Markdown

Problem

Sorting a literal drops a trailing comment, and crashes when the comment (or a string inside the literal) contains =.

Repro (with --sort-reexports):

__all__ = ["b", "a"]  # public names

isort currently produces:

__all__ = ["a", "b"]

The comment # public names is silently deleted. With a comment containing = (or any literal containing = in a string), isort crashes instead:

__all__ = ["b", "a"]  # note: key = value
ValueError: too many values to unpack (expected 2, got 3)

This affects both --sort-reexports and the # isort: list / # isort: dict / # isort: tuple / # isort: set inline directives. Closes #2646.

Root cause

Two split("=") calls without a maxsplit:

  1. isort/core.py reexport detection: _, rhs = stripped_line.split("=") — the comment # note: key = value contains two =, so the unpacking fails.
  2. isort/literal.py assignment(): variable_name, literal = code.split("=") — same crash when the comment or a string element (e.g. ["a=b", "c"]) contains =.

Separately, assignment() rebuilt the line as <name> = <sorted literal> and then only re-appended the trailing whitespace of the original line (code[len(code.rstrip()):]), so anything that followed the literal — i.e. a trailing comment — was discarded.

Fix

  • isort/core.py: split on the first = only (split("=", 1)).
  • isort/literal.py:
    • split on the first = only;
    • parse the literal once with ast.parse(..., mode="eval") (still evaluated via ast.literal_eval), then use the value node's end position to re-append everything that followed the literal verbatim — trailing comments and any trailing whitespace survive the rewrite.

Tests

  • tests/unit/test_literal.py (4 new): trailing comment preserved; trailing comment containing = preserved; literal with = inside a string sorts; trailing comment on a multi-line literal preserved.
  • tests/unit/test_isort.py (3 new): end-to-end --sort-reexports and # isort: list cases, including a comment containing = (the exact crash from the issue).

Regression-proven: all 7 new tests fail on pre-fix main (4 crash with ValueError: too many values to unpack, 3 silently drop the comment) and pass with the fix. The no-comment path is unchanged (existing test_reexport_* suite still green).

Verification

  • Full unit suite: 628 passed / 4 failed / 1 skipped — the 4 failures (issue_909, issue_938, issue_970, issue_1732) are pre-existing FileNotFoundError env noise, byte-identical on clean main.
  • tests/integration/: 12 passed, 1 skipped.
  • mypy isort/literal.py isort/core.py: clean.
  • ruff check / ruff format --check / flake8 / isort self-check on changed files: clean.

Note: this PR intentionally does not change the separate "statement after the literal" handling (that's #2286 / PR #2627's scope); it only fixes the comment-drop and the = crash.

AI assistance disclosure: this contribution was developed with assistance from an AI coding agent (Hermes/Lappy on behalf of Manny7717). All changes were locally reproduced, regression-tested (failing before the fix), and verified against the full test suite before opening.

Sorting a literal (sort_reexports or # isort: list/dict/tuple) silently
dropped any trailing comment on the assignment line, e.g.
'__all__ = ["b", "a"]  # exports' became '__all__ = ["a", "b"]'.
It also crashed with 'too many values to unpack' whenever the comment
(or a string inside the literal) contained '=', because both call sites
used split("=") without a maxsplit.

- literal.assignment: split on the first '=' only, and re-append
  everything after the parsed value's end position (the AST node end)
  so trailing comments survive the rewrite.
- core reexport detection: split on the first '=' only.

Closes PyCQA#2646
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Sorting a literal drops a trailing comment, and raises if the comment contains "="

1 participant