Skip to content

Commit 99654fe

Browse files
fix(rehype): handle promise rejections in lazy language loading (#1221)
1 parent 28dd090 commit 99654fe

File tree

2 files changed

+125
-1
lines changed

2 files changed

+125
-1
lines changed

packages/rehype/src/core.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,21 @@ function rehypeShikiFromHighlighter(
149149
if (lazyLoad) {
150150
try {
151151
// passed language is checked in sync, promise `.catch()` wouldn't work
152-
queue.push(highlighter.loadLanguage(lang).then(() => processNode(lang)))
152+
queue.push(
153+
highlighter.loadLanguage(lang)
154+
.then(() => processNode(lang))
155+
.catch((error) => {
156+
if (fallbackLanguage) {
157+
processNode(fallbackLanguage)
158+
}
159+
else if (onError) {
160+
onError(error)
161+
}
162+
else {
163+
throw error
164+
}
165+
}),
166+
)
153167
}
154168
catch (error) {
155169
if (fallbackLanguage)

packages/rehype/test/core.test.ts

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,3 +123,113 @@ it('run with lazy + fallback language', async () => {
123123

124124
await expect(file.toString()).toMatchFileSnapshot('./fixtures/d.out.html')
125125
})
126+
127+
it('lazy loading error handling with fallbackLanguage', async () => {
128+
using highlighter = await createHighlighter({
129+
themes: ['vitesse-light'],
130+
langs: ['text'],
131+
})
132+
133+
// Create a mock highlighter that fails to load a specific language
134+
const mockHighlighter = {
135+
...highlighter,
136+
loadLanguage: async (...langs: Parameters<typeof highlighter.loadLanguage>) => {
137+
const lang = langs[0] as string
138+
if (lang === 'nonexistent-lang') {
139+
throw new Error(`Language 'nonexistent-lang' not found`)
140+
}
141+
return highlighter.loadLanguage(...langs)
142+
},
143+
}
144+
145+
const markdown = '```nonexistent-lang\nconst x = 1\n```'
146+
147+
const file = await unified()
148+
.use(remarkParse)
149+
.use(remarkRehype)
150+
.use(rehypeShikiFromHighlighter, mockHighlighter, {
151+
lazy: true,
152+
theme: 'vitesse-light',
153+
fallbackLanguage: 'text',
154+
})
155+
.use(rehypeStringify)
156+
.process(markdown)
157+
158+
// Should use fallback language (text) instead of throwing
159+
expect(file.toString()).toContain('<pre')
160+
expect(file.toString()).toContain('<code')
161+
})
162+
163+
it('lazy loading error handling with onError callback', async () => {
164+
using highlighter = await createHighlighter({
165+
themes: ['vitesse-light'],
166+
langs: ['text'],
167+
})
168+
169+
const errors: unknown[] = []
170+
const onError = (error: unknown) => {
171+
errors.push(error)
172+
}
173+
174+
// Create a mock highlighter that fails to load a specific language
175+
const mockHighlighter = {
176+
...highlighter,
177+
loadLanguage: async (...langs: Parameters<typeof highlighter.loadLanguage>) => {
178+
const lang = langs[0] as string
179+
if (lang === 'failing-lang') {
180+
throw new Error(`Language 'failing-lang' not found`)
181+
}
182+
return highlighter.loadLanguage(...langs)
183+
},
184+
}
185+
186+
const markdown = '```failing-lang\nconst x = 1\n```'
187+
188+
await unified()
189+
.use(remarkParse)
190+
.use(remarkRehype)
191+
.use(rehypeShikiFromHighlighter, mockHighlighter, {
192+
lazy: true,
193+
theme: 'vitesse-light',
194+
onError,
195+
})
196+
.use(rehypeStringify)
197+
.process(markdown)
198+
199+
// onError should be called
200+
expect(errors.length).toBeGreaterThan(0)
201+
expect(errors[0]).toBeInstanceOf(Error)
202+
})
203+
204+
it('lazy loading error handling throws when no fallback or onError', async () => {
205+
using highlighter = await createHighlighter({
206+
themes: ['vitesse-light'],
207+
langs: ['text'],
208+
})
209+
210+
// Create a mock highlighter that fails to load a specific language
211+
const mockHighlighter = {
212+
...highlighter,
213+
loadLanguage: async (...langs: Parameters<typeof highlighter.loadLanguage>) => {
214+
const lang = langs[0] as string
215+
if (lang === 'error-lang') {
216+
throw new Error(`Language 'error-lang' not found`)
217+
}
218+
return highlighter.loadLanguage(...langs)
219+
},
220+
}
221+
222+
const markdown = '```error-lang\nconst x = 1\n```'
223+
224+
await expect(
225+
unified()
226+
.use(remarkParse)
227+
.use(remarkRehype)
228+
.use(rehypeShikiFromHighlighter, mockHighlighter, {
229+
lazy: true,
230+
theme: 'vitesse-light',
231+
})
232+
.use(rehypeStringify)
233+
.process(markdown),
234+
).rejects.toThrow()
235+
})

0 commit comments

Comments
 (0)