@kudzujs/core 0.8.36 → 0.8.37
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/MIGRATION_ROADMAP.md +1 -1
- package/PERFORMANCE.md +25 -0
- package/README.md +1 -1
- package/RELEASES.md +33 -0
- package/docs/next-architecture/README.md +1 -1
- package/docs/next-architecture/compiler-current-architecture.md +4 -4
- package/docs/next-architecture/large-application-ai-native-roadmap.md +4 -1
- package/docs/next-architecture/versioning.md +2 -1
- package/framework/compiler/analysis/binding-index.mjs +15 -1
- package/framework/compiler/analysis/component-analysis.mjs +8 -2
- package/framework/compiler/descriptor-session.mjs +72 -28
- package/framework/compiler/ir/module-ir.mjs +141 -30
- package/framework/compiler/source-compiler.mjs +108 -44
- package/package.json +1 -1
package/MIGRATION_ROADMAP.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
This document is the source of truth for Kudzu's product direction, architecture invariants, and future development order. Read it before extending React-shaped syntax or browser capabilities.
|
|
4
4
|
|
|
5
|
-
The executable post-`0.8.
|
|
5
|
+
The executable post-`0.8.37` compiler and large-application sequence is maintained in [`docs/next-architecture/large-application-ai-native-roadmap.md`](./docs/next-architecture/large-application-ai-native-roadmap.md). Follow its PR dependencies for implementation work; this document remains authoritative when selecting or accepting a migration capability.
|
|
6
6
|
|
|
7
7
|
[`GOAL_A.md`](./GOAL_A.md) and [`GOAL_B.md`](./GOAL_B.md) are completed capability-validation records. Their commerce and realtime dashboard fixtures prove general lifecycle, navigation, async-workflow, and Worker capabilities; they are not separate product verticals or future priority lists.
|
|
8
8
|
|
package/PERFORMANCE.md
CHANGED
|
@@ -2,6 +2,31 @@
|
|
|
2
2
|
|
|
3
3
|
Reproducibility classes: `npm run benchmark`, `npm run benchmark:keyed`, `npm run benchmark:native`, and `npm run benchmark:module-cache` are maintained in this repository; `npm run benchmark:commerce` is a maintained paired runner over the public external storefront; older excluded-workspace sections are historical provenance only and are not current framework rankings.
|
|
4
4
|
|
|
5
|
+
## P0.10 Structural ModuleIR References
|
|
6
|
+
|
|
7
|
+
Measured UTC 2026-08-11 on an Apple M4 macOS arm64 host with 10 logical CPUs, 16 GiB RAM, Node 24.14.0, and npm 11.9.0. The baseline was clean tag `v0.8.36` at `268cd9023c9f47a912601f298963a9ffe9c00da2`. The compiler and focused-check patch had SHA-256 `f684c79027b290bc8c7d549667ef0d7f21a9d0057b5d6291fcce18b91947eff2`, produced by:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
git diff --binary v0.8.36 -- framework/compiler/analysis/binding-index.mjs framework/compiler/analysis/component-analysis.mjs framework/compiler/descriptor-session.mjs framework/compiler/ir/module-ir.mjs framework/compiler/source-compiler.mjs test/compiler-passes.test.mjs test/framework.test.mjs | shasum -a 256
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
The maintained 100-importer fixture used one warm-up and seven alternating fresh-process samples. Both targets retain 103 parse misses, 103 export-summary misses, and 100 importer-local clones. ModuleIR and ComponentAnalysis v2 intentionally change the serialized compiler graph, so the previous equal-digest gate correctly rejects direct v1/v2 comparison: normalized result size changes from 382,603 B to 447,977 B and source-result size changes from 395,346 B to 460,720 B. The additional 65,374 B is deterministic structural slot, signal, symbol, and ownership metadata in compiler scratch; it is not deployed browser JavaScript.
|
|
14
|
+
|
|
15
|
+
| Target | Compiler median | Range | Peak RSS median | Source-result bytes |
|
|
16
|
+
|---|---:|---:|---:|---:|
|
|
17
|
+
| `v0.8.36` | 224.506 ms | 220.888-313.782 ms | 281.6 MiB | 395,346 B |
|
|
18
|
+
| `0.8.37` | 227.158 ms | 224.744-272.594 ms | 282.9 MiB | 460,720 B |
|
|
19
|
+
|
|
20
|
+
The candidate's unpaired median is 1.18% higher. Round-paired candidate-minus-baseline differences had a +6.270 ms median, with the candidate faster in two of seven pairs and the baseline faster in five. Timing ranges overlap, peak RSS differs by 1.3 MiB, and neither result crosses the 5% material-regression threshold.
|
|
21
|
+
|
|
22
|
+
```text
|
|
23
|
+
v0.8.36: [220.888,222.461,313.782,224.506,249.086,222.746,242.721]
|
|
24
|
+
candidate: [227.158,224.744,225.098,236.027,225.158,239.672,272.594]
|
|
25
|
+
paired candidate-baseline: [6.270,2.283,-88.684,11.521,-23.928,16.926,29.873]
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
ModuleIR and ComponentAnalysis are build-scratch contracts. Runtime-facing state names and export spellings remain only where generated module and browser ABIs require them. The standard suite verifies static zero-JavaScript output, capability exclusion, keyed identity, effect cleanup, Workers, navigation, and migration behavior; no browser runtime source was added for P0.10.
|
|
29
|
+
|
|
5
30
|
## P0.9 Semantic State Operations
|
|
6
31
|
|
|
7
32
|
Measured UTC 2026-08-11 on the Intel Core i5-9500 Linux x64 host with Node 24.14.0. The baseline was clean tag `v0.8.35` at `f25700d9d2b247c01db19f0e8c95f16cb1fa81a5`. The compiler and focused-check patch had SHA-256 `4c3c8a3de18b1e792ea84cf7608a89971850195036a58f37dd76e359bfc8a58d`, produced by:
|
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@ Kudzu compiles ordinary React-shaped TypeScript and TSX into complete static HTM
|
|
|
14
14
|
|
|
15
15
|
> Experimental `0.8.x`: the compiler API and supported TSX surface may change.
|
|
16
16
|
|
|
17
|
-
**Latest release: 0.8.
|
|
17
|
+
**Latest release: 0.8.37 - Structural ModuleIR references.** Compiler ownership and dependency edges now use validated source-local slots and stable symbols instead of formatted names. Read the [release notes](./RELEASES.md#0837---structural-moduleir-references), open the [release page](https://github.com/kudzujs/kudzu/releases/tag/v0.8.37), or follow the [architecture packet](./docs/next-architecture/README.md).
|
|
18
18
|
|
|
19
19
|
- [Documentation](https://kudzujs.cloud/docs)
|
|
20
20
|
- [Installation guide](https://kudzujs.cloud/docs#install)
|
package/RELEASES.md
CHANGED
|
@@ -1,5 +1,38 @@
|
|
|
1
1
|
# Kudzu Releases
|
|
2
2
|
|
|
3
|
+
## 0.8.37 - Structural ModuleIR references
|
|
4
|
+
|
|
5
|
+
Kudzu 0.8.37 completes P0.10 by replacing mixed state names, export strings, formatted owner keys, and partial slots with one validated structural reference graph.
|
|
6
|
+
|
|
7
|
+
### Changed in 0.8.37
|
|
8
|
+
|
|
9
|
+
- ModuleIR v2 assigns deterministic slots to symbols, signals, handlers, bindings, derived values, effects, keyed blocks, and imports.
|
|
10
|
+
- ComponentAnalysis v2 assigns deterministic owner, specialization, state, ref, and ID slots.
|
|
11
|
+
- StateRef, OwnerRef, source-local SymbolRef, and stable ModuleSymbol records connect state, capture, import, effect, collection, specialization, prop-signal, and row ownership edges.
|
|
12
|
+
- Descriptor finalization resolves effect handlers and imports structurally instead of searching by export spelling.
|
|
13
|
+
- Readable state and export names remain only as codegen, runtime ABI, and diagnostic metadata.
|
|
14
|
+
- One fail-closed pre-codegen validator rejects unsupported versions, malformed slots, duplicate exports, invalid effect handlers, broken keyed parent-child reciprocity, ownership cycles, and dangling specialization/ref edges.
|
|
15
|
+
|
|
16
|
+
### Performance
|
|
17
|
+
|
|
18
|
+
- The maintained 100-importer fixture retains 103 parse misses, 103 export-summary misses, and 100 importer-local clones.
|
|
19
|
+
- Seven alternating fresh-process samples measured `v0.8.36` and 0.8.37 compiler medians of 224.506 ms and 227.158 ms. The +6.270 ms paired median and overlapping timing/RSS ranges establish no material regression.
|
|
20
|
+
- Source-result size increases from 395,346 B to 460,720 B because ModuleIR and ComponentAnalysis now serialize structural symbol, signal, owner, and slot metadata. This is compiler scratch data, not deployed browser JavaScript.
|
|
21
|
+
- Existing static, command, binding, keyed, effect, Worker, navigation, and migration fixtures retain their browser behavior and zero-unused-runtime checks.
|
|
22
|
+
|
|
23
|
+
### Validation
|
|
24
|
+
|
|
25
|
+
- `npm run check`, `npm test`, and `npm run test:package` pass with all 198 tests and 152 generated pages.
|
|
26
|
+
- Focused checks cover unsupported versions, malformed references, duplicate exports, parent-child reciprocity, cycles, effect-handler roles, specialization ownership, and JSON round trips.
|
|
27
|
+
- Package smoke installation passes from the packed tarball.
|
|
28
|
+
- P0.11 explicit route artifact graph is next.
|
|
29
|
+
|
|
30
|
+
### Upgrade
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npm install @kudzujs/core@^0.8.37
|
|
34
|
+
```
|
|
35
|
+
|
|
3
36
|
## 0.8.36 - Semantic state operations
|
|
4
37
|
|
|
5
38
|
Kudzu 0.8.36 completes P0.9 by proving equivalent direct, aliased, and local-helper state updates and lowering them through the existing command HandlerIR path.
|
|
@@ -11,7 +11,7 @@ The top-level [`GOAL_A.md`](../../GOAL_A.md) and [`GOAL_B.md`](../../GOAL_B.md)
|
|
|
11
11
|
| C: state/resource model | Research only | Reduced fixtures expose a limitation |
|
|
12
12
|
| D: routing compatibility | Current behavior preserved | Revisit only with migration evidence and invariant review |
|
|
13
13
|
|
|
14
|
-
The active post-`0.8.
|
|
14
|
+
The active post-`0.8.37` implementation sequence is [`large-application-ai-native-roadmap.md`](./large-application-ai-native-roadmap.md). P0.10 ModuleIR reference unification is complete; P0.11 explicit route artifact graph is next. The plan orders compiler semantic generalization, large-application foundations, compatibility boundaries, AI tooling, and production validation without changing the invariants below.
|
|
15
15
|
|
|
16
16
|
## Required Invariants
|
|
17
17
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Current Compiler Architecture
|
|
2
2
|
|
|
3
|
-
This maps the current `0.8.
|
|
3
|
+
This maps the current `0.8.37` architecture, built on the completed `0.8.23` Goal A compiler foundation. File and function names are the stable references; line numbers are intentionally omitted because later work may still move code.
|
|
4
4
|
|
|
5
5
|
## Responsibility Map
|
|
6
6
|
|
|
@@ -17,8 +17,8 @@ This maps the current `0.8.36` architecture, built on the completed `0.8.23` Goa
|
|
|
17
17
|
| Source-local binding index | [`framework/compiler/analysis/binding-index.mjs`](../../framework/compiler/analysis/binding-index.mjs) | After normalization, assigns deterministic lexical slots and classifies local, parameter, import, capture, global, and unresolved references. Native handler, effect, binding, list evaluator, optimized-command, and effect-resource consumers use complete index-owned AST; synthesized expressions retain the existing fallback. |
|
|
18
18
|
| Pure collection language | [`framework/compiler/collection-analysis.mjs`](../../framework/compiler/collection-analysis.mjs) | Analyzes collection roots/selectors and serializes the allowed pure expression language used by lists and derived dependencies. |
|
|
19
19
|
| Main semantic analysis | [`framework/compiler/source-compiler.mjs`](../../framework/compiler/source-compiler.mjs), `createKudzuTransformer()` | Produces transformed source plus explicit component, handler, binding, derived, keyed, and effect ownership results. |
|
|
20
|
-
| Component ownership analysis | [`framework/compiler/analysis/component-analysis.mjs`](../../framework/compiler/analysis/component-analysis.mjs) |
|
|
21
|
-
| Per-source descriptor registration | [`framework/compiler/descriptor-session.mjs`](../../framework/compiler/descriptor-session.mjs), `createSemanticArtifact()`, `createDescriptorSession()` | Keeps AST descriptors private during analysis, then finalizes deterministic
|
|
20
|
+
| Component ownership analysis | [`framework/compiler/analysis/component-analysis.mjs`](../../framework/compiler/analysis/component-analysis.mjs) | Produces ComponentAnalysis v2 with ordered JSON-safe owner and specialization slots for state, setters, props, refs, IDs, direct SignalIR links, structural OwnerRefs, source-local SiteIds, and source provenance; AST identity remains private to its source-local session. |
|
|
21
|
+
| Per-source descriptor registration | [`framework/compiler/descriptor-session.mjs`](../../framework/compiler/descriptor-session.mjs), `createSemanticArtifact()`, `createDescriptorSession()` | Keeps AST descriptors private during analysis, then finalizes ModuleIR v2 with deterministic SymbolRef, SignalIR, HandlerIR, BindingIR, DerivedIR, EffectIR, KeyedBlockIR, and ImportIR slots. One fail-closed boundary validates every source-local and component ownership edge before build-module generation. |
|
|
22
22
|
| Command IR and codegen | [`framework/compiler/optimize/command-specialization.mjs`](../../framework/compiler/optimize/command-specialization.mjs), [`framework/compiler/ir/module-ir.mjs`](../../framework/compiler/ir/module-ir.mjs), [`framework/compiler/codegen/command-codegen.mjs`](../../framework/compiler/codegen/command-codegen.mjs) | Direct commands use the existing fast path; proven immutable state aliases and one-call local helpers specialize to the same JSON-safe command ModuleIR. Recursion, escape, mutation, and dynamic helper dispatch fail explicitly, while unrelated handlers retain native ESM. Codegen emits the existing `__kBehavior` AST and command ABI. |
|
|
23
23
|
| Source compilation | [`framework/compiler/source-compiler.mjs`](../../framework/compiler/source-compiler.mjs), `compileSource()` | Runs TypeScript with the Kudzu transformer, rejects surviving React/Router references, and returns a JSON-safe project-relative build module, component analysis, ModuleIR, optional handler module, and imported assets without filesystem writes. |
|
|
24
24
|
| Handler/evaluator lowering | [`framework/compiler/handler-lowering.mjs`](../../framework/compiler/handler-lowering.mjs) | Completes source-local callback/binding/list AST rewriting and diagnostics before the JSON-safe IR boundary. |
|
|
@@ -68,7 +68,7 @@ The browser consumes static HTML first. State seeds and descriptors in that HTML
|
|
|
68
68
|
## Residual Coupling
|
|
69
69
|
|
|
70
70
|
- `createKudzuTransformer()` remains one large source-local analysis unit combining validation, specialization, descriptor registration, and transformed-source emission.
|
|
71
|
-
- Transient component rewrite indexes remain source-local AST indexes; handler, binding, derived, keyed, effect, and component ownership
|
|
71
|
+
- Transient component rewrite indexes remain source-local AST indexes; finalized handler, binding, derived, keyed, effect, signal, import, and component ownership edges use explicit JSON-safe slots or stable symbols.
|
|
72
72
|
- `build()` still owns explicit artifact selection and filesystem writes after generator results are produced.
|
|
73
73
|
- Runtime generators intentionally specialize readable authored sources through exact anchors; every required anchor fails closed, but a future generator format may remove this transitional dependency.
|
|
74
74
|
- Source reachability and source compilation remain in one session-bound compiler factory because both consume the same normalization and import graph contracts. ModuleSymbol resolution is stable across canonical and cloned trees; unsupported export syntax remains intentionally narrow.
|
|
@@ -138,6 +138,7 @@ This is an incremental evolution of the current repository:
|
|
|
138
138
|
- [x] P0.7 Parsed module and export summary caching is complete in `0.8.34`. Canonical read-only source trees and narrow export summaries are invalidated together by source text and remain ProjectSession-local; every normalization context receives a deep clone with independent parent links. A 100-importer fixture parses and summarizes 103 unique page/barrel/component/helper modules exactly once and creates 200 importer-local clones. The maintained paired benchmark preserves the complete source-result digest and establishes no material timing or peak-RSS conclusion; all 193 tests and package checks pass without broadening exports, source syntax, or browser output.
|
|
139
139
|
- [x] P0.8 Stable ModuleSymbol and SiteId is complete in `0.8.35`. ProjectSession records source-local declaration, import, and re-export sites and resolves stable ModuleSymbol records through default/named exports, aliases, barrel chains, `export *`, ambiguity, and cycles. Cross-module compiler consumers locate resolved declarations by SiteId in their private normalized clones, while component calls, hooks, keyed blocks, effects, and ownership records expose deterministic source-local SiteIds. Repeated sessions and compilations preserve IDs, and symbol-only barrel traversal avoids cloning intermediate modules without changing browser artifacts or source syntax.
|
|
140
140
|
- [x] P0.9 Semantic State Operations is complete in `0.8.36`. Direct setters, one immutable state-value alias, one synchronous zero-argument arrow helper, and one synchronous one-parameter function helper lower to identical existing command HandlerIR. Binding identity proves state/setter/helper/parameter ownership; recursion, escape, mutation, and dynamic helper dispatch fail at authored source locations. Existing direct command specialization remains first, unrelated safe handlers retain native ESM, and no command ABI, runtime, JavaScript VM, or general expression IR is added.
|
|
141
|
+
- [x] P0.10 ModuleIR Reference Unification is complete in `0.8.37`. ModuleIR and ComponentAnalysis v2 assign deterministic slots to symbols, signals, handlers, bindings, derived values, effects, keyed blocks, imports, owners, specializations, states, refs, and IDs. State, capture, import, effect, collection, parent/child, specialization, and row edges use structural slots or ModuleSymbol records while readable names remain codegen/debug metadata. A fail-closed pre-codegen validator rejects malformed slots, unsupported versions, duplicate exports, broken reciprocity, and ownership cycles; focused JSON round-trip checks and all 198 tests pass without changing browser runtime behavior.
|
|
141
142
|
|
|
142
143
|
### P0: Semantic Correctness And Compiler Foundation
|
|
143
144
|
|
|
@@ -350,6 +351,8 @@ function increment(value) { setCount(value + 1) }; increment(count)
|
|
|
350
351
|
|
|
351
352
|
**Done condition:** every HandlerIR, BindingIR, DerivedIR, EffectIR, KeyedBlockIR, and component specialization edge validates before build-module generation.
|
|
352
353
|
|
|
354
|
+
**Completed in `0.8.37`:** ModuleIR and ComponentAnalysis v2 replace formatted owner keys and cross-record export/name lookups with source-local slots, StateRef/OwnerRef records, SymbolRef slots, and stable ModuleSymbol records. Descriptor finalization resolves imports, handlers, bindings, effects, derived dependencies, keyed collections, row ownership, and component prop signals before one fail-closed validation boundary. The validator checks slot/index integrity, state/setter ownership, captures/imports, duplicate exports, effect-handler roles, keyed parent/child reciprocity and cycles, specialization/ref edges, and JSON-safe round trips. Existing export names and state spellings remain only where emitted module/runtime ABIs require them.
|
|
355
|
+
|
|
353
356
|
### PR 11: RouteBuildRecord And Artifact Graph
|
|
354
357
|
|
|
355
358
|
**Objective:** make route artifact selection structural.
|
|
@@ -471,4 +474,4 @@ The first comparison is Kudzu versus React + Vite using the same agent, model, t
|
|
|
471
474
|
|
|
472
475
|
## Immediate Decision
|
|
473
476
|
|
|
474
|
-
PR 1 through PR
|
|
477
|
+
PR 1 through PR 10 are complete. The next PR is **PR 11: RouteBuildRecord And Artifact Graph**. Do not skip directly to a store, resource, router, virtualization, or ecosystem package feature.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Planned Version Sequence
|
|
2
2
|
|
|
3
|
-
This is an execution sequence, not release history. `0.8.16` through `0.8.
|
|
3
|
+
This is an execution sequence, not release history. `0.8.16` through `0.8.37` are completed scopes represented by package/release records.
|
|
4
4
|
|
|
5
5
|
Keep each patch behavior-preserving and independently reviewable. If a boundary proves inseparable, revise this plan before combining releases; do not silently broaden a patch.
|
|
6
6
|
|
|
@@ -27,6 +27,7 @@ Keep each patch behavior-preserving and independently reviewable. If a boundary
|
|
|
27
27
|
| `0.8.34` | Cache canonical parsed modules and narrow export summaries within one ProjectSession while cloning transformer input. | A 100-importer fixture parses and summarizes 103 unique modules once, creates independent normalization trees, preserves the complete source-result digest, and leaks no cache state across projects. |
|
|
28
28
|
| `0.8.35` | Resolve cross-module declarations through stable ModuleSymbol and source-local SiteId records. | Default/named exports, aliases, barrel chains, `export *`, cycles, ambiguity, repeated compilation, source invalidation, private clones, and measured build behavior remain deterministic. |
|
|
29
29
|
| `0.8.36` | Lower equivalent direct, aliased, and local-helper state updates through existing command HandlerIR. | The four required forms emit identical commands and no native handler module; recursion, escape, mutation, and dynamic dispatch fail at authored source locations. |
|
|
30
|
+
| `0.8.37` | Unify ModuleIR and ComponentAnalysis references through deterministic source-local slots and stable symbols. | Every handler, binding, derived, effect, keyed, specialization, state, ref, capture, and import edge validates before build-module generation. |
|
|
30
31
|
|
|
31
32
|
## Sequence Rules
|
|
32
33
|
|
|
@@ -8,6 +8,7 @@ export function createBindingIndex(sourceFile) {
|
|
|
8
8
|
const scopeByNode = new WeakMap()
|
|
9
9
|
const referenceBindings = new WeakMap()
|
|
10
10
|
const originalReferenceBindings = new WeakMap()
|
|
11
|
+
const declarationBindings = new WeakMap()
|
|
11
12
|
const bindings = []
|
|
12
13
|
const rootScope = createScope(undefined, sourceFile, "module")
|
|
13
14
|
const pseudoBindings = new Map()
|
|
@@ -31,6 +32,8 @@ export function createBindingIndex(sourceFile) {
|
|
|
31
32
|
bindings.push(binding)
|
|
32
33
|
scope.bindings.set(identifier.text, binding)
|
|
33
34
|
}
|
|
35
|
+
declarationBindings.set(identifier, binding)
|
|
36
|
+
declarationBindings.set(ts.getOriginalNode(identifier), binding)
|
|
34
37
|
return binding
|
|
35
38
|
}
|
|
36
39
|
|
|
@@ -218,6 +221,17 @@ export function createBindingIndex(sourceFile) {
|
|
|
218
221
|
}
|
|
219
222
|
}
|
|
220
223
|
|
|
224
|
+
function resolveBinding(identifier) {
|
|
225
|
+
const binding = declarationBindings.get(identifier) ?? declarationBindings.get(ts.getOriginalNode(identifier)) ?? lexicalBinding(scopeByNode.get(identifier) ?? rootScope, identifier.text)
|
|
226
|
+
if (!binding) return undefined
|
|
227
|
+
return {
|
|
228
|
+
slot: binding.slot,
|
|
229
|
+
debugName: binding.debugName,
|
|
230
|
+
declarationKind: binding.declarationKind,
|
|
231
|
+
...(binding.declaration ? { declarationRange: range(binding.declaration) } : {})
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
221
235
|
function references(root, boundary = root) {
|
|
222
236
|
const found = []
|
|
223
237
|
let complete = true
|
|
@@ -234,7 +248,7 @@ export function createBindingIndex(sourceFile) {
|
|
|
234
248
|
return complete ? found : undefined
|
|
235
249
|
}
|
|
236
250
|
|
|
237
|
-
return { bindings: () => bindings.map(({ declaration, ...binding }) => ({ ...binding, ...(declaration ? { declarationRange: range(declaration) } : {}) })), hasNode: node => scopeByNode.has(node), references, resolveReference }
|
|
251
|
+
return { bindings: () => bindings.map(({ declaration, ...binding }) => ({ ...binding, ...(declaration ? { declarationRange: range(declaration) } : {}) })), hasNode: node => scopeByNode.has(node), references, resolveBinding, resolveReference }
|
|
238
252
|
}
|
|
239
253
|
|
|
240
254
|
function inside(node, boundary) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export function createComponentAnalysis(file) {
|
|
2
|
-
return { version:
|
|
2
|
+
return { version: 2, file, owners: [], specializations: [] }
|
|
3
3
|
}
|
|
4
4
|
|
|
5
5
|
export function createComponentAnalysisSession(analysis) {
|
|
@@ -40,7 +40,13 @@ export function createComponentAnalysisSession(analysis) {
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
function registerSpecialization(descriptor) {
|
|
43
|
-
const specialization = {
|
|
43
|
+
const specialization = {
|
|
44
|
+
slot: analysis.specializations.length,
|
|
45
|
+
...descriptor,
|
|
46
|
+
states: (descriptor.states ?? []).map((state, slot) => ({ slot, ...state })),
|
|
47
|
+
refs: (descriptor.refs ?? []).map((ref, slot) => ({ slot, ...ref })),
|
|
48
|
+
ids: (descriptor.ids ?? []).map((id, slot) => ({ slot, ...id }))
|
|
49
|
+
}
|
|
44
50
|
analysis.specializations.push(specialization)
|
|
45
51
|
return specialization
|
|
46
52
|
}
|
|
@@ -3,23 +3,45 @@ import { createComponentAnalysis } from "./analysis/component-analysis.mjs"
|
|
|
3
3
|
import { knownGlobalNames } from "./analysis/binding-index.mjs"
|
|
4
4
|
import { bindingNames, isFunctionLike, isReferenceIdentifier, isShadowedByParameter, isShadowedIdentifier, unwrapExpression } from "./ast-helpers.mjs"
|
|
5
5
|
import { generateCommandBehavior } from "./codegen/command-codegen.mjs"
|
|
6
|
-
import { assertModuleIRReferences, createModuleIR, registerBinding, registerCommandHandler, registerDerived, registerEffect, registerKeyedBlock, registerModuleHandler } from "./ir/module-ir.mjs"
|
|
6
|
+
import { assertModuleIRReferences, createModuleIR, registerBinding, registerCommandHandler, registerDerived, registerEffect, registerKeyedBlock, registerModuleHandler, registerSignal } from "./ir/module-ir.mjs"
|
|
7
7
|
|
|
8
8
|
export function createSemanticArtifact(file) {
|
|
9
9
|
return { componentAnalysis: createComponentAnalysis(file), moduleIR: createModuleIR(file) }
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
export function createDescriptorSession({ semantic, handlerUrl, factory, context, bindingIndex, compileEventCommand, handlerLowering, isPrimitiveLiteral, rejectWorkerConstructions, sourceName = source => source.fileName }) {
|
|
12
|
+
export function createDescriptorSession({ semantic, handlerUrl, factory, context, bindingIndex, compileEventCommand, handlerLowering, isPrimitiveLiteral, rejectWorkerConstructions, stateReferences = () => new Map(), symbolReference, sourceName = source => source.fileName }) {
|
|
13
13
|
const { moduleIR } = semantic
|
|
14
|
+
moduleIR.symbols = bindingIndex.bindings()
|
|
14
15
|
const nativeHandlers = []
|
|
15
16
|
const effectHandlers = []
|
|
16
17
|
const reactiveBindings = []
|
|
17
18
|
const listExpressions = []
|
|
19
|
+
const pendingEffects = []
|
|
18
20
|
const clientModules = new Set()
|
|
19
21
|
|
|
22
|
+
const signal = (name, node, references, aliases = []) => {
|
|
23
|
+
let reference = references?.get(name) ?? stateReferences(node).get(name)
|
|
24
|
+
if (!reference && bindingIndex) {
|
|
25
|
+
const original = ts.getOriginalNode(node)
|
|
26
|
+
const names = new Set([name, ...aliases])
|
|
27
|
+
let symbol = bindingIndex.references(original, original)?.find(entry => names.has(entry.debugName))?.slot
|
|
28
|
+
if (symbol === undefined) {
|
|
29
|
+
for (let current = node; current && symbol === undefined; current = current.parent) if (isFunctionLike(current)) {
|
|
30
|
+
const parameter = current.parameters.map(parameter => bindingIdentifier(parameter.name, names)).find(Boolean)
|
|
31
|
+
symbol = parameter && bindingIndex.resolveBinding(parameter)?.slot
|
|
32
|
+
break
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
if (symbol !== undefined) reference = { kind: "symbol", symbol }
|
|
36
|
+
}
|
|
37
|
+
reference ??= symbolReference?.(name, node, aliases)
|
|
38
|
+
if (!reference) throw new Error(`ModuleIR state ${JSON.stringify(name)} has no resolved StateRef`)
|
|
39
|
+
return registerSignal(moduleIR, reference, name).slot
|
|
40
|
+
}
|
|
41
|
+
|
|
20
42
|
function compileListExpression(read, expression, item, index, states = new Set(), keyedBlock, indexedBindingIndex) {
|
|
21
43
|
const exportName = `listExpression${listExpressions.length}`
|
|
22
|
-
listExpressions.push({ exportName, expression, item, index, states, role: "list-expression", keyedBlock, bindingIndex: indexedBindingIndex })
|
|
44
|
+
listExpressions.push({ exportName, expression, item, index, states, signalRefs: new Map([...states].map(name => [name, signal(name, expression)])), role: "list-expression", keyedBlock, bindingIndex: indexedBindingIndex })
|
|
23
45
|
const arguments_ = [read, factory.createStringLiteral(handlerUrl), factory.createStringLiteral(exportName)]
|
|
24
46
|
if (states.size) arguments_.push(factory.createArrayLiteralExpression([...states].map(name => factory.createArrayLiteralExpression([factory.createStringLiteral(name), factory.createIdentifier(name)]))))
|
|
25
47
|
return factory.createCallExpression(factory.createIdentifier("__kListExpression"), undefined, arguments_)
|
|
@@ -28,7 +50,7 @@ export function createDescriptorSession({ semantic, handlerUrl, factory, context
|
|
|
28
50
|
function compileListConditional(entry) {
|
|
29
51
|
const exportName = `listExpression${listExpressions.length}`
|
|
30
52
|
const indexed = indexedReferences(bindingIndex, entry.condition, entry.condition)
|
|
31
|
-
listExpressions.push({ exportName, expression: entry.condition, item: entry.item, index: entry.index, role: "list-conditional", keyedBlock: entry.keyedBlock, bindingIndex: indexed ? bindingIndex : undefined })
|
|
53
|
+
listExpressions.push({ exportName, expression: entry.condition, item: entry.item, index: entry.index, states: new Set(), signalRefs: new Map(), role: "list-conditional", keyedBlock: entry.keyedBlock, bindingIndex: indexed ? bindingIndex : undefined })
|
|
32
54
|
const read = factory.createArrowFunction(undefined, undefined, [], undefined, factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), entry.condition)
|
|
33
55
|
const thunk = branch => factory.createArrowFunction(undefined, undefined, [], undefined, factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), branch)
|
|
34
56
|
return factory.createCallExpression(factory.createIdentifier("__kListConditional"), undefined, [
|
|
@@ -57,9 +79,10 @@ export function createDescriptorSession({ semantic, handlerUrl, factory, context
|
|
|
57
79
|
const parts = conditionalParts(expression)
|
|
58
80
|
const state = parts && directStateIdentifier(parts.condition, setters, bindingIndex)
|
|
59
81
|
if (state && isPrimitiveLiteral(parts.truthy) && isPrimitiveLiteral(parts.falsy)) {
|
|
60
|
-
return factory.createCallExpression(factory.createIdentifier("__kSelect"), undefined, [state, parts.truthy, parts.falsy])
|
|
82
|
+
return { node: factory.createCallExpression(factory.createIdentifier("__kSelect"), undefined, [state, parts.truthy, parts.falsy]) }
|
|
61
83
|
}
|
|
62
|
-
|
|
84
|
+
const binding = reactiveBindings.length
|
|
85
|
+
return { node: factory.createCallExpression(factory.createIdentifier("__kBinding"), undefined, compileReactiveExpression(expression, setters, importBindings, keyedBlock)), binding }
|
|
63
86
|
}
|
|
64
87
|
|
|
65
88
|
function compileConditional(kind, expression, truthy, falsy, setters) {
|
|
@@ -81,7 +104,7 @@ export function createDescriptorSession({ semantic, handlerUrl, factory, context
|
|
|
81
104
|
? new Set(indexed.filter(reference => ["capture", "unresolved"].includes(reference.kind) && !setters.has(reference.debugName) && !allStateNames.has(reference.debugName) && !importedNames.has(reference.debugName)).map(reference => reference.debugName))
|
|
82
105
|
: new Set([...captureNames(expression, expression, setters)].filter(name => !importedNames.has(name)))
|
|
83
106
|
const exportName = `binding${reactiveBindings.length}`
|
|
84
|
-
reactiveBindings.push({ exportName, expression, captures, states: usedStates, imports, role: "binding", keyedBlock, ...(indexed ? { bindingIndex } : {}) })
|
|
107
|
+
reactiveBindings.push({ slot: reactiveBindings.length, exportName, expression, captures, states: usedStates, signalRefs: new Map([...usedStates].map(name => [name, signal(name, expression)])), imports, role: "binding", keyedBlock, ...(indexed ? { bindingIndex } : {}) })
|
|
85
108
|
const states = [...usedStates].map(name => factory.createArrayLiteralExpression([factory.createStringLiteral(name), factory.createIdentifier(name)]))
|
|
86
109
|
const scope = [...captures].map(name => factory.createArrayLiteralExpression([factory.createStringLiteral(name), factory.createIdentifier(name)]))
|
|
87
110
|
const stateNames = new Set(usedStates)
|
|
@@ -107,7 +130,7 @@ export function createDescriptorSession({ semantic, handlerUrl, factory, context
|
|
|
107
130
|
const optimized = referencedReducerDispatches(expression.body, reducers, expression).size ? undefined : compileOptimizedEvent(expression, setters, stateOwners, owner, keyedBlock)
|
|
108
131
|
if (optimized) return optimized
|
|
109
132
|
rejectWorkerConstructions(expression)
|
|
110
|
-
const descriptor = compileNativeCallback(expression, { setters, reducers, entries: nativeHandlers, importBindings, prefix: "handler", role: "native", listItem, keyedBlock })
|
|
133
|
+
const descriptor = compileNativeCallback(expression, { setters, reducers, entries: nativeHandlers, importBindings, prefix: "handler", role: "native", listItem, keyedBlock, stateOwners })
|
|
111
134
|
return factory.createCallExpression(factory.createIdentifier("__kNativeBehavior"), undefined, [
|
|
112
135
|
factory.createStringLiteral(handlerUrl), factory.createStringLiteral(descriptor.exportName), descriptor.states, descriptor.scope
|
|
113
136
|
])
|
|
@@ -117,7 +140,7 @@ export function createDescriptorSession({ semantic, handlerUrl, factory, context
|
|
|
117
140
|
return compileNativeCallback(expression, { ...options, entries: effectHandlers, prefix: "effect", role: "effect" })
|
|
118
141
|
}
|
|
119
142
|
|
|
120
|
-
function compileNativeCallback(expression, { setters, reducers, entries, importBindings, prefix, role, listItem, keyedBlock, deferValues = false, snapshotNested = false, liveStates = new Set() }) {
|
|
143
|
+
function compileNativeCallback(expression, { setters, reducers, entries, importBindings, prefix, role, listItem, keyedBlock, stateOwners, deferValues = false, snapshotNested = false, liveStates = new Set() }) {
|
|
121
144
|
const indexedBindingIndex = indexedReferences(bindingIndex, expression, expression) ? bindingIndex : undefined
|
|
122
145
|
const allCaptures = nativeCaptureNames(expression, setters, indexedBindingIndex)
|
|
123
146
|
const usedReducers = referencedReducerDispatches(expression.body, reducers, expression, indexedBindingIndex)
|
|
@@ -131,11 +154,13 @@ export function createDescriptorSession({ semantic, handlerUrl, factory, context
|
|
|
131
154
|
if (reducer.contextAction) for (const state of referencedStateNames(reducer.contextAction.body, reducer.states, reducer.contextAction, bindingIndex)) usedStates.add(state)
|
|
132
155
|
}
|
|
133
156
|
const exportName = `${prefix}${entries.length}`
|
|
134
|
-
|
|
157
|
+
const entry = { exportName, expression, captures, deferValues, imports, listItem, keyedBlock, liveStates, role, setters: new Map([...setters].filter(([, state]) => usedStates.has(state))), reducers: new Map([...reducers].filter(([name]) => usedReducers.has(name))), snapshotNested, usedStates, signalRefs: new Map([...usedStates].map(name => [name, signal(name, expression, stateOwners, [...setters].filter(([, state]) => state === name).map(([setter]) => setter))])), bindingIndex: indexedBindingIndex }
|
|
158
|
+
entries.push(entry)
|
|
135
159
|
const value = name => deferValues
|
|
136
160
|
? factory.createArrowFunction(undefined, undefined, [], undefined, factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), factory.createIdentifier(name))
|
|
137
161
|
: factory.createIdentifier(name)
|
|
138
162
|
return {
|
|
163
|
+
entry,
|
|
139
164
|
exportName,
|
|
140
165
|
states: factory.createArrayLiteralExpression([...usedStates].map(name => factory.createArrayLiteralExpression([factory.createStringLiteral(name), value(name)]))),
|
|
141
166
|
scope: factory.createArrayLiteralExpression([...captures].map(name => factory.createArrayLiteralExpression([
|
|
@@ -154,7 +179,7 @@ export function createDescriptorSession({ semantic, handlerUrl, factory, context
|
|
|
154
179
|
if (!commands?.length || commands.some(command => !command)) return undefined
|
|
155
180
|
const original = ts.getOriginalNode(expression)
|
|
156
181
|
const source = original.pos >= 0 && original.end >= 0 ? { file: sourceName(original.getSourceFile()), start: original.getStart(), end: original.end } : undefined
|
|
157
|
-
const handler = registerCommandHandler(moduleIR, commands.map(command => ({ ...command,
|
|
182
|
+
const handler = registerCommandHandler(moduleIR, commands.map(command => ({ ...command, reference: stateOwners.get(command.state) ?? stateReferences(expression).get(command.state) })), source)
|
|
158
183
|
if (keyedBlock !== undefined) handler.keyedBlock = keyedBlock
|
|
159
184
|
return generateCommandBehavior(moduleIR, handler, factory)
|
|
160
185
|
}
|
|
@@ -176,7 +201,7 @@ export function createDescriptorSession({ semantic, handlerUrl, factory, context
|
|
|
176
201
|
|
|
177
202
|
function registerDerivedResult(kind, value, states = [], node) {
|
|
178
203
|
const normalized = JSON.parse(JSON.stringify(value))
|
|
179
|
-
return registerDerived(moduleIR, { kind, [kind]: normalized,
|
|
204
|
+
return registerDerived(moduleIR, { kind, [kind]: normalized, signals: [...states].map(name => signal(name, node)), ...(source(node) ? { source: source(node) } : {}) })
|
|
180
205
|
}
|
|
181
206
|
|
|
182
207
|
function registerKeyedBlockResult(descriptor) {
|
|
@@ -184,68 +209,79 @@ export function createDescriptorSession({ semantic, handlerUrl, factory, context
|
|
|
184
209
|
}
|
|
185
210
|
|
|
186
211
|
function registerEffectResult(handler, descriptor) {
|
|
187
|
-
|
|
212
|
+
const effect = { ...descriptor, setup: handler }
|
|
213
|
+
pendingEffects.push(effect)
|
|
214
|
+
return effect
|
|
188
215
|
}
|
|
189
216
|
|
|
190
217
|
function finalize() {
|
|
191
218
|
const callbacks = [...nativeHandlers, ...effectHandlers]
|
|
219
|
+
const imports = [...callbacks, ...reactiveBindings].flatMap(entry => entry.imports ?? []).map(importRecord)
|
|
220
|
+
moduleIR.imports = [...new Map(imports.map(entry => [`${entry.target}:${entry.kind}:${entry.imported ?? ""}:${entry.local}`, entry])).values()].map((entry, slot) => ({ slot, ...entry }))
|
|
221
|
+
const importSlots = new Map(moduleIR.imports.map(entry => [`${entry.target}:${entry.kind}:${entry.imported ?? ""}:${entry.local}`, entry.slot]))
|
|
222
|
+
const importSlot = entry => importSlots.get(`${entry.target}:${entry.kind}:${entry.imported ?? ""}:${entry.local}`)
|
|
192
223
|
for (const entry of callbacks) {
|
|
193
224
|
const lowered = handlerLowering.lowerNativeHandler(entry)
|
|
194
225
|
const setters = Map.groupBy(entry.setters, ([, state]) => state)
|
|
195
|
-
registerModuleHandler(moduleIR, {
|
|
226
|
+
entry.handler = registerModuleHandler(moduleIR, {
|
|
196
227
|
role: entry.role,
|
|
197
228
|
...(entry.keyedBlock !== undefined ? { keyedBlock: entry.keyedBlock } : {}),
|
|
198
229
|
exportName: entry.exportName,
|
|
199
230
|
async: Boolean(entry.expression.modifiers?.some(modifier => modifier.kind === ts.SyntaxKind.AsyncKeyword)),
|
|
200
231
|
generator: Boolean(entry.expression.asteriskToken),
|
|
201
232
|
signals: [...entry.usedStates].map(name => ({
|
|
233
|
+
signal: entry.signalRefs.get(name),
|
|
202
234
|
name,
|
|
203
235
|
setters: (setters.get(name) ?? []).map(([setter]) => setter),
|
|
204
236
|
value: entry.deferValues ? "deferred" : "direct",
|
|
205
237
|
snapshot: lowered.stateSnapshots.includes(name)
|
|
206
238
|
})),
|
|
207
239
|
captures: [...entry.captures].map(name => ({
|
|
240
|
+
...(symbolSlot(entry, name) !== undefined ? { symbol: symbolSlot(entry, name) } : {}),
|
|
208
241
|
name,
|
|
209
242
|
source: name === (typeof entry.listItem === "string" ? entry.listItem : entry.listItem?.item) ? "list-item" : name === entry.listItem?.index ? "list-index" : "scope",
|
|
210
243
|
value: entry.deferValues ? "deferred" : "direct",
|
|
211
244
|
snapshot: lowered.captureSnapshots.includes(name)
|
|
212
245
|
})),
|
|
213
|
-
imports: entry.imports.map(importRecord),
|
|
246
|
+
imports: entry.imports.map(entry => importSlot(importRecord(entry))),
|
|
214
247
|
code: lowered.code,
|
|
215
248
|
...(source(entry.expression) ? { source: source(entry.expression) } : {})
|
|
216
249
|
})
|
|
217
250
|
}
|
|
218
251
|
for (const entry of reactiveBindings) registerBinding(moduleIR, {
|
|
252
|
+
slot: entry.slot,
|
|
219
253
|
role: entry.role,
|
|
220
254
|
...(entry.keyedBlock !== undefined ? { keyedBlock: entry.keyedBlock } : {}),
|
|
221
255
|
exportName: entry.exportName,
|
|
222
256
|
parameters: ["__k"],
|
|
223
|
-
|
|
224
|
-
captures: [...entry.captures].map(name => ({ name, source: "scope" })),
|
|
225
|
-
imports: entry.imports.map(importRecord),
|
|
257
|
+
signals: [...entry.states].map(name => entry.signalRefs.get(name)),
|
|
258
|
+
captures: [...entry.captures].map(name => ({ ...(symbolSlot(entry, name) !== undefined ? { symbol: symbolSlot(entry, name) } : {}), name, source: "scope" })),
|
|
259
|
+
imports: entry.imports.map(entry => importSlot(importRecord(entry))),
|
|
226
260
|
code: handlerLowering.lowerReactiveBinding(entry),
|
|
227
261
|
...(source(entry.expression) ? { source: source(entry.expression) } : {})
|
|
228
262
|
})
|
|
229
|
-
for (const entry of listExpressions) registerBinding(moduleIR, {
|
|
263
|
+
for (const [index, entry] of listExpressions.entries()) registerBinding(moduleIR, {
|
|
264
|
+
slot: reactiveBindings.length + index,
|
|
230
265
|
role: entry.role,
|
|
231
266
|
...(entry.keyedBlock !== undefined ? { keyedBlock: entry.keyedBlock } : {}),
|
|
232
267
|
exportName: entry.exportName,
|
|
233
268
|
parameters: [entry.item, entry.index ?? "__kIndex", "__k"],
|
|
234
|
-
|
|
269
|
+
signals: [...(entry.states ?? [])].map(name => entry.signalRefs.get(name)),
|
|
235
270
|
captures: [],
|
|
236
271
|
imports: [],
|
|
237
272
|
code: handlerLowering.lowerListExpression(entry),
|
|
238
273
|
...(source(entry.expression) ? { source: source(entry.expression) } : {})
|
|
239
274
|
})
|
|
240
|
-
for (const effect of
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
effect.setup = { handler: handler.slot }
|
|
275
|
+
for (const effect of pendingEffects) {
|
|
276
|
+
if (!effect.setup.entry.handler) throw new Error(`Effect handler ${JSON.stringify(effect.setup.exportName)} was not finalized`)
|
|
277
|
+
registerEffect(moduleIR, { ...effect, setup: { handler: effect.setup.entry.handler.slot } })
|
|
244
278
|
}
|
|
245
|
-
const imports = [...callbacks, ...reactiveBindings].flatMap(entry => entry.imports ?? []).map(importRecord)
|
|
246
|
-
moduleIR.imports = [...new Map(imports.map(entry => [`${entry.target}:${entry.kind}:${entry.imported ?? ""}:${entry.local}`, entry])).values()]
|
|
247
279
|
moduleIR.clientModules = [...clientModules]
|
|
248
|
-
assertModuleIRReferences(moduleIR)
|
|
280
|
+
assertModuleIRReferences(moduleIR, semantic.componentAnalysis)
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function symbolSlot(entry, name) {
|
|
284
|
+
return entry.bindingIndex?.references(entry.expression, entry.expression)?.find(reference => reference.debugName === name)?.slot
|
|
249
285
|
}
|
|
250
286
|
|
|
251
287
|
function source(node) {
|
|
@@ -256,7 +292,15 @@ export function createDescriptorSession({ semantic, handlerUrl, factory, context
|
|
|
256
292
|
|
|
257
293
|
const importRecord = entry => ({ target: entry.target, kind: entry.kind, local: entry.local, ...(entry.imported ? { imported: entry.imported } : {}), package: Boolean(entry.package) })
|
|
258
294
|
|
|
259
|
-
return { compileConditional, compileEffectCallback, compileEvent, compileListConditional, compileListValue, compileReactiveBinding, finalize, registerDerived: registerDerivedResult, registerEffect: registerEffectResult, registerKeyedBlock: registerKeyedBlockResult }
|
|
295
|
+
return { compileConditional, compileEffectCallback, compileEvent, compileListConditional, compileListValue, compileReactiveBinding, finalize, registerDerived: registerDerivedResult, registerEffect: registerEffectResult, registerKeyedBlock: registerKeyedBlockResult, signal }
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function bindingIdentifier(name, names) {
|
|
299
|
+
if (ts.isIdentifier(name)) return names.has(name.text) ? name : undefined
|
|
300
|
+
for (const element of name.elements) if (ts.isBindingElement(element)) {
|
|
301
|
+
const identifier = bindingIdentifier(element.name, names)
|
|
302
|
+
if (identifier) return identifier
|
|
303
|
+
}
|
|
260
304
|
}
|
|
261
305
|
|
|
262
306
|
function directStateIdentifier(expression, setters, bindingIndex) {
|
|
@@ -1,48 +1,159 @@
|
|
|
1
1
|
export function createModuleIR(file) {
|
|
2
|
-
return { version:
|
|
2
|
+
return { version: 2, file, symbols: [], signals: [], handlers: [], bindings: [], derived: [], effects: [], keyedBlocks: [], imports: [], clientModules: [] }
|
|
3
3
|
}
|
|
4
4
|
|
|
5
|
-
export function assertModuleIRReferences(moduleIR) {
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
export function assertModuleIRReferences(moduleIR, componentAnalysis) {
|
|
6
|
+
if (moduleIR?.version !== 2) throw new Error(`Unsupported ModuleIR version: ${JSON.stringify(moduleIR?.version)}`)
|
|
7
|
+
if (componentAnalysis && componentAnalysis.version !== 2) throw new Error(`Unsupported ComponentAnalysis version: ${JSON.stringify(componentAnalysis.version)}`)
|
|
8
|
+
const slot = (records, value, label, kind) => {
|
|
9
|
+
if (!Number.isInteger(value) || value < 0 || value >= records.length) throw new Error(`${label} references missing ${kind} slot ${JSON.stringify(value)}`)
|
|
10
|
+
return records[value]
|
|
8
11
|
}
|
|
9
|
-
|
|
12
|
+
const indexed = (name, records) => {
|
|
10
13
|
records.forEach((record, index) => {
|
|
11
|
-
if (record.slot !== index) throw new Error(
|
|
14
|
+
if (record.slot !== index) throw new Error(`${name} slot ${JSON.stringify(record.slot)} must equal its index ${index}`)
|
|
12
15
|
})
|
|
13
16
|
}
|
|
17
|
+
indexed("SymbolRef", moduleIR.symbols)
|
|
18
|
+
indexed("SignalIR", moduleIR.signals)
|
|
19
|
+
indexed("HandlerIR", moduleIR.handlers)
|
|
20
|
+
indexed("BindingIR", moduleIR.bindings)
|
|
21
|
+
indexed("DerivedIR", moduleIR.derived)
|
|
22
|
+
indexed("EffectIR", moduleIR.effects)
|
|
23
|
+
indexed("KeyedBlockIR", moduleIR.keyedBlocks)
|
|
24
|
+
indexed("ImportIR", moduleIR.imports)
|
|
25
|
+
if (componentAnalysis) {
|
|
26
|
+
indexed("Component owner", componentAnalysis.owners)
|
|
27
|
+
indexed("Component specialization", componentAnalysis.specializations)
|
|
28
|
+
for (const owner of componentAnalysis.owners) {
|
|
29
|
+
indexed(`Component owner ${owner.slot} state`, owner.states)
|
|
30
|
+
indexed(`Component owner ${owner.slot} ref`, owner.refs)
|
|
31
|
+
indexed(`Component owner ${owner.slot} ID`, owner.ids)
|
|
32
|
+
for (const setter of owner.setters) slot(owner.states, setter.signal, `Component owner ${owner.slot} setter ${JSON.stringify(setter.name)}`, "state")
|
|
33
|
+
for (const state of owner.states) if (state.owner !== undefined) ownerRef(state.owner, `Component owner ${owner.slot} state ${state.slot} external owner`)
|
|
34
|
+
}
|
|
35
|
+
for (const specialization of componentAnalysis.specializations) {
|
|
36
|
+
indexed(`Component specialization ${specialization.slot} state`, specialization.states)
|
|
37
|
+
indexed(`Component specialization ${specialization.slot} ref`, specialization.refs)
|
|
38
|
+
indexed(`Component specialization ${specialization.slot} ID`, specialization.ids)
|
|
39
|
+
if (specialization.owner !== undefined) ownerRef(specialization.owner, `Component specialization ${specialization.slot} owner`)
|
|
40
|
+
for (const prop of specialization.props ?? []) for (const signal of prop.signals ?? []) slot(moduleIR.signals, signal, `Component specialization ${specialization.slot} prop ${JSON.stringify(prop.name)}`, "SignalIR")
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function moduleSymbol(symbol, label) {
|
|
44
|
+
if (!symbol || typeof symbol.id !== "string" || typeof symbol.module !== "string" || typeof symbol.site !== "string" || typeof symbol.name !== "string") throw new Error(`${label} must be a ModuleSymbol`)
|
|
45
|
+
}
|
|
46
|
+
function ownerRef(reference, label) {
|
|
47
|
+
if (!reference || typeof reference !== "object") throw new Error(`${label} must be an OwnerRef`)
|
|
48
|
+
if (reference.kind === "component") return slot(componentAnalysis?.owners ?? [], reference.slot, label, "component owner")
|
|
49
|
+
if (reference.kind === "specialization") return slot(componentAnalysis?.specializations ?? [], reference.slot, label, "component specialization")
|
|
50
|
+
if (reference.kind === "module-symbol") return moduleSymbol(reference.symbol, label)
|
|
51
|
+
if (reference.kind !== "module") throw new Error(`${label} has invalid kind ${JSON.stringify(reference.kind)}`)
|
|
52
|
+
}
|
|
53
|
+
function stateRef(reference, label) {
|
|
54
|
+
if (!reference || typeof reference !== "object") throw new Error(`${label} must be a StateRef`)
|
|
55
|
+
if (reference.kind === "module-symbol") return moduleSymbol(reference.symbol, label)
|
|
56
|
+
if (reference.kind === "symbol") return slot(moduleIR.symbols, reference.symbol, label, "SymbolRef")
|
|
57
|
+
if (reference.kind !== "state") throw new Error(`${label} has invalid kind ${JSON.stringify(reference.kind)}`)
|
|
58
|
+
const owner = ownerRef(reference.owner, `${label} owner`)
|
|
59
|
+
const states = reference.owner.kind === "component" || reference.owner.kind === "specialization" ? owner.states : []
|
|
60
|
+
slot(states, reference.slot, label, "owner state")
|
|
61
|
+
}
|
|
62
|
+
const exports = new Map()
|
|
63
|
+
const exported = (record, label) => {
|
|
64
|
+
if (record.kind !== "module-export") return
|
|
65
|
+
if (typeof record.exportName !== "string" || !record.exportName) throw new Error(`${label} requires an export name`)
|
|
66
|
+
const previous = exports.get(record.exportName)
|
|
67
|
+
if (previous) throw new Error(`ModuleIR export ${JSON.stringify(record.exportName)} is declared by both ${previous} and ${label}`)
|
|
68
|
+
exports.set(record.exportName, label)
|
|
69
|
+
}
|
|
70
|
+
for (const signal of moduleIR.signals) stateRef(signal.reference, `SignalIR ${signal.slot}`)
|
|
14
71
|
for (const handler of moduleIR.handlers) {
|
|
15
|
-
|
|
16
|
-
|
|
72
|
+
exported(handler, `HandlerIR ${handler.slot}`)
|
|
73
|
+
for (const [index, command] of (handler.commands ?? []).entries()) slot(moduleIR.signals, command.signal, `HandlerIR ${handler.slot} command ${index}`, "SignalIR")
|
|
74
|
+
for (const [index, signal] of (handler.signals ?? []).entries()) slot(moduleIR.signals, signal.signal, `HandlerIR ${handler.slot} signal ${index}`, "SignalIR")
|
|
75
|
+
for (const [index, imported] of (handler.imports ?? []).entries()) slot(moduleIR.imports, imported, `HandlerIR ${handler.slot} import ${index}`, "ImportIR")
|
|
76
|
+
for (const [index, capture] of (handler.captures ?? []).entries()) if (capture.symbol !== undefined) slot(moduleIR.symbols, capture.symbol, `HandlerIR ${handler.slot} capture ${index}`, "SymbolRef")
|
|
77
|
+
if (handler.keyedBlock !== undefined) slot(moduleIR.keyedBlocks, handler.keyedBlock, `HandlerIR ${handler.slot} keyed block`, "KeyedBlockIR")
|
|
17
78
|
}
|
|
18
|
-
for (const binding of moduleIR.bindings)
|
|
79
|
+
for (const binding of moduleIR.bindings) {
|
|
80
|
+
exported(binding, `BindingIR ${binding.slot}`)
|
|
81
|
+
for (const [index, signal] of (binding.signals ?? []).entries()) slot(moduleIR.signals, signal, `BindingIR ${binding.slot} signal ${index}`, "SignalIR")
|
|
82
|
+
for (const [index, imported] of (binding.imports ?? []).entries()) slot(moduleIR.imports, imported, `BindingIR ${binding.slot} import ${index}`, "ImportIR")
|
|
83
|
+
for (const [index, capture] of (binding.captures ?? []).entries()) if (capture.symbol !== undefined) slot(moduleIR.symbols, capture.symbol, `BindingIR ${binding.slot} capture ${index}`, "SymbolRef")
|
|
84
|
+
if (binding.keyedBlock !== undefined) slot(moduleIR.keyedBlocks, binding.keyedBlock, `BindingIR ${binding.slot} keyed block`, "KeyedBlockIR")
|
|
85
|
+
}
|
|
86
|
+
for (const derived of moduleIR.derived) for (const [index, signal] of (derived.signals ?? []).entries()) slot(moduleIR.signals, signal, `DerivedIR ${derived.slot} signal ${index}`, "SignalIR")
|
|
19
87
|
for (const effect of moduleIR.effects) {
|
|
20
|
-
slot(moduleIR.handlers, effect.setup?.handler, `
|
|
21
|
-
|
|
22
|
-
|
|
88
|
+
const handler = slot(moduleIR.handlers, effect.setup?.handler, `EffectIR ${effect.slot} setup`, "HandlerIR")
|
|
89
|
+
if (handler.kind !== "module-export" || handler.role !== "effect") throw new Error(`EffectIR ${effect.slot} setup HandlerIR ${handler.slot} must have role "effect"`)
|
|
90
|
+
for (const [index, dependency] of (effect.dependencies ?? []).entries()) {
|
|
91
|
+
if (dependency.kind === "signal") slot(moduleIR.signals, dependency.signal, `EffectIR ${effect.slot} dependency ${index}`, "SignalIR")
|
|
92
|
+
else if (dependency.kind === "derived") {
|
|
93
|
+
slot(moduleIR.derived, dependency.derived, `EffectIR ${effect.slot} dependency ${index}`, "DerivedIR")
|
|
94
|
+
for (const [sourceIndex, signal] of (dependency.sources ?? []).entries()) slot(moduleIR.signals, signal, `EffectIR ${effect.slot} dependency ${index} source ${sourceIndex}`, "SignalIR")
|
|
95
|
+
} else throw new Error(`EffectIR ${effect.slot} dependency ${index} has invalid kind ${JSON.stringify(dependency.kind)}`)
|
|
96
|
+
}
|
|
97
|
+
for (const [index, signal] of (effect.subscriptions ?? []).entries()) slot(moduleIR.signals, signal, `EffectIR ${effect.slot} subscription ${index}`, "SignalIR")
|
|
98
|
+
for (const [index, signal] of (effect.dependencySignals ?? []).entries()) slot(moduleIR.signals, signal, `EffectIR ${effect.slot} dependency signal ${index}`, "SignalIR")
|
|
99
|
+
if (effect.ownership?.owner) ownerRef(effect.ownership.owner, `EffectIR ${effect.slot} ownership`)
|
|
100
|
+
if (effect.ownership?.keyedBlock !== undefined) {
|
|
101
|
+
slot(moduleIR.keyedBlocks, effect.ownership.keyedBlock, `EffectIR ${effect.slot} ownership`, "KeyedBlockIR")
|
|
102
|
+
if (handler.keyedBlock !== effect.ownership.keyedBlock) throw new Error(`EffectIR ${effect.slot} and HandlerIR ${handler.slot} must reference the same KeyedBlockIR`)
|
|
103
|
+
}
|
|
23
104
|
}
|
|
24
105
|
for (const block of moduleIR.keyedBlocks) {
|
|
25
|
-
if (block.
|
|
26
|
-
|
|
27
|
-
if (block.
|
|
106
|
+
if (block.collection?.kind === "signal") slot(moduleIR.signals, block.collection.signal, `KeyedBlockIR ${block.slot} collection`, "SignalIR")
|
|
107
|
+
else if (block.collection?.kind === "binding") slot(moduleIR.bindings, block.collection.binding, `KeyedBlockIR ${block.slot} collection`, "BindingIR")
|
|
108
|
+
else if (block.collection?.kind === "symbol") slot(moduleIR.symbols, block.collection.symbol, `KeyedBlockIR ${block.slot} collection`, "SymbolRef")
|
|
109
|
+
else if (block.collection?.kind !== "static") throw new Error(`KeyedBlockIR ${block.slot} collection has invalid kind ${JSON.stringify(block.collection?.kind)}`)
|
|
110
|
+
if (block.parent !== undefined) {
|
|
111
|
+
const parent = slot(moduleIR.keyedBlocks, block.parent, `KeyedBlockIR ${block.slot} parent`, "KeyedBlockIR")
|
|
112
|
+
if (!(parent.children ?? []).includes(block.slot)) throw new Error(`KeyedBlockIR ${block.slot} parent ${parent.slot} does not reciprocally list child ${block.slot}`)
|
|
113
|
+
}
|
|
114
|
+
for (const childSlot of block.children ?? []) {
|
|
115
|
+
const child = slot(moduleIR.keyedBlocks, childSlot, `KeyedBlockIR ${block.slot} child`, "KeyedBlockIR")
|
|
116
|
+
if (child.parent !== block.slot) throw new Error(`KeyedBlockIR ${block.slot} child ${child.slot} does not reciprocally reference parent ${block.slot}`)
|
|
117
|
+
}
|
|
118
|
+
if (new Set(block.children ?? []).size !== (block.children ?? []).length) throw new Error(`KeyedBlockIR ${block.slot} has duplicate children`)
|
|
119
|
+
if (block.selector !== undefined) slot(moduleIR.derived, block.selector, `KeyedBlockIR ${block.slot} selector`, "DerivedIR")
|
|
120
|
+
for (const [index, signal] of (block.selectorSignals ?? []).entries()) slot(moduleIR.signals, signal, `KeyedBlockIR ${block.slot} selector signal ${index}`, "SignalIR")
|
|
121
|
+
for (const [index, specialization] of (block.specializations ?? []).entries()) slot(componentAnalysis?.specializations ?? [], specialization, `KeyedBlockIR ${block.slot} specialization ${index}`, "component specialization")
|
|
122
|
+
for (const [index, row] of (block.rowStates ?? []).entries()) slot(moduleIR.signals, row.signal, `KeyedBlockIR ${block.slot} row state ${index}`, "SignalIR")
|
|
123
|
+
for (const [index, row] of (block.rowRefs ?? []).entries()) {
|
|
124
|
+
const specialization = slot(componentAnalysis?.specializations ?? [], row.specialization, `KeyedBlockIR ${block.slot} row ref ${index}`, "component specialization")
|
|
125
|
+
slot(specialization.refs, row.ref, `KeyedBlockIR ${block.slot} row ref ${index}`, "specialization ref")
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
const visiting = new Set()
|
|
129
|
+
const visited = new Set()
|
|
130
|
+
const visit = (block, trail) => {
|
|
131
|
+
if (visiting.has(block.slot)) throw new Error(`KeyedBlockIR parent cycle: ${[...trail, block.slot].join(" -> ")}`)
|
|
132
|
+
if (visited.has(block.slot)) return
|
|
133
|
+
visiting.add(block.slot)
|
|
134
|
+
if (block.parent !== undefined) visit(moduleIR.keyedBlocks[block.parent], [...trail, block.slot])
|
|
135
|
+
visiting.delete(block.slot)
|
|
136
|
+
visited.add(block.slot)
|
|
28
137
|
}
|
|
138
|
+
for (const block of moduleIR.keyedBlocks) visit(block, [])
|
|
29
139
|
return moduleIR
|
|
30
140
|
}
|
|
31
141
|
|
|
32
|
-
export function
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
slots.set(key, slot)
|
|
39
|
-
moduleIR.signals.push({ slot, key, debugName: state })
|
|
142
|
+
export function registerSignal(moduleIR, reference, debugName) {
|
|
143
|
+
const key = JSON.stringify(reference)
|
|
144
|
+
let signal = moduleIR.signals.find(entry => JSON.stringify(entry.reference) === key)
|
|
145
|
+
if (!signal) {
|
|
146
|
+
signal = { slot: moduleIR.signals.length, reference, debugName }
|
|
147
|
+
moduleIR.signals.push(signal)
|
|
40
148
|
}
|
|
41
|
-
|
|
149
|
+
return signal
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export function registerCommandHandler(moduleIR, commands, source) {
|
|
42
153
|
const handler = {
|
|
43
154
|
slot: moduleIR.handlers.length,
|
|
44
155
|
kind: "commands",
|
|
45
|
-
commands: commands.map(({ operation,
|
|
156
|
+
commands: commands.map(({ operation, reference, state, value, syntax }) => ({ operation, signal: registerSignal(moduleIR, reference, state).slot, value, ...(syntax ? { syntax } : {}) })),
|
|
46
157
|
...(source ? { source } : {})
|
|
47
158
|
}
|
|
48
159
|
moduleIR.handlers.push(handler)
|
|
@@ -50,31 +161,31 @@ export function registerCommandHandler(moduleIR, commands, source, scope = "modu
|
|
|
50
161
|
}
|
|
51
162
|
|
|
52
163
|
export function registerModuleHandler(moduleIR, descriptor) {
|
|
53
|
-
const handler = { slot: moduleIR.handlers.length, kind: "module-export"
|
|
164
|
+
const handler = { ...descriptor, slot: moduleIR.handlers.length, kind: "module-export" }
|
|
54
165
|
moduleIR.handlers.push(handler)
|
|
55
166
|
return handler
|
|
56
167
|
}
|
|
57
168
|
|
|
58
169
|
export function registerBinding(moduleIR, descriptor) {
|
|
59
|
-
const binding = { slot: moduleIR.bindings.length, kind: "module-export"
|
|
170
|
+
const binding = { ...descriptor, slot: moduleIR.bindings.length, kind: "module-export" }
|
|
60
171
|
moduleIR.bindings.push(binding)
|
|
61
172
|
return binding
|
|
62
173
|
}
|
|
63
174
|
|
|
64
175
|
export function registerDerived(moduleIR, descriptor) {
|
|
65
|
-
const derived = { slot: moduleIR.derived.length
|
|
176
|
+
const derived = { ...descriptor, slot: moduleIR.derived.length }
|
|
66
177
|
moduleIR.derived.push(derived)
|
|
67
178
|
return derived
|
|
68
179
|
}
|
|
69
180
|
|
|
70
181
|
export function registerEffect(moduleIR, descriptor) {
|
|
71
|
-
const effect = { slot: moduleIR.effects.length
|
|
182
|
+
const effect = { ...descriptor, slot: moduleIR.effects.length }
|
|
72
183
|
moduleIR.effects.push(effect)
|
|
73
184
|
return effect
|
|
74
185
|
}
|
|
75
186
|
|
|
76
187
|
export function registerKeyedBlock(moduleIR, descriptor) {
|
|
77
|
-
const block = { slot: moduleIR.keyedBlocks.length
|
|
188
|
+
const block = { ...descriptor, slot: moduleIR.keyedBlocks.length }
|
|
78
189
|
moduleIR.keyedBlocks.push(block)
|
|
79
190
|
return block
|
|
80
191
|
}
|
|
@@ -257,6 +257,8 @@ function createKudzuTransformer({ semantic, handlerUrl, file, sourceFiles, sourc
|
|
|
257
257
|
const { customHookTimerStates } = normalized
|
|
258
258
|
const bindingIndex = createBindingIndex(sourceFile)
|
|
259
259
|
const factory = context.factory
|
|
260
|
+
let activeStateOwners
|
|
261
|
+
let activeKeyedBlock
|
|
260
262
|
const sourceName = source => relative(root, source.fileName).replaceAll(sep, "/")
|
|
261
263
|
const componentAnalysis = createComponentAnalysisSession(semantic.componentAnalysis)
|
|
262
264
|
const descriptors = createDescriptorSession({
|
|
@@ -268,6 +270,27 @@ function createKudzuTransformer({ semantic, handlerUrl, file, sourceFiles, sourc
|
|
|
268
270
|
compileEventCommand,
|
|
269
271
|
handlerLowering,
|
|
270
272
|
isPrimitiveLiteral: isPrimitiveDefaultLiteral,
|
|
273
|
+
stateReferences: node => new Map([...stateOwnersForNode(node), ...(activeStateOwners ?? [])]),
|
|
274
|
+
symbolReference: (name, node, aliases) => {
|
|
275
|
+
const names = new Set([name, ...aliases])
|
|
276
|
+
let binding
|
|
277
|
+
const visitName = current => {
|
|
278
|
+
if (binding) return
|
|
279
|
+
if (ts.isIdentifier(current)) {
|
|
280
|
+
if (names.has(current.text)) binding = current
|
|
281
|
+
return
|
|
282
|
+
}
|
|
283
|
+
for (const element of current.elements) if (ts.isBindingElement(element)) visitName(element.name)
|
|
284
|
+
}
|
|
285
|
+
for (let current = node; current && !binding; current = current.parent) if (isFunctionLike(current)) {
|
|
286
|
+
for (const parameter of current.parameters) visitName(parameter.name)
|
|
287
|
+
if (ts.isBlock(current.body)) for (const statement of current.body.statements) {
|
|
288
|
+
if (ts.isVariableStatement(statement)) for (const declaration of statement.declarationList.declarations) visitName(declaration.name)
|
|
289
|
+
if (ts.isFunctionDeclaration(statement) && statement.name) visitName(statement.name)
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
return binding ? { kind: "module-symbol", symbol: modules.symbol(binding.getSourceFile().fileName, binding, name) } : undefined
|
|
293
|
+
},
|
|
271
294
|
sourceName,
|
|
272
295
|
rejectWorkerConstructions: expression => workerCompiler.rejectConstructions(expression, expression.getSourceFile(), "Relative TypeScript Worker construction is only supported directly inside an inline useEffect() callback")
|
|
273
296
|
})
|
|
@@ -341,24 +364,33 @@ function createKudzuTransformer({ semantic, handlerUrl, file, sourceFiles, sourc
|
|
|
341
364
|
const ensureOwner = (owner, kind = "component") => componentAnalysis.registerOwner(owner, { kind, name: ownerName(owner), props: analyzedProps(owner), site: analysisSite(owner, "owner"), source: analysisSource(owner) })
|
|
342
365
|
const registerState = (owner, state, setter, kind, node, externalOwner) => {
|
|
343
366
|
const ownerRecord = ensureOwner(owner)
|
|
344
|
-
const
|
|
367
|
+
const stateRecord = componentAnalysis.registerState(owner, { name: state, setter, kind, ...(externalOwner ? { owner: externalOwner.owner } : {}), site: analysisSite(node, "hook"), source: analysisSource(node) })
|
|
368
|
+
const stateOwner = externalOwner?.state
|
|
369
|
+
? { kind: "module-symbol", symbol: externalOwner.state }
|
|
370
|
+
: { kind: "state", owner: { kind: "component", slot: ownerRecord.slot }, slot: stateRecord.slot }
|
|
345
371
|
const stateOwners = stateOwnersByFunction.get(owner) ?? new Map()
|
|
346
372
|
stateOwners.set(state, stateOwner)
|
|
347
373
|
stateOwnersByFunction.set(owner, stateOwners)
|
|
348
|
-
return
|
|
374
|
+
return stateRecord
|
|
349
375
|
}
|
|
350
376
|
const stateOwnersForNode = node => {
|
|
351
377
|
for (let current = node.parent; current; current = current.parent) {
|
|
352
|
-
if (isFunctionLike(current)
|
|
378
|
+
if (isFunctionLike(current)) {
|
|
379
|
+
const stateOwners = stateOwnersByFunction.get(current) ?? stateOwnersByFunction.get(ts.getOriginalNode(current))
|
|
380
|
+
if (stateOwners) return stateOwners
|
|
381
|
+
const site = analysisSite(current, "owner")
|
|
382
|
+
const owner = site && semantic.componentAnalysis.owners.find(entry => entry.site === site)
|
|
383
|
+
if (owner) return new Map(owner.states.map(state => [state.name, { kind: "state", owner: { kind: "component", slot: owner.slot }, slot: state.slot }]))
|
|
384
|
+
}
|
|
353
385
|
}
|
|
354
386
|
return new Map()
|
|
355
387
|
}
|
|
356
388
|
const fallbackOwner = node => {
|
|
357
389
|
for (let current = node.parent; current; current = current.parent) {
|
|
358
390
|
const owner = isFunctionLike(current) ? componentAnalysis.owner(current) : undefined
|
|
359
|
-
if (owner) return
|
|
391
|
+
if (owner) return { kind: "component", slot: owner.slot }
|
|
360
392
|
}
|
|
361
|
-
return "module"
|
|
393
|
+
return { kind: "module" }
|
|
362
394
|
}
|
|
363
395
|
let usesBehavior = false
|
|
364
396
|
let usesBinding = false
|
|
@@ -405,16 +437,20 @@ function createKudzuTransformer({ semantic, handlerUrl, file, sourceFiles, sourc
|
|
|
405
437
|
if (!value || !ts.isObjectLiteralExpression(value)) throw sourceNodeError(provider, providerSource, "Context Provider value must be one direct object literal")
|
|
406
438
|
const owner = nearestFunction(provider)
|
|
407
439
|
if (!owner) throw sourceNodeError(provider, providerSource, "Context Provider value must be returned by a component")
|
|
408
|
-
const stateOwner =
|
|
440
|
+
const stateOwner = { kind: "module-symbol", symbol: modules.symbol(providerSource.fileName, owner, ownerName(owner)) }
|
|
409
441
|
|
|
410
442
|
const states = new Map()
|
|
443
|
+
const stateSymbols = new Map()
|
|
411
444
|
const callbacks = new Map()
|
|
412
445
|
const hasUseState = hasFrameworkImport(providerSource, "useState")
|
|
413
446
|
const collectProviderBindings = node => {
|
|
414
447
|
if (ts.isVariableDeclaration(node) && nearestFunction(node) === owner) {
|
|
415
448
|
if (hasUseState && ts.isArrayBindingPattern(node.name) && node.initializer && ts.isCallExpression(node.initializer) && ts.isIdentifier(node.initializer.expression) && node.initializer.expression.text === "useState") {
|
|
416
449
|
const [state, setter] = node.name.elements
|
|
417
|
-
if (node.name.elements.length === 2 && state && setter && ts.isBindingElement(state) && ts.isBindingElement(setter) && ts.isIdentifier(state.name) && ts.isIdentifier(setter.name))
|
|
450
|
+
if (node.name.elements.length === 2 && state && setter && ts.isBindingElement(state) && ts.isBindingElement(setter) && ts.isIdentifier(state.name) && ts.isIdentifier(setter.name)) {
|
|
451
|
+
states.set(setter.name.text, state.name.text)
|
|
452
|
+
stateSymbols.set(state.name.text, modules.symbol(providerSource.fileName, state.name, state.name.text))
|
|
453
|
+
}
|
|
418
454
|
}
|
|
419
455
|
if (ts.isIdentifier(node.name) && node.initializer && (ts.isArrowFunction(node.initializer) || ts.isFunctionExpression(node.initializer))) callbacks.set(node.name.text, node.initializer)
|
|
420
456
|
}
|
|
@@ -443,7 +479,7 @@ function createKudzuTransformer({ semantic, handlerUrl, file, sourceFiles, sourc
|
|
|
443
479
|
if (!setter || !fields.has(state) || !fields.has(setter)) throw sourceNodeError(callback, providerSource, `Context action ${JSON.stringify(name)} requires exposed state and setter fields for ${JSON.stringify(state)}`)
|
|
444
480
|
}
|
|
445
481
|
}
|
|
446
|
-
return { callbacks: new Map([...callbacks].filter(([name]) => fields.has(name))), context: true, fields, privateStates: new Set(), stateOwner, states }
|
|
482
|
+
return { callbacks: new Map([...callbacks].filter(([name]) => fields.has(name))), context: true, fields, privateStates: new Set(), stateOwner, stateSymbols, states }
|
|
447
483
|
}
|
|
448
484
|
|
|
449
485
|
const resolveCustomHook = (binding, call) => {
|
|
@@ -518,7 +554,7 @@ function createKudzuTransformer({ semantic, handlerUrl, file, sourceFiles, sourc
|
|
|
518
554
|
if (!names.has(state) && !requiredContextStates.has(state)) continue
|
|
519
555
|
const localSetter = names.has(setter) || requiredContextStates.has(state) ? setter : `__kContextState_${state}`
|
|
520
556
|
setters.set(localSetter, state)
|
|
521
|
-
registerState(owner, state, localSetter, "context", node, hook.stateOwner)
|
|
557
|
+
registerState(owner, state, localSetter, "context", node, { owner: hook.stateOwner, state: hook.stateSymbols.get(state) })
|
|
522
558
|
if (requiredContextStates.has(state)) {
|
|
523
559
|
const fields = customHookPrivateFields.get(node) ?? []
|
|
524
560
|
for (const field of [state, setter]) {
|
|
@@ -813,6 +849,7 @@ function createKudzuTransformer({ semantic, handlerUrl, file, sourceFiles, sourc
|
|
|
813
849
|
return expanded
|
|
814
850
|
}
|
|
815
851
|
const componentSpecializations = new WeakMap()
|
|
852
|
+
const specializedEffectStateOwners = new WeakMap()
|
|
816
853
|
const setterHookHelpers = new WeakMap()
|
|
817
854
|
const expandedRowSpecializations = new WeakMap()
|
|
818
855
|
const nestedRowSpecializations = new Map()
|
|
@@ -833,11 +870,11 @@ function createKudzuTransformer({ semantic, handlerUrl, file, sourceFiles, sourc
|
|
|
833
870
|
if (ts.isIdentifier(expression) && setters.has(expression.text)) signals.add(setters.get(expression.text))
|
|
834
871
|
const callback = ts.isIdentifier(expression) ? callbacks.get(expression.text) : undefined
|
|
835
872
|
for (const state of referencedStateNames((callback ?? expression).body ?? callback ?? expression, setters, callback ?? expression, bindingIndex)) signals.add(state)
|
|
836
|
-
return [...signals].map(name => (
|
|
873
|
+
return [...signals].map(name => descriptors.signal(name, expression, stateOwners))
|
|
837
874
|
}
|
|
838
875
|
result.analysis = componentAnalysis.registerSpecialization({
|
|
839
876
|
kind: label,
|
|
840
|
-
...(owner ? { owner: ensureOwner(owner).slot } : {}),
|
|
877
|
+
...(owner ? { owner: { kind: "component", slot: ensureOwner(owner).slot } } : {}),
|
|
841
878
|
...(analysisSite(call, "component-call") ? { site: analysisSite(call, "component-call") } : {}),
|
|
842
879
|
...(analysisSource(call) ? { source: analysisSource(call) } : {}),
|
|
843
880
|
props: result.props.map(prop => {
|
|
@@ -852,8 +889,18 @@ function createKudzuTransformer({ semantic, handlerUrl, file, sourceFiles, sourc
|
|
|
852
889
|
refs: [...result.rowRefs.map(({ name, source }) => ({ name, kind: "row", ...(analysisSite(source, "hook") ? { site: analysisSite(source, "hook") } : {}), ...(analysisSource(source) ? { source: analysisSource(source) } : {}) })), ...result.ordinaryRefs.map(({ name, source }) => ({ name, kind: "component", ...(analysisSite(source, "hook") ? { site: analysisSite(source, "hook") } : {}), ...(analysisSource(source) ? { source: analysisSource(source) } : {}) }))],
|
|
853
890
|
ids: result.ordinaryIds.map(({ name, source }) => ({ name, ...(analysisSite(source, "hook") ? { site: analysisSite(source, "hook") } : {}), ...(analysisSource(source) ? { source: analysisSource(source) } : {}) }))
|
|
854
891
|
})
|
|
855
|
-
|
|
856
|
-
|
|
892
|
+
result.propStateOwners = new Map(result.props.flatMap(prop => {
|
|
893
|
+
const expression = result.propExpressions.get(prop.name)
|
|
894
|
+
const value = expression && unwrapExpression(expression)
|
|
895
|
+
const reference = value && ts.isIdentifier(value) ? stateOwners.get(value.text) : undefined
|
|
896
|
+
return reference ? [[prop.local, reference]] : []
|
|
897
|
+
}))
|
|
898
|
+
for (const effect of result.effects) {
|
|
899
|
+
effect.stateOwners = result.propStateOwners
|
|
900
|
+
effect.analysisOwner = { kind: "specialization", slot: result.analysis.slot }
|
|
901
|
+
}
|
|
902
|
+
for (const [slot, state] of [...result.rowStates, ...result.ordinaryStates].entries()) state.analysisReference = { kind: "state", owner: { kind: "specialization", slot: result.analysis.slot }, slot }
|
|
903
|
+
for (const [slot, ref] of [...result.rowRefs, ...result.ordinaryRefs].entries()) ref.analysisReference = { specialization: result.analysis.slot, ref: slot }
|
|
857
904
|
return result
|
|
858
905
|
}
|
|
859
906
|
const registerRowHooks = (call, specialization) => {
|
|
@@ -870,7 +917,7 @@ function createKudzuTransformer({ semantic, handlerUrl, file, sourceFiles, sourc
|
|
|
870
917
|
const stateOwners = new Map(stateOwnersByFunction.get(owner))
|
|
871
918
|
for (const state of specialization.rowStates) {
|
|
872
919
|
setters.set(state.setter, state.state)
|
|
873
|
-
stateOwners.set(state.state, state.
|
|
920
|
+
stateOwners.set(state.state, state.analysisReference)
|
|
874
921
|
}
|
|
875
922
|
settersByFunction.set(owner, setters)
|
|
876
923
|
stateOwnersByFunction.set(owner, stateOwners)
|
|
@@ -911,7 +958,7 @@ function createKudzuTransformer({ semantic, handlerUrl, file, sourceFiles, sourc
|
|
|
911
958
|
merged.parent = root.parent
|
|
912
959
|
return merged
|
|
913
960
|
}
|
|
914
|
-
const expandReducerCallbacks = (root, componentSource, call) => {
|
|
961
|
+
const expandReducerCallbacks = (root, componentSource, call, ownership) => {
|
|
915
962
|
const componentImports = clientImportBindings(componentSource, componentSource.fileName, sourceFiles)
|
|
916
963
|
const replacements = new WeakMap()
|
|
917
964
|
let count = 0
|
|
@@ -927,7 +974,7 @@ function createKudzuTransformer({ semantic, handlerUrl, file, sourceFiles, sourc
|
|
|
927
974
|
fail(nestedCalls[0], "Reducer callback props require a component imported from a relative TypeScript module")
|
|
928
975
|
}
|
|
929
976
|
for (const nestedCall of nestedCalls) {
|
|
930
|
-
const nested = specialize(nestedCall, nestedComponent, "Reducer-callback")
|
|
977
|
+
const nested = specialize(nestedCall, nestedComponent, "Reducer-callback", false, false, new Set(), ownership)
|
|
931
978
|
if (nested.effects.length) fail(nestedCall, "Reducer-callback components cannot declare effects")
|
|
932
979
|
nested.root = mergeSpecializedImports(nested.root, nestedComponent.getSourceFile(), nestedCall, nested.effects)
|
|
933
980
|
synthesizeTree(nested.root)
|
|
@@ -1001,7 +1048,7 @@ function createKudzuTransformer({ semantic, handlerUrl, file, sourceFiles, sourc
|
|
|
1001
1048
|
const setters = new Map(parentSetters)
|
|
1002
1049
|
for (const state of aggregate.ordinaryStates) setters.set(state.setter, state.state)
|
|
1003
1050
|
const stateOwners = new Map(parentStateOwners)
|
|
1004
|
-
for (const state of aggregate.ordinaryStates) stateOwners.set(state.state, state.
|
|
1051
|
+
for (const state of aggregate.ordinaryStates) stateOwners.set(state.state, state.analysisReference)
|
|
1005
1052
|
if (jsxSetterCallbackProps(node, setters, functionsForNode(node), reducersForNode(node, reducersByFunction)).length) fail(node, "Setter callbacks cannot cross a second component boundary")
|
|
1006
1053
|
const nested = specialize(node, component, "Nested setter-callback", true, true, new Set(setters.values()), { setters, stateOwners })
|
|
1007
1054
|
if (dynamic && (nested.hookDeclarations.length || nested.effects.length)) fail(node, "Hookful nested setter-callback components require an unconditional or statically truthy render path")
|
|
@@ -1099,6 +1146,7 @@ function createKudzuTransformer({ semantic, handlerUrl, file, sourceFiles, sourc
|
|
|
1099
1146
|
const effectCall = factory.updateCallExpression(entry.call, factory.createIdentifier("__kComponentUseEffect"), entry.call.typeArguments, entry.call.arguments)
|
|
1100
1147
|
synthesizeTree(effectCall)
|
|
1101
1148
|
ts.setOriginalNode(effectCall, entry.source)
|
|
1149
|
+
specializedEffectStateOwners.set(effectCall, { owner: { kind: "specialization", slot: specialization.analysis.slot }, references: specialization.propStateOwners })
|
|
1102
1150
|
return factory.createExpressionStatement(effectCall)
|
|
1103
1151
|
})
|
|
1104
1152
|
const helper = factory.createFunctionDeclaration(
|
|
@@ -1118,8 +1166,8 @@ function createKudzuTransformer({ semantic, handlerUrl, file, sourceFiles, sourc
|
|
|
1118
1166
|
const setters = new Map(settersForNode(call, settersByFunction))
|
|
1119
1167
|
for (const state of specialization.ordinaryStates) setters.set(state.setter, state.state)
|
|
1120
1168
|
settersByFunction.set(helper, setters)
|
|
1121
|
-
const stateOwners = new Map(stateOwnersForNode(call))
|
|
1122
|
-
for (const state of specialization.ordinaryStates) stateOwners.set(state.state, state.
|
|
1169
|
+
const stateOwners = new Map([...stateOwnersForNode(call), ...specialization.propStateOwners])
|
|
1170
|
+
for (const state of specialization.ordinaryStates) stateOwners.set(state.state, state.analysisReference)
|
|
1123
1171
|
stateOwnersByFunction.set(helper, stateOwners)
|
|
1124
1172
|
usesComponentState ||= specialization.ordinaryStates.length > 0
|
|
1125
1173
|
usesComponentId ||= specialization.usesComponentId
|
|
@@ -1162,7 +1210,10 @@ function createKudzuTransformer({ semantic, handlerUrl, file, sourceFiles, sourc
|
|
|
1162
1210
|
if (componentSpecializations.has(call)) fail(call, "Reducer dispatch props cannot be combined with another component specialization")
|
|
1163
1211
|
const specialization = specialize(call, component.function, "Reducer-dispatch")
|
|
1164
1212
|
registerRowHooks(call, specialization)
|
|
1165
|
-
specialization.root = expandReducerCallbacks(specialization.root, component.function.getSourceFile(), call
|
|
1213
|
+
specialization.root = expandReducerCallbacks(specialization.root, component.function.getSourceFile(), call, {
|
|
1214
|
+
setters: new Map([...settersForNode(call, settersByFunction), ...[...specialization.rowStates, ...specialization.ordinaryStates].map(state => [state.setter, state.state])]),
|
|
1215
|
+
stateOwners: new Map([...stateOwnersForNode(call), ...[...specialization.rowStates, ...specialization.ordinaryStates].map(state => [state.state, state.analysisReference])])
|
|
1216
|
+
})
|
|
1166
1217
|
componentSpecializations.set(call, specialization)
|
|
1167
1218
|
reducerComponentCalls.add(call)
|
|
1168
1219
|
}
|
|
@@ -1185,7 +1236,10 @@ function createKudzuTransformer({ semantic, handlerUrl, file, sourceFiles, sourc
|
|
|
1185
1236
|
if (componentSpecializations.has(call)) fail(call, "Reducer dispatch props cannot be combined with another component specialization")
|
|
1186
1237
|
const specialization = specialize(call, component, "Reducer-dispatch")
|
|
1187
1238
|
registerRowHooks(call, specialization)
|
|
1188
|
-
specialization.root = expandReducerCallbacks(specialization.root, componentSource, call
|
|
1239
|
+
specialization.root = expandReducerCallbacks(specialization.root, componentSource, call, {
|
|
1240
|
+
setters: new Map([...settersForNode(call, settersByFunction), ...[...specialization.rowStates, ...specialization.ordinaryStates].map(state => [state.setter, state.state])]),
|
|
1241
|
+
stateOwners: new Map([...stateOwnersForNode(call), ...[...specialization.rowStates, ...specialization.ordinaryStates].map(state => [state.state, state.analysisReference])])
|
|
1242
|
+
})
|
|
1189
1243
|
specialization.root = mergeSpecializedImports(specialization.root, componentSource, call, specialization.effects)
|
|
1190
1244
|
synthesizeTree(specialization.root)
|
|
1191
1245
|
componentSpecializations.set(call, specialization)
|
|
@@ -1337,6 +1391,7 @@ function createKudzuTransformer({ semantic, handlerUrl, file, sourceFiles, sourc
|
|
|
1337
1391
|
const call = factory.updateCallExpression(entry.call, factory.createIdentifier("__kListUseEffect"), entry.call.typeArguments, entry.call.arguments)
|
|
1338
1392
|
synthesizeTree(call)
|
|
1339
1393
|
ts.setOriginalNode(call, entry.source)
|
|
1394
|
+
specializedEffectStateOwners.set(call, { owner: entry.analysisOwner, references: entry.stateOwners ?? specialization.propStateOwners })
|
|
1340
1395
|
return factory.createExpressionStatement(call)
|
|
1341
1396
|
}))
|
|
1342
1397
|
}
|
|
@@ -1375,7 +1430,7 @@ function createKudzuTransformer({ semantic, handlerUrl, file, sourceFiles, sourc
|
|
|
1375
1430
|
specializations: [specialization.analysis?.slot, ...(specialization.specializations ?? [])].filter(slot => slot !== undefined),
|
|
1376
1431
|
rowStates: [...specialization.rowStates, ...specialization.ordinaryStates],
|
|
1377
1432
|
rowRefs: specialization.rowRefs,
|
|
1378
|
-
analysisStateOwners: new Map([...stateOwnersForNode(originalParts.root), ...[...specialization.rowStates, ...specialization.ordinaryStates].map(state => [state.state, state.
|
|
1433
|
+
analysisStateOwners: new Map([...stateOwnersForNode(originalParts.root), ...(specialization.propStateOwners ?? []), ...[...specialization.rowStates, ...specialization.ordinaryStates].map(state => [state.state, state.analysisReference])])
|
|
1379
1434
|
}
|
|
1380
1435
|
for (const calculation of specialization.calculations) {
|
|
1381
1436
|
ts.setParentRecursive(calculation, false)
|
|
@@ -1404,8 +1459,6 @@ function createKudzuTransformer({ semantic, handlerUrl, file, sourceFiles, sourc
|
|
|
1404
1459
|
)
|
|
1405
1460
|
}
|
|
1406
1461
|
|
|
1407
|
-
let activeStateOwners
|
|
1408
|
-
let activeKeyedBlock
|
|
1409
1462
|
const visitWithStateOwners = (node, stateOwners) => {
|
|
1410
1463
|
const previous = activeStateOwners
|
|
1411
1464
|
activeStateOwners = new Map([...(previous ?? []), ...stateOwners])
|
|
@@ -1419,18 +1472,23 @@ function createKudzuTransformer({ semantic, handlerUrl, file, sourceFiles, sourc
|
|
|
1419
1472
|
usesList = true
|
|
1420
1473
|
const blockSlot = moduleIR.keyedBlocks.length
|
|
1421
1474
|
let listSource = listParts.state
|
|
1422
|
-
|
|
1475
|
+
const collectionName = listParts.state?.text
|
|
1476
|
+
const collectionReference = collectionName && new Map([...stateOwnersForNode(node), ...(activeStateOwners ?? [])]).get(collectionName)
|
|
1477
|
+
const collectionSymbol = listParts.state && bindingIndex.resolveReference(listParts.state, node)?.slot
|
|
1478
|
+
let collection = collectionReference
|
|
1479
|
+
? { kind: "signal", signal: descriptors.signal(collectionName, node, new Map([[collectionName, collectionReference]])) }
|
|
1480
|
+
: collectionSymbol !== undefined ? { kind: "symbol", symbol: collectionSymbol } : { kind: "static" }
|
|
1423
1481
|
if (listParts.calculation) {
|
|
1424
1482
|
usesBinding = true
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
collection = { kind: "binding",
|
|
1483
|
+
const compiled = descriptors.compileReactiveBinding(listParts.calculation, { setters: settersForNode(node, settersByFunction), importBindings, keyedBlock: blockSlot })
|
|
1484
|
+
listSource = compiled.node
|
|
1485
|
+
collection = { kind: "binding", binding: compiled.binding }
|
|
1428
1486
|
}
|
|
1429
1487
|
const derived = listParts.selector?.length ? descriptors.registerDerived("selector", listParts.selector, listParts.selectorStates, node) : undefined
|
|
1430
1488
|
const parent = activeKeyedBlock?.block
|
|
1431
|
-
const rowStates = (listParts.rowStates ?? []).map(state => ({ name: state.state, setter: state.setter,
|
|
1432
|
-
const rowRefs = (listParts.rowRefs ?? []).map(ref => ({ name: ref.name,
|
|
1433
|
-
const specializations = [...new Set([...(listParts.specializations ?? []), ...rowStates.map(state => state.owner), ...rowRefs.map(ref => ref.
|
|
1489
|
+
const rowStates = (listParts.rowStates ?? []).map(state => ({ name: state.state, setter: state.setter, signal: descriptors.signal(state.state, node, new Map([[state.state, state.analysisReference]])), ...(analysisSource(state.source) ? { source: analysisSource(state.source) } : {}) }))
|
|
1490
|
+
const rowRefs = (listParts.rowRefs ?? []).map(ref => ({ name: ref.name, ...ref.analysisReference, ...(analysisSource(ref.source) ? { source: analysisSource(ref.source) } : {}) }))
|
|
1491
|
+
const specializations = [...new Set([...(listParts.specializations ?? []), ...rowStates.map(state => moduleIR.signals[state.signal].reference.owner?.slot), ...rowRefs.map(ref => ref.specialization)].filter(value => value !== undefined))]
|
|
1434
1492
|
const block = descriptors.registerKeyedBlock({
|
|
1435
1493
|
...(analysisSite(node, "keyed-list") ? { site: analysisSite(node, "keyed-list") } : {}),
|
|
1436
1494
|
...(analysisSource(node) ? { source: analysisSource(node) } : {}),
|
|
@@ -1444,7 +1502,7 @@ function createKudzuTransformer({ semantic, handlerUrl, file, sourceFiles, sourc
|
|
|
1444
1502
|
indexed: listParts.indexed,
|
|
1445
1503
|
static: Boolean(listParts.static),
|
|
1446
1504
|
...(derived ? { selector: derived.slot } : {}),
|
|
1447
|
-
|
|
1505
|
+
selectorSignals: [...(listParts.selectorStates ?? [])].map(name => descriptors.signal(name, node)),
|
|
1448
1506
|
specializations,
|
|
1449
1507
|
rowStates,
|
|
1450
1508
|
rowRefs
|
|
@@ -1462,7 +1520,7 @@ function createKudzuTransformer({ semantic, handlerUrl, file, sourceFiles, sourc
|
|
|
1462
1520
|
jsonExpression(derived?.selector ?? listParts.selector ?? [], factory),
|
|
1463
1521
|
block.indexed ? factory.createTrue() : factory.createFalse()
|
|
1464
1522
|
]
|
|
1465
|
-
if (block.
|
|
1523
|
+
if (block.selectorSignals.length || block.static) arguments_.push(factory.createArrayLiteralExpression([...(listParts.selectorStates ?? [])].map(name => factory.createArrayLiteralExpression([factory.createStringLiteral(name), factory.createIdentifier(name)]))))
|
|
1466
1524
|
if (block.static) arguments_.push(factory.createTrue())
|
|
1467
1525
|
return factory.updateJsxExpression(node, factory.createCallExpression(factory.createIdentifier("__kList"), undefined, arguments_))
|
|
1468
1526
|
}
|
|
@@ -1480,7 +1538,7 @@ function createKudzuTransformer({ semantic, handlerUrl, file, sourceFiles, sourc
|
|
|
1480
1538
|
if (specializedDeclarations.has(node)) return node
|
|
1481
1539
|
if (componentSpecializations.has(node)) {
|
|
1482
1540
|
const specialization = componentSpecializations.get(node)
|
|
1483
|
-
const stateOwners = new Map([...stateOwnersForNode(node), ...[...specialization.rowStates, ...specialization.ordinaryStates].map(state => [state.state, state.
|
|
1541
|
+
const stateOwners = new Map([...stateOwnersForNode(node), ...(specialization.propStateOwners ?? []), ...[...specialization.rowStates, ...specialization.ordinaryStates].map(state => [state.state, state.analysisReference])])
|
|
1484
1542
|
return visitWithStateOwners(specialization.root, stateOwners)
|
|
1485
1543
|
}
|
|
1486
1544
|
|
|
@@ -1595,6 +1653,7 @@ function createKudzuTransformer({ semantic, handlerUrl, file, sourceFiles, sourc
|
|
|
1595
1653
|
importBindings: specializedEffect?.imports ?? importBindings,
|
|
1596
1654
|
listItem: dependencyItem,
|
|
1597
1655
|
keyedBlock: activeKeyedBlock?.block.slot,
|
|
1656
|
+
stateOwners: new Map([...stateOwnersForNode(callbackArgument), ...stateOwnersForNode(node), ...(specializedEffectStateOwners.get(node)?.references ?? []), ...(activeStateOwners ?? [])]),
|
|
1598
1657
|
deferValues: true,
|
|
1599
1658
|
snapshotNested: returns.cleanup,
|
|
1600
1659
|
liveStates: customHookTimerStates
|
|
@@ -1604,14 +1663,19 @@ function createKudzuTransformer({ semantic, handlerUrl, file, sourceFiles, sourc
|
|
|
1604
1663
|
const derivedDependencies = hasDerivedDependency ? dependencyEntries.map(entry => entry.kind === "derived" ? descriptors.registerDerived("expression", entry.expression, entry.states, entry.source) : undefined) : []
|
|
1605
1664
|
const effectSource = specializedEffect?.source ?? node
|
|
1606
1665
|
const lexicalOwner = nearestFunction(effectSource)
|
|
1666
|
+
const effectStateOwners = new Map([...stateOwnersForNode(callbackArgument), ...stateOwnersForNode(node), ...(specializedEffectStateOwners.get(node)?.references ?? []), ...(activeStateOwners ?? [])])
|
|
1667
|
+
const signalFor = name => descriptors.signal(name, node, effectStateOwners)
|
|
1668
|
+
const subscriptionNames = (hasDerivedDependency ? subscriptionDependencies : ordinaryDependencies).map(dependency => dependency.text)
|
|
1669
|
+
const dependencyStateNames = [...dependencyStates.keys()]
|
|
1607
1670
|
const effect = descriptors.registerEffect(descriptor, {
|
|
1608
1671
|
cleanup: returns.cleanup,
|
|
1609
|
-
dependencies: hasDerivedDependency ? dependencyEntries.map((entry, index) => entry.kind === "derived" ? { kind: "derived", derived: derivedDependencies[index].slot, sources: [...entry.states] } : { kind: "signal",
|
|
1610
|
-
subscriptions:
|
|
1611
|
-
|
|
1672
|
+
dependencies: hasDerivedDependency ? dependencyEntries.map((entry, index) => entry.kind === "derived" ? { kind: "derived", derived: derivedDependencies[index].slot, sources: [...entry.states].map(signalFor) } : { kind: "signal", signal: signalFor(entry.name) }) : ordinaryDependencies.map(dependency => ({ kind: "signal", signal: signalFor(dependency.text) })),
|
|
1673
|
+
subscriptions: subscriptionNames.map(signalFor),
|
|
1674
|
+
dependencySignals: dependencyStateNames.map(signalFor),
|
|
1612
1675
|
itemDependencies,
|
|
1613
1676
|
ownership: {
|
|
1614
1677
|
kind: activeKeyedBlock ? "keyed" : "component",
|
|
1678
|
+
owner: specializedEffectStateOwners.get(node)?.owner ?? fallbackOwner(effectSource),
|
|
1615
1679
|
...(activeKeyedBlock ? { keyedBlock: activeKeyedBlock.block.slot } : {}),
|
|
1616
1680
|
...(lexicalOwner ? { component: { name: ownerName(lexicalOwner), ...(analysisSite(lexicalOwner, "owner") ? { site: analysisSite(lexicalOwner, "owner") } : {}), ...(analysisSource(lexicalOwner) ? { source: analysisSource(lexicalOwner) } : {}) } } : {})
|
|
1617
1681
|
},
|
|
@@ -1619,10 +1683,10 @@ function createKudzuTransformer({ semantic, handlerUrl, file, sourceFiles, sourc
|
|
|
1619
1683
|
...(analysisSite(effectSource, "hook") ? { site: analysisSite(effectSource, "hook") } : {}),
|
|
1620
1684
|
...(analysisSource(effectSource) ? { source: analysisSource(effectSource) } : {})
|
|
1621
1685
|
})
|
|
1622
|
-
const dependencyExpressions = effect.dependencies.map(dependency => dependency.kind === "derived" ? moduleIR.derived[dependency.derived].expression : ["state",
|
|
1686
|
+
const dependencyExpressions = effect.dependencies.map((dependency, index) => dependency.kind === "derived" ? moduleIR.derived[dependency.derived].expression : ["state", dependencyEntries[index]?.name ?? ordinaryDependencies[index].text])
|
|
1623
1687
|
return factory.updateCallExpression(node, node.expression, node.typeArguments, [
|
|
1624
1688
|
callback,
|
|
1625
|
-
factory.createArrayLiteralExpression(
|
|
1689
|
+
factory.createArrayLiteralExpression(subscriptionNames.map(name => factory.createIdentifier(name))),
|
|
1626
1690
|
factory.createStringLiteral(handlerUrl),
|
|
1627
1691
|
factory.createStringLiteral(effect.setup.exportName),
|
|
1628
1692
|
descriptor.states,
|
|
@@ -1631,7 +1695,7 @@ function createKudzuTransformer({ semantic, handlerUrl, file, sourceFiles, sourc
|
|
|
1631
1695
|
effect.cleanup ? factory.createTrue() : factory.createFalse(),
|
|
1632
1696
|
factory.createArrayLiteralExpression(effect.itemDependencies.map(field => factory.createStringLiteral(field))),
|
|
1633
1697
|
hasDerivedDependency ? jsonExpression(dependencyExpressions, factory) : factory.createArrayLiteralExpression(),
|
|
1634
|
-
factory.createArrayLiteralExpression(
|
|
1698
|
+
factory.createArrayLiteralExpression(dependencyStateNames.map(name => factory.createArrayLiteralExpression([factory.createStringLiteral(name), factory.createIdentifier(name)])))
|
|
1635
1699
|
])
|
|
1636
1700
|
}
|
|
1637
1701
|
|
|
@@ -1707,7 +1771,7 @@ function createKudzuTransformer({ semantic, handlerUrl, file, sourceFiles, sourc
|
|
|
1707
1771
|
if ((usedStates.size || captures.size) && !ts.isIdentifier(expression) && !containsJsx(expression)) {
|
|
1708
1772
|
usesBehavior = true
|
|
1709
1773
|
usesBinding = true
|
|
1710
|
-
return factory.updateJsxExpression(node, descriptors.compileReactiveBinding(expression, { setters, importBindings }))
|
|
1774
|
+
return factory.updateJsxExpression(node, descriptors.compileReactiveBinding(expression, { setters, importBindings }).node)
|
|
1711
1775
|
}
|
|
1712
1776
|
}
|
|
1713
1777
|
|
|
@@ -1721,7 +1785,7 @@ function createKudzuTransformer({ semantic, handlerUrl, file, sourceFiles, sourc
|
|
|
1721
1785
|
usesBehavior = true
|
|
1722
1786
|
usesBinding = true
|
|
1723
1787
|
const compiled = descriptors.compileReactiveBinding(expression, { setters, importBindings })
|
|
1724
|
-
return factory.updateJsxAttribute(node, node.name, factory.createJsxExpression(undefined, compiled))
|
|
1788
|
+
return factory.updateJsxAttribute(node, node.name, factory.createJsxExpression(undefined, compiled.node))
|
|
1725
1789
|
}
|
|
1726
1790
|
}
|
|
1727
1791
|
|
|
@@ -1729,7 +1793,7 @@ function createKudzuTransformer({ semantic, handlerUrl, file, sourceFiles, sourc
|
|
|
1729
1793
|
const setters = settersForNode(node, settersByFunction)
|
|
1730
1794
|
const event = descriptors.compileEvent(node.initializer.expression, {
|
|
1731
1795
|
owner: fallbackOwner(node),
|
|
1732
|
-
stateOwners:
|
|
1796
|
+
stateOwners: new Map([...stateOwnersForNode(node), ...(activeStateOwners ?? [])]),
|
|
1733
1797
|
setters,
|
|
1734
1798
|
reducers: reducersForNode(node, reducersByFunction),
|
|
1735
1799
|
functions: functionsForNode(node),
|
|
@@ -2023,7 +2087,7 @@ function validateKeyedList(parts, sourceFile, setters, rowStates, componentSpeci
|
|
|
2023
2087
|
specializations: [specialization?.analysis?.slot, ...(specialization?.specializations ?? [])].filter(slot => slot !== undefined),
|
|
2024
2088
|
rowStates: specializedStates,
|
|
2025
2089
|
rowRefs: specialization?.rowRefs ?? [],
|
|
2026
|
-
analysisStateOwners: new Map([...(parts.analysisStateOwners ?? []), ...specializedStates.map(state => [state.state, state.
|
|
2090
|
+
analysisStateOwners: new Map([...(parts.analysisStateOwners ?? []), ...(specialization?.propStateOwners ?? []), ...specializedStates.map(state => [state.state, state.analysisReference])])
|
|
2027
2091
|
}
|
|
2028
2092
|
for (const calculation of specialization?.calculations ?? []) {
|
|
2029
2093
|
ts.setParentRecursive(calculation, false)
|