Files
aurora-admin/tools/lib/parse-string-literal-array.mjs
T
aurora-admin eb25feedaf feat(S6-P21): 组件族参数化(13族/44成员/导航族15文件同源/79→48概念组件)
【本次核心 · S6-P21】
- 族层数据:families.json + 44 份契约注入 family/familyRole/familyParams;
  data.json / data.js / site/details 同步。13 族 / 44 成员 / 35 独立 → 概念组件 79→48。
- 79 个 slug 全保留、集合逐一不变(铁律 5 对外承诺未破);frameworks 仍 395 文件、薄壳仍 79。
- 归族判据为契约中可核对字段(semanticTypeCandidates 重叠 / anatomy 为同一骨架子集 /
  变体维度同构 / doNotInvent 显式从属声明),每族 mergeBasis 写明依据,不按名字猜。
- 实现层合并(导航族端到端切片):tools/gen-family-impl.mjs 从 5 端模板生成
  TopMenu / SideMenu / MixedNavigation 共 15 文件,参数 direction=top|side|mixed;
  三份 CSS md5 完全相同 = 一份样式表服务三个组件。
- 新增 tools/gen-families.mjs、tools/gen-family-impl.mjs、tools/verify-families.mjs、
  tools/lib/family-model.mjs、tools/lib/family-impl/nav-menu/*.tpl。

【同时清掉此前已完成但未提交的批次】
生成物(data.json / data.js / site/sources / site/components 薄壳 / sitemap.xml / tests 报告)
跨阶段交织,无法拆成互相自洽的多个提交,故按既有批量风格合并提交:
- Package:三端可 import(S5-P18)+ 发布到私有 npm 源
- Docs site:导航语言改下拉(S5-P19)、详情页代码块默认展开、中英切换完整性
- Security:生产部署链审计修复(2026-09-19)+ 线上部署
- Theme modes 日间/夜间/自动;S1-P4 data.js 瘦身;S2-P5 暗色;S2-P6 跨端一致性;
  S2-P7 行为断言;S2-P9 FAQ;S3-P8 RTL;S3-P9 契约缺口解释层;S4-P12 发布流程
- 补入 tools/pack-deploy.mjs、run-site-smoke.mjs、verify-*.mjs,.dockerignore、
  安全审计修复与待决策项.md

【验收】
- node tools/verify-families.mjs → OK: 族层端到端一致(13 族 / 44 成员 / 79 组件不变 / 395 文件不变)
- node tools/verify-cross-platform.mjs → 79/79 identical(HEAD 基线 high 44)
- node tools/run-regression.mjs → 100%(79/79 页,1017/1017 断言,N/A 34),连跑 8 次一致,0 超时
- 逐页实测:topmenu / sidemenu / mixednavigation 各 13/13,帧内 direction 参数正确,0 JS 错误
- 零运行时依赖 OK;build-site.ps1 ASCII-only OK

【未纳入】site/components/<slug>/ 平台薄壳 316 个 —— 历史从未跟踪且属构建产物,保持现状。
2026-09-20 03:32:31 +08:00

85 lines
4.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* Aurora Admin —— defineEmits 数组字面量的安全解析器(构建期唯一可信实现)
*
* 用途:tools/precompute.mjs 阶段 C 预计算 API 表时,从 frameworks/*.vue* 的
* `defineEmits([...])` 抽取事件名。
*
* 安全背景(实测缺陷 P2 / 构建链同源缺陷):
* 旧实现把数组字面量交给函数构造器求值(等价于动态执行代码)。
* 任何能改 frameworks/*.vue* 的人(内部威胁 / 投毒 PR)只要写
* defineEmits([1, globalThis.__pwned = 'x'])
* 就会在**构建机**(CI 往往带 secrets)上执行任意 JS。本模块只做字面量解析,
* 任何非字符串元素 / 未识别转义 / 裸换行 / 结构不符 → 返回 null(跳过该条,
* 不抽取事件、不抛错、绝不执行任何内容)。
*
* 「同一份实现」的落地方式(重要):
* 本文件是 site/app.js 内联实现(extractComponentAPI 内的 isHexRun +
* parseStringLiteralArray)的**逐行同源副本**。浏览器端 site/app.js 是
* classic script(<script src="app.js">,无模块系统),且 dev-server 的
* 公开白名单(isPublicPath)不含 tools/,因此 site/app.js 无法直接 import 本模块;
* 强行改造会改变文档站对外行为(回归基线)。
* 故由 tools/verify-emits-parse.mjs 的「漂移门」保证两份不漂移:
* 两侧函数在全部 158 个真实 frameworks/*.vue 的 defineEmits 字面量上抽取结果
* 逐例等价,并在对抗语料上同样不执行任何输入。
* 修改任意一侧后必须同步另一侧,否则该 verify 脚本会失败。
*
* 零依赖:只用语言内置能力。
*/
/* 判断 s 是否恰好是 n 位十六进制(供 \xXX / \uXXXX 校验,避免 parseInt 的宽松前缀解析) */
function isHexRun(s, n) {
if (s.length !== n) return false;
for (var h = 0; h < n; h++) {
var cc = s.charCodeAt(h);
if (!((cc >= 48 && cc <= 57) || (cc >= 97 && cc <= 102) || (cc >= 65 && cc <= 70))) return false;
}
return true;
}
/* 解析「元素全部为字符串字面量」的数组字面量(允许空白与尾逗号)。
只按字面量语义还原转义,绝不求值;不匹配 → null。 */
export function parseStringLiteralArray(literal) {
if (typeof literal !== 'string' || literal.charAt(0) !== '[' ||
literal.charAt(literal.length - 1) !== ']') return null;
var body = literal.slice(1, literal.length - 1);
var simple = { n: '\n', r: '\r', t: '\t', b: '\b', f: '\f', v: '\v' };
var out = [];
var i = 0;
for (;;) {
while (i < body.length && /\s/.test(body.charAt(i))) i++;
if (i >= body.length) break; // 空数组 / 尾逗号结束
var quote = body.charAt(i);
if (quote !== "'" && quote !== '"') return null; // 非字符串字面量 → 整体拒绝
i++;
var value = '';
var closed = false;
while (i < body.length) {
var ch = body.charAt(i);
if (ch === '\\') {
var esc = body.charAt(i + 1);
if (esc === '') return null;
if (esc === quote || esc === '\\') { value += esc; i += 2; continue; }
if (simple[esc] !== undefined) { value += simple[esc]; i += 2; continue; }
if (esc === '\n' || esc === '\r') { i += 2; continue; } // 续行
if (esc === 'x' && isHexRun(body.substr(i + 2, 2), 2)) {
value += String.fromCharCode(parseInt(body.substr(i + 2, 2), 16)); i += 4; continue;
}
if (esc === 'u' && isHexRun(body.substr(i + 2, 4), 4)) {
value += String.fromCharCode(parseInt(body.substr(i + 2, 4), 16)); i += 6; continue;
}
return null; // 未识别转义 → 整体拒绝
}
if (ch === quote) { closed = true; i++; break; }
if (ch === '\n' || ch === '\r') return null; // 字符串内裸换行非法
value += ch;
i++;
}
if (!closed) return null;
out.push(value);
while (i < body.length && /\s/.test(body.charAt(i))) i++;
if (i >= body.length) break;
if (body.charAt(i) !== ',') return null; // 不是逗号分隔 → 整体拒绝
i++;
}
return out;
}