@kudzujs/core 0.16.22 → 0.16.24
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/PERFORMANCE.md +74 -0
- package/README.md +1 -1
- package/RELEASES.md +59 -0
- package/docs/next-architecture/application-capability-release-plan.md +42 -1
- package/docs/next-architecture/compiler-current-architecture.md +1 -1
- package/docs/next-architecture/versioning.md +1 -1
- package/framework/compiler/collection-analysis.mjs +15 -2
- package/framework/compiler/source-compiler.mjs +44 -7
- package/package.json +1 -1
package/PERFORMANCE.md
CHANGED
|
@@ -76,6 +76,80 @@ route handlers. Benchmark contract and budget deltas are zero; the production AI
|
|
|
76
76
|
comparison is not rerun until selected-branch reactive text is also addressed.
|
|
77
77
|
This follow-up ships as `@kudzujs/core@0.16.22`.
|
|
78
78
|
|
|
79
|
+
The 2026-09-04 selected-branch follow-up lowers a reactive scalar branch through
|
|
80
|
+
the existing text-binding compiler path. It adds no semantic primitive,
|
|
81
|
+
compiler pass, runtime concept, API, dependency, or shared-runtime source byte;
|
|
82
|
+
net production growth is 11 lines in one existing compiler file. The focused
|
|
83
|
+
fixture covers an update that remains in the selected branch and a later branch
|
|
84
|
+
replacement. Required Chrome, all 302 tests, `npm run check`, and package smoke
|
|
85
|
+
pass. Rebuilding the five retained commerce sources against this tree changes
|
|
86
|
+
their frozen `below` check from false to true, so all five pass build, behavior,
|
|
87
|
+
accessibility, browser, and output acceptance while retaining the zero-JavaScript
|
|
88
|
+
static sibling. The existing checkout handler grows by 147-154 transferred bytes
|
|
89
|
+
across those authored variants; no shared runtime module was added. Benchmark
|
|
90
|
+
contracts and budgets are unchanged. This is a same-source compiler follow-up,
|
|
91
|
+
not a new equal-condition model run, so the published 11/25 versus 24/25 result
|
|
92
|
+
and the `1.0.0` block remain unchanged pending the complete production rerun.
|
|
93
|
+
This follow-up ships as `@kudzujs/core@0.16.23`.
|
|
94
|
+
|
|
95
|
+
### Imported Search Source Replay (2026-09-07)
|
|
96
|
+
|
|
97
|
+
Compiler follow-up shipping as `0.16.24` to `f88279024e68c875ea35592a1ab363bde1cec9d0`
|
|
98
|
+
(`0.16.23`), on Linux x64, Node 24.14.0 and Chrome 152.0.7977.64. Authorizing
|
|
99
|
+
evidence is the initial aliased search patch at line 50 of
|
|
100
|
+
`test-results/ai-delivery-production/0.21.4-gpt-5.6-sol-r3-kudzu-0.16.23-continuation-20260907/content/attempts/content-kudzu-0/adapter.stdout`.
|
|
101
|
+
The reduced fixture first reproduced both the source-located generic expression
|
|
102
|
+
diagnostic and the direct-filter-count `query.trim is not a function` build error.
|
|
103
|
+
|
|
104
|
+
Imported collection discovery now follows top-level const selector aliases from
|
|
105
|
+
map/count uses. Filter predicates encode proven pure query locals using the
|
|
106
|
+
existing expression language, and count dependencies use signal build values in
|
|
107
|
+
scratch rather than invoking string methods on signal objects. Existing binding,
|
|
108
|
+
selector, keyed, and conditional consumers remain authoritative.
|
|
109
|
+
|
|
110
|
+
| Metric | Result |
|
|
111
|
+
|---|---|
|
|
112
|
+
| Semantic primitives / IR kinds / runtime concepts / APIs | +0 / +0 / +0 / +0 |
|
|
113
|
+
| Core passes / ordered normalization entries / adapters | +0 / +0 / +0 |
|
|
114
|
+
| Existing normalization/proof boundaries extended | 3: import roots, pure filter locals, count build values |
|
|
115
|
+
| Core semantic LOC, unchanged 0.9 file set | +39, 5,466 -> 5,505; source compiler +26, collection analysis +13 |
|
|
116
|
+
| Regression evidence | 1 reduced fixture; 3 positive source variants, 6 negative forms, 4 tests |
|
|
117
|
+
| Reduced route JS | 9 files; 32,190 raw / 12,324 aggregate gzip B in every variant |
|
|
118
|
+
| Delta from fully inlined control | 0 raw / 0 gzip B; identical JS paths and contents |
|
|
119
|
+
| Reduced static sibling | Complete HTML, 0 B JavaScript |
|
|
120
|
+
| Full retained application replay | 11 pages, 2 interactive; build and existing content acceptance pass |
|
|
121
|
+
| Full application JS vs earlier retained inlined artifacts | 13 files; 55,447 raw / 20,976 aggregate gzip B; all paths/bytes identical |
|
|
122
|
+
| Build/browser timing and AI-cost delta | Not measured; no fresh model benchmark or ranking claim |
|
|
123
|
+
|
|
124
|
+
The full replay restores the initial page and stylesheet patch over the content
|
|
125
|
+
starter while preserving the retained application's other source. It uses the
|
|
126
|
+
current workspace compiler and the unchanged existing content acceptance runner,
|
|
127
|
+
not a new adapter invocation. It passes search/count/empty/restore, accessibility,
|
|
128
|
+
zero-JavaScript static output, and no browser exceptions or failed requests.
|
|
129
|
+
Raw build/acceptance output and `replay.json` are retained separately under
|
|
130
|
+
`test-results/ai-delivery-production/imported-article-search-source-replay-20260907/`. Its sorted
|
|
131
|
+
HTML/CSS/JS path-plus-NUL-plus-content SHA-256 is
|
|
132
|
+
`92d96f7cfdd4535de28ff9b7665aa241815ed2649e6db14fc22c5eadc8258904`.
|
|
133
|
+
The original attempt and earlier benchmark results are not rewritten.
|
|
134
|
+
|
|
135
|
+
Release verification: `npm run check` (224 pages, 2 interactive),
|
|
136
|
+
`KUDZU_REQUIRE_CHROME=1 npm test` (standalone project ownership gate, then
|
|
137
|
+
308/308 tests), `npm run test:package` (4 installed packages, one static page),
|
|
138
|
+
and both package dry-runs pass. The release gates ran sequentially with a
|
|
139
|
+
1,200-second tool limit, avoiding overlapping fixture tests.
|
|
140
|
+
Chrome regression checks also prove retained rows and fresh remounts; they are
|
|
141
|
+
correctness checks, not latency samples. Mutable aliases, impure calls, cycles,
|
|
142
|
+
collection escapes, mutating sort, and predicate-parameter state collisions remain
|
|
143
|
+
diagnosed. No general local/callback graph or effect cleanup claim is added.
|
|
144
|
+
The same release includes separate r4 benchmark lifecycle integrity changes:
|
|
145
|
+
atomic partial-usage checkpoints, bounded incremental stdout/stderr evidence,
|
|
146
|
+
setup-inclusive deadlines, owned process-tree termination, and incomplete timeout
|
|
147
|
+
attribution. Two Linux lifecycle regressions cover setup and model timeouts,
|
|
148
|
+
partial usage, descendant termination, and passing acceptance after adapter timeout.
|
|
149
|
+
The Windows `taskkill` path remains untested. Future r4 protocols pin published
|
|
150
|
+
`0.16.23` starters; historical raw evidence remains untouched. The published
|
|
151
|
+
11/25 versus 24/25 comparison and `1.0.0` block remain unchanged.
|
|
152
|
+
|
|
79
153
|
## 0.21.2 Browser Performance And Memory
|
|
80
154
|
|
|
81
155
|
Measured 2026-09-03 at revision
|
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.16.x`: the compiler API and supported TSX surface may change.
|
|
16
16
|
|
|
17
|
-
**Latest release: 0.16.
|
|
17
|
+
**Latest release: 0.16.24 - imported article search and benchmark integrity.** Ordinary imported search aliases and counts compile through existing semantics with byte-identical JavaScript to the inlined control. Revision-4 benchmark protocols preserve partial evidence and enforce setup-inclusive deadlines for future runs; no fresh AI performance result is claimed, and `1.0.0` remains blocked. Read the [release notes](./RELEASES.md#01624---imported-article-search-and-benchmark-integrity), open the [release page](https://github.com/kudzujs/kudzu/releases/tag/v0.16.24), 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,64 @@
|
|
|
1
1
|
# Kudzu Releases
|
|
2
2
|
|
|
3
|
+
## 0.16.24 - Imported Article Search And Benchmark Integrity
|
|
4
|
+
|
|
5
|
+
Kudzu 0.16.24 compiles the original authored imported-article search shape
|
|
6
|
+
without an inlined workaround and hardens evidence lifecycle integrity for
|
|
7
|
+
future AI delivery runs. This is source replay evidence, not a new AI score.
|
|
8
|
+
|
|
9
|
+
### Changed in 0.16.24
|
|
10
|
+
|
|
11
|
+
- Follows imported collection selector aliases, normalizes proven pure query
|
|
12
|
+
locals, and evaluates count dependencies from signal build values.
|
|
13
|
+
- Adds 39 net core semantic lines across two existing compiler files, with no
|
|
14
|
+
new semantic primitive, pass, IR kind, runtime concept, API, or dependency.
|
|
15
|
+
- Covers three byte-identical search variants, six rejected unsafe forms,
|
|
16
|
+
retained keyed identity, fresh remounts, and a zero-JavaScript static sibling.
|
|
17
|
+
- Replays the original authored application search successfully through the
|
|
18
|
+
unchanged content acceptance: 13 JavaScript files, 55,447 raw / 20,976 gzip
|
|
19
|
+
bytes, identical to the retained inlined application's JavaScript.
|
|
20
|
+
- Checkpoints adapter usage atomically, streams bounded output evidence, shares
|
|
21
|
+
the deadline with setup, kills owned process trees on timeout, and prevents
|
|
22
|
+
incomplete or timed-out attempts from becoming successful attribution.
|
|
23
|
+
- Freezes r4 protocols with published 0.16.23 starters for future runs only.
|
|
24
|
+
Historical raw evidence is unchanged. Linux lifecycle regressions cover the
|
|
25
|
+
termination paths; the Windows `taskkill` path remains untested.
|
|
26
|
+
- Makes no fresh timing, cost, or AI performance claim. The published 11/25
|
|
27
|
+
versus 24/25 comparison and the `1.0.0` block remain unchanged.
|
|
28
|
+
- Updates `create-kudzu@0.1.149` to generate `@kudzujs/core@^0.16.24` projects.
|
|
29
|
+
|
|
30
|
+
### Upgrade
|
|
31
|
+
|
|
32
|
+
```sh
|
|
33
|
+
npm install @kudzujs/core@^0.16.24
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## 0.16.23 - Reactive Conditional Branch Text
|
|
37
|
+
|
|
38
|
+
Kudzu 0.16.23 removes the second commerce compiler blocker selected by the
|
|
39
|
+
corrected `0.21.4` AI delivery evidence: scalar text inside a selected
|
|
40
|
+
conditional branch remains reactive without restructuring ordinary TSX.
|
|
41
|
+
|
|
42
|
+
### Changed in 0.16.23
|
|
43
|
+
|
|
44
|
+
- Lowers reactive scalar conditional branches through the existing text-binding
|
|
45
|
+
compiler path while preserving conditional ownership and static HTML.
|
|
46
|
+
- Verifies updates that remain in one branch and later replace that branch in
|
|
47
|
+
required Chrome.
|
|
48
|
+
- Passes all five retained commerce sources with `empty`, `below`, and `reached`
|
|
49
|
+
true while preserving their zero-JavaScript static siblings.
|
|
50
|
+
- Adds no semantic primitive, compiler pass, runtime concept, public API,
|
|
51
|
+
dependency, or shared-runtime source byte; `1.0.0` remains unauthorized pending
|
|
52
|
+
a complete equal-condition rerun.
|
|
53
|
+
- Updates `create-kudzu@0.1.148` to generate projects on
|
|
54
|
+
`@kudzujs/core@^0.16.23`.
|
|
55
|
+
|
|
56
|
+
### Upgrade
|
|
57
|
+
|
|
58
|
+
```sh
|
|
59
|
+
npm install @kudzujs/core@^0.16.23
|
|
60
|
+
```
|
|
61
|
+
|
|
3
62
|
## 0.16.22 - Reactive Collection Count Reuse
|
|
4
63
|
|
|
5
64
|
Kudzu 0.16.22 removes the first compiler blocker selected by the corrected
|
|
@@ -2169,6 +2169,47 @@ comparison must be rerun only after the remaining selected-branch reactive-text
|
|
|
2169
2169
|
blocker is addressed. This follow-up is released as `@kudzujs/core@0.16.22`
|
|
2170
2170
|
with `create-kudzu@0.1.147`. `1.0.0` remains unauthorized.
|
|
2171
2171
|
|
|
2172
|
+
The second evidence-selected follow-up now lowers reactive scalar text inside a
|
|
2173
|
+
selected conditional branch through the existing binding descriptor and
|
|
2174
|
+
conditional ownership paths. It adds no semantic primitive, compiler pass,
|
|
2175
|
+
runtime concept, API, dependency, or shared-runtime source byte; one production
|
|
2176
|
+
compiler file grows by net 11 lines. A focused fixture proves same-branch text
|
|
2177
|
+
updates and branch replacement, and required Chrome, all 302 tests, package
|
|
2178
|
+
smoke, and `npm run check` pass. The five retained commerce sources now pass
|
|
2179
|
+
their complete acceptance with `empty`, `below`, and `reached` all true and a
|
|
2180
|
+
zero-JavaScript static sibling. Their route-specific checkout handlers add
|
|
2181
|
+
147-154 transferred bytes; no runtime module or benchmark contract changes.
|
|
2182
|
+
This does not revise the published equal-condition score without a complete
|
|
2183
|
+
rerun, so `1.0.0` remains unauthorized. This follow-up is released as
|
|
2184
|
+
`@kudzujs/core@0.16.23` with `create-kudzu@0.1.148`.
|
|
2185
|
+
|
|
2186
|
+
The user-approved 2026-09-07 `0.16.24` continuation addresses the initial r3
|
|
2187
|
+
content patch's imported collection alias and direct filter-count failures.
|
|
2188
|
+
[`imported-article-search`](../../test/fixtures/imported-article-search/README.md)
|
|
2189
|
+
reproduced both failures before changes. The reduction extends existing import
|
|
2190
|
+
discovery, pure selector normalization, and count build-value lowering, with
|
|
2191
|
+
existing BindingIR/DerivedIR/KeyedBlockIR consumers and route/keyed/conditional
|
|
2192
|
+
ownership. It adds no primitive, IR kind, pass, runtime concept, API, or adapter;
|
|
2193
|
+
the two core files grow by net 39 lines. Three source variants produce identical
|
|
2194
|
+
JavaScript (32,190 raw / 12,324 gzip B) and six unsafe neighboring forms remain
|
|
2195
|
+
source-located errors. Required Chrome verifies search/count/empty transitions,
|
|
2196
|
+
retained keys, fresh remounts, and static zero-JavaScript exclusion. Check, the
|
|
2197
|
+
project ownership gate plus 308 tests, package smoke, and both package dry-runs pass.
|
|
2198
|
+
|
|
2199
|
+
The full initial-source replay passes the unchanged content acceptance and emits
|
|
2200
|
+
the same 13 JavaScript files/bytes as the earlier retained inlined workaround
|
|
2201
|
+
(55,447 raw / 20,976 gzip B). This is a source replay, not a fresh model trial or
|
|
2202
|
+
replacement benchmark score; no timing or AI-cost improvement is claimed.
|
|
2203
|
+
[`PERFORMANCE.md`](../../PERFORMANCE.md#imported-search-source-replay-2026-09-07)
|
|
2204
|
+
records commands, provenance, accounting, and limits. Preserve the original r3
|
|
2205
|
+
evidence. The release also includes the reviewed r4 protocol/adapter lifecycle
|
|
2206
|
+
work: atomic partial traces, bounded incremental output, setup-inclusive deadlines,
|
|
2207
|
+
owned process-tree termination, and incomplete timeout attribution. Two Linux
|
|
2208
|
+
lifecycle regressions protect future-run integrity; Windows termination remains
|
|
2209
|
+
untested. The frozen r4 starters stay on published `0.16.23`; old raw records are
|
|
2210
|
+
not reinterpreted. This packet ships as `@kudzujs/core@0.16.24` with
|
|
2211
|
+
`create-kudzu@0.1.149`, without authorizing `1.0.0` or a new AI performance claim.
|
|
2212
|
+
|
|
2172
2213
|
## `1.0.0`: Stable Application Model
|
|
2173
2214
|
|
|
2174
2215
|
`1.0.0` is authorized only after every required `0.21.x` gate passes. It means:
|
|
@@ -2314,4 +2355,4 @@ release transaction where possible or document and publish a forward-fix patch.
|
|
|
2314
2355
|
| `0.21.1` | Released as `0.16.16` | Preserve 100/1,000/10,000-route phase, RSS, output, digest, incremental-equivalence, recovery, and bounded canonical-AST retention evidence. | Full 10,000-route report has one complete measured run after two measured-stage diagnostics; no projection used |
|
|
2315
2356
|
| `0.21.2` | Closed by existing semantics | Preserve initial/session/lazy bytes, keyed/range/navigation median alarms, and bounded endurance ownership evidence. | Same-revision absolute gates; structural artifact bytes are not compressed network transfer |
|
|
2316
2357
|
| `0.21.3` | Released as `0.16.17` | Preserve exact package, browser, benchmark, registry, and fresh-install evidence. | One initial navigation benchmark invocation stalled at the outer timeout; its clean seven-profile rerun passed all alarms |
|
|
2317
|
-
| `0.21.4` | Measured gate failed;
|
|
2358
|
+
| `0.21.4` | Measured gate failed; selected blockers released through `0.16.24` | Preserve historical raw batches and failed success/cost decision; r4 lifecycle integrity applies only to future runs. Keep `1.0.0` blocked pending a complete rerun. | Published result remains Kudzu 11/25 versus React + Vite 24/25; commerce replay passes 5/5 and original imported search replay passes with unchanged JavaScript |
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Current Compiler Architecture
|
|
2
2
|
|
|
3
|
-
This maps the current `0.16.
|
|
3
|
+
This maps the current `0.16.24` architecture, built on the completed `0.9.0` semantic-compression release and `0.8.23` Goal A compiler foundation. The `0.21.4` AI delivery gate is measured and blocked; file and function names are the stable references, while line numbers are intentionally omitted because later work may still move code.
|
|
4
4
|
|
|
5
5
|
## Responsibility Map
|
|
6
6
|
|
|
@@ -66,7 +66,7 @@ Keep each patch behavior-preserving and independently reviewable. If a boundary
|
|
|
66
66
|
|
|
67
67
|
## Generator Versions
|
|
68
68
|
|
|
69
|
-
`create-kudzu@0.1.
|
|
69
|
+
`create-kudzu@0.1.149` retains the explicit install instructions and generates projects with `@kudzujs/core@^0.16.24`.
|
|
70
70
|
|
|
71
71
|
## Release Boundary
|
|
72
72
|
|
|
@@ -73,7 +73,7 @@ export function analyzeCollectionPipeline(expression, options) {
|
|
|
73
73
|
if (!source) return undefined
|
|
74
74
|
const parameters = collectionParameters(value.arguments[0], "Rendered collection filter()", fail)
|
|
75
75
|
const selectorStates = new Set(source.selectorStates)
|
|
76
|
-
return { ...source, selector: [...source.selector, ["filter", collectionExpression(unwrapExpression(value.arguments[0].body), { parameters, fail, stateNames, selectorStates })]], selectorStates }
|
|
76
|
+
return { ...source, selector: [...source.selector, ["filter", collectionExpression(unwrapExpression(value.arguments[0].body), { parameters, fail, stateNames, selectorStates, declarations })]], selectorStates }
|
|
77
77
|
}
|
|
78
78
|
if (method === "flatMap") {
|
|
79
79
|
if (value.arguments.length !== 1) fail(value, "Rendered collection flatMap() requires one inline projector")
|
|
@@ -122,7 +122,7 @@ export function analyzeCollectionPipeline(expression, options) {
|
|
|
122
122
|
}
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
-
export function collectionExpression(expression, { parameters = {}, fail, stateNames = new Set(), selectorStates = new Set() }) {
|
|
125
|
+
export function collectionExpression(expression, { parameters = {}, fail, stateNames = new Set(), selectorStates = new Set(), declarations, aliases = new Set() }) {
|
|
126
126
|
const encode = node => {
|
|
127
127
|
node = unwrapExpression(node)
|
|
128
128
|
if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node) || ts.isNumericLiteral(node)) return ["value", ts.isNumericLiteral(node) ? Number(node.text) : node.text]
|
|
@@ -137,6 +137,19 @@ export function collectionExpression(expression, { parameters = {}, fail, stateN
|
|
|
137
137
|
selectorStates.add(node.text)
|
|
138
138
|
return ["state", node.text]
|
|
139
139
|
}
|
|
140
|
+
const entries = declarations?.get(node.text)
|
|
141
|
+
if (entries?.length === 1 && entries[0].node.parent?.parent?.parent === nearestFunction(entries[0].node)?.body) {
|
|
142
|
+
if (aliases.has(node.text)) fail(node, `Rendered collection expression local cycle at "${node.text}"`)
|
|
143
|
+
aliases.add(node.text)
|
|
144
|
+
const dependencies = new Set()
|
|
145
|
+
const result = collectionExpression(entries[0].initializer, { fail, stateNames, selectorStates: dependencies, declarations, aliases })
|
|
146
|
+
for (const name of dependencies) {
|
|
147
|
+
if (name === parameters.item || name === parameters.index) fail(node, `Rendered collection expression local "${node.text}" cannot capture state shadowed by predicate parameters`)
|
|
148
|
+
selectorStates.add(name)
|
|
149
|
+
}
|
|
150
|
+
aliases.delete(node.text)
|
|
151
|
+
return result
|
|
152
|
+
}
|
|
140
153
|
fail(node, `Rendered collection expression identifier "${node.text}" is not allowed`)
|
|
141
154
|
}
|
|
142
155
|
if (ts.isPropertyAccessExpression(node)) {
|
|
@@ -1149,7 +1149,7 @@ function createKudzuTransformer({ semantic, handlerUrl, file, sourceFiles, sourc
|
|
|
1149
1149
|
if (!isUnshadowedGlobal(roundAccess.expression, sourceFile)) fail(roundAccess.expression, "Reactive JSX Intl.NumberFormat requires the unshadowed global Math object")
|
|
1150
1150
|
collectionExpression(rounded.arguments[0], { fail: (node, message) => fail(node, message.replace("Rendered collection", "Reactive JSX local")), stateNames: allowedNames })
|
|
1151
1151
|
}
|
|
1152
|
-
const resolveReactiveJsxExpression = (expression, owner, setters) => {
|
|
1152
|
+
const resolveReactiveJsxExpression = (expression, owner, setters, buildDeclarations) => {
|
|
1153
1153
|
const declarations = jsxLocalDeclarations.get(owner)
|
|
1154
1154
|
if (!declarations) return expression
|
|
1155
1155
|
const substitutions = new Map()
|
|
@@ -1187,6 +1187,7 @@ function createKudzuTransformer({ semantic, handlerUrl, file, sourceFiles, sourc
|
|
|
1187
1187
|
const captures = captureNames(expanded, expanded, setters, bindingIndex)
|
|
1188
1188
|
const allowedNames = new Set([...setters.values(), ...captures])
|
|
1189
1189
|
validateReactiveJsxExpression(expanded, allowedNames, setters)
|
|
1190
|
+
if (buildDeclarations) for (const name of substitutions.keys()) buildDeclarations.add(declarations.get(name)[0].node)
|
|
1190
1191
|
return expanded
|
|
1191
1192
|
}
|
|
1192
1193
|
const componentSpecializations = new WeakMap()
|
|
@@ -1696,6 +1697,7 @@ function createKudzuTransformer({ semantic, handlerUrl, file, sourceFiles, sourc
|
|
|
1696
1697
|
const collectionAliasUses = rawRenderedLists.flatMap(({ parts }) => parts.aliasUses ?? [])
|
|
1697
1698
|
const collectionAliasDeclarations = new Set(rawRenderedLists.flatMap(({ parts }) => parts.aliasDeclarations ?? []))
|
|
1698
1699
|
const collectionLengthDeclarations = new Set()
|
|
1700
|
+
const collectionValueDeclarations = new Set()
|
|
1699
1701
|
for (const declaration of collectionAliasDeclarations) {
|
|
1700
1702
|
const owner = nearestFunction(declaration)
|
|
1701
1703
|
const unsupported = identifierReferences(owner.body, declaration.name.text).find(reference => {
|
|
@@ -1713,6 +1715,19 @@ function createKudzuTransformer({ semantic, handlerUrl, file, sourceFiles, sourc
|
|
|
1713
1715
|
})
|
|
1714
1716
|
if (unsupported) fail(unsupported, `Rendered collection alias "${declaration.name.text}" may only be used as a rendered collection source`)
|
|
1715
1717
|
}
|
|
1718
|
+
for (const [owner, declarations] of jsxLocalDeclarations) for (const entries of declarations.values()) {
|
|
1719
|
+
if (entries.length !== 1 || entries[0].node.parent?.parent?.parent !== owner?.body) continue
|
|
1720
|
+
const { node, initializer } = entries[0]
|
|
1721
|
+
const value = unwrapExpression(initializer)
|
|
1722
|
+
if (!ts.isPropertyAccessExpression(value) || value.name.text !== "length") continue
|
|
1723
|
+
const setters = settersForNode(node, settersByFunction)
|
|
1724
|
+
const expression = resolveReactiveJsxExpression(initializer, owner, setters, collectionValueDeclarations)
|
|
1725
|
+
if (!referencedStateNames(expression, setters).size) continue
|
|
1726
|
+
const collection = analyzeCollectionPipeline(unwrapExpression(expression).expression, { setters, declarations, fail, importedCollections, stateNames: new Set(setters.values()), importedCollectionTransforms })
|
|
1727
|
+
if (!collection?.selector.length) continue
|
|
1728
|
+
validateReactiveJsxExpression(expression, new Set(setters.values()), setters)
|
|
1729
|
+
collectionLengthDeclarations.add(node)
|
|
1730
|
+
}
|
|
1716
1731
|
const rejectUnsupportedRenderControl = node => {
|
|
1717
1732
|
if (ts.isIfStatement(node) && containsRenderControl(node, jsxLocalsByFunction.get(nearestFunction(node)) ?? new Set())) {
|
|
1718
1733
|
const setters = settersForNode(node, settersByFunction)
|
|
@@ -1872,8 +1887,19 @@ function createKudzuTransformer({ semantic, handlerUrl, file, sourceFiles, sourc
|
|
|
1872
1887
|
|
|
1873
1888
|
const compileRenderExpression = (expression, anchor) => {
|
|
1874
1889
|
const parts = conditionalParts(expression)
|
|
1875
|
-
if (!parts) return ts.visitNode(expression, visitor)
|
|
1876
1890
|
const setters = settersForNode(anchor, settersByFunction)
|
|
1891
|
+
if (!parts) {
|
|
1892
|
+
if (containsJsx(expression)) return ts.visitNode(expression, visitor)
|
|
1893
|
+
const resolved = resolveReactiveJsxExpression(expression, nearestFunction(anchor), setters)
|
|
1894
|
+
const usedStates = referencedStateNames(resolved, setters, resolved, bindingIndex)
|
|
1895
|
+
const captures = captureNames(resolved, resolved, setters, bindingIndex)
|
|
1896
|
+
if ((usedStates.size || captures.size) && !ts.isIdentifier(resolved)) {
|
|
1897
|
+
usesBehavior = true
|
|
1898
|
+
usesBinding = true
|
|
1899
|
+
return descriptors.compileReactiveBinding(resolved, { setters, importBindings, derived: calculationBinding(expression, nearestFunction(anchor)) }).node
|
|
1900
|
+
}
|
|
1901
|
+
return ts.visitNode(expression, visitor)
|
|
1902
|
+
}
|
|
1877
1903
|
const condition = resolveReactiveJsxExpression(parts.condition, nearestFunction(anchor), setters)
|
|
1878
1904
|
const usedStates = referencedStateNames(condition, setters, condition, bindingIndex)
|
|
1879
1905
|
const captures = captureNames(condition, condition, setters, bindingIndex)
|
|
@@ -2218,12 +2244,12 @@ function createKudzuTransformer({ semantic, handlerUrl, file, sourceFiles, sourc
|
|
|
2218
2244
|
return factory.updateVariableDeclaration(node, node.name, node.exclamationToken, node.type, factory.createIdentifier("undefined"))
|
|
2219
2245
|
}
|
|
2220
2246
|
|
|
2221
|
-
if (ts.isVariableDeclaration(node) && collectionLengthDeclarations.has(node)
|
|
2247
|
+
if (ts.isVariableDeclaration(node) && node.initializer && (collectionLengthDeclarations.has(node) || collectionValueDeclarations.has(node))) {
|
|
2222
2248
|
const setters = settersForNode(node, settersByFunction)
|
|
2223
2249
|
const expression = resolveReactiveJsxExpression(node.initializer, nearestFunction(node), setters)
|
|
2224
2250
|
const stateNames = new Set(setters.values())
|
|
2225
2251
|
const rewrite = current => {
|
|
2226
|
-
if (ts.isIdentifier(current) && stateNames.has(current.text) && isReferenceIdentifier(current)) return factory.createPropertyAccessExpression(current, "value")
|
|
2252
|
+
if (ts.isIdentifier(current) && stateNames.has(current.text) && isReferenceIdentifier(current) && !isShadowedByParameter(current, expression)) return factory.createPropertyAccessExpression(current, "value")
|
|
2227
2253
|
return ts.visitEachChild(current, rewrite, context)
|
|
2228
2254
|
}
|
|
2229
2255
|
return factory.updateVariableDeclaration(node, node.name, node.exclamationToken, node.type, ts.visitNode(expression, rewrite))
|
|
@@ -3515,16 +3541,27 @@ function importedSerializableCollections(sourceFile, file, sourceFiles, sourceIn
|
|
|
3515
3541
|
for (const statement of sourceFile.statements) if (ts.isImportDeclaration(statement) && ["react", "@kudzujs/core"].includes(statement.moduleSpecifier.text) && statement.importClause?.namedBindings && ts.isNamedImports(statement.importClause.namedBindings)) {
|
|
3516
3542
|
for (const entry of statement.importClause.namedBindings.elements) if ((entry.propertyName ?? entry.name).text === "useMemo") memoNames.add(entry.name.text)
|
|
3517
3543
|
}
|
|
3544
|
+
const visitedAliases = new Set()
|
|
3518
3545
|
const collectRoot = node => {
|
|
3519
|
-
if (ts.isPropertyAccessExpression(node) && ["filter", "flatMap", "slice", "toSorted", "map"].includes(node.name.text)) {
|
|
3546
|
+
if (ts.isPropertyAccessExpression(node) && ["filter", "flatMap", "slice", "toSorted", "map", "length"].includes(node.name.text)) {
|
|
3520
3547
|
let expression = node.expression
|
|
3521
3548
|
while (ts.isCallExpression(expression) || ts.isPropertyAccessExpression(expression)) expression = expression.expression
|
|
3522
|
-
if (ts.isIdentifier(expression))
|
|
3549
|
+
if (ts.isIdentifier(expression)) {
|
|
3550
|
+
if (!isShadowedIdentifier(expression, sourceFile)) mapped.add(expression.text)
|
|
3551
|
+
const owner = nearestFunction(node)
|
|
3552
|
+
if (owner?.body && ts.isBlock(owner.body)) for (const statement of owner.body.statements) {
|
|
3553
|
+
if (!ts.isVariableStatement(statement) || !(statement.declarationList.flags & ts.NodeFlags.Const)) continue
|
|
3554
|
+
for (const declaration of statement.declarationList.declarations) if (ts.isIdentifier(declaration.name) && declaration.name.text === expression.text && declaration.initializer && !visitedAliases.has(declaration)) {
|
|
3555
|
+
visitedAliases.add(declaration)
|
|
3556
|
+
collectRoot(declaration.initializer)
|
|
3557
|
+
}
|
|
3558
|
+
}
|
|
3559
|
+
}
|
|
3523
3560
|
}
|
|
3524
3561
|
ts.forEachChild(node, collectRoot)
|
|
3525
3562
|
}
|
|
3526
3563
|
const visit = node => {
|
|
3527
|
-
if (ts.isPropertyAccessExpression(node) && node.name.text
|
|
3564
|
+
if (ts.isPropertyAccessExpression(node) && ["map", "length"].includes(node.name.text)) {
|
|
3528
3565
|
collectRoot(node)
|
|
3529
3566
|
}
|
|
3530
3567
|
if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && memoNames.has(node.expression.text) && node.arguments[0]) collectRoot(node.arguments[0])
|