Visitar URL original
[lexical-markdown] Bug Fix: Keep backslashes in leading code spans and unescape text only once by WeAreResilience · Pull Request #9396 · meta/lexical · GitHub
Skip to content

[lexical-markdown] Bug Fix: Keep backslashes in leading code spans and unescape text only once - #9396

Merged
etrepum merged 2 commits into
meta:mainfrom
WeAreResilience:fix/markdown-escape-once
Oct 20, 2026
Merged

etrepum merged 2 commits into
meta:mainfrom
WeAreResilience:fix/markdown-escape-once

Conversation

@WeAreResilience

@WeAreResilience WeAreResilience commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

Description

Markdown import unescaped the original text node after transformers finished, even when that node had already been processed recursively or converted to inline code. This removed literal backslashes from leading code spans and decoded escapes twice in reused text nodes.

Only unescape unmatched text, plus retained non-code text that a text-match transformer leaves behind without returning a node for recursive processing. This also handles custom transformers that edit or move the supplied node and the built-in link transformer when it declines a match.

Retained nodes are checked for a parent so detached Markdown generation works too. Code spans and newly generated replacement text keep their literal content.

Closes #9395
Closes #9333

Test plan

Added the four import/export regressions from #9395 and 40 cases covering retained text matches, escaped punctuation, character references, declined links, returned and moved nodes, code, and replacement text. Cases run with and without a prefix in attached and detached trees.

Before

The four #9395 import regressions fail on the base.

The retained-match tests against the earlier PR head report:

Test Files  1 failed (1)
     Tests  28 failed | 12 passed (40)

After

$ node node_modules/vitest/vitest.mjs run --project unit packages/lexical-markdown packages/lexical-mdast packages/lexical-playground/__tests__/unit --maxWorkers=1 --vmMemoryLimit=512MB

Test Files  44 passed (44)
     Tests  1163 passed (1163)

The test TypeScript check, ESLint and Prettier on the follow-up’s three changed files, and git diff --check pass.

Tested on Linux with Node 24.19.0. Local browser, E2E, builds, Flow and other platforms were not run.

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Oct 20, 2026
@vercel

vercel Bot commented Oct 20, 2026 •

Copy link
Copy Markdown

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

Project Deployment Actions Updated
lexical Ready Ready Preview Oct 20, 2026 6:48pm UTC
lexical-playground Ready Ready Preview Oct 20, 2026 6:48pm UTC

Request Review

@pkg-pr-new

pkg-pr-new Bot commented Oct 20, 2026 •

Copy link
Copy Markdown
lexical

npm i https://pkg.pr.new/lexical@9396
yarn add https://pkg.pr.new/lexical@9396.tgz

@lexical/a11y

npm i https://pkg.pr.new/@lexical/a11y@9396
yarn add https://pkg.pr.new/@lexical/a11y@9396.tgz

@lexical/clipboard

npm i https://pkg.pr.new/@lexical/clipboard@9396
yarn add https://pkg.pr.new/@lexical/clipboard@9396.tgz

@lexical/code

npm i https://pkg.pr.new/@lexical/code@9396
yarn add https://pkg.pr.new/@lexical/code@9396.tgz

@lexical/code-core

npm i https://pkg.pr.new/@lexical/code-core@9396
yarn add https://pkg.pr.new/@lexical/code-core@9396.tgz

@lexical/code-prism

npm i https://pkg.pr.new/@lexical/code-prism@9396
yarn add https://pkg.pr.new/@lexical/code-prism@9396.tgz

@lexical/code-shiki

npm i https://pkg.pr.new/@lexical/code-shiki@9396
yarn add https://pkg.pr.new/@lexical/code-shiki@9396.tgz

@lexical/compiler

npm i https://pkg.pr.new/@lexical/compiler@9396
yarn add https://pkg.pr.new/@lexical/compiler@9396.tgz

@lexical/devtools-core

npm i https://pkg.pr.new/@lexical/devtools-core@9396
yarn add https://pkg.pr.new/@lexical/devtools-core@9396.tgz

@lexical/dragon

npm i https://pkg.pr.new/@lexical/dragon@9396
yarn add https://pkg.pr.new/@lexical/dragon@9396.tgz

@lexical/eslint-plugin

npm i https://pkg.pr.new/@lexical/eslint-plugin@9396
yarn add https://pkg.pr.new/@lexical/eslint-plugin@9396.tgz

@lexical/extension

npm i https://pkg.pr.new/@lexical/extension@9396
yarn add https://pkg.pr.new/@lexical/extension@9396.tgz

@lexical/fast-check

npm i https://pkg.pr.new/@lexical/fast-check@9396
yarn add https://pkg.pr.new/@lexical/fast-check@9396.tgz

@lexical/file

npm i https://pkg.pr.new/@lexical/file@9396
yarn add https://pkg.pr.new/@lexical/file@9396.tgz

@lexical/hashtag

npm i https://pkg.pr.new/@lexical/hashtag@9396
yarn add https://pkg.pr.new/@lexical/hashtag@9396.tgz

@lexical/headless

npm i https://pkg.pr.new/@lexical/headless@9396
yarn add https://pkg.pr.new/@lexical/headless@9396.tgz

@lexical/history

npm i https://pkg.pr.new/@lexical/history@9396
yarn add https://pkg.pr.new/@lexical/history@9396.tgz

@lexical/html

npm i https://pkg.pr.new/@lexical/html@9396
yarn add https://pkg.pr.new/@lexical/html@9396.tgz

@lexical/internal

npm i https://pkg.pr.new/@lexical/internal@9396
yarn add https://pkg.pr.new/@lexical/internal@9396.tgz

@lexical/link

npm i https://pkg.pr.new/@lexical/link@9396
yarn add https://pkg.pr.new/@lexical/link@9396.tgz

@lexical/list

npm i https://pkg.pr.new/@lexical/list@9396
yarn add https://pkg.pr.new/@lexical/list@9396.tgz

@lexical/mark

npm i https://pkg.pr.new/@lexical/mark@9396
yarn add https://pkg.pr.new/@lexical/mark@9396.tgz

@lexical/markdown

npm i https://pkg.pr.new/@lexical/markdown@9396
yarn add https://pkg.pr.new/@lexical/markdown@9396.tgz

@lexical/mdast

npm i https://pkg.pr.new/@lexical/mdast@9396
yarn add https://pkg.pr.new/@lexical/mdast@9396.tgz

@lexical/offset

npm i https://pkg.pr.new/@lexical/offset@9396
yarn add https://pkg.pr.new/@lexical/offset@9396.tgz

@lexical/overflow

npm i https://pkg.pr.new/@lexical/overflow@9396
yarn add https://pkg.pr.new/@lexical/overflow@9396.tgz

@lexical/plain-text

npm i https://pkg.pr.new/@lexical/plain-text@9396
yarn add https://pkg.pr.new/@lexical/plain-text@9396.tgz

@lexical/react

npm i https://pkg.pr.new/@lexical/react@9396
yarn add https://pkg.pr.new/@lexical/react@9396.tgz

@lexical/rich-text

npm i https://pkg.pr.new/@lexical/rich-text@9396
yarn add https://pkg.pr.new/@lexical/rich-text@9396.tgz

@lexical/selection

npm i https://pkg.pr.new/@lexical/selection@9396
yarn add https://pkg.pr.new/@lexical/selection@9396.tgz

@lexical/table

npm i https://pkg.pr.new/@lexical/table@9396
yarn add https://pkg.pr.new/@lexical/table@9396.tgz

@lexical/tailwind

npm i https://pkg.pr.new/@lexical/tailwind@9396
yarn add https://pkg.pr.new/@lexical/tailwind@9396.tgz

@lexical/text

npm i https://pkg.pr.new/@lexical/text@9396
yarn add https://pkg.pr.new/@lexical/text@9396.tgz

@lexical/utils

npm i https://pkg.pr.new/@lexical/utils@9396
yarn add https://pkg.pr.new/@lexical/utils@9396.tgz

@lexical/yjs

npm i https://pkg.pr.new/@lexical/yjs@9396
yarn add https://pkg.pr.new/@lexical/yjs@9396.tgz

commit: d10cd72

## Description

After #9396, a text-match transformer that returns nothing can leave its matched text in the document without decoding its escapes or character references. This affects custom transformers that edit or move the supplied node, and the built-in link transformer when it declines a match such as `[a\*b]](url)`.

Keep the matched node alongside the returned node so retained, non-code text is unescaped once when no node was returned for recursive processing. Check for a parent rather than root attachment so detached Markdown generation works too. Code-formatted text and newly generated replacement text remain literal.

Add 40 cases covering escaped punctuation, character references, declined links, moved and returned nodes, code and replacement text, with and without a prefix, in attached and detached trees.

## Test plan

### Before

With the new tests on 5885b8c and unchanged implementation:

```text
$ node /workspace/lexical/node_modules/vitest/vitest.mjs run --project unit packages/lexical-markdown/src/__tests__/unit/RetainedTextMatchImport.test.ts --maxWorkers=1 --vmMemoryLimit=512MB
 Test Files  1 failed (1)
      Tests  28 failed | 12 passed (40)
```

### After

```text
$ node /workspace/lexical/node_modules/vitest/vitest.mjs run --project unit packages/lexical-markdown packages/lexical-mdast packages/lexical-playground/__tests__/unit --maxWorkers=1 --vmMemoryLimit=512MB
 Test Files  44 passed (44)
      Tests  1163 passed (1163)
```

The test TypeScript check (`tsc -p tsconfig.test.json --noEmit`), ESLint and Prettier on all three changed files, and `git diff --check` passed. Checks ran directly through the installed binaries. The initial before-test run emitted a Vite dependency-scan warning about the unrelated generated devtools `.wxt/tsconfig.json`; tests still executed normally.

Tested on Linux with Node 24.19.0. Browser, E2E, builds, Flow and other platforms were not run.
@etrepum
etrepum added this pull request to the merge queue Oct 20, 2026
Merged via the queue into meta:main with commit 98e936d Oct 20, 2026
54 checks passed
@zurfyx zurfyx mentioned this pull request Oct 27, 2026
zurfyx added a commit that referenced this pull request Oct 27, 2026
…ed since #9353

## Description

The changesets in this PR covered main up to #9353. Since then, 27 PRs
that change a published package's shipped code have merged without one,
so the v0.53.0 release would list them only under "All changes". This adds
a changeset for each, with a `PR:` line and the author's handle:

- `[breaking]`: #9478 (`@lexical/mdast` table alignment moves out of the
  `mdastTableAlign` NodeState) and #9329 (`reconcileObservedMutation`,
  `@internal`), both with `Migration:` and `Detect:`.
- `[feature]`: #9452, #9421, #9380 (experimental), #9360, #9365.
- `[perf]`: #9349.
- `[fix]`: the remaining 19. #9396, #9392, #9270 and #9360 carry `Verify:`
  because they change observable output.

Website, playground, devtools, CI and internal lint changes need none.

## Test plan

### Before

```
$ git log --format=%s fd661f0..origin/main   # merged since this PR's base
(27 PRs touching published packages' src/flow, none with a changeset)
```

### After

```
$ pnpm run check-changesets
Changeset check passed: 54 pending changeset(s) valid.

$ pnpm run changelog
## v0.53.0 (2026-10-27)
### Breaking changes
- **lexical, @lexical/clipboard, @lexical/selection, @lexical/table**: Let mouse drags select across inline decorators; ... (#9329)
- **@lexical/mdast**: `MdastTableExtension` reads and writes GFM tables, ... (#9478)
- **@lexical/code-core, ...**: Remove the extensions deprecated in v0.52.0 and earlier (#9316)
### Behavior changes
...
```

This branch was successfully deployed

2 active deployments
Preview – lexical — d10cd728 Deployed Oct 20, 2026 by vercel[bot]
Preview – lexical-playground — d10cd728 Deployed Oct 20, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. extended-tests Run extended e2e tests on a PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: Markdown import removes backslash escapes from a reused node twice, and from inline code at the start of a node

2 participants