diff --git a/packages/rehype/src/core.ts b/packages/rehype/src/core.ts index dceecbde6..5abd0d701 100644 --- a/packages/rehype/src/core.ts +++ b/packages/rehype/src/core.ts @@ -25,6 +25,7 @@ function rehypeShikiFromHighlighter( defaultLanguage, fallbackLanguage, onError, + onFallback, stripEndNewline = true, inline = false, lazy = false, @@ -123,6 +124,7 @@ function rehypeShikiFromHighlighter( lang = parsed.lang } else if (fallbackLanguage) { + onFallback?.({ requestedLanguage: parsed.lang, fallbackLanguage }) lang = fallbackLanguage } @@ -154,6 +156,7 @@ function rehypeShikiFromHighlighter( .then(() => processNode(lang)) .catch((error) => { if (fallbackLanguage) { + onFallback?.({ requestedLanguage: lang, fallbackLanguage }) processNode(fallbackLanguage) } else if (onError) { @@ -166,11 +169,16 @@ function rehypeShikiFromHighlighter( ) } catch (error) { - if (fallbackLanguage) + if (fallbackLanguage) { + onFallback?.({ requestedLanguage: lang, fallbackLanguage }) return processNode(fallbackLanguage) - else if (onError) + } + else if (onError) { onError(error) - else throw error + } + else { + throw error + } } } else { diff --git a/packages/rehype/src/types.ts b/packages/rehype/src/types.ts index b0f354d59..0e9cf6970 100644 --- a/packages/rehype/src/types.ts +++ b/packages/rehype/src/types.ts @@ -81,6 +81,14 @@ export interface RehypeShikiExtraOptions { * If not provided, the error will be thrown */ onError?: (error: unknown) => void + + /** + * Called when falling back to `fallbackLanguage` + */ + onFallback?: (context: { + requestedLanguage: string + fallbackLanguage: string + }) => void } export type RehypeShikiCoreOptions diff --git a/packages/rehype/test/core.test.ts b/packages/rehype/test/core.test.ts index ed3c6fc29..100d58765 100644 --- a/packages/rehype/test/core.test.ts +++ b/packages/rehype/test/core.test.ts @@ -5,10 +5,10 @@ import rehypeStringify from 'rehype-stringify' import remarkParse from 'remark-parse' import remarkRehype from 'remark-rehype' import { createHighlighter } from 'shiki' -import { unified } from 'unified' +import { unified } from 'unified' import { visit } from 'unist-util-visit' -import { expect, it } from 'vitest' +import { expect, it, vi } from 'vitest' import { transformerMetaHighlight } from '../../transformers/src' import rehypeShikiFromHighlighter from '../src/core' @@ -201,6 +201,103 @@ it('lazy loading error handling with onError callback', async () => { expect(errors[0]).toBeInstanceOf(Error) }) +it('onFallback with lazy sync error', async () => { + using highlighter = await createHighlighter({ + themes: ['vitesse-light'], + langs: ['text'], + }) + + const mockHighlighter = { + ...highlighter, + loadLanguage: (...langs: Parameters) => { + const lang = langs[0] as string + if (lang === 'sync-fail-lang') + throw new Error(`Language 'sync-fail-lang' not found`) + return highlighter.loadLanguage(...langs) + }, + } + + const onFallback = vi.fn() + const file = await unified() + .use(remarkParse) + .use(remarkRehype) + .use(rehypeShikiFromHighlighter, mockHighlighter, { + lazy: true, + theme: 'vitesse-light', + fallbackLanguage: 'text', + onFallback, + }) + .use(rehypeStringify) + .process('```sync-fail-lang\nconst x = 1\n```') + + expect(onFallback).toHaveBeenCalledWith({ + requestedLanguage: 'sync-fail-lang', + fallbackLanguage: 'text', + }) + expect(file.toString()).toContain(' { + using highlighter = await createHighlighter({ + themes: ['vitesse-light'], + langs: ['text'], + }) + + const mockHighlighter = { + ...highlighter, + loadLanguage: async (...langs: Parameters) => { + const lang = langs[0] as string + if (lang === 'unknown-lang') + throw new Error(`Language 'unknown-lang' not found`) + return highlighter.loadLanguage(...langs) + }, + } + + const onFallback = vi.fn() + const file = await unified() + .use(remarkParse) + .use(remarkRehype) + .use(rehypeShikiFromHighlighter, mockHighlighter, { + lazy: true, + theme: 'vitesse-light', + fallbackLanguage: 'text', + onFallback, + }) + .use(rehypeStringify) + .process('```unknown-lang\nconst x = 1\n```') + + expect(onFallback).toHaveBeenCalledWith({ + requestedLanguage: 'unknown-lang', + fallbackLanguage: 'text', + }) + expect(file.toString()).toContain(' { + using highlighter = await createHighlighter({ + themes: ['vitesse-light'], + langs: ['javascript'], + }) + + const onFallback = vi.fn() + const file = await unified() + .use(remarkParse) + .use(remarkRehype) + .use(rehypeShikiFromHighlighter, highlighter, { + theme: 'vitesse-light', + fallbackLanguage: 'javascript', + onFallback, + }) + .use(rehypeStringify) + .process('```python\nprint("hello")\n```') + + expect(onFallback).toHaveBeenCalledWith({ + requestedLanguage: 'python', + fallbackLanguage: 'javascript', + }) + expect(file.toString()).toContain(' { using highlighter = await createHighlighter({ themes: ['vitesse-light'],