@llui/vite-plugin 0.0.39 → 0.0.41
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/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/collect-deps.d.ts +15 -8
- package/dist/collect-deps.d.ts.map +1 -1
- package/dist/collect-deps.js +65 -32
- package/dist/collect-deps.js.map +1 -1
- package/dist/transform.d.ts.map +1 -1
- package/dist/transform.js +179 -112
- package/dist/transform.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import ts from 'typescript';
|
|
2
|
+
/**
|
|
3
|
+
* Helpers for resolving identifier references at reactive-accessor
|
|
4
|
+
* positions. Shared by `transform.ts` (compile-time prop classification
|
|
5
|
+
* + Pass 2 mask injection) and `collect-deps.ts` (state-path scanning
|
|
6
|
+
* for `__dirty` / `__maskLegend`).
|
|
7
|
+
*
|
|
8
|
+
* The compiler must distinguish the legitimate accessor shapes:
|
|
9
|
+
*
|
|
10
|
+
* - Inline arrow / function expression at the call site
|
|
11
|
+
* - Inline `memo(arrow)` at the call site
|
|
12
|
+
* - Identifier referencing a const-bound arrow / function expression
|
|
13
|
+
* - Identifier referencing a hoisted function declaration
|
|
14
|
+
* - Identifier referencing `const x = memo(arrow)`
|
|
15
|
+
*
|
|
16
|
+
* …from values we can't classify (imports, parameters, opaque calls), so
|
|
17
|
+
* those can be bailed-to-runtime instead of silently miscompiled. See the
|
|
18
|
+
* `disabled` binding bug, where a function reference at a reactive prop
|
|
19
|
+
* position was statically assigned (`__e.disabled = isGated`) — writing
|
|
20
|
+
* the function object onto the boolean DOM property and never re-evaluating.
|
|
21
|
+
*/
|
|
22
|
+
/**
|
|
23
|
+
* Walk parent chains to find a `const X = ...` declaration matching
|
|
24
|
+
* `use.text`, or a hoisted `function X(...)` declaration. Returns the
|
|
25
|
+
* resolved declaration or `null` for unresolvable references (imports,
|
|
26
|
+
* parameters, this-bindings, etc.).
|
|
27
|
+
*
|
|
28
|
+
* Limitations:
|
|
29
|
+
* - Only `const`. `let` resolution is unsafe — we can't track later
|
|
30
|
+
* reassignments without a type checker.
|
|
31
|
+
* - Only single-binding declarations (`const a = …`, not `const a = …, b = …`).
|
|
32
|
+
* - The declaration must dominate the use (lexical scope).
|
|
33
|
+
*/
|
|
34
|
+
export declare function resolveLocalConstInitializer(use: ts.Identifier): ts.Expression | ts.FunctionDeclaration | null;
|
|
35
|
+
/**
|
|
36
|
+
* Recognize `memo(arrow)` / `memo(fn)` calls so the inner accessor can
|
|
37
|
+
* be analyzed for state-path masking. The runtime `memo()` returns a
|
|
38
|
+
* cached accessor — its body's reads determine when it re-evaluates,
|
|
39
|
+
* not the call site.
|
|
40
|
+
*/
|
|
41
|
+
export declare function isMemoCallWithArrowArg(expr: ts.Expression): expr is ts.CallExpression & {
|
|
42
|
+
arguments: readonly [ts.ArrowFunction | ts.FunctionExpression, ...ts.Expression[]];
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* Resolve a value at a reactive-accessor position down to the callable
|
|
46
|
+
* AST node we can mask-analyze. Returns `null` when the value isn't a
|
|
47
|
+
* recognized accessor shape — caller leaves the call unchanged (runtime
|
|
48
|
+
* falls back to FULL_MASK, which is correct just slower).
|
|
49
|
+
*
|
|
50
|
+
* Recognized shapes:
|
|
51
|
+
* - `(s) => …` (ArrowFunction)
|
|
52
|
+
* - `function (s) { … }` (FunctionExpression)
|
|
53
|
+
* - `memo((s) => …)` — returns the inner arrow
|
|
54
|
+
* - `someIdentifier` resolving to any of the above (or to a hoisted
|
|
55
|
+
* `function X(s) { … }` declaration)
|
|
56
|
+
*/
|
|
57
|
+
export declare function resolveAccessorBody(value: ts.Expression): ts.ArrowFunction | ts.FunctionExpression | ts.FunctionDeclaration | null;
|
|
58
|
+
//# sourceMappingURL=accessor-resolver.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"accessor-resolver.d.ts","sourceRoot":"","sources":["../src/accessor-resolver.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,YAAY,CAAA;AAE3B;;;;;;;;;;;;;;;;;;;GAmBG;AAEH;;;;;;;;;;;GAWG;AACH,wBAAgB,4BAA4B,CAC1C,GAAG,EAAE,EAAE,CAAC,UAAU,GACjB,EAAE,CAAC,UAAU,GAAG,EAAE,CAAC,mBAAmB,GAAG,IAAI,CA8B/C;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,EAAE,CAAC,UAAU,GAAG,IAAI,IAAI,EAAE,CAAC,cAAc,GAAG;IACvF,SAAS,EAAE,SAAS,CAAC,EAAE,CAAC,aAAa,GAAG,EAAE,CAAC,kBAAkB,EAAE,GAAG,EAAE,CAAC,UAAU,EAAE,CAAC,CAAA;CACnF,CAQA;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,EAAE,CAAC,UAAU,GACnB,EAAE,CAAC,aAAa,GAAG,EAAE,CAAC,kBAAkB,GAAG,EAAE,CAAC,mBAAmB,GAAG,IAAI,CAoB1E"}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import ts from 'typescript';
|
|
2
|
+
/**
|
|
3
|
+
* Helpers for resolving identifier references at reactive-accessor
|
|
4
|
+
* positions. Shared by `transform.ts` (compile-time prop classification
|
|
5
|
+
* + Pass 2 mask injection) and `collect-deps.ts` (state-path scanning
|
|
6
|
+
* for `__dirty` / `__maskLegend`).
|
|
7
|
+
*
|
|
8
|
+
* The compiler must distinguish the legitimate accessor shapes:
|
|
9
|
+
*
|
|
10
|
+
* - Inline arrow / function expression at the call site
|
|
11
|
+
* - Inline `memo(arrow)` at the call site
|
|
12
|
+
* - Identifier referencing a const-bound arrow / function expression
|
|
13
|
+
* - Identifier referencing a hoisted function declaration
|
|
14
|
+
* - Identifier referencing `const x = memo(arrow)`
|
|
15
|
+
*
|
|
16
|
+
* …from values we can't classify (imports, parameters, opaque calls), so
|
|
17
|
+
* those can be bailed-to-runtime instead of silently miscompiled. See the
|
|
18
|
+
* `disabled` binding bug, where a function reference at a reactive prop
|
|
19
|
+
* position was statically assigned (`__e.disabled = isGated`) — writing
|
|
20
|
+
* the function object onto the boolean DOM property and never re-evaluating.
|
|
21
|
+
*/
|
|
22
|
+
/**
|
|
23
|
+
* Walk parent chains to find a `const X = ...` declaration matching
|
|
24
|
+
* `use.text`, or a hoisted `function X(...)` declaration. Returns the
|
|
25
|
+
* resolved declaration or `null` for unresolvable references (imports,
|
|
26
|
+
* parameters, this-bindings, etc.).
|
|
27
|
+
*
|
|
28
|
+
* Limitations:
|
|
29
|
+
* - Only `const`. `let` resolution is unsafe — we can't track later
|
|
30
|
+
* reassignments without a type checker.
|
|
31
|
+
* - Only single-binding declarations (`const a = …`, not `const a = …, b = …`).
|
|
32
|
+
* - The declaration must dominate the use (lexical scope).
|
|
33
|
+
*/
|
|
34
|
+
export function resolveLocalConstInitializer(use) {
|
|
35
|
+
const name = use.text;
|
|
36
|
+
let node = use;
|
|
37
|
+
while (node.parent) {
|
|
38
|
+
const parent = node.parent;
|
|
39
|
+
let statements = null;
|
|
40
|
+
if (ts.isBlock(parent) || ts.isSourceFile(parent) || ts.isModuleBlock(parent)) {
|
|
41
|
+
statements = parent.statements;
|
|
42
|
+
}
|
|
43
|
+
else if (ts.isCaseClause(parent) || ts.isDefaultClause(parent)) {
|
|
44
|
+
statements = parent.statements;
|
|
45
|
+
}
|
|
46
|
+
if (statements) {
|
|
47
|
+
for (const stmt of statements) {
|
|
48
|
+
if (ts.isFunctionDeclaration(stmt)) {
|
|
49
|
+
if (stmt.name && stmt.name.text === name)
|
|
50
|
+
return stmt;
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
if (!ts.isVariableStatement(stmt))
|
|
54
|
+
continue;
|
|
55
|
+
const flags = stmt.declarationList.flags;
|
|
56
|
+
if (!(flags & ts.NodeFlags.Const))
|
|
57
|
+
continue;
|
|
58
|
+
if (stmt.declarationList.declarations.length !== 1)
|
|
59
|
+
continue;
|
|
60
|
+
const decl = stmt.declarationList.declarations[0];
|
|
61
|
+
if (!ts.isIdentifier(decl.name) || decl.name.text !== name)
|
|
62
|
+
continue;
|
|
63
|
+
if (!decl.initializer)
|
|
64
|
+
continue;
|
|
65
|
+
return decl.initializer;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
node = parent;
|
|
69
|
+
}
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Recognize `memo(arrow)` / `memo(fn)` calls so the inner accessor can
|
|
74
|
+
* be analyzed for state-path masking. The runtime `memo()` returns a
|
|
75
|
+
* cached accessor — its body's reads determine when it re-evaluates,
|
|
76
|
+
* not the call site.
|
|
77
|
+
*/
|
|
78
|
+
export function isMemoCallWithArrowArg(expr) {
|
|
79
|
+
return (ts.isCallExpression(expr) &&
|
|
80
|
+
ts.isIdentifier(expr.expression) &&
|
|
81
|
+
expr.expression.text === 'memo' &&
|
|
82
|
+
expr.arguments.length >= 1 &&
|
|
83
|
+
(ts.isArrowFunction(expr.arguments[0]) || ts.isFunctionExpression(expr.arguments[0])));
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Resolve a value at a reactive-accessor position down to the callable
|
|
87
|
+
* AST node we can mask-analyze. Returns `null` when the value isn't a
|
|
88
|
+
* recognized accessor shape — caller leaves the call unchanged (runtime
|
|
89
|
+
* falls back to FULL_MASK, which is correct just slower).
|
|
90
|
+
*
|
|
91
|
+
* Recognized shapes:
|
|
92
|
+
* - `(s) => …` (ArrowFunction)
|
|
93
|
+
* - `function (s) { … }` (FunctionExpression)
|
|
94
|
+
* - `memo((s) => …)` — returns the inner arrow
|
|
95
|
+
* - `someIdentifier` resolving to any of the above (or to a hoisted
|
|
96
|
+
* `function X(s) { … }` declaration)
|
|
97
|
+
*/
|
|
98
|
+
export function resolveAccessorBody(value) {
|
|
99
|
+
if (ts.isArrowFunction(value) || ts.isFunctionExpression(value))
|
|
100
|
+
return value;
|
|
101
|
+
if (isMemoCallWithArrowArg(value)) {
|
|
102
|
+
return value.arguments[0];
|
|
103
|
+
}
|
|
104
|
+
if (ts.isIdentifier(value)) {
|
|
105
|
+
const resolved = resolveLocalConstInitializer(value);
|
|
106
|
+
if (!resolved)
|
|
107
|
+
return null;
|
|
108
|
+
if (ts.isArrowFunction(resolved) ||
|
|
109
|
+
ts.isFunctionExpression(resolved) ||
|
|
110
|
+
ts.isFunctionDeclaration(resolved)) {
|
|
111
|
+
return resolved;
|
|
112
|
+
}
|
|
113
|
+
if (isMemoCallWithArrowArg(resolved)) {
|
|
114
|
+
return resolved.arguments[0];
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
//# sourceMappingURL=accessor-resolver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"accessor-resolver.js","sourceRoot":"","sources":["../src/accessor-resolver.ts"],"names":[],"mappings":"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","sourcesContent":["import ts from 'typescript'\n\n/**\n * Helpers for resolving identifier references at reactive-accessor\n * positions. Shared by `transform.ts` (compile-time prop classification\n * + Pass 2 mask injection) and `collect-deps.ts` (state-path scanning\n * for `__dirty` / `__maskLegend`).\n *\n * The compiler must distinguish the legitimate accessor shapes:\n *\n * - Inline arrow / function expression at the call site\n * - Inline `memo(arrow)` at the call site\n * - Identifier referencing a const-bound arrow / function expression\n * - Identifier referencing a hoisted function declaration\n * - Identifier referencing `const x = memo(arrow)`\n *\n * …from values we can't classify (imports, parameters, opaque calls), so\n * those can be bailed-to-runtime instead of silently miscompiled. See the\n * `disabled` binding bug, where a function reference at a reactive prop\n * position was statically assigned (`__e.disabled = isGated`) — writing\n * the function object onto the boolean DOM property and never re-evaluating.\n */\n\n/**\n * Walk parent chains to find a `const X = ...` declaration matching\n * `use.text`, or a hoisted `function X(...)` declaration. Returns the\n * resolved declaration or `null` for unresolvable references (imports,\n * parameters, this-bindings, etc.).\n *\n * Limitations:\n * - Only `const`. `let` resolution is unsafe — we can't track later\n * reassignments without a type checker.\n * - Only single-binding declarations (`const a = …`, not `const a = …, b = …`).\n * - The declaration must dominate the use (lexical scope).\n */\nexport function resolveLocalConstInitializer(\n use: ts.Identifier,\n): ts.Expression | ts.FunctionDeclaration | null {\n const name = use.text\n let node: ts.Node = use\n while (node.parent) {\n const parent = node.parent\n let statements: readonly ts.Statement[] | null = null\n if (ts.isBlock(parent) || ts.isSourceFile(parent) || ts.isModuleBlock(parent)) {\n statements = parent.statements\n } else if (ts.isCaseClause(parent) || ts.isDefaultClause(parent)) {\n statements = parent.statements\n }\n if (statements) {\n for (const stmt of statements) {\n if (ts.isFunctionDeclaration(stmt)) {\n if (stmt.name && stmt.name.text === name) return stmt\n continue\n }\n if (!ts.isVariableStatement(stmt)) continue\n const flags = stmt.declarationList.flags\n if (!(flags & ts.NodeFlags.Const)) continue\n if (stmt.declarationList.declarations.length !== 1) continue\n const decl = stmt.declarationList.declarations[0]!\n if (!ts.isIdentifier(decl.name) || decl.name.text !== name) continue\n if (!decl.initializer) continue\n return decl.initializer\n }\n }\n node = parent\n }\n return null\n}\n\n/**\n * Recognize `memo(arrow)` / `memo(fn)` calls so the inner accessor can\n * be analyzed for state-path masking. The runtime `memo()` returns a\n * cached accessor — its body's reads determine when it re-evaluates,\n * not the call site.\n */\nexport function isMemoCallWithArrowArg(expr: ts.Expression): expr is ts.CallExpression & {\n arguments: readonly [ts.ArrowFunction | ts.FunctionExpression, ...ts.Expression[]]\n} {\n return (\n ts.isCallExpression(expr) &&\n ts.isIdentifier(expr.expression) &&\n expr.expression.text === 'memo' &&\n expr.arguments.length >= 1 &&\n (ts.isArrowFunction(expr.arguments[0]!) || ts.isFunctionExpression(expr.arguments[0]!))\n )\n}\n\n/**\n * Resolve a value at a reactive-accessor position down to the callable\n * AST node we can mask-analyze. Returns `null` when the value isn't a\n * recognized accessor shape — caller leaves the call unchanged (runtime\n * falls back to FULL_MASK, which is correct just slower).\n *\n * Recognized shapes:\n * - `(s) => …` (ArrowFunction)\n * - `function (s) { … }` (FunctionExpression)\n * - `memo((s) => …)` — returns the inner arrow\n * - `someIdentifier` resolving to any of the above (or to a hoisted\n * `function X(s) { … }` declaration)\n */\nexport function resolveAccessorBody(\n value: ts.Expression,\n): ts.ArrowFunction | ts.FunctionExpression | ts.FunctionDeclaration | null {\n if (ts.isArrowFunction(value) || ts.isFunctionExpression(value)) return value\n if (isMemoCallWithArrowArg(value)) {\n return value.arguments[0] as ts.ArrowFunction | ts.FunctionExpression\n }\n if (ts.isIdentifier(value)) {\n const resolved = resolveLocalConstInitializer(value)\n if (!resolved) return null\n if (\n ts.isArrowFunction(resolved) ||\n ts.isFunctionExpression(resolved) ||\n ts.isFunctionDeclaration(resolved)\n ) {\n return resolved\n }\n if (isMemoCallWithArrowArg(resolved)) {\n return resolved.arguments[0] as ts.ArrowFunction | ts.FunctionExpression\n }\n }\n return null\n}\n"]}
|
package/dist/collect-deps.d.ts
CHANGED
|
@@ -1,16 +1,23 @@
|
|
|
1
1
|
import ts from 'typescript';
|
|
2
2
|
/**
|
|
3
3
|
* Walk the AST and collect every unique state access path referenced by
|
|
4
|
-
* a reactive accessor. A reactive accessor is
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* a reactive accessor. A reactive accessor is one of:
|
|
5
|
+
*
|
|
6
|
+
* - An inline arrow / function expression at a reactive position
|
|
7
|
+
* (`text(s => s.count)`, `div({ title: s => s.title })`,
|
|
8
|
+
* `show({ when: s => s.gated })`, etc.).
|
|
9
|
+
* - An Identifier at a reactive position that resolves to a callable
|
|
10
|
+
* in this file — a const-bound arrow / function expression,
|
|
11
|
+
* a hoisted function declaration, or `const x = memo(arrow)`.
|
|
12
|
+
*
|
|
13
|
+
* The second case lets authors refactor a literal arrow into a named
|
|
14
|
+
* helper without losing the reactive-mask optimization (a precise mask
|
|
15
|
+
* for `__dirty` and structural-primitive `__mask`). Without it, the
|
|
16
|
+
* runtime falls back to FULL_MASK — correct, but every binding fires
|
|
17
|
+
* on every state change.
|
|
8
18
|
*
|
|
9
19
|
* Shared by the bit-assignment path (`collectDeps`, below) and the
|
|
10
|
-
* `diagnostics.ts` bitmask-overflow warning.
|
|
11
|
-
* see the same truth — previously the diagnostics side had its own naïve
|
|
12
|
-
* walker that produced false positives for `each({ key })`, `item(...)`,
|
|
13
|
-
* array-method callbacks, and user-land helpers.
|
|
20
|
+
* `diagnostics.ts` bitmask-overflow warning.
|
|
14
21
|
*/
|
|
15
22
|
export declare function collectStatePathsFromSource(sourceFile: ts.SourceFile): Set<string>;
|
|
16
23
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"collect-deps.d.ts","sourceRoot":"","sources":["../src/collect-deps.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,YAAY,CAAA;
|
|
1
|
+
{"version":3,"file":"collect-deps.d.ts","sourceRoot":"","sources":["../src/collect-deps.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,YAAY,CAAA;AAyB3B;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,2BAA2B,CAAC,UAAU,EAAE,EAAE,CAAC,UAAU,GAAG,GAAG,CAAC,MAAM,CAAC,CAuBlF;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,UAAU,EAAE,EAAE,CAAC,UAAU,GAAG,GAAG,CAAC,MAAM,CAAC,EAAE,CAwBhF;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CA4B/D"}
|
package/dist/collect-deps.js
CHANGED
|
@@ -1,34 +1,62 @@
|
|
|
1
1
|
import ts from 'typescript';
|
|
2
|
+
import { resolveAccessorBody } from './accessor-resolver.js';
|
|
3
|
+
/**
|
|
4
|
+
* Extract paths from a callable accessor (arrow / fn-expr / fn-decl)
|
|
5
|
+
* into the given set. Returns true if anything was extracted.
|
|
6
|
+
*
|
|
7
|
+
* Shared by `collectStatePathsFromSource` and `collectAccessorPathSets` —
|
|
8
|
+
* both need identical extraction semantics for the inline-arrow path AND
|
|
9
|
+
* for the resolved-from-identifier path.
|
|
10
|
+
*/
|
|
11
|
+
function extractAccessorPaths(accessor, paths) {
|
|
12
|
+
const params = accessor.parameters;
|
|
13
|
+
if (params.length !== 1)
|
|
14
|
+
return false;
|
|
15
|
+
const paramName = params[0].name;
|
|
16
|
+
if (!ts.isIdentifier(paramName))
|
|
17
|
+
return false;
|
|
18
|
+
if (!accessor.body)
|
|
19
|
+
return false;
|
|
20
|
+
const before = paths.size;
|
|
21
|
+
extractPaths(accessor.body, paramName.text, '', paths);
|
|
22
|
+
return paths.size > before;
|
|
23
|
+
}
|
|
2
24
|
/**
|
|
3
25
|
* Walk the AST and collect every unique state access path referenced by
|
|
4
|
-
* a reactive accessor. A reactive accessor is
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
26
|
+
* a reactive accessor. A reactive accessor is one of:
|
|
27
|
+
*
|
|
28
|
+
* - An inline arrow / function expression at a reactive position
|
|
29
|
+
* (`text(s => s.count)`, `div({ title: s => s.title })`,
|
|
30
|
+
* `show({ when: s => s.gated })`, etc.).
|
|
31
|
+
* - An Identifier at a reactive position that resolves to a callable
|
|
32
|
+
* in this file — a const-bound arrow / function expression,
|
|
33
|
+
* a hoisted function declaration, or `const x = memo(arrow)`.
|
|
34
|
+
*
|
|
35
|
+
* The second case lets authors refactor a literal arrow into a named
|
|
36
|
+
* helper without losing the reactive-mask optimization (a precise mask
|
|
37
|
+
* for `__dirty` and structural-primitive `__mask`). Without it, the
|
|
38
|
+
* runtime falls back to FULL_MASK — correct, but every binding fires
|
|
39
|
+
* on every state change.
|
|
8
40
|
*
|
|
9
41
|
* Shared by the bit-assignment path (`collectDeps`, below) and the
|
|
10
|
-
* `diagnostics.ts` bitmask-overflow warning.
|
|
11
|
-
* see the same truth — previously the diagnostics side had its own naïve
|
|
12
|
-
* walker that produced false positives for `each({ key })`, `item(...)`,
|
|
13
|
-
* array-method callbacks, and user-land helpers.
|
|
42
|
+
* `diagnostics.ts` bitmask-overflow warning.
|
|
14
43
|
*/
|
|
15
44
|
export function collectStatePathsFromSource(sourceFile) {
|
|
16
45
|
const paths = new Set();
|
|
17
46
|
function visit(node) {
|
|
18
|
-
//
|
|
19
|
-
// - First arg to text(): text(s => s.count)
|
|
20
|
-
// - Prop values in element helper calls: div({ title: s => s.title })
|
|
47
|
+
// Inline arrow / function expression at a reactive position.
|
|
21
48
|
if (ts.isArrowFunction(node) || ts.isFunctionExpression(node)) {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
49
|
+
if (isReactiveAccessor(node))
|
|
50
|
+
extractAccessorPaths(node, paths);
|
|
51
|
+
}
|
|
52
|
+
// Identifier at a reactive position — resolve to its declaration
|
|
53
|
+
// and extract paths from the resolved body. Skip identifiers
|
|
54
|
+
// imported from elsewhere (resolver returns null) — there's no
|
|
55
|
+
// body to scan, runtime falls back to FULL_MASK.
|
|
56
|
+
if (ts.isIdentifier(node) && isReactiveAccessor(node)) {
|
|
57
|
+
const resolved = resolveAccessorBody(node);
|
|
58
|
+
if (resolved)
|
|
59
|
+
extractAccessorPaths(resolved, paths);
|
|
32
60
|
}
|
|
33
61
|
ts.forEachChild(node, visit);
|
|
34
62
|
}
|
|
@@ -44,15 +72,18 @@ export function collectAccessorPathSets(sourceFile) {
|
|
|
44
72
|
const sets = [];
|
|
45
73
|
function visit(node) {
|
|
46
74
|
if (ts.isArrowFunction(node) || ts.isFunctionExpression(node)) {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
75
|
+
if (isReactiveAccessor(node)) {
|
|
76
|
+
const set = new Set();
|
|
77
|
+
if (extractAccessorPaths(node, set))
|
|
78
|
+
sets.push(set);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
if (ts.isIdentifier(node) && isReactiveAccessor(node)) {
|
|
82
|
+
const resolved = resolveAccessorBody(node);
|
|
83
|
+
if (resolved) {
|
|
84
|
+
const set = new Set();
|
|
85
|
+
if (extractAccessorPaths(resolved, set))
|
|
86
|
+
sets.push(set);
|
|
56
87
|
}
|
|
57
88
|
}
|
|
58
89
|
ts.forEachChild(node, visit);
|
|
@@ -102,8 +133,10 @@ function hasLluiImport(sourceFile) {
|
|
|
102
133
|
return false;
|
|
103
134
|
}
|
|
104
135
|
/**
|
|
105
|
-
* Determines if
|
|
106
|
-
*
|
|
136
|
+
* Determines if a node is at a reactive-accessor position — either an
|
|
137
|
+
* inline arrow / function expression OR an identifier that's about to
|
|
138
|
+
* be resolved to one. The check is identity-based on `parent.arguments[0]`
|
|
139
|
+
* etc., so the same logic works for both shapes.
|
|
107
140
|
*/
|
|
108
141
|
function isReactiveAccessor(node) {
|
|
109
142
|
const parent = node.parent;
|
package/dist/collect-deps.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"collect-deps.js","sourceRoot":"","sources":["../src/collect-deps.ts"],"names":[],"mappings":"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","sourcesContent":["import ts from 'typescript'\n\n/**\n * Walk the AST and collect every unique state access path referenced by\n * a reactive accessor. A reactive accessor is an arrow/function whose\n * placement passes `isReactiveAccessor` — text()/memo() first args,\n * element-helper prop values, and allowlisted framework-API prop values.\n * Paths are rooted at the accessor's single parameter name.\n *\n * Shared by the bit-assignment path (`collectDeps`, below) and the\n * `diagnostics.ts` bitmask-overflow warning. Extracted so both consumers\n * see the same truth — previously the diagnostics side had its own naïve\n * walker that produced false positives for `each({ key })`, `item(...)`,\n * array-method callbacks, and user-land helpers.\n */\nexport function collectStatePathsFromSource(sourceFile: ts.SourceFile): Set<string> {\n const paths = new Set<string>()\n\n function visit(node: ts.Node): void {\n // Look for arrow functions that are reactive accessors:\n // - First arg to text(): text(s => s.count)\n // - Prop values in element helper calls: div({ title: s => s.title })\n if (ts.isArrowFunction(node) || ts.isFunctionExpression(node)) {\n const params = node.parameters\n if (params.length === 1) {\n const paramName = params[0]!.name\n if (ts.isIdentifier(paramName)) {\n // Check if this looks like a reactive accessor (not an event handler)\n if (isReactiveAccessor(node)) {\n extractPaths(node.body, paramName.text, '', paths)\n }\n }\n }\n }\n\n ts.forEachChild(node, visit)\n }\n\n visit(sourceFile)\n return paths\n}\n\n/**\n * Per-accessor path sets — one entry per reactive arrow/function. Used\n * by the bitmask-overflow diagnostic to find clusters of paths that\n * always fire together (co-occurrence analysis).\n */\nexport function collectAccessorPathSets(sourceFile: ts.SourceFile): Set<string>[] {\n const sets: Set<string>[] = []\n\n function visit(node: ts.Node): void {\n if (ts.isArrowFunction(node) || ts.isFunctionExpression(node)) {\n const params = node.parameters\n if (params.length === 1) {\n const paramName = params[0]!.name\n if (ts.isIdentifier(paramName) && isReactiveAccessor(node)) {\n const set = new Set<string>()\n extractPaths(node.body, paramName.text, '', set)\n if (set.size > 0) sets.push(set)\n }\n }\n }\n ts.forEachChild(node, visit)\n }\n\n visit(sourceFile)\n return sets\n}\n\n/**\n * Pre-scan a source file to collect all unique state access paths\n * referenced by reactive accessors (arrow functions in props and text() calls).\n * Returns a Map<path, bitPosition> where each path gets a unique power-of-two bit.\n */\nexport function collectDeps(source: string): Map<string, number> {\n const sourceFile = ts.createSourceFile('input.ts', source, ts.ScriptTarget.Latest, true)\n\n // Check if file imports from @llui/dom\n if (!hasLluiImport(sourceFile)) {\n return new Map()\n }\n\n const paths = collectStatePathsFromSource(sourceFile)\n\n // Assign bit positions. The bitmask holds 31 unique paths (positions\n // 0..30). When the count exceeds 31, overflow paths use FULL_MASK (-1)\n // — they always trigger re-evaluation, degrading gracefully. The\n // diagnostic warns the user to decompose.\n const fieldBits = new Map<string, number>()\n let bit = 1\n let index = 0\n for (const path of paths) {\n if (index >= 31) {\n fieldBits.set(path, -1)\n } else {\n fieldBits.set(path, bit)\n bit <<= 1\n }\n index++\n }\n\n return fieldBits\n}\n\nfunction hasLluiImport(sourceFile: ts.SourceFile): boolean {\n for (const stmt of sourceFile.statements) {\n if (\n ts.isImportDeclaration(stmt) &&\n ts.isStringLiteral(stmt.moduleSpecifier) &&\n stmt.moduleSpecifier.text === '@llui/dom'\n ) {\n return true\n }\n }\n return false\n}\n\n/**\n * Determines if an arrow/function expression is a reactive accessor\n * (not an event handler, not a callback like onClick).\n */\nfunction isReactiveAccessor(node: ts.ArrowFunction | ts.FunctionExpression): boolean {\n const parent = node.parent\n\n // text(s => s.count) — first arg to a call\n if (ts.isCallExpression(parent) && parent.arguments[0] === node) {\n // Skip item(t => t.id) — per-item selectors inside each() render.\n // Skip sample(s => s.x) — imperative one-shot read, no binding created\n // (both the top-level import and the destructured-from-h form).\n if (ts.isIdentifier(parent.expression)) {\n if (parent.expression.text === 'item' || parent.expression.text === 'sample') {\n return false\n }\n }\n // Skip array method callbacks: .filter(t => ...), .map(t => ...), .some(t => ...), etc.\n // Allow view-helper primitive calls: h.text(s => ...), h.memo(s => ...)\n if (ts.isPropertyAccessExpression(parent.expression)) {\n const methodName = parent.expression.name.text\n if (methodName === 'text' || methodName === 'memo') {\n return true\n }\n return false\n }\n return true\n }\n\n // div({ title: s => s.title }) — value in a property assignment inside an object literal.\n // Only treat as reactive if the containing call is a known framework API whose\n // properties are reactive accessors. Otherwise user-land helpers like\n // sliceHandler({ narrow: (m) => m.type === ... }) would pollute the path set.\n if (ts.isPropertyAssignment(parent)) {\n const key = parent.name\n if (ts.isIdentifier(key)) {\n // Skip event handlers (onClick, onInput, etc.)\n if (/^on[A-Z]/.test(key.text)) return false\n // Skip each() key function and other non-reactive props\n if (key.text === 'key' || key.text === 'name') return false\n // Walk up to find the enclosing call expression\n let ancestor: ts.Node | undefined = parent.parent // ObjectLiteralExpression\n while (ancestor && !ts.isCallExpression(ancestor)) {\n ancestor = ancestor.parent\n }\n if (!ancestor) return false\n const callExpr = ancestor as ts.CallExpression\n if (!ts.isIdentifier(callExpr.expression)) return false\n return REACTIVE_API_NAMES.has(callExpr.expression.text)\n }\n }\n\n return false\n}\n\n// Framework APIs whose object-literal arguments contain reactive accessors.\n// Arrow functions in property values of these calls are state-tracked.\nconst REACTIVE_API_NAMES = new Set([\n // Element helpers (see ELEMENT_HELPERS in transform.ts — we keep a superset here)\n ...[\n 'a',\n 'abbr',\n 'article',\n 'aside',\n 'b',\n 'blockquote',\n 'br',\n 'button',\n 'canvas',\n 'code',\n 'dd',\n 'details',\n 'dialog',\n 'div',\n 'dl',\n 'dt',\n 'em',\n 'fieldset',\n 'figcaption',\n 'figure',\n 'footer',\n 'form',\n 'h1',\n 'h2',\n 'h3',\n 'h4',\n 'h5',\n 'h6',\n 'header',\n 'hr',\n 'i',\n 'iframe',\n 'img',\n 'input',\n 'label',\n 'legend',\n 'li',\n 'main',\n 'mark',\n 'nav',\n 'ol',\n 'optgroup',\n 'option',\n 'output',\n 'p',\n 'pre',\n 'progress',\n 'section',\n 'select',\n 'small',\n 'span',\n 'strong',\n 'sub',\n 'summary',\n 'sup',\n 'table',\n 'tbody',\n 'td',\n 'textarea',\n 'tfoot',\n 'th',\n 'thead',\n 'time',\n 'tr',\n 'ul',\n 'video',\n ],\n // Structural primitives\n 'each',\n 'branch',\n 'scope',\n 'show',\n 'memo',\n 'portal',\n 'foreign',\n 'child',\n 'errorBoundary',\n])\n\n/**\n * Extract state access paths from an expression body.\n * Handles:\n * - Direct property access: param.field, param.field.subfield\n * - Bracket notation with string literal: param['field']\n */\nfunction extractPaths(node: ts.Node, paramName: string, _prefix: string, paths: Set<string>): void {\n if (ts.isPropertyAccessExpression(node)) {\n // Skip if this is an intermediate in a deeper chain\n if (ts.isPropertyAccessExpression(node.parent)) {\n // handled when the leaf is visited\n }\n // Skip if this is the callee of a method call: s.todos.filter(...)\n else if (ts.isCallExpression(node.parent) && node.parent.expression === node) {\n // It's a method call — record the object, not the method\n // e.g. s.todos.filter(...) → record 'todos', not 'todos.filter'\n if (ts.isPropertyAccessExpression(node.expression)) {\n const chain = resolvePropertyChain(node.expression, paramName)\n if (chain) paths.add(chain)\n }\n } else {\n const chain = resolvePropertyChain(node, paramName)\n if (chain) {\n paths.add(chain)\n }\n }\n }\n\n if (ts.isElementAccessExpression(node)) {\n const chain = resolveElementAccess(node, paramName)\n if (chain) {\n paths.add(chain)\n }\n }\n\n ts.forEachChild(node, (child) => extractPaths(child, paramName, _prefix, paths))\n}\n\n/**\n * Resolve a property access chain like s.user.name to \"user.name\".\n * Returns null if the chain doesn't start with the state parameter.\n * Stops at depth 2.\n */\nfunction resolvePropertyChain(node: ts.PropertyAccessExpression, paramName: string): string | null {\n const parts: string[] = []\n let current: ts.Expression = node\n\n while (ts.isPropertyAccessExpression(current)) {\n parts.unshift(current.name.text)\n current = current.expression\n }\n\n // The root must be the state parameter\n if (!ts.isIdentifier(current) || current.text !== paramName) {\n return null\n }\n\n // Limit to depth 2\n if (parts.length > 2) {\n return parts.slice(0, 2).join('.')\n }\n\n return parts.join('.')\n}\n\n/**\n * Resolve bracket access with string literal: s['count'] → \"count\"\n */\nfunction resolveElementAccess(node: ts.ElementAccessExpression, paramName: string): string | null {\n if (!ts.isIdentifier(node.expression) || node.expression.text !== paramName) {\n return null\n }\n\n if (ts.isStringLiteral(node.argumentExpression)) {\n return node.argumentExpression.text\n }\n\n return null\n}\n"]}
|
|
1
|
+
{"version":3,"file":"collect-deps.js","sourceRoot":"","sources":["../src/collect-deps.ts"],"names":[],"mappings":"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","sourcesContent":["import ts from 'typescript'\nimport { resolveAccessorBody } from './accessor-resolver.js'\n\n/**\n * Extract paths from a callable accessor (arrow / fn-expr / fn-decl)\n * into the given set. Returns true if anything was extracted.\n *\n * Shared by `collectStatePathsFromSource` and `collectAccessorPathSets` —\n * both need identical extraction semantics for the inline-arrow path AND\n * for the resolved-from-identifier path.\n */\nfunction extractAccessorPaths(\n accessor: ts.ArrowFunction | ts.FunctionExpression | ts.FunctionDeclaration,\n paths: Set<string>,\n): boolean {\n const params = accessor.parameters\n if (params.length !== 1) return false\n const paramName = params[0]!.name\n if (!ts.isIdentifier(paramName)) return false\n if (!accessor.body) return false\n const before = paths.size\n extractPaths(accessor.body, paramName.text, '', paths)\n return paths.size > before\n}\n\n/**\n * Walk the AST and collect every unique state access path referenced by\n * a reactive accessor. A reactive accessor is one of:\n *\n * - An inline arrow / function expression at a reactive position\n * (`text(s => s.count)`, `div({ title: s => s.title })`,\n * `show({ when: s => s.gated })`, etc.).\n * - An Identifier at a reactive position that resolves to a callable\n * in this file — a const-bound arrow / function expression,\n * a hoisted function declaration, or `const x = memo(arrow)`.\n *\n * The second case lets authors refactor a literal arrow into a named\n * helper without losing the reactive-mask optimization (a precise mask\n * for `__dirty` and structural-primitive `__mask`). Without it, the\n * runtime falls back to FULL_MASK — correct, but every binding fires\n * on every state change.\n *\n * Shared by the bit-assignment path (`collectDeps`, below) and the\n * `diagnostics.ts` bitmask-overflow warning.\n */\nexport function collectStatePathsFromSource(sourceFile: ts.SourceFile): Set<string> {\n const paths = new Set<string>()\n\n function visit(node: ts.Node): void {\n // Inline arrow / function expression at a reactive position.\n if (ts.isArrowFunction(node) || ts.isFunctionExpression(node)) {\n if (isReactiveAccessor(node)) extractAccessorPaths(node, paths)\n }\n\n // Identifier at a reactive position — resolve to its declaration\n // and extract paths from the resolved body. Skip identifiers\n // imported from elsewhere (resolver returns null) — there's no\n // body to scan, runtime falls back to FULL_MASK.\n if (ts.isIdentifier(node) && isReactiveAccessor(node)) {\n const resolved = resolveAccessorBody(node)\n if (resolved) extractAccessorPaths(resolved, paths)\n }\n\n ts.forEachChild(node, visit)\n }\n\n visit(sourceFile)\n return paths\n}\n\n/**\n * Per-accessor path sets — one entry per reactive arrow/function. Used\n * by the bitmask-overflow diagnostic to find clusters of paths that\n * always fire together (co-occurrence analysis).\n */\nexport function collectAccessorPathSets(sourceFile: ts.SourceFile): Set<string>[] {\n const sets: Set<string>[] = []\n\n function visit(node: ts.Node): void {\n if (ts.isArrowFunction(node) || ts.isFunctionExpression(node)) {\n if (isReactiveAccessor(node)) {\n const set = new Set<string>()\n if (extractAccessorPaths(node, set)) sets.push(set)\n }\n }\n\n if (ts.isIdentifier(node) && isReactiveAccessor(node)) {\n const resolved = resolveAccessorBody(node)\n if (resolved) {\n const set = new Set<string>()\n if (extractAccessorPaths(resolved, set)) sets.push(set)\n }\n }\n\n ts.forEachChild(node, visit)\n }\n\n visit(sourceFile)\n return sets\n}\n\n/**\n * Pre-scan a source file to collect all unique state access paths\n * referenced by reactive accessors (arrow functions in props and text() calls).\n * Returns a Map<path, bitPosition> where each path gets a unique power-of-two bit.\n */\nexport function collectDeps(source: string): Map<string, number> {\n const sourceFile = ts.createSourceFile('input.ts', source, ts.ScriptTarget.Latest, true)\n\n // Check if file imports from @llui/dom\n if (!hasLluiImport(sourceFile)) {\n return new Map()\n }\n\n const paths = collectStatePathsFromSource(sourceFile)\n\n // Assign bit positions. The bitmask holds 31 unique paths (positions\n // 0..30). When the count exceeds 31, overflow paths use FULL_MASK (-1)\n // — they always trigger re-evaluation, degrading gracefully. The\n // diagnostic warns the user to decompose.\n const fieldBits = new Map<string, number>()\n let bit = 1\n let index = 0\n for (const path of paths) {\n if (index >= 31) {\n fieldBits.set(path, -1)\n } else {\n fieldBits.set(path, bit)\n bit <<= 1\n }\n index++\n }\n\n return fieldBits\n}\n\nfunction hasLluiImport(sourceFile: ts.SourceFile): boolean {\n for (const stmt of sourceFile.statements) {\n if (\n ts.isImportDeclaration(stmt) &&\n ts.isStringLiteral(stmt.moduleSpecifier) &&\n stmt.moduleSpecifier.text === '@llui/dom'\n ) {\n return true\n }\n }\n return false\n}\n\n/**\n * Determines if a node is at a reactive-accessor position — either an\n * inline arrow / function expression OR an identifier that's about to\n * be resolved to one. The check is identity-based on `parent.arguments[0]`\n * etc., so the same logic works for both shapes.\n */\nfunction isReactiveAccessor(node: ts.Node): boolean {\n const parent = node.parent\n\n // text(s => s.count) — first arg to a call\n if (ts.isCallExpression(parent) && parent.arguments[0] === node) {\n // Skip item(t => t.id) — per-item selectors inside each() render.\n // Skip sample(s => s.x) — imperative one-shot read, no binding created\n // (both the top-level import and the destructured-from-h form).\n if (ts.isIdentifier(parent.expression)) {\n if (parent.expression.text === 'item' || parent.expression.text === 'sample') {\n return false\n }\n }\n // Skip array method callbacks: .filter(t => ...), .map(t => ...), .some(t => ...), etc.\n // Allow view-helper primitive calls: h.text(s => ...), h.memo(s => ...)\n if (ts.isPropertyAccessExpression(parent.expression)) {\n const methodName = parent.expression.name.text\n if (methodName === 'text' || methodName === 'memo') {\n return true\n }\n return false\n }\n return true\n }\n\n // div({ title: s => s.title }) — value in a property assignment inside an object literal.\n // Only treat as reactive if the containing call is a known framework API whose\n // properties are reactive accessors. Otherwise user-land helpers like\n // sliceHandler({ narrow: (m) => m.type === ... }) would pollute the path set.\n if (ts.isPropertyAssignment(parent)) {\n const key = parent.name\n if (ts.isIdentifier(key)) {\n // Skip event handlers (onClick, onInput, etc.)\n if (/^on[A-Z]/.test(key.text)) return false\n // Skip each() key function and other non-reactive props\n if (key.text === 'key' || key.text === 'name') return false\n // Walk up to find the enclosing call expression\n let ancestor: ts.Node | undefined = parent.parent // ObjectLiteralExpression\n while (ancestor && !ts.isCallExpression(ancestor)) {\n ancestor = ancestor.parent\n }\n if (!ancestor) return false\n const callExpr = ancestor as ts.CallExpression\n if (!ts.isIdentifier(callExpr.expression)) return false\n return REACTIVE_API_NAMES.has(callExpr.expression.text)\n }\n }\n\n return false\n}\n\n// Framework APIs whose object-literal arguments contain reactive accessors.\n// Arrow functions in property values of these calls are state-tracked.\nconst REACTIVE_API_NAMES = new Set([\n // Element helpers (see ELEMENT_HELPERS in transform.ts — we keep a superset here)\n ...[\n 'a',\n 'abbr',\n 'article',\n 'aside',\n 'b',\n 'blockquote',\n 'br',\n 'button',\n 'canvas',\n 'code',\n 'dd',\n 'details',\n 'dialog',\n 'div',\n 'dl',\n 'dt',\n 'em',\n 'fieldset',\n 'figcaption',\n 'figure',\n 'footer',\n 'form',\n 'h1',\n 'h2',\n 'h3',\n 'h4',\n 'h5',\n 'h6',\n 'header',\n 'hr',\n 'i',\n 'iframe',\n 'img',\n 'input',\n 'label',\n 'legend',\n 'li',\n 'main',\n 'mark',\n 'nav',\n 'ol',\n 'optgroup',\n 'option',\n 'output',\n 'p',\n 'pre',\n 'progress',\n 'section',\n 'select',\n 'small',\n 'span',\n 'strong',\n 'sub',\n 'summary',\n 'sup',\n 'table',\n 'tbody',\n 'td',\n 'textarea',\n 'tfoot',\n 'th',\n 'thead',\n 'time',\n 'tr',\n 'ul',\n 'video',\n ],\n // Structural primitives\n 'each',\n 'branch',\n 'scope',\n 'show',\n 'memo',\n 'portal',\n 'foreign',\n 'child',\n 'errorBoundary',\n])\n\n/**\n * Extract state access paths from an expression body.\n * Handles:\n * - Direct property access: param.field, param.field.subfield\n * - Bracket notation with string literal: param['field']\n */\nfunction extractPaths(node: ts.Node, paramName: string, _prefix: string, paths: Set<string>): void {\n if (ts.isPropertyAccessExpression(node)) {\n // Skip if this is an intermediate in a deeper chain\n if (ts.isPropertyAccessExpression(node.parent)) {\n // handled when the leaf is visited\n }\n // Skip if this is the callee of a method call: s.todos.filter(...)\n else if (ts.isCallExpression(node.parent) && node.parent.expression === node) {\n // It's a method call — record the object, not the method\n // e.g. s.todos.filter(...) → record 'todos', not 'todos.filter'\n if (ts.isPropertyAccessExpression(node.expression)) {\n const chain = resolvePropertyChain(node.expression, paramName)\n if (chain) paths.add(chain)\n }\n } else {\n const chain = resolvePropertyChain(node, paramName)\n if (chain) {\n paths.add(chain)\n }\n }\n }\n\n if (ts.isElementAccessExpression(node)) {\n const chain = resolveElementAccess(node, paramName)\n if (chain) {\n paths.add(chain)\n }\n }\n\n ts.forEachChild(node, (child) => extractPaths(child, paramName, _prefix, paths))\n}\n\n/**\n * Resolve a property access chain like s.user.name to \"user.name\".\n * Returns null if the chain doesn't start with the state parameter.\n * Stops at depth 2.\n */\nfunction resolvePropertyChain(node: ts.PropertyAccessExpression, paramName: string): string | null {\n const parts: string[] = []\n let current: ts.Expression = node\n\n while (ts.isPropertyAccessExpression(current)) {\n parts.unshift(current.name.text)\n current = current.expression\n }\n\n // The root must be the state parameter\n if (!ts.isIdentifier(current) || current.text !== paramName) {\n return null\n }\n\n // Limit to depth 2\n if (parts.length > 2) {\n return parts.slice(0, 2).join('.')\n }\n\n return parts.join('.')\n}\n\n/**\n * Resolve bracket access with string literal: s['count'] → \"count\"\n */\nfunction resolveElementAccess(node: ts.ElementAccessExpression, paramName: string): string | null {\n if (!ts.isIdentifier(node.expression) || node.expression.text !== paramName) {\n return null\n }\n\n if (ts.isStringLiteral(node.argumentExpression)) {\n return node.argumentExpression.text\n }\n\n return null\n}\n"]}
|
package/dist/transform.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transform.d.ts","sourceRoot":"","sources":["../src/transform.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,YAAY,CAAA;
|
|
1
|
+
{"version":3,"file":"transform.d.ts","sourceRoot":"","sources":["../src/transform.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,YAAY,CAAA;AAO3B,OAAO,EACL,gBAAgB,EAChB,mBAAmB,EAIpB,MAAM,iBAAiB,CAAA;AACxB,OAAO,EAAE,qBAAqB,EAA2B,MAAM,sBAAsB,CAAA;AACrF,OAAO,EAAE,kBAAkB,EAAkB,MAAM,mBAAmB,CAAA;AAiOtE;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAA;IACb,GAAG,EAAE,MAAM,CAAA;IACX,WAAW,EAAE,MAAM,CAAA;CACpB;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,mBAAmB;IAClC,KAAK,CAAC,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAA;IAC5C,GAAG,CAAC,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAA;IAC1C,MAAM,CAAC,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAA;CAC9C;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,mBAAmB;IAClC,SAAS,CAAC,EAAE,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAA;IAC/C,cAAc,CAAC,EAAE,UAAU,CAAC,OAAO,qBAAqB,CAAC,CAAA;IACzD,WAAW,CAAC,EAAE,UAAU,CAAC,OAAO,kBAAkB,CAAC,CAAA;IACnD,YAAY,CAAC,EAAE,UAAU,CAAC,OAAO,mBAAmB,CAAC,CAAA;CACtD;AAED,wBAAgB,aAAa,CAC3B,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,OAAO,UAAQ,EACf,iBAAiB,UAAQ,EACzB,OAAO,GAAE,MAAM,GAAG,IAAW,EAC7B,OAAO,UAAQ,EACf,WAAW,CAAC,EAAE,mBAAmB,EACjC,YAAY,CAAC,EAAE,mBAAmB,GACjC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,aAAa,EAAE,CAAA;CAAE,GAAG,IAAI,CAqYnD;AAyxID,MAAM,WAAW,wBAAwB;IACvC,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,MAAM,EAAE,CAAA;CACnB;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,GAChB,wBAAwB,GAAG,IAAI,CAoGjC;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CA4B7D;AAID;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAI3D;AAED;;;GAGG;AACH,wBAAgB,8BAA8B,CAAC,IAAI,EAAE,EAAE,CAAC,cAAc,GAAG,MAAM,GAAG,IAAI,CAcrF;AAED;;;;;GAKG;AACH,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,MAAM,CASzF;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,MAAM,EACd,cAAc,EAAE,KAAK,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,CAAA;CAAE,CAAC,GAChE,MAAM,CAYR"}
|