@@ -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 [ ] ;
0 commit comments