Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions src/utils/__tests__/remark.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import assert from 'node:assert/strict';
import { describe, it } from 'node:test';

import { getRemarkRecma } from '../remark.mjs';

const getCodeTabsAttributes = tree => {
const attributes = [];

const visit = node => {
if (!node || typeof node !== 'object') {
return;
}

if (node.type === 'JSXOpeningElement' && node.name?.name === 'CodeTabs') {
attributes.push(
Object.fromEntries(
node.attributes.map(attribute => [
attribute.name.name,
attribute.value?.value,
])
)
);
}

Object.values(node).forEach(value => {
if (Array.isArray(value)) {
value.forEach(visit);
} else {
visit(value);
}
});
};

visit(tree);
Comment on lines +9 to +34

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can you use unist-util-visit?


return attributes;
};

describe('getRemarkRecma', () => {
it('preserves code tab display names when raw HTML is enabled', async () => {
const processor = getRemarkRecma();
const tree = await processor.run(
processor.parse(`
<div class="note">raw html</div>

\`\`\`cjs displayName="main.js"
console.log(1);
\`\`\`

\`\`\`cjs displayName="main.test.js"
console.log(2);
\`\`\`
`)
);
Comment on lines +43 to +54

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

dedent, please


assert.deepEqual(getCodeTabsAttributes(tree), [
{
languages: 'cjs|cjs',
displayNames: 'main.js|main.test.js',
defaultTab: '0',
},
]);
});
});
32 changes: 32 additions & 0 deletions src/utils/remark.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import remarkParse from 'remark-parse';
import remarkRehype from 'remark-rehype';
import remarkStringify from 'remark-stringify';
import { unified } from 'unified';
import { visit } from 'unist-util-visit';

import syntaxHighlighter, { highlighter } from './highlighter.mjs';
import { lazy } from './misc.mjs';
Expand All @@ -20,6 +21,35 @@ import transformAlerts from '../generators/jsx-ast/utils/plugins/alerts.mjs';
import transformElements from '../generators/jsx-ast/utils/plugins/transformer.mjs';

const passThrough = ['element', ...Object.values(AST_NODE_TYPES.MDX)];
const codeMetaProperty = 'codeMeta';

/**
* Stores fenced code metadata on properties before rehypeRaw reparses the tree.
*/
const preserveCodeMeta = () => tree => {
visit(tree, 'element', node => {
const meta = node.data?.meta;

if (node.tagName === 'code' && typeof meta === 'string') {
node.properties ||= {};
node.properties[codeMetaProperty] = meta;
}
});
};

/**
* Restores fenced code metadata so the Shiki plugin can read displayName.
*/
const restoreCodeMeta = () => tree => {
visit(tree, 'element', node => {
const meta = node.properties?.[codeMetaProperty];

if (node.tagName === 'code' && typeof meta === 'string') {
node.data = { ...node.data, meta };
delete node.properties[codeMetaProperty];
}
});
};

/**
* Retrieves an instance of Remark configured to parse GFM (GitHub Flavored Markdown)
Expand Down Expand Up @@ -91,8 +121,10 @@ export const getRemarkRecma = lazy(() =>
// We also allow dangerous HTML to be passed through, since we have HTML within our Markdown
// and we trust the sources of the Markdown files
.use(remarkRehype, { allowDangerousHtml: true, passThrough })
.use(preserveCodeMeta)
// Any `raw` HTML in the markdown must be converted to AST in order for Recma to understand it
.use(rehypeRaw, { passThrough })
.use(restoreCodeMeta)
.use(() => singletonShiki)
.use(transformElements)
.use(rehypeRecma)
Expand Down