Skip to content

Changed code blocks lose their fence and get mangled (0.8.0 fails its own test/diff.md) #62

Description

@tanghong123

Changed code blocks lose their fence and get mangled (pandiff 0.8.0 fails its own test/diff.md)

Summary

When a code block differs between the two inputs, the rendered output is corrupted rather than
merely ugly: the fence is dropped, the block round-trips back as a paragraph, lines are
reflowed together, and Pandoc's smart punctuation rewrites the contents — so --config becomes
–config and "world." becomes “world.”. Copy-pasting a command out of a pandiff diff can
silently give you the wrong command.

This is a regression: the committed golden file test/diff.md still describes the correct
behaviour, so pandiff 0.8.0 fails its own test suite.

Reproducing with the repo's own fixtures

$ pandiff test/old.md test/new.md | sed -n '14,16p'
print(“Hello”) -print(“world.”) +print(“world!”) print(“Lorem ipsum
dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
incididunt”)

Expected, per test/diff.md lines 14-19:

``` diff
 print("Hello")
-print("world.")
+print("world!")
 print("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt")

Note the straight quotes in the golden vs. the curly quotes in the actual output.

Environment: pandiff 0.8.0 (npm), pandoc 3.10.2, node 24, macOS.

## Cause 1 — `pre.textContent` destroys the `<code>` child

`src/index.ts:145`:

```ts
pre.className = 'diff';
pre.textContent = diffu(pre.textContent || '', post.textContent || '');

Assigning to textContent replaces every child of the <pre>, including its <code>.
Pandoc's HTML reader only treats <pre> as a code block when it wraps a <code>; a bare
<pre> is read as a paragraph. That single character of markup is the difference:

$ printf '<pre class="diff">-a --config\n+b --config</pre>' | pandoc -f html -t markdown
-a --config +b --config                    # paragraph: reflowed, and --  ->  en dash

$ printf '<pre class="diff"><code>-a --config\n+b --config</code></pre>' | pandoc -f html -t markdown
``` diff
-a --config
+b --config

## Cause 2 — highlighted blocks lose their line breaks

Separately, `convert()` builds the intermediate HTML with Pandoc's default syntax highlighting
enabled. Pandoc then emits one `<span id="cb1-N">` per line, with the newlines as text nodes
*between* the spans. `node-htmldiff` does not preserve those, so a changed block that has a
language tag collapses onto a single line even once cause 1 is fixed. Plain ``` blocks are
unaffected, which is why `test/old.md` (an indented block) only exposes cause 1.

## Proposed fix

Two small changes, both in `src/index.ts`:

1. Put the diff text in a `<code>` child instead of overwriting `pre.textContent`.
2. Pass `--no-highlight` to the **intermediate** conversion only. The final render is a separate
   Pandoc call and keeps highlighting, so ``` diff output is still colourised. I used
   `--no-highlight` rather than the modern `--syntax-highlighting=none` so the change still works
   on older Pandoc, but note that Pandoc 3.10 deprecates it and warns; you may prefer the new
   spelling, or to select it by version.

With both applied, `pandiff test/old.md test/new.md` matches `test/diff.md` apart from the image
paths, which the test harness supplies via its `extract-media` / `resource-path` options and the
bare CLI invocation does not.

## On the test suite

I could not use `npm test` as a gate: in my environment it reports 0 passing / 27 failing on
**unmodified** `master` as well (ENOENT on `test/*.md` and timeouts), identically before and after
the change, so the failures look environmental rather than related. The evidence above is the
direct `test/diff.md` comparison, which only the patched build matches. Your CI/devcontainer may
well behave differently.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions