@llui/compiler 0.5.1 → 0.5.3

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.
@@ -53,6 +53,14 @@ export declare function isMemoCallWithArrowArg(expr: ts.Expression): expr is ts.
53
53
  * - `memo((s) => …)` — returns the inner arrow
54
54
  * - `someIdentifier` resolving to any of the above (or to a hoisted
55
55
  * `function X(s) { … }` declaration)
56
+ *
57
+ * When `checker` is supplied, identifier resolution follows alias chains
58
+ * across files: `import { matrixOrEmpty } from '../state'` becomes
59
+ * resolvable. Without a checker the resolver falls back to file-local
60
+ * `const`/`function` lookup. The cross-file path requires the
61
+ * identifier's AST node to be bound to the checker's Program — pass
62
+ * nodes obtained via `program.getSourceFile(...)`, not from a freshly
63
+ * `ts.createSourceFile`'d copy. (See AnalysisContext.program.)
56
64
  */
57
- export declare function resolveAccessorBody(value: ts.Expression): ts.ArrowFunction | ts.FunctionExpression | ts.FunctionDeclaration | null;
65
+ export declare function resolveAccessorBody(value: ts.Expression, checker?: ts.TypeChecker): ts.ArrowFunction | ts.FunctionExpression | ts.FunctionDeclaration | null;
58
66
  //# sourceMappingURL=accessor-resolver.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"accessor-resolver.d.ts","sourceRoot":"","sources":["../src/accessor-resolver.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,YAAY,CAAA;AAE3B;;;;;;;;;;;;;;;;;;;GAmBG;AAEH;;;;;;;;;;;GAWG;AACH,wBAAgB,4BAA4B,CAC1C,GAAG,EAAE,EAAE,CAAC,UAAU,GACjB,EAAE,CAAC,UAAU,GAAG,EAAE,CAAC,mBAAmB,GAAG,IAAI,CA8B/C;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,EAAE,CAAC,UAAU,GAAG,IAAI,IAAI,EAAE,CAAC,cAAc,GAAG;IACvF,SAAS,EAAE,SAAS,CAAC,EAAE,CAAC,aAAa,GAAG,EAAE,CAAC,kBAAkB,EAAE,GAAG,EAAE,CAAC,UAAU,EAAE,CAAC,CAAA;CACnF,CAQA;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,EAAE,CAAC,UAAU,GACnB,EAAE,CAAC,aAAa,GAAG,EAAE,CAAC,kBAAkB,GAAG,EAAE,CAAC,mBAAmB,GAAG,IAAI,CAoB1E"}
1
+ {"version":3,"file":"accessor-resolver.d.ts","sourceRoot":"","sources":["../src/accessor-resolver.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,YAAY,CAAA;AAE3B;;;;;;;;;;;;;;;;;;;GAmBG;AAEH;;;;;;;;;;;GAWG;AACH,wBAAgB,4BAA4B,CAC1C,GAAG,EAAE,EAAE,CAAC,UAAU,GACjB,EAAE,CAAC,UAAU,GAAG,EAAE,CAAC,mBAAmB,GAAG,IAAI,CA8B/C;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,EAAE,CAAC,UAAU,GAAG,IAAI,IAAI,EAAE,CAAC,cAAc,GAAG;IACvF,SAAS,EAAE,SAAS,CAAC,EAAE,CAAC,aAAa,GAAG,EAAE,CAAC,kBAAkB,EAAE,GAAG,EAAE,CAAC,UAAU,EAAE,CAAC,CAAA;CACnF,CAQA;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,EAAE,CAAC,UAAU,EACpB,OAAO,CAAC,EAAE,EAAE,CAAC,WAAW,GACvB,EAAE,CAAC,aAAa,GAAG,EAAE,CAAC,kBAAkB,GAAG,EAAE,CAAC,mBAAmB,GAAG,IAAI,CA0B1E"}
@@ -94,24 +94,83 @@ export function isMemoCallWithArrowArg(expr) {
94
94
  * - `memo((s) => …)` — returns the inner arrow
95
95
  * - `someIdentifier` resolving to any of the above (or to a hoisted
96
96
  * `function X(s) { … }` declaration)
97
+ *
98
+ * When `checker` is supplied, identifier resolution follows alias chains
99
+ * across files: `import { matrixOrEmpty } from '../state'` becomes
100
+ * resolvable. Without a checker the resolver falls back to file-local
101
+ * `const`/`function` lookup. The cross-file path requires the
102
+ * identifier's AST node to be bound to the checker's Program — pass
103
+ * nodes obtained via `program.getSourceFile(...)`, not from a freshly
104
+ * `ts.createSourceFile`'d copy. (See AnalysisContext.program.)
97
105
  */
98
- export function resolveAccessorBody(value) {
106
+ export function resolveAccessorBody(value, checker) {
99
107
  if (ts.isArrowFunction(value) || ts.isFunctionExpression(value))
100
108
  return value;
101
109
  if (isMemoCallWithArrowArg(value)) {
102
110
  return value.arguments[0];
103
111
  }
104
112
  if (ts.isIdentifier(value)) {
105
- const resolved = resolveLocalConstInitializer(value);
106
- if (!resolved)
113
+ const local = resolveLocalConstInitializer(value);
114
+ if (local) {
115
+ if (ts.isArrowFunction(local) ||
116
+ ts.isFunctionExpression(local) ||
117
+ ts.isFunctionDeclaration(local)) {
118
+ return local;
119
+ }
120
+ if (isMemoCallWithArrowArg(local)) {
121
+ return local.arguments[0];
122
+ }
107
123
  return null;
108
- if (ts.isArrowFunction(resolved) ||
109
- ts.isFunctionExpression(resolved) ||
110
- ts.isFunctionDeclaration(resolved)) {
111
- return resolved;
112
124
  }
113
- if (isMemoCallWithArrowArg(resolved)) {
114
- return resolved.arguments[0];
125
+ if (checker) {
126
+ const resolved = resolveCrossFileAccessor(value, checker);
127
+ if (resolved)
128
+ return resolved;
129
+ }
130
+ }
131
+ return null;
132
+ }
133
+ /**
134
+ * Follow the alias chain for an identifier reference through the type
135
+ * checker, then inspect the resolved symbol's declarations for an arrow
136
+ * accessor we can mask-analyze. This is the same descent the cross-file
137
+ * walker does for view-helper classification (`cross-file-walker.ts`),
138
+ * applied here so a same-package import like
139
+ * `import { matrixOrEmpty } from '../state'`
140
+ * `value: (s) => matrixOrEmpty(s).field`
141
+ * doesn't trip the opaque-flow leak diagnostic — the walker descends
142
+ * into `matrixOrEmpty`'s body and the call is tracked.
143
+ *
144
+ * Returns null for ambient declarations, type-only imports, parameters,
145
+ * destructured bindings, and re-exports the checker can't pin to a
146
+ * function-like declaration — every shape the opaque-flow rule is
147
+ * documented to flag as a leak.
148
+ */
149
+ function resolveCrossFileAccessor(use, checker) {
150
+ let sym = checker.getSymbolAtLocation(use);
151
+ if (!sym)
152
+ return null;
153
+ if (sym.flags & ts.SymbolFlags.Alias) {
154
+ try {
155
+ sym = checker.getAliasedSymbol(sym);
156
+ }
157
+ catch {
158
+ return null;
159
+ }
160
+ }
161
+ const decls = sym.getDeclarations();
162
+ if (!decls || decls.length === 0)
163
+ return null;
164
+ for (const decl of decls) {
165
+ if (ts.isFunctionDeclaration(decl) && decl.body)
166
+ return decl;
167
+ if (ts.isVariableDeclaration(decl) && decl.initializer) {
168
+ const init = decl.initializer;
169
+ if (ts.isArrowFunction(init) || ts.isFunctionExpression(init))
170
+ return init;
171
+ if (isMemoCallWithArrowArg(init)) {
172
+ return init.arguments[0];
173
+ }
115
174
  }
116
175
  }
117
176
  return null;
@@ -1 +1 @@
1
- {"version":3,"file":"accessor-resolver.js","sourceRoot":"","sources":["../src/accessor-resolver.ts"],"names":[],"mappings":"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","sourcesContent":["import ts from 'typescript'\n\n/**\n * Helpers for resolving identifier references at reactive-accessor\n * positions. Shared by `transform.ts` (compile-time prop classification\n * + Pass 2 mask injection) and `collect-deps.ts` (state-path scanning\n * for `__dirty` / `__maskLegend`).\n *\n * The compiler must distinguish the legitimate accessor shapes:\n *\n * - Inline arrow / function expression at the call site\n * - Inline `memo(arrow)` at the call site\n * - Identifier referencing a const-bound arrow / function expression\n * - Identifier referencing a hoisted function declaration\n * - Identifier referencing `const x = memo(arrow)`\n *\n * …from values we can't classify (imports, parameters, opaque calls), so\n * those can be bailed-to-runtime instead of silently miscompiled. See the\n * `disabled` binding bug, where a function reference at a reactive prop\n * position was statically assigned (`__e.disabled = isGated`) — writing\n * the function object onto the boolean DOM property and never re-evaluating.\n */\n\n/**\n * Walk parent chains to find a `const X = ...` declaration matching\n * `use.text`, or a hoisted `function X(...)` declaration. Returns the\n * resolved declaration or `null` for unresolvable references (imports,\n * parameters, this-bindings, etc.).\n *\n * Limitations:\n * - Only `const`. `let` resolution is unsafe — we can't track later\n * reassignments without a type checker.\n * - Only single-binding declarations (`const a = …`, not `const a = …, b = …`).\n * - The declaration must dominate the use (lexical scope).\n */\nexport function resolveLocalConstInitializer(\n use: ts.Identifier,\n): ts.Expression | ts.FunctionDeclaration | null {\n const name = use.text\n let node: ts.Node = use\n while (node.parent) {\n const parent = node.parent\n let statements: readonly ts.Statement[] | null = null\n if (ts.isBlock(parent) || ts.isSourceFile(parent) || ts.isModuleBlock(parent)) {\n statements = parent.statements\n } else if (ts.isCaseClause(parent) || ts.isDefaultClause(parent)) {\n statements = parent.statements\n }\n if (statements) {\n for (const stmt of statements) {\n if (ts.isFunctionDeclaration(stmt)) {\n if (stmt.name && stmt.name.text === name) return stmt\n continue\n }\n if (!ts.isVariableStatement(stmt)) continue\n const flags = stmt.declarationList.flags\n if (!(flags & ts.NodeFlags.Const)) continue\n if (stmt.declarationList.declarations.length !== 1) continue\n const decl = stmt.declarationList.declarations[0]!\n if (!ts.isIdentifier(decl.name) || decl.name.text !== name) continue\n if (!decl.initializer) continue\n return decl.initializer\n }\n }\n node = parent\n }\n return null\n}\n\n/**\n * Recognize `memo(arrow)` / `memo(fn)` calls so the inner accessor can\n * be analyzed for state-path masking. The runtime `memo()` returns a\n * cached accessor — its body's reads determine when it re-evaluates,\n * not the call site.\n */\nexport function isMemoCallWithArrowArg(expr: ts.Expression): expr is ts.CallExpression & {\n arguments: readonly [ts.ArrowFunction | ts.FunctionExpression, ...ts.Expression[]]\n} {\n return (\n ts.isCallExpression(expr) &&\n ts.isIdentifier(expr.expression) &&\n expr.expression.text === 'memo' &&\n expr.arguments.length >= 1 &&\n (ts.isArrowFunction(expr.arguments[0]!) || ts.isFunctionExpression(expr.arguments[0]!))\n )\n}\n\n/**\n * Resolve a value at a reactive-accessor position down to the callable\n * AST node we can mask-analyze. Returns `null` when the value isn't a\n * recognized accessor shape — caller leaves the call unchanged (runtime\n * falls back to FULL_MASK, which is correct just slower).\n *\n * Recognized shapes:\n * - `(s) => …` (ArrowFunction)\n * - `function (s) { … }` (FunctionExpression)\n * - `memo((s) => …)` — returns the inner arrow\n * - `someIdentifier` resolving to any of the above (or to a hoisted\n * `function X(s) { … }` declaration)\n */\nexport function resolveAccessorBody(\n value: ts.Expression,\n): ts.ArrowFunction | ts.FunctionExpression | ts.FunctionDeclaration | null {\n if (ts.isArrowFunction(value) || ts.isFunctionExpression(value)) return value\n if (isMemoCallWithArrowArg(value)) {\n return value.arguments[0] as ts.ArrowFunction | ts.FunctionExpression\n }\n if (ts.isIdentifier(value)) {\n const resolved = resolveLocalConstInitializer(value)\n if (!resolved) return null\n if (\n ts.isArrowFunction(resolved) ||\n ts.isFunctionExpression(resolved) ||\n ts.isFunctionDeclaration(resolved)\n ) {\n return resolved\n }\n if (isMemoCallWithArrowArg(resolved)) {\n return resolved.arguments[0] as ts.ArrowFunction | ts.FunctionExpression\n }\n }\n return null\n}\n"]}
1
+ {"version":3,"file":"accessor-resolver.js","sourceRoot":"","sources":["../src/accessor-resolver.ts"],"names":[],"mappings":"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","sourcesContent":["import ts from 'typescript'\n\n/**\n * Helpers for resolving identifier references at reactive-accessor\n * positions. Shared by `transform.ts` (compile-time prop classification\n * + Pass 2 mask injection) and `collect-deps.ts` (state-path scanning\n * for `__dirty` / `__maskLegend`).\n *\n * The compiler must distinguish the legitimate accessor shapes:\n *\n * - Inline arrow / function expression at the call site\n * - Inline `memo(arrow)` at the call site\n * - Identifier referencing a const-bound arrow / function expression\n * - Identifier referencing a hoisted function declaration\n * - Identifier referencing `const x = memo(arrow)`\n *\n * …from values we can't classify (imports, parameters, opaque calls), so\n * those can be bailed-to-runtime instead of silently miscompiled. See the\n * `disabled` binding bug, where a function reference at a reactive prop\n * position was statically assigned (`__e.disabled = isGated`) — writing\n * the function object onto the boolean DOM property and never re-evaluating.\n */\n\n/**\n * Walk parent chains to find a `const X = ...` declaration matching\n * `use.text`, or a hoisted `function X(...)` declaration. Returns the\n * resolved declaration or `null` for unresolvable references (imports,\n * parameters, this-bindings, etc.).\n *\n * Limitations:\n * - Only `const`. `let` resolution is unsafe — we can't track later\n * reassignments without a type checker.\n * - Only single-binding declarations (`const a = …`, not `const a = …, b = …`).\n * - The declaration must dominate the use (lexical scope).\n */\nexport function resolveLocalConstInitializer(\n use: ts.Identifier,\n): ts.Expression | ts.FunctionDeclaration | null {\n const name = use.text\n let node: ts.Node = use\n while (node.parent) {\n const parent = node.parent\n let statements: readonly ts.Statement[] | null = null\n if (ts.isBlock(parent) || ts.isSourceFile(parent) || ts.isModuleBlock(parent)) {\n statements = parent.statements\n } else if (ts.isCaseClause(parent) || ts.isDefaultClause(parent)) {\n statements = parent.statements\n }\n if (statements) {\n for (const stmt of statements) {\n if (ts.isFunctionDeclaration(stmt)) {\n if (stmt.name && stmt.name.text === name) return stmt\n continue\n }\n if (!ts.isVariableStatement(stmt)) continue\n const flags = stmt.declarationList.flags\n if (!(flags & ts.NodeFlags.Const)) continue\n if (stmt.declarationList.declarations.length !== 1) continue\n const decl = stmt.declarationList.declarations[0]!\n if (!ts.isIdentifier(decl.name) || decl.name.text !== name) continue\n if (!decl.initializer) continue\n return decl.initializer\n }\n }\n node = parent\n }\n return null\n}\n\n/**\n * Recognize `memo(arrow)` / `memo(fn)` calls so the inner accessor can\n * be analyzed for state-path masking. The runtime `memo()` returns a\n * cached accessor — its body's reads determine when it re-evaluates,\n * not the call site.\n */\nexport function isMemoCallWithArrowArg(expr: ts.Expression): expr is ts.CallExpression & {\n arguments: readonly [ts.ArrowFunction | ts.FunctionExpression, ...ts.Expression[]]\n} {\n return (\n ts.isCallExpression(expr) &&\n ts.isIdentifier(expr.expression) &&\n expr.expression.text === 'memo' &&\n expr.arguments.length >= 1 &&\n (ts.isArrowFunction(expr.arguments[0]!) || ts.isFunctionExpression(expr.arguments[0]!))\n )\n}\n\n/**\n * Resolve a value at a reactive-accessor position down to the callable\n * AST node we can mask-analyze. Returns `null` when the value isn't a\n * recognized accessor shape — caller leaves the call unchanged (runtime\n * falls back to FULL_MASK, which is correct just slower).\n *\n * Recognized shapes:\n * - `(s) => …` (ArrowFunction)\n * - `function (s) { … }` (FunctionExpression)\n * - `memo((s) => …)` — returns the inner arrow\n * - `someIdentifier` resolving to any of the above (or to a hoisted\n * `function X(s) { … }` declaration)\n *\n * When `checker` is supplied, identifier resolution follows alias chains\n * across files: `import { matrixOrEmpty } from '../state'` becomes\n * resolvable. Without a checker the resolver falls back to file-local\n * `const`/`function` lookup. The cross-file path requires the\n * identifier's AST node to be bound to the checker's Program — pass\n * nodes obtained via `program.getSourceFile(...)`, not from a freshly\n * `ts.createSourceFile`'d copy. (See AnalysisContext.program.)\n */\nexport function resolveAccessorBody(\n value: ts.Expression,\n checker?: ts.TypeChecker,\n): ts.ArrowFunction | ts.FunctionExpression | ts.FunctionDeclaration | null {\n if (ts.isArrowFunction(value) || ts.isFunctionExpression(value)) return value\n if (isMemoCallWithArrowArg(value)) {\n return value.arguments[0] as ts.ArrowFunction | ts.FunctionExpression\n }\n if (ts.isIdentifier(value)) {\n const local = resolveLocalConstInitializer(value)\n if (local) {\n if (\n ts.isArrowFunction(local) ||\n ts.isFunctionExpression(local) ||\n ts.isFunctionDeclaration(local)\n ) {\n return local\n }\n if (isMemoCallWithArrowArg(local)) {\n return local.arguments[0] as ts.ArrowFunction | ts.FunctionExpression\n }\n return null\n }\n if (checker) {\n const resolved = resolveCrossFileAccessor(value, checker)\n if (resolved) return resolved\n }\n }\n return null\n}\n\n/**\n * Follow the alias chain for an identifier reference through the type\n * checker, then inspect the resolved symbol's declarations for an arrow\n * accessor we can mask-analyze. This is the same descent the cross-file\n * walker does for view-helper classification (`cross-file-walker.ts`),\n * applied here so a same-package import like\n * `import { matrixOrEmpty } from '../state'`\n * `value: (s) => matrixOrEmpty(s).field`\n * doesn't trip the opaque-flow leak diagnostic — the walker descends\n * into `matrixOrEmpty`'s body and the call is tracked.\n *\n * Returns null for ambient declarations, type-only imports, parameters,\n * destructured bindings, and re-exports the checker can't pin to a\n * function-like declaration — every shape the opaque-flow rule is\n * documented to flag as a leak.\n */\nfunction resolveCrossFileAccessor(\n use: ts.Identifier,\n checker: ts.TypeChecker,\n): ts.ArrowFunction | ts.FunctionExpression | ts.FunctionDeclaration | null {\n let sym = checker.getSymbolAtLocation(use)\n if (!sym) return null\n if (sym.flags & ts.SymbolFlags.Alias) {\n try {\n sym = checker.getAliasedSymbol(sym)\n } catch {\n return null\n }\n }\n const decls = sym.getDeclarations()\n if (!decls || decls.length === 0) return null\n for (const decl of decls) {\n if (ts.isFunctionDeclaration(decl) && decl.body) return decl\n if (ts.isVariableDeclaration(decl) && decl.initializer) {\n const init = decl.initializer\n if (ts.isArrowFunction(init) || ts.isFunctionExpression(init)) return init\n if (isMemoCallWithArrowArg(init)) {\n return init.arguments[0] as ts.ArrowFunction | ts.FunctionExpression\n }\n }\n }\n return null\n}\n"]}
@@ -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): Set<string>;
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;AAyG3B;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,2BAA2B,CAAC,UAAU,EAAE,EAAE,CAAC,UAAU,GAAG,GAAG,CAAC,MAAM,CAAC,CAuBlF;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;CACxB,CAsCA;AAeD;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,GAAG,OAAO,CA6DzD"}
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,CAuFzD"}
@@ -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. Skip identifiers
123
- // imported from elsewhere (resolver returns null) there's no
124
- // body to scan, runtime falls back to FULL_MASK.
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) {
@@ -271,6 +350,30 @@ export function isReactiveAccessor(node) {
271
350
  // Skip each() key function and other non-reactive props
272
351
  if (key.text === 'key' || key.text === 'name')
273
352
  return false;
353
+ // Skip view-builder slots: `default` / `render` / `fallback` on the
354
+ // structural primitives. Their callbacks receive a View<S, M> bag,
355
+ // not state — e.g. `branch({ default: (h) => h.text(...) })`. The
356
+ // single param is `h`, not `s`; treating it as a reactive accessor
357
+ // makes the opaque-flow walker chase `h` references as if they
358
+ // were state. The runtime knows these slots are view builders;
359
+ // the compiler did not, until now.
360
+ if (key.text === 'default' || key.text === 'render' || key.text === 'fallback') {
361
+ return false;
362
+ }
363
+ // Skip `cases.<k>` — the nested-object form of branch() cases.
364
+ // Each value is `(h: View<S, M>) => Node[]`, same as `default`.
365
+ // Identified by the enclosing object literal sitting in a
366
+ // `cases:` property assignment.
367
+ const enclosingObjLit = parent.parent;
368
+ if (enclosingObjLit && ts.isObjectLiteralExpression(enclosingObjLit)) {
369
+ const outerPA = enclosingObjLit.parent;
370
+ if (outerPA &&
371
+ ts.isPropertyAssignment(outerPA) &&
372
+ ts.isIdentifier(outerPA.name) &&
373
+ outerPA.name.text === 'cases') {
374
+ return false;
375
+ }
376
+ }
274
377
  // Walk up to find the enclosing call expression
275
378
  let ancestor = parent.parent; // ObjectLiteralExpression
276
379
  while (ancestor && !ts.isCallExpression(ancestor)) {
@@ -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":"AAAA,OAAO,EAAE,MAAM,YAAY,CAAA;AAC3B,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAA;AAE5D;;;;;;;GAOG;AACH,MAAM,sBAAsB,GAAG,IAAI,GAAG,CAAC,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,CAAC,CAAC,CAAA;AAExF;;;;;;;;;;;;;;;GAeG;AACH,SAAS,wBAAwB,CAC/B,IAAa,EACb,cAAsB,EACtB,MAA6F,EAC7F,YAAyB;IAEzB,SAAS,KAAK,CAAC,IAAa;QAC1B,IAAI,EAAE,CAAC,gBAAgB,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC;YAClE,MAAM,IAAI,GAAG,IAAI,CAAC,UAAU,CAAC,IAAI,CAAA;YACjC,IAAI,CAAC,sBAAsB,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;gBACtC,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAA;gBAC9B,IAAI,IAAI,IAAI,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,KAAK,cAAc,EAAE,CAAC;oBAClE,MAAM,QAAQ,GAAG,mBAAmB,CAAC,IAAI,CAAC,UAAU,CAAC,CAAA;oBACrD,IAAI,QAAQ;wBAAE,MAAM,CAAC,QAAQ,CAAC,CAAA;yBACzB,IAAI,YAAY;wBAAE,YAAY,EAAE,CAAA;gBACvC,CAAC;YACH,CAAC;QACH,CAAC;QACD,kEAAkE;QAClE,gEAAgE;QAChE,aAAa;QACb,IACE,EAAE,CAAC,eAAe,CAAC,IAAI,CAAC;YACxB,EAAE,CAAC,oBAAoB,CAAC,IAAI,CAAC;YAC7B,EAAE,CAAC,qBAAqB,CAAC,IAAI,CAAC,EAC9B,CAAC;YACD,OAAM;QACR,CAAC;QACD,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,KAAK,CAAC,CAAA;IAC9B,CAAC;IACD,+DAA+D;IAC/D,uDAAuD;IACvD,IAAI,EAAE,CAAC,eAAe,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,oBAAoB,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,qBAAqB,CAAC,IAAI,CAAC,EAAE,CAAC;QAChG,OAAM;IACR,CAAC;IACD,KAAK,CAAC,IAAI,CAAC,CAAA;AACb,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,SAAS,oBAAoB,CAC3B,QAA2E,EAC3E,KAAkB,EAClB,UAAwB,IAAI,GAAG,EAAE,EACjC,SAA8B;IAE9B,IAAI,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC;QAAE,OAAO,KAAK,CAAA;IACvC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAA;IAErB,MAAM,MAAM,GAAG,QAAQ,CAAC,UAAU,CAAA;IAClC,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,KAAK,CAAA;IACrC,MAAM,SAAS,GAAG,MAAM,CAAC,CAAC,CAAE,CAAC,IAAI,CAAA;IACjC,IAAI,CAAC,EAAE,CAAC,YAAY,CAAC,SAAS,CAAC,EAAE,CAAC;QAChC,8DAA8D;QAC9D,iEAAiE;QACjE,uDAAuD;QACvD,IAAI,SAAS;YAAE,SAAS,CAAC,KAAK,GAAG,IAAI,CAAA;QACrC,OAAO,KAAK,CAAA;IACd,CAAC;IACD,IAAI,CAAC,QAAQ,CAAC,IAAI;QAAE,OAAO,KAAK,CAAA;IAChC,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,CAAA;IAEzB,YAAY,CAAC,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC,IAAI,EAAE,EAAE,EAAE,KAAK,CAAC,CAAA;IAEtD,kEAAkE;IAClE,mEAAmE;IACnE,iEAAiE;IACjE,+DAA+D;IAC/D,gEAAgE;IAChE,sBAAsB;IACtB,IAAI,SAAS;QAAE,qBAAqB,CAAC,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC,IAAI,EAAE,SAAS,CAAC,CAAA;IAE9E,qEAAqE;IACrE,iEAAiE;IACjE,6DAA6D;IAC7D,kEAAkE;IAClE,8DAA8D;IAC9D,6CAA6C;IAC7C,wBAAwB,CACtB,QAAQ,CAAC,IAAI,EACb,SAAS,CAAC,IAAI,EACd,CAAC,QAAQ,EAAE,EAAE;QACX,oBAAoB,CAAC,QAAQ,EAAE,KAAK,EAAE,OAAO,EAAE,SAAS,CAAC,CAAA;IAC3D,CAAC,EACD,GAAG,EAAE;QACH,IAAI,SAAS;YAAE,SAAS,CAAC,KAAK,GAAG,IAAI,CAAA;IACvC,CAAC,CACF,CAAA;IAED,OAAO,KAAK,CAAC,IAAI,GAAG,MAAM,CAAA;AAC5B,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AACH,SAAS,qBAAqB,CAAC,IAAa,EAAE,UAAkB,EAAE,GAAuB;IACvF,SAAS,KAAK,CAAC,IAAa;QAC1B,IAAI,GAAG,CAAC,KAAK;YAAE,OAAM;QACrB,IAAI,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,KAAK,UAAU,EAAE,CAAC;YACtD,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAA;YAC1B,MAAM,SAAS,GAAG,CAAC,CAAC,MAAM,IAAI,EAAE,CAAC,WAAW,CAAC,MAAM,CAAC,CAAA;YACpD,IAAI,CAAC,SAAS,EAAE,CAAC;gBACf,IAAI,SAAS,GAAG,KAAK,CAAA;gBACrB,IAAI,MAAM,EAAE,CAAC;oBACX,IAAI,EAAE,CAAC,0BAA0B,CAAC,MAAM,CAAC,IAAI,MAAM,CAAC,UAAU,KAAK,IAAI,EAAE,CAAC;wBACxE,SAAS,GAAG,IAAI,CAAA;oBAClB,CAAC;yBAAM,IAAI,EAAE,CAAC,yBAAyB,CAAC,MAAM,CAAC,IAAI,MAAM,CAAC,UAAU,KAAK,IAAI,EAAE,CAAC;wBAC9E,SAAS;4BACP,EAAE,CAAC,mBAAmB,CAAC,MAAM,CAAC,kBAAkB,CAAC;gCACjD,EAAE,CAAC,gBAAgB,CAAC,MAAM,CAAC,kBAAkB,CAAC,CAAA;oBAClD,CAAC;yBAAM,IACL,EAAE,CAAC,gBAAgB,CAAC,MAAM,CAAC;wBAC3B,EAAE,CAAC,YAAY,CAAC,MAAM,CAAC,UAAU,CAAC;wBAClC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,IAAI;wBAC5B,CAAC,sBAAsB,CAAC,GAAG,CAAC,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,EACnD,CAAC;wBACD,2DAA2D;wBAC3D,uDAAuD;wBACvD,2DAA2D;wBAC3D,SAAS,GAAG,IAAI,CAAA;oBAClB,CAAC;gBACH,CAAC;gBACD,IAAI,CAAC,SAAS,EAAE,CAAC;oBACf,GAAG,CAAC,KAAK,GAAG,IAAI,CAAA;oBAChB,OAAM;gBACR,CAAC;YACH,CAAC;QACH,CAAC;QACD,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,KAAK,CAAC,CAAA;IAC9B,CAAC;IACD,KAAK,CAAC,IAAI,CAAC,CAAA;AACb,CAAC;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,2BAA2B,CAAC,UAAyB;IAInE,MAAM,KAAK,GAAG,IAAI,GAAG,EAAU,CAAA;IAC/B,MAAM,SAAS,GAAG,EAAE,KAAK,EAAE,KAAK,EAAE,CAAA;IAElC,SAAS,KAAK,CAAC,IAAa;QAC1B,6DAA6D;QAC7D,IAAI,EAAE,CAAC,eAAe,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,oBAAoB,CAAC,IAAI,CAAC,EAAE,CAAC;YAC9D,IAAI,kBAAkB,CAAC,IAAI,CAAC;gBAAE,oBAAoB,CAAC,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS,CAAC,CAAA;QACvF,CAAC;QAED,iEAAiE;QACjE,6DAA6D;QAC7D,+DAA+D;QAC/D,mEAAmE;QACnE,IAAI,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,kBAAkB,CAAC,IAAI,CAAC,EAAE,CAAC;YACtD,MAAM,QAAQ,GAAG,mBAAmB,CAAC,IAAI,CAAC,CAAA;YAC1C,IAAI,QAAQ;gBAAE,oBAAoB,CAAC,QAAQ,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS,CAAC,CAAA;;gBACpE,SAAS,CAAC,KAAK,GAAG,IAAI,CAAA;QAC7B,CAAC;QAED,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,KAAK,CAAC,CAAA;IAC9B,CAAC;IAED,KAAK,CAAC,UAAU,CAAC,CAAA;IACjB,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,CAAC,KAAK,EAAE,CAAA;AAC3C,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,uBAAuB,CAAC,UAAyB;IAC/D,MAAM,IAAI,GAAkB,EAAE,CAAA;IAE9B,SAAS,KAAK,CAAC,IAAa;QAC1B,IAAI,EAAE,CAAC,eAAe,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,oBAAoB,CAAC,IAAI,CAAC,EAAE,CAAC;YAC9D,IAAI,kBAAkB,CAAC,IAAI,CAAC,EAAE,CAAC;gBAC7B,MAAM,GAAG,GAAG,IAAI,GAAG,EAAU,CAAA;gBAC7B,IAAI,oBAAoB,CAAC,IAAI,EAAE,GAAG,CAAC;oBAAE,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;YACrD,CAAC;QACH,CAAC;QAED,IAAI,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,kBAAkB,CAAC,IAAI,CAAC,EAAE,CAAC;YACtD,MAAM,QAAQ,GAAG,mBAAmB,CAAC,IAAI,CAAC,CAAA;YAC1C,IAAI,QAAQ,EAAE,CAAC;gBACb,MAAM,GAAG,GAAG,IAAI,GAAG,EAAU,CAAA;gBAC7B,IAAI,oBAAoB,CAAC,QAAQ,EAAE,GAAG,CAAC;oBAAE,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;YACzD,CAAC;QACH,CAAC;QAED,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,KAAK,CAAC,CAAA;IAC9B,CAAC;IAED,KAAK,CAAC,UAAU,CAAC,CAAA;IACjB,OAAO,IAAI,CAAA;AACb,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,WAAW,CACzB,MAAc,EACd,UAAgC;IAMhC,MAAM,UAAU,GAAG,EAAE,CAAC,gBAAgB,CAAC,UAAU,EAAE,MAAM,EAAE,EAAE,CAAC,YAAY,CAAC,MAAM,EAAE,IAAI,CAAC,CAAA;IAExF,uCAAuC;IACvC,IAAI,CAAC,aAAa,CAAC,UAAU,CAAC,EAAE,CAAC;QAC/B,OAAO,EAAE,EAAE,EAAE,IAAI,GAAG,EAAE,EAAE,EAAE,EAAE,IAAI,GAAG,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,CAAA;IACxD,CAAC;IAED,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,2BAA2B,CAAC,UAAU,CAAC,CAAA;IACjE,wEAAwE;IACxE,mEAAmE;IACnE,qEAAqE;IACrE,+DAA+D;IAC/D,mEAAmE;IACnE,qEAAqE;IACrE,uBAAuB;IACvB,IAAI,UAAU,EAAE,CAAC;QACf,KAAK,MAAM,CAAC,IAAI,UAAU;YAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAA;IAC1C,CAAC;IAED,MAAM,EAAE,GAAG,IAAI,GAAG,EAAkB,CAAA;IACpC,MAAM,EAAE,GAAG,IAAI,GAAG,EAAkB,CAAA;IACpC,IAAI,KAAK,GAAG,CAAC,CAAA;IACb,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,KAAK,GAAG,EAAE,EAAE,CAAC;YACf,EAAE,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,IAAI,KAAK,CAAC,CAAA;QAC1B,CAAC;aAAM,IAAI,KAAK,GAAG,EAAE,EAAE,CAAC;YACtB,EAAE,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,KAAK,GAAG,EAAE,CAAC,CAAC,CAAA;QACjC,CAAC;aAAM,CAAC;YACN,+DAA+D;YAC/D,8DAA8D;YAC9D,qBAAqB;YACrB,EAAE,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAA;QAClB,CAAC;QACD,KAAK,EAAE,CAAA;IACT,CAAC;IAED,OAAO,EAAE,EAAE,EAAE,EAAE,EAAE,MAAM,EAAE,CAAA;AAC3B,CAAC;AAED,SAAS,aAAa,CAAC,UAAyB;IAC9C,KAAK,MAAM,IAAI,IAAI,UAAU,CAAC,UAAU,EAAE,CAAC;QACzC,IACE,EAAE,CAAC,mBAAmB,CAAC,IAAI,CAAC;YAC5B,EAAE,CAAC,eAAe,CAAC,IAAI,CAAC,eAAe,CAAC;YACxC,IAAI,CAAC,eAAe,CAAC,IAAI,KAAK,WAAW,EACzC,CAAC;YACD,OAAO,IAAI,CAAA;QACb,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAA;AACd,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,kBAAkB,CAAC,IAAa;IAC9C,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAA;IAE1B,2CAA2C;IAC3C,IAAI,EAAE,CAAC,gBAAgB,CAAC,MAAM,CAAC,IAAI,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;QAChE,kEAAkE;QAClE,uEAAuE;QACvE,gEAAgE;QAChE,IAAI,EAAE,CAAC,YAAY,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,CAAC;YACvC,IAAI,MAAM,CAAC,UAAU,CAAC,IAAI,KAAK,MAAM,IAAI,MAAM,CAAC,UAAU,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;gBAC7E,OAAO,KAAK,CAAA;YACd,CAAC;QACH,CAAC;QACD,wFAAwF;QACxF,wEAAwE;QACxE,IAAI,EAAE,CAAC,0BAA0B,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,CAAC;YACrD,MAAM,UAAU,GAAG,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAA;YAC9C,IAAI,UAAU,KAAK,MAAM,IAAI,UAAU,KAAK,MAAM,EAAE,CAAC;gBACnD,OAAO,IAAI,CAAA;YACb,CAAC;YACD,OAAO,KAAK,CAAA;QACd,CAAC;QACD,OAAO,IAAI,CAAA;IACb,CAAC;IAED,0FAA0F;IAC1F,+EAA+E;IAC/E,sEAAsE;IACtE,8EAA8E;IAC9E,IAAI,EAAE,CAAC,oBAAoB,CAAC,MAAM,CAAC,EAAE,CAAC;QACpC,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAA;QACvB,IAAI,EAAE,CAAC,YAAY,CAAC,GAAG,CAAC,EAAE,CAAC;YACzB,+CAA+C;YAC/C,IAAI,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC;gBAAE,OAAO,KAAK,CAAA;YAC3C,wDAAwD;YACxD,IAAI,GAAG,CAAC,IAAI,KAAK,KAAK,IAAI,GAAG,CAAC,IAAI,KAAK,MAAM;gBAAE,OAAO,KAAK,CAAA;YAC3D,oEAAoE;YACpE,mEAAmE;YACnE,kEAAkE;YAClE,mEAAmE;YACnE,+DAA+D;YAC/D,+DAA+D;YAC/D,mCAAmC;YACnC,IAAI,GAAG,CAAC,IAAI,KAAK,SAAS,IAAI,GAAG,CAAC,IAAI,KAAK,QAAQ,IAAI,GAAG,CAAC,IAAI,KAAK,UAAU,EAAE,CAAC;gBAC/E,OAAO,KAAK,CAAA;YACd,CAAC;YACD,+DAA+D;YAC/D,gEAAgE;YAChE,0DAA0D;YAC1D,gCAAgC;YAChC,MAAM,eAAe,GAAG,MAAM,CAAC,MAAM,CAAA;YACrC,IAAI,eAAe,IAAI,EAAE,CAAC,yBAAyB,CAAC,eAAe,CAAC,EAAE,CAAC;gBACrE,MAAM,OAAO,GAAG,eAAe,CAAC,MAAM,CAAA;gBACtC,IACE,OAAO;oBACP,EAAE,CAAC,oBAAoB,CAAC,OAAO,CAAC;oBAChC,EAAE,CAAC,YAAY,CAAC,OAAO,CAAC,IAAI,CAAC;oBAC7B,OAAO,CAAC,IAAI,CAAC,IAAI,KAAK,OAAO,EAC7B,CAAC;oBACD,OAAO,KAAK,CAAA;gBACd,CAAC;YACH,CAAC;YACD,gDAAgD;YAChD,IAAI,QAAQ,GAAwB,MAAM,CAAC,MAAM,CAAA,CAAC,0BAA0B;YAC5E,OAAO,QAAQ,IAAI,CAAC,EAAE,CAAC,gBAAgB,CAAC,QAAQ,CAAC,EAAE,CAAC;gBAClD,QAAQ,GAAG,QAAQ,CAAC,MAAM,CAAA;YAC5B,CAAC;YACD,IAAI,CAAC,QAAQ;gBAAE,OAAO,KAAK,CAAA;YAC3B,MAAM,QAAQ,GAAG,QAA6B,CAAA;YAC9C,6DAA6D;YAC7D,IAAI,EAAE,CAAC,YAAY,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC;gBACzC,OAAO,kBAAkB,CAAC,GAAG,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,CAAC,CAAA;YACzD,CAAC;YACD,kEAAkE;YAClE,mEAAmE;YACnE,yDAAyD;YACzD,gEAAgE;YAChE,+DAA+D;YAC/D,gDAAgD;YAChD,IAAI,EAAE,CAAC,0BAA0B,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC;gBACvD,OAAO,kBAAkB,CAAC,GAAG,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;YAC9D,CAAC;YACD,OAAO,KAAK,CAAA;QACd,CAAC;IACH,CAAC;IAED,OAAO,KAAK,CAAA;AACd,CAAC;AAED,4EAA4E;AAC5E,uEAAuE;AACvE,MAAM,kBAAkB,GAAG,IAAI,GAAG,CAAC;IACjC,kFAAkF;IAClF,GAAG;QACD,GAAG;QACH,MAAM;QACN,SAAS;QACT,OAAO;QACP,GAAG;QACH,YAAY;QACZ,IAAI;QACJ,QAAQ;QACR,QAAQ;QACR,MAAM;QACN,IAAI;QACJ,SAAS;QACT,QAAQ;QACR,KAAK;QACL,IAAI;QACJ,IAAI;QACJ,IAAI;QACJ,UAAU;QACV,YAAY;QACZ,QAAQ;QACR,QAAQ;QACR,MAAM;QACN,IAAI;QACJ,IAAI;QACJ,IAAI;QACJ,IAAI;QACJ,IAAI;QACJ,IAAI;QACJ,QAAQ;QACR,IAAI;QACJ,GAAG;QACH,QAAQ;QACR,KAAK;QACL,OAAO;QACP,OAAO;QACP,QAAQ;QACR,IAAI;QACJ,MAAM;QACN,MAAM;QACN,KAAK;QACL,IAAI;QACJ,UAAU;QACV,QAAQ;QACR,QAAQ;QACR,GAAG;QACH,KAAK;QACL,UAAU;QACV,SAAS;QACT,QAAQ;QACR,OAAO;QACP,MAAM;QACN,QAAQ;QACR,KAAK;QACL,SAAS;QACT,KAAK;QACL,OAAO;QACP,OAAO;QACP,IAAI;QACJ,UAAU;QACV,OAAO;QACP,IAAI;QACJ,OAAO;QACP,MAAM;QACN,IAAI;QACJ,IAAI;QACJ,OAAO;KACR;IACD,wBAAwB;IACxB,MAAM;IACN,QAAQ;IACR,OAAO;IACP,MAAM;IACN,MAAM;IACN,QAAQ;IACR,SAAS;IACT,OAAO;IACP,eAAe;IACf,sEAAsE;IACtE,qEAAqE;IACrE,gEAAgE;IAChE,iEAAiE;IACjE,8BAA8B;IAC9B,OAAO;CACR,CAAC,CAAA;AAEF;;;;;GAKG;AACH,SAAS,YAAY,CAAC,IAAa,EAAE,SAAiB,EAAE,OAAe,EAAE,KAAkB;IACzF,IAAI,EAAE,CAAC,0BAA0B,CAAC,IAAI,CAAC,EAAE,CAAC;QACxC,oDAAoD;QACpD,IAAI,EAAE,CAAC,0BAA0B,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;YAC/C,mCAAmC;QACrC,CAAC;QACD,mEAAmE;aAC9D,IAAI,EAAE,CAAC,gBAAgB,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,IAAI,CAAC,MAAM,CAAC,UAAU,KAAK,IAAI,EAAE,CAAC;YAC7E,yDAAyD;YACzD,gEAAgE;YAChE,IAAI,EAAE,CAAC,0BAA0B,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC;gBACnD,MAAM,KAAK,GAAG,oBAAoB,CAAC,IAAI,CAAC,UAAU,EAAE,SAAS,CAAC,CAAA;gBAC9D,IAAI,KAAK;oBAAE,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;YAC7B,CAAC;QACH,CAAC;aAAM,CAAC;YACN,MAAM,KAAK,GAAG,oBAAoB,CAAC,IAAI,EAAE,SAAS,CAAC,CAAA;YACnD,IAAI,KAAK,EAAE,CAAC;gBACV,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;YAClB,CAAC;QACH,CAAC;IACH,CAAC;IAED,IAAI,EAAE,CAAC,yBAAyB,CAAC,IAAI,CAAC,EAAE,CAAC;QACvC,MAAM,KAAK,GAAG,oBAAoB,CAAC,IAAI,EAAE,SAAS,CAAC,CAAA;QACnD,IAAI,KAAK,EAAE,CAAC;YACV,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;QAClB,CAAC;IACH,CAAC;IAED,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,YAAY,CAAC,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,CAAC,CAAC,CAAA;AAClF,CAAC;AAED;;;;GAIG;AACH,SAAS,oBAAoB,CAAC,IAAiC,EAAE,SAAiB;IAChF,MAAM,KAAK,GAAa,EAAE,CAAA;IAC1B,IAAI,OAAO,GAAkB,IAAI,CAAA;IAEjC,OAAO,EAAE,CAAC,0BAA0B,CAAC,OAAO,CAAC,EAAE,CAAC;QAC9C,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QAChC,OAAO,GAAG,OAAO,CAAC,UAAU,CAAA;IAC9B,CAAC;IAED,uCAAuC;IACvC,IAAI,CAAC,EAAE,CAAC,YAAY,CAAC,OAAO,CAAC,IAAI,OAAO,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;QAC5D,OAAO,IAAI,CAAA;IACb,CAAC;IAED,mBAAmB;IACnB,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACrB,OAAO,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IACpC,CAAC;IAED,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;AACxB,CAAC;AAED;;GAEG;AACH,SAAS,oBAAoB,CAAC,IAAgC,EAAE,SAAiB;IAC/E,IAAI,CAAC,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;QAC5E,OAAO,IAAI,CAAA;IACb,CAAC;IAED,IAAI,EAAE,CAAC,eAAe,CAAC,IAAI,CAAC,kBAAkB,CAAC,EAAE,CAAC;QAChD,OAAO,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAA;IACrC,CAAC;IAED,OAAO,IAAI,CAAA;AACb,CAAC","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 // Skip view-builder slots: `default` / `render` / `fallback` on the\n // structural primitives. Their callbacks receive a View<S, M> bag,\n // not state — e.g. `branch({ default: (h) => h.text(...) })`. The\n // single param is `h`, not `s`; treating it as a reactive accessor\n // makes the opaque-flow walker chase `h` references as if they\n // were state. The runtime knows these slots are view builders;\n // the compiler did not, until now.\n if (key.text === 'default' || key.text === 'render' || key.text === 'fallback') {\n return false\n }\n // Skip `cases.<k>` — the nested-object form of branch() cases.\n // Each value is `(h: View<S, M>) => Node[]`, same as `default`.\n // Identified by the enclosing object literal sitting in a\n // `cases:` property assignment.\n const enclosingObjLit = parent.parent\n if (enclosingObjLit && ts.isObjectLiteralExpression(enclosingObjLit)) {\n const outerPA = enclosingObjLit.parent\n if (\n outerPA &&\n ts.isPropertyAssignment(outerPA) &&\n ts.isIdentifier(outerPA.name) &&\n outerPA.name.text === 'cases'\n ) {\n return false\n }\n }\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): Set<string>;
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,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,EAAE,SAAS,EAAE,EAAE,CAAC,UAAU,GAAG,GAAG,CAAC,MAAM,CAAC,CA8BjG"}
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"}