Skip to content

[lexical-code-shiki] Bug Fix: Set the unsupported syntax flag only once#8606

Merged
etrepum merged 2 commits into
facebook:mainfrom
levensta:refactor-code-highlight-shiki
Jun 1, 2026
Merged

[lexical-code-shiki] Bug Fix: Set the unsupported syntax flag only once#8606
etrepum merged 2 commits into
facebook:mainfrom
levensta:refactor-code-highlight-shiki

Conversation

@levensta

@levensta levensta commented May 31, 2026

Copy link
Copy Markdown
Contributor

Description

While the syntax highlighter is loading, the CodeNode transform may continuously update the state of node.setIsSyntaxHighlightSupported(false). To fix this continuous updating, the return value from the loadCodeLanguage function is checked so node.setIsSyntaxHighlightSupported(false) is called only if the language is not supported

Closes #8605
Closes #8607

Test plan

Before

Switching from prism to shiki caused an infinite transform loop while loading the language highlighting module

After

When switching from prism to shiki, the unsupported syntax flag is set only if the highlighting module did not occur

@vercel

vercel Bot commented May 31, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
lexical Ready Ready Preview, Comment Jun 1, 2026 12:06am
lexical-playground Ready Ready Preview, Comment Jun 1, 2026 12:06am

Request Review

@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 May 31, 2026
@potatowagon

Copy link
Copy Markdown
Contributor

Code Review — Tater Thoughts Bobblehead (Sherry's Navi)

✅ This fix looks correct and well-reasoned. Safe to approve once CI is fully green.

What I verified:

  1. Root cause correctness: The infinite loop happens because setIsSyntaxHighlightSupported(false) marks the node dirty → re-triggers the transform → enters the same else branch → calls setIsSyntaxHighlightSupported(false) again → infinite loop. The fix correctly breaks this cycle by checking whether loadCodeLanguage actually initiated a download (returned a Promise) vs returned undefined (language unsupported).

  2. Return value semantics: loadCodeLanguage returns a Promise when it starts loading, and undefined when (a) already loaded or (b) not in bundledLanguagesInfo. The PR correctly uses !loadingTask to distinguish 'truly unsupported' from 'loading in progress'.

  3. No regression risk: The loadCodeLanguage call was just moved before the flag check — same arguments, same side effects. The only behavioral change is that setIsSyntaxHighlightSupported(false) is now gated on the language being genuinely unsupported (not merely loading). This is the correct semantics.

  4. Edge cases considered:

    • Language already loaded: handled by isCodeLanguageLoaded check earlier in the transform
    • Concurrent loads (race condition): shiki's internal dedup handles this (noted in existing comment)
    • Special/hardcoded languages (ansi, plaintext): handled by isSpecialLang in isCodeLanguageLoaded
  5. JSDoc addition: Clean, accurate documentation for loadCodeLanguage with correct return type description.

CI note: The integrity failure is the website build (@lexical/website), unrelated to this code change. Unit tests (22.x, 24.x) and e2e canary all pass.

@etrepum

etrepum commented Jun 1, 2026

Copy link
Copy Markdown
Collaborator

Looks like there's some kind of failure in the website build, usually that's incorrect syntax or a broken link in a jsdoc comment

@levensta levensta changed the title [lexical-code-shiki] Bug Fix: Sets the unsupported syntax flag only once Jun 1, 2026
@etrepum etrepum added the extended-tests Run extended e2e tests on a PR label Jun 1, 2026
@etrepum etrepum added this pull request to the merge queue Jun 1, 2026
Merged via the queue into facebook:main with commit e1fd49b Jun 1, 2026
41 checks passed
@potatowagon

Copy link
Copy Markdown
Contributor

Follow-up Review — Tater Thoughts Bobblehead (Sherry's Navi)

✅ Updated approach looks even better than the strict-inequality fix I reviewed earlier. Safe to approve.

What changed since last review:

  • JSDoc fix commit (62fac27) resolves the website build failure etrepum flagged
  • The core logic now uses loadCodeLanguage() return value as a signal: if the function returns undefined (language not supported/already loaded), only then is setIsSyntaxHighlightSupported(false) called

Why this is correct:

  1. loadCodeLanguage() returns a Promise when a valid language starts downloading, or undefined when the language isn't in the bundled list (truly unsupported)
  2. The guard !loadingTask && node.getIsSyntaxHighlightSupported() means the flag is only set to false for genuinely unsupported languages — never during an in-flight download
  3. This eliminates the infinite loop root cause: previously, the flag was set to false immediately (before knowing if the language was valid), triggering a node mutation → re-transform → set false again → loop
  4. CI is fully green (34/34 pass, 1 pending collab-mac webkit which is a known slow runner)

Compared to #8607: This approach is architecturally superior — it addresses the root cause (premature state mutation) rather than applying strict-inequality guards that mask the problem. Agree with etrepum's decision to prefer this PR.

Ready to approve.

@potatowagon potatowagon left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Reviewed by Navi (Tater Thoughts Bobblehead) on behalf of @potatowagon.

LGTM ✅ — Clean fix targeting the Shiki highlighter's unsupported-flag-setting logic.

What this does: Fixes the unsupported syntax flag being set incorrectly when a language isn't loaded yet. The fix reorders the logic: call loadCodeLanguage() first, check its return value — if it returns undefined (meaning the language is already loaded or isn't supported), then set isSyntaxHighlightSupported to false. This prevents the transform from redundantly setting the flag on every trigger while a language download is in flight.

What I checked:

  • ✅ Logic correctness: The key insight is that loadCodeLanguage returns a Promise when it initiates a download, or undefined when no download occurs (already loaded or unsupported). Only the undefined case should mark the language as unsupported. During an active download, the flag shouldn't change — it'll be set to true when the download completes (via the editor.update callback inside loadCodeLanguage).
  • ✅ JSDoc added: Good documentation on loadCodeLanguage return value semantics.
  • ✅ Note: inFlight = true is still set unconditionally — this is correct because if loadCodeLanguage returns a promise OR undefined (unsupported language), the remaining highlight logic should be skipped for this transform pass.
  • ✅ www compat: No public API changes. Shiki-only internal fix.
  • ✅ CI: All checks green (CLA signed, core-tests, e2e-tests pass).

Safe to land. Targeted fix for a code highlighting transform loop in the Shiki package.

@etrepum etrepum mentioned this pull request Jun 25, 2026
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

3 participants