fix: guard callout indicator parsing in pre-build plugin

Avoid accessing .match on undefined when blockquote indicator starts with a non-text node, and add a regression test to keep the pre-build pipeline stable.
This commit is contained in:
barkstone2 2026-02-08 17:21:17 +09:00 committed by barkstone2
parent ca68164d62
commit 3ed97d693b
2 changed files with 32 additions and 3 deletions

View file

@ -47,7 +47,8 @@ const rehypeCallout = () => {
}
function isCallout(indicatorNode) {
return indicatorNode.tagName === 'p' && indicatorNode.children[0].value.match(/\[!.*]/)
const indicatorValue = indicatorNode?.children?.[0]?.value;
return indicatorNode?.tagName === 'p' && typeof indicatorValue === 'string' && /\[!.*]/.test(indicatorValue)
}
function parseIndicatorNode(indicatorNode) {
@ -97,4 +98,4 @@ function determineClass(isFoldable, foldableState) {
return calloutClass;
}
export default rehypeCallout
export default rehypeCallout

View file

@ -85,6 +85,34 @@ describe('rehypeCallout 동작 시', () => {
expect(inputAst).toEqual(expectedAst)
});
it('indicator의 첫 자식이 text 노드가 아니어도 에러 없이 통과한다.', () => {
inputAst = u('root', [
{
type: 'element',
tagName: 'blockquote',
children: [
{ type: 'text', value: '\n' },
{
type: 'element',
tagName: 'p',
children: [
{
type: 'element',
tagName: 'strong',
children: [{ type: 'text', value: '[!info]' }],
},
{ type: 'text', value: '내용' },
],
},
],
}
]);
expectedAst = structuredClone(inputAst);
expect(() => rehypeCallout()(inputAst)).not.toThrow();
expect(inputAst).toEqual(expectedAst);
});
it('콜아웃 지시자가 있는 blockquote 태그의 경우 rehype 된다.', () => {
inputAst = u('root', [
{
@ -353,4 +381,4 @@ describe('rehypeCallout 동작 시', () => {
expect(found).true
});
});
});