refactor: 專案整理階段一——確認缺陷修復(jinze 冪等/pill 自身 avoid)+死代碼清理
下游審計(4 路並行測繪 + 逐條源碼複驗)之階段一:落地已複驗確認之缺陷與低風險 清理;架構統一(透明判定單一真相源)與文檔重整留待階段二/三。 修復(複驗確認): ・jinze 二次 render(未 revert)產生巢狀 jz-jinze、違反 I6 冪等——守衛改判 parent 自身即本 pass 產物(isJz(parent, PASS)),已綁定內容不再重包(實測 jz-jinze 3→6 已修)。 ・processPills 漏檢 avoidsSelf——自身命中 avoid(如 <code data-jz-skip>)之 pill 仍被補 間隙(isAvoided 只查祖先、不含自身,同「下游問題六」型)。processPills + root 自處理 分支補 avoidsSelf 檢查。 ・三分類互斥:CODE/KBD 自 DEFAULT_SKIP_TAGS 移除——二者已由預設 pillSelector "code, kbd" 以 pill 機制涵蓋(內部不可見+兩端補隙+斷 run/相鄰),再列 skipTags 致 同元素既 pill 又 avoid、pill 兩端間隙被 avoid 抹掉(違反互斥,且令上一項修復誤傷普通 code)。移除後普通 code 為純 pill、正常補隙;唯自身 data-jz-skip 者不補(即上項修復)。 清理: ・刪零引用死代碼 createMarker/ANY_CJK/BD_ALL/BD_CLASS_NAMES/CreateOptions.attrs; isJz 由 jinze 冪等守衛啟用、脫離死代碼。 ・死旗標 biaodian/emphasis/ruby 補 @reserved 註記(比照 hanging;點明與 charClass.biaodian 撞名異義,避免混淆)。 ・JzKind 補註 data-jz-kind 第三值 "splitoff"(split.ts 直接設、不經 createJz); longWord/orphan 冗餘 ===false 特判簡化(feature() 已對 false 回 false)。 複驗後重新定性(不照單全收): ・jinze 跨透明元素邊界避頭尾漏綁(你好<strong>。世界</strong> 之句末點號漏綁)——**真 缺陷**,但正解須改 jinze 之父級處理模型(穿越透明包裹上攀評估跨層綁定),與階段二 「透明判定單一真相源」一併處理更穩;forbiddenBreak 下探草稿已還原、缺口以註解標記。 ・lineedge edgeRect 攀穿 isolate——**非缺陷**:edgeRect 為物理版面測量(getClientRects), isolate 斷的是邏輯 run/相鄰,其內部字元之物理行首/行尾判定須跨 <mark> 找真實相鄰 內容(否則「文字<mark>「引」</mark>」之 「 誤判行首而錯收左半)。初評誤判、已還原並補註。 新增 3 回歸測試(jinze 冪等、預設 code 為 pill 非 skipTags、自身 avoid 之 pill 不補隙); 119 測試全綠、tsc + esbuild 構建乾淨。
This commit is contained in:
+3
-18
@@ -17,6 +17,9 @@ export type JzTag =
|
||||
| "jz-rb"
|
||||
| "jz-rt";
|
||||
|
||||
// createJz 之 kind:marker(純注入,還原時移除)/wrap(包裝原內容,還原時解包)。
|
||||
// data-jz-kind 另有第三種值 "splitoff"(克隆切分之邊界詞元),由 core/split.ts 直接以
|
||||
// setAttribute 設定、不經 createJz;revertPass 於 wrap 全解包後之第二階段併回(見下)。
|
||||
type JzKind = "marker" | "wrap";
|
||||
|
||||
export interface CreateOptions
|
||||
@@ -25,8 +28,6 @@ export interface CreateOptions
|
||||
classes?: string | string[];
|
||||
/** 文本內容。 */
|
||||
text?: string;
|
||||
/** 其餘屬性。 */
|
||||
attrs?: Record<string, string>;
|
||||
}
|
||||
|
||||
const KIND_ATTR = "data-jz-kind";
|
||||
@@ -57,26 +58,10 @@ export function createJz(
|
||||
const list = Array.isArray(opts.classes) ? opts.classes : [ opts.classes ];
|
||||
for (const c of list) { if (c) { el.classList.add(c); } }
|
||||
}
|
||||
if (opts.attrs)
|
||||
{
|
||||
for (const k in opts.attrs)
|
||||
{
|
||||
const v = opts.attrs[k];
|
||||
if (v !== undefined) { el.setAttribute(k, v); }
|
||||
}
|
||||
}
|
||||
if (opts.text !== undefined) { el.textContent = opts.text; }
|
||||
return el;
|
||||
}
|
||||
|
||||
/** 造一個 copy-clean 間隙標記(內含真實空白,user-select 由 CSS 關閉)。 */
|
||||
export function createMarker(tag: JzTag, pass: string): HTMLElement
|
||||
{
|
||||
const el = createJz(tag, pass, "marker", { text: " " });
|
||||
el.setAttribute("aria-hidden", "true");
|
||||
return el;
|
||||
}
|
||||
|
||||
/**
|
||||
* 還原某 pass 之全部產物(I6)。以 data-jz 定址,逆文件序處理(先內層後
|
||||
* 外層),marker 移除、wrap 解包,最後 normalize 受影響父節點合併文本。
|
||||
|
||||
@@ -23,9 +23,6 @@ export const ALNUM = "A-Za-z0-9";
|
||||
/** 西文字母。 */
|
||||
export const ALPHA = "A-Za-z";
|
||||
|
||||
/** 任一 CJK 字元測試。 */
|
||||
export const ANY_CJK = new RegExp("[" + CJK + "]");
|
||||
|
||||
// ----- Pangu 中西邊界用之擴充西文字集(沿用 v1)-----
|
||||
|
||||
// 西文(外文)字集——CJK 與其相鄰時補中西間隙。以 regex 字元類片段表示,供
|
||||
@@ -80,11 +77,6 @@ export const BD_MIDDLE = "·・‧";
|
||||
/** 連續省略/破折:…—。 */
|
||||
export const BD_LIGA = "…—";
|
||||
|
||||
/** 全體標點字元(供 charify 走訪偵測)。 */
|
||||
export const BD_ALL = (
|
||||
BD_STOP + BD_PAUSE + BD_OPEN + BD_CLOSE + BD_MIDDLE + BD_LIGA
|
||||
);
|
||||
|
||||
/** 全寬標點之六子類鍵。 */
|
||||
export type BdSubclass = "stop" | "pause" | "open" | "close" | "middle" | "liga";
|
||||
|
||||
@@ -173,11 +165,6 @@ export function classifyBiaodian(
|
||||
return null;
|
||||
}
|
||||
|
||||
/** 全體 bd-* 子類名(DOM 端讀取 jz-char 之 bd class 用)。 */
|
||||
export const BD_CLASS_NAMES = [
|
||||
"bd-stop", "bd-pause", "bd-open", "bd-close", "bd-middle", "bd-liga",
|
||||
] as const;
|
||||
|
||||
/**
|
||||
* 標點之「可壓縮空白」側(橫排)。字面偏左者空白在右(句內/句末/閉類),
|
||||
* 開類字面偏右、空白在左。間隔號/連接號不壓縮。擠壓即收掉對應側之半形空白。
|
||||
|
||||
+8
-3
@@ -34,8 +34,12 @@ const DEFAULT_BLOCK_TAGS = [
|
||||
"DL", "TABLE", "TR", "THEAD", "TBODY", "TFOOT", "FORM",
|
||||
"FIELDSET", "LEGEND", "LABEL", "BR", "HR",
|
||||
];
|
||||
// 跳過標籤(avoidsSelf):內部完全不處理、兩端不補間隙。**不含 CODE/KBD**——二者由
|
||||
// 預設 pillSelector "code, kbd" 以 pill 機制涵蓋(內部不可見+兩端補間隙+斷 run/相鄰),
|
||||
// 若再列入 skipTags 會與 pill 語義衝突(同元素既 pill 又 avoid、pill 兩端間隙被 avoid 抹掉,
|
||||
// 違反三分類互斥)。消費端若把 code 移出 pillSelector,須自行加入 skipTags/scope.avoid。
|
||||
const DEFAULT_SKIP_TAGS = [
|
||||
"CODE", "KBD", "PRE", "SAMP", "TT", "VAR",
|
||||
"PRE", "SAMP", "TT", "VAR",
|
||||
"SCRIPT", "STYLE", "TEXTAREA", "INPUT",
|
||||
];
|
||||
|
||||
@@ -103,10 +107,11 @@ export function normalizeOptions(opts: JuzhenOptions = {}): ResolvedOptions
|
||||
},
|
||||
features: {
|
||||
spacing: feature(opts.spacing, true),
|
||||
longWord: opts.longWord === false ? false : feature(opts.longWord, true),
|
||||
// feature(v,dflt) 對 v===false 即回 false,故無需 ===false 特判(與 jiya/jinze 同構)。
|
||||
longWord: feature(opts.longWord, true),
|
||||
jiya: feature(opts.jiya, true),
|
||||
jinze: feature(opts.jinze, true),
|
||||
orphan: opts.orphan === false ? false : feature(opts.orphan, true),
|
||||
orphan: feature(opts.orphan, true),
|
||||
biaodian: feature(opts.biaodian, false),
|
||||
emphasis: feature(opts.emphasis, false),
|
||||
ruby: feature(opts.ruby, false),
|
||||
|
||||
@@ -89,8 +89,12 @@ export interface JuzhenOptions
|
||||
* 守恆矛盾,CSS 接觸不到斷行器),原生 hanging-punctuation 為唯一正解但僅 Safari 支援、
|
||||
* 且與本庫 inline-block 原子互斥。此選項仍被接受以保 API 相容,但**無任何效果**。 */
|
||||
hanging?: boolean;
|
||||
/** @reserved 標點修正(簡/繁正規化)——架構已預留(切片 #7),**當前無任何效果**。
|
||||
* 注意:此為功能開關,與 `charClass.biaodian`(標點子類擴充表,語義完全不同)同名,勿混。 */
|
||||
biaodian?: boolean;
|
||||
/** @reserved 著重號/裝飾——架構已預留(切片 #8),**當前無任何效果**。 */
|
||||
emphasis?: boolean;
|
||||
/** @reserved Ruby/注音——架構已預留(切片 #8),**當前無任何效果**。 */
|
||||
ruby?: boolean;
|
||||
|
||||
/** 分塊功能選擇(§3.6)。 */
|
||||
|
||||
+11
-3
@@ -10,7 +10,7 @@
|
||||
// 兩階段:①依原始相鄰關係算「禁斷邊界」,對涉及之文本單元切出邊界字;
|
||||
// ②重掃當前子節點,把以禁斷邊界相連之相鄰原子併入同一 jz-jinze。
|
||||
|
||||
import { bdClassOf, createJz, revertPass } from "../core/dom.js";
|
||||
import { bdClassOf, createJz, isJz, revertPass } from "../core/dom.js";
|
||||
import type { Finder } from "../core/finder.js";
|
||||
import { isolateBoundaryToken } from "../core/split.js";
|
||||
import type { RenderContext, StandalonePass } from "../types.js";
|
||||
@@ -27,7 +27,12 @@ function isCharEl(n: Node | null): n is Element
|
||||
return !!n && n.nodeType === 1 && (n as Element).nodeName === "JZ-CHAR";
|
||||
}
|
||||
|
||||
/** 相鄰兩節點之間是否禁止斷行(僅由 jz-char 之類別決定)。 */
|
||||
/** 相鄰兩節點之間是否禁止斷行(僅由 jz-char 之類別決定)。
|
||||
* 已知缺口(下游審計,待階段二):邊界標點若落於透明樣式行內元素邊緣(如
|
||||
* 「你好<strong>。世界</strong>」之句末點號位於 <strong> 首位),因 jinze 僅處理
|
||||
* jz-char 之直接父(此處為 <strong>),外層 <p> 之「好↔strong」綁定不被評估、
|
||||
* 致跨邊界避頭尾漏綁。完整修復須改 jinze 之父級處理模型(穿越透明包裹上攀),
|
||||
* 與階段二「透明判定單一真相源」一併處理。 */
|
||||
function forbiddenBreak(a: Node, b: Node, finder: Finder): boolean
|
||||
{
|
||||
if (isCharEl(b))
|
||||
@@ -86,7 +91,10 @@ function splitBoundaries(t: Text, needFirst: boolean, needLast: boolean, anyCjk:
|
||||
|
||||
function processParent(parent: Node, finder: Finder, anyCjk: RegExp): void
|
||||
{
|
||||
// 冪等:已含 jz-jinze(前次 render 未 revert)則跳過。
|
||||
// 冪等(I6):parent 自身即本 pass 產物——二次 render 未 revert 時,jz-char 之父
|
||||
// 正是上輪產生之 jz-jinze,若不擋則對已綁定內容再包一層、巢狀增殖(下游審計實測 3→6)。
|
||||
if (isJz(parent, PASS)) { return; }
|
||||
// 冪等:parent 已含 jz-jinze 子(上輪已處理此 parent)則跳過。
|
||||
for (let c = parent.firstChild; c; c = c.nextSibling)
|
||||
{
|
||||
if (c.nodeType === 1 && (c as Element).nodeName === "JZ-JINZE") { return; }
|
||||
|
||||
@@ -98,6 +98,12 @@ function rectOf(n: Node, side: "prev" | "next"): DOMRect | null
|
||||
|
||||
/** 取某節點側邊界字元之矩形(橫排:判定相鄰內容是否同行);跨我們的行內包裹
|
||||
* 向上攀爬,遇 block 邊界回 null(視為行端)。 */
|
||||
// 註:edgeRect 為**版面**測量(getClientRects 之物理相鄰),非邏輯文本流。故攀爬邊界
|
||||
// 僅依 wrapSet(行內包裹之標籤集),**刻意不**排除 isolate(<mark>)——isolate 斷的是
|
||||
// 邏輯 run/相鄰,其內部字元之物理行首行尾判定仍須跨 mark 找真實相鄰內容(如
|
||||
// 「文字<mark>「引」</mark>」之 「 物理上接在「文字」之後、非行首,須攀出 mark 取「文字」
|
||||
// 之 rect,否則誤判行首而錯收左半)。pill 之內部無 jz-char(eachTextNode 已濾),且 pill
|
||||
// 標籤(code/kbd)不在 styleInlines、自然不在 wrapSet,攀爬遇之即止,無需另判。
|
||||
function edgeRect(
|
||||
start: Node | null,
|
||||
side: "prev" | "next",
|
||||
|
||||
@@ -252,7 +252,10 @@ function processPills(root: Element, finder: Finder, boundarySelector: string, p
|
||||
{
|
||||
root.querySelectorAll(boundarySelector).forEach((pill) =>
|
||||
{
|
||||
if (finder.isAvoided(pill) || finder.insidePill(pill) || !finder.inScope(pill))
|
||||
// 自身即 avoid(如 pill 帶 data-jz-skip)亦不補間隙——isAvoided 只查祖先鏈、
|
||||
// 不含自身,須另判 avoidsSelf(下游問題六同型:漏此半則 avoid 對自身形同虛設)。
|
||||
if (finder.avoidsSelf(pill) || finder.isAvoided(pill)
|
||||
|| finder.insidePill(pill) || !finder.inScope(pill))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -332,7 +335,8 @@ export const spacingPass: StandalonePass = {
|
||||
// 遇巢狀 block 仍會 flush,故與上面逐 block 不重複;行內 root 通常無 block 子)。
|
||||
if (!options.finder.blockTags.has(ctx.root.nodeName)
|
||||
&& finder.featureEnabledFor(ctx.root, PASS)
|
||||
&& finder.inScope(ctx.root) && !finder.isAvoided(ctx.root))
|
||||
&& finder.inScope(ctx.root)
|
||||
&& !finder.avoidsSelf(ctx.root) && !finder.isAvoided(ctx.root))
|
||||
{
|
||||
processBlock(ctx.root, finder, R);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user