diff --git a/.changeset/ninety-spies-count.md b/.changeset/ninety-spies-count.md new file mode 100644 index 0000000000..da37022f1f --- /dev/null +++ b/.changeset/ninety-spies-count.md @@ -0,0 +1,5 @@ +--- +'@astrojs/starlight': patch +--- + +Fixes a potential text rendering issue that could include extra whitespaces for text containing colons. diff --git a/packages/starlight/__tests__/remark-rehype/asides.test.ts b/packages/starlight/__tests__/remark-rehype/asides.test.ts index 327766e459..88110f682a 100644 --- a/packages/starlight/__tests__/remark-rehype/asides.test.ts +++ b/packages/starlight/__tests__/remark-rehype/asides.test.ts @@ -217,20 +217,25 @@ test('transforms back unhandled text directives', async () => { `This is a:test of a sentence with a text:name[content]{key=val} directive.` ); expect(res.code).toMatchInlineSnapshot(` - "

This is a:test - of a sentence with a text:name[content]{key="val"} - directive.

" + "

This is a:test of a sentence with a text:name[content]{key="val"} directive.

" `); }); test('transforms back unhandled leaf directives', async () => { const res = await processor.render(`::video[Title]{v=xxxxxxxxxxx}`); expect(res.code).toMatchInlineSnapshot(` - "

::video[Title]{v="xxxxxxxxxxx"} -

" + "

::video[Title]{v="xxxxxxxxxxx"}

" `); }); +test('does not add any whitespace character after any unhandled directive', async () => { + const res = await processor.render(`## Environment variables (astro:env)`); + expect(res.code).toMatchInlineSnapshot( + `"

Environment variables (astro:env)

"` + ); + expect(res.code).not.toMatch(/\n/); +}); + test('lets remark plugin injected by Starlight plugins handle text and leaf directives', async () => { const processor = await createMarkdownProcessor({ remarkPlugins: [ @@ -262,8 +267,6 @@ test('lets remark plugin injected by Starlight plugins handle text and leaf dire `This is a:test of a sentence with a :abbr[SL]{name="Starlight"} directive handled by another remark plugin and some other text:name[content]{key=val} directives not handled by any plugin.` ); expect(res.code).toMatchInlineSnapshot(` - "

This is a:test - of a sentence with a TEXT FROM REMARK PLUGIN directive handled by another remark plugin and some other text:name[content]{key="val"} - directives not handled by any plugin.

" + "

This is a:test of a sentence with a TEXT FROM REMARK PLUGIN directive handled by another remark plugin and some other text:name[content]{key="val"} directives not handled by any plugin.

" `); }); diff --git a/packages/starlight/integrations/asides.ts b/packages/starlight/integrations/asides.ts index f98cc09082..d98e6a403a 100644 --- a/packages/starlight/integrations/asides.ts +++ b/packages/starlight/integrations/asides.ts @@ -64,10 +64,20 @@ function transformUnhandledDirective( index: number, parent: Parent ) { - const textNode = { - type: 'text', - value: toMarkdown(node, { extensions: [directiveToMarkdown()] }), - } as const; + let markdown = toMarkdown(node, { extensions: [directiveToMarkdown()] }); + /** + * `mdast-util-to-markdown` assumes that the tree represents a complete document (as it's an AST + * and not a CST) and to follow the POSIX definition of a line (a sequence of zero or more + * non- characters plus a terminating character), a newline is automatically + * added at the end of the output so that the output is a valid file. + * In this specific case, we can safely remove the newline character at the end of the output + * before replacing the directive with its value. + * + * @see https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_206 + * @see https://github.com/syntax-tree/mdast-util-to-markdown/blob/fd6a508cc619b862f75b762dcf876c6b8315d330/lib/index.js#L79-L85 + */ + if (markdown.at(-1) === '\n') markdown = markdown.slice(0, -1); + const textNode = { type: 'text', value: markdown } as const; if (node.type === 'textDirective') { parent.children[index] = textNode; } else {