fix(parser): 修复 RE_FENCE_START 匹配但被 fencedCode 拒绝时主循环死循环导致 CI Run tests 卡死
根因:输入如 \\\ \code\ \\\ 匹配 RE_FENCE_START 使 isBlockStart 返回 true,但 fencedCode handler 因 info string 含反引号拒绝处理,导致主循环既无法处理也无法跳过该行,进入无限循环。修复:段落 fallback 中当 para 为空且行匹配 RE_FENCE_START 含反引号时强制收集,并增加兜底 i++ 跳过无法处理的非空行。src/parser.ts:353-373 同时更新 README.md 和 site 站点测试数为 725(真实全量测试数)
This commit is contained in:
@@ -4,7 +4,7 @@
|
||||
|
||||
[](https://git.metona.cn/MetonaTeam/-/packages/npm/@metona-team%2Fmetona-editor)
|
||||
[](./LICENSE)
|
||||
[](./tests)
|
||||
[](./tests)
|
||||
[](./tests)
|
||||
[](./tsconfig.json)
|
||||
|
||||
|
||||
Vendored
+11
-1
@@ -1140,8 +1140,16 @@ const parseTokens = (md) => {
|
||||
const l = lines[i];
|
||||
if (RE_EMPTY.test(l))
|
||||
break;
|
||||
if (isBlockStart(l))
|
||||
if (isBlockStart(l)) {
|
||||
// 防止死循环:若该行匹配 RE_FENCE_START 但被 fencedCode handler 拒绝(info string 含反引号),
|
||||
// 且 para 为空,则强制当作段落首行收集,否则主循环将无限重复处理此行
|
||||
if (para.length === 0 && RE_FENCE_START.test(l) && l.includes('`')) {
|
||||
para.push(l);
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (i + 1 < lines.length && (RE_SETEXT_H1.test(lines[i + 1]) || RE_SETEXT_H2.test(lines[i + 1])))
|
||||
break;
|
||||
if (/\|/.test(l) && i + 1 < lines.length && isTableSeparator(lines[i + 1]))
|
||||
@@ -1151,6 +1159,8 @@ const parseTokens = (md) => {
|
||||
}
|
||||
if (para.length)
|
||||
tokens.push({ type: 'paragraph', text: para.join('\n') });
|
||||
else if (i < lines.length && !RE_EMPTY.test(lines[i]))
|
||||
i++; // 兜底:跳过无法处理的非空行
|
||||
}
|
||||
return { tokens, footnotes, refs };
|
||||
};
|
||||
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+11
-1
@@ -1136,8 +1136,16 @@ const parseTokens = (md) => {
|
||||
const l = lines[i];
|
||||
if (RE_EMPTY.test(l))
|
||||
break;
|
||||
if (isBlockStart(l))
|
||||
if (isBlockStart(l)) {
|
||||
// 防止死循环:若该行匹配 RE_FENCE_START 但被 fencedCode handler 拒绝(info string 含反引号),
|
||||
// 且 para 为空,则强制当作段落首行收集,否则主循环将无限重复处理此行
|
||||
if (para.length === 0 && RE_FENCE_START.test(l) && l.includes('`')) {
|
||||
para.push(l);
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (i + 1 < lines.length && (RE_SETEXT_H1.test(lines[i + 1]) || RE_SETEXT_H2.test(lines[i + 1])))
|
||||
break;
|
||||
if (/\|/.test(l) && i + 1 < lines.length && isTableSeparator(lines[i + 1]))
|
||||
@@ -1147,6 +1155,8 @@ const parseTokens = (md) => {
|
||||
}
|
||||
if (para.length)
|
||||
tokens.push({ type: 'paragraph', text: para.join('\n') });
|
||||
else if (i < lines.length && !RE_EMPTY.test(lines[i]))
|
||||
i++; // 兜底:跳过无法处理的非空行
|
||||
}
|
||||
return { tokens, footnotes, refs };
|
||||
};
|
||||
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+11
-1
@@ -1142,8 +1142,16 @@
|
||||
const l = lines[i];
|
||||
if (RE_EMPTY.test(l))
|
||||
break;
|
||||
if (isBlockStart(l))
|
||||
if (isBlockStart(l)) {
|
||||
// 防止死循环:若该行匹配 RE_FENCE_START 但被 fencedCode handler 拒绝(info string 含反引号),
|
||||
// 且 para 为空,则强制当作段落首行收集,否则主循环将无限重复处理此行
|
||||
if (para.length === 0 && RE_FENCE_START.test(l) && l.includes('`')) {
|
||||
para.push(l);
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (i + 1 < lines.length && (RE_SETEXT_H1.test(lines[i + 1]) || RE_SETEXT_H2.test(lines[i + 1])))
|
||||
break;
|
||||
if (/\|/.test(l) && i + 1 < lines.length && isTableSeparator(lines[i + 1]))
|
||||
@@ -1153,6 +1161,8 @@
|
||||
}
|
||||
if (para.length)
|
||||
tokens.push({ type: 'paragraph', text: para.join('\n') });
|
||||
else if (i < lines.length && !RE_EMPTY.test(lines[i]))
|
||||
i++; // 兜底:跳过无法处理的非空行
|
||||
}
|
||||
return { tokens, footnotes, refs };
|
||||
};
|
||||
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
+2
-2
@@ -49,7 +49,7 @@ body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC","Micr
|
||||
<div class="badges">
|
||||
<span class="badge">TypeScript</span>
|
||||
<span class="badge">零运行时依赖</span>
|
||||
<span class="badge">353 tests</span>
|
||||
<span class="badge">725 tests</span>
|
||||
<span class="badge">6 个插件</span>
|
||||
<span class="badge">桌面端优先</span>
|
||||
</div>
|
||||
@@ -102,7 +102,7 @@ var demoMd=[
|
||||
'# 🚀 MetonaEditor v0.2.4 全功能演示',
|
||||
'',
|
||||
'> TypeScript 重构 · 零运行时依赖 · **桌面端** Markdown Editor 库。',
|
||||
'> 全模块 TypeScript 严格模式,12 个源文件,353 个测试全部通过。',
|
||||
'> 全模块 TypeScript 严格模式,12 个源文件,725 个测试全部通过。',
|
||||
'',
|
||||
'## ✨ v0.2.4 新特性',
|
||||
'',
|
||||
|
||||
+2
-2
@@ -109,7 +109,7 @@ footer a:hover{text-decoration:underline}
|
||||
<span class="badge accent">v0.2.4</span>
|
||||
<span class="badge">TypeScript</span>
|
||||
<span class="badge">零运行时依赖</span>
|
||||
<span class="badge">353 tests</span>
|
||||
<span class="badge">725 tests</span>
|
||||
<span class="badge">桌面端优先</span>
|
||||
<span class="badge">MIT</span>
|
||||
</div>
|
||||
@@ -120,7 +120,7 @@ footer a:hover{text-decoration:underline}
|
||||
<div class="stats">
|
||||
<div class="stat"><div class="stat-num">0</div><div class="stat-label">运行时依赖</div></div>
|
||||
<div class="stat"><div class="stat-num">99%</div><div class="stat-label">解析器行覆盖</div></div>
|
||||
<div class="stat"><div class="stat-num">353</div><div class="stat-label">单元测试</div></div>
|
||||
<div class="stat"><div class="stat-num">725</div><div class="stat-label">单元测试</div></div>
|
||||
<div class="stat"><div class="stat-num">~30KB</div><div class="stat-label">gzip 体积</div></div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
+10
-1
@@ -355,12 +355,21 @@ const parseTokens = (md: string | null | undefined): ParseResult => {
|
||||
while (i < lines.length) {
|
||||
const l = lines[i];
|
||||
if (RE_EMPTY.test(l)) break;
|
||||
if (isBlockStart(l)) break;
|
||||
if (isBlockStart(l)) {
|
||||
// 防止死循环:若该行匹配 RE_FENCE_START 但被 fencedCode handler 拒绝(info string 含反引号),
|
||||
// 且 para 为空,则强制当作段落首行收集,否则主循环将无限重复处理此行
|
||||
if (para.length === 0 && RE_FENCE_START.test(l) && l.includes('`')) {
|
||||
para.push(l); i++;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (i + 1 < lines.length && (RE_SETEXT_H1.test(lines[i + 1]) || RE_SETEXT_H2.test(lines[i + 1]))) break;
|
||||
if (/\|/.test(l) && i + 1 < lines.length && isTableSeparator(lines[i + 1])) break;
|
||||
para.push(l); i++;
|
||||
}
|
||||
if (para.length) tokens.push({ type: 'paragraph', text: para.join('\n') });
|
||||
else if (i < lines.length && !RE_EMPTY.test(lines[i])) i++; // 兜底:跳过无法处理的非空行
|
||||
}
|
||||
return { tokens, footnotes, refs };
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user