@fuzdev/fuz_util 0.49.0 → 0.49.2
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.
|
@@ -41,14 +41,16 @@ export declare const find_attribute: (node: AST.Component, name: string) => AST.
|
|
|
41
41
|
/**
|
|
42
42
|
* Recursively evaluates an expression AST node to a static string value.
|
|
43
43
|
*
|
|
44
|
-
* Handles string `Literal`, `TemplateLiteral`
|
|
45
|
-
* `BinaryExpression` with
|
|
44
|
+
* Handles string `Literal`, `TemplateLiteral` (including interpolations when all
|
|
45
|
+
* expressions resolve), `BinaryExpression` with `+`, and `Identifier` lookup
|
|
46
|
+
* via an optional bindings map built by `build_static_bindings`.
|
|
46
47
|
* Returns `null` for dynamic expressions, non-string literals, or unsupported node types.
|
|
47
48
|
*
|
|
48
49
|
* @param expr An ESTree expression AST node.
|
|
50
|
+
* @param bindings Optional map of variable names to their resolved static string values.
|
|
49
51
|
* @returns The resolved static string, or `null` if the expression is dynamic.
|
|
50
52
|
*/
|
|
51
|
-
export declare const evaluate_static_expr: (expr: Expression) => string | null;
|
|
53
|
+
export declare const evaluate_static_expr: (expr: Expression, bindings?: ReadonlyMap<string, string>) => string | null;
|
|
52
54
|
/**
|
|
53
55
|
* Extracts a static string value from a Svelte attribute value AST node.
|
|
54
56
|
*
|
|
@@ -61,9 +63,25 @@ export declare const evaluate_static_expr: (expr: Expression) => string | null;
|
|
|
61
63
|
* Returns `null` for null literals, mixed arrays, dynamic expressions, and non-string values.
|
|
62
64
|
*
|
|
63
65
|
* @param value The attribute value from `AST.Attribute['value']`.
|
|
66
|
+
* @param bindings Optional map of variable names to their resolved static string values.
|
|
64
67
|
* @returns The resolved static string, or `null` if the value is dynamic.
|
|
65
68
|
*/
|
|
66
|
-
export declare const extract_static_string: (value: AST.Attribute["value"]) => string | null;
|
|
69
|
+
export declare const extract_static_string: (value: AST.Attribute["value"], bindings?: ReadonlyMap<string, string>) => string | null;
|
|
70
|
+
/**
|
|
71
|
+
* Builds a map of statically resolvable `const` bindings from a Svelte AST.
|
|
72
|
+
*
|
|
73
|
+
* Scans top-level `const` variable declarations in both instance and module scripts.
|
|
74
|
+
* For each declarator with a plain `Identifier` pattern and a statically evaluable
|
|
75
|
+
* initializer, adds the binding to the map. Processes declarations in source order
|
|
76
|
+
* so that chained references resolve: `const a = 'x'; const b = a;` maps `b` to `'x'`.
|
|
77
|
+
*
|
|
78
|
+
* Skips destructuring patterns, `let`/`var` declarations, and declarations
|
|
79
|
+
* whose initializers reference dynamic values.
|
|
80
|
+
*
|
|
81
|
+
* @param ast The parsed Svelte AST root node.
|
|
82
|
+
* @returns Map of variable names to their resolved static string values.
|
|
83
|
+
*/
|
|
84
|
+
export declare const build_static_bindings: (ast: AST.Root) => Map<string, string>;
|
|
67
85
|
/**
|
|
68
86
|
* Resolves local names that import from specified source paths.
|
|
69
87
|
*
|
|
@@ -105,6 +123,10 @@ export declare const generate_import_lines: (imports: Map<string, PreprocessImpo
|
|
|
105
123
|
* `skip` set are excluded from traversal — used to skip `ImportDeclaration`
|
|
106
124
|
* nodes so the import's own specifier identifier doesn't false-positive.
|
|
107
125
|
*
|
|
126
|
+
* Skips `Identifier` nodes in non-reference positions defined by
|
|
127
|
+
* `NON_REFERENCE_FIELDS` — for example, `obj.Mdz` (non-computed member property),
|
|
128
|
+
* `{ Mdz: value }` (non-computed object key), and statement labels.
|
|
129
|
+
*
|
|
108
130
|
* Safe for Svelte template ASTs: `Component.name` is a plain string property
|
|
109
131
|
* (not an `Identifier` node), so `<Mdz>` tags do not produce false matches.
|
|
110
132
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"svelte_preprocess_helpers.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/svelte_preprocess_helpers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAC,UAAU,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,eAAe,EAAC,MAAM,QAAQ,CAAC;AACnG,OAAO,KAAK,EAAC,GAAG,EAAC,MAAM,iBAAiB,CAAC;AAEzC,qDAAqD;AACrD,MAAM,WAAW,oBAAoB;IACpC,sCAAsC;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,iDAAiD;IACjD,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC;CAC1B;AAED,qDAAqD;AACrD,MAAM,WAAW,uBAAuB;IACvC,gEAAgE;IAChE,WAAW,EAAE,iBAAiB,CAAC;IAC/B,mDAAmD;IACnD,SAAS,EAAE,eAAe,GAAG,sBAAsB,CAAC;CACpD;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,cAAc,GAAI,MAAM,GAAG,CAAC,SAAS,EAAE,MAAM,MAAM,KAAG,GAAG,CAAC,SAAS,GAAG,SAOlF,CAAC;AAEF
|
|
1
|
+
{"version":3,"file":"svelte_preprocess_helpers.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/svelte_preprocess_helpers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAC,UAAU,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,eAAe,EAAC,MAAM,QAAQ,CAAC;AACnG,OAAO,KAAK,EAAC,GAAG,EAAC,MAAM,iBAAiB,CAAC;AAEzC,qDAAqD;AACrD,MAAM,WAAW,oBAAoB;IACpC,sCAAsC;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,iDAAiD;IACjD,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC;CAC1B;AAED,qDAAqD;AACrD,MAAM,WAAW,uBAAuB;IACvC,gEAAgE;IAChE,WAAW,EAAE,iBAAiB,CAAC;IAC/B,mDAAmD;IACnD,SAAS,EAAE,eAAe,GAAG,sBAAsB,CAAC;CACpD;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,cAAc,GAAI,MAAM,GAAG,CAAC,SAAS,EAAE,MAAM,MAAM,KAAG,GAAG,CAAC,SAAS,GAAG,SAOlF,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,oBAAoB,GAChC,MAAM,UAAU,EAChB,WAAW,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,KACpC,MAAM,GAAG,IA+BX,CAAC;AAEF;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,qBAAqB,GACjC,OAAO,GAAG,CAAC,SAAS,CAAC,OAAO,CAAC,EAC7B,WAAW,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,KACpC,MAAM,GAAG,IAkBX,CAAC;AAOF;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,qBAAqB,GAAI,KAAK,GAAG,CAAC,IAAI,KAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAiBvE,CAAC;AAEF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,uBAAuB,GACnC,KAAK,GAAG,CAAC,IAAI,EACb,mBAAmB,KAAK,CAAC,MAAM,CAAC,KAC9B,GAAG,CAAC,MAAM,EAAE,uBAAuB,CAcrC,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,2BAA2B,GAAI,QAAQ,GAAG,CAAC,MAAM,KAAG,MAYhE,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,qBAAqB,GACjC,SAAS,GAAG,CAAC,MAAM,EAAE,oBAAoB,CAAC,EAC1C,SAAQ,MAAa,KACnB,MAyBF,CAAC;AA0BF;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,sBAAsB,GAClC,MAAM,OAAO,EACb,MAAM,MAAM,EACZ,OAAO,GAAG,CAAC,OAAO,CAAC,KACjB,OAgBF,CAAC;AAEF;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,kBAAkB,GAAI,MAAM,MAAM,KAAG,MAc/C,CAAC"}
|
|
@@ -32,30 +32,50 @@ export const find_attribute = (node, name) => {
|
|
|
32
32
|
/**
|
|
33
33
|
* Recursively evaluates an expression AST node to a static string value.
|
|
34
34
|
*
|
|
35
|
-
* Handles string `Literal`, `TemplateLiteral`
|
|
36
|
-
* `BinaryExpression` with
|
|
35
|
+
* Handles string `Literal`, `TemplateLiteral` (including interpolations when all
|
|
36
|
+
* expressions resolve), `BinaryExpression` with `+`, and `Identifier` lookup
|
|
37
|
+
* via an optional bindings map built by `build_static_bindings`.
|
|
37
38
|
* Returns `null` for dynamic expressions, non-string literals, or unsupported node types.
|
|
38
39
|
*
|
|
39
40
|
* @param expr An ESTree expression AST node.
|
|
41
|
+
* @param bindings Optional map of variable names to their resolved static string values.
|
|
40
42
|
* @returns The resolved static string, or `null` if the expression is dynamic.
|
|
41
43
|
*/
|
|
42
|
-
export const evaluate_static_expr = (expr) => {
|
|
44
|
+
export const evaluate_static_expr = (expr, bindings) => {
|
|
43
45
|
if (expr.type === 'Literal' && typeof expr.value === 'string')
|
|
44
46
|
return expr.value;
|
|
45
|
-
if (expr.type === 'TemplateLiteral'
|
|
46
|
-
|
|
47
|
+
if (expr.type === 'TemplateLiteral') {
|
|
48
|
+
if (expr.expressions.length === 0) {
|
|
49
|
+
return expr.quasis.map((q) => q.value.cooked ?? q.value.raw).join('');
|
|
50
|
+
}
|
|
51
|
+
// Try resolving interpolations through bindings
|
|
52
|
+
const parts = [];
|
|
53
|
+
for (let i = 0; i < expr.quasis.length; i++) {
|
|
54
|
+
const quasi = expr.quasis[i];
|
|
55
|
+
parts.push(quasi.value.cooked ?? quasi.value.raw);
|
|
56
|
+
if (i < expr.expressions.length) {
|
|
57
|
+
const val = evaluate_static_expr(expr.expressions[i], bindings);
|
|
58
|
+
if (val === null)
|
|
59
|
+
return null;
|
|
60
|
+
parts.push(val);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return parts.join('');
|
|
47
64
|
}
|
|
48
65
|
if (expr.type === 'BinaryExpression' && expr.operator === '+') {
|
|
49
66
|
if (expr.left.type === 'PrivateIdentifier')
|
|
50
67
|
return null;
|
|
51
|
-
const left = evaluate_static_expr(expr.left);
|
|
68
|
+
const left = evaluate_static_expr(expr.left, bindings);
|
|
52
69
|
if (left === null)
|
|
53
70
|
return null;
|
|
54
|
-
const right = evaluate_static_expr(expr.right);
|
|
71
|
+
const right = evaluate_static_expr(expr.right, bindings);
|
|
55
72
|
if (right === null)
|
|
56
73
|
return null;
|
|
57
74
|
return left + right;
|
|
58
75
|
}
|
|
76
|
+
if (expr.type === 'Identifier' && bindings?.has(expr.name)) {
|
|
77
|
+
return bindings.get(expr.name);
|
|
78
|
+
}
|
|
59
79
|
return null;
|
|
60
80
|
};
|
|
61
81
|
/**
|
|
@@ -70,9 +90,10 @@ export const evaluate_static_expr = (expr) => {
|
|
|
70
90
|
* Returns `null` for null literals, mixed arrays, dynamic expressions, and non-string values.
|
|
71
91
|
*
|
|
72
92
|
* @param value The attribute value from `AST.Attribute['value']`.
|
|
93
|
+
* @param bindings Optional map of variable names to their resolved static string values.
|
|
73
94
|
* @returns The resolved static string, or `null` if the value is dynamic.
|
|
74
95
|
*/
|
|
75
|
-
export const extract_static_string = (value) => {
|
|
96
|
+
export const extract_static_string = (value, bindings) => {
|
|
76
97
|
// Boolean attribute (e.g., <Mdz inline />)
|
|
77
98
|
if (value === true)
|
|
78
99
|
return null;
|
|
@@ -89,7 +110,47 @@ export const extract_static_string = (value) => {
|
|
|
89
110
|
// Null literal
|
|
90
111
|
if (expr.type === 'Literal' && expr.value === null)
|
|
91
112
|
return null;
|
|
92
|
-
return evaluate_static_expr(expr);
|
|
113
|
+
return evaluate_static_expr(expr, bindings);
|
|
114
|
+
};
|
|
115
|
+
// TODO cross-import tracing: resolve `import {x} from './constants.js'` by reading
|
|
116
|
+
// and parsing the imported module, extracting `export const` values. Would need path
|
|
117
|
+
// resolution ($lib, tsconfig paths), a Program-node variant of this function, and
|
|
118
|
+
// cache invalidation when the imported file changes. Start with relative .ts/.js only.
|
|
119
|
+
/**
|
|
120
|
+
* Builds a map of statically resolvable `const` bindings from a Svelte AST.
|
|
121
|
+
*
|
|
122
|
+
* Scans top-level `const` variable declarations in both instance and module scripts.
|
|
123
|
+
* For each declarator with a plain `Identifier` pattern and a statically evaluable
|
|
124
|
+
* initializer, adds the binding to the map. Processes declarations in source order
|
|
125
|
+
* so that chained references resolve: `const a = 'x'; const b = a;` maps `b` to `'x'`.
|
|
126
|
+
*
|
|
127
|
+
* Skips destructuring patterns, `let`/`var` declarations, and declarations
|
|
128
|
+
* whose initializers reference dynamic values.
|
|
129
|
+
*
|
|
130
|
+
* @param ast The parsed Svelte AST root node.
|
|
131
|
+
* @returns Map of variable names to their resolved static string values.
|
|
132
|
+
*/
|
|
133
|
+
export const build_static_bindings = (ast) => {
|
|
134
|
+
const bindings = new Map();
|
|
135
|
+
for (const script of [ast.instance, ast.module]) {
|
|
136
|
+
if (!script)
|
|
137
|
+
continue;
|
|
138
|
+
for (const node of script.content.body) {
|
|
139
|
+
if (node.type !== 'VariableDeclaration' || node.kind !== 'const')
|
|
140
|
+
continue;
|
|
141
|
+
for (const declarator of node.declarations) {
|
|
142
|
+
if (declarator.id.type !== 'Identifier')
|
|
143
|
+
continue;
|
|
144
|
+
if (!declarator.init)
|
|
145
|
+
continue;
|
|
146
|
+
const value = evaluate_static_expr(declarator.init, bindings);
|
|
147
|
+
if (value !== null) {
|
|
148
|
+
bindings.set(declarator.id.name, value);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return bindings;
|
|
93
154
|
};
|
|
94
155
|
/**
|
|
95
156
|
* Resolves local names that import from specified source paths.
|
|
@@ -178,6 +239,26 @@ export const generate_import_lines = (imports, indent = '\t') => {
|
|
|
178
239
|
}
|
|
179
240
|
return lines.join('\n');
|
|
180
241
|
};
|
|
242
|
+
/**
|
|
243
|
+
* ESTree node fields that contain `Identifier` nodes which are NOT binding references.
|
|
244
|
+
*
|
|
245
|
+
* Keyed by node type. Each entry lists fields to skip during identifier search,
|
|
246
|
+
* optionally conditioned on the node's `computed` property being falsy.
|
|
247
|
+
*
|
|
248
|
+
* Examples of non-reference positions:
|
|
249
|
+
* - `obj.Mdz` — `MemberExpression.property` when `computed: false`
|
|
250
|
+
* - `{ Mdz: value }` — `Property.key` when `computed: false`
|
|
251
|
+
* - `label: for(...)` — `LabeledStatement.label`
|
|
252
|
+
*/
|
|
253
|
+
const NON_REFERENCE_FIELDS = new Map([
|
|
254
|
+
['MemberExpression', [{ field: 'property', when_not_computed: true }]],
|
|
255
|
+
['Property', [{ field: 'key', when_not_computed: true }]],
|
|
256
|
+
['PropertyDefinition', [{ field: 'key', when_not_computed: true }]],
|
|
257
|
+
['MethodDefinition', [{ field: 'key', when_not_computed: true }]],
|
|
258
|
+
['LabeledStatement', [{ field: 'label' }]],
|
|
259
|
+
['BreakStatement', [{ field: 'label' }]],
|
|
260
|
+
['ContinueStatement', [{ field: 'label' }]],
|
|
261
|
+
]);
|
|
181
262
|
/**
|
|
182
263
|
* Checks if an identifier with the given name appears anywhere in an AST subtree.
|
|
183
264
|
*
|
|
@@ -186,6 +267,10 @@ export const generate_import_lines = (imports, indent = '\t') => {
|
|
|
186
267
|
* `skip` set are excluded from traversal — used to skip `ImportDeclaration`
|
|
187
268
|
* nodes so the import's own specifier identifier doesn't false-positive.
|
|
188
269
|
*
|
|
270
|
+
* Skips `Identifier` nodes in non-reference positions defined by
|
|
271
|
+
* `NON_REFERENCE_FIELDS` — for example, `obj.Mdz` (non-computed member property),
|
|
272
|
+
* `{ Mdz: value }` (non-computed object key), and statement labels.
|
|
273
|
+
*
|
|
189
274
|
* Safe for Svelte template ASTs: `Component.name` is a plain string property
|
|
190
275
|
* (not an `Identifier` node), so `<Mdz>` tags do not produce false matches.
|
|
191
276
|
*
|
|
@@ -205,7 +290,11 @@ export const has_identifier_in_tree = (node, name, skip) => {
|
|
|
205
290
|
const record = node;
|
|
206
291
|
if (record.type === 'Identifier' && record.name === name)
|
|
207
292
|
return true;
|
|
293
|
+
const rules = NON_REFERENCE_FIELDS.get(record.type);
|
|
208
294
|
for (const key of Object.keys(record)) {
|
|
295
|
+
if (rules?.some((r) => r.field === key && (!r.when_not_computed || !record.computed))) {
|
|
296
|
+
continue;
|
|
297
|
+
}
|
|
209
298
|
if (has_identifier_in_tree(record[key], name, skip))
|
|
210
299
|
return true;
|
|
211
300
|
}
|
package/package.json
CHANGED
|
@@ -53,26 +53,48 @@ export const find_attribute = (node: AST.Component, name: string): AST.Attribute
|
|
|
53
53
|
/**
|
|
54
54
|
* Recursively evaluates an expression AST node to a static string value.
|
|
55
55
|
*
|
|
56
|
-
* Handles string `Literal`, `TemplateLiteral`
|
|
57
|
-
* `BinaryExpression` with
|
|
56
|
+
* Handles string `Literal`, `TemplateLiteral` (including interpolations when all
|
|
57
|
+
* expressions resolve), `BinaryExpression` with `+`, and `Identifier` lookup
|
|
58
|
+
* via an optional bindings map built by `build_static_bindings`.
|
|
58
59
|
* Returns `null` for dynamic expressions, non-string literals, or unsupported node types.
|
|
59
60
|
*
|
|
60
61
|
* @param expr An ESTree expression AST node.
|
|
62
|
+
* @param bindings Optional map of variable names to their resolved static string values.
|
|
61
63
|
* @returns The resolved static string, or `null` if the expression is dynamic.
|
|
62
64
|
*/
|
|
63
|
-
export const evaluate_static_expr = (
|
|
65
|
+
export const evaluate_static_expr = (
|
|
66
|
+
expr: Expression,
|
|
67
|
+
bindings?: ReadonlyMap<string, string>,
|
|
68
|
+
): string | null => {
|
|
64
69
|
if (expr.type === 'Literal' && typeof expr.value === 'string') return expr.value;
|
|
65
|
-
if (expr.type === 'TemplateLiteral'
|
|
66
|
-
|
|
70
|
+
if (expr.type === 'TemplateLiteral') {
|
|
71
|
+
if (expr.expressions.length === 0) {
|
|
72
|
+
return expr.quasis.map((q) => q.value.cooked ?? q.value.raw).join('');
|
|
73
|
+
}
|
|
74
|
+
// Try resolving interpolations through bindings
|
|
75
|
+
const parts: Array<string> = [];
|
|
76
|
+
for (let i = 0; i < expr.quasis.length; i++) {
|
|
77
|
+
const quasi = expr.quasis[i]!;
|
|
78
|
+
parts.push(quasi.value.cooked ?? quasi.value.raw);
|
|
79
|
+
if (i < expr.expressions.length) {
|
|
80
|
+
const val = evaluate_static_expr(expr.expressions[i]!, bindings);
|
|
81
|
+
if (val === null) return null;
|
|
82
|
+
parts.push(val);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return parts.join('');
|
|
67
86
|
}
|
|
68
87
|
if (expr.type === 'BinaryExpression' && expr.operator === '+') {
|
|
69
88
|
if (expr.left.type === 'PrivateIdentifier') return null;
|
|
70
|
-
const left = evaluate_static_expr(expr.left);
|
|
89
|
+
const left = evaluate_static_expr(expr.left, bindings);
|
|
71
90
|
if (left === null) return null;
|
|
72
|
-
const right = evaluate_static_expr(expr.right);
|
|
91
|
+
const right = evaluate_static_expr(expr.right, bindings);
|
|
73
92
|
if (right === null) return null;
|
|
74
93
|
return left + right;
|
|
75
94
|
}
|
|
95
|
+
if (expr.type === 'Identifier' && bindings?.has(expr.name)) {
|
|
96
|
+
return bindings.get(expr.name)!;
|
|
97
|
+
}
|
|
76
98
|
return null;
|
|
77
99
|
};
|
|
78
100
|
|
|
@@ -88,9 +110,13 @@ export const evaluate_static_expr = (expr: Expression): string | null => {
|
|
|
88
110
|
* Returns `null` for null literals, mixed arrays, dynamic expressions, and non-string values.
|
|
89
111
|
*
|
|
90
112
|
* @param value The attribute value from `AST.Attribute['value']`.
|
|
113
|
+
* @param bindings Optional map of variable names to their resolved static string values.
|
|
91
114
|
* @returns The resolved static string, or `null` if the value is dynamic.
|
|
92
115
|
*/
|
|
93
|
-
export const extract_static_string = (
|
|
116
|
+
export const extract_static_string = (
|
|
117
|
+
value: AST.Attribute['value'],
|
|
118
|
+
bindings?: ReadonlyMap<string, string>,
|
|
119
|
+
): string | null => {
|
|
94
120
|
// Boolean attribute (e.g., <Mdz inline />)
|
|
95
121
|
if (value === true) return null;
|
|
96
122
|
|
|
@@ -107,7 +133,45 @@ export const extract_static_string = (value: AST.Attribute['value']): string | n
|
|
|
107
133
|
const expr = value.expression;
|
|
108
134
|
// Null literal
|
|
109
135
|
if (expr.type === 'Literal' && expr.value === null) return null;
|
|
110
|
-
return evaluate_static_expr(expr);
|
|
136
|
+
return evaluate_static_expr(expr, bindings);
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
// TODO cross-import tracing: resolve `import {x} from './constants.js'` by reading
|
|
140
|
+
// and parsing the imported module, extracting `export const` values. Would need path
|
|
141
|
+
// resolution ($lib, tsconfig paths), a Program-node variant of this function, and
|
|
142
|
+
// cache invalidation when the imported file changes. Start with relative .ts/.js only.
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Builds a map of statically resolvable `const` bindings from a Svelte AST.
|
|
146
|
+
*
|
|
147
|
+
* Scans top-level `const` variable declarations in both instance and module scripts.
|
|
148
|
+
* For each declarator with a plain `Identifier` pattern and a statically evaluable
|
|
149
|
+
* initializer, adds the binding to the map. Processes declarations in source order
|
|
150
|
+
* so that chained references resolve: `const a = 'x'; const b = a;` maps `b` to `'x'`.
|
|
151
|
+
*
|
|
152
|
+
* Skips destructuring patterns, `let`/`var` declarations, and declarations
|
|
153
|
+
* whose initializers reference dynamic values.
|
|
154
|
+
*
|
|
155
|
+
* @param ast The parsed Svelte AST root node.
|
|
156
|
+
* @returns Map of variable names to their resolved static string values.
|
|
157
|
+
*/
|
|
158
|
+
export const build_static_bindings = (ast: AST.Root): Map<string, string> => {
|
|
159
|
+
const bindings: Map<string, string> = new Map();
|
|
160
|
+
for (const script of [ast.instance, ast.module]) {
|
|
161
|
+
if (!script) continue;
|
|
162
|
+
for (const node of script.content.body) {
|
|
163
|
+
if (node.type !== 'VariableDeclaration' || node.kind !== 'const') continue;
|
|
164
|
+
for (const declarator of node.declarations) {
|
|
165
|
+
if (declarator.id.type !== 'Identifier') continue;
|
|
166
|
+
if (!declarator.init) continue;
|
|
167
|
+
const value = evaluate_static_expr(declarator.init, bindings);
|
|
168
|
+
if (value !== null) {
|
|
169
|
+
bindings.set(declarator.id.name, value);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return bindings;
|
|
111
175
|
};
|
|
112
176
|
|
|
113
177
|
/**
|
|
@@ -203,6 +267,30 @@ export const generate_import_lines = (
|
|
|
203
267
|
return lines.join('\n');
|
|
204
268
|
};
|
|
205
269
|
|
|
270
|
+
/**
|
|
271
|
+
* ESTree node fields that contain `Identifier` nodes which are NOT binding references.
|
|
272
|
+
*
|
|
273
|
+
* Keyed by node type. Each entry lists fields to skip during identifier search,
|
|
274
|
+
* optionally conditioned on the node's `computed` property being falsy.
|
|
275
|
+
*
|
|
276
|
+
* Examples of non-reference positions:
|
|
277
|
+
* - `obj.Mdz` — `MemberExpression.property` when `computed: false`
|
|
278
|
+
* - `{ Mdz: value }` — `Property.key` when `computed: false`
|
|
279
|
+
* - `label: for(...)` — `LabeledStatement.label`
|
|
280
|
+
*/
|
|
281
|
+
const NON_REFERENCE_FIELDS: Map<
|
|
282
|
+
string,
|
|
283
|
+
Array<{field: string; when_not_computed?: boolean}>
|
|
284
|
+
> = new Map([
|
|
285
|
+
['MemberExpression', [{field: 'property', when_not_computed: true}]],
|
|
286
|
+
['Property', [{field: 'key', when_not_computed: true}]],
|
|
287
|
+
['PropertyDefinition', [{field: 'key', when_not_computed: true}]],
|
|
288
|
+
['MethodDefinition', [{field: 'key', when_not_computed: true}]],
|
|
289
|
+
['LabeledStatement', [{field: 'label'}]],
|
|
290
|
+
['BreakStatement', [{field: 'label'}]],
|
|
291
|
+
['ContinueStatement', [{field: 'label'}]],
|
|
292
|
+
]);
|
|
293
|
+
|
|
206
294
|
/**
|
|
207
295
|
* Checks if an identifier with the given name appears anywhere in an AST subtree.
|
|
208
296
|
*
|
|
@@ -211,6 +299,10 @@ export const generate_import_lines = (
|
|
|
211
299
|
* `skip` set are excluded from traversal — used to skip `ImportDeclaration`
|
|
212
300
|
* nodes so the import's own specifier identifier doesn't false-positive.
|
|
213
301
|
*
|
|
302
|
+
* Skips `Identifier` nodes in non-reference positions defined by
|
|
303
|
+
* `NON_REFERENCE_FIELDS` — for example, `obj.Mdz` (non-computed member property),
|
|
304
|
+
* `{ Mdz: value }` (non-computed object key), and statement labels.
|
|
305
|
+
*
|
|
214
306
|
* Safe for Svelte template ASTs: `Component.name` is a plain string property
|
|
215
307
|
* (not an `Identifier` node), so `<Mdz>` tags do not produce false matches.
|
|
216
308
|
*
|
|
@@ -231,7 +323,11 @@ export const has_identifier_in_tree = (
|
|
|
231
323
|
}
|
|
232
324
|
const record = node as Record<string, unknown>;
|
|
233
325
|
if (record.type === 'Identifier' && record.name === name) return true;
|
|
326
|
+
const rules = NON_REFERENCE_FIELDS.get(record.type as string);
|
|
234
327
|
for (const key of Object.keys(record)) {
|
|
328
|
+
if (rules?.some((r) => r.field === key && (!r.when_not_computed || !record.computed))) {
|
|
329
|
+
continue;
|
|
330
|
+
}
|
|
235
331
|
if (has_identifier_in_tree(record[key], name, skip)) return true;
|
|
236
332
|
}
|
|
237
333
|
return false;
|