@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
|
@@ -4,46 +4,48 @@ An output symbol is a named entity that can be referenced in your output code.
|
|
|
4
4
|
|
|
5
5
|
## Members
|
|
6
6
|
|
|
7
|
-
| | |
|
|
8
|
-
| ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------- |
|
|
9
|
-
| \_\_v\_skip | boolean |
|
|
10
|
-
| \[inspect.custom] | () => string |
|
|
11
|
-
| constructor | (name: string \| Namekey, spaces: OutputSpace\[] \| OutputSpace \| undefined, options: OutputSymbolOptions) | Constructs a new instance of the [`OutputSymbol`](OutputSymbol.md) class
|
|
12
|
-
| aliasTarget | [OutputSymbol](../outputsymbol/) \| undefined | The symbol that this symbol is an alias for.
|
|
13
|
-
| binder | [Binder](../binder/) \| undefined | The binder that is tracking this symbol.
|
|
14
|
-
|
|
|
15
|
-
|
|
|
16
|
-
|
|
|
17
|
-
|
|
|
18
|
-
|
|
|
19
|
-
|
|
|
20
|
-
|
|
|
21
|
-
|
|
|
22
|
-
|
|
|
23
|
-
|
|
|
24
|
-
|
|
|
25
|
-
|
|
|
26
|
-
|
|
|
27
|
-
|
|
|
28
|
-
|
|
|
29
|
-
|
|
|
30
|
-
|
|
|
31
|
-
|
|
|
32
|
-
|
|
|
33
|
-
|
|
|
34
|
-
|
|
|
35
|
-
|
|
|
36
|
-
|
|
|
37
|
-
|
|
|
38
|
-
|
|
|
39
|
-
|
|
|
40
|
-
|
|
|
41
|
-
|
|
|
42
|
-
|
|
|
43
|
-
|
|
|
44
|
-
|
|
|
45
|
-
|
|
|
46
|
-
|
|
|
7
|
+
| | | |
|
|
8
|
+
| ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
9
|
+
| \_\_v\_skip | boolean | |
|
|
10
|
+
| \[inspect.custom] | () => string | |
|
|
11
|
+
| constructor | (name: string \| Namekey, spaces: OutputSpace\[] \| OutputSpace \| undefined, options: OutputSymbolOptions) | Constructs a new instance of the [`OutputSymbol`](OutputSymbol.md) class |
|
|
12
|
+
| aliasTarget | [OutputSymbol](../outputsymbol/) \| undefined | The symbol that this symbol is an alias for. |
|
|
13
|
+
| binder | [Binder](../binder/) \| undefined | The binder that is tracking this symbol. |
|
|
14
|
+
| canonicalName | string | The canonical requested name for this symbol: the result of applying the symbol’s name policy to its originalName, or the original name itself when no policy applies. This is the name the symbol would carry if there were no conflicts, and is stable across the symbol’s lifetime (it depends only on the immutable `originalName` and the name policy). Used by [SymbolTable](../symboltable/) as the grouping key for name-conflict resolution, so that symbols whose original names normalize to the same policy-applied name (e.g. `foo_bar` and `fooBar` under camelCase) are recognized as conflicting. |
|
|
15
|
+
| copy | () => OutputSymbol | Create a clone of this symbol whose name and flags reactively track the original. |
|
|
16
|
+
| copyMembersTo | (targetSymbol: OutputSymbol) => void | Copy the members of this symbol to the target symbol. This is reactive - whenever a member is added to this symbol, it will be copied to the target symbol. |
|
|
17
|
+
| copyToSpace | (space: OutputSpace) => OutputSymbol | Copy this symbol into the given space. Calls \[unresolved link] and places the result in `space`, then returns the copy. |
|
|
18
|
+
| dealias | () => OutputSymbol | If this symbol is an alias for another symbol, return the the aliased symbol. Otherwise, return this symbol. |
|
|
19
|
+
| debugInfo | Record\<string, unknown> | |
|
|
20
|
+
| deconflictedName | string \| undefined | The name assigned by a name-conflict resolver, or `undefined` when the symbol is not currently renamed by conflict resolution. Resolvers should assign to this slot (rather than `name`) to record that a rename exists only because of a conflict. On re-deconfliction (e.g. after a conflicting symbol is removed), resolvers clear this slot by assigning `undefined`; the effective name then falls back to the user-assigned name, which in turn falls back to the original name. Name policy is applied to values written here (unless `ignoreNamePolicy` is true), matching `name`‘s behavior. |
|
|
21
|
+
| delete | () => void | |
|
|
22
|
+
| protected getCopyOptions | () => { binder: Binder \| undefined; aliasTarget: OutputSymbol \| undefined; metadata: Record\<string, unknown>; transient: boolean; } | |
|
|
23
|
+
| hasTypeSymbol | boolean | Whether this symbol has its symbol representing its type available. |
|
|
24
|
+
| id | number | The unique id of this symbol. |
|
|
25
|
+
| ignoreNameConflict | boolean | Whether the name of this symbol bypasses the active name conflict resolution. When true, the name of this symbol will be fixed, though it may conflict with other symbols which are also ignoring name conflict resolution. |
|
|
26
|
+
| ignoreNamePolicy | boolean | Whether the name of this symbol bypasses the active name policy. When true, the name of this symbol will be fixed, though it may conflict with other symbols which are also ignoring the name policy. |
|
|
27
|
+
| protected initializeCopy | (copy: OutputSymbol) => void | Wires up reactive member-space copying and name tracking from this symbol to its `copy`. |
|
|
28
|
+
| isAlias | boolean | Whether this symbol is an alias for another symbol. |
|
|
29
|
+
| isMemberSymbol | boolean | Whether this symbol is a member of another symbol. |
|
|
30
|
+
| isMoved | boolean | Whether this symbol’s members have been moved to another symbol. |
|
|
31
|
+
| isTransient | boolean | Whether this symbol is a transient symbol. Transient symbols cannot be referenced and are meant to be combined with other symbols. |
|
|
32
|
+
| isTyped | boolean | Whether this symbol’s members are provided by a type symbol. The `typeSymbol` property is this symbol. It may not be available yet, so check `hasTypeSymbol`. |
|
|
33
|
+
| memberSpaceFor | (spaceKey: string) => OutputMemberSpace \| undefined | Get the member space for the given key. |
|
|
34
|
+
| memberSpaces | [OutputMemberSpace](../outputmemberspace/)\[] | The member spaces of this symbol. |
|
|
35
|
+
| memberSpaces | Readonly\<string\[]> | The member space keys for this symbol type. Subclasses override this to declare which member spaces are created on construction (e.g., `["static", "instance"]`). |
|
|
36
|
+
| metadata | Record\<string, unknown> | An arbitrary bag of metadata for this symbol. This property is read only, but the metadata is a reactive object. |
|
|
37
|
+
| movedTo | [OutputSymbol](../outputsymbol/) \| undefined | The symbol that this symbol’s members have been moved to. |
|
|
38
|
+
| moveMembersTo | (targetSymbol: OutputSymbol) => void | Move member symbols from this transient symbol to the target symbol. This is reactive - whenever a member is added to this symbol, it will be moved to the target symbol. |
|
|
39
|
+
| name | string | The name of this symbol. Assigning to this property applies the active name policy (unless `ignoreNamePolicy` is true) before storing the value. The effective name is computed as `deconflictedName ?? userName`, so if a name-conflict resolver has assigned a deconflictedName, that value is returned here; otherwise the value most recently assigned to `name` is returned. |
|
|
40
|
+
| namePolicy | [NamePolicyGetter](../namepolicygetter/) \| undefined | |
|
|
41
|
+
| originalName | string | Read only. The requested name of this symbol. The symbol’s actual name may be different depending on naming policy or conflicts with other symbols. |
|
|
42
|
+
| ownerSymbol | [OutputSymbol](../outputsymbol/) \| undefined | When this is a member symbol, this returns the symbol that this is symbol is a member of. |
|
|
43
|
+
| refkeys | [Refkey](../refkey/)\[] | The refkeys for this symbol. |
|
|
44
|
+
| resolveMemberByName | (name: string) => OutputSymbol \| undefined | Get a member symbol by name from this symbol’s member spaces. Checks member spaces in order until it finds a member with that name. |
|
|
45
|
+
| scope | import(”./output-scope.js”).[OutputScope](../outputscope/) \| undefined | The scope this symbol is in. When this symbol is a member symbol, this will return undefined. |
|
|
46
|
+
| spaces | [OutputSpace](../outputspace/)\[] | The declaration or member spaces this symbol belongs to. |
|
|
47
|
+
| toString | () => string | |
|
|
48
|
+
| type | [OutputSymbol](../outputsymbol/) \| undefined | The symbol which defines the type of this symbol. The type symbol provides information about the value this symbol contains, such as what members it has. |
|
|
47
49
|
|
|
48
50
|
## Remarks
|
|
49
51
|
|
package/package.json
CHANGED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { mkdtempSync, rmSync } from "node:fs";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { afterEach, beforeEach, expect, it } from "vitest";
|
|
5
|
+
import { DiagnosticsCollector } from "../diagnostics.js";
|
|
6
|
+
import { closeTrace, initTrace, setChangeListener } from "./trace-writer.js";
|
|
7
|
+
|
|
8
|
+
let tmpDir: string;
|
|
9
|
+
|
|
10
|
+
beforeEach(async () => {
|
|
11
|
+
tmpDir = mkdtempSync(join(tmpdir(), "alloy-diag-test-"));
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
afterEach(async () => {
|
|
15
|
+
setChangeListener(null);
|
|
16
|
+
closeTrace();
|
|
17
|
+
rmSync(tmpDir, { recursive: true, force: true });
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("does not call insertDiagnostic when tracing is disabled", () => {
|
|
21
|
+
let events = 0;
|
|
22
|
+
setChangeListener(() => {
|
|
23
|
+
events++;
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const collector = new DiagnosticsCollector();
|
|
27
|
+
for (let i = 0; i < 100; i++) {
|
|
28
|
+
collector.emit({ message: `diag ${i}`, severity: "warning" });
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// No trace DB => no change events should fire.
|
|
32
|
+
expect(events).toBe(0);
|
|
33
|
+
expect(collector.getDiagnostics()).toHaveLength(100);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("emits one added event per diagnostic (linear, not quadratic)", async () => {
|
|
37
|
+
await initTrace(join(tmpDir, "trace.db"));
|
|
38
|
+
const events: Array<{ channel: string; action: string }> = [];
|
|
39
|
+
setChangeListener((event) => {
|
|
40
|
+
events.push({ channel: event.channel, action: event.action });
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const collector = new DiagnosticsCollector();
|
|
44
|
+
const N = 50;
|
|
45
|
+
for (let i = 0; i < N; i++) {
|
|
46
|
+
collector.emit({ message: `diag ${i}`, severity: "warning" });
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const added = events.filter(
|
|
50
|
+
(e) => e.channel === "diagnostics" && e.action === "added",
|
|
51
|
+
);
|
|
52
|
+
expect(added).toHaveLength(N); // was previously N*(N+1)/2 due to re-broadcast
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("emits one removed event per dismissal", async () => {
|
|
56
|
+
await initTrace(join(tmpDir, "trace.db"));
|
|
57
|
+
const events: Array<{ channel: string; action: string }> = [];
|
|
58
|
+
setChangeListener((event) => {
|
|
59
|
+
events.push({ channel: event.channel, action: event.action });
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
const collector = new DiagnosticsCollector();
|
|
63
|
+
const handles = [];
|
|
64
|
+
for (let i = 0; i < 10; i++) {
|
|
65
|
+
handles.push(collector.emit({ message: `diag ${i}`, severity: "warning" }));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
for (const h of handles) h.dismiss();
|
|
69
|
+
|
|
70
|
+
const added = events.filter(
|
|
71
|
+
(e) => e.channel === "diagnostics" && e.action === "added",
|
|
72
|
+
);
|
|
73
|
+
const removed = events.filter(
|
|
74
|
+
(e) => e.channel === "diagnostics" && e.action === "removed",
|
|
75
|
+
);
|
|
76
|
+
expect(added).toHaveLength(10);
|
|
77
|
+
expect(removed).toHaveLength(10);
|
|
78
|
+
expect(collector.getDiagnostics()).toHaveLength(0);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("dismiss is idempotent and only fires one removal", async () => {
|
|
82
|
+
await initTrace(join(tmpDir, "trace.db"));
|
|
83
|
+
const events: Array<{ channel: string; action: string }> = [];
|
|
84
|
+
setChangeListener((event) => {
|
|
85
|
+
events.push({ channel: event.channel, action: event.action });
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
const collector = new DiagnosticsCollector();
|
|
89
|
+
const h = collector.emit({ message: "once", severity: "warning" });
|
|
90
|
+
h.dismiss();
|
|
91
|
+
h.dismiss();
|
|
92
|
+
h.dismiss();
|
|
93
|
+
|
|
94
|
+
const removed = events.filter(
|
|
95
|
+
(e) => e.channel === "diagnostics" && e.action === "removed",
|
|
96
|
+
);
|
|
97
|
+
expect(removed).toHaveLength(1);
|
|
98
|
+
});
|
|
@@ -801,6 +801,8 @@ export function insertRenderError(
|
|
|
801
801
|
});
|
|
802
802
|
}
|
|
803
803
|
|
|
804
|
+
let stmtDeleteDiagnostic: StatementSync | undefined;
|
|
805
|
+
|
|
804
806
|
export function insertDiagnostic(
|
|
805
807
|
message: string,
|
|
806
808
|
severity: string | undefined,
|
|
@@ -808,10 +810,10 @@ export function insertDiagnostic(
|
|
|
808
810
|
sourceLine: number | undefined,
|
|
809
811
|
sourceCol: number | undefined,
|
|
810
812
|
componentStack: string | undefined,
|
|
811
|
-
):
|
|
812
|
-
if (!db) return;
|
|
813
|
+
): number | undefined {
|
|
814
|
+
if (!db) return undefined;
|
|
813
815
|
const s = nextSeq();
|
|
814
|
-
stmtInsertDiagnostic.run(
|
|
816
|
+
const info = stmtInsertDiagnostic.run(
|
|
815
817
|
message,
|
|
816
818
|
severity ?? null,
|
|
817
819
|
sourceFile ?? null,
|
|
@@ -820,7 +822,9 @@ export function insertDiagnostic(
|
|
|
820
822
|
componentStack ?? null,
|
|
821
823
|
s,
|
|
822
824
|
);
|
|
825
|
+
const id = Number(info.lastInsertRowid);
|
|
823
826
|
notifyChange("diagnostics", "added", {
|
|
827
|
+
id,
|
|
824
828
|
message,
|
|
825
829
|
severity: severity ?? null,
|
|
826
830
|
source_file: sourceFile ?? null,
|
|
@@ -829,6 +833,16 @@ export function insertDiagnostic(
|
|
|
829
833
|
component_stack: componentStack ?? null,
|
|
830
834
|
seq: s,
|
|
831
835
|
});
|
|
836
|
+
return id;
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
export function deleteDiagnostic(id: number): void {
|
|
840
|
+
if (!db) return;
|
|
841
|
+
if (!stmtDeleteDiagnostic) {
|
|
842
|
+
stmtDeleteDiagnostic = db.prepare(`DELETE FROM diagnostics WHERE id = ?`);
|
|
843
|
+
}
|
|
844
|
+
stmtDeleteDiagnostic.run(id);
|
|
845
|
+
notifyChange("diagnostics", "removed", { id, seq: nextSeq() });
|
|
832
846
|
}
|
|
833
847
|
|
|
834
848
|
/**
|
|
@@ -946,6 +960,7 @@ export function commitTransaction(): void {
|
|
|
946
960
|
export function closeTrace(): void {
|
|
947
961
|
db?.close();
|
|
948
962
|
db = null;
|
|
963
|
+
stmtDeleteDiagnostic = undefined;
|
|
949
964
|
}
|
|
950
965
|
|
|
951
966
|
export function resetTrace(): void {
|
package/src/diagnostics.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { getRenderNodeId } from "./debug/index.js";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
deleteDiagnostic,
|
|
4
|
+
insertDiagnostic,
|
|
5
|
+
isTraceEnabled,
|
|
6
|
+
} from "./debug/trace-writer.js";
|
|
3
7
|
import { getContext } from "./reactivity.js";
|
|
4
8
|
import { getRenderStackSnapshot } from "./render-stack.js";
|
|
5
9
|
import type { SourceLocation } from "./runtime/component.js";
|
|
@@ -31,22 +35,31 @@ export interface DiagnosticHandle {
|
|
|
31
35
|
dismiss(): void;
|
|
32
36
|
}
|
|
33
37
|
|
|
38
|
+
function buildComponentStack(): DiagnosticStackEntry[] {
|
|
39
|
+
return getRenderStackSnapshot().map((entry) => ({
|
|
40
|
+
name: entry.displayName,
|
|
41
|
+
renderNodeId:
|
|
42
|
+
entry.context?.meta?.renderNode ?
|
|
43
|
+
getRenderNodeId(entry.context.meta.renderNode)
|
|
44
|
+
: undefined,
|
|
45
|
+
source: entry.source,
|
|
46
|
+
}));
|
|
47
|
+
}
|
|
48
|
+
|
|
34
49
|
export class DiagnosticsCollector {
|
|
35
50
|
private entries = new Map<string, Diagnostic>();
|
|
36
51
|
private order: string[] = [];
|
|
52
|
+
private traceRowIds = new Map<string, number>();
|
|
37
53
|
|
|
38
54
|
emit(input: DiagnosticInput): DiagnosticHandle {
|
|
55
|
+
const traceEnabled = isTraceEnabled();
|
|
56
|
+
// Component stack is only required when a trace consumer will read it,
|
|
57
|
+
// or when the caller explicitly provided one. Building it walks the full
|
|
58
|
+
// render stack, which is expensive on hot paths.
|
|
39
59
|
const componentStack =
|
|
40
60
|
input.componentStack ??
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
renderNodeId:
|
|
44
|
-
entry.context?.meta?.renderNode ?
|
|
45
|
-
getRenderNodeId(entry.context.meta.renderNode)
|
|
46
|
-
: undefined,
|
|
47
|
-
source: entry.source,
|
|
48
|
-
}));
|
|
49
|
-
const source = input.source ?? componentStack.at(-1)?.source ?? undefined;
|
|
61
|
+
(traceEnabled ? buildComponentStack() : undefined);
|
|
62
|
+
const source = input.source ?? componentStack?.at(-1)?.source ?? undefined;
|
|
50
63
|
const id = `diag-${this.order.length + 1}-${Date.now()}`;
|
|
51
64
|
const diagnostic: Diagnostic = {
|
|
52
65
|
id,
|
|
@@ -58,14 +71,30 @@ export class DiagnosticsCollector {
|
|
|
58
71
|
this.entries.set(id, diagnostic);
|
|
59
72
|
this.order.push(id);
|
|
60
73
|
|
|
61
|
-
|
|
62
|
-
|
|
74
|
+
if (traceEnabled) {
|
|
75
|
+
const rowId = insertDiagnostic(
|
|
76
|
+
diagnostic.message,
|
|
77
|
+
diagnostic.severity,
|
|
78
|
+
diagnostic.source?.fileName,
|
|
79
|
+
diagnostic.source?.lineNumber,
|
|
80
|
+
diagnostic.source?.columnNumber,
|
|
81
|
+
diagnostic.componentStack ?
|
|
82
|
+
JSON.stringify(diagnostic.componentStack)
|
|
83
|
+
: undefined,
|
|
84
|
+
);
|
|
85
|
+
if (rowId !== undefined) {
|
|
86
|
+
this.traceRowIds.set(id, rowId);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
63
89
|
|
|
64
90
|
return {
|
|
65
91
|
dismiss: () => {
|
|
66
|
-
this.entries.delete(id);
|
|
67
|
-
|
|
68
|
-
|
|
92
|
+
if (!this.entries.delete(id)) return;
|
|
93
|
+
const rowId = this.traceRowIds.get(id);
|
|
94
|
+
if (rowId !== undefined) {
|
|
95
|
+
this.traceRowIds.delete(id);
|
|
96
|
+
if (isTraceEnabled()) deleteDiagnostic(rowId);
|
|
97
|
+
}
|
|
69
98
|
},
|
|
70
99
|
};
|
|
71
100
|
}
|
|
@@ -75,22 +104,6 @@ export class DiagnosticsCollector {
|
|
|
75
104
|
.map((id) => this.entries.get(id))
|
|
76
105
|
.filter((entry): entry is Diagnostic => Boolean(entry));
|
|
77
106
|
}
|
|
78
|
-
|
|
79
|
-
private broadcast() {
|
|
80
|
-
const diagnostics = this.getDiagnostics();
|
|
81
|
-
for (const diagnostic of diagnostics) {
|
|
82
|
-
insertDiagnostic(
|
|
83
|
-
diagnostic.message,
|
|
84
|
-
diagnostic.severity,
|
|
85
|
-
diagnostic.source?.fileName,
|
|
86
|
-
diagnostic.source?.lineNumber,
|
|
87
|
-
diagnostic.source?.columnNumber,
|
|
88
|
-
diagnostic.componentStack ?
|
|
89
|
-
JSON.stringify(diagnostic.componentStack)
|
|
90
|
-
: undefined,
|
|
91
|
-
);
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
107
|
}
|
|
95
108
|
|
|
96
109
|
const DIAGNOSTICS_META_KEY = "diagnostics";
|
|
@@ -178,31 +178,113 @@ export abstract class OutputSymbol {
|
|
|
178
178
|
return this.#originalName;
|
|
179
179
|
}
|
|
180
180
|
|
|
181
|
-
//
|
|
182
|
-
|
|
181
|
+
// The user-assigned name (as set by constructor or direct `.name =`
|
|
182
|
+
// assignments). Always defined after construction.
|
|
183
|
+
#userName!: string;
|
|
184
|
+
|
|
185
|
+
// The name assigned by a name-conflict resolver, if any. When present, this
|
|
186
|
+
// takes precedence over `#userName` in the computed `name` getter. Resolvers
|
|
187
|
+
// assign via the `deconflictedName` setter; clearing (setting to undefined)
|
|
188
|
+
// causes the symbol to fall back to its user-assigned name.
|
|
189
|
+
#deconflictedName: string | undefined;
|
|
190
|
+
|
|
183
191
|
/**
|
|
184
192
|
* The name of this symbol. Assigning to this property applies the active
|
|
185
193
|
* name policy (unless `ignoreNamePolicy` is true) before storing the value.
|
|
186
194
|
*
|
|
195
|
+
* The effective name is computed as `deconflictedName ?? userName`, so if a
|
|
196
|
+
* name-conflict resolver has assigned a {@link OutputSymbol.deconflictedName | deconflictedName}, that value
|
|
197
|
+
* is returned here; otherwise the value most recently assigned to `name` is
|
|
198
|
+
* returned.
|
|
199
|
+
*
|
|
187
200
|
* @reactive
|
|
188
201
|
*/
|
|
189
202
|
get name() {
|
|
190
203
|
track(this, TrackOpTypes.GET, "name");
|
|
191
|
-
return this.#
|
|
204
|
+
return this.#deconflictedName ?? this.#userName;
|
|
192
205
|
}
|
|
193
206
|
|
|
194
207
|
set name(name: string) {
|
|
195
|
-
const
|
|
208
|
+
const policyApplied =
|
|
209
|
+
this.#namePolicy && !this.#ignoreNamePolicy ?
|
|
210
|
+
this.#namePolicy(name)
|
|
211
|
+
: name;
|
|
196
212
|
|
|
197
|
-
if (
|
|
213
|
+
if (this.#userName === policyApplied) {
|
|
198
214
|
return;
|
|
199
215
|
}
|
|
200
216
|
|
|
201
|
-
this.#
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
217
|
+
const old = this.#deconflictedName ?? this.#userName;
|
|
218
|
+
this.#userName = policyApplied;
|
|
219
|
+
const next = this.#deconflictedName ?? this.#userName;
|
|
220
|
+
if (next !== old) {
|
|
221
|
+
trigger(this, TriggerOpTypes.SET, "name", next, old);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* The name assigned by a name-conflict resolver, or `undefined` when the
|
|
227
|
+
* symbol is not currently renamed by conflict resolution.
|
|
228
|
+
*
|
|
229
|
+
* Resolvers should assign to this slot (rather than `name`) to record that a
|
|
230
|
+
* rename exists only because of a conflict. On re-deconfliction (e.g. after
|
|
231
|
+
* a conflicting symbol is removed), resolvers clear this slot by assigning
|
|
232
|
+
* `undefined`; the effective {@link OutputSymbol.name | name} then falls back to the
|
|
233
|
+
* user-assigned name, which in turn falls back to the original name.
|
|
234
|
+
*
|
|
235
|
+
* Name policy is applied to values written here (unless `ignoreNamePolicy`
|
|
236
|
+
* is true), matching `name`'s behavior.
|
|
237
|
+
*
|
|
238
|
+
* @reactive
|
|
239
|
+
*/
|
|
240
|
+
get deconflictedName(): string | undefined {
|
|
241
|
+
track(this, TrackOpTypes.GET, "deconflictedName");
|
|
242
|
+
return this.#deconflictedName;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
set deconflictedName(value: string | undefined) {
|
|
246
|
+
const policyApplied =
|
|
247
|
+
value !== undefined && this.#namePolicy && !this.#ignoreNamePolicy ?
|
|
248
|
+
this.#namePolicy(value)
|
|
249
|
+
: value;
|
|
250
|
+
|
|
251
|
+
if (this.#deconflictedName === policyApplied) {
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const oldName = this.#deconflictedName ?? this.#userName;
|
|
256
|
+
const oldDeconflicted = this.#deconflictedName;
|
|
257
|
+
this.#deconflictedName = policyApplied;
|
|
258
|
+
trigger(
|
|
259
|
+
this,
|
|
260
|
+
TriggerOpTypes.SET,
|
|
261
|
+
"deconflictedName",
|
|
262
|
+
policyApplied,
|
|
263
|
+
oldDeconflicted,
|
|
264
|
+
);
|
|
265
|
+
const nextName = this.#deconflictedName ?? this.#userName;
|
|
266
|
+
if (nextName !== oldName) {
|
|
267
|
+
trigger(this, TriggerOpTypes.SET, "name", nextName, oldName);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* The canonical requested name for this symbol: the result of applying the
|
|
273
|
+
* symbol's name policy to its {@link OutputSymbol.originalName | originalName}, or the original name
|
|
274
|
+
* itself when no policy applies. This is the name the symbol would carry if
|
|
275
|
+
* there were no conflicts, and is stable across the symbol's lifetime (it
|
|
276
|
+
* depends only on the immutable `originalName` and the name policy).
|
|
277
|
+
*
|
|
278
|
+
* Used by {@link SymbolTable} as the grouping key for name-conflict
|
|
279
|
+
* resolution, so that symbols whose original names normalize to the same
|
|
280
|
+
* policy-applied name (e.g. `foo_bar` and `fooBar` under camelCase) are
|
|
281
|
+
* recognized as conflicting.
|
|
282
|
+
*/
|
|
283
|
+
get canonicalName(): string {
|
|
284
|
+
if (this.#ignoreNamePolicy || !this.#namePolicy) {
|
|
285
|
+
return this.originalName;
|
|
286
|
+
}
|
|
287
|
+
return this.#namePolicy(this.originalName);
|
|
206
288
|
}
|
|
207
289
|
|
|
208
290
|
#id: number;
|
|
@@ -13,7 +13,7 @@ export abstract class SymbolTable extends ReactiveUnionSet<OutputSymbol> {
|
|
|
13
13
|
#deconflictNames = () => {
|
|
14
14
|
for (const name of this.#namesToDeconflict) {
|
|
15
15
|
const conflictedSymbols = [...this].filter(
|
|
16
|
-
(sym) => sym.
|
|
16
|
+
(sym) => sym.canonicalName === name && !sym.ignoreNameConflict,
|
|
17
17
|
);
|
|
18
18
|
if (this.#nameConflictResolver) {
|
|
19
19
|
this.#nameConflictResolver(name, conflictedSymbols);
|
|
@@ -67,7 +67,7 @@ export abstract class SymbolTable extends ReactiveUnionSet<OutputSymbol> {
|
|
|
67
67
|
`${formatSymbolName(symbol)} added to ${formatSymbolTableName(this)}`,
|
|
68
68
|
);
|
|
69
69
|
|
|
70
|
-
this.#namesToDeconflict.add(symbol.
|
|
70
|
+
this.#namesToDeconflict.add(symbol.canonicalName);
|
|
71
71
|
|
|
72
72
|
queueJob(this.#deconflictNames);
|
|
73
73
|
|
|
@@ -79,6 +79,12 @@ export abstract class SymbolTable extends ReactiveUnionSet<OutputSymbol> {
|
|
|
79
79
|
() =>
|
|
80
80
|
`${formatSymbolName(symbol)} removed from ${formatSymbolTableName(this)}`,
|
|
81
81
|
);
|
|
82
|
+
// Re-run conflict resolution for the deleted symbol's canonical name
|
|
83
|
+
// so survivors in the same cohort (those that share the canonical
|
|
84
|
+
// name) can clear any prior `deconflictedName` rename now that the
|
|
85
|
+
// collision is reduced.
|
|
86
|
+
this.#namesToDeconflict.add(symbol.canonicalName);
|
|
87
|
+
queueJob(this.#deconflictNames);
|
|
82
88
|
},
|
|
83
89
|
});
|
|
84
90
|
|
|
@@ -135,8 +141,12 @@ export abstract class SymbolTable extends ReactiveUnionSet<OutputSymbol> {
|
|
|
135
141
|
* to have a suffix of _2, _3, etc.
|
|
136
142
|
*/
|
|
137
143
|
function defaultConflictHandler(_: string, conflictedSymbols: OutputSymbol[]) {
|
|
144
|
+
if (conflictedSymbols.length === 0) return;
|
|
145
|
+
// The first symbol keeps its original name; clear any prior deconflict
|
|
146
|
+
// rename so it reverts after a collision is resolved.
|
|
147
|
+
conflictedSymbols[0].deconflictedName = undefined;
|
|
138
148
|
for (let i = 1; i < conflictedSymbols.length; i++) {
|
|
139
|
-
conflictedSymbols[i].
|
|
149
|
+
conflictedSymbols[i].deconflictedName =
|
|
140
150
|
conflictedSymbols[i].originalName + "_" + (i + 1);
|
|
141
151
|
}
|
|
142
152
|
}
|