@fuzdev/fuz_util 0.49.2 → 0.50.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
*
|
|
12
12
|
* @module
|
|
13
13
|
*/
|
|
14
|
-
import type { Expression, ImportDeclaration, ImportDefaultSpecifier, ImportSpecifier } from 'estree';
|
|
14
|
+
import type { Expression, ImportDeclaration, ImportDefaultSpecifier, ImportSpecifier, VariableDeclaration } from 'estree';
|
|
15
15
|
import type { AST } from 'svelte/compiler';
|
|
16
16
|
/** Import metadata for a single import specifier. */
|
|
17
17
|
export interface PreprocessImportInfo {
|
|
@@ -67,6 +67,33 @@ export declare const evaluate_static_expr: (expr: Expression, bindings?: Readonl
|
|
|
67
67
|
* @returns The resolved static string, or `null` if the value is dynamic.
|
|
68
68
|
*/
|
|
69
69
|
export declare const extract_static_string: (value: AST.Attribute["value"], bindings?: ReadonlyMap<string, string>) => string | null;
|
|
70
|
+
/** A single branch in a conditional chain extracted from nested ternary expressions. */
|
|
71
|
+
export interface ConditionalChainBranch {
|
|
72
|
+
/** The source text of the test expression, or `null` for the final else branch. */
|
|
73
|
+
test_source: string | null;
|
|
74
|
+
/** The resolved static string value for this branch. */
|
|
75
|
+
value: string;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Extracts a chain of conditional expressions where all leaf values are static strings.
|
|
79
|
+
*
|
|
80
|
+
* Handles nested ternaries like `a ? 'x' : b ? 'y' : 'z'` by iteratively walking
|
|
81
|
+
* the right-recursive `ConditionalExpression` chain. At each level, evaluates the
|
|
82
|
+
* consequent via `evaluate_static_expr` and continues into the alternate if it is
|
|
83
|
+
* another `ConditionalExpression`. The final alternate is the else branch.
|
|
84
|
+
*
|
|
85
|
+
* Returns `null` if the attribute value is not an `ExpressionTag` containing a
|
|
86
|
+
* `ConditionalExpression`, if any leaf fails to resolve to a static string, or
|
|
87
|
+
* if the chain exceeds 10 branches (safety limit).
|
|
88
|
+
*
|
|
89
|
+
* A 2-branch result covers the simple ternary case (`a ? 'x' : 'y'`).
|
|
90
|
+
*
|
|
91
|
+
* @param value The attribute value from `AST.Attribute['value']`.
|
|
92
|
+
* @param source The full source string (needed to slice test expression source text).
|
|
93
|
+
* @param bindings Map of variable names to their resolved static string values.
|
|
94
|
+
* @returns Array of conditional chain branches, or `null` if not extractable.
|
|
95
|
+
*/
|
|
96
|
+
export declare const try_extract_conditional_chain: (value: AST.Attribute["value"], source: string, bindings: ReadonlyMap<string, string>) => Array<ConditionalChainBranch> | null;
|
|
70
97
|
/**
|
|
71
98
|
* Builds a map of statically resolvable `const` bindings from a Svelte AST.
|
|
72
99
|
*
|
|
@@ -153,4 +180,70 @@ export declare const has_identifier_in_tree: (node: unknown, name: string, skip?
|
|
|
153
180
|
* manual escaping is required to match the runtime behavior.
|
|
154
181
|
*/
|
|
155
182
|
export declare const escape_svelte_text: (text: string) => string;
|
|
183
|
+
/**
|
|
184
|
+
* Removes a single-declarator `VariableDeclaration` from source using MagicString.
|
|
185
|
+
*
|
|
186
|
+
* Consumes leading whitespace (tabs/spaces) and trailing newline to avoid leaving
|
|
187
|
+
* blank lines. Only safe for single-declarator statements (`const x = 'val';`);
|
|
188
|
+
* callers must verify `node.declarations.length === 1` before calling.
|
|
189
|
+
*
|
|
190
|
+
* @param s The MagicString instance to modify.
|
|
191
|
+
* @param declaration_node The `VariableDeclaration` AST node with Svelte position data.
|
|
192
|
+
* @param source The original source string.
|
|
193
|
+
*/
|
|
194
|
+
export declare const remove_variable_declaration: (s: {
|
|
195
|
+
remove: (start: number, end: number) => unknown;
|
|
196
|
+
}, declaration_node: VariableDeclaration & {
|
|
197
|
+
start: number;
|
|
198
|
+
end: number;
|
|
199
|
+
}, source: string) => void;
|
|
200
|
+
/**
|
|
201
|
+
* Removes an `ImportDeclaration` from source using MagicString.
|
|
202
|
+
*
|
|
203
|
+
* Consumes leading whitespace (tabs/spaces) and trailing newline to avoid leaving
|
|
204
|
+
* blank lines.
|
|
205
|
+
*
|
|
206
|
+
* @param s The MagicString instance to modify.
|
|
207
|
+
* @param import_node The `ImportDeclaration` AST node with Svelte position data.
|
|
208
|
+
* @param source The original source string.
|
|
209
|
+
*/
|
|
210
|
+
export declare const remove_import_declaration: (s: {
|
|
211
|
+
remove: (start: number, end: number) => unknown;
|
|
212
|
+
}, import_node: ImportDeclaration & {
|
|
213
|
+
start: number;
|
|
214
|
+
end: number;
|
|
215
|
+
}, source: string) => void;
|
|
216
|
+
/**
|
|
217
|
+
* Removes a specifier from a multi-specifier import declaration by
|
|
218
|
+
* reconstructing the statement without the removed specifier.
|
|
219
|
+
*
|
|
220
|
+
* Overwrites the entire declaration range to avoid character-level comma surgery.
|
|
221
|
+
*
|
|
222
|
+
* Handles:
|
|
223
|
+
* - `import Mdz, {other} from '...'` → `import {other} from '...'`
|
|
224
|
+
* - `import {default as Mdz, other} from '...'` → `import {other} from '...'`
|
|
225
|
+
* - `import {Mdz, other} from '...'` → `import {other} from '...'`
|
|
226
|
+
*
|
|
227
|
+
* @param s The MagicString instance to modify.
|
|
228
|
+
* @param node The positioned `ImportDeclaration` AST node.
|
|
229
|
+
* @param specifier_to_remove The specifier to remove from the import.
|
|
230
|
+
* @param source The original source string.
|
|
231
|
+
* @param additional_lines Extra content appended after the reconstructed import
|
|
232
|
+
* (used to bundle new imports into the overwrite to avoid MagicString boundary conflicts).
|
|
233
|
+
*/
|
|
234
|
+
export declare const remove_import_specifier: (s: {
|
|
235
|
+
overwrite: (start: number, end: number, content: string) => unknown;
|
|
236
|
+
}, node: ImportDeclaration & {
|
|
237
|
+
start: number;
|
|
238
|
+
end: number;
|
|
239
|
+
}, specifier_to_remove: ImportDeclaration["specifiers"][number], source: string, additional_lines?: string) => void;
|
|
240
|
+
/**
|
|
241
|
+
* Handles errors during Svelte preprocessing with configurable behavior.
|
|
242
|
+
*
|
|
243
|
+
* @param error The caught error.
|
|
244
|
+
* @param prefix Log prefix (e.g. `'[fuz-mdz]'`, `'[fuz-code]'`).
|
|
245
|
+
* @param filename The file being processed.
|
|
246
|
+
* @param on_error `'throw'` to re-throw as a new Error, `'log'` to console.error.
|
|
247
|
+
*/
|
|
248
|
+
export declare const handle_preprocess_error: (error: unknown, prefix: string, filename: string | undefined, on_error: "throw" | "log") => void;
|
|
156
249
|
//# sourceMappingURL=svelte_preprocess_helpers.d.ts.map
|
|
@@ -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,
|
|
1
|
+
{"version":3,"file":"svelte_preprocess_helpers.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/svelte_preprocess_helpers.ts"],"names":[],"mappings":"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"}
|
|
@@ -112,6 +112,57 @@ export const extract_static_string = (value, bindings) => {
|
|
|
112
112
|
return null;
|
|
113
113
|
return evaluate_static_expr(expr, bindings);
|
|
114
114
|
};
|
|
115
|
+
/**
|
|
116
|
+
* Extracts a chain of conditional expressions where all leaf values are static strings.
|
|
117
|
+
*
|
|
118
|
+
* Handles nested ternaries like `a ? 'x' : b ? 'y' : 'z'` by iteratively walking
|
|
119
|
+
* the right-recursive `ConditionalExpression` chain. At each level, evaluates the
|
|
120
|
+
* consequent via `evaluate_static_expr` and continues into the alternate if it is
|
|
121
|
+
* another `ConditionalExpression`. The final alternate is the else branch.
|
|
122
|
+
*
|
|
123
|
+
* Returns `null` if the attribute value is not an `ExpressionTag` containing a
|
|
124
|
+
* `ConditionalExpression`, if any leaf fails to resolve to a static string, or
|
|
125
|
+
* if the chain exceeds 10 branches (safety limit).
|
|
126
|
+
*
|
|
127
|
+
* A 2-branch result covers the simple ternary case (`a ? 'x' : 'y'`).
|
|
128
|
+
*
|
|
129
|
+
* @param value The attribute value from `AST.Attribute['value']`.
|
|
130
|
+
* @param source The full source string (needed to slice test expression source text).
|
|
131
|
+
* @param bindings Map of variable names to their resolved static string values.
|
|
132
|
+
* @returns Array of conditional chain branches, or `null` if not extractable.
|
|
133
|
+
*/
|
|
134
|
+
export const try_extract_conditional_chain = (value, source, bindings) => {
|
|
135
|
+
if (value === true || Array.isArray(value))
|
|
136
|
+
return null;
|
|
137
|
+
const expr = value.expression;
|
|
138
|
+
if (expr.type !== 'ConditionalExpression')
|
|
139
|
+
return null;
|
|
140
|
+
const MAX_BRANCHES = 10;
|
|
141
|
+
const branches = [];
|
|
142
|
+
let current = expr;
|
|
143
|
+
for (;;) {
|
|
144
|
+
const consequent = evaluate_static_expr(current.consequent, bindings);
|
|
145
|
+
if (consequent === null)
|
|
146
|
+
return null;
|
|
147
|
+
const test = current.test;
|
|
148
|
+
const test_source = source.slice(test.start, test.end);
|
|
149
|
+
branches.push({ test_source, value: consequent });
|
|
150
|
+
if (branches.length >= MAX_BRANCHES)
|
|
151
|
+
return null;
|
|
152
|
+
if (current.alternate.type === 'ConditionalExpression') {
|
|
153
|
+
current = current.alternate;
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
// Final else branch
|
|
157
|
+
const alternate = evaluate_static_expr(current.alternate, bindings);
|
|
158
|
+
if (alternate === null)
|
|
159
|
+
return null;
|
|
160
|
+
branches.push({ test_source: null, value: alternate });
|
|
161
|
+
break;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return branches;
|
|
165
|
+
};
|
|
115
166
|
// TODO cross-import tracing: resolve `import {x} from './constants.js'` by reading
|
|
116
167
|
// and parsing the imported module, extracting `export const` values. Would need path
|
|
117
168
|
// resolution ($lib, tsconfig paths), a Program-node variant of this function, and
|
|
@@ -330,3 +381,128 @@ export const escape_svelte_text = (text) => text.replace(/[{}<&]/g, (ch) => {
|
|
|
330
381
|
return ch;
|
|
331
382
|
}
|
|
332
383
|
});
|
|
384
|
+
/**
|
|
385
|
+
* Removes a single-declarator `VariableDeclaration` from source using MagicString.
|
|
386
|
+
*
|
|
387
|
+
* Consumes leading whitespace (tabs/spaces) and trailing newline to avoid leaving
|
|
388
|
+
* blank lines. Only safe for single-declarator statements (`const x = 'val';`);
|
|
389
|
+
* callers must verify `node.declarations.length === 1` before calling.
|
|
390
|
+
*
|
|
391
|
+
* @param s The MagicString instance to modify.
|
|
392
|
+
* @param declaration_node The `VariableDeclaration` AST node with Svelte position data.
|
|
393
|
+
* @param source The original source string.
|
|
394
|
+
*/
|
|
395
|
+
export const remove_variable_declaration = (s, declaration_node, source) => {
|
|
396
|
+
remove_positioned_node(s, declaration_node, source);
|
|
397
|
+
};
|
|
398
|
+
/**
|
|
399
|
+
* Removes an `ImportDeclaration` from source using MagicString.
|
|
400
|
+
*
|
|
401
|
+
* Consumes leading whitespace (tabs/spaces) and trailing newline to avoid leaving
|
|
402
|
+
* blank lines.
|
|
403
|
+
*
|
|
404
|
+
* @param s The MagicString instance to modify.
|
|
405
|
+
* @param import_node The `ImportDeclaration` AST node with Svelte position data.
|
|
406
|
+
* @param source The original source string.
|
|
407
|
+
*/
|
|
408
|
+
export const remove_import_declaration = (s, import_node, source) => {
|
|
409
|
+
remove_positioned_node(s, import_node, source);
|
|
410
|
+
};
|
|
411
|
+
/**
|
|
412
|
+
* Removes a positioned AST node from source, consuming surrounding whitespace.
|
|
413
|
+
*
|
|
414
|
+
* Consumes leading whitespace (tabs/spaces) and trailing newline to avoid leaving
|
|
415
|
+
* blank lines. Shared implementation for `remove_variable_declaration` and
|
|
416
|
+
* `remove_import_declaration`.
|
|
417
|
+
*/
|
|
418
|
+
const remove_positioned_node = (s, node, source) => {
|
|
419
|
+
let start = node.start;
|
|
420
|
+
let end = node.end;
|
|
421
|
+
// Consume trailing newline
|
|
422
|
+
if (source[end] === '\n') {
|
|
423
|
+
end++;
|
|
424
|
+
}
|
|
425
|
+
else if (source[end] === '\r' && source[end + 1] === '\n') {
|
|
426
|
+
end += 2;
|
|
427
|
+
}
|
|
428
|
+
// Consume leading whitespace on the same line
|
|
429
|
+
while (start > 0 && (source[start - 1] === '\t' || source[start - 1] === ' ')) {
|
|
430
|
+
start--;
|
|
431
|
+
}
|
|
432
|
+
s.remove(start, end);
|
|
433
|
+
};
|
|
434
|
+
/**
|
|
435
|
+
* Removes a specifier from a multi-specifier import declaration by
|
|
436
|
+
* reconstructing the statement without the removed specifier.
|
|
437
|
+
*
|
|
438
|
+
* Overwrites the entire declaration range to avoid character-level comma surgery.
|
|
439
|
+
*
|
|
440
|
+
* Handles:
|
|
441
|
+
* - `import Mdz, {other} from '...'` → `import {other} from '...'`
|
|
442
|
+
* - `import {default as Mdz, other} from '...'` → `import {other} from '...'`
|
|
443
|
+
* - `import {Mdz, other} from '...'` → `import {other} from '...'`
|
|
444
|
+
*
|
|
445
|
+
* @param s The MagicString instance to modify.
|
|
446
|
+
* @param node The positioned `ImportDeclaration` AST node.
|
|
447
|
+
* @param specifier_to_remove The specifier to remove from the import.
|
|
448
|
+
* @param source The original source string.
|
|
449
|
+
* @param additional_lines Extra content appended after the reconstructed import
|
|
450
|
+
* (used to bundle new imports into the overwrite to avoid MagicString boundary conflicts).
|
|
451
|
+
*/
|
|
452
|
+
export const remove_import_specifier = (s, node, specifier_to_remove, source, additional_lines = '') => {
|
|
453
|
+
const remaining = node.specifiers.filter((spec) => spec !== specifier_to_remove);
|
|
454
|
+
if (remaining.length === 0)
|
|
455
|
+
return;
|
|
456
|
+
const source_path = node.source.value;
|
|
457
|
+
// Reconstruct the import statement
|
|
458
|
+
const default_specs = remaining.filter((sp) => sp.type === 'ImportDefaultSpecifier');
|
|
459
|
+
const named_specs = remaining.filter((sp) => sp.type === 'ImportSpecifier' || sp.type === 'ImportNamespaceSpecifier');
|
|
460
|
+
let import_clause = '';
|
|
461
|
+
if (default_specs.length > 0) {
|
|
462
|
+
import_clause = default_specs[0].local.name;
|
|
463
|
+
if (named_specs.length > 0) {
|
|
464
|
+
import_clause += `, {${format_named_specifiers(named_specs)}}`;
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
else if (named_specs.length > 0) {
|
|
468
|
+
import_clause = `{${format_named_specifiers(named_specs)}}`;
|
|
469
|
+
}
|
|
470
|
+
const reconstructed = `import ${import_clause} from '${source_path}';`;
|
|
471
|
+
// Find leading whitespace to preserve indentation
|
|
472
|
+
let line_start = node.start;
|
|
473
|
+
while (line_start > 0 && (source[line_start - 1] === '\t' || source[line_start - 1] === ' ')) {
|
|
474
|
+
line_start--;
|
|
475
|
+
}
|
|
476
|
+
const indent = source.slice(line_start, node.start);
|
|
477
|
+
s.overwrite(line_start, node.end, `${indent}${reconstructed}${additional_lines}`);
|
|
478
|
+
};
|
|
479
|
+
/** Formats named/namespace specifiers as comma-separated string. */
|
|
480
|
+
const format_named_specifiers = (specs) => specs
|
|
481
|
+
.map((spec) => {
|
|
482
|
+
if (spec.type === 'ImportNamespaceSpecifier')
|
|
483
|
+
return `* as ${spec.local.name}`;
|
|
484
|
+
if (spec.type !== 'ImportSpecifier')
|
|
485
|
+
return spec.local.name;
|
|
486
|
+
const imported_name = spec.imported.type === 'Identifier' ? spec.imported.name : spec.imported.value;
|
|
487
|
+
if (imported_name !== spec.local.name) {
|
|
488
|
+
return `${imported_name} as ${spec.local.name}`;
|
|
489
|
+
}
|
|
490
|
+
return spec.local.name;
|
|
491
|
+
})
|
|
492
|
+
.join(', ');
|
|
493
|
+
/**
|
|
494
|
+
* Handles errors during Svelte preprocessing with configurable behavior.
|
|
495
|
+
*
|
|
496
|
+
* @param error The caught error.
|
|
497
|
+
* @param prefix Log prefix (e.g. `'[fuz-mdz]'`, `'[fuz-code]'`).
|
|
498
|
+
* @param filename The file being processed.
|
|
499
|
+
* @param on_error `'throw'` to re-throw as a new Error, `'log'` to console.error.
|
|
500
|
+
*/
|
|
501
|
+
export const handle_preprocess_error = (error, prefix, filename, on_error) => {
|
|
502
|
+
const message = `${prefix} Preprocessing failed${filename ? ` in ${filename}` : ''}: ${error instanceof Error ? error.message : String(error)}`;
|
|
503
|
+
if (on_error === 'throw') {
|
|
504
|
+
throw new Error(message, { cause: error });
|
|
505
|
+
}
|
|
506
|
+
// eslint-disable-next-line no-console
|
|
507
|
+
console.error(message);
|
|
508
|
+
};
|
package/package.json
CHANGED
|
@@ -12,7 +12,14 @@
|
|
|
12
12
|
* @module
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
-
import type {
|
|
15
|
+
import type {
|
|
16
|
+
ConditionalExpression,
|
|
17
|
+
Expression,
|
|
18
|
+
ImportDeclaration,
|
|
19
|
+
ImportDefaultSpecifier,
|
|
20
|
+
ImportSpecifier,
|
|
21
|
+
VariableDeclaration,
|
|
22
|
+
} from 'estree';
|
|
16
23
|
import type {AST} from 'svelte/compiler';
|
|
17
24
|
|
|
18
25
|
/** Import metadata for a single import specifier. */
|
|
@@ -136,6 +143,70 @@ export const extract_static_string = (
|
|
|
136
143
|
return evaluate_static_expr(expr, bindings);
|
|
137
144
|
};
|
|
138
145
|
|
|
146
|
+
/** A single branch in a conditional chain extracted from nested ternary expressions. */
|
|
147
|
+
export interface ConditionalChainBranch {
|
|
148
|
+
/** The source text of the test expression, or `null` for the final else branch. */
|
|
149
|
+
test_source: string | null;
|
|
150
|
+
/** The resolved static string value for this branch. */
|
|
151
|
+
value: string;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Extracts a chain of conditional expressions where all leaf values are static strings.
|
|
156
|
+
*
|
|
157
|
+
* Handles nested ternaries like `a ? 'x' : b ? 'y' : 'z'` by iteratively walking
|
|
158
|
+
* the right-recursive `ConditionalExpression` chain. At each level, evaluates the
|
|
159
|
+
* consequent via `evaluate_static_expr` and continues into the alternate if it is
|
|
160
|
+
* another `ConditionalExpression`. The final alternate is the else branch.
|
|
161
|
+
*
|
|
162
|
+
* Returns `null` if the attribute value is not an `ExpressionTag` containing a
|
|
163
|
+
* `ConditionalExpression`, if any leaf fails to resolve to a static string, or
|
|
164
|
+
* if the chain exceeds 10 branches (safety limit).
|
|
165
|
+
*
|
|
166
|
+
* A 2-branch result covers the simple ternary case (`a ? 'x' : 'y'`).
|
|
167
|
+
*
|
|
168
|
+
* @param value The attribute value from `AST.Attribute['value']`.
|
|
169
|
+
* @param source The full source string (needed to slice test expression source text).
|
|
170
|
+
* @param bindings Map of variable names to their resolved static string values.
|
|
171
|
+
* @returns Array of conditional chain branches, or `null` if not extractable.
|
|
172
|
+
*/
|
|
173
|
+
export const try_extract_conditional_chain = (
|
|
174
|
+
value: AST.Attribute['value'],
|
|
175
|
+
source: string,
|
|
176
|
+
bindings: ReadonlyMap<string, string>,
|
|
177
|
+
): Array<ConditionalChainBranch> | null => {
|
|
178
|
+
if (value === true || Array.isArray(value)) return null;
|
|
179
|
+
const expr = value.expression;
|
|
180
|
+
if (expr.type !== 'ConditionalExpression') return null;
|
|
181
|
+
|
|
182
|
+
const MAX_BRANCHES = 10;
|
|
183
|
+
const branches: Array<ConditionalChainBranch> = [];
|
|
184
|
+
let current: ConditionalExpression = expr;
|
|
185
|
+
|
|
186
|
+
for (;;) {
|
|
187
|
+
const consequent = evaluate_static_expr(current.consequent, bindings);
|
|
188
|
+
if (consequent === null) return null;
|
|
189
|
+
|
|
190
|
+
const test = current.test as any;
|
|
191
|
+
const test_source = source.slice(test.start, test.end);
|
|
192
|
+
branches.push({test_source, value: consequent});
|
|
193
|
+
|
|
194
|
+
if (branches.length >= MAX_BRANCHES) return null;
|
|
195
|
+
|
|
196
|
+
if (current.alternate.type === 'ConditionalExpression') {
|
|
197
|
+
current = current.alternate;
|
|
198
|
+
} else {
|
|
199
|
+
// Final else branch
|
|
200
|
+
const alternate = evaluate_static_expr(current.alternate, bindings);
|
|
201
|
+
if (alternate === null) return null;
|
|
202
|
+
branches.push({test_source: null, value: alternate});
|
|
203
|
+
break;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return branches;
|
|
208
|
+
};
|
|
209
|
+
|
|
139
210
|
// TODO cross-import tracing: resolve `import {x} from './constants.js'` by reading
|
|
140
211
|
// and parsing the imported module, extracting `export const` values. Would need path
|
|
141
212
|
// resolution ($lib, tsconfig paths), a Program-node variant of this function, and
|
|
@@ -364,3 +435,165 @@ export const escape_svelte_text = (text: string): string =>
|
|
|
364
435
|
return ch;
|
|
365
436
|
}
|
|
366
437
|
});
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* Removes a single-declarator `VariableDeclaration` from source using MagicString.
|
|
441
|
+
*
|
|
442
|
+
* Consumes leading whitespace (tabs/spaces) and trailing newline to avoid leaving
|
|
443
|
+
* blank lines. Only safe for single-declarator statements (`const x = 'val';`);
|
|
444
|
+
* callers must verify `node.declarations.length === 1` before calling.
|
|
445
|
+
*
|
|
446
|
+
* @param s The MagicString instance to modify.
|
|
447
|
+
* @param declaration_node The `VariableDeclaration` AST node with Svelte position data.
|
|
448
|
+
* @param source The original source string.
|
|
449
|
+
*/
|
|
450
|
+
export const remove_variable_declaration = (
|
|
451
|
+
s: {remove: (start: number, end: number) => unknown},
|
|
452
|
+
declaration_node: VariableDeclaration & {start: number; end: number},
|
|
453
|
+
source: string,
|
|
454
|
+
): void => {
|
|
455
|
+
remove_positioned_node(s, declaration_node, source);
|
|
456
|
+
};
|
|
457
|
+
|
|
458
|
+
/**
|
|
459
|
+
* Removes an `ImportDeclaration` from source using MagicString.
|
|
460
|
+
*
|
|
461
|
+
* Consumes leading whitespace (tabs/spaces) and trailing newline to avoid leaving
|
|
462
|
+
* blank lines.
|
|
463
|
+
*
|
|
464
|
+
* @param s The MagicString instance to modify.
|
|
465
|
+
* @param import_node The `ImportDeclaration` AST node with Svelte position data.
|
|
466
|
+
* @param source The original source string.
|
|
467
|
+
*/
|
|
468
|
+
export const remove_import_declaration = (
|
|
469
|
+
s: {remove: (start: number, end: number) => unknown},
|
|
470
|
+
import_node: ImportDeclaration & {start: number; end: number},
|
|
471
|
+
source: string,
|
|
472
|
+
): void => {
|
|
473
|
+
remove_positioned_node(s, import_node, source);
|
|
474
|
+
};
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* Removes a positioned AST node from source, consuming surrounding whitespace.
|
|
478
|
+
*
|
|
479
|
+
* Consumes leading whitespace (tabs/spaces) and trailing newline to avoid leaving
|
|
480
|
+
* blank lines. Shared implementation for `remove_variable_declaration` and
|
|
481
|
+
* `remove_import_declaration`.
|
|
482
|
+
*/
|
|
483
|
+
const remove_positioned_node = (
|
|
484
|
+
s: {remove: (start: number, end: number) => unknown},
|
|
485
|
+
node: {start: number; end: number},
|
|
486
|
+
source: string,
|
|
487
|
+
): void => {
|
|
488
|
+
let start: number = node.start;
|
|
489
|
+
let end: number = node.end;
|
|
490
|
+
|
|
491
|
+
// Consume trailing newline
|
|
492
|
+
if (source[end] === '\n') {
|
|
493
|
+
end++;
|
|
494
|
+
} else if (source[end] === '\r' && source[end + 1] === '\n') {
|
|
495
|
+
end += 2;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
// Consume leading whitespace on the same line
|
|
499
|
+
while (start > 0 && (source[start - 1] === '\t' || source[start - 1] === ' ')) {
|
|
500
|
+
start--;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
s.remove(start, end);
|
|
504
|
+
};
|
|
505
|
+
|
|
506
|
+
/**
|
|
507
|
+
* Removes a specifier from a multi-specifier import declaration by
|
|
508
|
+
* reconstructing the statement without the removed specifier.
|
|
509
|
+
*
|
|
510
|
+
* Overwrites the entire declaration range to avoid character-level comma surgery.
|
|
511
|
+
*
|
|
512
|
+
* Handles:
|
|
513
|
+
* - `import Mdz, {other} from '...'` → `import {other} from '...'`
|
|
514
|
+
* - `import {default as Mdz, other} from '...'` → `import {other} from '...'`
|
|
515
|
+
* - `import {Mdz, other} from '...'` → `import {other} from '...'`
|
|
516
|
+
*
|
|
517
|
+
* @param s The MagicString instance to modify.
|
|
518
|
+
* @param node The positioned `ImportDeclaration` AST node.
|
|
519
|
+
* @param specifier_to_remove The specifier to remove from the import.
|
|
520
|
+
* @param source The original source string.
|
|
521
|
+
* @param additional_lines Extra content appended after the reconstructed import
|
|
522
|
+
* (used to bundle new imports into the overwrite to avoid MagicString boundary conflicts).
|
|
523
|
+
*/
|
|
524
|
+
export const remove_import_specifier = (
|
|
525
|
+
s: {overwrite: (start: number, end: number, content: string) => unknown},
|
|
526
|
+
node: ImportDeclaration & {start: number; end: number},
|
|
527
|
+
specifier_to_remove: ImportDeclaration['specifiers'][number],
|
|
528
|
+
source: string,
|
|
529
|
+
additional_lines: string = '',
|
|
530
|
+
): void => {
|
|
531
|
+
const remaining = node.specifiers.filter((spec) => spec !== specifier_to_remove);
|
|
532
|
+
if (remaining.length === 0) return;
|
|
533
|
+
|
|
534
|
+
const source_path = node.source.value as string;
|
|
535
|
+
|
|
536
|
+
// Reconstruct the import statement
|
|
537
|
+
const default_specs = remaining.filter((sp) => sp.type === 'ImportDefaultSpecifier');
|
|
538
|
+
const named_specs = remaining.filter(
|
|
539
|
+
(sp) => sp.type === 'ImportSpecifier' || sp.type === 'ImportNamespaceSpecifier',
|
|
540
|
+
);
|
|
541
|
+
|
|
542
|
+
let import_clause = '';
|
|
543
|
+
if (default_specs.length > 0) {
|
|
544
|
+
import_clause = default_specs[0]!.local.name;
|
|
545
|
+
if (named_specs.length > 0) {
|
|
546
|
+
import_clause += `, {${format_named_specifiers(named_specs)}}`;
|
|
547
|
+
}
|
|
548
|
+
} else if (named_specs.length > 0) {
|
|
549
|
+
import_clause = `{${format_named_specifiers(named_specs)}}`;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
const reconstructed = `import ${import_clause} from '${source_path}';`;
|
|
553
|
+
|
|
554
|
+
// Find leading whitespace to preserve indentation
|
|
555
|
+
let line_start = node.start;
|
|
556
|
+
while (line_start > 0 && (source[line_start - 1] === '\t' || source[line_start - 1] === ' ')) {
|
|
557
|
+
line_start--;
|
|
558
|
+
}
|
|
559
|
+
const indent = source.slice(line_start, node.start);
|
|
560
|
+
|
|
561
|
+
s.overwrite(line_start, node.end, `${indent}${reconstructed}${additional_lines}`);
|
|
562
|
+
};
|
|
563
|
+
|
|
564
|
+
/** Formats named/namespace specifiers as comma-separated string. */
|
|
565
|
+
const format_named_specifiers = (specs: Array<ImportDeclaration['specifiers'][number]>): string =>
|
|
566
|
+
specs
|
|
567
|
+
.map((spec) => {
|
|
568
|
+
if (spec.type === 'ImportNamespaceSpecifier') return `* as ${spec.local.name}`;
|
|
569
|
+
if (spec.type !== 'ImportSpecifier') return spec.local.name;
|
|
570
|
+
const imported_name =
|
|
571
|
+
spec.imported.type === 'Identifier' ? spec.imported.name : spec.imported.value;
|
|
572
|
+
if (imported_name !== spec.local.name) {
|
|
573
|
+
return `${imported_name} as ${spec.local.name}`;
|
|
574
|
+
}
|
|
575
|
+
return spec.local.name;
|
|
576
|
+
})
|
|
577
|
+
.join(', ');
|
|
578
|
+
|
|
579
|
+
/**
|
|
580
|
+
* Handles errors during Svelte preprocessing with configurable behavior.
|
|
581
|
+
*
|
|
582
|
+
* @param error The caught error.
|
|
583
|
+
* @param prefix Log prefix (e.g. `'[fuz-mdz]'`, `'[fuz-code]'`).
|
|
584
|
+
* @param filename The file being processed.
|
|
585
|
+
* @param on_error `'throw'` to re-throw as a new Error, `'log'` to console.error.
|
|
586
|
+
*/
|
|
587
|
+
export const handle_preprocess_error = (
|
|
588
|
+
error: unknown,
|
|
589
|
+
prefix: string,
|
|
590
|
+
filename: string | undefined,
|
|
591
|
+
on_error: 'throw' | 'log',
|
|
592
|
+
): void => {
|
|
593
|
+
const message = `${prefix} Preprocessing failed${filename ? ` in ${filename}` : ''}: ${error instanceof Error ? error.message : String(error)}`;
|
|
594
|
+
if (on_error === 'throw') {
|
|
595
|
+
throw new Error(message, {cause: error});
|
|
596
|
+
}
|
|
597
|
+
// eslint-disable-next-line no-console
|
|
598
|
+
console.error(message);
|
|
599
|
+
};
|