@abide/abide 0.50.0 → 0.51.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/AGENTS.md +580 -459
- package/CHANGELOG.md +16 -0
- package/README.md +173 -205
- package/package.json +1 -1
- package/src/lib/ui/compile/compileShadow.ts +49 -11
- package/src/lib/ui/compile/lowerScript.ts +18 -0
- package/src/lib/ui/compile/wrapReactionCellSources.ts +85 -0
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import ts from 'typescript'
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
Folds a cell-source `watch(source, handler)` into the auto-tracked thunk form
|
|
5
|
+
`watch(() => (handler)(source))`, so the reaction reads the cell reactively
|
|
6
|
+
instead of receiving a one-time value.
|
|
7
|
+
|
|
8
|
+
The plain-variable ergonomic makes a bare `count` a VALUE read everywhere the
|
|
9
|
+
read-lowering runs (`renameSignalRefs` → `docAccessTransformer`). That is right
|
|
10
|
+
for `{count}` / `count + 1`, but it also lowered `watch(count, handler)`'s source
|
|
11
|
+
to `$$model.read("count")` — a number — so the runtime `watch` (which expects a
|
|
12
|
+
`State` and reads `.value` inside an effect) never subscribed: the reaction was
|
|
13
|
+
inert. The `watch(count, n => …)` form was documented but silently dead.
|
|
14
|
+
|
|
15
|
+
This runs BEFORE the read-lowering, so the `source` moved inside the new thunk is
|
|
16
|
+
lowered to its normal reactive read there — one rewrite covers every cell kind
|
|
17
|
+
(doc-slot `state`, `.value` cell, `computed`, `linked`), because it reuses the
|
|
18
|
+
read the rest of the pipeline already emits. The runtime `watch(thunk)` is the
|
|
19
|
+
same auto-tracked effect the compiler's own bindings use, so semantics match the
|
|
20
|
+
old `effect(() => handler(cell.value))` cell branch exactly.
|
|
21
|
+
|
|
22
|
+
Only a bare cell reference (or an array literal of them — the `watch([a, b], …)`
|
|
23
|
+
form) is folded. A socket / rpc / arbitrary object source is left untouched so the
|
|
24
|
+
runtime's own source dispatch (`cache.on` / `reactToRpc`) still handles it; the
|
|
25
|
+
thunk (`watch(() => …)`) and rpc-with-args (`watch(fn, args, handler)`) forms are
|
|
26
|
+
already correct and are matched out by arity.
|
|
27
|
+
*/
|
|
28
|
+
export function wrapReactionCellSources(
|
|
29
|
+
cellNames: ReadonlySet<string>,
|
|
30
|
+
watchLocalNames: ReadonlySet<string>,
|
|
31
|
+
): ts.TransformerFactory<ts.SourceFile> {
|
|
32
|
+
/* A source that names a reactive cell: a bare cell identifier, or a non-empty array
|
|
33
|
+
literal whose every element is one (the multi-cell `watch([a, b], …)` form). */
|
|
34
|
+
function isCellSource(source: ts.Expression): boolean {
|
|
35
|
+
if (ts.isIdentifier(source)) {
|
|
36
|
+
return cellNames.has(source.text)
|
|
37
|
+
}
|
|
38
|
+
if (ts.isArrayLiteralExpression(source)) {
|
|
39
|
+
return (
|
|
40
|
+
source.elements.length > 0 &&
|
|
41
|
+
source.elements.every(
|
|
42
|
+
(element) => ts.isIdentifier(element) && cellNames.has(element.text),
|
|
43
|
+
)
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
return false
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return (context) => (root) => {
|
|
50
|
+
function visit(node: ts.Node): ts.Node {
|
|
51
|
+
const [sourceArg, handlerArg] = ts.isCallExpression(node) ? node.arguments : []
|
|
52
|
+
if (
|
|
53
|
+
ts.isCallExpression(node) &&
|
|
54
|
+
ts.isIdentifier(node.expression) &&
|
|
55
|
+
watchLocalNames.has(node.expression.text) &&
|
|
56
|
+
node.arguments.length === 2 &&
|
|
57
|
+
sourceArg !== undefined &&
|
|
58
|
+
handlerArg !== undefined &&
|
|
59
|
+
isCellSource(sourceArg)
|
|
60
|
+
) {
|
|
61
|
+
/* Visit the operands first — a handler body may itself contain a nested cell-source
|
|
62
|
+
watch, and the source stays a plain identifier the read-lowering rewrites next. */
|
|
63
|
+
const source = ts.visitNode(sourceArg, visit) as ts.Expression
|
|
64
|
+
const handler = ts.visitNode(handlerArg, visit) as ts.Expression
|
|
65
|
+
/* `(handler)(source)` — parenthesise the handler so an arrow callee prints callable. */
|
|
66
|
+
const call = ts.factory.createCallExpression(
|
|
67
|
+
ts.factory.createParenthesizedExpression(handler),
|
|
68
|
+
undefined,
|
|
69
|
+
[source],
|
|
70
|
+
)
|
|
71
|
+
const thunk = ts.factory.createArrowFunction(
|
|
72
|
+
undefined,
|
|
73
|
+
undefined,
|
|
74
|
+
[],
|
|
75
|
+
undefined,
|
|
76
|
+
ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken),
|
|
77
|
+
call,
|
|
78
|
+
)
|
|
79
|
+
return ts.factory.createCallExpression(node.expression, undefined, [thunk])
|
|
80
|
+
}
|
|
81
|
+
return ts.visitEachChild(node, visit, context)
|
|
82
|
+
}
|
|
83
|
+
return ts.visitNode(root, visit) as ts.SourceFile
|
|
84
|
+
}
|
|
85
|
+
}
|