diff --git a/packages/docusaurus-mdx-loader/src/processor.ts b/packages/docusaurus-mdx-loader/src/processor.ts
index f5120150eeaf..0035ab66c799 100644
--- a/packages/docusaurus-mdx-loader/src/processor.ts
+++ b/packages/docusaurus-mdx-loader/src/processor.ts
@@ -15,7 +15,7 @@ import details from './remark/details';
import head from './remark/head';
import mermaid from './remark/mermaid';
import transformAdmonitions from './remark/admonitions';
-import unusedDirectivesWarning from './remark/unusedDirectives';
+import unusedDirectives from './remark/unusedDirectives';
import codeCompatPlugin from './remark/mdx1Compat/codeCompatPlugin';
import {getFormat} from './format';
import type {WebpackCompilerName} from '@docusaurus/utils';
@@ -25,6 +25,7 @@ import type {AdmonitionOptions} from './remark/admonitions';
import type {PluginOptions as ResolveMarkdownLinksOptions} from './remark/resolveMarkdownLinks';
import type {PluginOptions as TransformLinksOptions} from './remark/transformLinks';
import type {PluginOptions as TransformImageOptions} from './remark/transformImage';
+import type {PluginOptions as UnusedDirectivesOptions} from './remark/unusedDirectives';
import type {ProcessorOptions} from '@mdx-js/mdx';
// TODO as of April 2023, no way to import/re-export this ESM type easily :/
@@ -151,7 +152,13 @@ async function createProcessorFactory() {
gfm,
options.markdownConfig.mdx1Compat.comments ? comment : null,
...(options.remarkPlugins ?? []),
- unusedDirectivesWarning,
+ [
+ unusedDirectives,
+ {
+ onUnusedMarkdownDirectives:
+ options.markdownConfig.hooks.onUnusedMarkdownDirectives,
+ } satisfies UnusedDirectivesOptions,
+ ],
].filter((plugin): plugin is MDXPlugin => Boolean(plugin));
// codeCompatPlugin needs to be applied last after user-provided plugins
diff --git a/packages/docusaurus-mdx-loader/src/remark/unusedDirectives/__tests__/__snapshots__/index.test.ts.snap b/packages/docusaurus-mdx-loader/src/remark/unusedDirectives/__tests__/__snapshots__/index.test.ts.snap
index 5735ead56a93..9d8cd750bfe0 100644
--- a/packages/docusaurus-mdx-loader/src/remark/unusedDirectives/__tests__/__snapshots__/index.test.ts.snap
+++ b/packages/docusaurus-mdx-loader/src/remark/unusedDirectives/__tests__/__snapshots__/index.test.ts.snap
@@ -58,6 +58,60 @@ exports[`directives remark plugin - client compiler > default behavior for text
"
`;
+exports[`directives remark plugin - client compiler > onUnusedMarkdownDirectives > function form > if file contains unused container directive > result 1`] = `
+"Take care of snowstorms...
+
+:::NotAContainerDirective with a phrase after
+:::
+Phrase before :::NotAContainerDirective
+:::
"
+`;
+
+exports[`directives remark plugin - client compiler > onUnusedMarkdownDirectives > function form > if file contains unused leaf directive > result 1`] = `
+"
+Leaf directive in a phrase ::NotALeafDirective
+::NotALeafDirective with a phrase after
"
+`;
+
+exports[`directives remark plugin - client compiler > onUnusedMarkdownDirectives > function form > if file contains unused text directive > result 1`] = `
+"Simple: textDirective1
+Simple: textDirectiveCode
+
+Simple:textDirective2
+Simple
+Simple
+Simple:textDirective5
+Simple:textDirectiveCode
+
"
+`;
+
+exports[`directives remark plugin - client compiler > onUnusedMarkdownDirectives > ignore > if file contains unused container directive > result 1`] = `
+"Take care of snowstorms...
+
+:::NotAContainerDirective with a phrase after
+:::
+Phrase before :::NotAContainerDirective
+:::
"
+`;
+
+exports[`directives remark plugin - client compiler > onUnusedMarkdownDirectives > ignore > if file contains unused leaf directive > result 1`] = `
+"
+Leaf directive in a phrase ::NotALeafDirective
+::NotALeafDirective with a phrase after
"
+`;
+
+exports[`directives remark plugin - client compiler > onUnusedMarkdownDirectives > ignore > if file contains unused text directive > result 1`] = `
+"Simple: textDirective1
+Simple: textDirectiveCode
+
+Simple:textDirective2
+Simple
label
+Simple
+Simple:textDirective5
+Simple:textDirectiveCode
+
"
+`;
+
exports[`directives remark plugin - server compiler > default behavior for container directives > result 1`] = `
"Take care of snowstorms...
diff --git a/packages/docusaurus-mdx-loader/src/remark/unusedDirectives/__tests__/index.test.ts b/packages/docusaurus-mdx-loader/src/remark/unusedDirectives/__tests__/index.test.ts
index e6eb8a8e3c43..b67f8356b78c 100644
--- a/packages/docusaurus-mdx-loader/src/remark/unusedDirectives/__tests__/index.test.ts
+++ b/packages/docusaurus-mdx-loader/src/remark/unusedDirectives/__tests__/index.test.ts
@@ -10,38 +10,47 @@ import path from 'path';
import remark2rehype from 'remark-rehype';
import stringify from 'rehype-stringify';
import vfile from 'to-vfile';
-import plugin from '../index';
+import plugin, {type PluginOptions} from '../index';
import admonition from '../../admonitions';
import type {WebpackCompilerName} from '@docusaurus/utils';
+const getProcessor = async (options?: Partial) => {
+ const {remark} = await import('remark');
+ const {default: directives} = await import('remark-directive');
+
+ return remark()
+ .use(directives)
+ .use(admonition)
+ .use(plugin, {
+ onUnusedMarkdownDirectives: 'warn',
+ ...options,
+ })
+ .use(remark2rehype)
+ .use(stringify);
+};
+
const processFixture = async (
name: string,
{compilerName}: {compilerName: WebpackCompilerName},
+ options?: Partial,
) => {
- const {remark} = await import('remark');
- const {default: directives} = await import('remark-directive');
+ const processor = await getProcessor(options);
const filePath = path.join(__dirname, '__fixtures__', `${name}.md`);
const file = await vfile.read(filePath);
file.data.compilerName = compilerName;
- const result = await remark()
- .use(directives)
- .use(admonition)
- .use(plugin)
- .use(remark2rehype)
- .use(stringify)
- .process(file);
+ const result = await processor.process(file);
return result.value;
};
describe('directives remark plugin - client compiler', () => {
- const options = {compilerName: 'client'} as const;
+ const fileData = {compilerName: 'client'} as const;
it('default behavior for container directives', async () => {
using warn = vi.spyOn(console, 'warn');
- const result = await processFixture('containerDirectives', options);
+ const result = await processFixture('containerDirectives', fileData);
expect(result).toMatchSnapshot('result');
expect(warn).toHaveBeenCalledTimes(1);
expect(warn.mock.calls).toMatchSnapshot('console');
@@ -49,7 +58,7 @@ describe('directives remark plugin - client compiler', () => {
it('default behavior for leaf directives', async () => {
using warn = vi.spyOn(console, 'warn');
- const result = await processFixture('leafDirectives', options);
+ const result = await processFixture('leafDirectives', fileData);
expect(result).toMatchSnapshot('result');
expect(warn).toHaveBeenCalledTimes(1);
expect(warn.mock.calls).toMatchSnapshot('console');
@@ -57,33 +66,260 @@ describe('directives remark plugin - client compiler', () => {
it('default behavior for text directives', async () => {
using warn = vi.spyOn(console, 'warn');
- const result = await processFixture('textDirectives', options);
+ const result = await processFixture('textDirectives', fileData);
expect(result).toMatchSnapshot('result');
expect(warn).toHaveBeenCalledTimes(1);
expect(warn.mock.calls).toMatchSnapshot('console');
});
+
+ describe('onUnusedMarkdownDirectives', () => {
+ describe('throws', () => {
+ const options = {onUnusedMarkdownDirectives: 'throw'} as const;
+ it('if file contains unused container directive', async () => {
+ await expect(processFixture('containerDirectives', fileData, options))
+ .rejects.toThrowErrorMatchingInlineSnapshot(`
+ [Error: Docusaurus found 1 unused Markdown directives in file "packages/docusaurus-mdx-loader/src/remark/unusedDirectives/__tests__/__fixtures__/containerDirectives.md"
+ - :::unusedDirective (7:1)
+ Your content might render in an unexpected way. Visit https://github.com/facebook/docusaurus/pull/9394 to find out why and how to fix it.
+ To ignore this error, use the \`siteConfig.markdown.hooks.onUnusedMarkdownDirectives\` option.]
+ `);
+ });
+ it('if file contains unused leaf directive', async () => {
+ await expect(processFixture('leafDirectives', fileData, options))
+ .rejects.toThrowErrorMatchingInlineSnapshot(`
+ [Error: Docusaurus found 1 unused Markdown directives in file "packages/docusaurus-mdx-loader/src/remark/unusedDirectives/__tests__/__fixtures__/leafDirectives.md"
+ - ::unusedLeafDirective (1:1)
+ Your content might render in an unexpected way. Visit https://github.com/facebook/docusaurus/pull/9394 to find out why and how to fix it.
+ To ignore this error, use the \`siteConfig.markdown.hooks.onUnusedMarkdownDirectives\` option.]
+ `);
+ });
+ it('if file contains unused text directive', async () => {
+ await expect(processFixture('textDirectives', fileData, options))
+ .rejects.toThrowErrorMatchingInlineSnapshot(`
+ [Error: Docusaurus found 2 unused Markdown directives in file "packages/docusaurus-mdx-loader/src/remark/unusedDirectives/__tests__/__fixtures__/textDirectives.md"
+ - :textDirective3 (9:7)
+ - :textDirective4 (11:7)
+ Your content might render in an unexpected way. Visit https://github.com/facebook/docusaurus/pull/9394 to find out why and how to fix it.
+ To ignore this error, use the \`siteConfig.markdown.hooks.onUnusedMarkdownDirectives\` option.]
+ `);
+ });
+ });
+
+ describe('function form', () => {
+ const options: PluginOptions = {
+ onUnusedMarkdownDirectives: (params) => {
+ console.log('onUnusedMarkdownDirectives called with', params);
+ // We can alter the AST Node
+ params.directives.forEach((directive) => {
+ directive.name = `fixed-${directive.name}`;
+ directive.children = [];
+ });
+ },
+ };
+ it('if file contains unused container directive', async () => {
+ using log = vi.spyOn(console, 'log');
+ const result = await processFixture(
+ 'containerDirectives',
+ fileData,
+ options,
+ );
+ expect(result).toMatchSnapshot('result');
+ expect(log).toHaveBeenCalledTimes(1);
+ expect(log.mock.calls).toMatchInlineSnapshot(`
+ [
+ [
+ "onUnusedMarkdownDirectives called with",
+ {
+ "directives": [
+ {
+ "attributes": {},
+ "children": [],
+ "name": "fixed-unusedDirective",
+ "position": {
+ "end": {
+ "column": 4,
+ "line": 11,
+ "offset": 93,
+ },
+ "start": {
+ "column": 1,
+ "line": 7,
+ "offset": 44,
+ },
+ },
+ "type": "containerDirective",
+ },
+ ],
+ "sourceFilePath": "packages/docusaurus-mdx-loader/src/remark/unusedDirectives/__tests__/__fixtures__/containerDirectives.md",
+ },
+ ],
+ ]
+ `);
+ });
+ it('if file contains unused leaf directive', async () => {
+ using log = vi.spyOn(console, 'log');
+ const result = await processFixture(
+ 'leafDirectives',
+ fileData,
+ options,
+ );
+ expect(result).toMatchSnapshot('result');
+ expect(log).toHaveBeenCalledTimes(1);
+ expect(log.mock.calls).toMatchInlineSnapshot(`
+ [
+ [
+ "onUnusedMarkdownDirectives called with",
+ {
+ "directives": [
+ {
+ "attributes": {},
+ "children": [],
+ "name": "fixed-unusedLeafDirective",
+ "position": {
+ "end": {
+ "column": 22,
+ "line": 1,
+ "offset": 21,
+ },
+ "start": {
+ "column": 1,
+ "line": 1,
+ "offset": 0,
+ },
+ },
+ "type": "leafDirective",
+ },
+ ],
+ "sourceFilePath": "packages/docusaurus-mdx-loader/src/remark/unusedDirectives/__tests__/__fixtures__/leafDirectives.md",
+ },
+ ],
+ ]
+ `);
+ });
+ it('if file contains unused text directive', async () => {
+ using log = vi.spyOn(console, 'log');
+ const result = await processFixture(
+ 'textDirectives',
+ fileData,
+ options,
+ );
+ expect(result).toMatchSnapshot('result');
+ expect(log).toHaveBeenCalledTimes(1);
+ expect(log.mock.calls).toMatchInlineSnapshot(`
+ [
+ [
+ "onUnusedMarkdownDirectives called with",
+ {
+ "directives": [
+ {
+ "attributes": {},
+ "children": [],
+ "name": "fixed-textDirective3",
+ "position": {
+ "end": {
+ "column": 29,
+ "line": 9,
+ "offset": 112,
+ },
+ "start": {
+ "column": 7,
+ "line": 9,
+ "offset": 90,
+ },
+ },
+ "type": "textDirective",
+ },
+ {
+ "attributes": {
+ "age": "42",
+ },
+ "children": [],
+ "name": "fixed-textDirective4",
+ "position": {
+ "end": {
+ "column": 30,
+ "line": 11,
+ "offset": 143,
+ },
+ "start": {
+ "column": 7,
+ "line": 11,
+ "offset": 120,
+ },
+ },
+ "type": "textDirective",
+ },
+ ],
+ "sourceFilePath": "packages/docusaurus-mdx-loader/src/remark/unusedDirectives/__tests__/__fixtures__/textDirectives.md",
+ },
+ ],
+ ]
+ `);
+ });
+ });
+
+ describe('ignore', () => {
+ const options = {onUnusedMarkdownDirectives: 'ignore'} as const;
+ it('if file contains unused container directive', async () => {
+ using warn = vi.spyOn(console, 'warn');
+ using log = vi.spyOn(console, 'log');
+ const result = await processFixture(
+ 'containerDirectives',
+ fileData,
+ options,
+ );
+ expect(result).toMatchSnapshot('result');
+ expect(log).toHaveBeenCalledTimes(0);
+ expect(warn).toHaveBeenCalledTimes(0);
+ });
+ it('if file contains unused leaf directive', async () => {
+ using warn = vi.spyOn(console, 'warn');
+ using log = vi.spyOn(console, 'log');
+ const result = await processFixture(
+ 'leafDirectives',
+ fileData,
+ options,
+ );
+ expect(result).toMatchSnapshot('result');
+ expect(log).toHaveBeenCalledTimes(0);
+ expect(warn).toHaveBeenCalledTimes(0);
+ });
+ it('if file contains unused text directive', async () => {
+ using warn = vi.spyOn(console, 'warn');
+ using log = vi.spyOn(console, 'log');
+ const result = await processFixture(
+ 'textDirectives',
+ fileData,
+ options,
+ );
+ expect(result).toMatchSnapshot('result');
+ expect(log).toHaveBeenCalledTimes(0);
+ expect(warn).toHaveBeenCalledTimes(0);
+ });
+ });
+ });
});
describe('directives remark plugin - server compiler', () => {
- const options = {compilerName: 'server'} as const;
+ const fileData = {compilerName: 'server'} as const;
it('default behavior for container directives', async () => {
using warn = vi.spyOn(console, 'warn');
- const result = await processFixture('containerDirectives', options);
+ const result = await processFixture('containerDirectives', fileData);
expect(result).toMatchSnapshot('result');
expect(warn).toHaveBeenCalledTimes(0);
});
it('default behavior for leaf directives', async () => {
using warn = vi.spyOn(console, 'warn');
- const result = await processFixture('leafDirectives', options);
+ const result = await processFixture('leafDirectives', fileData);
expect(result).toMatchSnapshot('result');
expect(warn).toHaveBeenCalledTimes(0);
});
it('default behavior for text directives', async () => {
using warn = vi.spyOn(console, 'warn');
- const result = await processFixture('textDirectives', options);
+ const result = await processFixture('textDirectives', fileData);
expect(result).toMatchSnapshot('result');
expect(warn).toHaveBeenCalledTimes(0);
});
diff --git a/packages/docusaurus-mdx-loader/src/remark/unusedDirectives/index.ts b/packages/docusaurus-mdx-loader/src/remark/unusedDirectives/index.ts
index 29b458b19ce9..337a53cce590 100644
--- a/packages/docusaurus-mdx-loader/src/remark/unusedDirectives/index.ts
+++ b/packages/docusaurus-mdx-loader/src/remark/unusedDirectives/index.ts
@@ -7,12 +7,16 @@
import path from 'path';
import process from 'process';
import logger from '@docusaurus/logger';
-import {posixPath} from '@docusaurus/utils';
+import {toMessageRelativeFilePath, posixPath} from '@docusaurus/utils';
import {formatNodePositionExtraMessage, transformNode} from '../utils';
import type {Root} from 'mdast';
import type {Parent} from 'unist';
import type {Transformer, Processor, Plugin} from 'unified';
import type {Directives, TextDirective} from 'mdast-util-directive';
+import type {
+ MarkdownConfig,
+ OnUnusedMarkdownDirectivesFunction,
+} from '@docusaurus/types';
type DirectiveType = Directives['type'];
@@ -64,19 +68,33 @@ ${warningMessages}
Your content might render in an unexpected way. Visit ${customSupportUrl} to find out why and how to fix it.`;
}
-function logUnusedDirectivesWarning({
- directives,
- filePath,
-}: {
- directives: Directives[];
- filePath: string;
-}) {
- if (directives.length > 0) {
- const message = formatUnusedDirectivesMessage({
- directives,
- filePath,
- });
- logger.warn(message);
+export type PluginOptions = {
+ onUnusedMarkdownDirectives: MarkdownConfig['hooks']['onUnusedMarkdownDirectives'];
+};
+
+function asFunction(
+ onUnusedMarkdownDirectives: PluginOptions['onUnusedMarkdownDirectives'],
+): OnUnusedMarkdownDirectivesFunction {
+ if (typeof onUnusedMarkdownDirectives === 'string') {
+ const extraHelp =
+ onUnusedMarkdownDirectives === 'throw'
+ ? logger.interpolate`\nTo ignore this error, use the code=${'siteConfig.markdown.hooks.onUnusedMarkdownDirectives'} option.`
+ : '';
+ return ({sourceFilePath, directives}) => {
+ const relativePath = toMessageRelativeFilePath(sourceFilePath);
+ logger.report(onUnusedMarkdownDirectives)`${formatUnusedDirectivesMessage(
+ {
+ directives,
+ filePath: relativePath,
+ },
+ )}${extraHelp}`;
+ };
+ } else {
+ return (params) =>
+ onUnusedMarkdownDirectives({
+ ...params,
+ sourceFilePath: toMessageRelativeFilePath(params.sourceFilePath),
+ });
}
}
@@ -112,9 +130,14 @@ function isUnusedDirective(directive: Directives) {
return !directive.data;
}
-const plugin: Plugin = function plugin(
+const plugin: Plugin = function plugin(
this: Processor,
+ options,
): Transformer {
+ const onUnusedMarkdownDirectives = asFunction(
+ options.onUnusedMarkdownDirectives,
+ );
+
return async (tree, file) => {
const {visit} = await import('unist-util-visit');
@@ -133,14 +156,14 @@ const plugin: Plugin = function plugin(
}
});
- // We only enable these warnings for the client compiler
- // This avoids emitting duplicate warnings in prod mode
+ // We only process unused directives for the client compiler
+ // This avoids emitting duplicate errors/warnings in prod mode
// Note: the client compiler is used in both dev/prod modes
// Also: the client compiler is what gets used when using crossCompilerCache
- if (file.data.compilerName === 'client') {
- logUnusedDirectivesWarning({
+ if (file.data.compilerName === 'client' && unusedDirectives.length > 0) {
+ onUnusedMarkdownDirectives({
+ sourceFilePath: file.path!,
directives: unusedDirectives,
- filePath: file.path,
});
}
};
diff --git a/packages/docusaurus-types/src/index.d.ts b/packages/docusaurus-types/src/index.d.ts
index 6dfca63d1f70..6cdd2f839bb1 100644
--- a/packages/docusaurus-types/src/index.d.ts
+++ b/packages/docusaurus-types/src/index.d.ts
@@ -28,6 +28,7 @@ export {
ParseFrontMatter,
OnBrokenMarkdownLinksFunction,
OnBrokenMarkdownImagesFunction,
+ OnUnusedMarkdownDirectivesFunction,
} from './markdown';
export {ReportingSeverity} from './reporting';
diff --git a/packages/docusaurus-types/src/markdown.d.ts b/packages/docusaurus-types/src/markdown.d.ts
index e7904f46bcc1..7c2d84cd3fdf 100644
--- a/packages/docusaurus-types/src/markdown.d.ts
+++ b/packages/docusaurus-types/src/markdown.d.ts
@@ -7,6 +7,7 @@
import type {ProcessorOptions} from '@mdx-js/mdx';
import type {Image, Definition, Link} from 'mdast';
+import type {Directives} from 'mdast-util-directive';
import type {ReportingSeverity} from './reporting';
@@ -86,6 +87,21 @@ export type OnBrokenMarkdownImagesFunction = (params: {
node: Image;
}) => void | string;
+export type OnUnusedMarkdownDirectivesFunction = (params: {
+ /**
+ * Path of the source file on which the unused directive was found
+ * Relative to the site dir.
+ * Example: "docs/category/myDoc.mdx"
+ */
+ sourceFilePath: string;
+
+ /**
+ * The Markdown directives that were unused.
+ * Example: "myDirective"
+ */
+ directives: Directives[];
+}) => void | string;
+
export type MarkdownHooks = {
/**
* The behavior of Docusaurus when it detects any broken Markdown link.
@@ -97,6 +113,10 @@ export type MarkdownHooks = {
onBrokenMarkdownLinks: ReportingSeverity | OnBrokenMarkdownLinksFunction;
onBrokenMarkdownImages: ReportingSeverity | OnBrokenMarkdownImagesFunction;
+
+ onUnusedMarkdownDirectives:
+ | ReportingSeverity
+ | OnUnusedMarkdownDirectivesFunction;
};
export type MarkdownConfig = {
diff --git a/packages/docusaurus/src/server/__tests__/__snapshots__/config.test.ts.snap b/packages/docusaurus/src/server/__tests__/__snapshots__/config.test.ts.snap
index 229eb8ecc9d4..91cd600f31af 100644
--- a/packages/docusaurus/src/server/__tests__/__snapshots__/config.test.ts.snap
+++ b/packages/docusaurus/src/server/__tests__/__snapshots__/config.test.ts.snap
@@ -50,6 +50,7 @@ exports[`loadSiteConfig > website with .cjs siteConfig 1`] = `
"hooks": {
"onBrokenMarkdownImages": "throw",
"onBrokenMarkdownLinks": "warn",
+ "onUnusedMarkdownDirectives": "warn",
},
"mdx1Compat": {
"admonitions": true,
@@ -137,6 +138,7 @@ exports[`loadSiteConfig > website with ts + js config 1`] = `
"hooks": {
"onBrokenMarkdownImages": "throw",
"onBrokenMarkdownLinks": "warn",
+ "onUnusedMarkdownDirectives": "warn",
},
"mdx1Compat": {
"admonitions": true,
@@ -224,6 +226,7 @@ exports[`loadSiteConfig > website with valid JS CJS config 1`] = `
"hooks": {
"onBrokenMarkdownImages": "throw",
"onBrokenMarkdownLinks": "warn",
+ "onUnusedMarkdownDirectives": "warn",
},
"mdx1Compat": {
"admonitions": true,
@@ -311,6 +314,7 @@ exports[`loadSiteConfig > website with valid JS ESM config 1`] = `
"hooks": {
"onBrokenMarkdownImages": "throw",
"onBrokenMarkdownLinks": "warn",
+ "onUnusedMarkdownDirectives": "warn",
},
"mdx1Compat": {
"admonitions": true,
@@ -398,6 +402,7 @@ exports[`loadSiteConfig > website with valid TypeScript CJS config 1`] = `
"hooks": {
"onBrokenMarkdownImages": "throw",
"onBrokenMarkdownLinks": "warn",
+ "onUnusedMarkdownDirectives": "warn",
},
"mdx1Compat": {
"admonitions": true,
@@ -485,6 +490,7 @@ exports[`loadSiteConfig > website with valid TypeScript ESM config 1`] = `
"hooks": {
"onBrokenMarkdownImages": "throw",
"onBrokenMarkdownLinks": "warn",
+ "onUnusedMarkdownDirectives": "warn",
},
"mdx1Compat": {
"admonitions": true,
@@ -572,6 +578,7 @@ exports[`loadSiteConfig > website with valid async config 1`] = `
"hooks": {
"onBrokenMarkdownImages": "throw",
"onBrokenMarkdownLinks": "warn",
+ "onUnusedMarkdownDirectives": "warn",
},
"mdx1Compat": {
"admonitions": true,
@@ -661,6 +668,7 @@ exports[`loadSiteConfig > website with valid async config creator function 1`] =
"hooks": {
"onBrokenMarkdownImages": "throw",
"onBrokenMarkdownLinks": "warn",
+ "onUnusedMarkdownDirectives": "warn",
},
"mdx1Compat": {
"admonitions": true,
@@ -750,6 +758,7 @@ exports[`loadSiteConfig > website with valid config creator function 1`] = `
"hooks": {
"onBrokenMarkdownImages": "throw",
"onBrokenMarkdownLinks": "warn",
+ "onUnusedMarkdownDirectives": "warn",
},
"mdx1Compat": {
"admonitions": true,
@@ -842,6 +851,7 @@ exports[`loadSiteConfig > website with valid siteConfig 1`] = `
"hooks": {
"onBrokenMarkdownImages": "throw",
"onBrokenMarkdownLinks": "warn",
+ "onUnusedMarkdownDirectives": "warn",
},
"mdx1Compat": {
"admonitions": true,
diff --git a/packages/docusaurus/src/server/__tests__/__snapshots__/site.test.ts.snap b/packages/docusaurus/src/server/__tests__/__snapshots__/site.test.ts.snap
index 0cda8a64877a..f2859430fdc3 100644
--- a/packages/docusaurus/src/server/__tests__/__snapshots__/site.test.ts.snap
+++ b/packages/docusaurus/src/server/__tests__/__snapshots__/site.test.ts.snap
@@ -138,6 +138,7 @@ exports[`loadSite > custom-i18n-site > loads site 1`] = `
"hooks": {
"onBrokenMarkdownImages": "throw",
"onBrokenMarkdownLinks": "warn",
+ "onUnusedMarkdownDirectives": "warn",
},
"mdx1Compat": {
"admonitions": true,
@@ -305,6 +306,7 @@ exports[`loadSite > simple-site-with-baseUrl > loads site - custom config 1`] =
"hooks": {
"onBrokenMarkdownImages": "throw",
"onBrokenMarkdownLinks": "warn",
+ "onUnusedMarkdownDirectives": "warn",
},
"mdx1Compat": {
"admonitions": true,
@@ -472,6 +474,7 @@ exports[`loadSite > simple-site-with-baseUrl > loads site - custom outDir 1`] =
"hooks": {
"onBrokenMarkdownImages": "throw",
"onBrokenMarkdownLinks": "warn",
+ "onUnusedMarkdownDirectives": "warn",
},
"mdx1Compat": {
"admonitions": true,
@@ -639,6 +642,7 @@ exports[`loadSite > simple-site-with-baseUrl > loads site 1`] = `
"hooks": {
"onBrokenMarkdownImages": "throw",
"onBrokenMarkdownLinks": "warn",
+ "onUnusedMarkdownDirectives": "warn",
},
"mdx1Compat": {
"admonitions": true,
@@ -872,6 +876,7 @@ exports[`loadSite > simple-site-with-baseUrl-i18n > loads site - locale fr + cu
"hooks": {
"onBrokenMarkdownImages": "throw",
"onBrokenMarkdownLinks": "warn",
+ "onUnusedMarkdownDirectives": "warn",
},
"mdx1Compat": {
"admonitions": true,
@@ -1105,6 +1110,7 @@ exports[`loadSite > simple-site-with-baseUrl-i18n > loads site - custom outDir 1
"hooks": {
"onBrokenMarkdownImages": "throw",
"onBrokenMarkdownLinks": "warn",
+ "onUnusedMarkdownDirectives": "warn",
},
"mdx1Compat": {
"admonitions": true,
@@ -1338,6 +1344,7 @@ exports[`loadSite > simple-site-with-baseUrl-i18n > loads site - locale de 1`] =
"hooks": {
"onBrokenMarkdownImages": "throw",
"onBrokenMarkdownLinks": "warn",
+ "onUnusedMarkdownDirectives": "warn",
},
"mdx1Compat": {
"admonitions": true,
@@ -1571,6 +1578,7 @@ exports[`loadSite > simple-site-with-baseUrl-i18n > loads site - locale en 1`] =
"hooks": {
"onBrokenMarkdownImages": "throw",
"onBrokenMarkdownLinks": "warn",
+ "onUnusedMarkdownDirectives": "warn",
},
"mdx1Compat": {
"admonitions": true,
@@ -1804,6 +1812,7 @@ exports[`loadSite > simple-site-with-baseUrl-i18n > loads site - locale es 1`] =
"hooks": {
"onBrokenMarkdownImages": "throw",
"onBrokenMarkdownLinks": "warn",
+ "onUnusedMarkdownDirectives": "warn",
},
"mdx1Compat": {
"admonitions": true,
@@ -2037,6 +2046,7 @@ exports[`loadSite > simple-site-with-baseUrl-i18n > loads site - locale fr 1`] =
"hooks": {
"onBrokenMarkdownImages": "throw",
"onBrokenMarkdownLinks": "warn",
+ "onUnusedMarkdownDirectives": "warn",
},
"mdx1Compat": {
"admonitions": true,
@@ -2270,6 +2280,7 @@ exports[`loadSite > simple-site-with-baseUrl-i18n > loads site - locale it 1`] =
"hooks": {
"onBrokenMarkdownImages": "throw",
"onBrokenMarkdownLinks": "warn",
+ "onUnusedMarkdownDirectives": "warn",
},
"mdx1Compat": {
"admonitions": true,
@@ -2503,6 +2514,7 @@ exports[`loadSite > simple-site-with-baseUrl-i18n > loads site 1`] = `
"hooks": {
"onBrokenMarkdownImages": "throw",
"onBrokenMarkdownLinks": "warn",
+ "onUnusedMarkdownDirectives": "warn",
},
"mdx1Compat": {
"admonitions": true,
diff --git a/packages/docusaurus/src/server/__tests__/configValidation.test.ts b/packages/docusaurus/src/server/__tests__/configValidation.test.ts
index 63dce02913f5..8f4bacaad688 100644
--- a/packages/docusaurus/src/server/__tests__/configValidation.test.ts
+++ b/packages/docusaurus/src/server/__tests__/configValidation.test.ts
@@ -130,6 +130,7 @@ describe('normalizeConfig', () => {
hooks: {
onBrokenMarkdownLinks: 'log',
onBrokenMarkdownImages: 'log',
+ onUnusedMarkdownDirectives: 'log',
},
},
};
@@ -546,6 +547,7 @@ describe('markdown', () => {
hooks: {
onBrokenMarkdownLinks: 'log',
onBrokenMarkdownImages: 'warn',
+ onUnusedMarkdownDirectives: 'warn',
},
};
expect(normalizeMarkdown(markdown)).toEqual(markdown);
@@ -818,6 +820,47 @@ describe('markdown', () => {
`);
});
});
+
+ describe('onUnusedMarkdownDirectives', () => {
+ function normalizeValue(
+ onUnusedMarkdownDirectives?: MarkdownHooks['onUnusedMarkdownDirectives'],
+ ) {
+ return normalizeHooks({
+ onUnusedMarkdownDirectives,
+ }).onUnusedMarkdownDirectives;
+ }
+
+ it('accepts undefined', () => {
+ expect(normalizeValue(undefined)).toBe('warn');
+ });
+
+ it('accepts severity level', () => {
+ expect(normalizeValue('log')).toBe('log');
+ });
+
+ it('rejects number', () => {
+ expect(() =>
+ normalizeValue(
+ // @ts-expect-error: bad value
+ 42,
+ ),
+ ).toThrowErrorMatchingInlineSnapshot(`
+ [Error: "markdown.hooks.onUnusedMarkdownDirectives" does not match any of the allowed types
+ ]
+ `);
+ });
+
+ it('accepts function', () => {
+ expect(normalizeValue(() => {})).toBeInstanceOf(Function);
+ });
+
+ it('rejects null', () => {
+ expect(() => normalizeValue(null)).toThrowErrorMatchingInlineSnapshot(`
+ [Error: "markdown.hooks.onUnusedMarkdownDirectives" does not match any of the allowed types
+ ]
+ `);
+ });
+ });
});
});
diff --git a/packages/docusaurus/src/server/configValidation.ts b/packages/docusaurus/src/server/configValidation.ts
index 462e5b2274d6..a108818d3b33 100644
--- a/packages/docusaurus/src/server/configValidation.ts
+++ b/packages/docusaurus/src/server/configValidation.ts
@@ -123,6 +123,7 @@ export const DEFAULT_FUTURE_CONFIG: FutureConfig = {
export const DEFAULT_MARKDOWN_HOOKS: MarkdownHooks = {
onBrokenMarkdownLinks: 'warn',
onBrokenMarkdownImages: 'throw',
+ onUnusedMarkdownDirectives: 'warn',
};
export const DEFAULT_MARKDOWN_MDX1COMPAT: MDX1CompatOptions = {
@@ -548,6 +549,12 @@ export const ConfigSchema = Joi.object({
Joi.function(),
)
.default(DEFAULT_CONFIG.markdown.hooks.onBrokenMarkdownImages),
+ onUnusedMarkdownDirectives: Joi.alternatives()
+ .try(
+ Joi.string().equal('ignore', 'log', 'warn', 'throw'),
+ Joi.function(),
+ )
+ .default(DEFAULT_CONFIG.markdown.hooks.onUnusedMarkdownDirectives),
}).default(DEFAULT_CONFIG.markdown.hooks),
}).default({
...DEFAULT_CONFIG.markdown,
diff --git a/website/docs/api/docusaurus.config.js.mdx b/website/docs/api/docusaurus.config.js.mdx
index 304235fa2f3e..f0400b72009c 100644
--- a/website/docs/api/docusaurus.config.js.mdx
+++ b/website/docs/api/docusaurus.config.js.mdx
@@ -661,7 +661,7 @@ type MarkdownAnchorsConfig = {
type OnBrokenMarkdownLinksFunction = (params: {
sourceFilePath: string; // MD/MDX source file relative to cwd
url: string; // Link url
- node: Link | Definition; // mdast Node
+ node: Link | Definition; // mdast node
}) => void | string;
type OnBrokenMarkdownImagesFunction = (params: {
@@ -670,11 +670,19 @@ type OnBrokenMarkdownImagesFunction = (params: {
node: Image; // mdast node
}) => void | string;
+type OnUnusedMarkdownDirectivesFunction = (params: {
+ sourceFilePath: string; // MD/MDX source file relative to cwd
+ directives: Directives[]; // mdast nodes
+}) => void | string;
+
type ReportingSeverity = 'ignore' | 'log' | 'warn' | 'throw';
type MarkdownHooks = {
onBrokenMarkdownLinks: ReportingSeverity | OnBrokenMarkdownLinksFunction;
onBrokenMarkdownImages: ReportingSeverity | OnBrokenMarkdownImagesFunction;
+ onUnusedMarkdownDirectives:
+ | ReportingSeverity
+ | onUnusedMarkdownDirectivesFunction;
};
type MarkdownConfig = {
@@ -718,6 +726,7 @@ export default {
hooks: {
onBrokenMarkdownLinks: 'warn',
onBrokenMarkdownImages: 'throw',
+ onUnusedMarkdownDirectives: 'warn',
},
},
};