Fix cases for JSX-like expressions in code blocks of headings (#3502)
* chore: fix typo in remark tests * test: add test cases for markdown expressions in header * fix: avoid evaluating JSX-like expressions inside inline code in heading * fix: generate slug for id including values in backtick blocks
This commit is contained in:
parent
d2bfb754c0
commit
939fe15925
3 changed files with 51 additions and 10 deletions
5
.changeset/lazy-humans-invent.md
Normal file
5
.changeset/lazy-humans-invent.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
'@astrojs/markdown-remark': minor
|
||||
---
|
||||
|
||||
Fix cases for JSX-like expressions in code blocks of headings
|
|
@ -1,4 +1,5 @@
|
|||
import { visit } from 'unist-util-visit';
|
||||
import { toHtml } from 'hast-util-to-html';
|
||||
import Slugger from 'github-slugger';
|
||||
|
||||
import type { MarkdownHeader, RehypePlugin } from './types.js';
|
||||
|
@ -17,32 +18,36 @@ export default function createCollectHeaders() {
|
|||
if (!level) return;
|
||||
const depth = Number.parseInt(level);
|
||||
|
||||
let raw = '';
|
||||
let text = '';
|
||||
let isJSX = false;
|
||||
visit(node, (child) => {
|
||||
if (child.type === 'element') {
|
||||
visit(node, (child, _, parent) => {
|
||||
if (child.type === 'element' || parent == null) {
|
||||
return;
|
||||
}
|
||||
if (child.type === 'raw') {
|
||||
// HACK: serialized JSX from internal plugins, ignore these for slug
|
||||
if (child.value.startsWith('\n<') || child.value.endsWith('>\n')) {
|
||||
raw += child.value.replace(/^\n|\n$/g, '');
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (child.type === 'text' || child.type === 'raw') {
|
||||
raw += child.value;
|
||||
text += child.value;
|
||||
isJSX = isJSX || child.value.includes('{');
|
||||
if (new Set(['code', 'pre']).has(parent.tagName)) {
|
||||
text += child.value;
|
||||
} else {
|
||||
text += child.value.replace(/\{/g, '${');
|
||||
isJSX = isJSX || child.value.includes('{');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
node.properties = node.properties || {};
|
||||
if (typeof node.properties.id !== 'string') {
|
||||
if (isJSX) {
|
||||
// HACK: serialized JSX from internal plugins, ignore these for slug
|
||||
const raw = toHtml(node.children, { allowDangerousHtml: true })
|
||||
.replace(/\n(<)/g, '<')
|
||||
.replace(/(>)\n/g, '>');
|
||||
// HACK: for ids that have JSX content, use $$slug helper to generate slug at runtime
|
||||
node.properties.id = `$$slug(\`${text.replace(/\{/g, '${')}\`)`;
|
||||
node.properties.id = `$$slug(\`${text}\`)`;
|
||||
(node as any).type = 'raw';
|
||||
(
|
||||
node as any
|
||||
|
|
|
@ -2,7 +2,7 @@ import { renderMarkdown } from '../dist/index.js';
|
|||
import chai from 'chai';
|
||||
|
||||
describe('expressions', () => {
|
||||
it('should be able to serialize bare expession', async () => {
|
||||
it('should be able to serialize bare expression', async () => {
|
||||
const { code } = await renderMarkdown(`{a}`, {});
|
||||
|
||||
chai.expect(code).to.equal(`{a}`);
|
||||
|
@ -40,6 +40,37 @@ describe('expressions', () => {
|
|||
);
|
||||
});
|
||||
|
||||
it('should be able to avoid evaluating JSX-like expressions in an inline code & generate a slug for id', async () => {
|
||||
const { code } = await renderMarkdown(`# \`{frontmatter.title}\``, {});
|
||||
|
||||
chai
|
||||
.expect(code)
|
||||
.to.equal('<h1 id="frontmattertitle"><code is:raw>{frontmatter.title}</code></h1>');
|
||||
});
|
||||
|
||||
it('should be able to avoid evaluating JSX-like expressions in inline codes', async () => {
|
||||
const { code } = await renderMarkdown(`# \`{ foo }\` is a shorthand for \`{ foo: foo }\``, {});
|
||||
|
||||
chai
|
||||
.expect(code)
|
||||
.to.equal(
|
||||
'<h1 id="-foo--is-a-shorthand-for--foo-foo-"><code is:raw>{ foo }</code> is a shorthand for <code is:raw>{ foo: foo }</code></h1>'
|
||||
);
|
||||
});
|
||||
|
||||
it('should be able to avoid evaluating JSX-like expressions & escape HTML tag characters in inline codes', async () => {
|
||||
const { code } = await renderMarkdown(
|
||||
`###### \`{}\` is equivalent to \`Record<never, never>\` <small>(at TypeScript v{frontmatter.version})</small>`,
|
||||
{}
|
||||
);
|
||||
|
||||
chai
|
||||
.expect(code)
|
||||
.to.equal(
|
||||
`<h6 id={$$slug(\`{} is equivalent to Record<never, never> (at TypeScript v\${frontmatter.version})\`)}><code is:raw>{}</code> is equivalent to <code is:raw>Record<never, never></code> <small>(at TypeScript v{frontmatter.version})</small></h6>`
|
||||
);
|
||||
});
|
||||
|
||||
it('should be able to serialize function expression', async () => {
|
||||
const { code } = await renderMarkdown(
|
||||
`{frontmatter.list.map(item => <p id={item}>{item}</p>)}`,
|
||||
|
|
Loading…
Reference in a new issue