@alloy-js/core 0.23.0-dev.16 → 0.23.0-dev.18
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/dev/src/debug/diagnostics-broadcast.test.js +100 -0
- package/dist/dev/src/debug/diagnostics-broadcast.test.js.map +1 -0
- package/dist/dev/src/debug/trace-writer.js +18 -2
- package/dist/dev/src/debug/trace-writer.js.map +1 -1
- package/dist/dev/src/diagnostics.js +27 -19
- package/dist/dev/src/diagnostics.js.map +1 -1
- package/dist/dev/src/symbols/output-symbol.js +77 -7
- package/dist/dev/src/symbols/output-symbol.js.map +1 -1
- package/dist/dev/src/symbols/symbol-table.js +13 -3
- package/dist/dev/src/symbols/symbol-table.js.map +1 -1
- package/dist/dev/test/symbols/deconflicted-name.test.js +120 -0
- package/dist/dev/test/symbols/deconflicted-name.test.js.map +1 -0
- package/dist/dev/test/symbols/output-scope.test.js +41 -0
- package/dist/dev/test/symbols/output-scope.test.js.map +1 -1
- package/dist/src/debug/diagnostics-broadcast.test.d.ts +2 -0
- package/dist/src/debug/diagnostics-broadcast.test.d.ts.map +1 -0
- package/dist/src/debug/diagnostics-broadcast.test.js +100 -0
- package/dist/src/debug/diagnostics-broadcast.test.js.map +1 -0
- package/dist/src/debug/trace-writer.d.ts +2 -1
- package/dist/src/debug/trace-writer.d.ts.map +1 -1
- package/dist/src/debug/trace-writer.js +18 -2
- package/dist/src/debug/trace-writer.js.map +1 -1
- package/dist/src/diagnostics.d.ts +1 -1
- package/dist/src/diagnostics.d.ts.map +1 -1
- package/dist/src/diagnostics.js +27 -19
- package/dist/src/diagnostics.js.map +1 -1
- package/dist/src/symbols/output-symbol.d.ts +35 -0
- package/dist/src/symbols/output-symbol.d.ts.map +1 -1
- package/dist/src/symbols/output-symbol.js +77 -7
- package/dist/src/symbols/output-symbol.js.map +1 -1
- package/dist/src/symbols/symbol-table.d.ts.map +1 -1
- package/dist/src/symbols/symbol-table.js +13 -3
- package/dist/src/symbols/symbol-table.js.map +1 -1
- package/dist/test/symbols/deconflicted-name.test.d.ts +2 -0
- package/dist/test/symbols/deconflicted-name.test.d.ts.map +1 -0
- package/dist/test/symbols/deconflicted-name.test.js +120 -0
- package/dist/test/symbols/deconflicted-name.test.js.map +1 -0
- package/dist/test/symbols/output-scope.test.js +41 -0
- package/dist/test/symbols/output-scope.test.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/docs/api/types/OutputSymbol.md +42 -40
- package/package.json +1 -1
- package/src/debug/diagnostics-broadcast.test.tsx +98 -0
- package/src/debug/trace-writer.ts +18 -3
- package/src/diagnostics.ts +44 -31
- package/src/symbols/output-symbol.ts +92 -10
- package/src/symbols/symbol-table.ts +13 -3
- package/temp/api.json +61 -1
- package/test/symbols/deconflicted-name.test.ts +120 -0
- package/test/symbols/output-scope.test.ts +38 -0
package/temp/api.json
CHANGED
|
@@ -17645,6 +17645,36 @@
|
|
|
17645
17645
|
"isProtected": false,
|
|
17646
17646
|
"isAbstract": false
|
|
17647
17647
|
},
|
|
17648
|
+
{
|
|
17649
|
+
"kind": "Property",
|
|
17650
|
+
"canonicalReference": "@alloy-js/core!OutputSymbol#canonicalName:member",
|
|
17651
|
+
"docComment": "/**\n * The canonical requested name for this symbol: the result of applying the\n * symbol's name policy to its {@link OutputSymbol.originalName | originalName}, or the original name\n * itself when no policy applies. This is the name the symbol would carry if\n * there were no conflicts, and is stable across the symbol's lifetime (it\n * depends only on the immutable `originalName` and the name policy).\n *\n * Used by {@link SymbolTable} as the grouping key for name-conflict\n * resolution, so that symbols whose original names normalize to the same\n * policy-applied name (e.g. `foo_bar` and `fooBar` under camelCase) are\n * recognized as conflicting.\n */\n",
|
|
17652
|
+
"excerptTokens": [
|
|
17653
|
+
{
|
|
17654
|
+
"kind": "Content",
|
|
17655
|
+
"text": "get canonicalName(): "
|
|
17656
|
+
},
|
|
17657
|
+
{
|
|
17658
|
+
"kind": "Content",
|
|
17659
|
+
"text": "string"
|
|
17660
|
+
},
|
|
17661
|
+
{
|
|
17662
|
+
"kind": "Content",
|
|
17663
|
+
"text": ";"
|
|
17664
|
+
}
|
|
17665
|
+
],
|
|
17666
|
+
"isReadonly": true,
|
|
17667
|
+
"isOptional": false,
|
|
17668
|
+
"releaseTag": "Public",
|
|
17669
|
+
"name": "canonicalName",
|
|
17670
|
+
"propertyTypeTokenRange": {
|
|
17671
|
+
"startIndex": 1,
|
|
17672
|
+
"endIndex": 2
|
|
17673
|
+
},
|
|
17674
|
+
"isStatic": false,
|
|
17675
|
+
"isProtected": false,
|
|
17676
|
+
"isAbstract": false
|
|
17677
|
+
},
|
|
17648
17678
|
{
|
|
17649
17679
|
"kind": "Method",
|
|
17650
17680
|
"canonicalReference": "@alloy-js/core!OutputSymbol#copy:member(1)",
|
|
@@ -17843,6 +17873,36 @@
|
|
|
17843
17873
|
"isProtected": false,
|
|
17844
17874
|
"isAbstract": false
|
|
17845
17875
|
},
|
|
17876
|
+
{
|
|
17877
|
+
"kind": "Property",
|
|
17878
|
+
"canonicalReference": "@alloy-js/core!OutputSymbol#deconflictedName:member",
|
|
17879
|
+
"docComment": "/**\n * The name assigned by a name-conflict resolver, or `undefined` when the\n * symbol is not currently renamed by conflict resolution.\n *\n * Resolvers should assign to this slot (rather than `name`) to record that a\n * rename exists only because of a conflict. On re-deconfliction (e.g. after\n * a conflicting symbol is removed), resolvers clear this slot by assigning\n * `undefined`; the effective {@link OutputSymbol.name | name} then falls back to the\n * user-assigned name, which in turn falls back to the original name.\n *\n * Name policy is applied to values written here (unless `ignoreNamePolicy`\n * is true), matching `name`'s behavior.\n *\n *\n * @reactive\n */\n",
|
|
17880
|
+
"excerptTokens": [
|
|
17881
|
+
{
|
|
17882
|
+
"kind": "Content",
|
|
17883
|
+
"text": "get deconflictedName(): "
|
|
17884
|
+
},
|
|
17885
|
+
{
|
|
17886
|
+
"kind": "Content",
|
|
17887
|
+
"text": "string | undefined"
|
|
17888
|
+
},
|
|
17889
|
+
{
|
|
17890
|
+
"kind": "Content",
|
|
17891
|
+
"text": ";\n\nset deconflictedName(value: string | undefined);"
|
|
17892
|
+
}
|
|
17893
|
+
],
|
|
17894
|
+
"isReadonly": false,
|
|
17895
|
+
"isOptional": false,
|
|
17896
|
+
"releaseTag": "Public",
|
|
17897
|
+
"name": "deconflictedName",
|
|
17898
|
+
"propertyTypeTokenRange": {
|
|
17899
|
+
"startIndex": 1,
|
|
17900
|
+
"endIndex": 2
|
|
17901
|
+
},
|
|
17902
|
+
"isStatic": false,
|
|
17903
|
+
"isProtected": false,
|
|
17904
|
+
"isAbstract": false
|
|
17905
|
+
},
|
|
17846
17906
|
{
|
|
17847
17907
|
"kind": "Method",
|
|
17848
17908
|
"canonicalReference": "@alloy-js/core!OutputSymbol#delete:member(1)",
|
|
@@ -18496,7 +18556,7 @@
|
|
|
18496
18556
|
{
|
|
18497
18557
|
"kind": "Property",
|
|
18498
18558
|
"canonicalReference": "@alloy-js/core!OutputSymbol#name:member",
|
|
18499
|
-
"docComment": "/**\n * The name of this symbol. Assigning to this property applies the active\n * name policy (unless `ignoreNamePolicy` is true) before storing the value.\n *\n *\n * @reactive\n */\n",
|
|
18559
|
+
"docComment": "/**\n * The name of this symbol. Assigning to this property applies the active\n * name policy (unless `ignoreNamePolicy` is true) before storing the value.\n *\n * The effective name is computed as `deconflictedName ?? userName`, so if a\n * name-conflict resolver has assigned a {@link OutputSymbol.deconflictedName | deconflictedName}, that value\n * is returned here; otherwise the value most recently assigned to `name` is\n * returned.\n *\n *\n * @reactive\n */\n",
|
|
18500
18560
|
"excerptTokens": [
|
|
18501
18561
|
{
|
|
18502
18562
|
"kind": "Content",
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { Binder, createOutputBinder } from "../../src/binder.js";
|
|
3
|
+
import { flushJobs } from "../../src/scheduler.js";
|
|
4
|
+
import { BasicScope } from "../../src/symbols/basic-scope.js";
|
|
5
|
+
import { BasicSymbol } from "../../src/symbols/basic-symbol.js";
|
|
6
|
+
import type { OutputSymbol } from "../../src/symbols/output-symbol.js";
|
|
7
|
+
|
|
8
|
+
function setup(
|
|
9
|
+
nameConflictResolver?: (name: string, syms: OutputSymbol[]) => void,
|
|
10
|
+
) {
|
|
11
|
+
const binder: Binder = createOutputBinder({ nameConflictResolver });
|
|
12
|
+
const scope = new BasicScope("root", undefined, { binder });
|
|
13
|
+
return { binder, scope };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
describe("deconflictedName", () => {
|
|
17
|
+
it("default resolver renames losers via deconflictedName and reverts on removal", () => {
|
|
18
|
+
const { scope } = setup();
|
|
19
|
+
const a = new BasicSymbol("foo", scope.symbols, {});
|
|
20
|
+
const b = new BasicSymbol("foo", scope.symbols, {});
|
|
21
|
+
flushJobs();
|
|
22
|
+
expect(a.name).toBe("foo");
|
|
23
|
+
expect(b.name).toBe("foo_2");
|
|
24
|
+
expect(a.deconflictedName).toBeUndefined();
|
|
25
|
+
expect(b.deconflictedName).toBe("foo_2");
|
|
26
|
+
|
|
27
|
+
b.delete();
|
|
28
|
+
flushJobs();
|
|
29
|
+
expect(a.name).toBe("foo");
|
|
30
|
+
expect(a.deconflictedName).toBeUndefined();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("works with a custom rename scheme that doesn't match the default _N pattern", () => {
|
|
34
|
+
// Resolver uses a `$foo` prefix instead of `foo_N` suffix — the old
|
|
35
|
+
// isAutoAlias regex would never match this.
|
|
36
|
+
const customResolver = (_: string, symbols: OutputSymbol[]) => {
|
|
37
|
+
if (symbols.length === 0) return;
|
|
38
|
+
symbols[0].deconflictedName = undefined;
|
|
39
|
+
for (let i = 1; i < symbols.length; i++) {
|
|
40
|
+
symbols[i].deconflictedName = "$" + symbols[i].originalName;
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
const { scope } = setup(customResolver);
|
|
44
|
+
|
|
45
|
+
const a = new BasicSymbol("foo", scope.symbols, {});
|
|
46
|
+
const b = new BasicSymbol("foo", scope.symbols, {});
|
|
47
|
+
flushJobs();
|
|
48
|
+
expect(a.name).toBe("foo");
|
|
49
|
+
expect(b.name).toBe("$foo");
|
|
50
|
+
|
|
51
|
+
b.delete();
|
|
52
|
+
flushJobs();
|
|
53
|
+
// Survivor reverts to the original name regardless of scheme.
|
|
54
|
+
expect(a.name).toBe("foo");
|
|
55
|
+
expect(a.deconflictedName).toBeUndefined();
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("direct .name assignment does not clear an active deconflictedName", () => {
|
|
59
|
+
const { scope } = setup();
|
|
60
|
+
const a = new BasicSymbol("foo", scope.symbols, {});
|
|
61
|
+
const b = new BasicSymbol("foo", scope.symbols, {});
|
|
62
|
+
flushJobs();
|
|
63
|
+
expect(b.name).toBe("foo_2");
|
|
64
|
+
|
|
65
|
+
// User-assigns the name while a conflict rename is active.
|
|
66
|
+
b.name = "bar";
|
|
67
|
+
// Effective name still shows the deconflict rename (resolver wins while active).
|
|
68
|
+
expect(b.name).toBe("foo_2");
|
|
69
|
+
|
|
70
|
+
// Once the collision clears, the user-assigned name takes effect.
|
|
71
|
+
a.delete();
|
|
72
|
+
flushJobs();
|
|
73
|
+
expect(b.deconflictedName).toBeUndefined();
|
|
74
|
+
expect(b.name).toBe("bar");
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("detects conflict when different original names normalize to the same policy name", () => {
|
|
78
|
+
// Mirrors PR #394: under camelCase, `foo_bar` and `fooBar` both become
|
|
79
|
+
// `fooBar` — they collide on the rendered name and should be deconflicted.
|
|
80
|
+
const toCamel = (n: string) =>
|
|
81
|
+
n.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
|
|
82
|
+
const { scope } = setup();
|
|
83
|
+
|
|
84
|
+
const a = new BasicSymbol("foo_bar", scope.symbols, {
|
|
85
|
+
namePolicy: toCamel,
|
|
86
|
+
});
|
|
87
|
+
const b = new BasicSymbol("fooBar", scope.symbols, {
|
|
88
|
+
namePolicy: toCamel,
|
|
89
|
+
});
|
|
90
|
+
flushJobs();
|
|
91
|
+
|
|
92
|
+
expect(a.name).toBe("fooBar");
|
|
93
|
+
// Without canonical-name grouping, `b` would keep its original `fooBar`
|
|
94
|
+
// and silently collide with `a`'s policy-applied name.
|
|
95
|
+
expect(b.name).not.toBe("fooBar");
|
|
96
|
+
expect(b.name).toMatch(/^fooBar/);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it("survivor reverts when the winner is removed (canonical-name cohort lookup)", () => {
|
|
100
|
+
// After `a` is removed, the delete hook must find `b` as a member of the
|
|
101
|
+
// same canonical-name cohort so it can clear its deconflictedName.
|
|
102
|
+
const toCamel = (n: string) =>
|
|
103
|
+
n.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
|
|
104
|
+
const { scope } = setup();
|
|
105
|
+
|
|
106
|
+
const a = new BasicSymbol("foo_bar", scope.symbols, {
|
|
107
|
+
namePolicy: toCamel,
|
|
108
|
+
});
|
|
109
|
+
const b = new BasicSymbol("fooBar", scope.symbols, {
|
|
110
|
+
namePolicy: toCamel,
|
|
111
|
+
});
|
|
112
|
+
flushJobs();
|
|
113
|
+
expect(b.deconflictedName).toBeDefined();
|
|
114
|
+
|
|
115
|
+
a.delete();
|
|
116
|
+
flushJobs();
|
|
117
|
+
expect(b.deconflictedName).toBeUndefined();
|
|
118
|
+
expect(b.name).toBe("fooBar");
|
|
119
|
+
});
|
|
120
|
+
});
|
|
@@ -206,3 +206,41 @@ describe("OutputScope#children", () => {
|
|
|
206
206
|
expect(child2.parent).toBe(parentScope);
|
|
207
207
|
});
|
|
208
208
|
});
|
|
209
|
+
|
|
210
|
+
describe("symbol name deduplication with name policy", () => {
|
|
211
|
+
it("deduplicates symbols whose names collide after name policy", () => {
|
|
212
|
+
const scope = createScope("scope");
|
|
213
|
+
const policy = (name: string) => name.toUpperCase();
|
|
214
|
+
const [s1] = createSymbol("foo", scope, { namePolicy: policy });
|
|
215
|
+
const [s2] = createSymbol("FOO", scope, { namePolicy: policy });
|
|
216
|
+
|
|
217
|
+
flushJobs();
|
|
218
|
+
|
|
219
|
+
expect(s1.name).toBe("FOO");
|
|
220
|
+
expect(s2.name).toBe("FOO_2");
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it("deduplicates symbols when policy adds a prefix", () => {
|
|
224
|
+
const scope = createScope("scope");
|
|
225
|
+
const policy = (name: string) => (name === "reserved" ? `@${name}` : name);
|
|
226
|
+
const [s1] = createSymbol("reserved", scope, { namePolicy: policy });
|
|
227
|
+
const [s2] = createSymbol("reserved", scope, { namePolicy: policy });
|
|
228
|
+
|
|
229
|
+
flushJobs();
|
|
230
|
+
|
|
231
|
+
expect(s1.name).toBe("@reserved");
|
|
232
|
+
expect(s2.name).toBe("reserved_2");
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
it("does not deduplicates symbols with different names after policy", () => {
|
|
236
|
+
const scope = createScope("scope");
|
|
237
|
+
const policy = (name: string) => `prefix_${name}`;
|
|
238
|
+
const [s1] = createSymbol("foo", scope, { namePolicy: policy });
|
|
239
|
+
const [s2] = createSymbol("bar", scope, { namePolicy: policy });
|
|
240
|
+
|
|
241
|
+
flushJobs();
|
|
242
|
+
|
|
243
|
+
expect(s1.name).toBe("prefix_foo");
|
|
244
|
+
expect(s2.name).toBe("prefix_bar");
|
|
245
|
+
});
|
|
246
|
+
});
|