@llui/compiler 0.5.3 → 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.
- package/dist/collect-deps.d.ts.map +1 -1
- package/dist/collect-deps.js +193 -11
- package/dist/collect-deps.js.map +1 -1
- package/dist/modules/map-on-state-array.d.ts.map +1 -1
- package/dist/modules/map-on-state-array.js +54 -1
- package/dist/modules/map-on-state-array.js.map +1 -1
- package/dist/modules/opaque-state-flow.d.ts.map +1 -1
- package/dist/modules/opaque-state-flow.js +41 -0
- package/dist/modules/opaque-state-flow.js.map +1 -1
- package/package.json +1 -1
|
@@ -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,
|
|
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"}
|
package/dist/collect-deps.js
CHANGED
|
@@ -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
|
-
//
|
|
322
|
-
//
|
|
323
|
-
//
|
|
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
|
-
|
|
326
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
@@ -400,6 +415,173 @@ export function isReactiveAccessor(node) {
|
|
|
400
415
|
}
|
|
401
416
|
return false;
|
|
402
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
|
+
}
|
|
403
585
|
// Framework APIs whose object-literal arguments contain reactive accessors.
|
|
404
586
|
// Arrow functions in property values of these calls are state-tracked.
|
|
405
587
|
const REACTIVE_API_NAMES = new Set([
|
package/dist/collect-deps.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"collect-deps.js","sourceRoot":"","sources":["../src/collect-deps.ts"],"names":[],"mappings":"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","sourcesContent":["import ts from 'typescript'\nimport { resolveAccessorBody } from './accessor-resolver.js'\n\n/**\n * Names whose first arg is itself a reactive accessor (the existing\n * arrow walker handles them) or which are explicitly excluded\n * (sample/item read state imperatively / per-row, not as state\n * accessors). When a delegating accessor's body contains a call to one\n * of these, we don't follow it — recursion is reserved for \"this is\n * just a thin wrapper that hands the state to another local helper.\"\n */\nconst NON_DELEGATION_HELPERS = new Set(['sample', 'item', 'memo', 'text', 'unsafeHtml'])\n\n/**\n * Walk a delegating accessor's body looking for calls to OTHER local\n * functions that take the state param verbatim — `helper(s)` where\n * `s` matches the outer accessor's param name. For each, hand the\n * resolved declaration back so the caller can recurse into its body.\n *\n * Skips:\n * - Framework helpers (`memo`, `text`, etc.) — their arrow args are\n * visited by the top-level arrow walker; we'd double-count.\n * - Method calls (`s.items.filter(...)`) — the callee is a builtin,\n * not a local function we can resolve.\n * - Nested function bodies — params inside a `(item) => …` shadow\n * ours, so a `helper(s)` deep in there isn't (necessarily)\n * handing OUR state in. Conservative: don't recurse through\n * lambda boundaries.\n */\nfunction visitTopLevelDelegations(\n body: ts.Node,\n stateParamName: string,\n follow: (resolved: ts.ArrowFunction | ts.FunctionExpression | ts.FunctionDeclaration) => void,\n 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"]}
|
|
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"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"map-on-state-array.d.ts","sourceRoot":"","sources":["../../src/modules/map-on-state-array.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;
|
|
1
|
+
{"version":3,"file":"map-on-state-array.d.ts","sourceRoot":"","sources":["../../src/modules/map-on-state-array.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAmFlD,wBAAgB,qBAAqB,IAAI,cAAc,CAiDtD"}
|
|
@@ -17,6 +17,58 @@ function findViewProperty(call) {
|
|
|
17
17
|
}
|
|
18
18
|
return undefined;
|
|
19
19
|
}
|
|
20
|
+
/**
|
|
21
|
+
* True when the `.map()` call sits at any depth inside the `items:`
|
|
22
|
+
* accessor of an enclosing `each({ items, ... })` call. In that
|
|
23
|
+
* position the `.map()`'s array is what `items` is supposed to return —
|
|
24
|
+
* the caller already adopted the `each` pattern. Firing the rule here
|
|
25
|
+
* was a self-referential false positive: the diagnostic told the
|
|
26
|
+
* author to use the very `each` they were inside.
|
|
27
|
+
*
|
|
28
|
+
* The walker stops at function boundaries to avoid attributing nested
|
|
29
|
+
* helper definitions to their enclosing scope, but follows property
|
|
30
|
+
* assignments transparently — the canonical shape is `each({ items:
|
|
31
|
+
* (s) => s.foo.map(...) })`, and the `.map()` lives several AST levels
|
|
32
|
+
* down from the `items:` PropertyAssignment.
|
|
33
|
+
*/
|
|
34
|
+
function isInsideEachItemsAccessor(n) {
|
|
35
|
+
let cur = n.parent;
|
|
36
|
+
// The enclosing function whose body contains the `.map()`. If it
|
|
37
|
+
// turns out to be the `items:` arrow of an `each(...)`, suppress.
|
|
38
|
+
let enclosingFn;
|
|
39
|
+
while (cur) {
|
|
40
|
+
if (ts.isArrowFunction(cur) || ts.isFunctionExpression(cur)) {
|
|
41
|
+
enclosingFn = cur;
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
cur = cur.parent;
|
|
45
|
+
}
|
|
46
|
+
if (!enclosingFn)
|
|
47
|
+
return false;
|
|
48
|
+
// The function must be the value of a `items:` PropertyAssignment...
|
|
49
|
+
const pa = enclosingFn.parent;
|
|
50
|
+
if (!pa ||
|
|
51
|
+
!ts.isPropertyAssignment(pa) ||
|
|
52
|
+
!ts.isIdentifier(pa.name) ||
|
|
53
|
+
pa.name.text !== 'items') {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
// ...inside an object literal passed as the first arg to a call
|
|
57
|
+
// whose callee resolves (by name) to `each`. Handles both the
|
|
58
|
+
// bare import (`each({...})`) and the View-bag form (`h.each({...})`).
|
|
59
|
+
const obj = pa.parent;
|
|
60
|
+
if (!obj || !ts.isObjectLiteralExpression(obj))
|
|
61
|
+
return false;
|
|
62
|
+
const call = obj.parent;
|
|
63
|
+
if (!call || !ts.isCallExpression(call) || call.arguments[0] !== obj)
|
|
64
|
+
return false;
|
|
65
|
+
if (ts.isIdentifier(call.expression))
|
|
66
|
+
return call.expression.text === 'each';
|
|
67
|
+
if (ts.isPropertyAccessExpression(call.expression)) {
|
|
68
|
+
return call.expression.name.text === 'each';
|
|
69
|
+
}
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
20
72
|
/**
|
|
21
73
|
* True when `expr` resolves to a state-like reference. We recognize
|
|
22
74
|
* the conventional names — `state`, `s`, `_state` — and chained
|
|
@@ -60,7 +112,8 @@ export function mapOnStateArrayModule() {
|
|
|
60
112
|
ts.isPropertyAccessExpression(n.expression) &&
|
|
61
113
|
ts.isIdentifier(n.expression.name) &&
|
|
62
114
|
n.expression.name.text === 'map' &&
|
|
63
|
-
isStateReference(n.expression.expression)
|
|
115
|
+
isStateReference(n.expression.expression) &&
|
|
116
|
+
!isInsideEachItemsAccessor(n)) {
|
|
64
117
|
ctx.reportDiagnostic({
|
|
65
118
|
id: 'llui/map-on-state-array',
|
|
66
119
|
severity: 'error',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"map-on-state-array.js","sourceRoot":"","sources":["../../src/modules/map-on-state-array.ts"],"names":[],"mappings":"AAAA,uEAAuE;AACvE,kEAAkE;AAClE,sEAAsE;AACtE,oEAAoE;AACpE,8DAA8D;AAE9D,OAAO,EAAE,MAAM,YAAY,CAAA;AAC3B,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAA;AAEnD,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAA;AAEjD,SAAS,gBAAgB,CAAC,IAAuB;IAC/C,MAAM,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAA;IAC7B,IAAI,CAAC,GAAG,IAAI,CAAC,EAAE,CAAC,yBAAyB,CAAC,GAAG,CAAC;QAAE,OAAO,SAAS,CAAA;IAChE,KAAK,MAAM,IAAI,IAAI,GAAG,CAAC,UAAU,EAAE,CAAC;QAClC,IAAI,EAAE,CAAC,oBAAoB,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;YAC7F,OAAO,IAAI,CAAA;QACb,CAAC;IACH,CAAC;IACD,OAAO,SAAS,CAAA;AAClB,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,gBAAgB,CAAC,IAAmB;IAC3C,IAAI,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE,CAAC;QAC1B,OAAO,IAAI,CAAC,IAAI,KAAK,OAAO,IAAI,IAAI,CAAC,IAAI,KAAK,GAAG,IAAI,IAAI,CAAC,IAAI,KAAK,QAAQ,CAAA;IAC7E,CAAC;IACD,IAAI,EAAE,CAAC,0BAA0B,CAAC,IAAI,CAAC,EAAE,CAAC;QACxC,OAAO,gBAAgB,CAAC,IAAI,CAAC,UAAU,CAAC,CAAA;IAC1C,CAAC;IACD,OAAO,KAAK,CAAA;AACd,CAAC;AAED,MAAM,UAAU,qBAAqB;IACnC,OAAO;QACL,IAAI,EAAE,oBAAoB;QAC1B,eAAe,EAAE,QAAQ;QACzB,WAAW,EAAE;YACX;gBACE,EAAE,EAAE,yBAAyB;gBAC7B,WAAW,EACT,iFAAiF;aACpF;SACF;QACD,QAAQ,EAAE;YACR,CAAC,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE;gBACxC,MAAM,OAAO,GAAG,IAAqB,CAAA;gBACrC,MAAM,EAAE,GAAG,EAAE,CAAC,gBAAgB,CAAC,OAAO,CAAC,QAAQ,EAAE,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,YAAY,CAAC,MAAM,EAAE,IAAI,CAAC,CAAA;gBAC5F,KAAK,MAAM,IAAI,IAAI,kBAAkB,CAAC,EAAE,CAAC,EAAE,CAAC;oBAC1C,MAAM,QAAQ,GAAG,gBAAgB,CAAC,IAAI,CAAC,CAAA;oBACvC,IAAI,CAAC,QAAQ;wBAAE,SAAQ;oBACvB,MAAM,EAAE,GAAG,QAAQ,CAAC,WAAW,CAAA;oBAC/B,IAAI,CAAC,EAAE,CAAC,eAAe,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,oBAAoB,CAAC,EAAE,CAAC;wBAAE,SAAQ;oBAErE,MAAM,IAAI,GAAG,CAAC,CAAU,EAAQ,EAAE;wBAChC,IACE,EAAE,CAAC,gBAAgB,CAAC,CAAC,CAAC;4BACtB,EAAE,CAAC,0BAA0B,CAAC,CAAC,CAAC,UAAU,CAAC;4BAC3C,EAAE,CAAC,YAAY,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC;4BAClC,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,KAAK,KAAK;4BAChC,gBAAgB,CAAC,CAAC,CAAC,UAAU,CAAC,UAAU,CAAC,
|
|
1
|
+
{"version":3,"file":"map-on-state-array.js","sourceRoot":"","sources":["../../src/modules/map-on-state-array.ts"],"names":[],"mappings":"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","sourcesContent":["// `map-on-state-array` — errors when a state-derived array is iterated\n// with `.map()` inside a view function. The reactive primitive is\n// `each(...)`; `.map()` produces a static list with no per-row scope,\n// no key-based reconciliation, and no precise mask gating. Migrated\n// from `@llui/eslint-plugin/src/rules/map-on-state-array.ts`.\n\nimport ts from 'typescript'\nimport { rangeFromOffsets } from '../diagnostic.js'\nimport type { CompilerModule } from '../module.js'\nimport { findComponentCalls } from './_shared.js'\n\nfunction findViewProperty(call: ts.CallExpression): ts.PropertyAssignment | undefined {\n const arg = call.arguments[0]\n if (!arg || !ts.isObjectLiteralExpression(arg)) return undefined\n for (const prop of arg.properties) {\n if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name) && prop.name.text === 'view') {\n return prop\n }\n }\n return undefined\n}\n\n/**\n * True when the `.map()` call sits at any depth inside the `items:`\n * accessor of an enclosing `each({ items, ... })` call. In that\n * position the `.map()`'s array is what `items` is supposed to return —\n * the caller already adopted the `each` pattern. Firing the rule here\n * was a self-referential false positive: the diagnostic told the\n * author to use the very `each` they were inside.\n *\n * The walker stops at function boundaries to avoid attributing nested\n * helper definitions to their enclosing scope, but follows property\n * assignments transparently — the canonical shape is `each({ items:\n * (s) => s.foo.map(...) })`, and the `.map()` lives several AST levels\n * down from the `items:` PropertyAssignment.\n */\nfunction isInsideEachItemsAccessor(n: ts.Node): boolean {\n let cur: ts.Node | undefined = n.parent\n // The enclosing function whose body contains the `.map()`. If it\n // turns out to be the `items:` arrow of an `each(...)`, suppress.\n let enclosingFn: ts.ArrowFunction | ts.FunctionExpression | undefined\n while (cur) {\n if (ts.isArrowFunction(cur) || ts.isFunctionExpression(cur)) {\n enclosingFn = cur\n break\n }\n cur = cur.parent\n }\n if (!enclosingFn) return false\n // The function must be the value of a `items:` PropertyAssignment...\n const pa = enclosingFn.parent\n if (\n !pa ||\n !ts.isPropertyAssignment(pa) ||\n !ts.isIdentifier(pa.name) ||\n pa.name.text !== 'items'\n ) {\n return false\n }\n // ...inside an object literal passed as the first arg to a call\n // whose callee resolves (by name) to `each`. Handles both the\n // bare import (`each({...})`) and the View-bag form (`h.each({...})`).\n const obj = pa.parent\n if (!obj || !ts.isObjectLiteralExpression(obj)) return false\n const call = obj.parent\n if (!call || !ts.isCallExpression(call) || call.arguments[0] !== obj) return false\n if (ts.isIdentifier(call.expression)) return call.expression.text === 'each'\n if (ts.isPropertyAccessExpression(call.expression)) {\n return call.expression.name.text === 'each'\n }\n return false\n}\n\n/**\n * True when `expr` resolves to a state-like reference. We recognize\n * the conventional names — `state`, `s`, `_state` — and chained\n * property accesses rooted at one of them (e.g. `s.items.filtered`).\n * Type-aware resolution would be more precise; the conventions are\n * stable enough across the codebase that name matching catches the\n * intended cases without a checker.\n */\nfunction isStateReference(expr: ts.Expression): boolean {\n if (ts.isIdentifier(expr)) {\n return expr.text === 'state' || expr.text === 's' || expr.text === '_state'\n }\n if (ts.isPropertyAccessExpression(expr)) {\n return isStateReference(expr.expression)\n }\n return false\n}\n\nexport function mapOnStateArrayModule(): CompilerModule {\n return {\n name: 'map-on-state-array',\n compilerVersion: '^0.3.0',\n diagnostics: [\n {\n id: 'llui/map-on-state-array',\n description:\n 'Array .map() on a state-derived value in view(). Use each() for reactive lists.',\n },\n ],\n visitors: {\n [ts.SyntaxKind.SourceFile]: (ctx, node) => {\n const visited = node as ts.SourceFile\n const sf = ts.createSourceFile(visited.fileName, visited.text, ts.ScriptTarget.Latest, true)\n for (const call of findComponentCalls(sf)) {\n const viewProp = findViewProperty(call)\n if (!viewProp) continue\n const fn = viewProp.initializer\n if (!ts.isArrowFunction(fn) && !ts.isFunctionExpression(fn)) continue\n\n const walk = (n: ts.Node): void => {\n if (\n ts.isCallExpression(n) &&\n ts.isPropertyAccessExpression(n.expression) &&\n ts.isIdentifier(n.expression.name) &&\n n.expression.name.text === 'map' &&\n isStateReference(n.expression.expression) &&\n !isInsideEachItemsAccessor(n)\n ) {\n ctx.reportDiagnostic({\n id: 'llui/map-on-state-array',\n severity: 'error',\n category: 'reactivity',\n message:\n 'Array `.map()` on a state-derived value inside view(). `.map()` produces a static list — use `each({ items, key, render })` for reactive lists with per-row scope and key-based reconciliation.',\n location: {\n file: sf.fileName,\n range: rangeFromOffsets(sf.text, n.getStart(sf), n.getEnd()),\n },\n })\n }\n ts.forEachChild(n, walk)\n }\n if (fn.body) walk(fn.body)\n }\n },\n },\n }\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"opaque-state-flow.d.ts","sourceRoot":"","sources":["../../src/modules/opaque-state-flow.ts"],"names":[],"mappings":"AAwCA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;
|
|
1
|
+
{"version":3,"file":"opaque-state-flow.d.ts","sourceRoot":"","sources":["../../src/modules/opaque-state-flow.ts"],"names":[],"mappings":"AAwCA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAkLlD,wBAAgB,qBAAqB,IAAI,cAAc,CAiEtD"}
|
|
@@ -181,6 +181,34 @@ function findFirstLeakInAccessor(accessor, checker) {
|
|
|
181
181
|
visit(accessor.body);
|
|
182
182
|
return leak;
|
|
183
183
|
}
|
|
184
|
+
/**
|
|
185
|
+
* True when `arrow` is the value of a `deps:` PropertyAssignment in
|
|
186
|
+
* a `track({ ... })` call. The diagnostic is suppressed in that
|
|
187
|
+
* position because `track` is the documented escape hatch for cases
|
|
188
|
+
* the walker can't statically infer; firing the lint inside it moves
|
|
189
|
+
* the diagnostic without giving the author a path forward.
|
|
190
|
+
*
|
|
191
|
+
* Handles both forms: bare `track({...})` (import from `@llui/dom`)
|
|
192
|
+
* and the View-bag form `h.track({...})` if it ever exists.
|
|
193
|
+
*/
|
|
194
|
+
function isInsideTrackDeps(arrow) {
|
|
195
|
+
const pa = arrow.parent;
|
|
196
|
+
if (!pa || !ts.isPropertyAssignment(pa) || !ts.isIdentifier(pa.name) || pa.name.text !== 'deps') {
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
199
|
+
const obj = pa.parent;
|
|
200
|
+
if (!obj || !ts.isObjectLiteralExpression(obj))
|
|
201
|
+
return false;
|
|
202
|
+
const call = obj.parent;
|
|
203
|
+
if (!call || !ts.isCallExpression(call) || call.arguments[0] !== obj)
|
|
204
|
+
return false;
|
|
205
|
+
if (ts.isIdentifier(call.expression))
|
|
206
|
+
return call.expression.text === 'track';
|
|
207
|
+
if (ts.isPropertyAccessExpression(call.expression)) {
|
|
208
|
+
return call.expression.name.text === 'track';
|
|
209
|
+
}
|
|
210
|
+
return false;
|
|
211
|
+
}
|
|
184
212
|
function describe(node) {
|
|
185
213
|
if (ts.isIdentifier(node))
|
|
186
214
|
return node.text;
|
|
@@ -214,6 +242,19 @@ export function opaqueStateFlowModule() {
|
|
|
214
242
|
ts.createSourceFile(visited.fileName, visited.text, ts.ScriptTarget.Latest, true);
|
|
215
243
|
const walk = (n) => {
|
|
216
244
|
if ((ts.isArrowFunction(n) || ts.isFunctionExpression(n)) && isReactiveAccessor(n)) {
|
|
245
|
+
// `track({ deps: (s) => [...] })` is the user's explicit
|
|
246
|
+
// opt-in for "this binding's reads can't be inferred — trust
|
|
247
|
+
// my declaration." Firing a perf lint inside the user's
|
|
248
|
+
// declaration defeats the primitive's purpose; the
|
|
249
|
+
// diagnostic moves from the original call site to inside
|
|
250
|
+
// track.deps without going away, leaving authors with no
|
|
251
|
+
// recovery path. Suppress here. The mask/path classifier
|
|
252
|
+
// still walks the body for what it can extract; this only
|
|
253
|
+
// silences the lint.
|
|
254
|
+
if (isInsideTrackDeps(n)) {
|
|
255
|
+
ts.forEachChild(n, walk);
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
217
258
|
const leak = findFirstLeakInAccessor(n, checker);
|
|
218
259
|
if (leak) {
|
|
219
260
|
ctx.reportDiagnostic({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"opaque-state-flow.js","sourceRoot":"","sources":["../../src/modules/opaque-state-flow.ts"],"names":[],"mappings":"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","sourcesContent":["// `opaque-state-flow` — errors when a reactive accessor's body flows\n// the state identifier into an expression the walker can't statically\n// trace. The compiler still produces a *correct* binding by forcing\n// FULL_MASK and emitting a whole-state `(s) => s` sentinel into\n// `__prefixes` (see `02 Compiler.md` § \"Opaque-flow classifier\"), but\n// the binding then re-evaluates on every state change rather than\n// only when its actual reads change. This rule surfaces the leak so\n// authors can either:\n//\n// - Rewrite the accessor as direct property access (`s.foo`,\n// `s.foo['literal']`), the form the walker can resolve.\n// - Declare the reads explicitly via `track({ deps: (s) => [...] })`\n// — the compile-time escape hatch the framework provides for cases\n// where the read genuinely can't be expressed inline.\n//\n// Detected leak shapes (mirrors the classifier in\n// `transform.ts:computeAccessorMask`):\n// - `helper(s)` with an Identifier callee that can't be resolved to\n// a local declaration (function parameter, import, destructured\n// binding) — the callee may read any field of `s`.\n//\n// NOT flagged (intentional): `obj.helper(s)` / `lib.fn(s)` —\n// PropertyAccessExpression callees. This is the documented headless-\n// components idiom (`pr.valueText(s)` where `pr` comes from\n// `progress.connect()`), and refactoring it defeats the API surface.\n// The runtime sentinel keeps such bindings correct — the cost is a\n// per-update re-evaluation, which is a property of the composition\n// pattern rather than an author mistake worth blocking.\n// - `new Wrapper(s)` — NewExpression with state as an argument.\n// - `` tag`${s}` `` — TaggedTemplate with state in a span.\n// - `{ ...s }` / `[...s]` — spread of state.\n// - `const x = s` — const aliasing.\n// - `cond ? s : other` — state in a conditional branch (state\n// reaches the binding via a path the walker can't trace).\n// - `s[expr]` — dynamic element access (literal keys are tracked).\n// - State passed as `arg1+` to any call (the existing delegation\n// branch only inspects `arg0`).\n\nimport ts from 'typescript'\nimport { rangeFromOffsets } from '../diagnostic.js'\nimport type { CompilerModule } from '../module.js'\nimport { isReactiveAccessor } from '../collect-deps.js'\nimport { resolveAccessorBody } from '../accessor-resolver.js'\n\n// Mirrors the file-local list in collect-deps.ts. Calls to these\n// framework primitives are visited as accessor positions in their\n// own right, so we don't double-classify.\nconst NON_DELEGATION_HELPERS = new Set(['sample', 'item', 'memo', 'text', 'unsafeHtml'])\n\ninterface LeakSite {\n node: ts.Node\n shape: string\n hint: string\n}\n\nfunction findFirstLeakInAccessor(\n accessor: ts.ArrowFunction | ts.FunctionExpression | ts.FunctionDeclaration,\n checker: ts.TypeChecker | undefined,\n): LeakSite | null {\n if (accessor.parameters.length !== 1) return null\n const param = accessor.parameters[0]!\n if (!ts.isIdentifier(param.name)) return null\n if (!accessor.body) return null\n const stateParam = param.name.text\n\n let leak: LeakSite | null = null\n const visit = (node: ts.Node): void => {\n if (leak) return\n if (ts.isIdentifier(node) && node.text === stateParam) {\n const parent = node.parent\n if (!parent || ts.isParameter(parent)) {\n ts.forEachChild(node, visit)\n return\n }\n // Tracked containers — the same set the mask classifier honors.\n let tracked = false\n let shape = ''\n let hint = ''\n if (ts.isPropertyAccessExpression(parent) && parent.expression === node) {\n tracked = true\n } else if (ts.isElementAccessExpression(parent) && parent.expression === node) {\n if (\n ts.isStringLiteralLike(parent.argumentExpression) ||\n ts.isNumericLiteral(parent.argumentExpression)\n ) {\n tracked = true\n } else {\n shape = `dynamic element access \\`s[<expr>]\\``\n hint =\n 'replace the dynamic key with a literal property (e.g. `s.foo`), or declare the read via `track({ deps: (s) => [s[key]] })`.'\n }\n } else if (ts.isCallExpression(parent)) {\n const argIndex = parent.arguments.indexOf(node as ts.Expression)\n if (argIndex > 0) {\n // State passed as arg1+ to a call. The header documents this\n // as NOT flagged (intentional): the existing delegation\n // branch only attempts to trace arg0, and the mask classifier\n // emits a whole-state sentinel into `__prefixes` so the\n // binding stays correct. The cost is per-update re-evaluation\n // — a property of the composition pattern, not an author\n // mistake worth blocking. Without this branch we'd fall\n // through to the default \"outside a tracked container\" leak.\n tracked = true\n } else if (argIndex === 0) {\n if (\n ts.isIdentifier(parent.expression) &&\n !NON_DELEGATION_HELPERS.has(parent.expression.text)\n ) {\n // Identifier-callee delegation. Recurse into the callee's\n // body via the same resolver the mask walker uses. If it\n // resolves to a local accessor, the helper's reads are\n // walked transitively and the call is tracked. If the\n // callee is a function parameter, import, destructured\n // binding, or otherwise unresolvable, this IS the leak\n // shape — flag it here so the diagnostic points at the\n // call site rather than at some deeper unresolvable read.\n const resolved = resolveAccessorBody(parent.expression, checker)\n if (resolved) {\n tracked = true\n } else {\n const calleeSymbol = checker?.getSymbolAtLocation(parent.expression)\n const isFunctionParam = !!calleeSymbol?.declarations?.some((d: ts.Declaration) =>\n ts.isParameter(d),\n )\n shape = `call to an unresolvable callee \\`${parent.expression.text}(s)\\` (function parameter, import, or destructured binding)`\n if (isFunctionParam) {\n hint =\n 'this callee is a function parameter — the closure passed at the call site is opaque to per-binding analysis. The framework expects per-row dynamic state to flow through `each` items (slot data on `item.*`) rather than through `(s) => ...` callback parameters; restructure the helper so its bindings read `item.*` and the call site builds the slot data once in `items: (s) => …`.'\n } else {\n hint =\n 'inline the read against `s` directly, refactor the callee into a same-module `const`/`function` declaration, or declare the dependencies via `track({ deps: (s) => [...] })`.'\n }\n }\n } else if (!ts.isIdentifier(parent.expression)) {\n // Method-call / computed callee with state arg —\n // `obj.helper(s)`, `lib.fn(s)`. This is the documented\n // headless-components idiom (`pr.valueText(s)` where `pr`\n // comes from `progress.connect()`); refactoring it would\n // defeat the API surface. The runtime sentinel keeps the\n // binding correct — just at the cost of re-evaluating on\n // every update. Treat as tracked from the lint's POV so\n // legitimate composition doesn't error the build; the\n // perf cost is a property of the composition pattern, not\n // an author mistake worth blocking.\n tracked = true\n }\n }\n } else if (ts.isNewExpression(parent)) {\n shape = 'state passed as a constructor argument (`new X(s)`)'\n hint =\n 'compute the derived value inline against direct state reads, or use `track({ deps: (s) => [...] })` to declare the reads.'\n } else if (ts.isSpreadElement(parent) || ts.isSpreadAssignment(parent)) {\n shape = 'state spread (`{...s}` / `[...s]`)'\n hint =\n 'spread only the fields you actually need (`{...s.user}`), or use `track({ deps: (s) => [...] })`.'\n } else if (ts.isVariableDeclaration(parent)) {\n shape = 'const alias (`const x = s; … x.foo`)'\n hint =\n 'inline the alias to `s.foo`, or split the deeper read into a separate single-assignment alias `const foo = s.foo`.'\n } else if (ts.isConditionalExpression(parent)) {\n shape = 'state in a conditional branch (`cond ? s : other`)'\n hint = 'move the conditional inside the property access: `cond ? s.foo : other.foo`.'\n } else if (ts.isAsExpression(parent) || ts.isTypeAssertionExpression(parent)) {\n shape = 'type assertion wrapping state (`(s as T).foo`)'\n hint = 'drop the assertion — the chain `s.foo` already carries the type.'\n } else if (ts.isParenthesizedExpression(parent)) {\n // Walk up through parens transparently. Don't flag here; the\n // outer parent classifies.\n ts.forEachChild(node, visit)\n return\n } else {\n shape = `state used outside a tracked container (${describe(parent)})`\n hint =\n 'restructure the expression so `s` appears only as the root of a property/element-access chain, or declare the read via `track({ deps: (s) => [...] })`.'\n }\n if (!tracked) {\n leak = { node, shape, hint }\n return\n }\n }\n ts.forEachChild(node, visit)\n }\n visit(accessor.body)\n return leak\n}\n\nfunction describe(node: ts.Node): string {\n if (ts.isIdentifier(node)) return node.text\n if (ts.isPropertyAccessExpression(node)) return `${describe(node.expression)}.${node.name.text}`\n return ts.SyntaxKind[node.kind]\n}\n\nexport function opaqueStateFlowModule(): CompilerModule {\n return {\n name: 'opaque-state-flow',\n compilerVersion: '^0.3.0',\n diagnostics: [\n {\n id: 'llui/opaque-state-flow',\n description:\n \"Reactive accessor flows state into an opaque expression the walker can't trace. The runtime stays correct via a FULL_MASK binding + whole-state sentinel in `__prefixes`, but the binding then re-evaluates on every state change.\",\n },\n ],\n visitors: {\n [ts.SyntaxKind.SourceFile]: (ctx, node) => {\n // When the host adapter has built a Program, walk the checker's\n // own SourceFile so symbol resolution (Alias → Symbol via\n // `getSymbolAtLocation`) actually works. The reparsed file used\n // in the AST-only fallback is not part of any Program, so the\n // checker can't resolve identifiers in it. Fall back to a\n // reparse for paths without a Program (test harness, lint\n // adapters without cross-file resolution).\n const visited = node as ts.SourceFile\n const fromProgram = ctx.program?.getSourceFile(visited.fileName)\n const checker = fromProgram ? ctx.checker : undefined\n const sf =\n fromProgram ??\n ts.createSourceFile(visited.fileName, visited.text, ts.ScriptTarget.Latest, true)\n\n const walk = (n: ts.Node): void => {\n if ((ts.isArrowFunction(n) || ts.isFunctionExpression(n)) && isReactiveAccessor(n)) {\n const leak = findFirstLeakInAccessor(n, checker)\n if (leak) {\n ctx.reportDiagnostic({\n id: 'llui/opaque-state-flow',\n severity: 'error',\n category: 'perf',\n message:\n `Reactive accessor flows state opaquely — ${leak.shape}. ` +\n `The compiler ships a correct binding (FULL_MASK + whole-state sentinel), ` +\n `but it re-evaluates on every state change. ${leak.hint}`,\n location: {\n file: sf.fileName,\n range: rangeFromOffsets(sf.text, leak.node.getStart(sf), leak.node.getEnd()),\n },\n })\n }\n }\n ts.forEachChild(n, walk)\n }\n walk(sf)\n },\n },\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"opaque-state-flow.js","sourceRoot":"","sources":["../../src/modules/opaque-state-flow.ts"],"names":[],"mappings":"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","sourcesContent":["// `opaque-state-flow` — errors when a reactive accessor's body flows\n// the state identifier into an expression the walker can't statically\n// trace. The compiler still produces a *correct* binding by forcing\n// FULL_MASK and emitting a whole-state `(s) => s` sentinel into\n// `__prefixes` (see `02 Compiler.md` § \"Opaque-flow classifier\"), but\n// the binding then re-evaluates on every state change rather than\n// only when its actual reads change. This rule surfaces the leak so\n// authors can either:\n//\n// - Rewrite the accessor as direct property access (`s.foo`,\n// `s.foo['literal']`), the form the walker can resolve.\n// - Declare the reads explicitly via `track({ deps: (s) => [...] })`\n// — the compile-time escape hatch the framework provides for cases\n// where the read genuinely can't be expressed inline.\n//\n// Detected leak shapes (mirrors the classifier in\n// `transform.ts:computeAccessorMask`):\n// - `helper(s)` with an Identifier callee that can't be resolved to\n// a local declaration (function parameter, import, destructured\n// binding) — the callee may read any field of `s`.\n//\n// NOT flagged (intentional): `obj.helper(s)` / `lib.fn(s)` —\n// PropertyAccessExpression callees. This is the documented headless-\n// components idiom (`pr.valueText(s)` where `pr` comes from\n// `progress.connect()`), and refactoring it defeats the API surface.\n// The runtime sentinel keeps such bindings correct — the cost is a\n// per-update re-evaluation, which is a property of the composition\n// pattern rather than an author mistake worth blocking.\n// - `new Wrapper(s)` — NewExpression with state as an argument.\n// - `` tag`${s}` `` — TaggedTemplate with state in a span.\n// - `{ ...s }` / `[...s]` — spread of state.\n// - `const x = s` — const aliasing.\n// - `cond ? s : other` — state in a conditional branch (state\n// reaches the binding via a path the walker can't trace).\n// - `s[expr]` — dynamic element access (literal keys are tracked).\n// - State passed as `arg1+` to any call (the existing delegation\n// branch only inspects `arg0`).\n\nimport ts from 'typescript'\nimport { rangeFromOffsets } from '../diagnostic.js'\nimport type { CompilerModule } from '../module.js'\nimport { isReactiveAccessor } from '../collect-deps.js'\nimport { resolveAccessorBody } from '../accessor-resolver.js'\n\n// Mirrors the file-local list in collect-deps.ts. Calls to these\n// framework primitives are visited as accessor positions in their\n// own right, so we don't double-classify.\nconst NON_DELEGATION_HELPERS = new Set(['sample', 'item', 'memo', 'text', 'unsafeHtml'])\n\ninterface LeakSite {\n node: ts.Node\n shape: string\n hint: string\n}\n\nfunction findFirstLeakInAccessor(\n accessor: ts.ArrowFunction | ts.FunctionExpression | ts.FunctionDeclaration,\n checker: ts.TypeChecker | undefined,\n): LeakSite | null {\n if (accessor.parameters.length !== 1) return null\n const param = accessor.parameters[0]!\n if (!ts.isIdentifier(param.name)) return null\n if (!accessor.body) return null\n const stateParam = param.name.text\n\n let leak: LeakSite | null = null\n const visit = (node: ts.Node): void => {\n if (leak) return\n if (ts.isIdentifier(node) && node.text === stateParam) {\n const parent = node.parent\n if (!parent || ts.isParameter(parent)) {\n ts.forEachChild(node, visit)\n return\n }\n // Tracked containers — the same set the mask classifier honors.\n let tracked = false\n let shape = ''\n let hint = ''\n if (ts.isPropertyAccessExpression(parent) && parent.expression === node) {\n tracked = true\n } else if (ts.isElementAccessExpression(parent) && parent.expression === node) {\n if (\n ts.isStringLiteralLike(parent.argumentExpression) ||\n ts.isNumericLiteral(parent.argumentExpression)\n ) {\n tracked = true\n } else {\n shape = `dynamic element access \\`s[<expr>]\\``\n hint =\n 'replace the dynamic key with a literal property (e.g. `s.foo`), or declare the read via `track({ deps: (s) => [s[key]] })`.'\n }\n } else if (ts.isCallExpression(parent)) {\n const argIndex = parent.arguments.indexOf(node as ts.Expression)\n if (argIndex > 0) {\n // State passed as arg1+ to a call. The header documents this\n // as NOT flagged (intentional): the existing delegation\n // branch only attempts to trace arg0, and the mask classifier\n // emits a whole-state sentinel into `__prefixes` so the\n // binding stays correct. The cost is per-update re-evaluation\n // — a property of the composition pattern, not an author\n // mistake worth blocking. Without this branch we'd fall\n // through to the default \"outside a tracked container\" leak.\n tracked = true\n } else if (argIndex === 0) {\n if (\n ts.isIdentifier(parent.expression) &&\n !NON_DELEGATION_HELPERS.has(parent.expression.text)\n ) {\n // Identifier-callee delegation. Recurse into the callee's\n // body via the same resolver the mask walker uses. If it\n // resolves to a local accessor, the helper's reads are\n // walked transitively and the call is tracked. If the\n // callee is a function parameter, import, destructured\n // binding, or otherwise unresolvable, this IS the leak\n // shape — flag it here so the diagnostic points at the\n // call site rather than at some deeper unresolvable read.\n const resolved = resolveAccessorBody(parent.expression, checker)\n if (resolved) {\n tracked = true\n } else {\n const calleeSymbol = checker?.getSymbolAtLocation(parent.expression)\n const isFunctionParam = !!calleeSymbol?.declarations?.some((d: ts.Declaration) =>\n ts.isParameter(d),\n )\n shape = `call to an unresolvable callee \\`${parent.expression.text}(s)\\` (function parameter, import, or destructured binding)`\n if (isFunctionParam) {\n hint =\n 'this callee is a function parameter — the closure passed at the call site is opaque to per-binding analysis. The framework expects per-row dynamic state to flow through `each` items (slot data on `item.*`) rather than through `(s) => ...` callback parameters; restructure the helper so its bindings read `item.*` and the call site builds the slot data once in `items: (s) => …`.'\n } else {\n hint =\n 'inline the read against `s` directly, refactor the callee into a same-module `const`/`function` declaration, or declare the dependencies via `track({ deps: (s) => [...] })`.'\n }\n }\n } else if (!ts.isIdentifier(parent.expression)) {\n // Method-call / computed callee with state arg —\n // `obj.helper(s)`, `lib.fn(s)`. This is the documented\n // headless-components idiom (`pr.valueText(s)` where `pr`\n // comes from `progress.connect()`); refactoring it would\n // defeat the API surface. The runtime sentinel keeps the\n // binding correct — just at the cost of re-evaluating on\n // every update. Treat as tracked from the lint's POV so\n // legitimate composition doesn't error the build; the\n // perf cost is a property of the composition pattern, not\n // an author mistake worth blocking.\n tracked = true\n }\n }\n } else if (ts.isNewExpression(parent)) {\n shape = 'state passed as a constructor argument (`new X(s)`)'\n hint =\n 'compute the derived value inline against direct state reads, or use `track({ deps: (s) => [...] })` to declare the reads.'\n } else if (ts.isSpreadElement(parent) || ts.isSpreadAssignment(parent)) {\n shape = 'state spread (`{...s}` / `[...s]`)'\n hint =\n 'spread only the fields you actually need (`{...s.user}`), or use `track({ deps: (s) => [...] })`.'\n } else if (ts.isVariableDeclaration(parent)) {\n shape = 'const alias (`const x = s; … x.foo`)'\n hint =\n 'inline the alias to `s.foo`, or split the deeper read into a separate single-assignment alias `const foo = s.foo`.'\n } else if (ts.isConditionalExpression(parent)) {\n shape = 'state in a conditional branch (`cond ? s : other`)'\n hint = 'move the conditional inside the property access: `cond ? s.foo : other.foo`.'\n } else if (ts.isAsExpression(parent) || ts.isTypeAssertionExpression(parent)) {\n shape = 'type assertion wrapping state (`(s as T).foo`)'\n hint = 'drop the assertion — the chain `s.foo` already carries the type.'\n } else if (ts.isParenthesizedExpression(parent)) {\n // Walk up through parens transparently. Don't flag here; the\n // outer parent classifies.\n ts.forEachChild(node, visit)\n return\n } else {\n shape = `state used outside a tracked container (${describe(parent)})`\n hint =\n 'restructure the expression so `s` appears only as the root of a property/element-access chain, or declare the read via `track({ deps: (s) => [...] })`.'\n }\n if (!tracked) {\n leak = { node, shape, hint }\n return\n }\n }\n ts.forEachChild(node, visit)\n }\n visit(accessor.body)\n return leak\n}\n\n/**\n * True when `arrow` is the value of a `deps:` PropertyAssignment in\n * a `track({ ... })` call. The diagnostic is suppressed in that\n * position because `track` is the documented escape hatch for cases\n * the walker can't statically infer; firing the lint inside it moves\n * the diagnostic without giving the author a path forward.\n *\n * Handles both forms: bare `track({...})` (import from `@llui/dom`)\n * and the View-bag form `h.track({...})` if it ever exists.\n */\nfunction isInsideTrackDeps(arrow: ts.ArrowFunction | ts.FunctionExpression): boolean {\n const pa = arrow.parent\n if (!pa || !ts.isPropertyAssignment(pa) || !ts.isIdentifier(pa.name) || pa.name.text !== 'deps') {\n return false\n }\n const obj = pa.parent\n if (!obj || !ts.isObjectLiteralExpression(obj)) return false\n const call = obj.parent\n if (!call || !ts.isCallExpression(call) || call.arguments[0] !== obj) return false\n if (ts.isIdentifier(call.expression)) return call.expression.text === 'track'\n if (ts.isPropertyAccessExpression(call.expression)) {\n return call.expression.name.text === 'track'\n }\n return false\n}\n\nfunction describe(node: ts.Node): string {\n if (ts.isIdentifier(node)) return node.text\n if (ts.isPropertyAccessExpression(node)) return `${describe(node.expression)}.${node.name.text}`\n return ts.SyntaxKind[node.kind]\n}\n\nexport function opaqueStateFlowModule(): CompilerModule {\n return {\n name: 'opaque-state-flow',\n compilerVersion: '^0.3.0',\n diagnostics: [\n {\n id: 'llui/opaque-state-flow',\n description:\n \"Reactive accessor flows state into an opaque expression the walker can't trace. The runtime stays correct via a FULL_MASK binding + whole-state sentinel in `__prefixes`, but the binding then re-evaluates on every state change.\",\n },\n ],\n visitors: {\n [ts.SyntaxKind.SourceFile]: (ctx, node) => {\n // When the host adapter has built a Program, walk the checker's\n // own SourceFile so symbol resolution (Alias → Symbol via\n // `getSymbolAtLocation`) actually works. The reparsed file used\n // in the AST-only fallback is not part of any Program, so the\n // checker can't resolve identifiers in it. Fall back to a\n // reparse for paths without a Program (test harness, lint\n // adapters without cross-file resolution).\n const visited = node as ts.SourceFile\n const fromProgram = ctx.program?.getSourceFile(visited.fileName)\n const checker = fromProgram ? ctx.checker : undefined\n const sf =\n fromProgram ??\n ts.createSourceFile(visited.fileName, visited.text, ts.ScriptTarget.Latest, true)\n\n const walk = (n: ts.Node): void => {\n if ((ts.isArrowFunction(n) || ts.isFunctionExpression(n)) && isReactiveAccessor(n)) {\n // `track({ deps: (s) => [...] })` is the user's explicit\n // opt-in for \"this binding's reads can't be inferred — trust\n // my declaration.\" Firing a perf lint inside the user's\n // declaration defeats the primitive's purpose; the\n // diagnostic moves from the original call site to inside\n // track.deps without going away, leaving authors with no\n // recovery path. Suppress here. The mask/path classifier\n // still walks the body for what it can extract; this only\n // silences the lint.\n if (isInsideTrackDeps(n)) {\n ts.forEachChild(n, walk)\n return\n }\n const leak = findFirstLeakInAccessor(n, checker)\n if (leak) {\n ctx.reportDiagnostic({\n id: 'llui/opaque-state-flow',\n severity: 'error',\n category: 'perf',\n message:\n `Reactive accessor flows state opaquely — ${leak.shape}. ` +\n `The compiler ships a correct binding (FULL_MASK + whole-state sentinel), ` +\n `but it re-evaluates on every state change. ${leak.hint}`,\n location: {\n file: sf.fileName,\n range: rangeFromOffsets(sf.text, leak.node.getStart(sf), leak.node.getEnd()),\n },\n })\n }\n }\n ts.forEachChild(n, walk)\n }\n walk(sf)\n },\n },\n }\n}\n"]}
|