0
Fork 0
mirror of https://github.com/withastro/astro.git synced 2024-12-16 21:46:22 -05:00

Pass meta to shiki transformers (#10494)

This commit is contained in:
Bjorn Lu 2024-03-20 19:15:07 +08:00 committed by GitHub
parent 17b4991cff
commit 19e42c3681
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 55 additions and 3 deletions

View file

@ -0,0 +1,5 @@
---
"@astrojs/markdown-remark": patch
---
Fixes support for Shiki transformers that access the `meta` to conditionally perform transformations

View file

@ -12,6 +12,9 @@ export default async function shiki(config?: ShikiConfig): Promise<AstroMarkdocC
fence: {
attributes: Markdoc.nodes.fence.attributes!,
transform({ attributes }) {
// NOTE: The `meta` from fence code, e.g. ```js {1,3-4}, isn't quite supported by Markdoc.
// Only the `js` part is parsed as `attributes.language` and the rest is ignored. This means
// some Shiki transformers may not work correctly as it relies on the `meta`.
const lang = typeof attributes.language === 'string' ? attributes.language : 'plaintext';
const html = highlighter.highlight(attributes.content, lang);

View file

@ -4,7 +4,7 @@ import { toText } from 'hast-util-to-text';
import { removePosition } from 'unist-util-remove-position';
import { visitParents } from 'unist-util-visit-parents';
type Highlighter = (code: string, language: string) => string;
type Highlighter = (code: string, language: string, options?: { meta?: string }) => string;
const languagePattern = /\blanguage-(\S+)\b/;
@ -55,8 +55,9 @@ export function highlightCodeBlocks(tree: Root, highlighter: Highlighter) {
return;
}
const meta = (node.data as any)?.meta ?? node.properties.metastring ?? undefined;
const code = toText(node, { whitespace: 'pre' });
const html = highlighter(code, languageMatch?.[1] || 'plaintext');
const html = highlighter(code, languageMatch?.[1] || 'plaintext', { meta });
// The replacement returns a root node with 1 child, the `<pr>` element replacement.
const replacement = fromHtml(html, { fragment: true }).children[0] as Element;
// We just generated this node, so any positional information is invalid.

View file

@ -7,7 +7,14 @@ export interface ShikiHighlighter {
highlight(
code: string,
lang?: string,
options?: { inline?: boolean; attributes?: Record<string, string> }
options?: {
inline?: boolean;
attributes?: Record<string, string>;
/**
* Raw `meta` information to be used by Shiki transformers
*/
meta?: string;
}
): string;
}
@ -56,6 +63,10 @@ export async function createShikiHighlighter({
return highlighter.codeToHtml(code, {
...themeOptions,
lang,
// NOTE: while we can spread `options.attributes` here so that Shiki can auto-serialize this as rendered
// attributes on the top-level tag, it's not clear whether it is fine to pass all attributes as meta, as
// they're technically not meta, nor parsed from Shiki's `parseMetaString` API.
meta: options?.meta ? { __raw: options?.meta } : undefined,
transformers: [
{
pre(node) {

View file

@ -53,4 +53,36 @@ describe('shiki syntax highlighting', () => {
assert.match(html, />-<\/span>/);
assert.match(html, />+<\/span>/);
});
it('renders attributes', async () => {
const highlighter = await createShikiHighlighter();
const html = highlighter.highlight(`foo`, 'js', {
attributes: { 'data-foo': 'bar', autofocus: true },
});
assert.match(html, /data-foo="bar"/);
assert.match(html, /autofocus(?!=)/);
});
it('supports transformers that reads meta', async () => {
const highlighter = await createShikiHighlighter({
transformers: [
{
pre(node) {
const meta = this.options.meta?.__raw;
if (meta) {
node.properties['data-test'] = meta;
}
},
},
],
});
const html = highlighter.highlight(`foo`, 'js', {
meta: '{1,3-4}',
});
assert.match(html, /data-test="\{1,3-4\}"/);
});
});