fix(MD013): preserve list marker spacing to prevent corruption of nested code blocks#710
Conversation
…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
left a comment
There was a problem hiding this comment.
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
|
Should be updated, and passing all CI now, PTAL! |
|
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 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 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. |
|
SG, just happy with fixes. =] |
|
Thanks again @chandlerc for the issue and the work you've put in the PRs, really appreciated! 🚀 |
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:
trim_start_matches(' ')instead oftrim_start(). This ensures tabs (e.g.,- \tfoo) are not treated as extra spacing and are kept out of the list marker.- [ ] task) are correctly recognized as part of the marker prefix, protecting them from corruption during reflow.test_issue_509_*suite intests/formats/md013_mkdocs_reflow_test.rsto 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