Skip to content

Commit f77e353

Browse files
committed
fix:1.3.2
- **修复:shorthand 在紧邻父级闭合边界时不再抢占闭合(`=bold<bold<>=`)** - 在 inline 参数 shorthand 模式下,`name<` 曾可能在参数起点恰好命中父级 `endTag` 时仍被当作 shorthand 子标签。 - `tryPushInlineShorthandChild` 现已在该边界拒绝进入 shorthand 子帧,父级 inline 闭合归属父帧。 - **修复:shorthand 不再通过首个 `tagClose` 误吃父级闭合(`=bold<bold<<>=`)** - shorthand 候选可能吞掉本该作为父级 `endTag` 起点的 `tagClose`,导致外层 inline 退化为纯文本。 - 现在该模式会按歧义处理,`name<...` 保留为父级内容文本。 - **性能:同帧复用 shorthand 歧义前探结果** - 新增帧级前探缓存(`start` / `firstClose` / `firstCloseIsEndTag`),减少相邻 shorthand 候选的重复向前扫描。 - 语义不变,仅优化重复探测开销。
1 parent c7dd5ca commit f77e353

3 files changed

Lines changed: 76 additions & 0 deletions

File tree

CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,19 @@
22

33
# Changelog
44

5+
### 1.3.2
6+
7+
- **Fix: shorthand no longer steals parent close on immediate boundary (`=bold<bold<>=`)**
8+
- In inline-arg shorthand mode, `name<` could be parsed as a shorthand child even when its arg start was exactly at the parent `endTag` boundary.
9+
- `tryPushInlineShorthandChild` now rejects shorthand push on that boundary, so the parent inline close remains owned by the parent frame.
10+
- **Fix: shorthand no longer steals parent close via first `tagClose` (`=bold<bold<<>=`)**
11+
- A shorthand candidate could consume the `tagClose` that should start the parent `endTag`, causing outer inline degradation to plain text.
12+
- The parser now treats this pattern as ambiguous and keeps `name<...` as text under the parent inline frame.
13+
- **Perf: reuse shorthand ambiguity probe within a frame**
14+
- Added per-frame probe cache (`start`, `firstClose`, `firstCloseIsEndTag`) to avoid repeated forward scans for nearby shorthand candidates.
15+
- This preserves behavior while reducing repeated work in dense shorthand inputs.
16+
- No public API changes
17+
518
### 1.3.1
619

720
- **Fix: shorthand now respects `depthLimit`**

CHANGELOG.zh-CN.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,19 @@
22

33
# 更新日志
44

5+
### 1.3.2
6+
7+
- **修复:shorthand 在紧邻父级闭合边界时不再抢占闭合(`=bold<bold<>=`**
8+
- 在 inline 参数 shorthand 模式下,`name<` 曾可能在参数起点恰好命中父级 `endTag` 时仍被当作 shorthand 子标签。
9+
- `tryPushInlineShorthandChild` 现已在该边界拒绝进入 shorthand 子帧,父级 inline 闭合归属父帧。
10+
- **修复:shorthand 不再通过首个 `tagClose` 误吃父级闭合(`=bold<bold<<>=`**
11+
- shorthand 候选可能吞掉本该作为父级 `endTag` 起点的 `tagClose`,导致外层 inline 退化为纯文本。
12+
- 现在该模式会按歧义处理,`name<...` 保留为父级内容文本。
13+
- **性能:同帧复用 shorthand 歧义前探结果**
14+
- 新增帧级前探缓存(`start` / `firstClose` / `firstCloseIsEndTag`),减少相邻 shorthand 候选的重复向前扫描。
15+
- 语义不变,仅优化重复探测开销。
16+
- 无公共 API 变化
17+
518
### 1.3.1
619

720
- **修复:shorthand 现在正确受 `depthLimit` 约束**

src/structural.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,11 @@ const parseNodesWithFactory = <TNode extends StructuralNode | IndexedStructuralN
255255
pendingArgs: TNode[] | null; // blockArgs 完成后暂存
256256
contentStartI: number; // block content 起始位置
257257
contentEndI: number; // block/raw content 结束位置
258+
259+
// ── shorthand 前探缓存(仅父 inline endTag 模式使用) ──
260+
shorthandProbeStartI: number; // 最近一次前探起点
261+
shorthandProbeFirstCloseI: number; // 最近一次前探命中的首个 tagClose;-1 表示未找到
262+
shorthandProbeFirstCloseIsEndTag: boolean; // 首个 tagClose 是否就是 endTag 起点
258263
}
259264

260265
const makeFrame = (
@@ -287,6 +292,9 @@ const parseNodesWithFactory = <TNode extends StructuralNode | IndexedStructuralN
287292
pendingArgs: null,
288293
contentStartI: 0,
289294
contentEndI: 0,
295+
shorthandProbeStartI: -1,
296+
shorthandProbeFirstCloseI: -1,
297+
shorthandProbeFirstCloseIsEndTag: false,
290298
});
291299

292300
// ── 缓冲区 ──
@@ -548,6 +556,48 @@ const parseNodesWithFactory = <TNode extends StructuralNode | IndexedStructuralN
548556
tagStartI: number,
549557
info: ShorthandStartInfo,
550558
): boolean => {
559+
// Guard ambiguity like `=bold<bold<>=`:
560+
// if shorthand arg starts exactly at parent's inline close token (`endTag`),
561+
// this `name<` is text and the following close belongs to parent.
562+
if (frame.inlineCloseToken === endTag && frame.text.startsWith(endTag, info.argStart)) {
563+
return false;
564+
}
565+
566+
// Guard ambiguity where shorthand would consume the `tagClose` that is
567+
// actually the start of parent's `endTag` (e.g. `=bold<bold<<>=`).
568+
if (frame.inlineCloseToken === endTag) {
569+
const canReuseProbe =
570+
frame.shorthandProbeStartI >= 0 &&
571+
info.argStart >= frame.shorthandProbeStartI &&
572+
(frame.shorthandProbeFirstCloseI === -1 || info.argStart <= frame.shorthandProbeFirstCloseI);
573+
574+
if (!canReuseProbe) {
575+
let firstClose = -1;
576+
let probe = info.argStart;
577+
while (probe < frame.text.length) {
578+
const [escaped, nextEsc] = readEscapedSequence(frame.text, probe, syntax);
579+
if (escaped !== null) {
580+
probe = nextEsc;
581+
continue;
582+
}
583+
if (frame.text.startsWith(tagClose, probe)) {
584+
firstClose = probe;
585+
break;
586+
}
587+
probe++;
588+
}
589+
590+
frame.shorthandProbeStartI = info.argStart;
591+
frame.shorthandProbeFirstCloseI = firstClose;
592+
frame.shorthandProbeFirstCloseIsEndTag =
593+
firstClose !== -1 && frame.text.startsWith(endTag, firstClose);
594+
}
595+
596+
if (frame.shorthandProbeFirstCloseIsEndTag) {
597+
return false;
598+
}
599+
}
600+
551601
if (frame.depth >= depthLimit) {
552602
const span = info.argStart - info.tagOpenPos;
553603
emitError(tracker, onError, "DEPTH_LIMIT", frame.text, tagStartI, span, emittedErrorKeys);

0 commit comments

Comments
 (0)