@llui/compiler 0.3.0
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/LICENSE +21 -0
- package/dist/accessor-resolver.d.ts +58 -0
- package/dist/accessor-resolver.d.ts.map +1 -0
- package/dist/accessor-resolver.js +119 -0
- package/dist/accessor-resolver.js.map +1 -0
- package/dist/binding-descriptors.d.ts +105 -0
- package/dist/binding-descriptors.d.ts.map +1 -0
- package/dist/binding-descriptors.js +340 -0
- package/dist/binding-descriptors.js.map +1 -0
- package/dist/collect-deps.d.ts +49 -0
- package/dist/collect-deps.d.ts.map +1 -0
- package/dist/collect-deps.js +444 -0
- package/dist/collect-deps.js.map +1 -0
- package/dist/compiler-cache.d.ts +20 -0
- package/dist/compiler-cache.d.ts.map +1 -0
- package/dist/compiler-cache.js +20 -0
- package/dist/compiler-cache.js.map +1 -0
- package/dist/cross-file-resolver.d.ts +109 -0
- package/dist/cross-file-resolver.d.ts.map +1 -0
- package/dist/cross-file-resolver.js +530 -0
- package/dist/cross-file-resolver.js.map +1 -0
- package/dist/cross-file-walker.d.ts +63 -0
- package/dist/cross-file-walker.d.ts.map +1 -0
- package/dist/cross-file-walker.js +516 -0
- package/dist/cross-file-walker.js.map +1 -0
- package/dist/diagnostic.d.ts +76 -0
- package/dist/diagnostic.d.ts.map +1 -0
- package/dist/diagnostic.js +59 -0
- package/dist/diagnostic.js.map +1 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +37 -0
- package/dist/index.js.map +1 -0
- package/dist/introspection-factory.d.ts +54 -0
- package/dist/introspection-factory.d.ts.map +1 -0
- package/dist/introspection-factory.js +46 -0
- package/dist/introspection-factory.js.map +1 -0
- package/dist/manifest.d.ts +144 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/manifest.js +209 -0
- package/dist/manifest.js.map +1 -0
- package/dist/module.d.ts +222 -0
- package/dist/module.d.ts.map +1 -0
- package/dist/module.js +256 -0
- package/dist/module.js.map +1 -0
- package/dist/modules/_element-helpers.d.ts +4 -0
- package/dist/modules/_element-helpers.d.ts.map +1 -0
- package/dist/modules/_element-helpers.js +138 -0
- package/dist/modules/_element-helpers.js.map +1 -0
- package/dist/modules/_msg-variants.d.ts +10 -0
- package/dist/modules/_msg-variants.d.ts.map +1 -0
- package/dist/modules/_msg-variants.js +97 -0
- package/dist/modules/_msg-variants.js.map +1 -0
- package/dist/modules/_shared.d.ts +16 -0
- package/dist/modules/_shared.d.ts.map +1 -0
- package/dist/modules/_shared.js +30 -0
- package/dist/modules/_shared.js.map +1 -0
- package/dist/modules/accessibility.d.ts +3 -0
- package/dist/modules/accessibility.d.ts.map +1 -0
- package/dist/modules/accessibility.js +82 -0
- package/dist/modules/accessibility.js.map +1 -0
- package/dist/modules/accessor-side-effect.d.ts +3 -0
- package/dist/modules/accessor-side-effect.d.ts.map +1 -0
- package/dist/modules/accessor-side-effect.js +113 -0
- package/dist/modules/accessor-side-effect.js.map +1 -0
- package/dist/modules/agent-emits-drift.d.ts +3 -0
- package/dist/modules/agent-emits-drift.d.ts.map +1 -0
- package/dist/modules/agent-emits-drift.js +158 -0
- package/dist/modules/agent-emits-drift.js.map +1 -0
- package/dist/modules/agent-example-on-payload.d.ts +3 -0
- package/dist/modules/agent-example-on-payload.d.ts.map +1 -0
- package/dist/modules/agent-example-on-payload.js +53 -0
- package/dist/modules/agent-example-on-payload.js.map +1 -0
- package/dist/modules/agent-exclusive-annotations.d.ts +3 -0
- package/dist/modules/agent-exclusive-annotations.d.ts.map +1 -0
- package/dist/modules/agent-exclusive-annotations.js +68 -0
- package/dist/modules/agent-exclusive-annotations.js.map +1 -0
- package/dist/modules/agent-missing-intent.d.ts +3 -0
- package/dist/modules/agent-missing-intent.d.ts.map +1 -0
- package/dist/modules/agent-missing-intent.js +47 -0
- package/dist/modules/agent-missing-intent.js.map +1 -0
- package/dist/modules/agent-msg-resolvable.d.ts +3 -0
- package/dist/modules/agent-msg-resolvable.d.ts.map +1 -0
- package/dist/modules/agent-msg-resolvable.js +161 -0
- package/dist/modules/agent-msg-resolvable.js.map +1 -0
- package/dist/modules/agent-nonextractable-handler.d.ts +3 -0
- package/dist/modules/agent-nonextractable-handler.d.ts.map +1 -0
- package/dist/modules/agent-nonextractable-handler.js +127 -0
- package/dist/modules/agent-nonextractable-handler.js.map +1 -0
- package/dist/modules/agent-optional-field-undocumented.d.ts +3 -0
- package/dist/modules/agent-optional-field-undocumented.d.ts.map +1 -0
- package/dist/modules/agent-optional-field-undocumented.js +67 -0
- package/dist/modules/agent-optional-field-undocumented.js.map +1 -0
- package/dist/modules/agent-tagsend-translator-missing.d.ts +3 -0
- package/dist/modules/agent-tagsend-translator-missing.d.ts.map +1 -0
- package/dist/modules/agent-tagsend-translator-missing.js +58 -0
- package/dist/modules/agent-tagsend-translator-missing.js.map +1 -0
- package/dist/modules/agent-warning-on-confirm.d.ts +3 -0
- package/dist/modules/agent-warning-on-confirm.d.ts.map +1 -0
- package/dist/modules/agent-warning-on-confirm.js +46 -0
- package/dist/modules/agent-warning-on-confirm.js.map +1 -0
- package/dist/modules/async-update.d.ts +3 -0
- package/dist/modules/async-update.d.ts.map +1 -0
- package/dist/modules/async-update.js +86 -0
- package/dist/modules/async-update.js.map +1 -0
- package/dist/modules/binding-descriptors.d.ts +4 -0
- package/dist/modules/binding-descriptors.d.ts.map +1 -0
- package/dist/modules/binding-descriptors.js +48 -0
- package/dist/modules/binding-descriptors.js.map +1 -0
- package/dist/modules/bitmask-overflow.d.ts +3 -0
- package/dist/modules/bitmask-overflow.d.ts.map +1 -0
- package/dist/modules/bitmask-overflow.js +152 -0
- package/dist/modules/bitmask-overflow.js.map +1 -0
- package/dist/modules/compiler-stamp.d.ts +3 -0
- package/dist/modules/compiler-stamp.d.ts.map +1 -0
- package/dist/modules/compiler-stamp.js +44 -0
- package/dist/modules/compiler-stamp.js.map +1 -0
- package/dist/modules/component-meta.d.ts +3 -0
- package/dist/modules/component-meta.d.ts.map +1 -0
- package/dist/modules/component-meta.js +44 -0
- package/dist/modules/component-meta.js.map +1 -0
- package/dist/modules/controlled-input.d.ts +3 -0
- package/dist/modules/controlled-input.d.ts.map +1 -0
- package/dist/modules/controlled-input.js +68 -0
- package/dist/modules/controlled-input.js.map +1 -0
- package/dist/modules/core-synthesis.d.ts +18 -0
- package/dist/modules/core-synthesis.d.ts.map +1 -0
- package/dist/modules/core-synthesis.js +748 -0
- package/dist/modules/core-synthesis.js.map +1 -0
- package/dist/modules/direct-state-in-view.d.ts +3 -0
- package/dist/modules/direct-state-in-view.d.ts.map +1 -0
- package/dist/modules/direct-state-in-view.js +103 -0
- package/dist/modules/direct-state-in-view.js.map +1 -0
- package/dist/modules/each-closure-violation.d.ts +3 -0
- package/dist/modules/each-closure-violation.d.ts.map +1 -0
- package/dist/modules/each-closure-violation.js +255 -0
- package/dist/modules/each-closure-violation.js.map +1 -0
- package/dist/modules/each-memo.d.ts +15 -0
- package/dist/modules/each-memo.d.ts.map +1 -0
- package/dist/modules/each-memo.js +115 -0
- package/dist/modules/each-memo.js.map +1 -0
- package/dist/modules/effect-without-handler.d.ts +3 -0
- package/dist/modules/effect-without-handler.d.ts.map +1 -0
- package/dist/modules/effect-without-handler.js +92 -0
- package/dist/modules/effect-without-handler.js.map +1 -0
- package/dist/modules/element-rewrite.d.ts +22 -0
- package/dist/modules/element-rewrite.d.ts.map +1 -0
- package/dist/modules/element-rewrite.js +1017 -0
- package/dist/modules/element-rewrite.js.map +1 -0
- package/dist/modules/empty-props.d.ts +3 -0
- package/dist/modules/empty-props.d.ts.map +1 -0
- package/dist/modules/empty-props.js +50 -0
- package/dist/modules/empty-props.js.map +1 -0
- package/dist/modules/exhaustive-effect-handling.d.ts +3 -0
- package/dist/modules/exhaustive-effect-handling.d.ts.map +1 -0
- package/dist/modules/exhaustive-effect-handling.js +61 -0
- package/dist/modules/exhaustive-effect-handling.js.map +1 -0
- package/dist/modules/exhaustive-update.d.ts +3 -0
- package/dist/modules/exhaustive-update.d.ts.map +1 -0
- package/dist/modules/exhaustive-update.js +146 -0
- package/dist/modules/exhaustive-update.js.map +1 -0
- package/dist/modules/forgotten-spread.d.ts +3 -0
- package/dist/modules/forgotten-spread.d.ts.map +1 -0
- package/dist/modules/forgotten-spread.js +51 -0
- package/dist/modules/forgotten-spread.js.map +1 -0
- package/dist/modules/form-boilerplate.d.ts +3 -0
- package/dist/modules/form-boilerplate.d.ts.map +1 -0
- package/dist/modules/form-boilerplate.js +101 -0
- package/dist/modules/form-boilerplate.js.map +1 -0
- package/dist/modules/imperative-dom-in-view.d.ts +3 -0
- package/dist/modules/imperative-dom-in-view.d.ts.map +1 -0
- package/dist/modules/imperative-dom-in-view.js +123 -0
- package/dist/modules/imperative-dom-in-view.js.map +1 -0
- package/dist/modules/item-dedup.d.ts +7 -0
- package/dist/modules/item-dedup.d.ts.map +1 -0
- package/dist/modules/item-dedup.js +204 -0
- package/dist/modules/item-dedup.js.map +1 -0
- package/dist/modules/map-on-state-array.d.ts +3 -0
- package/dist/modules/map-on-state-array.d.ts.map +1 -0
- package/dist/modules/map-on-state-array.js +84 -0
- package/dist/modules/map-on-state-array.js.map +1 -0
- package/dist/modules/mask-legend.d.ts +10 -0
- package/dist/modules/mask-legend.d.ts.map +1 -0
- package/dist/modules/mask-legend.js +50 -0
- package/dist/modules/mask-legend.js.map +1 -0
- package/dist/modules/missing-memo.d.ts +3 -0
- package/dist/modules/missing-memo.d.ts.map +1 -0
- package/dist/modules/missing-memo.js +114 -0
- package/dist/modules/missing-memo.js.map +1 -0
- package/dist/modules/msg-annotations.d.ts +9 -0
- package/dist/modules/msg-annotations.d.ts.map +1 -0
- package/dist/modules/msg-annotations.js +54 -0
- package/dist/modules/msg-annotations.js.map +1 -0
- package/dist/modules/msg-schema.d.ts +10 -0
- package/dist/modules/msg-schema.d.ts.map +1 -0
- package/dist/modules/msg-schema.js +70 -0
- package/dist/modules/msg-schema.js.map +1 -0
- package/dist/modules/namespace-import.d.ts +3 -0
- package/dist/modules/namespace-import.d.ts.map +1 -0
- package/dist/modules/namespace-import.js +80 -0
- package/dist/modules/namespace-import.js.map +1 -0
- package/dist/modules/nested-send-in-update.d.ts +3 -0
- package/dist/modules/nested-send-in-update.d.ts.map +1 -0
- package/dist/modules/nested-send-in-update.js +77 -0
- package/dist/modules/nested-send-in-update.js.map +1 -0
- package/dist/modules/no-barrel-import-when-subpath-exists.d.ts +3 -0
- package/dist/modules/no-barrel-import-when-subpath-exists.d.ts.map +1 -0
- package/dist/modules/no-barrel-import-when-subpath-exists.js +100 -0
- package/dist/modules/no-barrel-import-when-subpath-exists.js.map +1 -0
- package/dist/modules/no-eager-item-accessor.d.ts +3 -0
- package/dist/modules/no-eager-item-accessor.d.ts.map +1 -0
- package/dist/modules/no-eager-item-accessor.js +74 -0
- package/dist/modules/no-eager-item-accessor.js.map +1 -0
- package/dist/modules/no-let-reactive-accessor.d.ts +3 -0
- package/dist/modules/no-let-reactive-accessor.d.ts.map +1 -0
- package/dist/modules/no-let-reactive-accessor.js +227 -0
- package/dist/modules/no-let-reactive-accessor.js.map +1 -0
- package/dist/modules/no-list-render-in-sample.d.ts +3 -0
- package/dist/modules/no-list-render-in-sample.d.ts.map +1 -0
- package/dist/modules/no-list-render-in-sample.js +89 -0
- package/dist/modules/no-list-render-in-sample.js.map +1 -0
- package/dist/modules/no-sample-in-accessor.d.ts +3 -0
- package/dist/modules/no-sample-in-accessor.d.ts.map +1 -0
- package/dist/modules/no-sample-in-accessor.js +141 -0
- package/dist/modules/no-sample-in-accessor.js.map +1 -0
- package/dist/modules/no-sample-in-reactive-position.d.ts +3 -0
- package/dist/modules/no-sample-in-reactive-position.d.ts.map +1 -0
- package/dist/modules/no-sample-in-reactive-position.js +72 -0
- package/dist/modules/no-sample-in-reactive-position.js.map +1 -0
- package/dist/modules/pure-update-function.d.ts +3 -0
- package/dist/modules/pure-update-function.d.ts.map +1 -0
- package/dist/modules/pure-update-function.js +127 -0
- package/dist/modules/pure-update-function.js.map +1 -0
- package/dist/modules/reactive-paths.d.ts +3 -0
- package/dist/modules/reactive-paths.d.ts.map +1 -0
- package/dist/modules/reactive-paths.js +77 -0
- package/dist/modules/reactive-paths.js.map +1 -0
- package/dist/modules/row-factory.d.ts +12 -0
- package/dist/modules/row-factory.d.ts.map +1 -0
- package/dist/modules/row-factory.js +385 -0
- package/dist/modules/row-factory.js.map +1 -0
- package/dist/modules/schema-hash.d.ts +15 -0
- package/dist/modules/schema-hash.d.ts.map +1 -0
- package/dist/modules/schema-hash.js +70 -0
- package/dist/modules/schema-hash.js.map +1 -0
- package/dist/modules/spread-in-children.d.ts +3 -0
- package/dist/modules/spread-in-children.d.ts.map +1 -0
- package/dist/modules/spread-in-children.js +144 -0
- package/dist/modules/spread-in-children.js.map +1 -0
- package/dist/modules/state-mutation.d.ts +3 -0
- package/dist/modules/state-mutation.d.ts.map +1 -0
- package/dist/modules/state-mutation.js +138 -0
- package/dist/modules/state-mutation.js.map +1 -0
- package/dist/modules/state-schema.d.ts +8 -0
- package/dist/modules/state-schema.d.ts.map +1 -0
- package/dist/modules/state-schema.js +55 -0
- package/dist/modules/state-schema.js.map +1 -0
- package/dist/modules/static-items.d.ts +3 -0
- package/dist/modules/static-items.d.ts.map +1 -0
- package/dist/modules/static-items.js +125 -0
- package/dist/modules/static-items.js.map +1 -0
- package/dist/modules/static-on.d.ts +3 -0
- package/dist/modules/static-on.d.ts.map +1 -0
- package/dist/modules/static-on.js +100 -0
- package/dist/modules/static-on.js.map +1 -0
- package/dist/modules/string-effect-callback.d.ts +3 -0
- package/dist/modules/string-effect-callback.d.ts.map +1 -0
- package/dist/modules/string-effect-callback.js +50 -0
- package/dist/modules/string-effect-callback.js.map +1 -0
- package/dist/modules/structural-mask.d.ts +8 -0
- package/dist/modules/structural-mask.d.ts.map +1 -0
- package/dist/modules/structural-mask.js +76 -0
- package/dist/modules/structural-mask.js.map +1 -0
- package/dist/modules/subapp-requires-reason.d.ts +3 -0
- package/dist/modules/subapp-requires-reason.d.ts.map +1 -0
- package/dist/modules/subapp-requires-reason.js +129 -0
- package/dist/modules/subapp-requires-reason.js.map +1 -0
- package/dist/modules/text-mask.d.ts +12 -0
- package/dist/modules/text-mask.d.ts.map +1 -0
- package/dist/modules/text-mask.js +63 -0
- package/dist/modules/text-mask.js.map +1 -0
- package/dist/modules/view-bag-import.d.ts +3 -0
- package/dist/modules/view-bag-import.d.ts.map +1 -0
- package/dist/modules/view-bag-import.js +80 -0
- package/dist/modules/view-bag-import.js.map +1 -0
- package/dist/msg-annotations.d.ts +104 -0
- package/dist/msg-annotations.d.ts.map +1 -0
- package/dist/msg-annotations.js +242 -0
- package/dist/msg-annotations.js.map +1 -0
- package/dist/msg-schema.d.ts +130 -0
- package/dist/msg-schema.d.ts.map +1 -0
- package/dist/msg-schema.js +770 -0
- package/dist/msg-schema.js.map +1 -0
- package/dist/schema-hash.d.ts +16 -0
- package/dist/schema-hash.d.ts.map +1 -0
- package/dist/schema-hash.js +31 -0
- package/dist/schema-hash.js.map +1 -0
- package/dist/state-schema.d.ts +41 -0
- package/dist/state-schema.d.ts.map +1 -0
- package/dist/state-schema.js +156 -0
- package/dist/state-schema.js.map +1 -0
- package/dist/transform.d.ts +109 -0
- package/dist/transform.d.ts.map +1 -0
- package/dist/transform.js +1390 -0
- package/dist/transform.js.map +1 -0
- package/dist/version.d.ts +11 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +11 -0
- package/dist/version.js.map +1 -0
- package/package.json +47 -0
|
@@ -0,0 +1,516 @@
|
|
|
1
|
+
// Cross-file walker — v2b prototype (Phase 1).
|
|
2
|
+
//
|
|
3
|
+
// Classifies every call expression in a TypeScript Program against the
|
|
4
|
+
// §2.1 view-helper resolution rule. Returns diagnostics + a per-callsite
|
|
5
|
+
// classification trace.
|
|
6
|
+
//
|
|
7
|
+
// This is *prototype-grade*: no manifest consumption, no incremental cache,
|
|
8
|
+
// no reverse-deps tracking. Phase 3 adds those layers on top.
|
|
9
|
+
//
|
|
10
|
+
// The rule (§2.1):
|
|
11
|
+
// A call is a view-helper iff at least one of:
|
|
12
|
+
// case 1 — the callee accepts a parameter assignable to View<S,M> or
|
|
13
|
+
// one of the documented structural subsets (send-only,
|
|
14
|
+
// text-only, send+text, send+show+each+branch).
|
|
15
|
+
// case 2 — the callee's *declared* return type is assignable to
|
|
16
|
+
// Node, Node[], Node|undefined, or ReadonlyArray<Node>.
|
|
17
|
+
// case 3 — the callee has a `/** @llui-helper */` JSDoc tag.
|
|
18
|
+
//
|
|
19
|
+
// Async helpers (declared return Promise<Node[]>) are NOT view-helpers
|
|
20
|
+
// and produce `llui/async-view-helper` (hard error).
|
|
21
|
+
//
|
|
22
|
+
// Everything else: opaque. Emits `llui/opaque-view-call` if the call site
|
|
23
|
+
// is structurally a view position (its result flows into a view-returning
|
|
24
|
+
// expression); otherwise the call is uninteresting and not reported.
|
|
25
|
+
import ts from 'typescript';
|
|
26
|
+
import { rangeFromOffsets, relativizeFile, } from './diagnostic.js';
|
|
27
|
+
/**
|
|
28
|
+
* Classify the symbol's declaration against the §2.1 rule.
|
|
29
|
+
*
|
|
30
|
+
* Operates on the *declared* type (`getTypeOfSymbolAtLocation(symbol,
|
|
31
|
+
* symbol.declarations[0])`), not the inferred-at-call-site type. This is
|
|
32
|
+
* load-bearing: TypeScript inference at call sites widens to union
|
|
33
|
+
* shapes (`Node[] | undefined`, `JSX.Element | string`) that miss
|
|
34
|
+
* assignability for case 2. The rule's intent is "did the author commit
|
|
35
|
+
* to a view-helper signature in the declaration" — inference-narrowed
|
|
36
|
+
* types don't satisfy that intent.
|
|
37
|
+
*/
|
|
38
|
+
export function classifyViewHelper(symbol, checker) {
|
|
39
|
+
const decls = symbol.getDeclarations() ?? [];
|
|
40
|
+
const decl = decls.find(isCallableDeclaration);
|
|
41
|
+
if (!decl)
|
|
42
|
+
return { kind: 'not-a-helper', cases: [], reason: 'no callable declaration found' };
|
|
43
|
+
const cases = [];
|
|
44
|
+
const reasons = [];
|
|
45
|
+
// Case 3: @llui-helper JSDoc tag. Cheap to check, decisive when present.
|
|
46
|
+
if (hasLluiHelperTag(decl)) {
|
|
47
|
+
cases.push(3);
|
|
48
|
+
reasons.push('@llui-helper tag');
|
|
49
|
+
}
|
|
50
|
+
// Get the signature's parameters + return type from the *declaration*.
|
|
51
|
+
const declType = checker.getTypeOfSymbolAtLocation(symbol, decl);
|
|
52
|
+
const sigs = declType.getCallSignatures();
|
|
53
|
+
if (sigs.length === 0) {
|
|
54
|
+
if (cases.includes(3)) {
|
|
55
|
+
return { kind: 'walked', cases, reason: reasons.join('; ') };
|
|
56
|
+
}
|
|
57
|
+
return { kind: 'not-a-helper', cases: [], reason: 'declared type has no call signature' };
|
|
58
|
+
}
|
|
59
|
+
// Use the first signature. Overloaded helpers are rare in view code;
|
|
60
|
+
// production walker should iterate all.
|
|
61
|
+
const sig = sigs[0];
|
|
62
|
+
// Case 2: declared return type assignable to Node / Node[] / etc.
|
|
63
|
+
const returnType = checker.getReturnTypeOfSignature(sig);
|
|
64
|
+
const asyncMatch = returnTypeIsAsyncNode(returnType, checker);
|
|
65
|
+
if (asyncMatch) {
|
|
66
|
+
return { kind: 'async', cases: [], reason: 'declared return is Promise<Node[] | Node>' };
|
|
67
|
+
}
|
|
68
|
+
if (returnTypeIsNodeShape(returnType, checker)) {
|
|
69
|
+
cases.push(2);
|
|
70
|
+
reasons.push('declared return is Node[]-like');
|
|
71
|
+
}
|
|
72
|
+
// Case 1: at least one parameter assignable to View<S,M> or a documented
|
|
73
|
+
// structural subset. Match on the declared parameter type's shape — we
|
|
74
|
+
// use property-name plus per-property callability as a structural
|
|
75
|
+
// signature. The 5 documented subsets are listed below.
|
|
76
|
+
for (const param of sig.getParameters()) {
|
|
77
|
+
const paramDecl = param.getDeclarations()?.[0];
|
|
78
|
+
if (!paramDecl)
|
|
79
|
+
continue;
|
|
80
|
+
const paramType = checker.getTypeOfSymbolAtLocation(param, paramDecl);
|
|
81
|
+
const subset = matchViewSubset(paramType, checker);
|
|
82
|
+
if (subset) {
|
|
83
|
+
cases.push(1);
|
|
84
|
+
reasons.push(`accepts View subset (${subset})`);
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
if (cases.length === 0) {
|
|
89
|
+
return { kind: 'opaque', cases: [], reason: 'no §2.1 case matched' };
|
|
90
|
+
}
|
|
91
|
+
return { kind: 'walked', cases, reason: reasons.join('; ') };
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* The 5 documented View subsets, enumerated. New subsets require a doc
|
|
95
|
+
* revision and a fixture per §2.1.
|
|
96
|
+
*
|
|
97
|
+
* Returns the subset's identifier when matched, undefined otherwise.
|
|
98
|
+
* Matching is by *property presence* — we check that the parameter type
|
|
99
|
+
* exposes the required property names, each with a callable type. We
|
|
100
|
+
* intentionally do NOT call `isTypeAssignableTo` against a synthetic
|
|
101
|
+
* subset type, because TypeScript's structural assignability would
|
|
102
|
+
* accept arbitrary supersets and lose the "documented subset" guarantee.
|
|
103
|
+
*/
|
|
104
|
+
function matchViewSubset(t, _checker) {
|
|
105
|
+
if (!isObjectLike(t))
|
|
106
|
+
return undefined;
|
|
107
|
+
const props = t.getProperties().map((p) => p.getName());
|
|
108
|
+
const has = (name) => props.includes(name);
|
|
109
|
+
// Full View<S,M> bag — must expose at least send + show + each + branch.
|
|
110
|
+
// Real-world callers spread or destructure; the type they pass is the
|
|
111
|
+
// full View<S,M> regardless of how much they use.
|
|
112
|
+
if (has('send') && has('show') && has('each') && has('branch') && has('text')) {
|
|
113
|
+
return 'View<S, M>';
|
|
114
|
+
}
|
|
115
|
+
// send + show + each + branch (no text)
|
|
116
|
+
if (has('send') && has('show') && has('each') && has('branch') && !has('text')) {
|
|
117
|
+
return '{ send, show, each, branch }';
|
|
118
|
+
}
|
|
119
|
+
// send + text
|
|
120
|
+
if (has('send') && has('text') && !has('show') && !has('each')) {
|
|
121
|
+
return '{ send, text }';
|
|
122
|
+
}
|
|
123
|
+
// send only
|
|
124
|
+
if (has('send') && !has('text') && !has('show') && !has('each')) {
|
|
125
|
+
return '{ send }';
|
|
126
|
+
}
|
|
127
|
+
// text only
|
|
128
|
+
if (has('text') && !has('send') && !has('show') && !has('each')) {
|
|
129
|
+
return '{ text }';
|
|
130
|
+
}
|
|
131
|
+
return undefined;
|
|
132
|
+
}
|
|
133
|
+
function isObjectLike(t) {
|
|
134
|
+
return (t.getFlags() & ts.TypeFlags.Object) !== 0;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Returns true if `t` is assignable to one of: `Node`, `Node[]`,
|
|
138
|
+
* `Node | undefined`, `ReadonlyArray<Node>`.
|
|
139
|
+
*
|
|
140
|
+
* Uses the type checker's structural matching against a synthesized
|
|
141
|
+
* `Node`-like — we look up the global `Node` symbol from the lib.dom
|
|
142
|
+
* declarations.
|
|
143
|
+
*/
|
|
144
|
+
function returnTypeIsNodeShape(t, checker) {
|
|
145
|
+
// Strip undefined from a union for the "Node | undefined" case.
|
|
146
|
+
const nonUndefined = stripUndefined(t);
|
|
147
|
+
// Array / ReadonlyArray case: element type is Node-like.
|
|
148
|
+
// TypeScript represents both as a `TypeReference` whose target is the
|
|
149
|
+
// global Array / ReadonlyArray and whose first type-arg is the element.
|
|
150
|
+
if (isArrayOrReadonlyArray(nonUndefined)) {
|
|
151
|
+
const elem = getFirstTypeArg(nonUndefined);
|
|
152
|
+
if (elem && isNodeLike(elem, checker))
|
|
153
|
+
return true;
|
|
154
|
+
}
|
|
155
|
+
// Singular Node return: `function (...): Node`.
|
|
156
|
+
if (isNodeLike(nonUndefined, checker))
|
|
157
|
+
return true;
|
|
158
|
+
// Union: any member is Node-shaped.
|
|
159
|
+
if (nonUndefined.isUnion()) {
|
|
160
|
+
for (const u of nonUndefined.types) {
|
|
161
|
+
if (returnTypeIsNodeShape(u, checker))
|
|
162
|
+
return true;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
function returnTypeIsAsyncNode(t, checker) {
|
|
168
|
+
// Promise<X> where X is Node-shaped.
|
|
169
|
+
const sym = t.getSymbol();
|
|
170
|
+
if (!sym || sym.getName() !== 'Promise')
|
|
171
|
+
return false;
|
|
172
|
+
const args = t.typeArguments;
|
|
173
|
+
if (!args || args.length === 0)
|
|
174
|
+
return false;
|
|
175
|
+
return returnTypeIsNodeShape(args[0], checker);
|
|
176
|
+
}
|
|
177
|
+
function stripUndefined(t) {
|
|
178
|
+
if (!t.isUnion())
|
|
179
|
+
return t;
|
|
180
|
+
const nonUndef = t.types.filter((u) => (u.getFlags() & (ts.TypeFlags.Undefined | ts.TypeFlags.Void)) === 0);
|
|
181
|
+
if (nonUndef.length === t.types.length)
|
|
182
|
+
return t;
|
|
183
|
+
if (nonUndef.length === 1)
|
|
184
|
+
return nonUndef[0];
|
|
185
|
+
return t;
|
|
186
|
+
}
|
|
187
|
+
function isArrayOrReadonlyArray(t) {
|
|
188
|
+
const sym = t.getSymbol();
|
|
189
|
+
if (!sym)
|
|
190
|
+
return false;
|
|
191
|
+
const name = sym.getName();
|
|
192
|
+
return name === 'ReadonlyArray' || name === 'Array';
|
|
193
|
+
}
|
|
194
|
+
function getFirstTypeArg(t) {
|
|
195
|
+
const args = t.typeArguments;
|
|
196
|
+
return args?.[0];
|
|
197
|
+
}
|
|
198
|
+
function isNodeLike(t, _checker) {
|
|
199
|
+
const sym = t.getSymbol();
|
|
200
|
+
if (!sym)
|
|
201
|
+
return false;
|
|
202
|
+
const name = sym.getName();
|
|
203
|
+
// Built-in DOM Node base types — return true on the abstract names.
|
|
204
|
+
if (name === 'Node' ||
|
|
205
|
+
name === 'Element' ||
|
|
206
|
+
name === 'HTMLElement' ||
|
|
207
|
+
name === 'Text' ||
|
|
208
|
+
name === 'Comment' ||
|
|
209
|
+
name === 'DocumentFragment' ||
|
|
210
|
+
name === 'ChildNode') {
|
|
211
|
+
return true;
|
|
212
|
+
}
|
|
213
|
+
// The concrete `HTMLDivElement` / `SVGCircleElement` / `MathMLMathElement`
|
|
214
|
+
// names that `HTMLElementTagNameMap[K]` resolves to. Element helpers
|
|
215
|
+
// (`div(...)`, `button(...)`) all hit this branch — without it they
|
|
216
|
+
// would be misclassified as opaque and flood the diagnostic stream.
|
|
217
|
+
return (/^HTML[A-Z]\w*Element$/.test(name) ||
|
|
218
|
+
/^SVG[A-Z]\w*Element$/.test(name) ||
|
|
219
|
+
/^MathML[A-Z]\w*Element$/.test(name));
|
|
220
|
+
}
|
|
221
|
+
function hasLluiHelperTag(decl) {
|
|
222
|
+
const jsDocs = ts.getJSDocTags(decl);
|
|
223
|
+
for (const tag of jsDocs) {
|
|
224
|
+
if (tag.tagName.text === 'llui-helper')
|
|
225
|
+
return true;
|
|
226
|
+
}
|
|
227
|
+
return false;
|
|
228
|
+
}
|
|
229
|
+
function isCallableDeclaration(d) {
|
|
230
|
+
return (ts.isFunctionDeclaration(d) ||
|
|
231
|
+
ts.isFunctionExpression(d) ||
|
|
232
|
+
ts.isArrowFunction(d) ||
|
|
233
|
+
ts.isMethodDeclaration(d) ||
|
|
234
|
+
ts.isVariableDeclaration(d));
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Whether the call site is structurally in a view position. A view
|
|
238
|
+
* position is one where the result flows into:
|
|
239
|
+
* - the return of a `view()` callback,
|
|
240
|
+
* - a `Node[]` literal element being built by a structural primitive
|
|
241
|
+
* (`each.render`, `show.render`, `branch.cases.X`, `scope.render`),
|
|
242
|
+
* - the children array of an element-helper call.
|
|
243
|
+
*
|
|
244
|
+
* Approximation for the prototype: the call is inside a function whose
|
|
245
|
+
* return type (declared OR inferred from the surrounding context) is
|
|
246
|
+
* Node-shaped. Less precise than tracking JSX-style returns, but
|
|
247
|
+
* sufficient to gate the diagnostic emission for the validation run.
|
|
248
|
+
*/
|
|
249
|
+
function isViewPositionCall(call, checker) {
|
|
250
|
+
// Walk up: an enclosing function declaration / arrow whose return is
|
|
251
|
+
// Node-shaped. Stop at the source file.
|
|
252
|
+
let cur = call.parent;
|
|
253
|
+
while (cur) {
|
|
254
|
+
if (ts.isFunctionDeclaration(cur) ||
|
|
255
|
+
ts.isFunctionExpression(cur) ||
|
|
256
|
+
ts.isArrowFunction(cur) ||
|
|
257
|
+
ts.isMethodDeclaration(cur)) {
|
|
258
|
+
const sig = checker.getSignatureFromDeclaration(cur);
|
|
259
|
+
if (sig) {
|
|
260
|
+
const ret = checker.getReturnTypeOfSignature(sig);
|
|
261
|
+
if (returnTypeIsNodeShape(ret, checker))
|
|
262
|
+
return true;
|
|
263
|
+
}
|
|
264
|
+
return false;
|
|
265
|
+
}
|
|
266
|
+
cur = cur.parent;
|
|
267
|
+
}
|
|
268
|
+
return false;
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Walk a Program looking for call expressions that should be classified
|
|
272
|
+
* by the §2.1 rule. Restricts the walk to files matching `filter` so
|
|
273
|
+
* tests can scope to a subdirectory.
|
|
274
|
+
*/
|
|
275
|
+
export function walkProgram(program, options = {}) {
|
|
276
|
+
const checker = program.getTypeChecker();
|
|
277
|
+
const filter = options.filter ?? (() => true);
|
|
278
|
+
const diagnostics = [];
|
|
279
|
+
const perFile = new Map();
|
|
280
|
+
for (const sf of program.getSourceFiles()) {
|
|
281
|
+
if (sf.isDeclarationFile)
|
|
282
|
+
continue;
|
|
283
|
+
if (!filter(sf))
|
|
284
|
+
continue;
|
|
285
|
+
const counts = { callsClassified: 0, walked: 0, opaque: 0, async: 0, notAHelper: 0 };
|
|
286
|
+
perFile.set(sf.fileName, counts);
|
|
287
|
+
const visit = (node) => {
|
|
288
|
+
if (ts.isCallExpression(node)) {
|
|
289
|
+
// Resolve the symbol of the callee.
|
|
290
|
+
const callee = node.expression;
|
|
291
|
+
let symbol = checker.getSymbolAtLocation(callee);
|
|
292
|
+
if (symbol && symbol.flags & ts.SymbolFlags.Alias) {
|
|
293
|
+
symbol = checker.getAliasedSymbol(symbol);
|
|
294
|
+
}
|
|
295
|
+
if (symbol && !(symbol.flags & ts.SymbolFlags.Transient)) {
|
|
296
|
+
const cls = classifyViewHelper(symbol, checker);
|
|
297
|
+
counts.callsClassified++;
|
|
298
|
+
if (cls.kind === 'walked')
|
|
299
|
+
counts.walked++;
|
|
300
|
+
else if (cls.kind === 'opaque')
|
|
301
|
+
counts.opaque++;
|
|
302
|
+
else if (cls.kind === 'async')
|
|
303
|
+
counts.async++;
|
|
304
|
+
else
|
|
305
|
+
counts.notAHelper++;
|
|
306
|
+
if (cls.kind === 'opaque' && isViewPositionCall(node, checker)) {
|
|
307
|
+
diagnostics.push({
|
|
308
|
+
id: 'llui/opaque-view-call',
|
|
309
|
+
file: sf.fileName,
|
|
310
|
+
pos: node.getStart(sf),
|
|
311
|
+
end: node.getEnd(),
|
|
312
|
+
helperName: getCalleeName(callee),
|
|
313
|
+
message: `Call to "${getCalleeName(callee) ?? '<unknown>'}" in a view position is opaque to the cross-file walker (${cls.reason}). Either add an explicit return-type annotation (Node[] / Node / ReadonlyArray<Node>), accept a View bag parameter (or a documented subset), or mark with /** @llui-helper */ if the helper genuinely cannot be annotated. As a last resort, use track({ deps: ... }) at the call site.`,
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
else if (cls.kind === 'async' && isViewPositionCall(node, checker)) {
|
|
317
|
+
diagnostics.push({
|
|
318
|
+
id: 'llui/async-view-helper',
|
|
319
|
+
file: sf.fileName,
|
|
320
|
+
pos: node.getStart(sf),
|
|
321
|
+
end: node.getEnd(),
|
|
322
|
+
helperName: getCalleeName(callee),
|
|
323
|
+
message: `Call to "${getCalleeName(callee) ?? '<unknown>'}" in a view position returns Promise<Node[] | Node>. LLui's view layer is synchronous — wrap async work in onMount() or use clientOnly() instead.`,
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
ts.forEachChild(node, visit);
|
|
329
|
+
};
|
|
330
|
+
visit(sf);
|
|
331
|
+
}
|
|
332
|
+
return { diagnostics, perFile };
|
|
333
|
+
}
|
|
334
|
+
function getCalleeName(expr) {
|
|
335
|
+
if (ts.isIdentifier(expr))
|
|
336
|
+
return expr.text;
|
|
337
|
+
if (ts.isPropertyAccessExpression(expr))
|
|
338
|
+
return expr.name.text;
|
|
339
|
+
return undefined;
|
|
340
|
+
}
|
|
341
|
+
// ── Diagnostic schema integration (v2c §3) ──────────────────────────
|
|
342
|
+
//
|
|
343
|
+
// The walker emits a lightweight `WalkerDiagnostic` (with raw byte
|
|
344
|
+
// offsets) for internal accumulation. Adapters and the host pipeline
|
|
345
|
+
// want the canonical `Diagnostic` shape with project-relative paths
|
|
346
|
+
// and line/column ranges. This converter resolves the position pair to
|
|
347
|
+
// a Range and relativizes the file path; the caller supplies the
|
|
348
|
+
// project root.
|
|
349
|
+
const WALKER_DIAGNOSTIC_META = {
|
|
350
|
+
'llui/opaque-view-call': {
|
|
351
|
+
severity: 'warning',
|
|
352
|
+
category: 'reactivity',
|
|
353
|
+
documentation: 'https://github.com/fponticelli/llui/blob/main/docs/proposals/v2-compiler/v2b.md#21-view-helper-resolution-rule',
|
|
354
|
+
},
|
|
355
|
+
'llui/async-view-helper': {
|
|
356
|
+
severity: 'error',
|
|
357
|
+
category: 'composition',
|
|
358
|
+
documentation: 'https://github.com/fponticelli/llui/blob/main/docs/proposals/v2-compiler/v2b.md#21-view-helper-resolution-rule',
|
|
359
|
+
},
|
|
360
|
+
'llui/helper-cycle': {
|
|
361
|
+
severity: 'warning',
|
|
362
|
+
category: 'composition',
|
|
363
|
+
documentation: 'https://github.com/fponticelli/llui/blob/main/docs/proposals/v2-compiler/v2b.md#23-recursion-and-cycles',
|
|
364
|
+
},
|
|
365
|
+
};
|
|
366
|
+
/**
|
|
367
|
+
* Convert a walker-internal diagnostic to the canonical `Diagnostic`
|
|
368
|
+
* shape. Reads the source text (for line/column resolution) and a
|
|
369
|
+
* project root (for path relativization).
|
|
370
|
+
*/
|
|
371
|
+
export function toCanonicalDiagnostic(d, sourceText, projectRoot) {
|
|
372
|
+
const meta = WALKER_DIAGNOSTIC_META[d.id];
|
|
373
|
+
return {
|
|
374
|
+
id: d.id,
|
|
375
|
+
severity: meta.severity,
|
|
376
|
+
category: meta.category,
|
|
377
|
+
message: d.message,
|
|
378
|
+
location: {
|
|
379
|
+
file: relativizeFile(d.file, projectRoot),
|
|
380
|
+
range: rangeFromOffsets(sourceText, d.pos, d.end),
|
|
381
|
+
},
|
|
382
|
+
...(meta.documentation ? { documentation: meta.documentation } : {}),
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
// ── Cross-file accessor path collection ─────────────────────────────
|
|
386
|
+
//
|
|
387
|
+
// Given a focal source file inside a Program, walk every reactive-accessor
|
|
388
|
+
// arrow in the file. For each accessor:
|
|
389
|
+
// - Collect paths it reads directly (the existing AST-only collector
|
|
390
|
+
// handles this — see `collect-deps.ts`).
|
|
391
|
+
// - For every call site inside the accessor whose callee resolves to a
|
|
392
|
+
// view-helper (per §2.1), descend into the callee and merge its reads.
|
|
393
|
+
//
|
|
394
|
+
// This is the cross-file extension of `collectStatePathsFromSource`. The
|
|
395
|
+
// AST-only collector terminates at the file boundary; the cross-file
|
|
396
|
+
// version follows view-helper calls into other files using the TypeChecker.
|
|
397
|
+
//
|
|
398
|
+
// Used by the focal file's compiler to compute its __prefixes table. The
|
|
399
|
+
// production wiring (Vite adapter builds a Program; compileFile consumes
|
|
400
|
+
// it) is v2c module work — for v2b this is exposed as a callable engine
|
|
401
|
+
// API that downstream tools can drive.
|
|
402
|
+
/**
|
|
403
|
+
* Collect the cross-file union of accessor paths read from a focal file.
|
|
404
|
+
* Returns the union over every reactive accessor in `focalFile`, with
|
|
405
|
+
* cross-file view-helper descents merged in.
|
|
406
|
+
*/
|
|
407
|
+
export function crossFileAccessorPaths(program, focalFile) {
|
|
408
|
+
const checker = program.getTypeChecker();
|
|
409
|
+
const paths = new Set();
|
|
410
|
+
const visitedHelpers = new Set();
|
|
411
|
+
const visit = (node, paramName) => {
|
|
412
|
+
// Reactive accessor entry: a 1-param arrow or function expression.
|
|
413
|
+
if ((ts.isArrowFunction(node) || ts.isFunctionExpression(node)) &&
|
|
414
|
+
node.parameters.length === 1) {
|
|
415
|
+
const p0 = node.parameters[0];
|
|
416
|
+
if (ts.isIdentifier(p0.name) && node.body) {
|
|
417
|
+
// Inner walk over the body — collect property chains rooted at
|
|
418
|
+
// this accessor's param, AND chase view-helper call sites.
|
|
419
|
+
walkAccessorBody(node.body, p0.name.text, paths, checker, visitedHelpers);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
ts.forEachChild(node, (child) => visit(child, paramName));
|
|
423
|
+
};
|
|
424
|
+
visit(focalFile, undefined);
|
|
425
|
+
return paths;
|
|
426
|
+
}
|
|
427
|
+
function walkAccessorBody(body, paramName, paths, checker, visitedHelpers) {
|
|
428
|
+
const visit = (node) => {
|
|
429
|
+
// Property-chain extraction (mirrors collect-deps' depth-2 normaliser).
|
|
430
|
+
if (ts.isPropertyAccessExpression(node)) {
|
|
431
|
+
const chain = resolveDepth2(node, paramName);
|
|
432
|
+
if (chain)
|
|
433
|
+
paths.add(chain);
|
|
434
|
+
}
|
|
435
|
+
// View-helper call chase. The call may pass the state through to the
|
|
436
|
+
// helper, in which case the helper's own reads contribute to our
|
|
437
|
+
// accessor's read set.
|
|
438
|
+
if (ts.isCallExpression(node)) {
|
|
439
|
+
const callee = node.expression;
|
|
440
|
+
const sym = resolveAliasedSymbol(callee, checker);
|
|
441
|
+
if (sym) {
|
|
442
|
+
const cls = classifyViewHelper(sym, checker);
|
|
443
|
+
if (cls.kind === 'walked') {
|
|
444
|
+
const decl = sym.getDeclarations()?.find(isFunctionLikeDecl);
|
|
445
|
+
if (decl && !visitedHelpers.has(decl)) {
|
|
446
|
+
visitedHelpers.add(decl);
|
|
447
|
+
descendIntoHelper(decl, node, paramName, paths, checker, visitedHelpers);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
ts.forEachChild(node, visit);
|
|
453
|
+
};
|
|
454
|
+
visit(body);
|
|
455
|
+
}
|
|
456
|
+
function descendIntoHelper(decl, callSite, outerParamName, paths, checker, visitedHelpers) {
|
|
457
|
+
// Match each parameter to its argument at the call site. For accessor
|
|
458
|
+
// arguments `(t) => t.foo`, the helper's body reads `t` which is our
|
|
459
|
+
// outer state slice. Track this so reads inside the helper get
|
|
460
|
+
// attributed to our state shape.
|
|
461
|
+
const fnDecl = decl;
|
|
462
|
+
if (!fnDecl.body)
|
|
463
|
+
return;
|
|
464
|
+
const params = fnDecl.parameters;
|
|
465
|
+
for (let i = 0; i < params.length; i++) {
|
|
466
|
+
const param = params[i];
|
|
467
|
+
const arg = callSite.arguments[i];
|
|
468
|
+
if (!arg)
|
|
469
|
+
continue;
|
|
470
|
+
if (!ts.isIdentifier(param.name))
|
|
471
|
+
continue;
|
|
472
|
+
if (ts.isArrowFunction(arg) || ts.isFunctionExpression(arg)) {
|
|
473
|
+
const a0 = arg.parameters[0];
|
|
474
|
+
if (a0 && ts.isIdentifier(a0.name) && arg.body) {
|
|
475
|
+
// The arg accessor binds the helper's lift; walk it under the
|
|
476
|
+
// outer paramName so paths chain into our state.
|
|
477
|
+
walkAccessorBody(arg.body, a0.name.text, paths, checker, visitedHelpers);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
else if (ts.isIdentifier(arg) && arg.text === outerParamName) {
|
|
481
|
+
// The helper is called with our state directly — recurse into its
|
|
482
|
+
// body under the helper's parameter name.
|
|
483
|
+
walkAccessorBody(fnDecl.body, param.name.text, paths, checker, visitedHelpers);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
function resolveAliasedSymbol(node, checker) {
|
|
488
|
+
let sym = checker.getSymbolAtLocation(node);
|
|
489
|
+
if (!sym)
|
|
490
|
+
return undefined;
|
|
491
|
+
if (sym.flags & ts.SymbolFlags.Alias)
|
|
492
|
+
sym = checker.getAliasedSymbol(sym);
|
|
493
|
+
if (sym.flags & ts.SymbolFlags.Transient)
|
|
494
|
+
return undefined;
|
|
495
|
+
return sym;
|
|
496
|
+
}
|
|
497
|
+
function resolveDepth2(node, paramName) {
|
|
498
|
+
const parts = [];
|
|
499
|
+
let current = node;
|
|
500
|
+
while (ts.isPropertyAccessExpression(current)) {
|
|
501
|
+
parts.unshift(current.name.text);
|
|
502
|
+
current = current.expression;
|
|
503
|
+
}
|
|
504
|
+
if (!ts.isIdentifier(current) || current.text !== paramName)
|
|
505
|
+
return null;
|
|
506
|
+
if (parts.length === 0)
|
|
507
|
+
return null;
|
|
508
|
+
return parts.slice(0, 2).join('.');
|
|
509
|
+
}
|
|
510
|
+
function isFunctionLikeDecl(d) {
|
|
511
|
+
return (ts.isFunctionDeclaration(d) ||
|
|
512
|
+
ts.isFunctionExpression(d) ||
|
|
513
|
+
ts.isArrowFunction(d) ||
|
|
514
|
+
ts.isMethodDeclaration(d));
|
|
515
|
+
}
|
|
516
|
+
//# sourceMappingURL=cross-file-walker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cross-file-walker.js","sourceRoot":"","sources":["../src/cross-file-walker.ts"],"names":[],"mappings":"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","sourcesContent":["// Cross-file walker — v2b prototype (Phase 1).\n//\n// Classifies every call expression in a TypeScript Program against the\n// §2.1 view-helper resolution rule. Returns diagnostics + a per-callsite\n// classification trace.\n//\n// This is *prototype-grade*: no manifest consumption, no incremental cache,\n// no reverse-deps tracking. Phase 3 adds those layers on top.\n//\n// The rule (§2.1):\n// A call is a view-helper iff at least one of:\n// case 1 — the callee accepts a parameter assignable to View<S,M> or\n// one of the documented structural subsets (send-only,\n// text-only, send+text, send+show+each+branch).\n// case 2 — the callee's *declared* return type is assignable to\n// Node, Node[], Node|undefined, or ReadonlyArray<Node>.\n// case 3 — the callee has a `/** @llui-helper */` JSDoc tag.\n//\n// Async helpers (declared return Promise<Node[]>) are NOT view-helpers\n// and produce `llui/async-view-helper` (hard error).\n//\n// Everything else: opaque. Emits `llui/opaque-view-call` if the call site\n// is structurally a view position (its result flows into a view-returning\n// expression); otherwise the call is uninteresting and not reported.\n\nimport ts from 'typescript'\nimport {\n type Diagnostic,\n type DiagnosticCategory,\n type DiagnosticSeverity,\n rangeFromOffsets,\n relativizeFile,\n} from './diagnostic.js'\n\nexport type ViewHelperKind = 'walked' | 'opaque' | 'async' | 'not-a-helper'\n\nexport interface ViewHelperClassification {\n kind: ViewHelperKind\n /** Which §2.1 case fired. Only populated when kind === 'walked'. */\n cases: Array<1 | 2 | 3>\n /** Human-readable reason. */\n reason: string\n}\n\nexport type DiagnosticId = 'llui/opaque-view-call' | 'llui/async-view-helper' | 'llui/helper-cycle'\n\nexport interface WalkerDiagnostic {\n id: DiagnosticId\n file: string\n pos: number\n end: number\n message: string\n helperName: string | undefined\n}\n\nexport interface WalkerResult {\n diagnostics: WalkerDiagnostic[]\n /** Per-file counts for telemetry. */\n perFile: Map<\n string,\n {\n callsClassified: number\n walked: number\n opaque: number\n async: number\n notAHelper: number\n }\n >\n}\n\n/**\n * Classify the symbol's declaration against the §2.1 rule.\n *\n * Operates on the *declared* type (`getTypeOfSymbolAtLocation(symbol,\n * symbol.declarations[0])`), not the inferred-at-call-site type. This is\n * load-bearing: TypeScript inference at call sites widens to union\n * shapes (`Node[] | undefined`, `JSX.Element | string`) that miss\n * assignability for case 2. The rule's intent is \"did the author commit\n * to a view-helper signature in the declaration\" — inference-narrowed\n * types don't satisfy that intent.\n */\nexport function classifyViewHelper(\n symbol: ts.Symbol,\n checker: ts.TypeChecker,\n): ViewHelperClassification {\n const decls = symbol.getDeclarations() ?? []\n const decl = decls.find(isCallableDeclaration)\n if (!decl) return { kind: 'not-a-helper', cases: [], reason: 'no callable declaration found' }\n\n const cases: Array<1 | 2 | 3> = []\n const reasons: string[] = []\n\n // Case 3: @llui-helper JSDoc tag. Cheap to check, decisive when present.\n if (hasLluiHelperTag(decl)) {\n cases.push(3)\n reasons.push('@llui-helper tag')\n }\n\n // Get the signature's parameters + return type from the *declaration*.\n const declType = checker.getTypeOfSymbolAtLocation(symbol, decl)\n const sigs = declType.getCallSignatures()\n if (sigs.length === 0) {\n if (cases.includes(3)) {\n return { kind: 'walked', cases, reason: reasons.join('; ') }\n }\n return { kind: 'not-a-helper', cases: [], reason: 'declared type has no call signature' }\n }\n // Use the first signature. Overloaded helpers are rare in view code;\n // production walker should iterate all.\n const sig = sigs[0]!\n\n // Case 2: declared return type assignable to Node / Node[] / etc.\n const returnType = checker.getReturnTypeOfSignature(sig)\n const asyncMatch = returnTypeIsAsyncNode(returnType, checker)\n if (asyncMatch) {\n return { kind: 'async', cases: [], reason: 'declared return is Promise<Node[] | Node>' }\n }\n if (returnTypeIsNodeShape(returnType, checker)) {\n cases.push(2)\n reasons.push('declared return is Node[]-like')\n }\n\n // Case 1: at least one parameter assignable to View<S,M> or a documented\n // structural subset. Match on the declared parameter type's shape — we\n // use property-name plus per-property callability as a structural\n // signature. The 5 documented subsets are listed below.\n for (const param of sig.getParameters()) {\n const paramDecl = param.getDeclarations()?.[0]\n if (!paramDecl) continue\n const paramType = checker.getTypeOfSymbolAtLocation(param, paramDecl)\n const subset = matchViewSubset(paramType, checker)\n if (subset) {\n cases.push(1)\n reasons.push(`accepts View subset (${subset})`)\n break\n }\n }\n\n if (cases.length === 0) {\n return { kind: 'opaque', cases: [], reason: 'no §2.1 case matched' }\n }\n return { kind: 'walked', cases, reason: reasons.join('; ') }\n}\n\n/**\n * The 5 documented View subsets, enumerated. New subsets require a doc\n * revision and a fixture per §2.1.\n *\n * Returns the subset's identifier when matched, undefined otherwise.\n * Matching is by *property presence* — we check that the parameter type\n * exposes the required property names, each with a callable type. We\n * intentionally do NOT call `isTypeAssignableTo` against a synthetic\n * subset type, because TypeScript's structural assignability would\n * accept arbitrary supersets and lose the \"documented subset\" guarantee.\n */\nfunction matchViewSubset(t: ts.Type, _checker: ts.TypeChecker): string | undefined {\n if (!isObjectLike(t)) return undefined\n const props = t.getProperties().map((p) => p.getName())\n const has = (name: string): boolean => props.includes(name)\n\n // Full View<S,M> bag — must expose at least send + show + each + branch.\n // Real-world callers spread or destructure; the type they pass is the\n // full View<S,M> regardless of how much they use.\n if (has('send') && has('show') && has('each') && has('branch') && has('text')) {\n return 'View<S, M>'\n }\n // send + show + each + branch (no text)\n if (has('send') && has('show') && has('each') && has('branch') && !has('text')) {\n return '{ send, show, each, branch }'\n }\n // send + text\n if (has('send') && has('text') && !has('show') && !has('each')) {\n return '{ send, text }'\n }\n // send only\n if (has('send') && !has('text') && !has('show') && !has('each')) {\n return '{ send }'\n }\n // text only\n if (has('text') && !has('send') && !has('show') && !has('each')) {\n return '{ text }'\n }\n\n return undefined\n}\n\nfunction isObjectLike(t: ts.Type): boolean {\n return (t.getFlags() & ts.TypeFlags.Object) !== 0\n}\n\n/**\n * Returns true if `t` is assignable to one of: `Node`, `Node[]`,\n * `Node | undefined`, `ReadonlyArray<Node>`.\n *\n * Uses the type checker's structural matching against a synthesized\n * `Node`-like — we look up the global `Node` symbol from the lib.dom\n * declarations.\n */\nfunction returnTypeIsNodeShape(t: ts.Type, checker: ts.TypeChecker): boolean {\n // Strip undefined from a union for the \"Node | undefined\" case.\n const nonUndefined = stripUndefined(t)\n\n // Array / ReadonlyArray case: element type is Node-like.\n // TypeScript represents both as a `TypeReference` whose target is the\n // global Array / ReadonlyArray and whose first type-arg is the element.\n if (isArrayOrReadonlyArray(nonUndefined)) {\n const elem = getFirstTypeArg(nonUndefined)\n if (elem && isNodeLike(elem, checker)) return true\n }\n // Singular Node return: `function (...): Node`.\n if (isNodeLike(nonUndefined, checker)) return true\n // Union: any member is Node-shaped.\n if (nonUndefined.isUnion()) {\n for (const u of nonUndefined.types) {\n if (returnTypeIsNodeShape(u, checker)) return true\n }\n }\n return false\n}\n\nfunction returnTypeIsAsyncNode(t: ts.Type, checker: ts.TypeChecker): boolean {\n // Promise<X> where X is Node-shaped.\n const sym = t.getSymbol()\n if (!sym || sym.getName() !== 'Promise') return false\n const args = (t as ts.TypeReference).typeArguments\n if (!args || args.length === 0) return false\n return returnTypeIsNodeShape(args[0]!, checker)\n}\n\nfunction stripUndefined(t: ts.Type): ts.Type {\n if (!t.isUnion()) return t\n const nonUndef = t.types.filter(\n (u) => (u.getFlags() & (ts.TypeFlags.Undefined | ts.TypeFlags.Void)) === 0,\n )\n if (nonUndef.length === t.types.length) return t\n if (nonUndef.length === 1) return nonUndef[0]!\n return t\n}\n\nfunction isArrayOrReadonlyArray(t: ts.Type): boolean {\n const sym = t.getSymbol()\n if (!sym) return false\n const name = sym.getName()\n return name === 'ReadonlyArray' || name === 'Array'\n}\n\nfunction getFirstTypeArg(t: ts.Type): ts.Type | undefined {\n const args = (t as ts.TypeReference).typeArguments\n return args?.[0]\n}\n\nfunction isNodeLike(t: ts.Type, _checker: ts.TypeChecker): boolean {\n const sym = t.getSymbol()\n if (!sym) return false\n const name = sym.getName()\n // Built-in DOM Node base types — return true on the abstract names.\n if (\n name === 'Node' ||\n name === 'Element' ||\n name === 'HTMLElement' ||\n name === 'Text' ||\n name === 'Comment' ||\n name === 'DocumentFragment' ||\n name === 'ChildNode'\n ) {\n return true\n }\n // The concrete `HTMLDivElement` / `SVGCircleElement` / `MathMLMathElement`\n // names that `HTMLElementTagNameMap[K]` resolves to. Element helpers\n // (`div(...)`, `button(...)`) all hit this branch — without it they\n // would be misclassified as opaque and flood the diagnostic stream.\n return (\n /^HTML[A-Z]\\w*Element$/.test(name) ||\n /^SVG[A-Z]\\w*Element$/.test(name) ||\n /^MathML[A-Z]\\w*Element$/.test(name)\n )\n}\n\nfunction hasLluiHelperTag(decl: ts.Declaration): boolean {\n const jsDocs = ts.getJSDocTags(decl)\n for (const tag of jsDocs) {\n if (tag.tagName.text === 'llui-helper') return true\n }\n return false\n}\n\nfunction isCallableDeclaration(d: ts.Declaration): boolean {\n return (\n ts.isFunctionDeclaration(d) ||\n ts.isFunctionExpression(d) ||\n ts.isArrowFunction(d) ||\n ts.isMethodDeclaration(d) ||\n ts.isVariableDeclaration(d)\n )\n}\n\n/**\n * Whether the call site is structurally in a view position. A view\n * position is one where the result flows into:\n * - the return of a `view()` callback,\n * - a `Node[]` literal element being built by a structural primitive\n * (`each.render`, `show.render`, `branch.cases.X`, `scope.render`),\n * - the children array of an element-helper call.\n *\n * Approximation for the prototype: the call is inside a function whose\n * return type (declared OR inferred from the surrounding context) is\n * Node-shaped. Less precise than tracking JSX-style returns, but\n * sufficient to gate the diagnostic emission for the validation run.\n */\nfunction isViewPositionCall(call: ts.CallExpression, checker: ts.TypeChecker): boolean {\n // Walk up: an enclosing function declaration / arrow whose return is\n // Node-shaped. Stop at the source file.\n let cur: ts.Node | undefined = call.parent\n while (cur) {\n if (\n ts.isFunctionDeclaration(cur) ||\n ts.isFunctionExpression(cur) ||\n ts.isArrowFunction(cur) ||\n ts.isMethodDeclaration(cur)\n ) {\n const sig = checker.getSignatureFromDeclaration(cur)\n if (sig) {\n const ret = checker.getReturnTypeOfSignature(sig)\n if (returnTypeIsNodeShape(ret, checker)) return true\n }\n return false\n }\n cur = cur.parent\n }\n return false\n}\n\n/**\n * Walk a Program looking for call expressions that should be classified\n * by the §2.1 rule. Restricts the walk to files matching `filter` so\n * tests can scope to a subdirectory.\n */\nexport function walkProgram(\n program: ts.Program,\n options: { filter?: (sourceFile: ts.SourceFile) => boolean } = {},\n): WalkerResult {\n const checker = program.getTypeChecker()\n const filter = options.filter ?? (() => true)\n const diagnostics: WalkerDiagnostic[] = []\n const perFile = new Map<\n string,\n WalkerResult['perFile'] extends Map<string, infer V> ? V : never\n >()\n\n for (const sf of program.getSourceFiles()) {\n if (sf.isDeclarationFile) continue\n if (!filter(sf)) continue\n const counts = { callsClassified: 0, walked: 0, opaque: 0, async: 0, notAHelper: 0 }\n perFile.set(sf.fileName, counts)\n\n const visit = (node: ts.Node): void => {\n if (ts.isCallExpression(node)) {\n // Resolve the symbol of the callee.\n const callee = node.expression\n let symbol = checker.getSymbolAtLocation(callee)\n if (symbol && symbol.flags & ts.SymbolFlags.Alias) {\n symbol = checker.getAliasedSymbol(symbol)\n }\n if (symbol && !(symbol.flags & ts.SymbolFlags.Transient)) {\n const cls = classifyViewHelper(symbol, checker)\n counts.callsClassified++\n if (cls.kind === 'walked') counts.walked++\n else if (cls.kind === 'opaque') counts.opaque++\n else if (cls.kind === 'async') counts.async++\n else counts.notAHelper++\n\n if (cls.kind === 'opaque' && isViewPositionCall(node, checker)) {\n diagnostics.push({\n id: 'llui/opaque-view-call',\n file: sf.fileName,\n pos: node.getStart(sf),\n end: node.getEnd(),\n helperName: getCalleeName(callee),\n message: `Call to \"${getCalleeName(callee) ?? '<unknown>'}\" in a view position is opaque to the cross-file walker (${cls.reason}). Either add an explicit return-type annotation (Node[] / Node / ReadonlyArray<Node>), accept a View bag parameter (or a documented subset), or mark with /** @llui-helper */ if the helper genuinely cannot be annotated. As a last resort, use track({ deps: ... }) at the call site.`,\n })\n } else if (cls.kind === 'async' && isViewPositionCall(node, checker)) {\n diagnostics.push({\n id: 'llui/async-view-helper',\n file: sf.fileName,\n pos: node.getStart(sf),\n end: node.getEnd(),\n helperName: getCalleeName(callee),\n message: `Call to \"${getCalleeName(callee) ?? '<unknown>'}\" in a view position returns Promise<Node[] | Node>. LLui's view layer is synchronous — wrap async work in onMount() or use clientOnly() instead.`,\n })\n }\n }\n }\n ts.forEachChild(node, visit)\n }\n visit(sf)\n }\n\n return { diagnostics, perFile }\n}\n\nfunction getCalleeName(expr: ts.Expression): string | undefined {\n if (ts.isIdentifier(expr)) return expr.text\n if (ts.isPropertyAccessExpression(expr)) return expr.name.text\n return undefined\n}\n\n// ── Diagnostic schema integration (v2c §3) ──────────────────────────\n//\n// The walker emits a lightweight `WalkerDiagnostic` (with raw byte\n// offsets) for internal accumulation. Adapters and the host pipeline\n// want the canonical `Diagnostic` shape with project-relative paths\n// and line/column ranges. This converter resolves the position pair to\n// a Range and relativizes the file path; the caller supplies the\n// project root.\n\nconst WALKER_DIAGNOSTIC_META: Record<\n DiagnosticId,\n {\n severity: DiagnosticSeverity\n category: DiagnosticCategory\n documentation?: string\n }\n> = {\n 'llui/opaque-view-call': {\n severity: 'warning',\n category: 'reactivity',\n documentation:\n 'https://github.com/fponticelli/llui/blob/main/docs/proposals/v2-compiler/v2b.md#21-view-helper-resolution-rule',\n },\n 'llui/async-view-helper': {\n severity: 'error',\n category: 'composition',\n documentation:\n 'https://github.com/fponticelli/llui/blob/main/docs/proposals/v2-compiler/v2b.md#21-view-helper-resolution-rule',\n },\n 'llui/helper-cycle': {\n severity: 'warning',\n category: 'composition',\n documentation:\n 'https://github.com/fponticelli/llui/blob/main/docs/proposals/v2-compiler/v2b.md#23-recursion-and-cycles',\n },\n}\n\n/**\n * Convert a walker-internal diagnostic to the canonical `Diagnostic`\n * shape. Reads the source text (for line/column resolution) and a\n * project root (for path relativization).\n */\nexport function toCanonicalDiagnostic(\n d: WalkerDiagnostic,\n sourceText: string,\n projectRoot: string,\n): Diagnostic {\n const meta = WALKER_DIAGNOSTIC_META[d.id]\n return {\n id: d.id,\n severity: meta.severity,\n category: meta.category,\n message: d.message,\n location: {\n file: relativizeFile(d.file, projectRoot),\n range: rangeFromOffsets(sourceText, d.pos, d.end),\n },\n ...(meta.documentation ? { documentation: meta.documentation } : {}),\n }\n}\n\n// ── Cross-file accessor path collection ─────────────────────────────\n//\n// Given a focal source file inside a Program, walk every reactive-accessor\n// arrow in the file. For each accessor:\n// - Collect paths it reads directly (the existing AST-only collector\n// handles this — see `collect-deps.ts`).\n// - For every call site inside the accessor whose callee resolves to a\n// view-helper (per §2.1), descend into the callee and merge its reads.\n//\n// This is the cross-file extension of `collectStatePathsFromSource`. The\n// AST-only collector terminates at the file boundary; the cross-file\n// version follows view-helper calls into other files using the TypeChecker.\n//\n// Used by the focal file's compiler to compute its __prefixes table. The\n// production wiring (Vite adapter builds a Program; compileFile consumes\n// it) is v2c module work — for v2b this is exposed as a callable engine\n// API that downstream tools can drive.\n\n/**\n * Collect the cross-file union of accessor paths read from a focal file.\n * Returns the union over every reactive accessor in `focalFile`, with\n * cross-file view-helper descents merged in.\n */\nexport function crossFileAccessorPaths(program: ts.Program, focalFile: ts.SourceFile): Set<string> {\n const checker = program.getTypeChecker()\n const paths = new Set<string>()\n const visitedHelpers = new Set<ts.Declaration>()\n\n const visit = (node: ts.Node, paramName: string | undefined): void => {\n // Reactive accessor entry: a 1-param arrow or function expression.\n if (\n (ts.isArrowFunction(node) || ts.isFunctionExpression(node)) &&\n node.parameters.length === 1\n ) {\n const p0 = node.parameters[0]!\n if (ts.isIdentifier(p0.name) && node.body) {\n // Inner walk over the body — collect property chains rooted at\n // this accessor's param, AND chase view-helper call sites.\n walkAccessorBody(node.body, p0.name.text, paths, checker, visitedHelpers)\n }\n }\n ts.forEachChild(node, (child) => visit(child, paramName))\n }\n visit(focalFile, undefined)\n return paths\n}\n\nfunction walkAccessorBody(\n body: ts.Node,\n paramName: string,\n paths: Set<string>,\n checker: ts.TypeChecker,\n visitedHelpers: Set<ts.Declaration>,\n): void {\n const visit = (node: ts.Node): void => {\n // Property-chain extraction (mirrors collect-deps' depth-2 normaliser).\n if (ts.isPropertyAccessExpression(node)) {\n const chain = resolveDepth2(node, paramName)\n if (chain) paths.add(chain)\n }\n\n // View-helper call chase. The call may pass the state through to the\n // helper, in which case the helper's own reads contribute to our\n // accessor's read set.\n if (ts.isCallExpression(node)) {\n const callee = node.expression\n const sym = resolveAliasedSymbol(callee, checker)\n if (sym) {\n const cls = classifyViewHelper(sym, checker)\n if (cls.kind === 'walked') {\n const decl = sym.getDeclarations()?.find(isFunctionLikeDecl)\n if (decl && !visitedHelpers.has(decl)) {\n visitedHelpers.add(decl)\n descendIntoHelper(decl, node, paramName, paths, checker, visitedHelpers)\n }\n }\n }\n }\n\n ts.forEachChild(node, visit)\n }\n visit(body)\n}\n\nfunction descendIntoHelper(\n decl: ts.Declaration,\n callSite: ts.CallExpression,\n outerParamName: string,\n paths: Set<string>,\n checker: ts.TypeChecker,\n visitedHelpers: Set<ts.Declaration>,\n): void {\n // Match each parameter to its argument at the call site. For accessor\n // arguments `(t) => t.foo`, the helper's body reads `t` which is our\n // outer state slice. Track this so reads inside the helper get\n // attributed to our state shape.\n const fnDecl = decl as ts.FunctionLikeDeclaration\n if (!fnDecl.body) return\n const params = fnDecl.parameters\n for (let i = 0; i < params.length; i++) {\n const param = params[i]!\n const arg = callSite.arguments[i]\n if (!arg) continue\n if (!ts.isIdentifier(param.name)) continue\n if (ts.isArrowFunction(arg) || ts.isFunctionExpression(arg)) {\n const a0 = arg.parameters[0]\n if (a0 && ts.isIdentifier(a0.name) && arg.body) {\n // The arg accessor binds the helper's lift; walk it under the\n // outer paramName so paths chain into our state.\n walkAccessorBody(arg.body, a0.name.text, paths, checker, visitedHelpers)\n }\n } else if (ts.isIdentifier(arg) && arg.text === outerParamName) {\n // The helper is called with our state directly — recurse into its\n // body under the helper's parameter name.\n walkAccessorBody(fnDecl.body, param.name.text, paths, checker, visitedHelpers)\n }\n }\n}\n\nfunction resolveAliasedSymbol(node: ts.Node, checker: ts.TypeChecker): ts.Symbol | undefined {\n let sym = checker.getSymbolAtLocation(node)\n if (!sym) return undefined\n if (sym.flags & ts.SymbolFlags.Alias) sym = checker.getAliasedSymbol(sym)\n if (sym.flags & ts.SymbolFlags.Transient) return undefined\n return sym\n}\n\nfunction resolveDepth2(node: ts.PropertyAccessExpression, paramName: string): string | null {\n const parts: string[] = []\n let current: ts.Expression = node\n while (ts.isPropertyAccessExpression(current)) {\n parts.unshift(current.name.text)\n current = current.expression\n }\n if (!ts.isIdentifier(current) || current.text !== paramName) return null\n if (parts.length === 0) return null\n return parts.slice(0, 2).join('.')\n}\n\nfunction isFunctionLikeDecl(d: ts.Declaration): boolean {\n return (\n ts.isFunctionDeclaration(d) ||\n ts.isFunctionExpression(d) ||\n ts.isArrowFunction(d) ||\n ts.isMethodDeclaration(d)\n )\n}\n"]}
|