@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,115 @@
|
|
|
1
|
+
// `each-memo` — wraps `each({ items: (s) => ... })` accessor in
|
|
2
|
+
// `memo(accessor, mask)` when the accessor allocates a new array on
|
|
3
|
+
// every call (filter/map/slice/sort/etc.). Each's runtime same-ref
|
|
4
|
+
// fast path handles non-allocating accessors (`(s) => s.items`)
|
|
5
|
+
// already, so wrap is gated on `accessorAllocatesArray`.
|
|
6
|
+
//
|
|
7
|
+
// Fires top-down (transformCallEnter) — wraps the each() call's
|
|
8
|
+
// items accessor before children are visited. Subsequent passes
|
|
9
|
+
// (dedup, mask-injection, the inline element rewrites still in
|
|
10
|
+
// transform.ts) see the wrapped form, which is structurally
|
|
11
|
+
// transparent: `memo(...)` is a CallExpression that still resolves
|
|
12
|
+
// to the same logical accessor at runtime.
|
|
13
|
+
//
|
|
14
|
+
// The module signals back via a per-file slot (`EACH_MEMO_SLOT`)
|
|
15
|
+
// when at least one wrap occurred. The umbrella reads it to decide
|
|
16
|
+
// whether `cleanupImports` must add `memo` to the @llui/dom imports.
|
|
17
|
+
import ts from 'typescript';
|
|
18
|
+
import { computeAccessorMask, createMaskLiteral, isHelperCall } from '../transform.js';
|
|
19
|
+
export const EACH_MEMO_SLOT = 'each-memo:state';
|
|
20
|
+
export function eachMemoModule(options) {
|
|
21
|
+
const { fieldBits, viewHelperNames, viewHelperAliases } = options;
|
|
22
|
+
return {
|
|
23
|
+
name: 'each-memo',
|
|
24
|
+
compilerVersion: '^0.3.0',
|
|
25
|
+
diagnostics: [],
|
|
26
|
+
visitors: {},
|
|
27
|
+
transformCallEnter(ctx, node) {
|
|
28
|
+
if (!isHelperCall(node.expression, 'each', viewHelperNames, viewHelperAliases))
|
|
29
|
+
return null;
|
|
30
|
+
const arg = node.arguments[0];
|
|
31
|
+
if (!arg || !ts.isObjectLiteralExpression(arg))
|
|
32
|
+
return null;
|
|
33
|
+
let itemsProp = null;
|
|
34
|
+
for (const prop of arg.properties) {
|
|
35
|
+
if (ts.isPropertyAssignment(prop) &&
|
|
36
|
+
ts.isIdentifier(prop.name) &&
|
|
37
|
+
prop.name.text === 'items') {
|
|
38
|
+
itemsProp = prop;
|
|
39
|
+
break;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
if (!itemsProp)
|
|
43
|
+
return null;
|
|
44
|
+
const accessor = itemsProp.initializer;
|
|
45
|
+
if (!ts.isArrowFunction(accessor) && !ts.isFunctionExpression(accessor))
|
|
46
|
+
return null;
|
|
47
|
+
// Only wrap allocating accessors — each's same-ref fast path
|
|
48
|
+
// handles `(s) => s.items` correctly without memo.
|
|
49
|
+
if (!accessorAllocatesArray(accessor.body))
|
|
50
|
+
return null;
|
|
51
|
+
const { mask, readsState } = computeAccessorMask(accessor, fieldBits);
|
|
52
|
+
if (mask === 0 && !readsState)
|
|
53
|
+
return null; // constant, nothing to memoize
|
|
54
|
+
const finalMask = mask === 0 && readsState ? 0xffffffff | 0 : mask;
|
|
55
|
+
const f = ctx.factory;
|
|
56
|
+
const wrapped = f.createCallExpression(f.createIdentifier('memo'), undefined, [
|
|
57
|
+
accessor,
|
|
58
|
+
createMaskLiteral(f, finalMask),
|
|
59
|
+
]);
|
|
60
|
+
const newProps = arg.properties.map((p) => p === itemsProp ? f.createPropertyAssignment('items', wrapped) : p);
|
|
61
|
+
const newArg = f.createObjectLiteralExpression(newProps, true);
|
|
62
|
+
const slot = ctx.analysis.perModule.get(EACH_MEMO_SLOT);
|
|
63
|
+
if (slot)
|
|
64
|
+
slot.usesMemo = true;
|
|
65
|
+
else
|
|
66
|
+
ctx.analysis.perModule.set(EACH_MEMO_SLOT, { usesMemo: true });
|
|
67
|
+
return f.createCallExpression(node.expression, node.typeArguments, [
|
|
68
|
+
newArg,
|
|
69
|
+
...node.arguments.slice(1),
|
|
70
|
+
]);
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
const ALLOCATING_METHODS = new Set([
|
|
75
|
+
'filter',
|
|
76
|
+
'map',
|
|
77
|
+
'slice',
|
|
78
|
+
'sort',
|
|
79
|
+
'reverse',
|
|
80
|
+
'concat',
|
|
81
|
+
'flat',
|
|
82
|
+
'flatMap',
|
|
83
|
+
'reduce',
|
|
84
|
+
]);
|
|
85
|
+
function accessorAllocatesArray(body) {
|
|
86
|
+
let found = false;
|
|
87
|
+
function walk(n) {
|
|
88
|
+
if (found)
|
|
89
|
+
return;
|
|
90
|
+
if (ts.isCallExpression(n) &&
|
|
91
|
+
ts.isPropertyAccessExpression(n.expression) &&
|
|
92
|
+
ts.isIdentifier(n.expression.name) &&
|
|
93
|
+
ALLOCATING_METHODS.has(n.expression.name.text)) {
|
|
94
|
+
found = true;
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
if (ts.isArrayLiteralExpression(n) && n.elements.some((el) => ts.isSpreadElement(el))) {
|
|
98
|
+
found = true;
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
if (ts.isCallExpression(n) &&
|
|
102
|
+
ts.isPropertyAccessExpression(n.expression) &&
|
|
103
|
+
ts.isIdentifier(n.expression.expression) &&
|
|
104
|
+
n.expression.expression.text === 'Array' &&
|
|
105
|
+
ts.isIdentifier(n.expression.name) &&
|
|
106
|
+
n.expression.name.text === 'from') {
|
|
107
|
+
found = true;
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
ts.forEachChild(n, walk);
|
|
111
|
+
}
|
|
112
|
+
walk(body);
|
|
113
|
+
return found;
|
|
114
|
+
}
|
|
115
|
+
//# sourceMappingURL=each-memo.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"each-memo.js","sourceRoot":"","sources":["../../src/modules/each-memo.ts"],"names":[],"mappings":"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","sourcesContent":["// `each-memo` — wraps `each({ items: (s) => ... })` accessor in\n// `memo(accessor, mask)` when the accessor allocates a new array on\n// every call (filter/map/slice/sort/etc.). Each's runtime same-ref\n// fast path handles non-allocating accessors (`(s) => s.items`)\n// already, so wrap is gated on `accessorAllocatesArray`.\n//\n// Fires top-down (transformCallEnter) — wraps the each() call's\n// items accessor before children are visited. Subsequent passes\n// (dedup, mask-injection, the inline element rewrites still in\n// transform.ts) see the wrapped form, which is structurally\n// transparent: `memo(...)` is a CallExpression that still resolves\n// to the same logical accessor at runtime.\n//\n// The module signals back via a per-file slot (`EACH_MEMO_SLOT`)\n// when at least one wrap occurred. The umbrella reads it to decide\n// whether `cleanupImports` must add `memo` to the @llui/dom imports.\n\nimport ts from 'typescript'\nimport type { CompilerModule } from '../module.js'\nimport { computeAccessorMask, createMaskLiteral, isHelperCall } from '../transform.js'\n\nexport interface EachMemoModuleOptions {\n fieldBits: Map<string, number>\n /** View-helper names — gates `isHelperCall` on each() under aliased shapes. */\n viewHelperNames: Set<string>\n /** Destructured view-helper aliases (e.g. `{ each: e }` → `e` → `each`). */\n viewHelperAliases: Map<string, string>\n}\n\nexport interface EachMemoSlot {\n /** True when the module wrapped at least one each() items accessor. */\n usesMemo: boolean\n}\n\nexport const EACH_MEMO_SLOT = 'each-memo:state'\n\nexport function eachMemoModule(options: EachMemoModuleOptions): CompilerModule {\n const { fieldBits, viewHelperNames, viewHelperAliases } = options\n return {\n name: 'each-memo',\n compilerVersion: '^0.3.0',\n diagnostics: [],\n visitors: {},\n\n transformCallEnter(ctx, node) {\n if (!isHelperCall(node.expression, 'each', viewHelperNames, viewHelperAliases)) return null\n const arg = node.arguments[0]\n if (!arg || !ts.isObjectLiteralExpression(arg)) return null\n\n let itemsProp: ts.PropertyAssignment | null = null\n for (const prop of arg.properties) {\n if (\n ts.isPropertyAssignment(prop) &&\n ts.isIdentifier(prop.name) &&\n prop.name.text === 'items'\n ) {\n itemsProp = prop\n break\n }\n }\n if (!itemsProp) return null\n\n const accessor = itemsProp.initializer\n if (!ts.isArrowFunction(accessor) && !ts.isFunctionExpression(accessor)) return null\n\n // Only wrap allocating accessors — each's same-ref fast path\n // handles `(s) => s.items` correctly without memo.\n if (!accessorAllocatesArray(accessor.body)) return null\n\n const { mask, readsState } = computeAccessorMask(accessor, fieldBits)\n if (mask === 0 && !readsState) return null // constant, nothing to memoize\n const finalMask = mask === 0 && readsState ? 0xffffffff | 0 : mask\n\n const f = ctx.factory\n const wrapped = f.createCallExpression(f.createIdentifier('memo'), undefined, [\n accessor,\n createMaskLiteral(f, finalMask),\n ])\n const newProps = arg.properties.map((p) =>\n p === itemsProp ? f.createPropertyAssignment('items', wrapped) : p,\n )\n const newArg = f.createObjectLiteralExpression(newProps, true)\n const slot = ctx.analysis.perModule.get(EACH_MEMO_SLOT) as EachMemoSlot | undefined\n if (slot) slot.usesMemo = true\n else ctx.analysis.perModule.set(EACH_MEMO_SLOT, { usesMemo: true } as EachMemoSlot)\n return f.createCallExpression(node.expression, node.typeArguments, [\n newArg,\n ...node.arguments.slice(1),\n ])\n },\n }\n}\n\nconst ALLOCATING_METHODS = new Set([\n 'filter',\n 'map',\n 'slice',\n 'sort',\n 'reverse',\n 'concat',\n 'flat',\n 'flatMap',\n 'reduce',\n])\n\nfunction accessorAllocatesArray(body: ts.ConciseBody | ts.Expression): boolean {\n let found = false\n function walk(n: ts.Node): void {\n if (found) return\n if (\n ts.isCallExpression(n) &&\n ts.isPropertyAccessExpression(n.expression) &&\n ts.isIdentifier(n.expression.name) &&\n ALLOCATING_METHODS.has(n.expression.name.text)\n ) {\n found = true\n return\n }\n if (ts.isArrayLiteralExpression(n) && n.elements.some((el) => ts.isSpreadElement(el))) {\n found = true\n return\n }\n if (\n ts.isCallExpression(n) &&\n ts.isPropertyAccessExpression(n.expression) &&\n ts.isIdentifier(n.expression.expression) &&\n n.expression.expression.text === 'Array' &&\n ts.isIdentifier(n.expression.name) &&\n n.expression.name.text === 'from'\n ) {\n found = true\n return\n }\n ts.forEachChild(n, walk)\n }\n walk(body)\n return found\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"effect-without-handler.d.ts","sourceRoot":"","sources":["../../src/modules/effect-without-handler.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AA6ClD,wBAAgB,0BAA0B,IAAI,cAAc,CA6C3D"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
// `effect-without-handler` — errors when a component's update returns
|
|
2
|
+
// effects (a non-empty second array in the `[state, effects]` tuple)
|
|
3
|
+
// but the component declares no `onEffect` handler. The runtime only
|
|
4
|
+
// handles `delay` and `log` automatically; any other effect is silently
|
|
5
|
+
// dropped. Migrated from
|
|
6
|
+
// `@llui/eslint-plugin/src/rules/effect-without-handler.ts`.
|
|
7
|
+
import ts from 'typescript';
|
|
8
|
+
import { rangeFromOffsets } from '../diagnostic.js';
|
|
9
|
+
import { findComponentCalls } from './_shared.js';
|
|
10
|
+
function getConfigArg(call) {
|
|
11
|
+
const arg = call.arguments[0];
|
|
12
|
+
if (!arg || !ts.isObjectLiteralExpression(arg))
|
|
13
|
+
return undefined;
|
|
14
|
+
return arg;
|
|
15
|
+
}
|
|
16
|
+
function findProp(obj, name) {
|
|
17
|
+
for (const prop of obj.properties) {
|
|
18
|
+
if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name) && prop.name.text === name) {
|
|
19
|
+
return prop;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* True when the function body contains an array literal of length 2
|
|
26
|
+
* whose second element is a non-empty array — the `[state, [effect1, …]]`
|
|
27
|
+
* pattern. We walk the whole body because the return may be inside
|
|
28
|
+
* a switch branch; we don't try to scope to "actually reached" returns,
|
|
29
|
+
* matching the ESLint rule's coarse-but-useful heuristic.
|
|
30
|
+
*/
|
|
31
|
+
function returnsNonEmptyEffects(body) {
|
|
32
|
+
let found = false;
|
|
33
|
+
const walk = (n) => {
|
|
34
|
+
if (found)
|
|
35
|
+
return;
|
|
36
|
+
if (ts.isArrayLiteralExpression(n) && n.elements.length === 2) {
|
|
37
|
+
const second = n.elements[1];
|
|
38
|
+
if (second && ts.isArrayLiteralExpression(second) && second.elements.length > 0) {
|
|
39
|
+
found = true;
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
ts.forEachChild(n, walk);
|
|
44
|
+
};
|
|
45
|
+
walk(body);
|
|
46
|
+
return found;
|
|
47
|
+
}
|
|
48
|
+
export function effectWithoutHandlerModule() {
|
|
49
|
+
return {
|
|
50
|
+
name: 'effect-without-handler',
|
|
51
|
+
compilerVersion: '^0.3.0',
|
|
52
|
+
diagnostics: [
|
|
53
|
+
{
|
|
54
|
+
id: 'llui/effect-without-handler',
|
|
55
|
+
description: 'update() returns effects but the component has no onEffect handler — effects will be silently dropped.',
|
|
56
|
+
},
|
|
57
|
+
],
|
|
58
|
+
visitors: {
|
|
59
|
+
[ts.SyntaxKind.SourceFile]: (ctx, node) => {
|
|
60
|
+
const visited = node;
|
|
61
|
+
const sf = ts.createSourceFile(visited.fileName, visited.text, ts.ScriptTarget.Latest, true);
|
|
62
|
+
for (const call of findComponentCalls(sf)) {
|
|
63
|
+
const config = getConfigArg(call);
|
|
64
|
+
if (!config)
|
|
65
|
+
continue;
|
|
66
|
+
const updateProp = findProp(config, 'update');
|
|
67
|
+
const onEffectProp = findProp(config, 'onEffect');
|
|
68
|
+
if (!updateProp || onEffectProp)
|
|
69
|
+
continue;
|
|
70
|
+
const fn = updateProp.initializer;
|
|
71
|
+
if (!ts.isArrowFunction(fn) && !ts.isFunctionExpression(fn))
|
|
72
|
+
continue;
|
|
73
|
+
if (!fn.body)
|
|
74
|
+
continue;
|
|
75
|
+
if (!returnsNonEmptyEffects(fn.body))
|
|
76
|
+
continue;
|
|
77
|
+
ctx.reportDiagnostic({
|
|
78
|
+
id: 'llui/effect-without-handler',
|
|
79
|
+
severity: 'error',
|
|
80
|
+
category: 'composition',
|
|
81
|
+
message: 'Component returns effects from update() but has no onEffect handler. Only `delay` and `log` are handled by the core runtime; any other effect is silently dropped. Add an `onEffect` handler — typically via `handleEffects<Effect>().on(...).else(...)` — or remove the effect.',
|
|
82
|
+
location: {
|
|
83
|
+
file: sf.fileName,
|
|
84
|
+
range: rangeFromOffsets(sf.text, updateProp.name.getStart(sf), updateProp.name.getEnd()),
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=effect-without-handler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"effect-without-handler.js","sourceRoot":"","sources":["../../src/modules/effect-without-handler.ts"],"names":[],"mappings":"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","sourcesContent":["// `effect-without-handler` — errors when a component's update returns\n// effects (a non-empty second array in the `[state, effects]` tuple)\n// but the component declares no `onEffect` handler. The runtime only\n// handles `delay` and `log` automatically; any other effect is silently\n// dropped. Migrated from\n// `@llui/eslint-plugin/src/rules/effect-without-handler.ts`.\n\nimport ts from 'typescript'\nimport { rangeFromOffsets } from '../diagnostic.js'\nimport type { CompilerModule } from '../module.js'\nimport { findComponentCalls } from './_shared.js'\n\nfunction getConfigArg(call: ts.CallExpression): ts.ObjectLiteralExpression | undefined {\n const arg = call.arguments[0]\n if (!arg || !ts.isObjectLiteralExpression(arg)) return undefined\n return arg\n}\n\nfunction findProp(\n obj: ts.ObjectLiteralExpression,\n name: string,\n): ts.PropertyAssignment | undefined {\n for (const prop of obj.properties) {\n if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name) && prop.name.text === name) {\n return prop\n }\n }\n return undefined\n}\n\n/**\n * True when the function body contains an array literal of length 2\n * whose second element is a non-empty array — the `[state, [effect1, …]]`\n * pattern. We walk the whole body because the return may be inside\n * a switch branch; we don't try to scope to \"actually reached\" returns,\n * matching the ESLint rule's coarse-but-useful heuristic.\n */\nfunction returnsNonEmptyEffects(body: ts.Node): boolean {\n let found = false\n const walk = (n: ts.Node): void => {\n if (found) return\n if (ts.isArrayLiteralExpression(n) && n.elements.length === 2) {\n const second = n.elements[1]\n if (second && ts.isArrayLiteralExpression(second) && second.elements.length > 0) {\n found = true\n return\n }\n }\n ts.forEachChild(n, walk)\n }\n walk(body)\n return found\n}\n\nexport function effectWithoutHandlerModule(): CompilerModule {\n return {\n name: 'effect-without-handler',\n compilerVersion: '^0.3.0',\n diagnostics: [\n {\n id: 'llui/effect-without-handler',\n description:\n 'update() returns effects but the component has no onEffect handler — effects will be silently dropped.',\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 config = getConfigArg(call)\n if (!config) continue\n const updateProp = findProp(config, 'update')\n const onEffectProp = findProp(config, 'onEffect')\n if (!updateProp || onEffectProp) continue\n const fn = updateProp.initializer\n if (!ts.isArrowFunction(fn) && !ts.isFunctionExpression(fn)) continue\n if (!fn.body) continue\n if (!returnsNonEmptyEffects(fn.body)) continue\n\n ctx.reportDiagnostic({\n id: 'llui/effect-without-handler',\n severity: 'error',\n category: 'composition',\n message:\n 'Component returns effects from update() but has no onEffect handler. Only `delay` and `log` are handled by the core runtime; any other effect is silently dropped. Add an `onEffect` handler — typically via `handleEffects<Effect>().on(...).else(...)` — or remove the effect.',\n location: {\n file: sf.fileName,\n range: rangeFromOffsets(\n sf.text,\n updateProp.name.getStart(sf),\n updateProp.name.getEnd(),\n ),\n },\n })\n }\n },\n },\n }\n}\n"]}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { CompilerModule } from '../module.js';
|
|
2
|
+
export interface ElementRewriteModuleOptions {
|
|
3
|
+
/** localName → originalName for element-helper imports (alias-aware). */
|
|
4
|
+
importedHelpers: Map<string, string>;
|
|
5
|
+
fieldBits: Map<string, number>;
|
|
6
|
+
fieldBitsHi: Map<string, number>;
|
|
7
|
+
}
|
|
8
|
+
export interface ElementRewriteSlot {
|
|
9
|
+
/** Helpers whose call sites the module successfully rewrote. */
|
|
10
|
+
compiled: Set<string>;
|
|
11
|
+
/** Helpers that bailed (kept their import — runtime falls back). */
|
|
12
|
+
bailed: Set<string>;
|
|
13
|
+
/** Module emitted at least one `elSplit(...)` call. */
|
|
14
|
+
usesElSplit: boolean;
|
|
15
|
+
/** Module emitted at least one `elTemplate(...)` call. */
|
|
16
|
+
usesElTemplate: boolean;
|
|
17
|
+
/** Module emitted at least one `__cloneStaticTemplate(...)` call. */
|
|
18
|
+
usesCloneStaticTemplate: boolean;
|
|
19
|
+
}
|
|
20
|
+
export declare const ELEMENT_REWRITE_SLOT = "element-rewrite:state";
|
|
21
|
+
export declare function elementRewriteModule(options: ElementRewriteModuleOptions): CompilerModule;
|
|
22
|
+
//# sourceMappingURL=element-rewrite.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"element-rewrite.d.ts","sourceRoot":"","sources":["../../src/modules/element-rewrite.ts"],"names":[],"mappings":"AAgCA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAMlD,MAAM,WAAW,2BAA2B;IAC1C,yEAAyE;IACzE,eAAe,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACpC,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC9B,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CACjC;AAED,MAAM,WAAW,kBAAkB;IACjC,gEAAgE;IAChE,QAAQ,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;IACrB,oEAAoE;IACpE,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;IACnB,uDAAuD;IACvD,WAAW,EAAE,OAAO,CAAA;IACpB,0DAA0D;IAC1D,cAAc,EAAE,OAAO,CAAA;IACvB,qEAAqE;IACrE,uBAAuB,EAAE,OAAO,CAAA;CACjC;AAED,eAAO,MAAM,oBAAoB,0BAA0B,CAAA;AAE3D,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,2BAA2B,GAAG,cAAc,CAyCzF"}
|