@llui/compiler 0.5.2 → 0.5.4

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"]}
@@ -1 +1 @@
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"}
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,CAqGzD"}
@@ -318,24 +318,39 @@ export function isReactiveAccessor(node) {
318
318
  const parent = node.parent;
319
319
  // text(s => s.count) — first arg to a call
320
320
  if (ts.isCallExpression(parent) && parent.arguments[0] === node) {
321
- // Skip item(t => t.id) per-item selectors inside each() render.
322
- // Skip sample(s => s.x) imperative one-shot read, no binding created
323
- // (both the top-level import and the destructured-from-h form).
321
+ // Bare-identifier callee only the small set of @llui/dom primitives
322
+ // that take a reactive accessor as arg[0] qualifies. Defaulting to
323
+ // `true` here used to misclassify every user mutator (change(updater),
324
+ // dispatch(s), setTimeout(fn, ms), array helpers like
325
+ // `tryGet(s).then(arrow)`, etc.) as a reactive accessor, with two
326
+ // downstream symptoms: (a) the path collector treated the arrow's
327
+ // param as state and polluted `__prefixes` with phantom paths;
328
+ // (b) the opaque-state-flow lint walked the body and flagged
329
+ // perfectly legitimate updater patterns like
330
+ // `change((c) => cond ? newC : c)` as "state in conditional branch".
331
+ // The PropertyAssignment branch below already uses the equivalent
332
+ // allow-list pattern (`REACTIVE_API_NAMES.has(...)`); this branch
333
+ // is now symmetric.
334
+ //
335
+ // Destructured-renamed View-bag aliases (`view: ({text: t}) =>
336
+ // [t(s => ...)]`) resolve via the primitive's property name, so the
337
+ // membership check uses the original primitive name rather than the
338
+ // local alias. Without this, `t(s => s.count)` would silently skip
339
+ // path collection and mask injection.
324
340
  if (ts.isIdentifier(parent.expression)) {
325
- if (parent.expression.text === 'item' || parent.expression.text === 'sample') {
326
- return false;
327
- }
341
+ const originalName = resolveBareIdentToPrimitive(parent.expression);
342
+ return REACTIVE_BARE_IDENT_ARG0.has(originalName);
328
343
  }
329
344
  // Skip array method callbacks: .filter(t => ...), .map(t => ...), .some(t => ...), etc.
330
- // Allow view-helper primitive calls: h.text(s => ...), h.memo(s => ...)
345
+ // Allow view-helper primitive calls same set as the bare-identifier
346
+ // allow-list above, kept in sync so `h.text(s => …)` and `text(s => …)`
347
+ // are treated symmetrically.
331
348
  if (ts.isPropertyAccessExpression(parent.expression)) {
332
- const methodName = parent.expression.name.text;
333
- if (methodName === 'text' || methodName === 'memo') {
349
+ if (REACTIVE_BARE_IDENT_ARG0.has(parent.expression.name.text))
334
350
  return true;
335
- }
336
351
  return false;
337
352
  }
338
- return true;
353
+ return false;
339
354
  }
340
355
  // div({ title: s => s.title }) — value in a property assignment inside an object literal.
341
356
  // Only treat as reactive if the containing call is a known framework API whose
@@ -350,6 +365,30 @@ export function isReactiveAccessor(node) {
350
365
  // Skip each() key function and other non-reactive props
351
366
  if (key.text === 'key' || key.text === 'name')
352
367
  return false;
368
+ // Skip view-builder slots: `default` / `render` / `fallback` on the
369
+ // structural primitives. Their callbacks receive a View<S, M> bag,
370
+ // not state — e.g. `branch({ default: (h) => h.text(...) })`. The
371
+ // single param is `h`, not `s`; treating it as a reactive accessor
372
+ // makes the opaque-flow walker chase `h` references as if they
373
+ // were state. The runtime knows these slots are view builders;
374
+ // the compiler did not, until now.
375
+ if (key.text === 'default' || key.text === 'render' || key.text === 'fallback') {
376
+ return false;
377
+ }
378
+ // Skip `cases.<k>` — the nested-object form of branch() cases.
379
+ // Each value is `(h: View<S, M>) => Node[]`, same as `default`.
380
+ // Identified by the enclosing object literal sitting in a
381
+ // `cases:` property assignment.
382
+ const enclosingObjLit = parent.parent;
383
+ if (enclosingObjLit && ts.isObjectLiteralExpression(enclosingObjLit)) {
384
+ const outerPA = enclosingObjLit.parent;
385
+ if (outerPA &&
386
+ ts.isPropertyAssignment(outerPA) &&
387
+ ts.isIdentifier(outerPA.name) &&
388
+ outerPA.name.text === 'cases') {
389
+ return false;
390
+ }
391
+ }
353
392
  // Walk up to find the enclosing call expression
354
393
  let ancestor = parent.parent; // ObjectLiteralExpression
355
394
  while (ancestor && !ts.isCallExpression(ancestor)) {
@@ -376,6 +415,173 @@ export function isReactiveAccessor(node) {
376
415
  }
377
416
  return false;
378
417
  }
418
+ // Framework primitives whose first positional argument IS a reactive
419
+ // accessor (an arrow taking state). The set is intentionally tiny —
420
+ // every other bare-identifier callee with an arrow arg0 (user mutators,
421
+ // async helpers, timers, array constructors, …) must NOT be visited as
422
+ // a reactive accessor, or its closure parameter gets misclassified as
423
+ // state and its body gets walked by the opaque-flow lint.
424
+ //
425
+ // Excluded by design:
426
+ // - `sample(s => s.x)` — imperative one-shot read, no binding.
427
+ // - `item(t => t.x)` — per-item selector inside an `each.render`
428
+ // callback; the param is per-row, not the component's state.
429
+ // - `track({ deps })` — takes an object literal, not an arrow.
430
+ // - `provide(key, accessor)` — accessor is arg1, not arg0.
431
+ const REACTIVE_BARE_IDENT_ARG0 = new Set(['text', 'memo', 'unsafeHtml', 'selector']);
432
+ /**
433
+ * Resolve a bare-identifier callee back to the original primitive name,
434
+ * unwrapping the alias forms an author can produce locally:
435
+ *
436
+ * - Destructure rename in a function parameter — the canonical View-bag
437
+ * pattern: `view: ({text: t}) => [t(s => …)]` aliases `t` to `text`.
438
+ * - Destructure rename in a `const { ... } = …` declaration.
439
+ * - Const rebinding: `const t = text; t(s => …)` aliases `t` to `text`.
440
+ *
441
+ * The walker climbs the lexical scope finding the innermost binding for
442
+ * `ident.text`; for const rebinding it recursively follows the
443
+ * initializer when it's another bare Identifier (with a visited-set
444
+ * guard against cycles like `const a = b; const b = a`). Stops at the
445
+ * first non-Identifier initializer — `const t = someCall()` returns
446
+ * `'t'` unchanged, because the value isn't a primitive name we can
447
+ * statically pin.
448
+ *
449
+ * Restricted to local lexical resolution. Cross-file alias chains
450
+ * (`import { t } from './aliases'`) are the cross-file resolver's job;
451
+ * this AST-only predicate stops at the module boundary.
452
+ */
453
+ function resolveBareIdentToPrimitive(ident) {
454
+ const result = resolveBareIdentFrom(ident, ident.text, new Set());
455
+ return result ?? ident.text;
456
+ }
457
+ // Returns:
458
+ // - string: the resolved primitive-candidate name (caller checks
459
+ // REACTIVE_BARE_IDENT_ARG0 membership).
460
+ // - null: a local declaration was found that shadows the name with
461
+ // a value the resolver can't follow (e.g. `const t = someCall()`,
462
+ // `const t = (x) => …`). The caller should treat as a non-primitive
463
+ // local binding. Distinguishing `null` from "name unchanged" matters
464
+ // when the name happens to match a primitive — e.g.
465
+ // `const text = (x) => x.toUpperCase()` followed by `text((s) => …)`
466
+ // must NOT be classified as reactive.
467
+ function resolveBareIdentFrom(fromNode, name, visited) {
468
+ if (visited.has(name))
469
+ return null;
470
+ visited.add(name);
471
+ let node = fromNode;
472
+ while (node.parent) {
473
+ const parent = node.parent;
474
+ let params = null;
475
+ let statements = null;
476
+ if (ts.isArrowFunction(parent) ||
477
+ ts.isFunctionExpression(parent) ||
478
+ ts.isFunctionDeclaration(parent) ||
479
+ ts.isMethodDeclaration(parent)) {
480
+ params = parent.parameters;
481
+ }
482
+ else if (ts.isBlock(parent) || ts.isSourceFile(parent) || ts.isModuleBlock(parent)) {
483
+ statements = parent.statements;
484
+ }
485
+ if (params) {
486
+ for (const param of params) {
487
+ if (!ts.isObjectBindingPattern(param.name))
488
+ continue;
489
+ const hit = findInBindingPattern(param.name, name);
490
+ if (hit !== null)
491
+ return hit;
492
+ }
493
+ // Identifier-binding parameter shadowing (`(text: …) => text(…)`):
494
+ // the parameter itself binds `name` to whatever the caller passed,
495
+ // which we can't see locally. Treat as shadowed.
496
+ for (const param of params) {
497
+ if (ts.isIdentifier(param.name) && param.name.text === name)
498
+ return null;
499
+ }
500
+ }
501
+ if (statements) {
502
+ for (const stmt of statements) {
503
+ if (ts.isFunctionDeclaration(stmt) && stmt.name?.text === name) {
504
+ // `function text(...) { … }` — local function shadows the name.
505
+ return null;
506
+ }
507
+ if (ts.isImportDeclaration(stmt)) {
508
+ if (importShadowsName(stmt, name)) {
509
+ // Imported binding with this name. We can't statically
510
+ // confirm whether it actually resolves to the @llui/dom
511
+ // primitive (could be `import { text } from './my-utils'`),
512
+ // so the caller mustn't assume primitive-hood blindly.
513
+ // Defer to the existing transform.ts viewHelperNames pass,
514
+ // which IS import-aware, and treat as a primitive-named
515
+ // binding here. Returning `name` preserves the existing
516
+ // predicate behavior for direct `import { text } from
517
+ // '@llui/dom'`; the residual user-shadows-via-rename-import
518
+ // case is a pre-existing predicate gap (transform.ts has
519
+ // the same gap), not regressed by this change.
520
+ return name;
521
+ }
522
+ }
523
+ if (!ts.isVariableStatement(stmt))
524
+ continue;
525
+ const isConst = !!(stmt.declarationList.flags & ts.NodeFlags.Const);
526
+ for (const decl of stmt.declarationList.declarations) {
527
+ // `const { text: t } = h` — destructure rename.
528
+ if (ts.isObjectBindingPattern(decl.name)) {
529
+ const hit = findInBindingPattern(decl.name, name);
530
+ if (hit !== null)
531
+ return hit;
532
+ continue;
533
+ }
534
+ if (!ts.isIdentifier(decl.name) || decl.name.text !== name)
535
+ continue;
536
+ // Bound to `name` at this scope — stop here regardless of how
537
+ // it's bound. Either we can follow (const rebinding to another
538
+ // identifier) or this is a shadowing definition we can't see
539
+ // through.
540
+ if (isConst && decl.initializer && ts.isIdentifier(decl.initializer)) {
541
+ return resolveBareIdentFrom(decl.initializer, decl.initializer.text, visited);
542
+ }
543
+ return null;
544
+ }
545
+ }
546
+ }
547
+ node = parent;
548
+ }
549
+ return name;
550
+ }
551
+ function importShadowsName(imp, name) {
552
+ const clause = imp.importClause;
553
+ if (!clause)
554
+ return false;
555
+ if (clause.name && clause.name.text === name)
556
+ return true; // default import
557
+ const bindings = clause.namedBindings;
558
+ if (!bindings)
559
+ return false;
560
+ if (ts.isNamespaceImport(bindings))
561
+ return bindings.name.text === name;
562
+ if (ts.isNamedImports(bindings)) {
563
+ for (const spec of bindings.elements) {
564
+ if (spec.name.text === name)
565
+ return true;
566
+ }
567
+ }
568
+ return false;
569
+ }
570
+ function findInBindingPattern(pat, localName) {
571
+ for (const element of pat.elements) {
572
+ if (!ts.isIdentifier(element.name))
573
+ continue;
574
+ if (element.name.text !== localName)
575
+ continue;
576
+ // `{ text: t }` — propertyName='text', name='t' → original is 'text'.
577
+ // `{ text }` — no propertyName, name='text' → original is 'text'.
578
+ if (element.propertyName && ts.isIdentifier(element.propertyName)) {
579
+ return element.propertyName.text;
580
+ }
581
+ return localName;
582
+ }
583
+ return null;
584
+ }
379
585
  // Framework APIs whose object-literal arguments contain reactive accessors.
380
586
  // Arrow functions in property values of these calls are state-tracked.
381
587
  const REACTIVE_API_NAMES = new Set([
@@ -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 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"]}
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 // Bare-identifier callee — only the small set of @llui/dom primitives\n // that take a reactive accessor as arg[0] qualifies. Defaulting to\n // `true` here used to misclassify every user mutator (change(updater),\n // dispatch(s), setTimeout(fn, ms), array helpers like\n // `tryGet(s).then(arrow)`, etc.) as a reactive accessor, with two\n // downstream symptoms: (a) the path collector treated the arrow's\n // param as state and polluted `__prefixes` with phantom paths;\n // (b) the opaque-state-flow lint walked the body and flagged\n // perfectly legitimate updater patterns like\n // `change((c) => cond ? newC : c)` as \"state in conditional branch\".\n // The PropertyAssignment branch below already uses the equivalent\n // allow-list pattern (`REACTIVE_API_NAMES.has(...)`); this branch\n // is now symmetric.\n //\n // Destructured-renamed View-bag aliases (`view: ({text: t}) =>\n // [t(s => ...)]`) resolve via the primitive's property name, so the\n // membership check uses the original primitive name rather than the\n // local alias. Without this, `t(s => s.count)` would silently skip\n // path collection and mask injection.\n if (ts.isIdentifier(parent.expression)) {\n const originalName = resolveBareIdentToPrimitive(parent.expression)\n return REACTIVE_BARE_IDENT_ARG0.has(originalName)\n }\n // Skip array method callbacks: .filter(t => ...), .map(t => ...), .some(t => ...), etc.\n // Allow view-helper primitive calls — same set as the bare-identifier\n // allow-list above, kept in sync so `h.text(s => …)` and `text(s => …)`\n // are treated symmetrically.\n if (ts.isPropertyAccessExpression(parent.expression)) {\n if (REACTIVE_BARE_IDENT_ARG0.has(parent.expression.name.text)) return true\n return false\n }\n return false\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 primitives whose first positional argument IS a reactive\n// accessor (an arrow taking state). The set is intentionally tiny —\n// every other bare-identifier callee with an arrow arg0 (user mutators,\n// async helpers, timers, array constructors, …) must NOT be visited as\n// a reactive accessor, or its closure parameter gets misclassified as\n// state and its body gets walked by the opaque-flow lint.\n//\n// Excluded by design:\n// - `sample(s => s.x)` — imperative one-shot read, no binding.\n// - `item(t => t.x)` — per-item selector inside an `each.render`\n// callback; the param is per-row, not the component's state.\n// - `track({ deps })` — takes an object literal, not an arrow.\n// - `provide(key, accessor)` — accessor is arg1, not arg0.\nconst REACTIVE_BARE_IDENT_ARG0 = new Set(['text', 'memo', 'unsafeHtml', 'selector'])\n\n/**\n * Resolve a bare-identifier callee back to the original primitive name,\n * unwrapping the alias forms an author can produce locally:\n *\n * - Destructure rename in a function parameter — the canonical View-bag\n * pattern: `view: ({text: t}) => [t(s => …)]` aliases `t` to `text`.\n * - Destructure rename in a `const { ... } = …` declaration.\n * - Const rebinding: `const t = text; t(s => …)` aliases `t` to `text`.\n *\n * The walker climbs the lexical scope finding the innermost binding for\n * `ident.text`; for const rebinding it recursively follows the\n * initializer when it's another bare Identifier (with a visited-set\n * guard against cycles like `const a = b; const b = a`). Stops at the\n * first non-Identifier initializer — `const t = someCall()` returns\n * `'t'` unchanged, because the value isn't a primitive name we can\n * statically pin.\n *\n * Restricted to local lexical resolution. Cross-file alias chains\n * (`import { t } from './aliases'`) are the cross-file resolver's job;\n * this AST-only predicate stops at the module boundary.\n */\nfunction resolveBareIdentToPrimitive(ident: ts.Identifier): string {\n const result = resolveBareIdentFrom(ident, ident.text, new Set())\n return result ?? ident.text\n}\n\n// Returns:\n// - string: the resolved primitive-candidate name (caller checks\n// REACTIVE_BARE_IDENT_ARG0 membership).\n// - null: a local declaration was found that shadows the name with\n// a value the resolver can't follow (e.g. `const t = someCall()`,\n// `const t = (x) => …`). The caller should treat as a non-primitive\n// local binding. Distinguishing `null` from \"name unchanged\" matters\n// when the name happens to match a primitive — e.g.\n// `const text = (x) => x.toUpperCase()` followed by `text((s) => …)`\n// must NOT be classified as reactive.\nfunction resolveBareIdentFrom(\n fromNode: ts.Node,\n name: string,\n visited: Set<string>,\n): string | null {\n if (visited.has(name)) return null\n visited.add(name)\n let node: ts.Node = fromNode\n while (node.parent) {\n const parent = node.parent\n let params: readonly ts.ParameterDeclaration[] | null = null\n let statements: readonly ts.Statement[] | null = null\n if (\n ts.isArrowFunction(parent) ||\n ts.isFunctionExpression(parent) ||\n ts.isFunctionDeclaration(parent) ||\n ts.isMethodDeclaration(parent)\n ) {\n params = parent.parameters\n } else if (ts.isBlock(parent) || ts.isSourceFile(parent) || ts.isModuleBlock(parent)) {\n statements = parent.statements\n }\n if (params) {\n for (const param of params) {\n if (!ts.isObjectBindingPattern(param.name)) continue\n const hit = findInBindingPattern(param.name, name)\n if (hit !== null) return hit\n }\n // Identifier-binding parameter shadowing (`(text: …) => text(…)`):\n // the parameter itself binds `name` to whatever the caller passed,\n // which we can't see locally. Treat as shadowed.\n for (const param of params) {\n if (ts.isIdentifier(param.name) && param.name.text === name) return null\n }\n }\n if (statements) {\n for (const stmt of statements) {\n if (ts.isFunctionDeclaration(stmt) && stmt.name?.text === name) {\n // `function text(...) { … }` — local function shadows the name.\n return null\n }\n if (ts.isImportDeclaration(stmt)) {\n if (importShadowsName(stmt, name)) {\n // Imported binding with this name. We can't statically\n // confirm whether it actually resolves to the @llui/dom\n // primitive (could be `import { text } from './my-utils'`),\n // so the caller mustn't assume primitive-hood blindly.\n // Defer to the existing transform.ts viewHelperNames pass,\n // which IS import-aware, and treat as a primitive-named\n // binding here. Returning `name` preserves the existing\n // predicate behavior for direct `import { text } from\n // '@llui/dom'`; the residual user-shadows-via-rename-import\n // case is a pre-existing predicate gap (transform.ts has\n // the same gap), not regressed by this change.\n return name\n }\n }\n if (!ts.isVariableStatement(stmt)) continue\n const isConst = !!(stmt.declarationList.flags & ts.NodeFlags.Const)\n for (const decl of stmt.declarationList.declarations) {\n // `const { text: t } = h` — destructure rename.\n if (ts.isObjectBindingPattern(decl.name)) {\n const hit = findInBindingPattern(decl.name, name)\n if (hit !== null) return hit\n continue\n }\n if (!ts.isIdentifier(decl.name) || decl.name.text !== name) continue\n // Bound to `name` at this scope — stop here regardless of how\n // it's bound. Either we can follow (const rebinding to another\n // identifier) or this is a shadowing definition we can't see\n // through.\n if (isConst && decl.initializer && ts.isIdentifier(decl.initializer)) {\n return resolveBareIdentFrom(decl.initializer, decl.initializer.text, visited)\n }\n return null\n }\n }\n }\n node = parent\n }\n return name\n}\n\nfunction importShadowsName(imp: ts.ImportDeclaration, name: string): boolean {\n const clause = imp.importClause\n if (!clause) return false\n if (clause.name && clause.name.text === name) return true // default import\n const bindings = clause.namedBindings\n if (!bindings) return false\n if (ts.isNamespaceImport(bindings)) return bindings.name.text === name\n if (ts.isNamedImports(bindings)) {\n for (const spec of bindings.elements) {\n if (spec.name.text === name) return true\n }\n }\n return false\n}\n\nfunction findInBindingPattern(pat: ts.ObjectBindingPattern, localName: string): string | null {\n for (const element of pat.elements) {\n if (!ts.isIdentifier(element.name)) continue\n if (element.name.text !== localName) continue\n // `{ text: t }` — propertyName='text', name='t' → original is 'text'.\n // `{ text }` — no propertyName, name='text' → original is 'text'.\n if (element.propertyName && ts.isIdentifier(element.propertyName)) {\n return element.propertyName.text\n }\n return localName\n }\n return null\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"]}