DavidWells / DavidWells/markdown-magic

TOC block corrupts multi-block files: sortTransforms reorders TOC last but splice offsets assume document order

Open
#127 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
JavaScript
Stars
868
Forks
223
PR merge metrics
No merged PRs in 30d

Description

Summary

In comment-block-transformer@0.7.0 (via markdown-magic@4.8.0), any file that contains a TOC block plus at least one other block whose replacement content differs in length from its previous content gets corrupted: content is spliced at wrong offsets, producing fused/duplicated text like:

<!-- /docs -->te](#using-this-template)

This happens whenever the TOC block appears earlier in the document than another block — the common "TOC at the top, generated tables below" layout.

Root cause

Two pieces of comment-block-transformer/src/index.js don't compose:

  1. sortTransforms() always moves blocks typed TOC/sectionToc to the end of processing order, regardless of document position (so TOC can see other blocks' final content). This reordering happens even when no TOC transform is registered — it keys on the block's type string alone.
  2. The splice loop assumes strict document-order processing: it keeps a single running cumulativeOffset and adds it to each block's original block.start. When TOC is processed last but positioned first, the accumulated offset includes length deltas from blocks after it in the document, so its splice position is garbage.

Because every block is re-spliced (including missing-transform blocks, whose reconstruction is not byte-identical), the corruption triggers even with TOC unregistered, registered as an identity function, or processed in a separate call.

Repro
const { markdownMagic } = require("markdown-magic");

// file.md:
// <!-- docs TOC -->
// <!-- /docs -->
// ## Section
// <!-- docs CODE src=./snippet.js -->   (any block whose output length differs)
// <!-- /docs -->

await markdownMagic(["./file.md"], { dryRun: true, dry: true });
// results[0].updatedContents contains fused/duplicated text

Bisect: {TOC} alone → clean; {TOC + short-delta block} → subtly wrong offsets; {TOC + long-delta block} → visibly corrupted every time.

Fix (patch we're running in production)

Track per-block deltas and, when splicing, apply only the deltas of blocks earlier in the document than the block being spliced — preserving the TOC-last processing order while making the offset math order-independent:

-  let cumulativeOffset = 0
+  const appliedDeltas = []
+  const offsetBefore = (start) => appliedDeltas.reduce((sum, edit) => (edit.start < start ? sum + edit.delta : sum), 0)
   ...
-    const adjustedStart = block.start + cumulativeOffset
-    const adjustedEnd = block.end + cumulativeOffset
+    const adjustedStart = block.start + offsetBefore(block.start)
+    const adjustedEnd = block.end + offsetBefore(block.start)
   ...
-    cumulativeOffset += (newCont.length - block.value.length)
+    appliedDeltas.push({ start: block.start, delta: newCont.length - block.value.length })

With this applied, a TOC-at-top + generated-tables file reaches a byte-stable fixed point in one pass. Happy to open a PR if useful.

Environment: markdown-magic@4.8.0 / comment-block-transformer@0.7.0, Node 25.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in comment-block-transformer/src/index.js, inspect sortTransforms and the splice loop, then reproduce the provided TOC-at-top case through markdownMagic with a length-changing block. Done means the generated file is not corrupted and reaches a byte-stable fixed point in one pass.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript, node.js
Domain
tooling
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
72/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.