summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHiDeoo2025-07-16 13:17:26 +0200
committerGitHub2025-07-16 13:17:26 +0200
commit80ccff7c542794e04a4d2abb17227a076fa57c5d (patch)
tree412d018bc8961de8d9171bea4f49a5bff12d69fc
parent8d4d3bf80b474a14953e1768124df6b8d18f9355 (diff)
downloadIT.starlight-80ccff7c542794e04a4d2abb17227a076fa57c5d.tar.gz
IT.starlight-80ccff7c542794e04a4d2abb17227a076fa57c5d.tar.bz2
IT.starlight-80ccff7c542794e04a4d2abb17227a076fa57c5d.zip
Restrict remark/rehype plugins usage (#3274)
Co-authored-by: Sgal Cheung <zhangsigao@live.com> Co-authored-by: Chris Swithinbank <swithinbank@gmail.com>
-rw-r--r--.changeset/moody-donkeys-applaud.md5
-rw-r--r--.changeset/unlucky-bananas-accept.md11
-rw-r--r--packages/starlight/__e2e__/components.test.ts35
-rw-r--r--packages/starlight/__e2e__/fixtures/basics/src/content/reviews/alice.mdx8
-rw-r--r--packages/starlight/__e2e__/fixtures/basics/src/pages/markdown-page.md8
-rw-r--r--packages/starlight/__tests__/remark-rehype/anchor-links.test.ts13
-rw-r--r--packages/starlight/__tests__/remark-rehype/asides.test.ts16
-rw-r--r--packages/starlight/__tests__/remark-rehype/code-rtl-support.test.ts37
-rw-r--r--packages/starlight/index.ts2
-rw-r--r--packages/starlight/integrations/asides.ts5
-rw-r--r--packages/starlight/integrations/code-rtl-support.ts15
-rw-r--r--packages/starlight/integrations/heading-links.ts17
-rw-r--r--packages/starlight/integrations/remark-rehype-utils.ts38
13 files changed, 188 insertions, 22 deletions
diff --git a/.changeset/moody-donkeys-applaud.md b/.changeset/moody-donkeys-applaud.md
new file mode 100644
index 00000000..b76c1ed5
--- /dev/null
+++ b/.changeset/moody-donkeys-applaud.md
@@ -0,0 +1,5 @@
+---
+'@astrojs/starlight': patch
+---
+
+Prevents Starlight remark and rehype plugins from transforming Markdown and MDX content when using the Astro [`renderMarkdown()`](https://docs.astro.build/en/reference/content-loader-reference/#rendermarkdown) content loader API.
diff --git a/.changeset/unlucky-bananas-accept.md b/.changeset/unlucky-bananas-accept.md
new file mode 100644
index 00000000..a13dfff5
--- /dev/null
+++ b/.changeset/unlucky-bananas-accept.md
@@ -0,0 +1,11 @@
+---
+'@astrojs/starlight': minor
+---
+
+Fixes an issue where some Starlight remark and rehype plugins were transforming Markdown and MDX content in non-Starlight pages.
+
+⚠️ **BREAKING CHANGE:**
+
+Previously, some of Starlight’s remark and rehype plugins, most notably the plugin transforming Starlight's custom Markdown syntax for [rendering asides](https://starlight.astro.build/guides/authoring-content/#asides), were applied to all Markdown and MDX content. This included content from [individual Markdown pages](https://docs.astro.build/en/guides/markdown-content/#individual-markdown-pages) and content from [content collections](https://docs.astro.build/en/guides/content-collections/) other than the `docs` collection used by Starlight.
+
+This change restricts the application of Starlight’s remark and rehype plugins to only Markdown and MDX content loaded using Starlight's [`docsLoader()`](https://starlight.astro.build/reference/configuration/#docsloader). If you were relying on this behavior, please let us know about your use case in the dedicated `#starlight` channel in the [Astro Discord](https://astro.build/chat/) or by [opening an issue](https://github.com/withastro/starlight/issues/new?template=---01-bug-report.yml).
diff --git a/packages/starlight/__e2e__/components.test.ts b/packages/starlight/__e2e__/components.test.ts
index 85010436..6f217b72 100644
--- a/packages/starlight/__e2e__/components.test.ts
+++ b/packages/starlight/__e2e__/components.test.ts
@@ -408,6 +408,41 @@ test.describe('anchor headings', () => {
});
});
+test.describe('asides', () => {
+ test('does not render Markdown asides for individual Markdown pages and entries not part of the `docs` collection', async ({
+ getProdServer,
+ page,
+ }) => {
+ const starlight = await getProdServer();
+
+ // Individual Markdown page
+ await starlight.goto('/markdown-page');
+ await expect(page.locator('.starlight-aside')).not.toBeAttached();
+ await page.pause();
+
+ // Content entry from the `reviews` content collection
+ await starlight.goto('/reviews/alice');
+ await expect(page.locator('.starlight-aside')).not.toBeAttached();
+ });
+});
+
+test.describe('RTL support', () => {
+ test('does not add RTL support to code and preformatted text elements for individual Markdown pages and entries not part of the `docs` collection', async ({
+ getProdServer,
+ page,
+ }) => {
+ const starlight = await getProdServer();
+
+ // Individual Markdown page
+ await starlight.goto('/markdown-page');
+ await expect(page.locator('code[dir="auto"]')).not.toBeAttached();
+
+ // Content entry from the `reviews` content collection
+ await starlight.goto('/reviews/alice');
+ await expect(page.locator('code[dir="auto"]')).not.toBeAttached();
+ });
+});
+
test.describe('head propagation', () => {
/**
* Due to a head propagation issue in development mode, dynamic routes alphabetically sorted
diff --git a/packages/starlight/__e2e__/fixtures/basics/src/content/reviews/alice.mdx b/packages/starlight/__e2e__/fixtures/basics/src/content/reviews/alice.mdx
index 8230272c..938357bf 100644
--- a/packages/starlight/__e2e__/fixtures/basics/src/content/reviews/alice.mdx
+++ b/packages/starlight/__e2e__/fixtures/basics/src/content/reviews/alice.mdx
@@ -9,4 +9,10 @@ This is a review from Alice.
## Description
This content collection entry is not part of the Starlight `docs` collection.
-It is used to test that anchor links for headings are not generated for non-docs collection entries.
+It is used to test that various remark/rehype plugins are not transforming non-docs collection entries.
+
+:::note
+This is a note using Starlight Markdown aside syntax.
+:::
+
+This is an `inline code` example.
diff --git a/packages/starlight/__e2e__/fixtures/basics/src/pages/markdown-page.md b/packages/starlight/__e2e__/fixtures/basics/src/pages/markdown-page.md
index d3b8d9cf..085dd68c 100644
--- a/packages/starlight/__e2e__/fixtures/basics/src/pages/markdown-page.md
+++ b/packages/starlight/__e2e__/fixtures/basics/src/pages/markdown-page.md
@@ -7,4 +7,10 @@ title: Individual Markdown Page
## Description
This page is an [individual Markdown page](https://docs.astro.build/en/guides/markdown-content/#individual-markdown-pages).
-It is used to test that anchor links for headings are not generated for individual Markdown pages.
+It is used to test that various remark/rehype plugins are not transforming individual Markdown pages.
+
+:::note
+This is a note using Starlight Markdown aside syntax.
+:::
+
+This is an `inline code` example.
diff --git a/packages/starlight/__tests__/remark-rehype/anchor-links.test.ts b/packages/starlight/__tests__/remark-rehype/anchor-links.test.ts
index 90b86f36..af5e92de 100644
--- a/packages/starlight/__tests__/remark-rehype/anchor-links.test.ts
+++ b/packages/starlight/__tests__/remark-rehype/anchor-links.test.ts
@@ -86,3 +86,16 @@ test('localizes accessible label for the current language', async () => {
);
expect(res.code).includes('<span class="sr-only">Section intitulée « Some text »</span>');
});
+
+test('does not generate anchor links for documents without a file path', async () => {
+ const res = await processor.render(
+ `
+## Some text
+`,
+ // Rendering Markdown content using the content loader `renderMarkdown()` API does not provide
+ // a `fileURL` option.
+ {}
+ );
+
+ expect(res.code).not.includes('Section titled');
+});
diff --git a/packages/starlight/__tests__/remark-rehype/asides.test.ts b/packages/starlight/__tests__/remark-rehype/asides.test.ts
index e1486a74..35ad73fb 100644
--- a/packages/starlight/__tests__/remark-rehype/asides.test.ts
+++ b/packages/starlight/__tests__/remark-rehype/asides.test.ts
@@ -334,3 +334,19 @@ test('does not transform back directive nodes with data', async () => {
`"<p>This method is available in the <span class="api">thing</span> API.</p>"`
);
});
+
+test('does not generate asides for documents without a file path', async () => {
+ const res = await processor.render(
+ `
+:::note
+Some text
+:::
+`,
+ // Rendering Markdown content using the content loader `renderMarkdown()` API does not provide
+ // a `fileURL` option.
+ {}
+ );
+
+ expect(res.code).not.includes(`aside`);
+ expect(res.code).not.includes(`</svg>Note</p>`);
+});
diff --git a/packages/starlight/__tests__/remark-rehype/code-rtl-support.test.ts b/packages/starlight/__tests__/remark-rehype/code-rtl-support.test.ts
index 89435816..b96a7347 100644
--- a/packages/starlight/__tests__/remark-rehype/code-rtl-support.test.ts
+++ b/packages/starlight/__tests__/remark-rehype/code-rtl-support.test.ts
@@ -1,12 +1,29 @@
import { rehype } from 'rehype';
+import { VFile } from 'vfile';
import { expect, test } from 'vitest';
import { rehypeRtlCodeSupport } from '../../integrations/code-rtl-support';
-const processor = rehype().data('settings', { fragment: true }).use(rehypeRtlCodeSupport());
+const astroConfig = {
+ root: new URL(import.meta.url),
+ srcDir: new URL('./_src/', import.meta.url),
+};
+
+const processor = rehype()
+ .data('settings', { fragment: true })
+ .use(rehypeRtlCodeSupport({ astroConfig }));
+
+function renderMarkdown(content: string, options: { fileURL?: URL } = {}) {
+ return processor.process(
+ new VFile({
+ path: options.fileURL ?? new URL(`./_src/content/docs/index.md`, import.meta.url),
+ value: content,
+ })
+ );
+}
test('applies `dir="auto"` to inline code', async () => {
const input = `<p>Some text with <code>inline code</code>.</p>`;
- const output = String(await processor.process(input));
+ const output = String(await renderMarkdown(input));
expect(output).not.toEqual(input);
expect(output).includes('dir="auto"');
expect(output).toMatchInlineSnapshot(
@@ -16,10 +33,24 @@ test('applies `dir="auto"` to inline code', async () => {
test('applies `dir="ltr"` to code blocks', async () => {
const input = `<p>Some text in a paragraph:</p><pre><code>console.log('test')</code></pre>`;
- const output = String(await processor.process(input));
+ const output = String(await renderMarkdown(input));
expect(output).not.toEqual(input);
expect(output).includes('dir="ltr"');
expect(output).toMatchInlineSnapshot(
`"<p>Some text in a paragraph:</p><pre dir="ltr"><code>console.log('test')</code></pre>"`
);
});
+
+test('does not transform documents without a file path', async () => {
+ const input = `<p>Some text with <code>inline code</code>.</p>`;
+ const output = String(
+ await processor.process(
+ new VFile({
+ // Rendering Markdown content using the content loader `renderMarkdown()` API does not
+ // provide a `path` option.
+ value: input,
+ })
+ )
+ );
+ expect(output).toEqual(input);
+});
diff --git a/packages/starlight/index.ts b/packages/starlight/index.ts
index 96cd63de..ea6fd8e1 100644
--- a/packages/starlight/index.ts
+++ b/packages/starlight/index.ts
@@ -129,7 +129,7 @@ export default function StarlightIntegration(
}),
],
rehypePlugins: [
- rehypeRtlCodeSupport(),
+ rehypeRtlCodeSupport({ astroConfig: config }),
// Process headings and add anchor links.
...starlightAutolinkHeadings({
starlightConfig,
diff --git a/packages/starlight/integrations/asides.ts b/packages/starlight/integrations/asides.ts
index 939cfe7c..85334d70 100644
--- a/packages/starlight/integrations/asides.ts
+++ b/packages/starlight/integrations/asides.ts
@@ -15,6 +15,7 @@ import remarkDirective from 'remark-directive';
import type { Plugin, Transformer } from 'unified';
import { visit } from 'unist-util-visit';
import type { HookParameters, StarlightConfig } from '../types';
+import { getRemarkRehypeDocsCollectionPath, shouldTransformFile } from './remark-rehype-utils';
interface AsidesOptions {
starlightConfig: Pick<StarlightConfig, 'defaultLocale' | 'locales'>;
@@ -148,7 +149,11 @@ function remarkAsides(options: AsidesOptions): Plugin<[], Root> {
],
};
+ const docsCollectionPath = getRemarkRehypeDocsCollectionPath(options.astroConfig.srcDir);
+
const transformer: Transformer<Root> = (tree, file) => {
+ if (!shouldTransformFile(file, docsCollectionPath)) return;
+
const lang = options.absolutePathToLang(file.path);
const t = options.useTranslations(lang);
visit(tree, (node, index, parent) => {
diff --git a/packages/starlight/integrations/code-rtl-support.ts b/packages/starlight/integrations/code-rtl-support.ts
index 781b7afc..be7c9e8d 100644
--- a/packages/starlight/integrations/code-rtl-support.ts
+++ b/packages/starlight/integrations/code-rtl-support.ts
@@ -1,5 +1,12 @@
+import type { AstroConfig } from 'astro';
import type { Root } from 'hast';
import { CONTINUE, SKIP, visit } from 'unist-util-visit';
+import type { VFile } from 'vfile';
+import { getRemarkRehypeDocsCollectionPath, shouldTransformFile } from './remark-rehype-utils';
+
+interface RtlCodeSupportOptions {
+ astroConfig: Pick<AstroConfig, 'srcDir'>;
+}
/**
* rehype plugin that adds `dir` attributes to `<code>` and `<pre>`
@@ -15,8 +22,12 @@ import { CONTINUE, SKIP, visit } from 'unist-util-visit';
* - `<code>` is often LTR, but could also be RTL. `dir="auto"` ensures the bidirectional
* algorithm treats the contents of `<code>` in isolation and gives its best guess.
*/
-export function rehypeRtlCodeSupport() {
- return () => (root: Root) => {
+export function rehypeRtlCodeSupport({ astroConfig }: RtlCodeSupportOptions) {
+ const docsCollectionPath = getRemarkRehypeDocsCollectionPath(astroConfig.srcDir);
+
+ return () => (root: Root, file: VFile) => {
+ if (!shouldTransformFile(file, docsCollectionPath)) return;
+
visit(root, 'element', (el) => {
if (el.tagName === 'pre' || el.tagName === 'code') {
el.properties ||= {};
diff --git a/packages/starlight/integrations/heading-links.ts b/packages/starlight/integrations/heading-links.ts
index ebb61d4a..9e5086fe 100644
--- a/packages/starlight/integrations/heading-links.ts
+++ b/packages/starlight/integrations/heading-links.ts
@@ -6,7 +6,7 @@ import { h } from 'hastscript';
import type { Transformer } from 'unified';
import { SKIP, visit } from 'unist-util-visit';
import type { HookParameters, StarlightConfig } from '../types';
-import { resolveCollectionPath } from '../utils/collection';
+import { getRemarkRehypeDocsCollectionPath, shouldTransformFile } from './remark-rehype-utils';
const AnchorLinkIcon = h(
'span',
@@ -30,8 +30,7 @@ export default function rehypeAutolinkHeadings(
absolutePathToLang: AutolinkHeadingsOptions['absolutePathToLang']
) {
const transformer: Transformer<Root> = (tree, file) => {
- // If the document is not part of the Starlight docs collection, skip it.
- if (!normalizePath(file.path).startsWith(docsCollectionPath)) return;
+ if (!shouldTransformFile(file, docsCollectionPath)) return;
const pageLang = absolutePathToLang(file.path);
const t = useTranslationsForLang(pageLang);
@@ -93,23 +92,13 @@ export const starlightAutolinkHeadings = ({
{ experimentalHeadingIdCompat: astroConfig.experimental?.headingIdCompat },
],
rehypeAutolinkHeadings(
- normalizePath(resolveCollectionPath('docs', astroConfig.srcDir)),
+ getRemarkRehypeDocsCollectionPath(astroConfig.srcDir),
useTranslations,
absolutePathToLang
),
]
: [];
-/**
- * File path separators seems to be inconsistent on Windows when the rehype plugin is used on
- * Markdown vs MDX files.
- * For the time being, we normalize the path to unix style path.
- */
-const backSlashRegex = /\\/g;
-function normalizePath(path: string) {
- return path.replace(backSlashRegex, '/');
-}
-
// This utility is inlined from https://github.com/syntax-tree/hast-util-heading-rank
// Copyright (c) 2020 Titus Wormer <tituswormer@gmail.com>
// MIT License: https://github.com/syntax-tree/hast-util-heading-rank/blob/main/license
diff --git a/packages/starlight/integrations/remark-rehype-utils.ts b/packages/starlight/integrations/remark-rehype-utils.ts
new file mode 100644
index 00000000..97ff09b0
--- /dev/null
+++ b/packages/starlight/integrations/remark-rehype-utils.ts
@@ -0,0 +1,38 @@
+import type { AstroConfig } from 'astro';
+import type { VFile } from 'vfile';
+import { resolveCollectionPath } from '../utils/collection';
+
+/**
+ * Returns the path to the Starlight docs collection ready to be used in remark/rehype plugins,
+ * e.g. with the `shouldTransformFile()` utility to determine if a file should be transformed
+ * by a plugin or not.
+ */
+export function getRemarkRehypeDocsCollectionPath(srcDir: AstroConfig['srcDir']) {
+ return normalizePath(resolveCollectionPath('docs', srcDir));
+}
+
+/**
+ * Determines if a file should be transformed by a remark/rehype plugin, e.g. files without a known
+ * path or files that are not part of the Starlight docs collection should be skipped.
+ */
+export function shouldTransformFile(file: VFile, docsCollectionPath: string) {
+ // If the content is rendered using the content loader `renderMarkdown()` API, a file path
+ // is not provided.
+ // In that case, we skip the file.
+ if (!file?.path) return false;
+
+ // If the document is not part of the Starlight docs collection, skip it.
+ if (!normalizePath(file.path).startsWith(docsCollectionPath)) return false;
+
+ return true;
+}
+
+/**
+ * File path separators seems to be inconsistent on Windows between remark/rehype plugins used on
+ * Markdown vs MDX files.
+ * For the time being, we normalize all paths to unix style paths.
+ */
+const backSlashRegex = /\\/g;
+function normalizePath(path: string) {
+ return path.replace(backSlashRegex, '/');
+}