@aster-cloud/aster-lang-ts 1.0.28 → 1.0.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/scripts/generate_error_codes.js +49 -6
- package/dist/scripts/generate_error_codes.js.map +1 -1
- package/dist/src/ai/providers/openai.d.ts +10 -0
- package/dist/src/ai/providers/openai.d.ts.map +1 -1
- package/dist/src/ai/providers/openai.js +4 -0
- package/dist/src/ai/providers/openai.js.map +1 -1
- package/dist/src/config/lexicons/regex-guard.d.ts.map +1 -1
- package/dist/src/config/lexicons/regex-guard.js +128 -0
- package/dist/src/config/lexicons/regex-guard.js.map +1 -1
- package/dist/src/core/interpreter.d.ts.map +1 -1
- package/dist/src/core/interpreter.js +221 -27
- package/dist/src/core/interpreter.js.map +1 -1
- package/dist/src/cst/cst_builder.js +0 -14
- package/dist/src/cst/cst_builder.js.map +1 -1
- package/dist/src/cst/cst_printer.d.ts.map +1 -1
- package/dist/src/cst/cst_printer.js +20 -3
- package/dist/src/cst/cst_printer.js.map +1 -1
- package/dist/src/diagnostics/error_codes.d.ts +3 -3
- package/dist/src/diagnostics/error_codes.d.ts.map +1 -1
- package/dist/src/diagnostics/error_codes.js +509 -78
- package/dist/src/diagnostics/error_codes.js.map +1 -1
- package/dist/src/formatter.d.ts +7 -0
- package/dist/src/formatter.d.ts.map +1 -1
- package/dist/src/formatter.js +175 -5
- package/dist/src/formatter.js.map +1 -1
- package/dist/src/frontend/canonicalizer.d.ts.map +1 -1
- package/dist/src/frontend/canonicalizer.js +47 -6
- package/dist/src/frontend/canonicalizer.js.map +1 -1
- package/dist/src/frontend/lexer.d.ts +2 -1
- package/dist/src/frontend/lexer.d.ts.map +1 -1
- package/dist/src/frontend/lexer.js +10 -3
- package/dist/src/frontend/lexer.js.map +1 -1
- package/dist/src/frontend/transformers.d.ts.map +1 -1
- package/dist/src/frontend/transformers.js +50 -3
- package/dist/src/frontend/transformers.js.map +1 -1
- package/dist/src/lower_to_core.js +101 -4
- package/dist/src/lower_to_core.js.map +1 -1
- package/dist/src/lsp/canonicalize-options.d.ts +26 -0
- package/dist/src/lsp/canonicalize-options.d.ts.map +1 -0
- package/dist/src/lsp/canonicalize-options.js +14 -0
- package/dist/src/lsp/canonicalize-options.js.map +1 -0
- package/dist/src/lsp/completion.d.ts.map +1 -1
- package/dist/src/lsp/completion.js +22 -4
- package/dist/src/lsp/completion.js.map +1 -1
- package/dist/src/lsp/server.js +29 -1
- package/dist/src/lsp/server.js.map +1 -1
- package/dist/src/lsp/tenant-init.d.ts +55 -0
- package/dist/src/lsp/tenant-init.d.ts.map +1 -0
- package/dist/src/lsp/tenant-init.js +88 -0
- package/dist/src/lsp/tenant-init.js.map +1 -0
- package/dist/src/mapping/candidate-generator.d.ts +84 -0
- package/dist/src/mapping/candidate-generator.d.ts.map +1 -0
- package/dist/src/mapping/candidate-generator.js +114 -0
- package/dist/src/mapping/candidate-generator.js.map +1 -0
- package/dist/src/mapping/entity-proposer.d.ts +85 -0
- package/dist/src/mapping/entity-proposer.d.ts.map +1 -0
- package/dist/src/mapping/entity-proposer.js +162 -0
- package/dist/src/mapping/entity-proposer.js.map +1 -0
- package/dist/src/mapping/mapping-ir.d.ts +85 -0
- package/dist/src/mapping/mapping-ir.d.ts.map +1 -0
- package/dist/src/mapping/mapping-ir.js +195 -0
- package/dist/src/mapping/mapping-ir.js.map +1 -0
- package/dist/src/mapping/proof-ir.d.ts +125 -0
- package/dist/src/mapping/proof-ir.d.ts.map +1 -0
- package/dist/src/mapping/proof-ir.js +48 -0
- package/dist/src/mapping/proof-ir.js.map +1 -0
- package/dist/src/mapping/quantity-ir.d.ts +93 -0
- package/dist/src/mapping/quantity-ir.d.ts.map +1 -0
- package/dist/src/mapping/quantity-ir.js +151 -0
- package/dist/src/mapping/quantity-ir.js.map +1 -0
- package/dist/src/mapping/source-ir.d.ts +82 -0
- package/dist/src/mapping/source-ir.d.ts.map +1 -0
- package/dist/src/mapping/source-ir.js +231 -0
- package/dist/src/mapping/source-ir.js.map +1 -0
- package/dist/src/nodeid/change-impact.d.ts +77 -0
- package/dist/src/nodeid/change-impact.d.ts.map +1 -0
- package/dist/src/nodeid/change-impact.js +132 -0
- package/dist/src/nodeid/change-impact.js.map +1 -0
- package/dist/src/nodeid/node-id-map.d.ts +69 -0
- package/dist/src/nodeid/node-id-map.d.ts.map +1 -0
- package/dist/src/nodeid/node-id-map.js +193 -0
- package/dist/src/nodeid/node-id-map.js.map +1 -0
- package/dist/src/parser/decl-parser.d.ts.map +1 -1
- package/dist/src/parser/decl-parser.js +10 -4
- package/dist/src/parser/decl-parser.js.map +1 -1
- package/dist/src/parser/expr-stmt-parser.d.ts.map +1 -1
- package/dist/src/parser/expr-stmt-parser.js +36 -24
- package/dist/src/parser/expr-stmt-parser.js.map +1 -1
- package/dist/src/parser/span-utils.d.ts +25 -0
- package/dist/src/parser/span-utils.d.ts.map +1 -1
- package/dist/src/parser/span-utils.js +43 -0
- package/dist/src/parser/span-utils.js.map +1 -1
- package/dist/src/parser/type-parser.d.ts.map +1 -1
- package/dist/src/parser/type-parser.js +21 -0
- package/dist/src/parser/type-parser.js.map +1 -1
- package/dist/src/typecheck/expression.d.ts.map +1 -1
- package/dist/src/typecheck/expression.js +26 -0
- package/dist/src/typecheck/expression.js.map +1 -1
- package/dist/test/fuzz/fuzz.test.js +62 -2
- package/dist/test/fuzz/fuzz.test.js.map +1 -1
- package/dist/test/unit/call-arity.test.d.ts +2 -0
- package/dist/test/unit/call-arity.test.d.ts.map +1 -0
- package/dist/test/unit/call-arity.test.js +71 -0
- package/dist/test/unit/call-arity.test.js.map +1 -0
- package/dist/test/unit/canonicalizer/canonicalizer.test.js +42 -4
- package/dist/test/unit/canonicalizer/canonicalizer.test.js.map +1 -1
- package/dist/test/unit/canonicalizer/line-preservation.test.d.ts +2 -0
- package/dist/test/unit/canonicalizer/line-preservation.test.d.ts.map +1 -0
- package/dist/test/unit/canonicalizer/line-preservation.test.js +71 -0
- package/dist/test/unit/canonicalizer/line-preservation.test.js.map +1 -0
- package/dist/test/unit/canonicalizer/marker-restore-linear.test.d.ts +2 -0
- package/dist/test/unit/canonicalizer/marker-restore-linear.test.d.ts.map +1 -0
- package/dist/test/unit/canonicalizer/marker-restore-linear.test.js +98 -0
- package/dist/test/unit/canonicalizer/marker-restore-linear.test.js.map +1 -0
- package/dist/test/unit/canonicalizer/possessive-anchor.test.d.ts +2 -0
- package/dist/test/unit/canonicalizer/possessive-anchor.test.d.ts.map +1 -0
- package/dist/test/unit/canonicalizer/possessive-anchor.test.js +101 -0
- package/dist/test/unit/canonicalizer/possessive-anchor.test.js.map +1 -0
- package/dist/test/unit/formatter-redos.test.d.ts +2 -0
- package/dist/test/unit/formatter-redos.test.d.ts.map +1 -0
- package/dist/test/unit/formatter-redos.test.js +196 -0
- package/dist/test/unit/formatter-redos.test.js.map +1 -0
- package/dist/test/unit/interpreter/interpreter.test.js +7 -2
- package/dist/test/unit/interpreter/interpreter.test.js.map +1 -1
- package/dist/test/unit/lexer/bom.test.d.ts +2 -0
- package/dist/test/unit/lexer/bom.test.d.ts.map +1 -0
- package/dist/test/unit/lexer/bom.test.js +49 -0
- package/dist/test/unit/lexer/bom.test.js.map +1 -0
- package/dist/test/unit/list-get-bounds.test.d.ts +2 -0
- package/dist/test/unit/list-get-bounds.test.d.ts.map +1 -0
- package/dist/test/unit/list-get-bounds.test.js +66 -0
- package/dist/test/unit/list-get-bounds.test.js.map +1 -0
- package/dist/test/unit/long-bigint-exact.test.d.ts +2 -0
- package/dist/test/unit/long-bigint-exact.test.d.ts.map +1 -0
- package/dist/test/unit/long-bigint-exact.test.js +79 -0
- package/dist/test/unit/long-bigint-exact.test.js.map +1 -0
- package/dist/test/unit/lsp/completion-lexicon.test.d.ts +2 -0
- package/dist/test/unit/lsp/completion-lexicon.test.d.ts.map +1 -0
- package/dist/test/unit/lsp/completion-lexicon.test.js +83 -0
- package/dist/test/unit/lsp/completion-lexicon.test.js.map +1 -0
- package/dist/test/unit/lsp/tenant-vocabulary.test.d.ts +2 -0
- package/dist/test/unit/lsp/tenant-vocabulary.test.d.ts.map +1 -0
- package/dist/test/unit/lsp/tenant-vocabulary.test.js +187 -0
- package/dist/test/unit/lsp/tenant-vocabulary.test.js.map +1 -0
- package/dist/test/unit/map-builtins.test.js +163 -3
- package/dist/test/unit/map-builtins.test.js.map +1 -1
- package/dist/test/unit/map-structural-equality.test.d.ts +2 -0
- package/dist/test/unit/map-structural-equality.test.d.ts.map +1 -0
- package/dist/test/unit/map-structural-equality.test.js +64 -0
- package/dist/test/unit/map-structural-equality.test.js.map +1 -0
- package/dist/test/unit/map-type-guard.test.js +8 -5
- package/dist/test/unit/map-type-guard.test.js.map +1 -1
- package/dist/test/unit/mapping/candidate-generator.test.d.ts +2 -0
- package/dist/test/unit/mapping/candidate-generator.test.d.ts.map +1 -0
- package/dist/test/unit/mapping/candidate-generator.test.js +177 -0
- package/dist/test/unit/mapping/candidate-generator.test.js.map +1 -0
- package/dist/test/unit/mapping/entity-proposer.test.d.ts +2 -0
- package/dist/test/unit/mapping/entity-proposer.test.d.ts.map +1 -0
- package/dist/test/unit/mapping/entity-proposer.test.js +168 -0
- package/dist/test/unit/mapping/entity-proposer.test.js.map +1 -0
- package/dist/test/unit/mapping/mapping-ir.test.d.ts +2 -0
- package/dist/test/unit/mapping/mapping-ir.test.d.ts.map +1 -0
- package/dist/test/unit/mapping/mapping-ir.test.js +144 -0
- package/dist/test/unit/mapping/mapping-ir.test.js.map +1 -0
- package/dist/test/unit/mapping/proof-ir.test.d.ts +2 -0
- package/dist/test/unit/mapping/proof-ir.test.d.ts.map +1 -0
- package/dist/test/unit/mapping/proof-ir.test.js +119 -0
- package/dist/test/unit/mapping/proof-ir.test.js.map +1 -0
- package/dist/test/unit/mapping/quantity-ir.test.d.ts +2 -0
- package/dist/test/unit/mapping/quantity-ir.test.d.ts.map +1 -0
- package/dist/test/unit/mapping/quantity-ir.test.js +144 -0
- package/dist/test/unit/mapping/quantity-ir.test.js.map +1 -0
- package/dist/test/unit/mapping/quantity-redos-property.test.d.ts +2 -0
- package/dist/test/unit/mapping/quantity-redos-property.test.d.ts.map +1 -0
- package/dist/test/unit/mapping/quantity-redos-property.test.js +182 -0
- package/dist/test/unit/mapping/quantity-redos-property.test.js.map +1 -0
- package/dist/test/unit/mapping/redos-budget.test.d.ts +2 -0
- package/dist/test/unit/mapping/redos-budget.test.d.ts.map +1 -0
- package/dist/test/unit/mapping/redos-budget.test.js +168 -0
- package/dist/test/unit/mapping/redos-budget.test.js.map +1 -0
- package/dist/test/unit/mapping/source-ir.test.d.ts +2 -0
- package/dist/test/unit/mapping/source-ir.test.d.ts.map +1 -0
- package/dist/test/unit/mapping/source-ir.test.js +113 -0
- package/dist/test/unit/mapping/source-ir.test.js.map +1 -0
- package/dist/test/unit/nodeid/change-impact.test.d.ts +2 -0
- package/dist/test/unit/nodeid/change-impact.test.d.ts.map +1 -0
- package/dist/test/unit/nodeid/change-impact.test.js +152 -0
- package/dist/test/unit/nodeid/change-impact.test.js.map +1 -0
- package/dist/test/unit/nodeid/rename-declaration.test.d.ts +2 -0
- package/dist/test/unit/nodeid/rename-declaration.test.d.ts.map +1 -0
- package/dist/test/unit/nodeid/rename-declaration.test.js +75 -0
- package/dist/test/unit/nodeid/rename-declaration.test.js.map +1 -0
- package/dist/test/unit/parse-type-recursion.test.d.ts +2 -0
- package/dist/test/unit/parse-type-recursion.test.d.ts.map +1 -0
- package/dist/test/unit/parse-type-recursion.test.js +58 -0
- package/dist/test/unit/parse-type-recursion.test.js.map +1 -0
- package/dist/test/unit/regex-guard.test.js +64 -0
- package/dist/test/unit/regex-guard.test.js.map +1 -1
- package/dist/test/unit/strict-int-coercion.test.d.ts +2 -0
- package/dist/test/unit/strict-int-coercion.test.d.ts.map +1 -0
- package/dist/test/unit/strict-int-coercion.test.js +96 -0
- package/dist/test/unit/strict-int-coercion.test.js.map +1 -0
- package/dist/test/unit/ufcs-receiver.test.d.ts +2 -0
- package/dist/test/unit/ufcs-receiver.test.d.ts.map +1 -0
- package/dist/test/unit/ufcs-receiver.test.js +75 -0
- package/dist/test/unit/ufcs-receiver.test.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { describe, it } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { parseSourceIr, verifyCoverage, nodeAtOffset } from '../../../src/mapping/source-ir.js';
|
|
4
|
+
import { verifyMapping } from '../../../src/mapping/mapping-ir.js';
|
|
5
|
+
/**
|
|
6
|
+
* SourceIR(ADR 0037 §2/§9.5)—— 人类文档的结构化表示。
|
|
7
|
+
*
|
|
8
|
+
* <p>核心契约:**只标结构,不改内容**。每个节点的 span 必须能逐字节切回原文,
|
|
9
|
+
* 且父必须覆盖子——这两条是「点击原文 ↔ 定位 IR 节点」的前提。
|
|
10
|
+
*/
|
|
11
|
+
const POLICY = [
|
|
12
|
+
'# 付款审批政策', '',
|
|
13
|
+
'## 阈值', '',
|
|
14
|
+
'单笔付款超过 $10,000 时需要财务经理审批。', '',
|
|
15
|
+
'- 低于阈值:自动通过', '',
|
|
16
|
+
'- 高于阈值:转人工', '',
|
|
17
|
+
'## 例外', '',
|
|
18
|
+
'紧急付款可事后补批。', '',
|
|
19
|
+
].join('\n');
|
|
20
|
+
function allNodes(root) {
|
|
21
|
+
const out = [];
|
|
22
|
+
(function walk(n) { out.push(n); n.children.forEach(walk); })(root);
|
|
23
|
+
return out;
|
|
24
|
+
}
|
|
25
|
+
describe('SourceIR — 人类文档的结构化表示', () => {
|
|
26
|
+
it('★每个节点的 span 必须能逐字节切回原文(不做任何改写)', () => {
|
|
27
|
+
const ir = parseSourceIr(POLICY);
|
|
28
|
+
assert.deepStrictEqual(verifyCoverage(ir, POLICY), [], '覆盖不变式被破坏 —— span 切片与 text 不符或存在重叠。');
|
|
29
|
+
for (const n of allNodes(ir)) {
|
|
30
|
+
assert.strictEqual(n.text, POLICY.slice(n.span.start, n.span.end), `${n.nodeId} 的 text 不等于 span 切片 —— 说明做了文本改写。`);
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
it('★标题层级必须嵌套(`##` 归到 `#` 之下)', () => {
|
|
34
|
+
const ir = parseSourceIr(POLICY);
|
|
35
|
+
assert.strictEqual(ir.children.length, 1, `顶层应只有一个 \`# \` 标题节点,实际 ${ir.children.length} 个 —— 嵌套没建起来。`);
|
|
36
|
+
const h1 = ir.children[0];
|
|
37
|
+
assert.strictEqual(h1.level, 1);
|
|
38
|
+
const h2s = h1.children.filter(c => c.kind === 'HEADING');
|
|
39
|
+
assert.strictEqual(h2s.length, 2, '`## 阈值` 与 `## 例外` 都应挂在 `#` 之下。');
|
|
40
|
+
assert.ok(h2s.every(h => h.level === 2));
|
|
41
|
+
});
|
|
42
|
+
it('★父的 span 必须覆盖子(否则 nodeAtOffset 无法下降)', () => {
|
|
43
|
+
// 这是我实际踩过的 bug:HEADING 原本只覆盖标题那一行,其子段落在父之外,
|
|
44
|
+
// 于是「点击 $10,000 定位所在节点」返回 DOCUMENT 而不是那个段落。
|
|
45
|
+
const ir = parseSourceIr(POLICY);
|
|
46
|
+
for (const n of allNodes(ir)) {
|
|
47
|
+
for (const c of n.children) {
|
|
48
|
+
assert.ok(c.span.start >= n.span.start && c.span.end <= n.span.end, `${c.nodeId} 不被父 ${n.nodeId} 包含:子 [${c.span.start},${c.span.end}) `
|
|
49
|
+
+ `父 [${n.span.start},${n.span.end})`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
it('★nodeAtOffset 返回**最深**的命中节点', () => {
|
|
54
|
+
const ir = parseSourceIr(POLICY);
|
|
55
|
+
const offset = POLICY.indexOf('$10,000');
|
|
56
|
+
const hit = nodeAtOffset(ir, offset);
|
|
57
|
+
assert.strictEqual(hit?.kind, 'PARAGRAPH', `应命中段落而非祖先,实际 ${hit?.kind}(${hit?.nodeId})`
|
|
58
|
+
+ '\n★若返回 DOCUMENT/HEADING,说明包含不变式被破坏、无法逐层下降。');
|
|
59
|
+
assert.ok(hit.text.includes('$10,000'));
|
|
60
|
+
});
|
|
61
|
+
it('★verifyCoverage 必须能抓出错位的 span(否则它是个假门禁)', () => {
|
|
62
|
+
// 反向守卫:手工构造一个 text 与 span 不符的节点,必须被抓。
|
|
63
|
+
const bad = {
|
|
64
|
+
nodeId: 'doc', kind: 'DOCUMENT', span: { start: 0, end: 5 },
|
|
65
|
+
text: '完全不同的内容', children: [],
|
|
66
|
+
};
|
|
67
|
+
const problems = verifyCoverage(bad, POLICY);
|
|
68
|
+
assert.ok(problems.length > 0, 'text 与 span 不符却没被抓到 —— 这道检查形同虚设。');
|
|
69
|
+
});
|
|
70
|
+
it('★围栏代码块内的空行不得把代码块撕开', () => {
|
|
71
|
+
const doc = [
|
|
72
|
+
'# 标题', '',
|
|
73
|
+
'```', 'line1', '', 'line3', '```', '',
|
|
74
|
+
'正文。', '',
|
|
75
|
+
].join('\n');
|
|
76
|
+
const ir = parseSourceIr(doc);
|
|
77
|
+
assert.deepStrictEqual(verifyCoverage(ir, doc), []);
|
|
78
|
+
const codeBlocks = allNodes(ir).filter(n => n.kind === 'CODE_BLOCK');
|
|
79
|
+
assert.strictEqual(codeBlocks.length, 1, `代码块应是一个整体节点,实际被切成 ${codeBlocks.length} 块。`);
|
|
80
|
+
assert.ok(codeBlocks[0].text.includes('line1') && codeBlocks[0].text.includes('line3'), '代码块内容不完整 —— 空行处被切开了。');
|
|
81
|
+
});
|
|
82
|
+
it('★与 MappingIR 打通:SourceIR 的 offset 可直接做候选映射的 span', () => {
|
|
83
|
+
// 这是 SourceIR 存在的理由:让 MappingIR 能吃**人类文档**而不只是 .aster。
|
|
84
|
+
const ir = parseSourceIr(POLICY);
|
|
85
|
+
const start = POLICY.indexOf('$10,000');
|
|
86
|
+
const text = POLICY.slice(start, start + '$10,000'.length);
|
|
87
|
+
// 该 offset 必须落在某个具体结构节点里(不是孤立的字符)
|
|
88
|
+
assert.ok(nodeAtOffset(ir, start) !== undefined, 'offset 应落在某个 SourceIR 节点内。');
|
|
89
|
+
const r = verifyMapping({ span: { start, end: start + text.length }, text, nodeId: '$.threshold' }, () => ({ kind: 'Int', value: 10000 }));
|
|
90
|
+
assert.strictEqual(r.verdict, 'VERIFIED', `人类文档里的 $10,000 应能验过 Int(10000)。实际 ${r.verdict}:${r.reason}`);
|
|
91
|
+
});
|
|
92
|
+
it('★文档首尾的空白必须原样保留(text 不得被 trim)', () => {
|
|
93
|
+
// 反向守卫。★原先的样本 POLICY 首尾没有可 trim 的空白,于是给 freeze 加
|
|
94
|
+
// `.trim()` 这个变异**测不出来**——契约说「不改内容」,但没有样本能
|
|
95
|
+
// 证伪它。这里显式构造首尾带空白的文档。
|
|
96
|
+
const doc = '\n\n# 标题\n\n正文。\n\n \n';
|
|
97
|
+
const ir = parseSourceIr(doc);
|
|
98
|
+
assert.deepStrictEqual(verifyCoverage(ir, doc), [], '首尾空白应原样保留 —— 若这里变红,多半是某处对 text 做了 trim/规范化。');
|
|
99
|
+
assert.strictEqual(ir.text, doc, 'DOCUMENT 的 text 必须逐字节等于原文(含首尾空白)。');
|
|
100
|
+
});
|
|
101
|
+
it('空文档与纯空白文档不崩', () => {
|
|
102
|
+
for (const doc of ['', ' \n\n \n']) {
|
|
103
|
+
const ir = parseSourceIr(doc);
|
|
104
|
+
assert.deepStrictEqual(verifyCoverage(ir, doc), [], `文档 ${JSON.stringify(doc)} 覆盖检查失败`);
|
|
105
|
+
assert.strictEqual(ir.kind, 'DOCUMENT');
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
it('nodeId 在文档内唯一(否则映射会指向多个节点)', () => {
|
|
109
|
+
const ids = allNodes(parseSourceIr(POLICY)).map(n => n.nodeId);
|
|
110
|
+
assert.strictEqual(new Set(ids).size, ids.length, `nodeId 有重复:${ids.filter((v, i) => ids.indexOf(v) !== i).join(', ')}`);
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
//# sourceMappingURL=source-ir.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"source-ir.test.js","sourceRoot":"","sources":["../../../../test/unit/mapping/source-ir.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"change-impact.test.d.ts","sourceRoot":"","sources":["../../../../test/unit/nodeid/change-impact.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { describe, it } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { canonicalize } from '../../../src/frontend/canonicalizer.js';
|
|
4
|
+
import { lex } from '../../../src/frontend/lexer.js';
|
|
5
|
+
import { parse } from '../../../src/parser.js';
|
|
6
|
+
import { lowerModule } from '../../../src/lower_to_core.js';
|
|
7
|
+
import { computeNodeIds, canonicalHash } from '../../../src/nodeid/node-id-map.js';
|
|
8
|
+
import { diffNodeIds } from '../../../src/nodeid/change-impact.js';
|
|
9
|
+
/**
|
|
10
|
+
* ADR 0037 §4 的核心能力:跨版本 change impact(TS 侧,与 Java 对等)。
|
|
11
|
+
*
|
|
12
|
+
* <p>每条用例都锚在一个**具体的产品承诺**上(「改阈值 → 立刻知道哪条规则
|
|
13
|
+
* stale」),而不是锚在实现细节(路径字符串长什么样)上——后者会在重构时
|
|
14
|
+
* 变红却证明不了任何事。
|
|
15
|
+
*/
|
|
16
|
+
const V1 = [
|
|
17
|
+
'Module demo.approve.',
|
|
18
|
+
'',
|
|
19
|
+
'Rule approve given amount, produce:',
|
|
20
|
+
' If amount greater than 10000:',
|
|
21
|
+
' Return "REFER".',
|
|
22
|
+
' Otherwise:',
|
|
23
|
+
' Return "APPROVE".',
|
|
24
|
+
'',
|
|
25
|
+
].join('\n');
|
|
26
|
+
/** 完整链路:源码 → canonicalize → lex → parse → lower → Core IR。 */
|
|
27
|
+
function ir(src) {
|
|
28
|
+
const ast = parse(lex(canonicalize(src))).ast;
|
|
29
|
+
return JSON.parse(JSON.stringify(lowerModule(ast)));
|
|
30
|
+
}
|
|
31
|
+
function idsOf(src) {
|
|
32
|
+
return computeNodeIds(ir(src));
|
|
33
|
+
}
|
|
34
|
+
describe('change impact(TS 侧)', () => {
|
|
35
|
+
it('★改阈值:节点身份不变、内容指纹变 —— 这正是 change impact 的前提', () => {
|
|
36
|
+
const v2 = V1.replace('10000', '20000');
|
|
37
|
+
assert.notStrictEqual(v2, V1, '★replace 未生效则本用例测的是「未编辑」,结论无效。');
|
|
38
|
+
const changes = diffNodeIds(idsOf(V1), idsOf(v2));
|
|
39
|
+
// 契约 1:不能出现「旧节点消失 + 新节点出现」——那说明身份没稳住。
|
|
40
|
+
assert.ok(!changes.some(c => c.kind === 'ADDED'), `改一个字面量不应产生 ADDED 节点,实际:${JSON.stringify(changes)}`);
|
|
41
|
+
assert.ok(!changes.some(c => c.kind === 'REMOVED'), `改一个字面量不应产生 REMOVED 节点,实际:${JSON.stringify(changes)}`);
|
|
42
|
+
// 契约 2:必须有 MODIFIED,否则 change impact 根本没察觉到这次修改。
|
|
43
|
+
assert.ok(changes.some(c => c.kind === 'MODIFIED'), '改了阈值却没报告任何 MODIFIED —— change impact 失效。');
|
|
44
|
+
// 契约 3:包含该阈值的那条规则必须被标为 stale(§4 的「谁受影响」)。
|
|
45
|
+
assert.ok(changes.flatMap(c => c.staleAncestors).some(a => a.includes('{approve}')), `改了 approve 规则内的阈值,该规则应出现在 staleAncestors 里。实际:${JSON.stringify(changes)}`);
|
|
46
|
+
});
|
|
47
|
+
it('★在前面插入一条无关规则:原有规则的身份必须纹丝不动', () => {
|
|
48
|
+
// 这是纯下标路径方案的灾难场景(实测 5/16 存活)。命名作用域路径应完全免疫。
|
|
49
|
+
const v2 = V1.replace('Rule approve', 'Rule noop, produce:\n Return "X".\n\nRule approve');
|
|
50
|
+
assert.notStrictEqual(v2, V1, '★replace 未生效则本用例无效。');
|
|
51
|
+
const before = idsOf(V1);
|
|
52
|
+
const after = idsOf(v2);
|
|
53
|
+
for (const [id, identity] of before) {
|
|
54
|
+
const now = after.get(id);
|
|
55
|
+
assert.ok(now !== undefined, `插入无关规则后,原节点 ${id} 的身份丢失了`
|
|
56
|
+
+ '\n★这会让 change impact 把「没动过的规则」误报成被删除。');
|
|
57
|
+
// ★根节点 `$` 除外:模块的 decls 确实多了一条规则,它的内容**本就应该**变。
|
|
58
|
+
// 契约是「祖先 stale、后代不受扰」,不是「插入后整棵树一个字节都不许动」。
|
|
59
|
+
if (id !== '$') {
|
|
60
|
+
assert.strictEqual(now.contentHash, identity.contentHash, `原节点 ${id} 内容未变,contentHash 却变了`
|
|
61
|
+
+ '\n★多半是 contentHash 混进了位置信息(origin 未剥干净)。');
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
// 反向守卫:根节点**必须**变——它是「本模块多了一条规则」的唯一体现。
|
|
65
|
+
assert.notStrictEqual(after.get('$').contentHash, before.get('$').contentHash, '模块新增了一条规则,根节点 contentHash 却没变 —— 内容指纹失效。');
|
|
66
|
+
assert.ok(diffNodeIds(before, after).some(c => c.kind === 'ADDED'), '插入了一条新规则却没有任何 ADDED。');
|
|
67
|
+
});
|
|
68
|
+
it('未修改的程序:不得报告任何变更(防止 change impact 恒报 stale)', () => {
|
|
69
|
+
// 若本用例变红,说明 nodeId 或 contentHash 里混进了非确定性内容
|
|
70
|
+
// (随机序、时间戳、位置…)——那会让每次编译都「全量 stale」,能力等于没有。
|
|
71
|
+
assert.deepStrictEqual(diffNodeIds(idsOf(V1), idsOf(V1)), [], '同一份源码两次编译不应产生任何变更。');
|
|
72
|
+
});
|
|
73
|
+
it('★contentHash 必须剥掉 origin:位置不是内容', () => {
|
|
74
|
+
// 在**文件开头**加一行注释:所有代码整体下移一行,位置全变、内容全同。
|
|
75
|
+
const v2 = `# 一行注释\n${V1}`;
|
|
76
|
+
assert.deepStrictEqual(diffNodeIds(idsOf(V1), idsOf(v2)), [], '只加了一行注释(位置变、内容不变),却报告了变更。'
|
|
77
|
+
+ '\n★说明 contentHash 未剥干净 origin —— 这会让每次挪动代码都触发'
|
|
78
|
+
+ '\n 全量 stale,change impact 沦为噪声。');
|
|
79
|
+
});
|
|
80
|
+
it('已知局限:重命名规则会使其子树身份迁移(如实暴露,不掩盖)', () => {
|
|
81
|
+
// ADR 0037 §8.5 记录的固有代价(实测 1/16 存活)。本用例把它**钉成已知行为**,
|
|
82
|
+
// 而不是让它在生产里以「一条规则凭空消失 + 一条凭空出现」的形式被发现。
|
|
83
|
+
const v2 = V1.replace('Rule approve given amount', 'Rule assess given amount');
|
|
84
|
+
assert.notStrictEqual(v2, V1, '★replace 未生效则本用例无效。');
|
|
85
|
+
const changes = diffNodeIds(idsOf(V1), idsOf(v2));
|
|
86
|
+
assert.ok(changes.some(c => c.kind === 'REMOVED'), `重命名应表现为旧身份 REMOVED(需由显式 rename 声明消解),实际:${JSON.stringify(changes)}`);
|
|
87
|
+
assert.ok(changes.some(c => c.kind === 'ADDED'), `重命名应表现为新身份 ADDED,实际:${JSON.stringify(changes)}`);
|
|
88
|
+
});
|
|
89
|
+
it('stale 只向上传播,不向下 —— 否则影响面被夸大到整棵子树', () => {
|
|
90
|
+
const changes = diffNodeIds(idsOf(V1), idsOf(V1.replace('10000', '20000')));
|
|
91
|
+
for (const c of changes) {
|
|
92
|
+
for (const ancestor of c.staleAncestors) {
|
|
93
|
+
assert.ok(c.nodeId.startsWith(ancestor), `staleAncestors 含非祖先项:节点 ${c.nodeId} 的 ${ancestor}`
|
|
94
|
+
+ '\n★若把后代也标 stale,改一个字面量会波及整棵子树,影响面失真。');
|
|
95
|
+
assert.notStrictEqual(c.nodeId, ancestor, '节点自身不应出现在 staleAncestors 里。');
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
describe('canonicalHash:与 Java 同口径', () => {
|
|
101
|
+
it('★object key 按 Unicode code point 升序(不是 UTF-16 code unit 序)', () => {
|
|
102
|
+
// JS 的字符串比较(`<` 与默认 `.sort()`)按 **UTF-16 code unit**;Java 的
|
|
103
|
+
// CanonicalJson 按 **code point**。两者对增补平面字符的排序**相反**——
|
|
104
|
+
// 两侧会对同一棵树产出不同 hash,且只在含这类字符的程序上暴露(极易隐身)。
|
|
105
|
+
//
|
|
106
|
+
// ★判别用例必须选一对在两种排序下**次序相反**的键,否则测试恒绿:
|
|
107
|
+
// U+1F600 😀 的代理对以 D83D 开头 → UTF-16 序排在 U+FF5A z **之前**;
|
|
108
|
+
// 而 code point 序 0x1F600 > 0xFF5A → 应排在**之后**。
|
|
109
|
+
// (最初我用的是 `\u{1F600}` vs `'z'`(U+007A),两种排序结果相同,
|
|
110
|
+
// 把 compareByCodePoint 换成默认 .sort() 测试照样绿 = 假门禁。)
|
|
111
|
+
const emoji = '\u{1F600}';
|
|
112
|
+
const fullwidthZ = '\uFF5A';
|
|
113
|
+
assert.notStrictEqual(emoji < fullwidthZ, emoji.codePointAt(0) < fullwidthZ.codePointAt(0), '★选用的键对在两种排序下次序相同,本用例无法判别 —— 换一对键。');
|
|
114
|
+
// 期望值取自 Java CanonicalJson.canonicalHash(实跑),非 TS 自产回填。
|
|
115
|
+
assert.strictEqual(canonicalHash({ [emoji]: 1, [fullwidthZ]: 2 }), 'b87d68a90ea148ae96b7a6680061882d749a96d3b0a269a9c5b3a18ff647961e', '键序与 Java 不一致 —— 多半是用了 JS 默认的 UTF-16 比较而非 code point 比较。');
|
|
116
|
+
});
|
|
117
|
+
it('★拒绝浮点与超安全整数:静默降级会产出两侧不同的 hash', () => {
|
|
118
|
+
assert.throws(() => canonicalHash({ x: 1.5 }), /安全整数/, '浮点应抛错——跨引擎浮点表示不一致,静默接受会让两侧 hash 不同且无人察觉。');
|
|
119
|
+
assert.throws(() => canonicalHash({ x: Number.MAX_SAFE_INTEGER + 2 }), /安全整数/);
|
|
120
|
+
assert.throws(() => canonicalHash({ x: NaN }), /NaN|Infinity/);
|
|
121
|
+
});
|
|
122
|
+
it('hash 随内容变化(防止退化成常量)', () => {
|
|
123
|
+
assert.notStrictEqual(canonicalHash({ a: 1 }), canonicalHash({ a: 2 }), '不同内容产出了相同 hash —— 指纹失效。');
|
|
124
|
+
});
|
|
125
|
+
it('★与 Java 引擎产出逐字节相同的 hash(跨引擎黄金向量)', () => {
|
|
126
|
+
// ADR 0037 §7 要求 `Verify_TS == Verify_Java`。contentHash 是 verifier 的
|
|
127
|
+
// 输入之一,两侧必须对同一棵树产出同一个 hash。
|
|
128
|
+
//
|
|
129
|
+
// ★下列期望值是从 **Java `CanonicalJson.canonicalHash`** 实跑取得的
|
|
130
|
+
// (aster-lang-core,同一组输入),不是从 TS 自己的输出回填的——
|
|
131
|
+
// 后者会让本测试退化成「TS 和它自己一致」,恒绿且毫无意义。
|
|
132
|
+
//
|
|
133
|
+
// 覆盖四类易分叉点:键序、数组保序、字符串转义、null/bool 表示。
|
|
134
|
+
const golden = [
|
|
135
|
+
[{ kind: 'Func', name: 'approve' },
|
|
136
|
+
'1d734937ddec06a461c07bd9288b2794e5ed4c3f6412c35e77eb4f2ccedfa779'],
|
|
137
|
+
[{ kind: 'Module', name: 'demo.x', decls: [] },
|
|
138
|
+
'c57535c007893ea6a6e20cf310eac1bb94fe2034ccdbaa5d81c60672dc74b890'],
|
|
139
|
+
[{ z: 2, a: 1, m: [1, 2, 3] },
|
|
140
|
+
'ab46e446797b892a3626bbb51dac66c3bd1593612eb644be1f3f452232d876e3'],
|
|
141
|
+
[{ s: 'hello "world"\n', b: true, n: null },
|
|
142
|
+
'92c558f3dd8a1fd1e7be98261a6cb4fc37f01433418de7332dbb6540f8d5b8ea'],
|
|
143
|
+
[{ nested: { kind: 'If', cond: { kind: 'Bool', value: true } } },
|
|
144
|
+
'247d63263789e93f4c6700e1badf401abcd26662ca37767b4552873398571c7e'],
|
|
145
|
+
];
|
|
146
|
+
for (const [value, expected] of golden) {
|
|
147
|
+
assert.strictEqual(canonicalHash(value), expected, `TS 与 Java 的 canonical hash 不一致,输入:${JSON.stringify(value)}`
|
|
148
|
+
+ '\n★两侧对同一棵树必须产出同一 hash,否则跨引擎 verifier 无法成立。');
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
//# sourceMappingURL=change-impact.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"change-impact.test.js","sourceRoot":"","sources":["../../../../test/unit/nodeid/change-impact.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rename-declaration.test.d.ts","sourceRoot":"","sources":["../../../../test/unit/nodeid/rename-declaration.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { describe, it } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { canonicalize } from '../../../src/frontend/canonicalizer.js';
|
|
4
|
+
import { lex } from '../../../src/frontend/lexer.js';
|
|
5
|
+
import { parse } from '../../../src/parser.js';
|
|
6
|
+
import { lowerModule } from '../../../src/lower_to_core.js';
|
|
7
|
+
import { computeNodeIds } from '../../../src/nodeid/node-id-map.js';
|
|
8
|
+
import { diffNodeIds, rename } from '../../../src/nodeid/change-impact.js';
|
|
9
|
+
/**
|
|
10
|
+
* 显式 rename 声明(ADR 0037 §8.5 最后一项未决,与 Java 侧对等)。
|
|
11
|
+
*
|
|
12
|
+
* 命名作用域路径对重命名敏感:改一条规则的名字,其整棵子树的 nodeId 全变。
|
|
13
|
+
* 实测 16 节点样本 → 31 条变更,而其中 14 个节点内容逐字节未变。
|
|
14
|
+
*/
|
|
15
|
+
const V1 = [
|
|
16
|
+
'Module demo.approve.', '',
|
|
17
|
+
'Rule approve given amount, produce:',
|
|
18
|
+
' If amount greater than 10000:',
|
|
19
|
+
' Return "REFER".',
|
|
20
|
+
' Otherwise:',
|
|
21
|
+
' Return "APPROVE".', '',
|
|
22
|
+
].join('\n');
|
|
23
|
+
const idsOf = (src) => computeNodeIds(JSON.parse(JSON.stringify(lowerModule(parse(lex(canonicalize(src))).ast))));
|
|
24
|
+
describe('显式 rename 声明(TS 侧)', () => {
|
|
25
|
+
it('★声明后子树身份整体迁移,REMOVED/ADDED 噪声归零', () => {
|
|
26
|
+
const v2 = V1.replace('Rule approve given amount', 'Rule assess given amount');
|
|
27
|
+
assert.notStrictEqual(v2, V1, '★replace 未生效则本用例无效。');
|
|
28
|
+
const before = idsOf(V1), after = idsOf(v2);
|
|
29
|
+
const noiseBefore = diffNodeIds(before, after).filter(c => c.kind !== 'MODIFIED').length;
|
|
30
|
+
const noiseAfter = diffNodeIds(before, after, [rename('approve', 'assess')])
|
|
31
|
+
.filter(c => c.kind !== 'MODIFIED').length;
|
|
32
|
+
assert.ok(noiseBefore > 0, '未声明时应存在 REMOVED/ADDED 噪声(问题本身)。');
|
|
33
|
+
assert.strictEqual(noiseAfter, 0, `声明后不应再有 REMOVED/ADDED,实际 ${noiseAfter} 条。`);
|
|
34
|
+
});
|
|
35
|
+
it('★真实改动仍被报告(反向守卫:声明不得吞掉一切)', () => {
|
|
36
|
+
// 没有这条,「声明重命名」可以退化成「把所有变更都吞掉」,上面那条照样绿。
|
|
37
|
+
const v2 = V1
|
|
38
|
+
.replace('Rule approve given amount', 'Rule assess given amount')
|
|
39
|
+
.replace('10000', '20000');
|
|
40
|
+
const changes = diffNodeIds(idsOf(V1), idsOf(v2), [rename('approve', 'assess')]);
|
|
41
|
+
assert.ok(changes.some(c => c.kind === 'MODIFIED'), '阈值确实改了,必须仍报 MODIFIED。');
|
|
42
|
+
assert.ok(changes.some(c => c.staleAncestors.some(a => a.includes('{assess}'))), '受影响的规则应以**新**名字出现在 staleAncestors 里。');
|
|
43
|
+
});
|
|
44
|
+
it('★contentHash 不唯一 —— 自动配对不可靠的依据', () => {
|
|
45
|
+
const ids = idsOf(['Module m.', '', 'Rule alpha, produce:', ' Return 1.', '',
|
|
46
|
+
'Rule beta, produce:', ' Return 1.', ''].join('\n'));
|
|
47
|
+
const a = ids.get('$.decls{alpha}.body');
|
|
48
|
+
const b = ids.get('$.decls{beta}.body');
|
|
49
|
+
assert.ok(a && b, '未找到被测节点,测试失去对象。');
|
|
50
|
+
assert.strictEqual(a.contentHash, b.contentHash, '两条同体规则的 body 应有相同 contentHash。'
|
|
51
|
+
+ '\n★若本条变红,说明 hash 已唯一,届时可重新评估自动配对方案。');
|
|
52
|
+
});
|
|
53
|
+
it('只替换完整路径段,不做子串误伤(approve 不碰 approveAll)', () => {
|
|
54
|
+
const src = ['Module m.', '', 'Rule approve, produce:', ' Return 1.', '',
|
|
55
|
+
'Rule approveAll, produce:', ' Return 2.', ''].join('\n');
|
|
56
|
+
const renamed = src.replace('Rule approve,', 'Rule assess,');
|
|
57
|
+
assert.notStrictEqual(renamed, src, '★构造未生效则本用例无效。');
|
|
58
|
+
const changes = diffNodeIds(idsOf(src), idsOf(renamed), [rename('approve', 'assess')]);
|
|
59
|
+
assert.ok(!changes.some(c => c.nodeId.includes('approveAll')), `approveAll 未被修改,不应出现在变更里 —— 多半是把 approve 当子串误伤了。实际:${JSON.stringify(changes)}`);
|
|
60
|
+
assert.ok(changes.every(c => c.kind === 'MODIFIED'), '声明后不应再有 REMOVED/ADDED。');
|
|
61
|
+
});
|
|
62
|
+
it('自相矛盾的声明直接拒绝', () => {
|
|
63
|
+
assert.throws(() => diffNodeIds(idsOf(V1), idsOf(V1), [rename('approve', 'assess'), rename('approve', 'review')]), /多个目标/, '同一名字声明到两个目标,应当拒绝而非静默取其一。');
|
|
64
|
+
});
|
|
65
|
+
it('非法 Rename 构造直接拒绝', () => {
|
|
66
|
+
assert.throws(() => rename('', 'x'), /不能为空/);
|
|
67
|
+
assert.throws(() => rename('x', ' '), /不能为空/);
|
|
68
|
+
assert.throws(() => rename('same', 'same'), /相同/);
|
|
69
|
+
});
|
|
70
|
+
it('不传 renames 时与旧行为一致(向后兼容)', () => {
|
|
71
|
+
const v2 = V1.replace('10000', '20000');
|
|
72
|
+
assert.deepStrictEqual(diffNodeIds(idsOf(V1), idsOf(v2)), diffNodeIds(idsOf(V1), idsOf(v2), []));
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
//# sourceMappingURL=rename-declaration.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rename-declaration.test.js","sourceRoot":"","sources":["../../../../test/unit/nodeid/rename-declaration.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parse-type-recursion.test.d.ts","sourceRoot":"","sources":["../../../test/unit/parse-type-recursion.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { describe, it } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { compile } from '../../src/browser.js';
|
|
4
|
+
/**
|
|
5
|
+
* 类型递归的深度护栏(issue aster-lang-ts#145)。
|
|
6
|
+
*
|
|
7
|
+
* ★真实缺陷:`MAX_RECURSION_DEPTH=300` 的设计目标就是「病态输入不许打爆原生栈」,
|
|
8
|
+
* 但它此前**只接在 `parseExpr` 上**;而 `parseType → parseTypePrimary → parseType`
|
|
9
|
+
* 的类型递归完全无护栏。
|
|
10
|
+
*
|
|
11
|
+
* 实测(修复前):`'maybe '.repeat(5000) + 'Int'`(约 30KB)→ 原生 `RangeError`
|
|
12
|
+
* 「Maximum call stack size exceeded」,被 decl-parser 的 catch-all 转成
|
|
13
|
+
* **span 恒为 1:1 的假诊断**——用户既不知道是哪一行,也不知道真正原因是嵌套过深。
|
|
14
|
+
*
|
|
15
|
+
* 对照:400 层**括号**一直正确报「nesting too deep (exceeds limit of 300)」——
|
|
16
|
+
* 同一份护栏,只是没接到类型这条路径上。
|
|
17
|
+
*/
|
|
18
|
+
function parseErrorOf(src) {
|
|
19
|
+
const c = compile(src);
|
|
20
|
+
const errs = c.parseErrors ?? [];
|
|
21
|
+
return errs.length > 0 ? errs[0].message : null;
|
|
22
|
+
}
|
|
23
|
+
function nestedMaybe(depth) {
|
|
24
|
+
return `Module p.\nRule r given x as ${'maybe '.repeat(depth)}Int, produce Int:\n Return 1.\n`;
|
|
25
|
+
}
|
|
26
|
+
describe('类型嵌套深度护栏', () => {
|
|
27
|
+
it('★5000 层嵌套类型报可恢复诊断,不是原生 RangeError', () => {
|
|
28
|
+
const msg = parseErrorOf(nestedMaybe(5000));
|
|
29
|
+
assert.ok(msg, '应有解析错误');
|
|
30
|
+
assert.doesNotMatch(msg, /Maximum call stack size exceeded/, `不得渗出原生 RangeError;实际:${msg}`);
|
|
31
|
+
assert.match(msg, /nesting too deep/, `应报嵌套过深;实际:${msg}`);
|
|
32
|
+
});
|
|
33
|
+
it('★超限即报,且报的是嵌套过深而非别的错', () => {
|
|
34
|
+
const msg = parseErrorOf(nestedMaybe(400));
|
|
35
|
+
assert.ok(msg, '400 层已超 300 上限,应报错');
|
|
36
|
+
assert.match(msg, /nesting too deep/, `实际:${msg}`);
|
|
37
|
+
});
|
|
38
|
+
it('★护栏不随深度增加而失效(10000 层同样是可恢复诊断)', () => {
|
|
39
|
+
// 若护栏仍在递归之后,更深的输入只会更早栈溢出。
|
|
40
|
+
const msg = parseErrorOf(nestedMaybe(10000));
|
|
41
|
+
assert.ok(msg);
|
|
42
|
+
assert.doesNotMatch(msg, /Maximum call stack size exceeded/, `实际:${msg}`);
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
describe('正常类型不受影响(反向护栏)', () => {
|
|
46
|
+
// ★没有这一组,把 parseType 写成「一律报嵌套过深」也能让上面全部变绿。
|
|
47
|
+
it('普通类型正常解析', () => {
|
|
48
|
+
assert.equal(parseErrorOf('Module p.\nRule r given x as Int, produce Int:\n Return 1.\n'), null);
|
|
49
|
+
});
|
|
50
|
+
it('浅层嵌套类型正常解析', () => {
|
|
51
|
+
assert.equal(parseErrorOf(nestedMaybe(10)), null, '10 层远低于上限');
|
|
52
|
+
});
|
|
53
|
+
it('★接近上限但未超(290 层)仍应通过', () => {
|
|
54
|
+
// 锁住「上限是 300 而不是被我改小了」——防护栏顺手收紧了合法输入。
|
|
55
|
+
assert.equal(parseErrorOf(nestedMaybe(290)), null, '290 层未超 300 上限,应通过');
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
//# sourceMappingURL=parse-type-recursion.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parse-type-recursion.test.js","sourceRoot":"","sources":["../../../test/unit/parse-type-recursion.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAE/C;;;;;;;;;;;;;GAaG;AACH,SAAS,YAAY,CAAC,GAAW;IAC/B,MAAM,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;IACvB,MAAM,IAAI,GAAI,CAA6C,CAAC,WAAW,IAAI,EAAE,CAAC;IAC9E,OAAO,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAE,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC;AACnD,CAAC;AAED,SAAS,WAAW,CAAC,KAAa;IAChC,OAAO,gCAAgC,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,kCAAkC,CAAC;AAClG,CAAC;AAED,QAAQ,CAAC,UAAU,EAAE,GAAG,EAAE;IACxB,EAAE,CAAC,mCAAmC,EAAE,GAAG,EAAE;QAC3C,MAAM,GAAG,GAAG,YAAY,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC;QAC5C,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;QACzB,MAAM,CAAC,YAAY,CAAC,GAAI,EAAE,kCAAkC,EAC1D,wBAAwB,GAAG,EAAE,CAAC,CAAC;QACjC,MAAM,CAAC,KAAK,CAAC,GAAI,EAAE,kBAAkB,EAAE,aAAa,GAAG,EAAE,CAAC,CAAC;IAC7D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qBAAqB,EAAE,GAAG,EAAE;QAC7B,MAAM,GAAG,GAAG,YAAY,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC;QAC3C,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,oBAAoB,CAAC,CAAC;QACrC,MAAM,CAAC,KAAK,CAAC,GAAI,EAAE,kBAAkB,EAAE,MAAM,GAAG,EAAE,CAAC,CAAC;IACtD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+BAA+B,EAAE,GAAG,EAAE;QACvC,0BAA0B;QAC1B,MAAM,GAAG,GAAG,YAAY,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,CAAC;QAC7C,MAAM,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC;QACf,MAAM,CAAC,YAAY,CAAC,GAAI,EAAE,kCAAkC,EAAE,MAAM,GAAG,EAAE,CAAC,CAAC;IAC7E,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,gBAAgB,EAAE,GAAG,EAAE;IAC9B,2CAA2C;IAC3C,EAAE,CAAC,UAAU,EAAE,GAAG,EAAE;QAClB,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,+DAA+D,CAAC,EAAE,IAAI,CAAC,CAAC;IACpG,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,YAAY,EAAE,GAAG,EAAE;QACpB,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,WAAW,CAAC,CAAC;IACjE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qBAAqB,EAAE,GAAG,EAAE;QAC7B,sCAAsC;QACtC,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,oBAAoB,CAAC,CAAC;IAC3E,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -49,6 +49,70 @@ test('regex-guard 测试套件', async (t) => {
|
|
|
49
49
|
assert.match(result.error, /invalid regular expression/i);
|
|
50
50
|
}
|
|
51
51
|
});
|
|
52
|
+
await t.test('★相邻量词歧义也必须拒绝(嵌套量词检查抓不到它)', () => {
|
|
53
|
+
// ★这个缺口由独立审查者发现,我已复现:
|
|
54
|
+
// `hasNestedQuantifier` 只看「被量词修饰的**分组**」,
|
|
55
|
+
// 而歧义**不需要分组**——两个相邻、匹配同一原子的量词即可产生
|
|
56
|
+
// 2^n 种切分,后缀失配时全部被穷举。
|
|
57
|
+
//
|
|
58
|
+
// 实测(守卫改前全部 ACCEPTED):
|
|
59
|
+
// a*a*a*a*a*a*a*a*a*a*b 16→292ms 20→361ms 22→808ms 24→1705ms(每 +2 翻倍)
|
|
60
|
+
// a+a+a+a+a+a+a+a+b 同样指数
|
|
61
|
+
//
|
|
62
|
+
// ★TS 侧**没有看门狗兜底**(Java 的 replaceAllWithTimeout 有),
|
|
63
|
+
// 单线程 JS 下这就是无限挂死。
|
|
64
|
+
for (const evil of [
|
|
65
|
+
'a*a*a*a*a*a*a*a*a*a*b',
|
|
66
|
+
'a+a+a+a+a+a+a+a+b',
|
|
67
|
+
'\\d*\\d*x',
|
|
68
|
+
'[ab]*[ab]*c',
|
|
69
|
+
'a{1,}a{1,}b',
|
|
70
|
+
// ★以下三条是独立审查者找出的绕过(第一版全部 ACCEPTED,实测 24 字符
|
|
71
|
+
// 输入 1720ms / 1720ms / 640ms,与已修的 a*a*b 同级)。
|
|
72
|
+
// 根因:readQuantifiedAtom 遇 `(` 直接 return null 并注释「交给
|
|
73
|
+
// hasNestedQuantifier」,但后者只看**分组内部**有无量词——
|
|
74
|
+
// `(a)*(a)*` 两侧内部都没有,于是**无人负责**。
|
|
75
|
+
'(a)*(a)*b',
|
|
76
|
+
'(?:a)*(?:a)*b',
|
|
77
|
+
'a*?a*?b',
|
|
78
|
+
// ★字符类里的括号会骗过深度扫描——这是**加分组支持时新引入**的漏判
|
|
79
|
+
// (上一版遇 `(` 直接 return null,反而不会误算深度)。实测 22 字符 794ms。
|
|
80
|
+
'([)])*([)])*b',
|
|
81
|
+
'([(])*([(])*b',
|
|
82
|
+
// ★这条同样能区分两态(跳过=REJECT / 不跳过=ACCEPT),
|
|
83
|
+
// 且原子内「字符类 + 普通字符」混排,覆盖面比上两条更宽。
|
|
84
|
+
'([)]a)*([)]a)*b',
|
|
85
|
+
]) {
|
|
86
|
+
const result = compileGuardedRegex(evil, '');
|
|
87
|
+
assert.strictEqual(result.ok, false, `相邻量词模式 ${evil} 未被拒绝`);
|
|
88
|
+
if (!result.ok) {
|
|
89
|
+
assert.match(result.error, /adjacent ambiguous quantifier/i);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
await t.test('★合法模式不得被相邻量词检查误伤(误伤比漏网更糟)', () => {
|
|
94
|
+
// 反向守卫:守卫收得过紧会**静默丢掉**用户的合法 overlay 规则,
|
|
95
|
+
// 那比漏掉一个 ReDoS 更难发现。
|
|
96
|
+
//
|
|
97
|
+
// ★`a*b*c`(不同原子)、`a*a`/`aa*`(只有一个量词)都必须通过。
|
|
98
|
+
// 实证:扫了两仓 79 条**生产**词典正则,零误伤。
|
|
99
|
+
for (const ok of [
|
|
100
|
+
'a*b*c', '\\d+\\w+', '[a-z]+[0-9]*', '(ab)+c', 'a+b',
|
|
101
|
+
'\\bfoo\\b', 'greater\\s+than', '[\\p{L}]+', 'x{2,5}y',
|
|
102
|
+
'a*a', 'aa*', '\\s+\\S+', '^(#{1,6})\\s',
|
|
103
|
+
// ★反向:不同原子的相邻量词、含分组的合法模式不得被误伤
|
|
104
|
+
'(a)(b)*c', '(?:ab)*(?:cd)*e', 'a*?b*?c',
|
|
105
|
+
// ★`([)])(a)*b` 是**非回归样本**,不是「字符类跳过」的守卫:
|
|
106
|
+
// 我复刻「跳过/不跳过」两版逐条对拍,它在两态下**判定相同**(均 false),
|
|
107
|
+
// 故变异掉字符类跳过时它照样绿——**没有证明力**。保留它只为确认
|
|
108
|
+
// 含字符类的合法模式不被误伤,不得把它算进「反向守卫有效」的证据。
|
|
109
|
+
// 真正能区分两态的样本全在**攻击方向**(见上面的 `([)])*([)])*b`)。
|
|
110
|
+
'([)])(a)*b',
|
|
111
|
+
]) {
|
|
112
|
+
const result = compileGuardedRegex(ok, ok.includes('p{') ? 'u' : '');
|
|
113
|
+
assert.strictEqual(result.ok, true, `合法模式 ${ok} 被误伤:${result.ok ? '' : result.error}`);
|
|
114
|
+
}
|
|
115
|
+
});
|
|
52
116
|
await t.test('overlay-loader 跳过恶意 type-inference 规则', () => {
|
|
53
117
|
const rules = loadTypeInferenceRules({
|
|
54
118
|
version: 1,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"regex-guard.test.js","sourceRoot":"","sources":["../../../test/unit/regex-guard.test.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"regex-guard.test.js","sourceRoot":"","sources":["../../../test/unit/regex-guard.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"strict-int-coercion.test.d.ts","sourceRoot":"","sources":["../../../test/unit/strict-int-coercion.test.ts"],"names":[],"mappings":""}
|