Skip to content

fix: keep a backslash in a code span from hiding emphasis - #4117

Merged
UziTech merged 2 commits into
markedjs:masterfrom
breken-ai:fix/code-span-backslash-emphasis
Oct 10, 2026
Merged

UziTech merged 2 commits into
markedjs:masterfrom
breken-ai:fix/code-span-backslash-emphasis

Conversation

@breken-ai

Copy link
Copy Markdown
Contributor

Marked version: 18.0.14 (master at 4ee44f7)

Markdown flavor: CommonMark (also GitHub Flavored Markdown)

Description

No existing issue that I could find.

Expectation

A code span that ends in a backslash does not affect emphasis later in the paragraph. This line from the minimatch README:

Note that `\` or `/` _will_ be interpreted as path separators in paths on
Windows, and will match against `/` in glob expressions.

should render <em>will</em>, as it does in the CommonMark reference implementation, markdown-it and on GitHub.

Result

<p>Note that <code>\</code> or <code>/</code> _will_ be interpreted as path separators in paths on
Windows, and will match against <code>/</code> in glob expressions.</p>

The underscores come through as literal text. The same happens with **bold** in the glob README (`\` characters to `/` in pattern strings, **thus making it impossible...**) and with any Windows path in code, e.g. Use `C:\Users\` for **home** and `D:\` for data. Minimal case: `a\` *b* `c`.

What was attempted

In #inlineTokensInner, the em/strong mask replaces every backslash escape (anyPunctuation) before blockSkip masks code spans. A backslash is literal inside a code span, but the mask still turned \` into ++. That removed the closing backtick of `\`, so blockSkip paired the opening backtick with the next code span's backtick and masked everything in between, including the emphasis delimiters. This ordering dates from #2838.

The fix adds anyPunctuationOutsideCode. It matches a code span (same shape as inline.code) or an unmatched backtick run whole and leaves it unchanged, and masks only the escapes outside code spans. Escaped backticks outside code still work: \`*escaped tick*\` keeps its emphasis. anyPunctuation itself is unchanged because the tokenizer also uses it to unescape hrefs, titles and info strings.

Test: test/specs/new/codespan_backslash_emphasis.md. It fails on master and passes with the fix. npm run test:specs (1865/1865), test:unit, test:cjs, test:umd, test:types and test:lint all pass. As a further check, I compared this branch with master on 300k generated inputs mixing backticks, backslashes and delimiters. Every output that changed now matches commonmark.js (or markdown-it with gfm: true), and none moved away from it.

This change was prepared with the help of an AI coding assistant, and I reviewed and tested it.

Contributor

  • Test(s) exist to ensure functionality and minimize regression (if no tests added, list tests covering this PR); or,
  • no tests required for this PR.
  • If submitting new feature, it has been documented in the appropriate places.

Committer

In most cases, this should be a different person than the contributor.

The em/strong mask replaced every backslash escape before it masked
code spans, but a backslash inside a code span is literal. A code span
ending in `\` therefore lost its closing backtick in the mask, so the
next backtick paired with it and the emphasis between them was masked
out and rendered as literal `_` or `*`.

Mask escapes only outside code spans.
@vercel

vercel Bot commented Sep 30, 2026

Copy link
Copy Markdown

@breken-ai is attempting to deploy a commit to the MarkedJS Team on Vercel.

A member of the Team first needs to authorize it.

@vercel

vercel Bot commented Sep 30, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
marked-website Ready Ready Preview Oct 8, 2026 2:44am UTC

Request Review

@UziTech UziTech left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks! 💯

@styfle styfle left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Looks good though we deviate a bit from commonmark. Here's an example:

https://spec.commonmark.org/dingus/?text=_%60%60%5C%60_%60%60

@breken-ai

breken-ai commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor Author

@styfle thanks for the example. It was a regression from this PR. master renders _``\`_`` as <p>_<code>\`_</code></p> (the same as the CommonMark dingus), but the earlier head 521ec76 rendered <p><em>```</em>``</p>.

Here is why. Before this PR, masking \` as ++ happened to leave that code span with no backticks inside, so blockSkip ((?<b>`+)[^`]+\k<b>(?!`)) masked the whole span. Once the PR stopped masking escapes inside code spans, the inner backtick stayed, blockSkip no longer matched the span, and the _ inside it closed the emphasis.

Fixed in 7d5c8c9. Inside a code span that the escape mask matches, the inner backticks are now masked to + (the length stays the same), so blockSkip skips the whole span again. The other characters are untouched. I added two cases to test/specs/new/codespan_backslash_emphasis.md:

_``\`_``

_`` a`_ ``

These now render as <p>_<code>\`_</code></p> and <p>_<code>a`_</code></p>. On this spec file the fix matches the commonmark reference CLI byte for byte. The spec fails on 521ec76 (Actual: …<p><em>```</em>``</p>) and passes on 7d5c8c9. test:specs passes 1865/1865, test:unit passes 193/193, and test:lint and test:types are clean.

@UziTech UziTech left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for the fix!

@UziTech
UziTech merged commit 6a57018 into markedjs:master Oct 10, 2026
8 checks passed
github-actions Bot pushed a commit that referenced this pull request Oct 12, 2026
## [18.1.1](v18.1.0...v18.1.1) (2026-10-12)

### Bug Fixes

* **cli:** honor an explicitly empty string input ([#4113](#4113)) ([ec3a4e5](ec3a4e5))
* end blockquote before a lazy line after a heading, hr or html block ([#4115](#4115)) ([9e64a6a](9e64a6a))
* keep a backslash in a code span from hiding emphasis ([#4117](#4117)) ([6a57018](6a57018))
* keep a decoded character reference from forming a new one ([#4111](#4111)) ([6bbac0c](6bbac0c)), closes [#38](#38) [#112](#112)
* let indented and type 3-5 html blocks interrupt a paragraph ([#4124](#4124)) ([eaa1fff](eaa1fff))
* make walkTokens linear by appending values in place ([#4121](#4121)) ([627d9ae](627d9ae)), closes [#4120](#4120)
* match pre, script, style and textarea as whole tag names ([#4114](#4114)) ([0db74cb](0db74cb))
* preserve enclosing link state after parsing images ([#4123](#4123)) ([cc07bb9](cc07bb9))
* respect tab stops in list continuation indentation ([#4119](#4119)) ([4bd2528](4bd2528))

### Performance Improvements

* avoid repeated raw string copies in nested lists ([#4082](#4082)) ([7e8754d](7e8754d))

This branch was successfully deployed

1 active deployment
Preview — 7d5c8c9f Deployed Oct 8, 2026 by vercel[bot]
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.

3 participants