From 4d81712b1f30a516f898e82055598ebfa90a2e91 Mon Sep 17 00:00:00 2001 From: Anthony Fu Date: Thu, 22 Aug 2024 16:06:34 +0200 Subject: [PATCH] chore: refactor --- docs/packages/rehype.md | 17 +++-- packages/rehype/src/core.ts | 124 ++++++---------------------------- packages/rehype/src/index.ts | 2 +- packages/rehype/src/inline.ts | 42 ++++++++++++ packages/rehype/src/types.ts | 83 +++++++++++++++++++++++ 5 files changed, 156 insertions(+), 112 deletions(-) create mode 100644 packages/rehype/src/inline.ts create mode 100644 packages/rehype/src/types.ts diff --git a/docs/packages/rehype.md b/docs/packages/rehype.md index bb7430f70..ef3a9b9aa 100644 --- a/docs/packages/rehype.md +++ b/docs/packages/rehype.md @@ -100,7 +100,12 @@ console.log('4') // highlighted ### Inline Code -You can also highlight inline codes with the `code{:language}` syntax. +You can also highlight inline codes with the `inline` option. + +| Option | Example | Description | +| ----------------------- | ---------------- | ----------------------------------------------------------- | +| `false` | - | Disable inline code highlighting (default) | +| `'tailing-curly-colon'` | `let a = 1{:js}` | Highlight with a `{:language}` marker inside the code block | Enable `inline` on the Rehype plugin: @@ -116,15 +121,15 @@ const file = await unified() .use(remarkParse) .use(remarkRehype) .use(rehypeShiki, { - inline: 'tailing-curly-colon', - // other options + inline: 'tailing-curly-colon', // or other options + // ... }) .use(rehypeStringify) .process(await fs.readFile('./input.md')) ``` +Then you can use inline code in markdown: + ```md -`console.log("Hello World"){:js}` +This code `console.log("Hello World"){:js}` will be highlighted. ``` - -Will give you a pretty looking JavaScript inline code. diff --git a/packages/rehype/src/core.ts b/packages/rehype/src/core.ts index 1efb90e31..4a3a5c2ff 100644 --- a/packages/rehype/src/core.ts +++ b/packages/rehype/src/core.ts @@ -1,90 +1,17 @@ import type { - CodeOptionsMeta, - CodeOptionsThemes, CodeToHastOptions, - CodeToHastOptionsCommon, HighlighterGeneric, - TransformerOptions, } from 'shiki/core' import type { Element, Root } from 'hast' -import type { BuiltinTheme } from 'shiki' import type { Transformer } from 'unified' import { toString } from 'hast-util-to-string' import { visit } from 'unist-util-visit' +import { InlineCodeProcessors } from './inline' +import type { RehypeShikiCoreOptions } from './types' -export interface MapLike { - get: (key: K) => V | undefined - set: (key: K, value: V) => this -} - -export interface RehypeShikiExtraOptions { - /** - * Add `language-*` class to code element - * - * @default false - */ - addLanguageClass?: boolean - - /** - * The default language to use when is not specified - */ - defaultLanguage?: string - - /** - * The fallback language to use when specified language is not loaded - */ - fallbackLanguage?: string - - /** - * `mdast-util-to-hast` adds a newline to the end of code blocks - * - * This option strips that newline from the code block - * - * @default true - * @see https://github.com/syntax-tree/mdast-util-to-hast/blob/f511a93817b131fb73419bf7d24d73a5b8b0f0c2/lib/handlers/code.js#L22 - */ - stripEndNewline?: boolean - - /** - * Custom meta string parser - * Return an object to merge with `meta` - */ - parseMetaString?: ( - metaString: string, - node: Element, - tree: Root, - ) => Record | undefined | null - - /** - * Highlight inline code blocks - * - * @default false - */ - inline?: false | 'tailing-curly-colon' - - /** - * Custom map to cache transformed codeToHast result - * - * @default undefined - */ - cache?: MapLike - - /** - * Chance to handle the error - * If not provided, the error will be thrown - */ - onError?: (error: unknown) => void -} - -export type RehypeShikiCoreOptions = - & CodeOptionsThemes - & TransformerOptions - & CodeOptionsMeta - & RehypeShikiExtraOptions - & Omit +export * from './types' const languagePrefix = 'language-' -const inlineCodeSuffix = /(.+)\{:([\w-]+)\}$/ function rehypeShikiFromHighlighter( highlighter: HighlighterGeneric, @@ -109,11 +36,15 @@ function rehypeShikiFromHighlighter( function getLanguage(lang = defaultLanguage): string | undefined { if (lang && fallbackLanguage && !langs.includes(lang)) return fallbackLanguage - return lang } - function processCode(lang: string, metaString: string, meta: Record, code: string): Root | undefined { + function highlight( + lang: string, + code: string, + metaString: string = '', + meta: Record = {}, + ): Root | undefined { const cacheKey = `${lang}:${metaString}:${code}` const cachedValue = cache?.get(cacheKey) @@ -156,7 +87,8 @@ function rehypeShikiFromHighlighter( catch (error) { if (onError) onError(error) - else throw error + else + throw error } } @@ -179,37 +111,20 @@ function rehypeShikiFromHighlighter( ) : undefined - const lang = getLanguage(typeof languageClass === 'string' ? languageClass.slice(languagePrefix.length) : undefined) + const lang = getLanguage( + typeof languageClass === 'string' + ? languageClass.slice(languagePrefix.length) + : undefined, + ) if (!lang) return const code = toString(head) - const metaString - = head.data?.meta ?? head.properties.metastring?.toString() ?? '' + const metaString = head.data?.meta ?? head.properties.metastring?.toString() ?? '' const meta = parseMetaString?.(metaString, node, tree) || {} - return processCode(lang, metaString, meta, code) - } - - function processInlineCode(node: Element): Root | undefined { - const raw = toString(node) - const result = inlineCodeSuffix.exec(raw) - const lang = getLanguage(result?.[2]) - if (!lang) - return - - const code = result?.[1] ?? raw - const fragment = processCode(lang, '', {}, code) - if (!fragment) - return - - const head = fragment.children[0] - if (head.type === 'element' && head.tagName === 'pre') { - head.tagName = 'span' - } - - return fragment + return highlight(lang, code, metaString, meta) } return function (tree) { @@ -230,8 +145,7 @@ function rehypeShikiFromHighlighter( } if (node.tagName === 'code' && inline) { - const result = processInlineCode(node) - + const result = InlineCodeProcessors[inline]?.({ node, getLanguage, highlight }) if (result) { parent.children.splice(index, 1, ...result.children) } diff --git a/packages/rehype/src/index.ts b/packages/rehype/src/index.ts index 2f5cc2f96..55a09c5ab 100644 --- a/packages/rehype/src/index.ts +++ b/packages/rehype/src/index.ts @@ -6,7 +6,7 @@ import { bundledLanguages, getSingletonHighlighter } from 'shiki' import type { Plugin } from 'unified' import type { Root } from 'hast' import rehypeShikiFromHighlighter from './core' -import type { RehypeShikiCoreOptions } from './core' +import type { RehypeShikiCoreOptions } from './types' export type RehypeShikiOptions = RehypeShikiCoreOptions & { diff --git a/packages/rehype/src/inline.ts b/packages/rehype/src/inline.ts new file mode 100644 index 000000000..4a454c856 --- /dev/null +++ b/packages/rehype/src/inline.ts @@ -0,0 +1,42 @@ +import type { Element, Root } from 'hast' +import { toString } from 'hast-util-to-string' +import type { RehypeShikiCoreOptions } from './types' + +interface InlineCodeProcessorContext { + node: Element + getLanguage: (lang?: string) => string | undefined + highlight: ( + lang: string, + code: string, + metaString?: string, + meta?: Record + ) => Root | undefined +} + +type InlineCodeProcessor = (context: InlineCodeProcessorContext) => Root | undefined + +type Truthy = T extends false | '' | 0 | null | undefined ? never : T + +export const InlineCodeProcessors: Record, InlineCodeProcessor> = { + 'tailing-curly-colon': ({ node, getLanguage, highlight }) => { + const raw = toString(node) + const match = raw.match(/(.+)\{:([\w-]+)\}$/) + if (!match) + return + const lang = getLanguage(match[2]) + if (!lang) + return + + const code = match[1] ?? raw + const fragment = highlight(lang, code) + if (!fragment) + return + + const head = fragment.children[0] + if (head.type === 'element' && head.tagName === 'pre') { + head.tagName = 'span' + } + + return fragment + }, +} diff --git a/packages/rehype/src/types.ts b/packages/rehype/src/types.ts new file mode 100644 index 000000000..bb127d20c --- /dev/null +++ b/packages/rehype/src/types.ts @@ -0,0 +1,83 @@ +import type { Element, Root } from 'hast' +import type { + BuiltinTheme, + CodeOptionsMeta, + CodeOptionsThemes, + CodeToHastOptionsCommon, + TransformerOptions, +} from 'shiki' + +export interface MapLike { + get: (key: K) => V | undefined + set: (key: K, value: V) => this +} + +export interface RehypeShikiExtraOptions { + /** + * Add `language-*` class to code element + * + * @default false + */ + addLanguageClass?: boolean + + /** + * The default language to use when is not specified + */ + defaultLanguage?: string + + /** + * The fallback language to use when specified language is not loaded + */ + fallbackLanguage?: string + + /** + * `mdast-util-to-hast` adds a newline to the end of code blocks + * + * This option strips that newline from the code block + * + * @default true + * @see https://github.com/syntax-tree/mdast-util-to-hast/blob/f511a93817b131fb73419bf7d24d73a5b8b0f0c2/lib/handlers/code.js#L22 + */ + stripEndNewline?: boolean + + /** + * Custom meta string parser + * Return an object to merge with `meta` + */ + parseMetaString?: ( + metaString: string, + node: Element, + tree: Root + ) => Record | undefined | null + + /** + * Highlight inline code blocks + * + * - `false`: disable inline code block highlighting + * - `tailing-curly-colon`: highlight with `\`code{:lang}\`` + * + * @see https://shiki.style/packages/rehype#inline-code + * @default false + */ + inline?: false | 'tailing-curly-colon' + + /** + * Custom map to cache transformed codeToHast result + * + * @default undefined + */ + cache?: MapLike + + /** + * Chance to handle the error + * If not provided, the error will be thrown + */ + onError?: (error: unknown) => void +} + +export type RehypeShikiCoreOptions = + & CodeOptionsThemes + & TransformerOptions + & CodeOptionsMeta + & RehypeShikiExtraOptions + & Omit