Skip to content

Commit 592f184

Browse files
NexucisCopilot
andcommitted
Highlight the code blocks whose language is not lowercase
GitHub ignores the case of the language of a fenced code block, whereas the language ids of Shiki are case-sensitive. The `typeScript` blocks of the upgrade guide to v0.54.0 were rendered as plain text. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Augustin Husson <husson.augustin@gmail.com>
1 parent 655b24b commit 592f184

2 files changed

Lines changed: 29 additions & 0 deletions

File tree

‎src/content/markdown/render.server.test.ts‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,13 @@ describe('renderMarkdown', () => {
7777
expect(result.hasMermaid).toBe(true);
7878
});
7979

80+
it('ignores the case of the language of the code blocks, like GitHub', async () => {
81+
const mixedCase = await renderMarkdown('```typeScript\nconst a = 1;\n```\n');
82+
const lowerCase = await renderMarkdown('```typescript\nconst a = 1;\n```\n');
83+
expect(mixedCase.hast).toEqual(lowerCase.hast);
84+
expect(findElements(mixedCase.hast, 'code')[0]?.properties.class).toEqual(['language-typescript']);
85+
});
86+
8087
it('rewrites the URLs with the resolver', async () => {
8188
const result = await renderMarkdown('[page](./other.md#anchor) ![image](img.png) [external](https://example.com)', {
8289
resolveUrl: (url, kind) => (url.startsWith('http') ? undefined : `/resolved/${kind}/${url}`),

‎src/content/markdown/render.server.ts‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,27 @@ export function markdownTitle(markdown: string): string | undefined {
135135
return undefined;
136136
}
137137

138+
/**
139+
* rehypeLowercaseLanguages lowercases the `language-*` class of the code blocks. GitHub ignores the case of the
140+
* language of a fenced code block (`typeScript`), whereas the language ids of Shiki are case-sensitive.
141+
*/
142+
function rehypeLowercaseLanguages() {
143+
return (tree: HastRoot) => {
144+
visit(tree, 'element', (node: Element, _index, parent) => {
145+
if (node.tagName !== 'code' || parent?.type !== 'element' || parent.tagName !== 'pre') {
146+
return;
147+
}
148+
const classes = node.properties.className;
149+
if (!Array.isArray(classes)) {
150+
return;
151+
}
152+
node.properties.className = classes.map((name) =>
153+
typeof name === 'string' && name.startsWith('language-') ? name.toLowerCase() : name,
154+
);
155+
});
156+
};
157+
}
158+
138159
/**
139160
* rehypeMermaid replaces the mermaid code blocks with a `pre.mermaid` element, rendered on the client side.
140161
*/
@@ -234,6 +255,7 @@ export async function renderMarkdown(markdown: string, options: RenderOptions =
234255
.use(remarkRehype, { allowDangerousHtml: true })
235256
.use(rehypeRaw)
236257
.use(rehypeHeadings, headings)
258+
.use(rehypeLowercaseLanguages)
237259
.use(rehypeMermaid, mermaid)
238260
.use(rehypeShikiFromHighlighter, highlighter, {
239261
themes: shikiThemes,

0 commit comments

Comments
 (0)