Skip to content

Commit 63e5cd4

Browse files
committed
fix:shorthand/full close 优先级
1 parent dcdfbf4 commit 63e5cd4

2 files changed

Lines changed: 58 additions & 11 deletions

File tree

src/structural.ts

Lines changed: 53 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,7 @@ const parseNodesWithFactory = <TNode extends StructuralNode | IndexedStructuralN
257257
contentEndI: number; // block/raw content 结束位置
258258

259259
// ── shorthand 前探缓存(仅父 inline endTag 模式使用) ──
260+
shorthandProbeTextEnd: number; // 缓存建立时的 frame.textEnd 版本
260261
shorthandProbeStartI: number; // 最近一次前探起点
261262
shorthandProbeBoundaryI: number; // 最近一次前探命中的首个边界位置(tagClose / full tag start / EOF)
262263
shorthandProbeReject: boolean; // 该边界是否表示“会误吃父级 endTag,需拒绝 shorthand”
@@ -292,6 +293,7 @@ const parseNodesWithFactory = <TNode extends StructuralNode | IndexedStructuralN
292293
pendingArgs: null,
293294
contentStartI: 0,
294295
contentEndI: 0,
296+
shorthandProbeTextEnd: -1,
295297
shorthandProbeStartI: -1,
296298
shorthandProbeBoundaryI: -1,
297299
shorthandProbeReject: false,
@@ -312,7 +314,8 @@ const parseNodesWithFactory = <TNode extends StructuralNode | IndexedStructuralN
312314
if (segLen === 2) {
313315
value = frame.text.slice(segments[0], segments[1]);
314316
} else if (segLen === 4) {
315-
value = frame.text.slice(segments[0], segments[1]) + frame.text.slice(segments[2], segments[3]);
317+
value =
318+
frame.text.slice(segments[0], segments[1]) + frame.text.slice(segments[2], segments[3]);
316319
} else {
317320
let result = "";
318321
for (let index = 0; index < segLen; index += 2) {
@@ -574,7 +577,9 @@ const parseNodesWithFactory = <TNode extends StructuralNode | IndexedStructuralN
574577
info?: ShorthandStartInfo;
575578
at?: number;
576579
}
577-
const resolveShorthandOwnership = (input: ShorthandOwnershipInput): ShorthandOwnershipDecision => {
580+
const resolveShorthandOwnership = (
581+
input: ShorthandOwnershipInput,
582+
): ShorthandOwnershipDecision => {
578583
if (input.phase === "push") {
579584
const info = input.info;
580585
if (!info) return "allow";
@@ -591,6 +596,7 @@ const parseNodesWithFactory = <TNode extends StructuralNode | IndexedStructuralN
591596
if (frame.inlineCloseToken !== endTag) return "allow";
592597

593598
const canReuseProbe =
599+
frame.shorthandProbeTextEnd === frame.textEnd &&
594600
frame.shorthandProbeStartI >= 0 &&
595601
frame.shorthandProbeBoundaryI >= 0 &&
596602
info.argStart >= frame.shorthandProbeStartI &&
@@ -619,6 +625,7 @@ const parseNodesWithFactory = <TNode extends StructuralNode | IndexedStructuralN
619625
probe++;
620626
}
621627

628+
frame.shorthandProbeTextEnd = frame.textEnd;
622629
frame.shorthandProbeStartI = info.argStart;
623630
frame.shorthandProbeBoundaryI = boundary;
624631
frame.shorthandProbeReject = reject;
@@ -680,13 +687,36 @@ const parseNodesWithFactory = <TNode extends StructuralNode | IndexedStructuralN
680687
// ── 主循环 ──
681688

682689
const stack: ParseFrame[] = [makeFrame(text, depth, insideArgs, baseOffset)];
683-
const tryConsumeInlineCloseAtCursor = (frame: ParseFrame, frameText: string, i: number): boolean => {
690+
const tryConsumeInlineCloseAtCursor = (
691+
frame: ParseFrame,
692+
frameText: string,
693+
i: number,
694+
): boolean => {
684695
if (frame.inlineCloseToken === null) return false;
685696
const { tagClose, rawOpen, blockOpen, blockClose } = syntax;
686697

687698
if (frame.inlineCloseToken === tagClose) {
688-
if (!frameText.startsWith(tagClose, i)) return false;
689699
const parent = frame.parentIndex >= 0 ? stack[frame.parentIndex] : null;
700+
// full-form close 与 shorthand close 竞争时,先让 full-form close 拥有 token。
701+
if (
702+
scanEndTagAt(frameText, i, frame.textEnd) === "full" &&
703+
resolveShorthandOwnership({
704+
phase: "close",
705+
frame,
706+
parent,
707+
at: i,
708+
}) === "defer-parent"
709+
) {
710+
stack.pop();
711+
if (!parent) {
712+
return true;
713+
}
714+
appendBuf(parent, frame.tagStartI, frame.argStartI);
715+
parent.i = frame.argStartI;
716+
return true;
717+
}
718+
719+
if (!frameText.startsWith(tagClose, i)) return false;
690720
if (
691721
resolveShorthandOwnership({
692722
phase: "close",
@@ -793,11 +823,7 @@ const parseNodesWithFactory = <TNode extends StructuralNode | IndexedStructuralN
793823
const tagStartI = frame.tagStartI;
794824

795825
if (closeStart === -1) {
796-
const malformed = findMalformedWholeLineTokenCandidate(
797-
frameText,
798-
contentStart,
799-
blockClose,
800-
);
826+
const malformed = findMalformedWholeLineTokenCandidate(frameText, contentStart, blockClose);
801827
emitError(
802828
tracker,
803829
onError,
@@ -863,7 +889,11 @@ const parseNodesWithFactory = <TNode extends StructuralNode | IndexedStructuralN
863889
frame.i += tagClose.length;
864890
return true;
865891
};
866-
const tryConsumeTagOrTextAtCursor = (frame: ParseFrame, frameText: string, i: number): boolean => {
892+
const tryConsumeTagOrTextAtCursor = (
893+
frame: ParseFrame,
894+
frameText: string,
895+
i: number,
896+
): boolean => {
867897
// ── 标签头识别 ──
868898
const info = readTagStartInfo(frameText, i, syntax, tagName);
869899
if (!info) {
@@ -1127,7 +1157,15 @@ const parseNodesWithFactory = <TNode extends StructuralNode | IndexedStructuralN
11271157

11281158
// ── 非 inline 帧的意外 endTag ──
11291159
if (scanEndTagAt(frameText, i, frame.textEnd) === "full") {
1130-
emitError(tracker, onError, "UNEXPECTED_CLOSE", frameText, i, endTag.length, emittedErrorKeys);
1160+
emitError(
1161+
tracker,
1162+
onError,
1163+
"UNEXPECTED_CLOSE",
1164+
frameText,
1165+
i,
1166+
endTag.length,
1167+
emittedErrorKeys,
1168+
);
11311169
appendBuf(frame, i, i + endTag.length);
11321170
frame.i += endTag.length;
11331171
continue;
@@ -1148,6 +1186,10 @@ const parseNodesWithFactory = <TNode extends StructuralNode | IndexedStructuralN
11481186
if (tryConsumeTagOrTextAtCursor(frame, frameText, i)) {
11491187
continue;
11501188
}
1189+
1190+
// 防御性兜底:避免未来重构导致该分支返回 false 时卡住游标。
1191+
appendBuf(frame, i, i + 1);
1192+
frame.i++;
11511193
}
11521194

11531195
return [];

tests/shorthandBehavior.test.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,11 @@ const matrixCases: MatrixCase[] = [
6262
expectedOff: "天気がbold<いlink<baidu.com|い>から>=散歩しましょう",
6363
expectedOn: "天気がbold<いlink<baidu.com|い>から>=散歩しましょう",
6464
},
65+
{
66+
input: "=bold<bold<=italic<1>=>>=Q",
67+
expectedOff: "bold<1>Q",
68+
expectedOn: "1Q",
69+
},
6570
];
6671

6772
const runModeText = (input: string, shorthand: boolean): string => {

0 commit comments

Comments
 (0)