Skip to content

Unused link reference definitions leak into the output (legacy path) #1061

Description

@tivie

Summary

Link reference definitions that are never referenced leak into the HTML output as paragraphs, instead of being stripped. This affects every flavor that uses the legacy parsing path (default/vanilla, original, ghost). The commonmark and gfm flavors are not affected — the CommonMark definition scanner handles all cases correctly.

Repro

const conv = new showdown.Converter(); // any non-cmSpec flavor

conv.makeHtml('[unused]: http://example.com/');
// actual:   "<p>[unused]: http://example.com/</p>"
// expected: ""

Not limited to document boundaries — any unreferenced definition leaks:

conv.makeHtml('one\n\n[unused]: /url\n\ntwo');
// actual:   "<p>one</p>\n<p>[unused]: /url</p>\n<p>two</p>"
// expected: "<p>one</p>\n<p>two</p>"

Expected behavior

Link reference definitions are metadata and must produce no output, referenced or not:

  • Markdown.pl 1.0.1 strips definitions unconditionally (verified against the actual script: [unused]: http://example.com/ as the sole document content yields empty output).
  • CommonMark is explicit about it; spec example 207 is exactly this case ([foo]: /url → empty output), and our commonmark flavor already passes it.
  • specs/original.md covers it under Link reference definitions ("A definition that is never used still produces no output"); the corresponding case is currently skipped in testsuite.original.js pending this fix.

Root cause

The legacy branch of src/subParsers/makehtml/stripLinkDefinitions.js deliberately backs out of stripping when the link id occurs fewer than two times in the document:

// if there aren't two instances of linkId it must not be a reference link so back out
linkId = showdown.helper.caseFold(linkId);
if (showdown.helper.caseFold(text).split(linkId).length - 1 < 2) {
  return wholeMatch;
}

The heuristic itself contradicts both original Markdown and CommonMark, and its implementation makes the outcome depend on coincidental substrings, because it counts raw substring occurrences anywhere in the text rather than actual references:

input output why
[unused]: /url leaks as <p> id occurs once
this feature is unused + blank line + [unused]: /url stripped the word "unused" in prose counts as a second instance
[a]: /a stripped the a in its own URL counts
[zqx]: /url leaks no coincidental substring anywhere

Proposed fix

Remove the back-out check and strip definitions unconditionally, matching Markdown.pl, the CommonMark spec, and the behavior of the cmSpec scanner (parseCmLinkDefinitions) in the same file.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions