Skip to content

fix(MD013): preserve list marker spacing to prevent corruption of nested code blocks#710

Closed
chandlerc wants to merge 2 commits into
rvben:mainfrom
chandlerc:fixmd013-preserve-list-marker-spacing-to/tmppumlnuuwn
Closed

fix(MD013): preserve list marker spacing to prevent corruption of nested code blocks#710
chandlerc wants to merge 2 commits into
rvben:mainfrom
chandlerc:fixmd013-preserve-list-marker-spacing-to/tmppumlnuuwn

Conversation

@chandlerc

@chandlerc chandlerc commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

When reflowing list items, the linter reconstructed the list marker with a default/configured spacing (e.g. 1 space) without accounting for the original spacing. This caused misalignment and corruption of nested code blocks because the indentation shift was calculated incorrectly.

Now we extract and preserve the exact spacing after the bullet or numbered marker, ensuring that continuation lines and nested blocks maintain their relative alignment.

Updates addressing review feedback:

  • Tab Handling: Refined extra space extraction to use trim_start_matches(' ') instead of trim_start(). This ensures tabs (e.g., - \tfoo) are not treated as extra spacing and are kept out of the list marker.
  • GFM Checkbox Recognition: Hoisted the space extraction above the checkbox checks. This ensures checkboxes with extra preceding spaces (e.g., - [ ] task) are correctly recognized as part of the marker prefix, protecting them from corruption during reflow.
  • MkDocs Reflow Test Expectations: Updated the expectations for the test_issue_509_* suite in tests/formats/md013_mkdocs_reflow_test.rs to preserve the wider list marker spacing. This is the correct behavior for MD013 in isolation, as MD030 will eventually normalize the marker and reindent the body during a full run (thus preserving spacing inside MD013 never leaks wider markers into the final output).

Assisted-by: Antigravity with Gemini

…ted code blocks

When reflowing list items, the linter reconstructed the list marker
with a default/configured spacing (e.g. 1 space) without accounting
for the original spacing. This caused misalignment and corruption of
nested code blocks because the indentation shift was calculated
incorrectly.

Now we extract and preserve the exact spacing after the bullet or
numbered marker, ensuring that continuation lines and nested blocks
maintain their relative alignment.

Assisted-by: Antigravity with Gemini

@rvben rvben left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @chandlerc, the diagnosis here is right. marker_len at md013_line_length.rs:1972 is the length of the normalized marker, but it is used as the baseline for shift = new_col - marker_len, which repositions nested blocks. When the source marker is wider than one space that baseline is wrong. I confirmed your new test fails on main exactly as described (fence moves 4 -> 6).

Before this can land, three things:

1. Six tests in tests/formats/md013_mkdocs_reflow_test.rs fail (CI reports three): the test_issue_509_* set. This is structural, not incidental. For MkDocs, md013_line_length.rs:2426 takes the else branch and emits the extracted marker verbatim, bypassing the MD030 expected_spaces rebuild, so preserving source spacing changes MkDocs output.

I think those expectations are wrong and should be updated to your output. Running the full rumdl fmt on the #509 reporter's input (MkDocs flavor, default MD030):

main:  !!! note at col 2, ```yaml at col 8   (6 past the header)
PR:    !!! note at col 2, ```yaml at col 6   (correct)

MD030 fires in that same run and rewrites - back to - , reindenting the body with it, so preserving spacing inside MD013 never leaks a wider marker into real output. The old expectations only hold when MD013::fix() is called in isolation with MD030 off. Please update them and note the reasoning in the PR description.

2. Tabs are an off-by-one. rest.len() - rest.trim_start().len() counts bytes and trim_start() strips all Unicode whitespace. For - \tfoo the marker becomes - \t (3 bytes) for a content column of 4:

in:   "- \tsome long text...\n\n    ```\n    x\n    ```\n"
PR:   fence lands at col 3   (should be 2, the new content column)

rest.trim_start_matches(' ') fixes it and keeps tabs out of the marker entirely.

3. Optional, but you are already in this function. The checkbox loops run against rest, which still carries the extra spaces, so strip_prefix("[ ] ") never matches for - [ ] task. It falls through to the prose path and reflow rewrites [ ] into [], silently turning a task item into plain text. This is pre-existing on main, not caused by your change, but hoisting the space extraction above the checkbox loops and matching on content instead of rest fixes both in one move.

One question: min_continuation_indent = marker_len (md013_line_length.rs:1993) goes from 2 to 4 for a - item, so a 3-space lazy continuation is no longer collected into the paragraph and stops being reflowed. Rendering is unchanged and I confirmed the fixer stays idempotent, so this is not blocking. Intentional?

…t expectations

1. Exclude tabs from extra spaces in list markers by using
   `trim_start_matches(' ')` instead of `trim_start()` in
   `extract_list_marker_and_content`.
2. Hoist space extraction above GFM checkbox checks so checkboxes with
   extra preceding spaces (e.g. `-   [ ] task`) are correctly recognized
   and not corrupted during reflow.
3. Update expected outputs in `md013_mkdocs_reflow_test.rs` to preserve
   list marker spacing, which is the correct behavior for MD013 in
   isolation (MD030 will eventually normalize the spacing and reindent
   the body in a full run).

Assisted-by: Antigravity with Gemini
@chandlerc

Copy link
Copy Markdown
Contributor Author

Should be updated, and passing all CI now, PTAL!

@rvben

rvben commented Jul 9, 2026

Copy link
Copy Markdown
Owner

Thanks for the quick turnaround, and I owe you an awkward update: this PR got superseded on main, but your diagnosis and your tests are what landed.

While re-testing the branch I convinced myself the conflict with main is semantic, not just textual. f0f97a2 (the checkbox and tab fix that landed this week) makes tabs and extra spaces after a marker part of the marker padding, normalized away on emit. This branch preserves extra spaces in the emitted marker and keeps tabs out of it entirely. Both are self-consistent, but they disagree about what a marker is, and every rebase would have to re-litigate that.

So I took a different cut at the same bug and landed it as 54f1359, released in v0.2.30. Instead of preserving source spacing, it separates the two quantities that extract_list_marker_and_content conflates: the normalized marker that gets re-emitted, and the source content column that the indentation arithmetic must measure against. A new source_list_marker() helper reports the marker exactly as written plus the column where its content starts, with tabs expanded to CommonMark's 4-column stops. The reflow shift is measured against that column. Normalization stays as it was.

Your acceptance test and your updated MkDocs expectations are on main verbatim; I used them as the acceptance criteria for the design. They pass alongside the checkbox and tab tests, and the min_continuation_indent questions from my review are moot under this shape because the collection thresholds are untouched.

If you can point your real corpus at v0.2.30 and confirm the corruption is gone, I'll close this out. If anything still misbehaves, that is a bug in the landed approach and I want to know about it.

@chandlerc

Copy link
Copy Markdown
Contributor Author

SG, just happy with fixes. =]

@chandlerc chandlerc closed this Jul 10, 2026
@rvben

rvben commented Jul 10, 2026

Copy link
Copy Markdown
Owner

Thanks again @chandlerc for the issue and the work you've put in the PRs, really appreciated! 🚀

@chandlerc chandlerc deleted the fixmd013-preserve-list-marker-spacing-to/tmppumlnuuwn branch July 10, 2026 07:19
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.

2 participants