Skip to content

Bug: A code span of only spaces grows by two spaces on every save, and loses its bold or italic #9405

Closed

Description

@kyoshino

Lexical version: 0.52.0 (main at 98e936d gives the same results)

Steps to reproduce

Import each line below with $convertFromMarkdownString(markdown, TRANSFORMERS), then export it with $convertToMarkdownString(TRANSFORMERS), and repeat:

Markdown Imported Exported Exported again Expected
` ` code (1 space) ✅ ` ` (3 spaces) ` ` (5 spaces) ` `
` ` code with 2 spaces ✅ ` ` ` ` ` `
a ` ` b a , code , b ✅ a ` ` b a ` ` b unchanged
**` `** code , bold ✅ ` ` (bold lost) ` ` **` `**
*` `* x code , italic ✅ ` ` x (italic lost) ` ` x unchanged

Current behaviour

  1. Import is correct: CommonMark strips one space from each end only when the content isn't all spaces, so ` ` is a single space. Export, though, pads every code span that starts and ends with whitespace, including one that is only spaces. A reader never strips that padding, so the span gets two spaces longer on every save.
  2. Bold, italic and other formats around inline code that holds only whitespace are dropped on export.

Cause

  • getCodeSpanDelimiter() in packages/lexical-markdown/src/MarkdownTransformers.ts pads when /^\s/.test(content) && /\s$/.test(content), without checking for a character other than a space.
  • checkHasFormat() in packages/lexical-markdown/src/MarkdownExport.ts returns false for a node whose text is only whitespace, unless the format being checked is code. That treats a whitespace-only code node as having no bold, italic or other format, so exportTextFormat() never opens those tags around it. Inline code is written with its backticks, so formats can go around it like any other text.

Suggested fix

--- a/packages/lexical-markdown/src/MarkdownTransformers.ts
+++ b/packages/lexical-markdown/src/MarkdownTransformers.ts
@@ -1098,7 +1098,10 @@ export function getCodeSpanDelimiter(content: string): {
   const needsPadding =
     content.length === 0 ||
     content.includes('`') ||
-    (/^\s/.test(content) && /\s$/.test(content));
+    // A reader strips one space from each end only when the content is not
+    // all spaces, so padding content of only spaces would add two spaces.
+    // https://spec.commonmark.org/0.31.2/#code-spans
+    (/^\s/.test(content) && /\s$/.test(content) && /[^ ]/.test(content));
   const padded = needsPadding ? ` ${content} ` : content;
--- a/packages/lexical-markdown/src/MarkdownExport.ts
+++ b/packages/lexical-markdown/src/MarkdownExport.ts
@@ -651,7 +651,9 @@ function checkHasFormat(n: TextNode | null, f: TextFormatType): boolean {
   if (!hasFormat(n, f)) {
     return false;
   }
-  if (f === 'code') {
+  // Inline code is written with its backticks, so other formats can go around
+  // it even when it holds only whitespace.
+  if (f === 'code' || (n && n.hasFormat('code'))) {
     return true;
   }

With this change, all the rows above round-trip unchanged. These are unchanged too: ` a ` (exported as `a`, as before), ` a `, `a` and **`a`** b.

Tested with headless Lexical against main at 98e936d. The unit tests for lexical-markdown, lexical-mdast and lexical-playground/__tests__/unit pass (1175), along with tsc, ESLint and Prettier. No new unit tests are added.

Related: PR #8723 (code spans containing backticks), which added the padding rules.

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

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions