@llui/compiler 0.5.1 → 0.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/collect-deps.d.ts +5 -1
- package/dist/collect-deps.d.ts.map +1 -1
- package/dist/collect-deps.js +93 -14
- package/dist/collect-deps.js.map +1 -1
- package/dist/cross-file-walker.d.ts +4 -1
- package/dist/cross-file-walker.d.ts.map +1 -1
- package/dist/cross-file-walker.js +72 -9
- package/dist/cross-file-walker.js.map +1 -1
- package/dist/lint-modules.d.ts.map +1 -1
- package/dist/lint-modules.js +2 -0
- package/dist/lint-modules.js.map +1 -1
- package/dist/modules/bitmask-overflow.js +1 -1
- package/dist/modules/bitmask-overflow.js.map +1 -1
- package/dist/modules/core-synthesis.d.ts +7 -0
- package/dist/modules/core-synthesis.d.ts.map +1 -1
- package/dist/modules/core-synthesis.js +24 -12
- package/dist/modules/core-synthesis.js.map +1 -1
- package/dist/modules/opaque-state-flow.d.ts +3 -0
- package/dist/modules/opaque-state-flow.d.ts.map +1 -0
- package/dist/modules/opaque-state-flow.js +208 -0
- package/dist/modules/opaque-state-flow.js.map +1 -0
- package/dist/transform.d.ts +1 -1
- package/dist/transform.d.ts.map +1 -1
- package/dist/transform.js +75 -5
- package/dist/transform.js.map +1 -1
- package/package.json +1 -1
package/dist/collect-deps.d.ts
CHANGED
|
@@ -19,7 +19,10 @@ import ts from 'typescript';
|
|
|
19
19
|
* Shared by the bit-assignment path (`collectDeps`, below) and the
|
|
20
20
|
* `diagnostics.ts` bitmask-overflow warning.
|
|
21
21
|
*/
|
|
22
|
-
export declare function collectStatePathsFromSource(sourceFile: ts.SourceFile):
|
|
22
|
+
export declare function collectStatePathsFromSource(sourceFile: ts.SourceFile): {
|
|
23
|
+
paths: Set<string>;
|
|
24
|
+
opaque: boolean;
|
|
25
|
+
};
|
|
23
26
|
/**
|
|
24
27
|
* Per-accessor path sets — one entry per reactive arrow/function. Used
|
|
25
28
|
* by the bitmask-overflow diagnostic to find clusters of paths that
|
|
@@ -45,6 +48,7 @@ export declare function collectAccessorPathSets(sourceFile: ts.SourceFile): Set<
|
|
|
45
48
|
export declare function collectDeps(source: string, extraPaths?: ReadonlySet<string>): {
|
|
46
49
|
lo: Map<string, number>;
|
|
47
50
|
hi: Map<string, number>;
|
|
51
|
+
opaque: boolean;
|
|
48
52
|
};
|
|
49
53
|
/**
|
|
50
54
|
* Determines if a node is at a reactive-accessor position — either an
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"collect-deps.d.ts","sourceRoot":"","sources":["../src/collect-deps.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,YAAY,CAAA;
|
|
1
|
+
{"version":3,"file":"collect-deps.d.ts","sourceRoot":"","sources":["../src/collect-deps.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,YAAY,CAAA;AA0L3B;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,2BAA2B,CAAC,UAAU,EAAE,EAAE,CAAC,UAAU,GAAG;IACtE,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;IAClB,MAAM,EAAE,OAAO,CAAA;CAChB,CAyBA;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,UAAU,EAAE,EAAE,CAAC,UAAU,GAAG,GAAG,CAAC,MAAM,CAAC,EAAE,CAwBhF;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,WAAW,CACzB,MAAM,EAAE,MAAM,EACd,UAAU,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,GAC/B;IACD,EAAE,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACvB,EAAE,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACvB,MAAM,EAAE,OAAO,CAAA;CAChB,CAsCA;AAeD;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,GAAG,OAAO,CA6DzD"}
|
package/dist/collect-deps.js
CHANGED
|
@@ -25,7 +25,7 @@ const NON_DELEGATION_HELPERS = new Set(['sample', 'item', 'memo', 'text', 'unsaf
|
|
|
25
25
|
* handing OUR state in. Conservative: don't recurse through
|
|
26
26
|
* lambda boundaries.
|
|
27
27
|
*/
|
|
28
|
-
function visitTopLevelDelegations(body, stateParamName, follow) {
|
|
28
|
+
function visitTopLevelDelegations(body, stateParamName, follow, onUnresolved) {
|
|
29
29
|
function visit(node) {
|
|
30
30
|
if (ts.isCallExpression(node) && ts.isIdentifier(node.expression)) {
|
|
31
31
|
const name = node.expression.text;
|
|
@@ -35,6 +35,8 @@ function visitTopLevelDelegations(body, stateParamName, follow) {
|
|
|
35
35
|
const resolved = resolveAccessorBody(node.expression);
|
|
36
36
|
if (resolved)
|
|
37
37
|
follow(resolved);
|
|
38
|
+
else if (onUnresolved)
|
|
39
|
+
onUnresolved();
|
|
38
40
|
}
|
|
39
41
|
}
|
|
40
42
|
}
|
|
@@ -68,7 +70,7 @@ function visitTopLevelDelegations(body, stateParamName, follow) {
|
|
|
68
70
|
* `visited` breaks cycles on mutually-recursive helpers — terminates
|
|
69
71
|
* the walk; doesn't try to be precise about what such helpers read.
|
|
70
72
|
*/
|
|
71
|
-
function extractAccessorPaths(accessor, paths, visited = new Set()) {
|
|
73
|
+
function extractAccessorPaths(accessor, paths, visited = new Set(), opaqueOut) {
|
|
72
74
|
if (visited.has(accessor))
|
|
73
75
|
return false;
|
|
74
76
|
visited.add(accessor);
|
|
@@ -76,20 +78,94 @@ function extractAccessorPaths(accessor, paths, visited = new Set()) {
|
|
|
76
78
|
if (params.length !== 1)
|
|
77
79
|
return false;
|
|
78
80
|
const paramName = params[0].name;
|
|
79
|
-
if (!ts.isIdentifier(paramName))
|
|
81
|
+
if (!ts.isIdentifier(paramName)) {
|
|
82
|
+
// Destructured/anonymous param — the path walker can't follow
|
|
83
|
+
// reads through it. Conservative: mark the accessor as opaque so
|
|
84
|
+
// the synthesis pipeline emits a whole-state sentinel.
|
|
85
|
+
if (opaqueOut)
|
|
86
|
+
opaqueOut.value = true;
|
|
80
87
|
return false;
|
|
88
|
+
}
|
|
81
89
|
if (!accessor.body)
|
|
82
90
|
return false;
|
|
83
91
|
const before = paths.size;
|
|
84
92
|
extractPaths(accessor.body, paramName.text, '', paths);
|
|
93
|
+
// Detect opaque state flow alongside path extraction. Mirrors the
|
|
94
|
+
// classifier in `transform.ts`'s `computeAccessorMask` (Identifier
|
|
95
|
+
// `s` used in a non-tracked position) — any leak means a precise
|
|
96
|
+
// `__prefixes` table is insufficient because a field read only
|
|
97
|
+
// through the leak never enters fieldBits and the runtime can't
|
|
98
|
+
// dirty it on change.
|
|
99
|
+
if (opaqueOut)
|
|
100
|
+
detectOpaqueStateFlow(accessor.body, paramName.text, opaqueOut);
|
|
85
101
|
// Follow delegations: `(s) => helper(s)` — extract `helper`'s body's
|
|
86
102
|
// state paths too. Reuses the `visited` set across the recursion
|
|
87
|
-
// chain so cycles terminate.
|
|
103
|
+
// chain so cycles terminate. When the callee is unresolvable
|
|
104
|
+
// (function parameter, import, destructured), the same logic that
|
|
105
|
+
// forces FULL_MASK in `computeAccessorMask` flags the file as
|
|
106
|
+
// opaque here, so the sentinel gets emitted.
|
|
88
107
|
visitTopLevelDelegations(accessor.body, paramName.text, (resolved) => {
|
|
89
|
-
extractAccessorPaths(resolved, paths, visited);
|
|
108
|
+
extractAccessorPaths(resolved, paths, visited, opaqueOut);
|
|
109
|
+
}, () => {
|
|
110
|
+
if (opaqueOut)
|
|
111
|
+
opaqueOut.value = true;
|
|
90
112
|
});
|
|
91
113
|
return paths.size > before;
|
|
92
114
|
}
|
|
115
|
+
/**
|
|
116
|
+
* Mirror of the classifier in `computeAccessorMask` (transform.ts). An
|
|
117
|
+
* accessor "leaks state" — and so demands the conservative
|
|
118
|
+
* FULL_MASK / whole-state sentinel — when the state identifier `s`
|
|
119
|
+
* appears in any position OTHER than:
|
|
120
|
+
* - the param binding itself
|
|
121
|
+
* - the root of `s.x.y…` (PropertyAccessExpression)
|
|
122
|
+
* - the root of `s['literal']` / `s[0]` (ElementAccess with literal key)
|
|
123
|
+
* - arg0 of `helper(s)` with an Identifier callee (handled by the
|
|
124
|
+
* delegation visitor — resolvable → recursion, unresolvable →
|
|
125
|
+
* marks opaque via the callback)
|
|
126
|
+
*
|
|
127
|
+
* Every other context (NewExpression arg, TaggedTemplate span, spread,
|
|
128
|
+
* const-alias, conditional branch, method-call arg, dynamic key
|
|
129
|
+
* `s[expr]`, return-the-whole-state, …) is treated as a leak.
|
|
130
|
+
*/
|
|
131
|
+
function detectOpaqueStateFlow(body, stateParam, out) {
|
|
132
|
+
function visit(node) {
|
|
133
|
+
if (out.value)
|
|
134
|
+
return;
|
|
135
|
+
if (ts.isIdentifier(node) && node.text === stateParam) {
|
|
136
|
+
const parent = node.parent;
|
|
137
|
+
const isBinding = !!parent && ts.isParameter(parent);
|
|
138
|
+
if (!isBinding) {
|
|
139
|
+
let isTracked = false;
|
|
140
|
+
if (parent) {
|
|
141
|
+
if (ts.isPropertyAccessExpression(parent) && parent.expression === node) {
|
|
142
|
+
isTracked = true;
|
|
143
|
+
}
|
|
144
|
+
else if (ts.isElementAccessExpression(parent) && parent.expression === node) {
|
|
145
|
+
isTracked =
|
|
146
|
+
ts.isStringLiteralLike(parent.argumentExpression) ||
|
|
147
|
+
ts.isNumericLiteral(parent.argumentExpression);
|
|
148
|
+
}
|
|
149
|
+
else if (ts.isCallExpression(parent) &&
|
|
150
|
+
ts.isIdentifier(parent.expression) &&
|
|
151
|
+
parent.arguments[0] === node &&
|
|
152
|
+
!NON_DELEGATION_HELPERS.has(parent.expression.text)) {
|
|
153
|
+
// The delegation visitor either recurses into the resolved
|
|
154
|
+
// body (transitively detecting opaque inside) or flags
|
|
155
|
+
// opaque via its second callback for unresolvable callees.
|
|
156
|
+
isTracked = true;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
if (!isTracked) {
|
|
160
|
+
out.value = true;
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
ts.forEachChild(node, visit);
|
|
166
|
+
}
|
|
167
|
+
visit(body);
|
|
168
|
+
}
|
|
93
169
|
/**
|
|
94
170
|
* Walk the AST and collect every unique state access path referenced by
|
|
95
171
|
* a reactive accessor. A reactive accessor is one of:
|
|
@@ -112,25 +188,28 @@ function extractAccessorPaths(accessor, paths, visited = new Set()) {
|
|
|
112
188
|
*/
|
|
113
189
|
export function collectStatePathsFromSource(sourceFile) {
|
|
114
190
|
const paths = new Set();
|
|
191
|
+
const opaqueOut = { value: false };
|
|
115
192
|
function visit(node) {
|
|
116
193
|
// Inline arrow / function expression at a reactive position.
|
|
117
194
|
if (ts.isArrowFunction(node) || ts.isFunctionExpression(node)) {
|
|
118
195
|
if (isReactiveAccessor(node))
|
|
119
|
-
extractAccessorPaths(node, paths);
|
|
196
|
+
extractAccessorPaths(node, paths, undefined, opaqueOut);
|
|
120
197
|
}
|
|
121
198
|
// Identifier at a reactive position — resolve to its declaration
|
|
122
|
-
// and extract paths from the resolved body.
|
|
123
|
-
//
|
|
124
|
-
//
|
|
199
|
+
// and extract paths from the resolved body. Identifiers that
|
|
200
|
+
// resolve elsewhere (imports, etc.) leave a binding the walker
|
|
201
|
+
// can't see — treat the host file as opaque so the sentinel fires.
|
|
125
202
|
if (ts.isIdentifier(node) && isReactiveAccessor(node)) {
|
|
126
203
|
const resolved = resolveAccessorBody(node);
|
|
127
204
|
if (resolved)
|
|
128
|
-
extractAccessorPaths(resolved, paths);
|
|
205
|
+
extractAccessorPaths(resolved, paths, undefined, opaqueOut);
|
|
206
|
+
else
|
|
207
|
+
opaqueOut.value = true;
|
|
129
208
|
}
|
|
130
209
|
ts.forEachChild(node, visit);
|
|
131
210
|
}
|
|
132
211
|
visit(sourceFile);
|
|
133
|
-
return paths;
|
|
212
|
+
return { paths, opaque: opaqueOut.value };
|
|
134
213
|
}
|
|
135
214
|
/**
|
|
136
215
|
* Per-accessor path sets — one entry per reactive arrow/function. Used
|
|
@@ -180,9 +259,9 @@ export function collectDeps(source, extraPaths) {
|
|
|
180
259
|
const sourceFile = ts.createSourceFile('input.ts', source, ts.ScriptTarget.Latest, true);
|
|
181
260
|
// Check if file imports from @llui/dom
|
|
182
261
|
if (!hasLluiImport(sourceFile)) {
|
|
183
|
-
return { lo: new Map(), hi: new Map() };
|
|
262
|
+
return { lo: new Map(), hi: new Map(), opaque: false };
|
|
184
263
|
}
|
|
185
|
-
const paths = collectStatePathsFromSource(sourceFile);
|
|
264
|
+
const { paths, opaque } = collectStatePathsFromSource(sourceFile);
|
|
186
265
|
// Cross-file extension (v2c pipeline integration): the host adapter may
|
|
187
266
|
// pass paths discovered by `crossFileAccessorPaths()` — paths read
|
|
188
267
|
// through in-repo view-helpers in *other* files. Union them with the
|
|
@@ -212,7 +291,7 @@ export function collectDeps(source, extraPaths) {
|
|
|
212
291
|
}
|
|
213
292
|
index++;
|
|
214
293
|
}
|
|
215
|
-
return { lo, hi };
|
|
294
|
+
return { lo, hi, opaque };
|
|
216
295
|
}
|
|
217
296
|
function hasLluiImport(sourceFile) {
|
|
218
297
|
for (const stmt of sourceFile.statements) {
|
package/dist/collect-deps.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"collect-deps.js","sourceRoot":"","sources":["../src/collect-deps.ts"],"names":[],"mappings":"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","sourcesContent":["import ts from 'typescript'\nimport { resolveAccessorBody } from './accessor-resolver.js'\n\n/**\n * Names whose first arg is itself a reactive accessor (the existing\n * arrow walker handles them) or which are explicitly excluded\n * (sample/item read state imperatively / per-row, not as state\n * accessors). When a delegating accessor's body contains a call to one\n * of these, we don't follow it — recursion is reserved for \"this is\n * just a thin wrapper that hands the state to another local helper.\"\n */\nconst NON_DELEGATION_HELPERS = new Set(['sample', 'item', 'memo', 'text', 'unsafeHtml'])\n\n/**\n * Walk a delegating accessor's body looking for calls to OTHER local\n * functions that take the state param verbatim — `helper(s)` where\n * `s` matches the outer accessor's param name. For each, hand the\n * resolved declaration back so the caller can recurse into its body.\n *\n * Skips:\n * - Framework helpers (`memo`, `text`, etc.) — their arrow args are\n * visited by the top-level arrow walker; we'd double-count.\n * - Method calls (`s.items.filter(...)`) — the callee is a builtin,\n * not a local function we can resolve.\n * - Nested function bodies — params inside a `(item) => …` shadow\n * ours, so a `helper(s)` deep in there isn't (necessarily)\n * handing OUR state in. Conservative: don't recurse through\n * lambda boundaries.\n */\nfunction visitTopLevelDelegations(\n body: ts.Node,\n stateParamName: string,\n follow: (resolved: ts.ArrowFunction | ts.FunctionExpression | ts.FunctionDeclaration) => void,\n): void {\n function visit(node: ts.Node): void {\n if (ts.isCallExpression(node) && ts.isIdentifier(node.expression)) {\n const name = node.expression.text\n if (!NON_DELEGATION_HELPERS.has(name)) {\n const arg0 = node.arguments[0]\n if (arg0 && ts.isIdentifier(arg0) && arg0.text === stateParamName) {\n const resolved = resolveAccessorBody(node.expression)\n if (resolved) follow(resolved)\n }\n }\n }\n // Don't descend into nested function bodies — their params shadow\n // ours, and any call inside them isn't unambiguously delegating\n // our state.\n if (\n ts.isArrowFunction(node) ||\n ts.isFunctionExpression(node) ||\n ts.isFunctionDeclaration(node)\n ) {\n return\n }\n ts.forEachChild(node, visit)\n }\n // If the body itself is a function, there's nothing at the top\n // level to inspect — its own body is a separate scope.\n if (ts.isArrowFunction(body) || ts.isFunctionExpression(body) || ts.isFunctionDeclaration(body)) {\n return\n }\n visit(body)\n}\n\n/**\n * Extract paths from a callable accessor (arrow / fn-expr / fn-decl)\n * into the given set. Recurses through call-delegations to other local\n * helpers so that `(s) => filtered(s)` / `(s) => { void s.x; return\n * inner(s) }` correctly contribute the helper's state-path reads.\n * Without recursion the precise mask under-counts — fields read only\n * via the helper drop off the bitmask, and any sibling reactive\n * accessor that reads them produces a non-zero `dirty` that AND'd with\n * the narrow each.__mask is zero, silently skipping the reconcile.\n *\n * `visited` breaks cycles on mutually-recursive helpers — terminates\n * the walk; doesn't try to be precise about what such helpers read.\n */\nfunction extractAccessorPaths(\n accessor: ts.ArrowFunction | ts.FunctionExpression | ts.FunctionDeclaration,\n paths: Set<string>,\n visited: Set<ts.Node> = new Set(),\n): boolean {\n if (visited.has(accessor)) return false\n visited.add(accessor)\n\n const params = accessor.parameters\n if (params.length !== 1) return false\n const paramName = params[0]!.name\n if (!ts.isIdentifier(paramName)) return false\n if (!accessor.body) return false\n const before = paths.size\n\n extractPaths(accessor.body, paramName.text, '', paths)\n\n // Follow delegations: `(s) => helper(s)` — extract `helper`'s body's\n // state paths too. Reuses the `visited` set across the recursion\n // chain so cycles terminate.\n visitTopLevelDelegations(accessor.body, paramName.text, (resolved) => {\n extractAccessorPaths(resolved, paths, visited)\n })\n\n return paths.size > before\n}\n\n/**\n * Walk the AST and collect every unique state access path referenced by\n * a reactive accessor. A reactive accessor is one of:\n *\n * - An inline arrow / function expression at a reactive position\n * (`text(s => s.count)`, `div({ title: s => s.title })`,\n * `show({ when: s => s.gated })`, etc.).\n * - An Identifier at a reactive position that resolves to a callable\n * in this file — a const-bound arrow / function expression,\n * a hoisted function declaration, or `const x = memo(arrow)`.\n *\n * The second case lets authors refactor a literal arrow into a named\n * helper without losing the reactive-mask optimization (a precise mask\n * for `__dirty` and structural-primitive `__mask`). Without it, the\n * runtime falls back to FULL_MASK — correct, but every binding fires\n * on every state change.\n *\n * Shared by the bit-assignment path (`collectDeps`, below) and the\n * `diagnostics.ts` bitmask-overflow warning.\n */\nexport function collectStatePathsFromSource(sourceFile: ts.SourceFile): Set<string> {\n const paths = new Set<string>()\n\n function visit(node: ts.Node): void {\n // Inline arrow / function expression at a reactive position.\n if (ts.isArrowFunction(node) || ts.isFunctionExpression(node)) {\n if (isReactiveAccessor(node)) extractAccessorPaths(node, paths)\n }\n\n // Identifier at a reactive position — resolve to its declaration\n // and extract paths from the resolved body. Skip identifiers\n // imported from elsewhere (resolver returns null) — there's no\n // body to scan, runtime falls back to FULL_MASK.\n if (ts.isIdentifier(node) && isReactiveAccessor(node)) {\n const resolved = resolveAccessorBody(node)\n if (resolved) extractAccessorPaths(resolved, paths)\n }\n\n ts.forEachChild(node, visit)\n }\n\n visit(sourceFile)\n return paths\n}\n\n/**\n * Per-accessor path sets — one entry per reactive arrow/function. Used\n * by the bitmask-overflow diagnostic to find clusters of paths that\n * always fire together (co-occurrence analysis).\n */\nexport function collectAccessorPathSets(sourceFile: ts.SourceFile): Set<string>[] {\n const sets: Set<string>[] = []\n\n function visit(node: ts.Node): void {\n if (ts.isArrowFunction(node) || ts.isFunctionExpression(node)) {\n if (isReactiveAccessor(node)) {\n const set = new Set<string>()\n if (extractAccessorPaths(node, set)) sets.push(set)\n }\n }\n\n if (ts.isIdentifier(node) && isReactiveAccessor(node)) {\n const resolved = resolveAccessorBody(node)\n if (resolved) {\n const set = new Set<string>()\n if (extractAccessorPaths(resolved, set)) sets.push(set)\n }\n }\n\n ts.forEachChild(node, visit)\n }\n\n visit(sourceFile)\n return sets\n}\n\n/**\n * Pre-scan a source file to collect all unique state access paths\n * referenced by reactive accessors (arrow functions in props and text() calls).\n *\n * Returns a pair of maps:\n * - `lo`: paths at bit positions 0..30, with value `1 << position`\n * - `hi`: paths at bit positions 31..61, with value `1 << (position - 31)`\n *\n * Bit positions past 61 collapse to `-1` (FULL_MASK) in the `lo` map and\n * cause every binding reading them to re-evaluate on every cycle. The\n * `bitmask-overflow` lint rule warns the user to restructure state.\n *\n * Components with ≤31 paths see an empty `hi` map; the compiler skips\n * all high-word emit so the generated code is byte-identical to the\n * pre-multi-word baseline.\n */\nexport function collectDeps(\n source: string,\n extraPaths?: ReadonlySet<string>,\n): {\n lo: Map<string, number>\n hi: Map<string, number>\n} {\n const sourceFile = ts.createSourceFile('input.ts', source, ts.ScriptTarget.Latest, true)\n\n // Check if file imports from @llui/dom\n if (!hasLluiImport(sourceFile)) {\n return { lo: new Map(), hi: new Map() }\n }\n\n const paths = collectStatePathsFromSource(sourceFile)\n // Cross-file extension (v2c pipeline integration): the host adapter may\n // pass paths discovered by `crossFileAccessorPaths()` — paths read\n // through in-repo view-helpers in *other* files. Union them with the\n // file-local set before bit assignment. Without this merge the\n // sentinel-`show()` workaround from v2b §1 remains necessary; with\n // it, helpers in other files contribute to the consumer's __prefixes\n // table automatically.\n if (extraPaths) {\n for (const p of extraPaths) paths.add(p)\n }\n\n const lo = new Map<string, number>()\n const hi = new Map<string, number>()\n let index = 0\n for (const path of paths) {\n if (index < 31) {\n lo.set(path, 1 << index)\n } else if (index < 62) {\n hi.set(path, 1 << (index - 31))\n } else {\n // Past 61 paths — graceful FULL_MASK fallback in the low word.\n // Realistic LLui components shouldn't hit this; the lint rule\n // fires well before.\n lo.set(path, -1)\n }\n index++\n }\n\n return { lo, hi }\n}\n\nfunction hasLluiImport(sourceFile: ts.SourceFile): boolean {\n for (const stmt of sourceFile.statements) {\n if (\n ts.isImportDeclaration(stmt) &&\n ts.isStringLiteral(stmt.moduleSpecifier) &&\n stmt.moduleSpecifier.text === '@llui/dom'\n ) {\n return true\n }\n }\n return false\n}\n\n/**\n * Determines if a node is at a reactive-accessor position — either an\n * inline arrow / function expression OR an identifier that's about to\n * be resolved to one. The check is identity-based on `parent.arguments[0]`\n * etc., so the same logic works for both shapes.\n *\n * Exported so the cross-file walker can use the same gate. Without this\n * gate the walker descends into every 1-param arrow in the file —\n * including `onEffect: (bag) => bag.send(...)` — and pollutes\n * `__prefixes` with non-state property names (issue #5, bug 3).\n */\nexport function isReactiveAccessor(node: ts.Node): boolean {\n const parent = node.parent\n\n // text(s => s.count) — first arg to a call\n if (ts.isCallExpression(parent) && parent.arguments[0] === node) {\n // Skip item(t => t.id) — per-item selectors inside each() render.\n // Skip sample(s => s.x) — imperative one-shot read, no binding created\n // (both the top-level import and the destructured-from-h form).\n if (ts.isIdentifier(parent.expression)) {\n if (parent.expression.text === 'item' || parent.expression.text === 'sample') {\n return false\n }\n }\n // Skip array method callbacks: .filter(t => ...), .map(t => ...), .some(t => ...), etc.\n // Allow view-helper primitive calls: h.text(s => ...), h.memo(s => ...)\n if (ts.isPropertyAccessExpression(parent.expression)) {\n const methodName = parent.expression.name.text\n if (methodName === 'text' || methodName === 'memo') {\n return true\n }\n return false\n }\n return true\n }\n\n // div({ title: s => s.title }) — value in a property assignment inside an object literal.\n // Only treat as reactive if the containing call is a known framework API whose\n // properties are reactive accessors. Otherwise user-land helpers like\n // sliceHandler({ narrow: (m) => m.type === ... }) would pollute the path set.\n if (ts.isPropertyAssignment(parent)) {\n const key = parent.name\n if (ts.isIdentifier(key)) {\n // Skip event handlers (onClick, onInput, etc.)\n if (/^on[A-Z]/.test(key.text)) return false\n // Skip each() key function and other non-reactive props\n if (key.text === 'key' || key.text === 'name') return false\n // Walk up to find the enclosing call expression\n let ancestor: ts.Node | undefined = parent.parent // ObjectLiteralExpression\n while (ancestor && !ts.isCallExpression(ancestor)) {\n ancestor = ancestor.parent\n }\n if (!ancestor) return false\n const callExpr = ancestor as ts.CallExpression\n // Bare identifier: `scope({on: …})`, `div({title: …})`, etc.\n if (ts.isIdentifier(callExpr.expression)) {\n return REACTIVE_API_NAMES.has(callExpr.expression.text)\n }\n // Method-call form: `h.scope({on: …})`, `h.show({when: …})`, etc.\n // The docs and View bag promote this shape; without recognizing it\n // here, paths read ONLY through a structural primitive's\n // `on`/`when`/`items` accessor never enter `__prefixes`, so the\n // runtime dirty mask can't see changes to those fields and the\n // structural block silently fails to reconcile.\n if (ts.isPropertyAccessExpression(callExpr.expression)) {\n return REACTIVE_API_NAMES.has(callExpr.expression.name.text)\n }\n return false\n }\n }\n\n return false\n}\n\n// Framework APIs whose object-literal arguments contain reactive accessors.\n// Arrow functions in property values of these calls are state-tracked.\nconst REACTIVE_API_NAMES = new Set([\n // Element helpers (see ELEMENT_HELPERS in transform.ts — we keep a superset here)\n ...[\n 'a',\n 'abbr',\n 'article',\n 'aside',\n 'b',\n 'blockquote',\n 'br',\n 'button',\n 'canvas',\n 'code',\n 'dd',\n 'details',\n 'dialog',\n 'div',\n 'dl',\n 'dt',\n 'em',\n 'fieldset',\n 'figcaption',\n 'figure',\n 'footer',\n 'form',\n 'h1',\n 'h2',\n 'h3',\n 'h4',\n 'h5',\n 'h6',\n 'header',\n 'hr',\n 'i',\n 'iframe',\n 'img',\n 'input',\n 'label',\n 'legend',\n 'li',\n 'main',\n 'mark',\n 'nav',\n 'ol',\n 'optgroup',\n 'option',\n 'output',\n 'p',\n 'pre',\n 'progress',\n 'section',\n 'select',\n 'small',\n 'span',\n 'strong',\n 'sub',\n 'summary',\n 'sup',\n 'table',\n 'tbody',\n 'td',\n 'textarea',\n 'tfoot',\n 'th',\n 'thead',\n 'time',\n 'tr',\n 'ul',\n 'video',\n ],\n // Structural primitives\n 'each',\n 'branch',\n 'scope',\n 'show',\n 'memo',\n 'portal',\n 'foreign',\n 'child',\n 'errorBoundary',\n // track({ deps: (s) => [...] }) — explicit reactivity declaration for\n // paths static analysis can't infer. The compiler treats `deps` as a\n // reactive accessor so its paths fold into the host component's\n // __prefixes; the call expression is then stripped from emission\n // (see transform.ts). v2b §3.\n 'track',\n])\n\n/**\n * Extract state access paths from an expression body.\n * Handles:\n * - Direct property access: param.field, param.field.subfield\n * - Bracket notation with string literal: param['field']\n */\nfunction extractPaths(node: ts.Node, paramName: string, _prefix: string, paths: Set<string>): void {\n if (ts.isPropertyAccessExpression(node)) {\n // Skip if this is an intermediate in a deeper chain\n if (ts.isPropertyAccessExpression(node.parent)) {\n // handled when the leaf is visited\n }\n // Skip if this is the callee of a method call: s.todos.filter(...)\n else if (ts.isCallExpression(node.parent) && node.parent.expression === node) {\n // It's a method call — record the object, not the method\n // e.g. s.todos.filter(...) → record 'todos', not 'todos.filter'\n if (ts.isPropertyAccessExpression(node.expression)) {\n const chain = resolvePropertyChain(node.expression, paramName)\n if (chain) paths.add(chain)\n }\n } else {\n const chain = resolvePropertyChain(node, paramName)\n if (chain) {\n paths.add(chain)\n }\n }\n }\n\n if (ts.isElementAccessExpression(node)) {\n const chain = resolveElementAccess(node, paramName)\n if (chain) {\n paths.add(chain)\n }\n }\n\n ts.forEachChild(node, (child) => extractPaths(child, paramName, _prefix, paths))\n}\n\n/**\n * Resolve a property access chain like s.user.name to \"user.name\".\n * Returns null if the chain doesn't start with the state parameter.\n * Stops at depth 2.\n */\nfunction resolvePropertyChain(node: ts.PropertyAccessExpression, paramName: string): string | null {\n const parts: string[] = []\n let current: ts.Expression = node\n\n while (ts.isPropertyAccessExpression(current)) {\n parts.unshift(current.name.text)\n current = current.expression\n }\n\n // The root must be the state parameter\n if (!ts.isIdentifier(current) || current.text !== paramName) {\n return null\n }\n\n // Limit to depth 2\n if (parts.length > 2) {\n return parts.slice(0, 2).join('.')\n }\n\n return parts.join('.')\n}\n\n/**\n * Resolve bracket access with string literal: s['count'] → \"count\"\n */\nfunction resolveElementAccess(node: ts.ElementAccessExpression, paramName: string): string | null {\n if (!ts.isIdentifier(node.expression) || node.expression.text !== paramName) {\n return null\n }\n\n if (ts.isStringLiteral(node.argumentExpression)) {\n return node.argumentExpression.text\n }\n\n return null\n}\n"]}
|
|
1
|
+
{"version":3,"file":"collect-deps.js","sourceRoot":"","sources":["../src/collect-deps.ts"],"names":[],"mappings":"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","sourcesContent":["import ts from 'typescript'\nimport { resolveAccessorBody } from './accessor-resolver.js'\n\n/**\n * Names whose first arg is itself a reactive accessor (the existing\n * arrow walker handles them) or which are explicitly excluded\n * (sample/item read state imperatively / per-row, not as state\n * accessors). When a delegating accessor's body contains a call to one\n * of these, we don't follow it — recursion is reserved for \"this is\n * just a thin wrapper that hands the state to another local helper.\"\n */\nconst NON_DELEGATION_HELPERS = new Set(['sample', 'item', 'memo', 'text', 'unsafeHtml'])\n\n/**\n * Walk a delegating accessor's body looking for calls to OTHER local\n * functions that take the state param verbatim — `helper(s)` where\n * `s` matches the outer accessor's param name. For each, hand the\n * resolved declaration back so the caller can recurse into its body.\n *\n * Skips:\n * - Framework helpers (`memo`, `text`, etc.) — their arrow args are\n * visited by the top-level arrow walker; we'd double-count.\n * - Method calls (`s.items.filter(...)`) — the callee is a builtin,\n * not a local function we can resolve.\n * - Nested function bodies — params inside a `(item) => …` shadow\n * ours, so a `helper(s)` deep in there isn't (necessarily)\n * handing OUR state in. Conservative: don't recurse through\n * lambda boundaries.\n */\nfunction visitTopLevelDelegations(\n body: ts.Node,\n stateParamName: string,\n follow: (resolved: ts.ArrowFunction | ts.FunctionExpression | ts.FunctionDeclaration) => void,\n onUnresolved?: () => void,\n): void {\n function visit(node: ts.Node): void {\n if (ts.isCallExpression(node) && ts.isIdentifier(node.expression)) {\n const name = node.expression.text\n if (!NON_DELEGATION_HELPERS.has(name)) {\n const arg0 = node.arguments[0]\n if (arg0 && ts.isIdentifier(arg0) && arg0.text === stateParamName) {\n const resolved = resolveAccessorBody(node.expression)\n if (resolved) follow(resolved)\n else if (onUnresolved) onUnresolved()\n }\n }\n }\n // Don't descend into nested function bodies — their params shadow\n // ours, and any call inside them isn't unambiguously delegating\n // our state.\n if (\n ts.isArrowFunction(node) ||\n ts.isFunctionExpression(node) ||\n ts.isFunctionDeclaration(node)\n ) {\n return\n }\n ts.forEachChild(node, visit)\n }\n // If the body itself is a function, there's nothing at the top\n // level to inspect — its own body is a separate scope.\n if (ts.isArrowFunction(body) || ts.isFunctionExpression(body) || ts.isFunctionDeclaration(body)) {\n return\n }\n visit(body)\n}\n\n/**\n * Extract paths from a callable accessor (arrow / fn-expr / fn-decl)\n * into the given set. Recurses through call-delegations to other local\n * helpers so that `(s) => filtered(s)` / `(s) => { void s.x; return\n * inner(s) }` correctly contribute the helper's state-path reads.\n * Without recursion the precise mask under-counts — fields read only\n * via the helper drop off the bitmask, and any sibling reactive\n * accessor that reads them produces a non-zero `dirty` that AND'd with\n * the narrow each.__mask is zero, silently skipping the reconcile.\n *\n * `visited` breaks cycles on mutually-recursive helpers — terminates\n * the walk; doesn't try to be precise about what such helpers read.\n */\nfunction extractAccessorPaths(\n accessor: ts.ArrowFunction | ts.FunctionExpression | ts.FunctionDeclaration,\n paths: Set<string>,\n visited: Set<ts.Node> = new Set(),\n opaqueOut?: { value: boolean },\n): boolean {\n if (visited.has(accessor)) return false\n visited.add(accessor)\n\n const params = accessor.parameters\n if (params.length !== 1) return false\n const paramName = params[0]!.name\n if (!ts.isIdentifier(paramName)) {\n // Destructured/anonymous param — the path walker can't follow\n // reads through it. Conservative: mark the accessor as opaque so\n // the synthesis pipeline emits a whole-state sentinel.\n if (opaqueOut) opaqueOut.value = true\n return false\n }\n if (!accessor.body) return false\n const before = paths.size\n\n extractPaths(accessor.body, paramName.text, '', paths)\n\n // Detect opaque state flow alongside path extraction. Mirrors the\n // classifier in `transform.ts`'s `computeAccessorMask` (Identifier\n // `s` used in a non-tracked position) — any leak means a precise\n // `__prefixes` table is insufficient because a field read only\n // through the leak never enters fieldBits and the runtime can't\n // dirty it on change.\n if (opaqueOut) detectOpaqueStateFlow(accessor.body, paramName.text, opaqueOut)\n\n // Follow delegations: `(s) => helper(s)` — extract `helper`'s body's\n // state paths too. Reuses the `visited` set across the recursion\n // chain so cycles terminate. When the callee is unresolvable\n // (function parameter, import, destructured), the same logic that\n // forces FULL_MASK in `computeAccessorMask` flags the file as\n // opaque here, so the sentinel gets emitted.\n visitTopLevelDelegations(\n accessor.body,\n paramName.text,\n (resolved) => {\n extractAccessorPaths(resolved, paths, visited, opaqueOut)\n },\n () => {\n if (opaqueOut) opaqueOut.value = true\n },\n )\n\n return paths.size > before\n}\n\n/**\n * Mirror of the classifier in `computeAccessorMask` (transform.ts). An\n * accessor \"leaks state\" — and so demands the conservative\n * FULL_MASK / whole-state sentinel — when the state identifier `s`\n * appears in any position OTHER than:\n * - the param binding itself\n * - the root of `s.x.y…` (PropertyAccessExpression)\n * - the root of `s['literal']` / `s[0]` (ElementAccess with literal key)\n * - arg0 of `helper(s)` with an Identifier callee (handled by the\n * delegation visitor — resolvable → recursion, unresolvable →\n * marks opaque via the callback)\n *\n * Every other context (NewExpression arg, TaggedTemplate span, spread,\n * const-alias, conditional branch, method-call arg, dynamic key\n * `s[expr]`, return-the-whole-state, …) is treated as a leak.\n */\nfunction detectOpaqueStateFlow(body: ts.Node, stateParam: string, out: { value: boolean }): void {\n function visit(node: ts.Node): void {\n if (out.value) return\n if (ts.isIdentifier(node) && node.text === stateParam) {\n const parent = node.parent\n const isBinding = !!parent && ts.isParameter(parent)\n if (!isBinding) {\n let isTracked = false\n if (parent) {\n if (ts.isPropertyAccessExpression(parent) && parent.expression === node) {\n isTracked = true\n } else if (ts.isElementAccessExpression(parent) && parent.expression === node) {\n isTracked =\n ts.isStringLiteralLike(parent.argumentExpression) ||\n ts.isNumericLiteral(parent.argumentExpression)\n } else if (\n ts.isCallExpression(parent) &&\n ts.isIdentifier(parent.expression) &&\n parent.arguments[0] === node &&\n !NON_DELEGATION_HELPERS.has(parent.expression.text)\n ) {\n // The delegation visitor either recurses into the resolved\n // body (transitively detecting opaque inside) or flags\n // opaque via its second callback for unresolvable callees.\n isTracked = true\n }\n }\n if (!isTracked) {\n out.value = true\n return\n }\n }\n }\n ts.forEachChild(node, visit)\n }\n visit(body)\n}\n\n/**\n * Walk the AST and collect every unique state access path referenced by\n * a reactive accessor. A reactive accessor is one of:\n *\n * - An inline arrow / function expression at a reactive position\n * (`text(s => s.count)`, `div({ title: s => s.title })`,\n * `show({ when: s => s.gated })`, etc.).\n * - An Identifier at a reactive position that resolves to a callable\n * in this file — a const-bound arrow / function expression,\n * a hoisted function declaration, or `const x = memo(arrow)`.\n *\n * The second case lets authors refactor a literal arrow into a named\n * helper without losing the reactive-mask optimization (a precise mask\n * for `__dirty` and structural-primitive `__mask`). Without it, the\n * runtime falls back to FULL_MASK — correct, but every binding fires\n * on every state change.\n *\n * Shared by the bit-assignment path (`collectDeps`, below) and the\n * `diagnostics.ts` bitmask-overflow warning.\n */\nexport function collectStatePathsFromSource(sourceFile: ts.SourceFile): {\n paths: Set<string>\n opaque: boolean\n} {\n const paths = new Set<string>()\n const opaqueOut = { value: false }\n\n function visit(node: ts.Node): void {\n // Inline arrow / function expression at a reactive position.\n if (ts.isArrowFunction(node) || ts.isFunctionExpression(node)) {\n if (isReactiveAccessor(node)) extractAccessorPaths(node, paths, undefined, opaqueOut)\n }\n\n // Identifier at a reactive position — resolve to its declaration\n // and extract paths from the resolved body. Identifiers that\n // resolve elsewhere (imports, etc.) leave a binding the walker\n // can't see — treat the host file as opaque so the sentinel fires.\n if (ts.isIdentifier(node) && isReactiveAccessor(node)) {\n const resolved = resolveAccessorBody(node)\n if (resolved) extractAccessorPaths(resolved, paths, undefined, opaqueOut)\n else opaqueOut.value = true\n }\n\n ts.forEachChild(node, visit)\n }\n\n visit(sourceFile)\n return { paths, opaque: opaqueOut.value }\n}\n\n/**\n * Per-accessor path sets — one entry per reactive arrow/function. Used\n * by the bitmask-overflow diagnostic to find clusters of paths that\n * always fire together (co-occurrence analysis).\n */\nexport function collectAccessorPathSets(sourceFile: ts.SourceFile): Set<string>[] {\n const sets: Set<string>[] = []\n\n function visit(node: ts.Node): void {\n if (ts.isArrowFunction(node) || ts.isFunctionExpression(node)) {\n if (isReactiveAccessor(node)) {\n const set = new Set<string>()\n if (extractAccessorPaths(node, set)) sets.push(set)\n }\n }\n\n if (ts.isIdentifier(node) && isReactiveAccessor(node)) {\n const resolved = resolveAccessorBody(node)\n if (resolved) {\n const set = new Set<string>()\n if (extractAccessorPaths(resolved, set)) sets.push(set)\n }\n }\n\n ts.forEachChild(node, visit)\n }\n\n visit(sourceFile)\n return sets\n}\n\n/**\n * Pre-scan a source file to collect all unique state access paths\n * referenced by reactive accessors (arrow functions in props and text() calls).\n *\n * Returns a pair of maps:\n * - `lo`: paths at bit positions 0..30, with value `1 << position`\n * - `hi`: paths at bit positions 31..61, with value `1 << (position - 31)`\n *\n * Bit positions past 61 collapse to `-1` (FULL_MASK) in the `lo` map and\n * cause every binding reading them to re-evaluate on every cycle. The\n * `bitmask-overflow` lint rule warns the user to restructure state.\n *\n * Components with ≤31 paths see an empty `hi` map; the compiler skips\n * all high-word emit so the generated code is byte-identical to the\n * pre-multi-word baseline.\n */\nexport function collectDeps(\n source: string,\n extraPaths?: ReadonlySet<string>,\n): {\n lo: Map<string, number>\n hi: Map<string, number>\n opaque: boolean\n} {\n const sourceFile = ts.createSourceFile('input.ts', source, ts.ScriptTarget.Latest, true)\n\n // Check if file imports from @llui/dom\n if (!hasLluiImport(sourceFile)) {\n return { lo: new Map(), hi: new Map(), opaque: false }\n }\n\n const { paths, opaque } = collectStatePathsFromSource(sourceFile)\n // Cross-file extension (v2c pipeline integration): the host adapter may\n // pass paths discovered by `crossFileAccessorPaths()` — paths read\n // through in-repo view-helpers in *other* files. Union them with the\n // file-local set before bit assignment. Without this merge the\n // sentinel-`show()` workaround from v2b §1 remains necessary; with\n // it, helpers in other files contribute to the consumer's __prefixes\n // table automatically.\n if (extraPaths) {\n for (const p of extraPaths) paths.add(p)\n }\n\n const lo = new Map<string, number>()\n const hi = new Map<string, number>()\n let index = 0\n for (const path of paths) {\n if (index < 31) {\n lo.set(path, 1 << index)\n } else if (index < 62) {\n hi.set(path, 1 << (index - 31))\n } else {\n // Past 61 paths — graceful FULL_MASK fallback in the low word.\n // Realistic LLui components shouldn't hit this; the lint rule\n // fires well before.\n lo.set(path, -1)\n }\n index++\n }\n\n return { lo, hi, opaque }\n}\n\nfunction hasLluiImport(sourceFile: ts.SourceFile): boolean {\n for (const stmt of sourceFile.statements) {\n if (\n ts.isImportDeclaration(stmt) &&\n ts.isStringLiteral(stmt.moduleSpecifier) &&\n stmt.moduleSpecifier.text === '@llui/dom'\n ) {\n return true\n }\n }\n return false\n}\n\n/**\n * Determines if a node is at a reactive-accessor position — either an\n * inline arrow / function expression OR an identifier that's about to\n * be resolved to one. The check is identity-based on `parent.arguments[0]`\n * etc., so the same logic works for both shapes.\n *\n * Exported so the cross-file walker can use the same gate. Without this\n * gate the walker descends into every 1-param arrow in the file —\n * including `onEffect: (bag) => bag.send(...)` — and pollutes\n * `__prefixes` with non-state property names (issue #5, bug 3).\n */\nexport function isReactiveAccessor(node: ts.Node): boolean {\n const parent = node.parent\n\n // text(s => s.count) — first arg to a call\n if (ts.isCallExpression(parent) && parent.arguments[0] === node) {\n // Skip item(t => t.id) — per-item selectors inside each() render.\n // Skip sample(s => s.x) — imperative one-shot read, no binding created\n // (both the top-level import and the destructured-from-h form).\n if (ts.isIdentifier(parent.expression)) {\n if (parent.expression.text === 'item' || parent.expression.text === 'sample') {\n return false\n }\n }\n // Skip array method callbacks: .filter(t => ...), .map(t => ...), .some(t => ...), etc.\n // Allow view-helper primitive calls: h.text(s => ...), h.memo(s => ...)\n if (ts.isPropertyAccessExpression(parent.expression)) {\n const methodName = parent.expression.name.text\n if (methodName === 'text' || methodName === 'memo') {\n return true\n }\n return false\n }\n return true\n }\n\n // div({ title: s => s.title }) — value in a property assignment inside an object literal.\n // Only treat as reactive if the containing call is a known framework API whose\n // properties are reactive accessors. Otherwise user-land helpers like\n // sliceHandler({ narrow: (m) => m.type === ... }) would pollute the path set.\n if (ts.isPropertyAssignment(parent)) {\n const key = parent.name\n if (ts.isIdentifier(key)) {\n // Skip event handlers (onClick, onInput, etc.)\n if (/^on[A-Z]/.test(key.text)) return false\n // Skip each() key function and other non-reactive props\n if (key.text === 'key' || key.text === 'name') return false\n // Walk up to find the enclosing call expression\n let ancestor: ts.Node | undefined = parent.parent // ObjectLiteralExpression\n while (ancestor && !ts.isCallExpression(ancestor)) {\n ancestor = ancestor.parent\n }\n if (!ancestor) return false\n const callExpr = ancestor as ts.CallExpression\n // Bare identifier: `scope({on: …})`, `div({title: …})`, etc.\n if (ts.isIdentifier(callExpr.expression)) {\n return REACTIVE_API_NAMES.has(callExpr.expression.text)\n }\n // Method-call form: `h.scope({on: …})`, `h.show({when: …})`, etc.\n // The docs and View bag promote this shape; without recognizing it\n // here, paths read ONLY through a structural primitive's\n // `on`/`when`/`items` accessor never enter `__prefixes`, so the\n // runtime dirty mask can't see changes to those fields and the\n // structural block silently fails to reconcile.\n if (ts.isPropertyAccessExpression(callExpr.expression)) {\n return REACTIVE_API_NAMES.has(callExpr.expression.name.text)\n }\n return false\n }\n }\n\n return false\n}\n\n// Framework APIs whose object-literal arguments contain reactive accessors.\n// Arrow functions in property values of these calls are state-tracked.\nconst REACTIVE_API_NAMES = new Set([\n // Element helpers (see ELEMENT_HELPERS in transform.ts — we keep a superset here)\n ...[\n 'a',\n 'abbr',\n 'article',\n 'aside',\n 'b',\n 'blockquote',\n 'br',\n 'button',\n 'canvas',\n 'code',\n 'dd',\n 'details',\n 'dialog',\n 'div',\n 'dl',\n 'dt',\n 'em',\n 'fieldset',\n 'figcaption',\n 'figure',\n 'footer',\n 'form',\n 'h1',\n 'h2',\n 'h3',\n 'h4',\n 'h5',\n 'h6',\n 'header',\n 'hr',\n 'i',\n 'iframe',\n 'img',\n 'input',\n 'label',\n 'legend',\n 'li',\n 'main',\n 'mark',\n 'nav',\n 'ol',\n 'optgroup',\n 'option',\n 'output',\n 'p',\n 'pre',\n 'progress',\n 'section',\n 'select',\n 'small',\n 'span',\n 'strong',\n 'sub',\n 'summary',\n 'sup',\n 'table',\n 'tbody',\n 'td',\n 'textarea',\n 'tfoot',\n 'th',\n 'thead',\n 'time',\n 'tr',\n 'ul',\n 'video',\n ],\n // Structural primitives\n 'each',\n 'branch',\n 'scope',\n 'show',\n 'memo',\n 'portal',\n 'foreign',\n 'child',\n 'errorBoundary',\n // track({ deps: (s) => [...] }) — explicit reactivity declaration for\n // paths static analysis can't infer. The compiler treats `deps` as a\n // reactive accessor so its paths fold into the host component's\n // __prefixes; the call expression is then stripped from emission\n // (see transform.ts). v2b §3.\n 'track',\n])\n\n/**\n * Extract state access paths from an expression body.\n * Handles:\n * - Direct property access: param.field, param.field.subfield\n * - Bracket notation with string literal: param['field']\n */\nfunction extractPaths(node: ts.Node, paramName: string, _prefix: string, paths: Set<string>): void {\n if (ts.isPropertyAccessExpression(node)) {\n // Skip if this is an intermediate in a deeper chain\n if (ts.isPropertyAccessExpression(node.parent)) {\n // handled when the leaf is visited\n }\n // Skip if this is the callee of a method call: s.todos.filter(...)\n else if (ts.isCallExpression(node.parent) && node.parent.expression === node) {\n // It's a method call — record the object, not the method\n // e.g. s.todos.filter(...) → record 'todos', not 'todos.filter'\n if (ts.isPropertyAccessExpression(node.expression)) {\n const chain = resolvePropertyChain(node.expression, paramName)\n if (chain) paths.add(chain)\n }\n } else {\n const chain = resolvePropertyChain(node, paramName)\n if (chain) {\n paths.add(chain)\n }\n }\n }\n\n if (ts.isElementAccessExpression(node)) {\n const chain = resolveElementAccess(node, paramName)\n if (chain) {\n paths.add(chain)\n }\n }\n\n ts.forEachChild(node, (child) => extractPaths(child, paramName, _prefix, paths))\n}\n\n/**\n * Resolve a property access chain like s.user.name to \"user.name\".\n * Returns null if the chain doesn't start with the state parameter.\n * Stops at depth 2.\n */\nfunction resolvePropertyChain(node: ts.PropertyAccessExpression, paramName: string): string | null {\n const parts: string[] = []\n let current: ts.Expression = node\n\n while (ts.isPropertyAccessExpression(current)) {\n parts.unshift(current.name.text)\n current = current.expression\n }\n\n // The root must be the state parameter\n if (!ts.isIdentifier(current) || current.text !== paramName) {\n return null\n }\n\n // Limit to depth 2\n if (parts.length > 2) {\n return parts.slice(0, 2).join('.')\n }\n\n return parts.join('.')\n}\n\n/**\n * Resolve bracket access with string literal: s['count'] → \"count\"\n */\nfunction resolveElementAccess(node: ts.ElementAccessExpression, paramName: string): string | null {\n if (!ts.isIdentifier(node.expression) || node.expression.text !== paramName) {\n return null\n }\n\n if (ts.isStringLiteral(node.argumentExpression)) {\n return node.argumentExpression.text\n }\n\n return null\n}\n"]}
|
|
@@ -69,5 +69,8 @@ export declare function toCanonicalDiagnostic(d: WalkerDiagnostic, sourceText: s
|
|
|
69
69
|
* including `onEffect: (bag) => bag.send(...)`, where `bag.send` ends
|
|
70
70
|
* up in the path set as a phantom "send" prefix. Issue #5, bug 3.
|
|
71
71
|
*/
|
|
72
|
-
export declare function crossFileAccessorPaths(program: ts.Program, focalFile: ts.SourceFile):
|
|
72
|
+
export declare function crossFileAccessorPaths(program: ts.Program, focalFile: ts.SourceFile): {
|
|
73
|
+
paths: Set<string>;
|
|
74
|
+
opaque: boolean;
|
|
75
|
+
};
|
|
73
76
|
//# sourceMappingURL=cross-file-walker.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cross-file-walker.d.ts","sourceRoot":"","sources":["../src/cross-file-walker.ts"],"names":[],"mappings":"AAyBA,OAAO,EAAE,MAAM,YAAY,CAAA;AAC3B,OAAO,EACL,KAAK,UAAU,EAKhB,MAAM,iBAAiB,CAAA;AAGxB,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,QAAQ,GAAG,OAAO,GAAG,cAAc,CAAA;AAE3E,MAAM,WAAW,wBAAwB;IACvC,IAAI,EAAE,cAAc,CAAA;IACpB,oEAAoE;IACpE,KAAK,EAAE,KAAK,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAA;IACvB,6BAA6B;IAC7B,MAAM,EAAE,MAAM,CAAA;CACf;AAED,MAAM,MAAM,YAAY,GAAG,uBAAuB,GAAG,wBAAwB,GAAG,mBAAmB,CAAA;AAEnG,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,YAAY,CAAA;IAChB,IAAI,EAAE,MAAM,CAAA;IACZ,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;IACX,OAAO,EAAE,MAAM,CAAA;IACf,UAAU,EAAE,MAAM,GAAG,SAAS,CAAA;CAC/B;AAED,MAAM,WAAW,YAAY;IAC3B,WAAW,EAAE,gBAAgB,EAAE,CAAA;IAC/B,qCAAqC;IACrC,OAAO,EAAE,GAAG,CACV,MAAM,EACN;QACE,eAAe,EAAE,MAAM,CAAA;QACvB,MAAM,EAAE,MAAM,CAAA;QACd,MAAM,EAAE,MAAM,CAAA;QACd,KAAK,EAAE,MAAM,CAAA;QACb,UAAU,EAAE,MAAM,CAAA;KACnB,CACF,CAAA;CACF;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,EAAE,CAAC,MAAM,EACjB,OAAO,EAAE,EAAE,CAAC,WAAW,GACtB,wBAAwB,CA0D1B;AA8LD;;;;GAIG;AACH,wBAAgB,WAAW,CACzB,OAAO,EAAE,EAAE,CAAC,OAAO,EACnB,OAAO,GAAE;IAAE,MAAM,CAAC,EAAE,CAAC,UAAU,EAAE,EAAE,CAAC,UAAU,KAAK,OAAO,CAAA;CAAO,GAChE,YAAY,CA0Dd;AA6CD;;;;GAIG;AACH,wBAAgB,qBAAqB,CACnC,CAAC,EAAE,gBAAgB,EACnB,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,GAClB,UAAU,CAaZ;AAoBD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,sBAAsB,
|
|
1
|
+
{"version":3,"file":"cross-file-walker.d.ts","sourceRoot":"","sources":["../src/cross-file-walker.ts"],"names":[],"mappings":"AAyBA,OAAO,EAAE,MAAM,YAAY,CAAA;AAC3B,OAAO,EACL,KAAK,UAAU,EAKhB,MAAM,iBAAiB,CAAA;AAGxB,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,QAAQ,GAAG,OAAO,GAAG,cAAc,CAAA;AAE3E,MAAM,WAAW,wBAAwB;IACvC,IAAI,EAAE,cAAc,CAAA;IACpB,oEAAoE;IACpE,KAAK,EAAE,KAAK,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAA;IACvB,6BAA6B;IAC7B,MAAM,EAAE,MAAM,CAAA;CACf;AAED,MAAM,MAAM,YAAY,GAAG,uBAAuB,GAAG,wBAAwB,GAAG,mBAAmB,CAAA;AAEnG,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,YAAY,CAAA;IAChB,IAAI,EAAE,MAAM,CAAA;IACZ,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;IACX,OAAO,EAAE,MAAM,CAAA;IACf,UAAU,EAAE,MAAM,GAAG,SAAS,CAAA;CAC/B;AAED,MAAM,WAAW,YAAY;IAC3B,WAAW,EAAE,gBAAgB,EAAE,CAAA;IAC/B,qCAAqC;IACrC,OAAO,EAAE,GAAG,CACV,MAAM,EACN;QACE,eAAe,EAAE,MAAM,CAAA;QACvB,MAAM,EAAE,MAAM,CAAA;QACd,MAAM,EAAE,MAAM,CAAA;QACd,KAAK,EAAE,MAAM,CAAA;QACb,UAAU,EAAE,MAAM,CAAA;KACnB,CACF,CAAA;CACF;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,EAAE,CAAC,MAAM,EACjB,OAAO,EAAE,EAAE,CAAC,WAAW,GACtB,wBAAwB,CA0D1B;AA8LD;;;;GAIG;AACH,wBAAgB,WAAW,CACzB,OAAO,EAAE,EAAE,CAAC,OAAO,EACnB,OAAO,GAAE;IAAE,MAAM,CAAC,EAAE,CAAC,UAAU,EAAE,EAAE,CAAC,UAAU,KAAK,OAAO,CAAA;CAAO,GAChE,YAAY,CA0Dd;AA6CD;;;;GAIG;AACH,wBAAgB,qBAAqB,CACnC,CAAC,EAAE,gBAAgB,EACnB,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,GAClB,UAAU,CAaZ;AAoBD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,EAAE,CAAC,OAAO,EACnB,SAAS,EAAE,EAAE,CAAC,UAAU,GACvB;IAAE,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAAC,MAAM,EAAE,OAAO,CAAA;CAAE,CA+BzC"}
|
|
@@ -418,6 +418,7 @@ export function toCanonicalDiagnostic(d, sourceText, projectRoot) {
|
|
|
418
418
|
export function crossFileAccessorPaths(program, focalFile) {
|
|
419
419
|
const checker = program.getTypeChecker();
|
|
420
420
|
const paths = new Set();
|
|
421
|
+
const opaqueOut = { value: false };
|
|
421
422
|
const visitedHelpers = new Set();
|
|
422
423
|
const isViewHelperCallArg0 = (arrow) => {
|
|
423
424
|
const parent = arrow.parent;
|
|
@@ -436,14 +437,14 @@ export function crossFileAccessorPaths(program, focalFile) {
|
|
|
436
437
|
const p0 = node.parameters[0];
|
|
437
438
|
if (ts.isIdentifier(p0.name) && node.body) {
|
|
438
439
|
if (isReactiveAccessor(node) || isViewHelperCallArg0(node)) {
|
|
439
|
-
walkAccessorBody(node.body, p0.name.text, paths, checker, visitedHelpers);
|
|
440
|
+
walkAccessorBody(node.body, p0.name.text, paths, checker, visitedHelpers, opaqueOut);
|
|
440
441
|
}
|
|
441
442
|
}
|
|
442
443
|
}
|
|
443
444
|
ts.forEachChild(node, visit);
|
|
444
445
|
};
|
|
445
446
|
visit(focalFile);
|
|
446
|
-
return paths;
|
|
447
|
+
return { paths, opaque: opaqueOut.value };
|
|
447
448
|
}
|
|
448
449
|
// Helpers whose arrow args are NOT state accessors — same exclusion list
|
|
449
450
|
// the file-local walker uses (collect-deps.ts § NON_DELEGATION_HELPERS).
|
|
@@ -452,7 +453,7 @@ export function crossFileAccessorPaths(program, focalFile) {
|
|
|
452
453
|
// `unsafeHtml` already have their inline arrow walked by the top-level
|
|
453
454
|
// visitor — we'd double-count if we recursed through the call again.
|
|
454
455
|
const NON_DELEGATION_CALLEES = new Set(['sample', 'item', 'memo', 'text', 'unsafeHtml']);
|
|
455
|
-
function walkAccessorBody(body, paramName, paths, checker, visitedHelpers) {
|
|
456
|
+
function walkAccessorBody(body, paramName, paths, checker, visitedHelpers, opaqueOut) {
|
|
456
457
|
const visit = (node) => {
|
|
457
458
|
// Property-chain extraction (mirrors collect-deps' depth-2 normaliser).
|
|
458
459
|
if (ts.isPropertyAccessExpression(node)) {
|
|
@@ -460,6 +461,43 @@ function walkAccessorBody(body, paramName, paths, checker, visitedHelpers) {
|
|
|
460
461
|
if (chain)
|
|
461
462
|
paths.add(chain);
|
|
462
463
|
}
|
|
464
|
+
// Opaque-state-flow classifier (mirrors the per-binding mask
|
|
465
|
+
// classifier in transform.ts:computeAccessorMask). Any standalone
|
|
466
|
+
// appearance of the param identifier in a non-tracked container
|
|
467
|
+
// means the helper reads through an expression we can't trace, so
|
|
468
|
+
// a precise prefix table is insufficient — the host needs a
|
|
469
|
+
// whole-state sentinel in `__prefixes`.
|
|
470
|
+
if (ts.isIdentifier(node) && node.text === paramName) {
|
|
471
|
+
const parent = node.parent;
|
|
472
|
+
const isBinding = !!parent && ts.isParameter(parent);
|
|
473
|
+
if (!isBinding) {
|
|
474
|
+
let isTracked = false;
|
|
475
|
+
if (parent) {
|
|
476
|
+
if (ts.isPropertyAccessExpression(parent) && parent.expression === node) {
|
|
477
|
+
isTracked = true;
|
|
478
|
+
}
|
|
479
|
+
else if (ts.isElementAccessExpression(parent) && parent.expression === node) {
|
|
480
|
+
isTracked =
|
|
481
|
+
ts.isStringLiteralLike(parent.argumentExpression) ||
|
|
482
|
+
ts.isNumericLiteral(parent.argumentExpression);
|
|
483
|
+
}
|
|
484
|
+
else if (ts.isCallExpression(parent) &&
|
|
485
|
+
ts.isIdentifier(parent.expression) &&
|
|
486
|
+
parent.arguments[0] === node &&
|
|
487
|
+
!NON_DELEGATION_CALLEES.has(parent.expression.text)) {
|
|
488
|
+
// Identifier-callee delegations are handled by the
|
|
489
|
+
// descend-into-helper branch below — if the callee
|
|
490
|
+
// resolves, recursion finds opaque inside; if not, the
|
|
491
|
+
// call's `sym` is undefined and the host sees no descent.
|
|
492
|
+
// Treat as tracked here so this branch alone doesn't flip
|
|
493
|
+
// opaque on every well-formed `helper(s)`.
|
|
494
|
+
isTracked = true;
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
if (!isTracked)
|
|
498
|
+
opaqueOut.value = true;
|
|
499
|
+
}
|
|
500
|
+
}
|
|
463
501
|
if (ts.isCallExpression(node)) {
|
|
464
502
|
const callee = node.expression;
|
|
465
503
|
const sym = resolveAliasedSymbol(callee, checker);
|
|
@@ -471,7 +509,7 @@ function walkAccessorBody(body, paramName, paths, checker, visitedHelpers) {
|
|
|
471
509
|
// into our state, identifier args pass our state through).
|
|
472
510
|
visitedHelpers.add(decl);
|
|
473
511
|
descendIntoHelper(decl, node, paramName, paths, checker, visitedHelpers,
|
|
474
|
-
/*viewHelper*/ true);
|
|
512
|
+
/*viewHelper*/ true, opaqueOut);
|
|
475
513
|
}
|
|
476
514
|
else if (decl && !visitedHelpers.has(decl)) {
|
|
477
515
|
// Non-view-helper: only follow if our state param is passed
|
|
@@ -490,10 +528,35 @@ function walkAccessorBody(body, paramName, paths, checker, visitedHelpers) {
|
|
|
490
528
|
!NON_DELEGATION_CALLEES.has(callee.text) &&
|
|
491
529
|
callPassesParamIdent(node, paramName)) {
|
|
492
530
|
visitedHelpers.add(decl);
|
|
493
|
-
|
|
494
|
-
|
|
531
|
+
const fnDecl = decl;
|
|
532
|
+
if (fnDecl.body) {
|
|
533
|
+
descendIntoHelper(decl, node, paramName, paths, checker, visitedHelpers,
|
|
534
|
+
/*viewHelper*/ false, opaqueOut);
|
|
535
|
+
}
|
|
536
|
+
else {
|
|
537
|
+
// Declaration without a body — ambient `declare function`,
|
|
538
|
+
// overload signature, or compiled .d.ts. State flows in
|
|
539
|
+
// but we can't see what it reads. Conservative: opaque.
|
|
540
|
+
opaqueOut.value = true;
|
|
541
|
+
}
|
|
495
542
|
}
|
|
496
543
|
}
|
|
544
|
+
else if (!decl && ts.isIdentifier(callee) && !NON_DELEGATION_CALLEES.has(callee.text)) {
|
|
545
|
+
// Callee resolved to a symbol but no function-like
|
|
546
|
+
// declaration (e.g., ambient declaration, type-only import,
|
|
547
|
+
// or a binding whose initializer the checker can't pin
|
|
548
|
+
// down). If state flows in, treat as opaque — same
|
|
549
|
+
// conservative read as the file-local classifier.
|
|
550
|
+
if (callPassesParamIdent(node, paramName))
|
|
551
|
+
opaqueOut.value = true;
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
else if (ts.isIdentifier(callee) && !NON_DELEGATION_CALLEES.has(callee.text)) {
|
|
555
|
+
// Callee identifier didn't resolve to ANY symbol (declared
|
|
556
|
+
// outside the program, lost through transient binding, etc.).
|
|
557
|
+
// The standalone `s` classifier above already flagged the arg
|
|
558
|
+
// as opaque, so no additional bookkeeping is needed here; the
|
|
559
|
+
// branch is kept to mirror the file-local handling shape.
|
|
497
560
|
}
|
|
498
561
|
}
|
|
499
562
|
ts.forEachChild(node, visit);
|
|
@@ -507,7 +570,7 @@ function callPassesParamIdent(call, paramName) {
|
|
|
507
570
|
}
|
|
508
571
|
return false;
|
|
509
572
|
}
|
|
510
|
-
function descendIntoHelper(decl, callSite, outerParamName, paths, checker, visitedHelpers, viewHelper) {
|
|
573
|
+
function descendIntoHelper(decl, callSite, outerParamName, paths, checker, visitedHelpers, viewHelper, opaqueOut) {
|
|
511
574
|
// Match each parameter to its argument at the call site.
|
|
512
575
|
//
|
|
513
576
|
// For §2.1 view-helpers: arrow-arg accessors like `(t) => t.foo` are
|
|
@@ -533,11 +596,11 @@ function descendIntoHelper(decl, callSite, outerParamName, paths, checker, visit
|
|
|
533
596
|
if (viewHelper && (ts.isArrowFunction(arg) || ts.isFunctionExpression(arg))) {
|
|
534
597
|
const a0 = arg.parameters[0];
|
|
535
598
|
if (a0 && ts.isIdentifier(a0.name) && arg.body) {
|
|
536
|
-
walkAccessorBody(arg.body, a0.name.text, paths, checker, visitedHelpers);
|
|
599
|
+
walkAccessorBody(arg.body, a0.name.text, paths, checker, visitedHelpers, opaqueOut);
|
|
537
600
|
}
|
|
538
601
|
}
|
|
539
602
|
else if (ts.isIdentifier(arg) && arg.text === outerParamName) {
|
|
540
|
-
walkAccessorBody(fnDecl.body, param.name.text, paths, checker, visitedHelpers);
|
|
603
|
+
walkAccessorBody(fnDecl.body, param.name.text, paths, checker, visitedHelpers, opaqueOut);
|
|
541
604
|
}
|
|
542
605
|
}
|
|
543
606
|
}
|