@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` without interpolation, and
45
- * `BinaryExpression` with the `+` operator (string concatenation).
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;;;;;;;;;GASG;AACH,eAAO,MAAM,oBAAoB,GAAI,MAAM,UAAU,KAAG,MAAM,GAAG,IAchE,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,qBAAqB,GAAI,OAAO,GAAG,CAAC,SAAS,CAAC,OAAO,CAAC,KAAG,MAAM,GAAG,IAkB9E,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;AAEF;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,sBAAsB,GAClC,MAAM,OAAO,EACb,MAAM,MAAM,EACZ,OAAO,GAAG,CAAC,OAAO,CAAC,KACjB,OAYF,CAAC;AAEF;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,kBAAkB,GAAI,MAAM,MAAM,KAAG,MAc/C,CAAC"}
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` without interpolation, and
36
- * `BinaryExpression` with the `+` operator (string concatenation).
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' && expr.expressions.length === 0) {
46
- return expr.quasis.map((q) => q.value.cooked ?? q.value.raw).join('');
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fuzdev/fuz_util",
3
- "version": "0.49.0",
3
+ "version": "0.49.2",
4
4
  "description": "utility belt for JS",
5
5
  "glyph": "🦕",
6
6
  "logo": "logo.svg",
@@ -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` without interpolation, and
57
- * `BinaryExpression` with the `+` operator (string concatenation).
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 = (expr: Expression): string | null => {
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' && expr.expressions.length === 0) {
66
- return expr.quasis.map((q) => q.value.cooked ?? q.value.raw).join('');
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 = (value: AST.Attribute['value']): string | null => {
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;