filesystem-routing 0.4.2 → 0.5.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.
@@ -0,0 +1,935 @@
1
+ // All credit for this work goes to the amazing Next.js team.
2
+ // https://github.com/vercel/next.js/blob/canary/packages/next/build/babel/plugins/next-ssg-transform.ts
3
+ import { extname } from "node:path";
4
+ import MagicString from "magic-string";
5
+ import { parseSync } from "oxc-parser";
6
+ const TYPE_ONLY_ANCESTORS = new Set([
7
+ "TSArrayType",
8
+ "TSClassImplements",
9
+ "TSConditionalType",
10
+ "TSConstructorType",
11
+ "TSFunctionType",
12
+ "TSImportType",
13
+ "TSIndexedAccessType",
14
+ "TSInferType",
15
+ "TSInterfaceHeritage",
16
+ "TSIntersectionType",
17
+ "TSLiteralType",
18
+ "TSMappedType",
19
+ "TSOptionalType",
20
+ "TSParenthesizedType",
21
+ "TSRestType",
22
+ "TSTemplateLiteralType",
23
+ "TSThisType",
24
+ "TSTupleType",
25
+ "TSTypeLiteral",
26
+ "TSTypeOperator",
27
+ "TSTypePredicate",
28
+ "TSTypeQuery",
29
+ "TSTypeReference",
30
+ "TSUnionType"
31
+ ]);
32
+ function forEachChild(node, visit) {
33
+ for (const key in node) {
34
+ const value = node[key];
35
+ if (Array.isArray(value)) {
36
+ for (const item of value)
37
+ if (item && typeof item.type === "string")
38
+ visit(item);
39
+ }
40
+ else if (value && typeof value.type === "string") {
41
+ visit(value);
42
+ }
43
+ }
44
+ }
45
+ function unwrapParentheses(node) {
46
+ for (const key in node) {
47
+ const value = node[key];
48
+ if (Array.isArray(value)) {
49
+ value.forEach((item, index) => {
50
+ while (item?.type === "ParenthesizedExpression")
51
+ value[index] = item = item.expression;
52
+ });
53
+ }
54
+ else {
55
+ let inner = value;
56
+ while (inner?.type === "ParenthesizedExpression")
57
+ node[key] = inner = inner.expression;
58
+ }
59
+ }
60
+ }
61
+ function isFunctionLike(node) {
62
+ return (node.type === "FunctionDeclaration" ||
63
+ node.type === "FunctionExpression" ||
64
+ node.type === "ArrowFunctionExpression");
65
+ }
66
+ function isClassLike(node) {
67
+ return node.type === "ClassDeclaration" || node.type === "ClassExpression";
68
+ }
69
+ function isNamedDeclaration(node) {
70
+ return node.type === "FunctionDeclaration" || node.type === "ClassDeclaration";
71
+ }
72
+ function isExportWrapper(node) {
73
+ return node.type === "ExportNamedDeclaration" || node.type === "ExportDefaultDeclaration";
74
+ }
75
+ function isReference(node, parent, grandparent) {
76
+ if (node.type === "JSXIdentifier" &&
77
+ parent.type !== "JSXMemberExpression" &&
78
+ /^[a-z]/.test(node.name)) {
79
+ return false;
80
+ }
81
+ switch (parent.type) {
82
+ case "MemberExpression":
83
+ return parent.property === node ? parent.computed : true;
84
+ case "JSXMemberExpression":
85
+ return parent.object === node;
86
+ case "VariableDeclarator":
87
+ return parent.init === node;
88
+ case "ArrowFunctionExpression":
89
+ return parent.body === node;
90
+ case "Property":
91
+ if (parent.key === node)
92
+ return parent.computed;
93
+ return grandparent?.type !== "ObjectPattern";
94
+ case "MethodDefinition":
95
+ case "TSAbstractMethodDefinition":
96
+ return parent.key === node ? parent.computed : false;
97
+ case "PropertyDefinition":
98
+ case "AccessorProperty":
99
+ case "TSAbstractPropertyDefinition":
100
+ case "TSAbstractAccessorProperty":
101
+ case "TSPropertySignature":
102
+ return parent.key === node ? parent.computed : true;
103
+ case "ClassDeclaration":
104
+ case "ClassExpression":
105
+ return parent.superClass === node;
106
+ case "AssignmentExpression":
107
+ case "AssignmentPattern":
108
+ return parent.right === node;
109
+ case "ExportSpecifier":
110
+ return parent.local === node && !grandparent?.source;
111
+ case "TSEnumMember":
112
+ return parent.id !== node;
113
+ case "TSQualifiedName":
114
+ return parent.right !== node;
115
+ case "TSTypeParameter":
116
+ return parent.name !== node;
117
+ case "TSMappedType":
118
+ return parent.key !== node;
119
+ case "LabeledStatement":
120
+ case "CatchClause":
121
+ case "RestElement":
122
+ case "BreakStatement":
123
+ case "ContinueStatement":
124
+ case "FunctionDeclaration":
125
+ case "FunctionExpression":
126
+ case "ExportAllDeclaration":
127
+ case "ImportDefaultSpecifier":
128
+ case "ImportNamespaceSpecifier":
129
+ case "ImportSpecifier":
130
+ case "ImportAttribute":
131
+ case "JSXAttribute":
132
+ case "ObjectPattern":
133
+ case "ArrayPattern":
134
+ case "MetaProperty":
135
+ return false;
136
+ default:
137
+ return true;
138
+ }
139
+ }
140
+ function patternIdentifiers(pattern, out = []) {
141
+ switch (pattern?.type) {
142
+ case "Identifier":
143
+ out.push(pattern);
144
+ break;
145
+ case "ObjectPattern":
146
+ for (const property of pattern.properties) {
147
+ patternIdentifiers(property.type === "Property" ? property.value : property, out);
148
+ }
149
+ break;
150
+ case "ArrayPattern":
151
+ for (const element of pattern.elements)
152
+ patternIdentifiers(element, out);
153
+ break;
154
+ case "AssignmentPattern":
155
+ patternIdentifiers(pattern.left, out);
156
+ break;
157
+ case "RestElement":
158
+ patternIdentifiers(pattern.argument, out);
159
+ break;
160
+ case "TSParameterProperty":
161
+ patternIdentifiers(pattern.parameter, out);
162
+ break;
163
+ }
164
+ return out;
165
+ }
166
+ function lookup(start, name) {
167
+ let previous;
168
+ for (let scope = start; scope; previous = scope, scope = scope.parent) {
169
+ const binding = scope.bindings.get(name);
170
+ if (binding) {
171
+ // A parameter default cannot see `var`s declared in the function body.
172
+ if (!previous?.isParams || binding.kind !== "declared")
173
+ return binding;
174
+ }
175
+ else if (name === "arguments" && scope.ownsArguments) {
176
+ return;
177
+ }
178
+ }
179
+ }
180
+ function crawl(program, tree) {
181
+ const { parents, removed, unexported, localDeclarators } = tree;
182
+ const scopes = new Map();
183
+ const references = [];
184
+ const violations = [];
185
+ const childScope = (parent, options) => ({
186
+ parent,
187
+ bindings: new Map(),
188
+ ...options
189
+ });
190
+ const declare = (scope, name, declaration, kind) => {
191
+ const existing = scope.bindings.get(name);
192
+ if (existing)
193
+ existing.violations.push(declaration);
194
+ else
195
+ scope.bindings.set(name, { kind, declaration, references: [], violations: [] });
196
+ };
197
+ const declareExportReferences = (node, scope) => {
198
+ const declaration = node.declaration;
199
+ if (!declaration || unexported.has(node))
200
+ return;
201
+ if (isNamedDeclaration(declaration)) {
202
+ if (declaration.id)
203
+ references.push([declaration.id.name, scope, node]);
204
+ }
205
+ else if (declaration.type === "VariableDeclaration") {
206
+ for (const declarator of declaration.declarations) {
207
+ if (removed.has(declarator) || localDeclarators.has(declarator))
208
+ continue;
209
+ for (const id of patternIdentifiers(declarator.id))
210
+ references.push([id.name, scope, node]);
211
+ }
212
+ }
213
+ };
214
+ const visitChildren = (node, scope) => forEachChild(node, child => visit(child, scope));
215
+ const visit = (node, scope) => {
216
+ if (removed.has(node))
217
+ return;
218
+ switch (node.type) {
219
+ case "TSTypeAnnotation":
220
+ return;
221
+ case "TSImportEqualsDeclaration":
222
+ declare(scope, node.id.name, node, "declared");
223
+ visit(node.moduleReference, scope);
224
+ return;
225
+ case "Identifier":
226
+ case "JSXIdentifier":
227
+ if (isReference(node, parents.get(node), parents.get(parents.get(node)))) {
228
+ references.push([node.name, scope, node]);
229
+ }
230
+ break;
231
+ case "ImportDeclaration":
232
+ for (const specifier of node.specifiers) {
233
+ if (!removed.has(specifier))
234
+ declare(scope, specifier.local.name, specifier, "declared");
235
+ }
236
+ return;
237
+ case "ExportNamedDeclaration":
238
+ case "ExportDefaultDeclaration":
239
+ declareExportReferences(node, scope);
240
+ break;
241
+ case "VariableDeclaration": {
242
+ let target = scope;
243
+ if (node.kind === "var")
244
+ while (!target.hoists && target.parent)
245
+ target = target.parent;
246
+ for (const declarator of node.declarations) {
247
+ if (removed.has(declarator))
248
+ continue;
249
+ for (const id of patternIdentifiers(declarator.id)) {
250
+ declare(target, id.name, declarator, "declared");
251
+ }
252
+ }
253
+ break;
254
+ }
255
+ case "FunctionDeclaration":
256
+ case "FunctionExpression":
257
+ case "ArrowFunctionExpression": {
258
+ if (node.type === "FunctionDeclaration" && node.id) {
259
+ declare(scope, node.id.name, node, "declared");
260
+ }
261
+ const inner = childScope(scope, {
262
+ hoists: true,
263
+ ownsArguments: node.type !== "ArrowFunctionExpression"
264
+ });
265
+ scopes.set(node, inner);
266
+ if (node.type === "FunctionExpression" && node.id) {
267
+ declare(inner, node.id.name, node, "local");
268
+ }
269
+ const params = childScope(inner, { isParams: true });
270
+ for (const param of node.params) {
271
+ for (const id of patternIdentifiers(param))
272
+ declare(inner, id.name, param, "param");
273
+ visit(param, params);
274
+ }
275
+ forEachChild(node, child => {
276
+ if (node.params.includes(child))
277
+ return;
278
+ if (child === node.body && child.type === "BlockStatement") {
279
+ scopes.set(child, inner);
280
+ visitChildren(child, inner);
281
+ }
282
+ else {
283
+ visit(child, inner);
284
+ }
285
+ });
286
+ return;
287
+ }
288
+ case "ClassDeclaration":
289
+ case "ClassExpression": {
290
+ if (node.type === "ClassDeclaration" && node.id && !node.declare) {
291
+ declare(scope, node.id.name, node, "declared");
292
+ }
293
+ const inner = childScope(scope);
294
+ scopes.set(node, inner);
295
+ if (node.type === "ClassExpression" && node.id) {
296
+ declare(inner, node.id.name, node, "local");
297
+ }
298
+ visitChildren(node, inner);
299
+ return;
300
+ }
301
+ case "CatchClause": {
302
+ const inner = childScope(scope);
303
+ scopes.set(node, inner);
304
+ for (const id of patternIdentifiers(node.param))
305
+ declare(inner, id.name, node, "declared");
306
+ if (node.param)
307
+ visit(node.param, inner);
308
+ scopes.set(node.body, inner);
309
+ visitChildren(node.body, inner);
310
+ return;
311
+ }
312
+ case "StaticBlock":
313
+ case "TSModuleBlock": {
314
+ const inner = childScope(scope, { hoists: true });
315
+ scopes.set(node, inner);
316
+ visitChildren(node, inner);
317
+ return;
318
+ }
319
+ case "SwitchStatement": {
320
+ visit(node.discriminant, scope);
321
+ const inner = childScope(scope);
322
+ scopes.set(node, inner);
323
+ for (const switchCase of node.cases)
324
+ visit(switchCase, inner);
325
+ return;
326
+ }
327
+ case "ForInStatement":
328
+ case "ForOfStatement":
329
+ case "ForStatement":
330
+ case "BlockStatement":
331
+ case "WhileStatement":
332
+ case "DoWhileStatement": {
333
+ const inner = childScope(scope);
334
+ scopes.set(node, inner);
335
+ if (node.left && node.left.type !== "VariableDeclaration") {
336
+ for (const id of patternIdentifiers(node.left))
337
+ violations.push([id.name, inner, node]);
338
+ }
339
+ visitChildren(node, inner);
340
+ return;
341
+ }
342
+ case "AssignmentExpression":
343
+ for (const id of patternIdentifiers(node.left))
344
+ violations.push([id.name, scope, node]);
345
+ break;
346
+ case "UpdateExpression":
347
+ if (node.argument.type === "Identifier") {
348
+ violations.push([node.argument.name, scope, node]);
349
+ }
350
+ break;
351
+ case "UnaryExpression":
352
+ if (node.operator === "delete" && node.argument.type === "Identifier") {
353
+ violations.push([node.argument.name, scope, node]);
354
+ }
355
+ break;
356
+ }
357
+ visitChildren(node, scope);
358
+ };
359
+ const root = { bindings: new Map(), hoists: true };
360
+ scopes.set(program, root);
361
+ visitChildren(program, root);
362
+ for (const [name, scope, node] of references)
363
+ lookup(scope, name)?.references.push(node);
364
+ for (const [name, scope, node] of violations)
365
+ lookup(scope, name)?.violations.push(node);
366
+ const bindingOf = (identifier) => {
367
+ let node = parents.get(identifier);
368
+ if (isNamedDeclaration(node))
369
+ node = parents.get(node);
370
+ while (node && !scopes.has(node))
371
+ node = parents.get(node);
372
+ return node && lookup(scopes.get(node), identifier.name);
373
+ };
374
+ return { bindingOf };
375
+ }
376
+ function isTypeOnly(reference, tree) {
377
+ const { parents, localDeclarators } = tree;
378
+ for (let node = reference; node; node = parents.get(node)) {
379
+ if (TYPE_ONLY_ANCESTORS.has(node.type))
380
+ return true;
381
+ if (node.type === "ExportSpecifier" && node.exportKind === "type")
382
+ return true;
383
+ if (node.type === "ExportNamedDeclaration" && node.exportKind === "type") {
384
+ // The picked half of a split `export declare` is emitted as a plain export.
385
+ if (!node.declaration?.declarations?.some((d) => localDeclarators.has(d)))
386
+ return true;
387
+ }
388
+ }
389
+ return false;
390
+ }
391
+ function isInside(node, ancestor, parents) {
392
+ for (let parent = parents.get(node); parent; parent = parents.get(parent)) {
393
+ if (parent === ancestor)
394
+ return true;
395
+ }
396
+ return false;
397
+ }
398
+ function isBindingReferenced(binding, tree, includeTypes) {
399
+ if (!binding)
400
+ return false;
401
+ const references = binding.violations.concat(includeTypes
402
+ ? binding.references
403
+ : binding.references.filter(reference => !isTypeOnly(reference, tree)));
404
+ if (!references.length)
405
+ return false;
406
+ if (binding.declaration.type === "FunctionDeclaration") {
407
+ return !references.every(ref => isInside(ref, binding.declaration, tree.parents));
408
+ }
409
+ return true;
410
+ }
411
+ function boundIdentifier(node, parents) {
412
+ const parent = parents.get(node);
413
+ if (parent.type === "VariableDeclarator")
414
+ return parent.id.type === "Identifier" ? parent.id : null;
415
+ if (parent.type === "AssignmentExpression") {
416
+ return parent.left.type === "Identifier" ? parent.left : null;
417
+ }
418
+ if (node.type === "ArrowFunctionExpression")
419
+ return null;
420
+ return node.id?.type === "Identifier" ? node.id : null;
421
+ }
422
+ // A function or class expression's own name cannot leave it, so it never decides liveness.
423
+ function isOwnExpressionName(identifier, parents) {
424
+ const parent = parents.get(identifier);
425
+ return ((parent.type === "FunctionExpression" || parent.type === "ClassExpression") &&
426
+ parent.id === identifier);
427
+ }
428
+ // These stay when their binding dies, so what they reference must stay live.
429
+ function isKeptWithDeadBinding(identifier, parents) {
430
+ const parent = parents.get(identifier);
431
+ if (parent.type === "AssignmentExpression") {
432
+ return parents.get(parent).type !== "ExpressionStatement";
433
+ }
434
+ return parent.type === "Property" && parents.get(parent).properties.at(-1).type === "RestElement";
435
+ }
436
+ function patternLocals(pattern) {
437
+ if (pattern.type === "ObjectPattern") {
438
+ return pattern.properties.flatMap((property) => {
439
+ const local = property.type === "Property" ? property.value : property.argument;
440
+ return local.type === "Identifier" ? [[property, local]] : [];
441
+ });
442
+ }
443
+ return pattern.elements.flatMap((element) => {
444
+ if (element?.type === "Identifier")
445
+ return [[element, element]];
446
+ if (element?.type === "RestElement" && element.argument.type === "Identifier") {
447
+ return [[element, element.argument]];
448
+ }
449
+ return [];
450
+ });
451
+ }
452
+ function exportedName(specifier) {
453
+ return specifier.exported.name ?? specifier.exported.value;
454
+ }
455
+ function removeStatement(node, tree) {
456
+ tree.removed.add(node);
457
+ const parent = tree.parents.get(node);
458
+ if (isExportWrapper(parent))
459
+ tree.removed.add(parent);
460
+ }
461
+ function removeDeclarator(declarator, tree) {
462
+ const { parents, removed } = tree;
463
+ const declaration = parents.get(declarator);
464
+ const owner = parents.get(declaration);
465
+ // `for (const x of y)` has no valid form without its declaration.
466
+ if ((owner.type === "ForInStatement" || owner.type === "ForOfStatement") &&
467
+ owner.left === declaration) {
468
+ return false;
469
+ }
470
+ removed.add(declarator);
471
+ if (declaration.declarations.every((d) => removed.has(d)))
472
+ removeStatement(declaration, tree);
473
+ return true;
474
+ }
475
+ export function pickExports(code, id, pick) {
476
+ const { program, errors } = parseSync(id, code, {
477
+ // `<T>(x: T) => x` is valid in a .ts file and ambiguous with JSX elsewhere.
478
+ lang: extname(id.split("?")[0]) === ".ts" ? "ts" : "tsx",
479
+ astType: "ts",
480
+ sourceType: "module"
481
+ });
482
+ const error = errors[0];
483
+ if (error)
484
+ throw new SyntaxError(`Failed to parse ${id}:\n${error.codeframe || error.message}`);
485
+ const parents = new Map();
486
+ const link = (node) => {
487
+ unwrapParentheses(node);
488
+ forEachChild(node, child => {
489
+ parents.set(child, node);
490
+ link(child);
491
+ });
492
+ };
493
+ link(program);
494
+ const tree = {
495
+ parents,
496
+ removed: new Set(),
497
+ unexported: new Set(),
498
+ localDeclarators: new Set()
499
+ };
500
+ const { removed, unexported, localDeclarators } = tree;
501
+ const before = crawl(program, tree);
502
+ const candidates = new Set();
503
+ const cssLocals = new Set();
504
+ for (const node of program.body) {
505
+ switch (node.type) {
506
+ case "ImportDeclaration":
507
+ if (node.source.value.endsWith(".css") && !pick.includes("$css")) {
508
+ // A stylesheet imported for its side effect goes, but a binding such
509
+ // as a CSS module's class map stays while picked code reads it.
510
+ if (node.specifiers.length)
511
+ for (const s of node.specifiers)
512
+ cssLocals.add(s.local);
513
+ else
514
+ removed.add(node);
515
+ }
516
+ break;
517
+ case "ExportAllDeclaration":
518
+ if (node.exported && !pick.includes(exportedName(node)))
519
+ removed.add(node);
520
+ break;
521
+ case "ExportDefaultDeclaration": {
522
+ if (pick.includes("default"))
523
+ break;
524
+ const declaration = node.declaration;
525
+ // A named declaration creates a binding that picked exports may reference.
526
+ if (isNamedDeclaration(declaration) && declaration.id) {
527
+ unexported.add(node);
528
+ candidates.add(declaration.id);
529
+ }
530
+ else {
531
+ removed.add(node);
532
+ }
533
+ break;
534
+ }
535
+ case "ExportNamedDeclaration": {
536
+ if (node.specifiers.length) {
537
+ for (const specifier of node.specifiers) {
538
+ if (!pick.includes(exportedName(specifier)))
539
+ removed.add(specifier);
540
+ }
541
+ if (node.specifiers.every((specifier) => removed.has(specifier)))
542
+ removed.add(node);
543
+ break;
544
+ }
545
+ const declaration = node.declaration;
546
+ if (declaration && isNamedDeclaration(declaration)) {
547
+ if (declaration.id && !pick.includes(declaration.id.name)) {
548
+ unexported.add(node);
549
+ candidates.add(declaration.id);
550
+ }
551
+ }
552
+ else if (declaration?.type === "VariableDeclaration") {
553
+ for (const declarator of declaration.declarations) {
554
+ if (declarator.id.type === "Identifier" && !pick.includes(declarator.id.name)) {
555
+ localDeclarators.add(declarator);
556
+ candidates.add(declarator.id);
557
+ }
558
+ }
559
+ }
560
+ break;
561
+ }
562
+ }
563
+ }
564
+ // Code that was already unused may run for its side effects, so it stays.
565
+ const sweepable = new Set([...candidates, ...cssLocals]);
566
+ const markIfReferenced = (identifier) => {
567
+ if (isBindingReferenced(before.bindingOf(identifier), tree, true))
568
+ sweepable.add(identifier);
569
+ };
570
+ const collect = (node) => {
571
+ if (removed.has(node))
572
+ return;
573
+ if (node.type === "VariableDeclarator") {
574
+ if (node.id.type === "Identifier")
575
+ markIfReferenced(node.id);
576
+ else if (node.id.type === "ObjectPattern" || node.id.type === "ArrayPattern") {
577
+ for (const [, local] of patternLocals(node.id))
578
+ markIfReferenced(local);
579
+ }
580
+ }
581
+ else if (isFunctionLike(node) || isClassLike(node)) {
582
+ const identifier = boundIdentifier(node, parents);
583
+ if (identifier)
584
+ markIfReferenced(identifier);
585
+ }
586
+ else if (node.type.startsWith("Import") && node.local) {
587
+ markIfReferenced(node.local);
588
+ }
589
+ forEachChild(node, collect);
590
+ };
591
+ collect(program);
592
+ removeUnreachable(program, tree, sweepable, candidates);
593
+ let changed;
594
+ do {
595
+ changed = false;
596
+ const { bindingOf } = crawl(program, tree);
597
+ const isDead = (identifier) => sweepable.has(identifier) && !isBindingReferenced(bindingOf(identifier), tree, false);
598
+ const sweep = (node) => {
599
+ if (removed.has(node))
600
+ return;
601
+ if (node.type === "VariableDeclarator") {
602
+ if (node.id.type === "Identifier") {
603
+ if (isDead(node.id) && removeDeclarator(node, tree))
604
+ changed = true;
605
+ }
606
+ else if (node.id.type === "ObjectPattern") {
607
+ changed = sweepObjectPattern(node, isDead, tree) || changed;
608
+ }
609
+ else if (node.id.type === "ArrayPattern") {
610
+ changed = sweepArrayPattern(node, isDead, tree) || changed;
611
+ }
612
+ }
613
+ else if (isFunctionLike(node) || isClassLike(node)) {
614
+ const identifier = boundIdentifier(node, parents);
615
+ if (identifier && !isOwnExpressionName(identifier, parents) && isDead(identifier)) {
616
+ const parent = parents.get(node);
617
+ if (parent.type === "VariableDeclarator") {
618
+ changed = removeDeclarator(parent, tree) || changed;
619
+ }
620
+ else if (parent.type === "AssignmentExpression") {
621
+ const statement = parents.get(parent);
622
+ if (statement.type === "ExpressionStatement") {
623
+ removeStatement(statement, tree);
624
+ changed = true;
625
+ }
626
+ }
627
+ else {
628
+ removeStatement(node, tree);
629
+ changed = true;
630
+ }
631
+ }
632
+ }
633
+ else if (node.type === "ImportDeclaration") {
634
+ for (const specifier of node.specifiers) {
635
+ if (!removed.has(specifier) && isDead(specifier.local)) {
636
+ removed.add(specifier);
637
+ changed = true;
638
+ }
639
+ }
640
+ if (node.specifiers.length && node.specifiers.every((s) => removed.has(s))) {
641
+ removed.add(node);
642
+ }
643
+ return;
644
+ }
645
+ if (!removed.has(node))
646
+ forEachChild(node, sweep);
647
+ };
648
+ sweep(program);
649
+ } while (changed);
650
+ const output = new MagicString(code);
651
+ render(program, code, output, tree);
652
+ return {
653
+ code: output.toString(),
654
+ map: output.generateMap({ source: id, includeContent: true, hires: "boundary" })
655
+ };
656
+ }
657
+ // References alone cannot tell live bindings from dead cycles, so this walks a graph of them.
658
+ function removeUnreachable(program, tree, sweepable, candidates) {
659
+ const { parents, removed, unexported, localDeclarators } = tree;
660
+ const { bindingOf } = crawl(program, tree);
661
+ const tracked = new Map();
662
+ const candidateOwners = new Map();
663
+ const addOwner = (owners, binding, owner) => {
664
+ const nodes = owners.get(binding) ?? new Set();
665
+ nodes.add(owner);
666
+ owners.set(binding, nodes);
667
+ };
668
+ for (const identifier of sweepable) {
669
+ if (isOwnExpressionName(identifier, parents) || isKeptWithDeadBinding(identifier, parents)) {
670
+ continue;
671
+ }
672
+ const binding = bindingOf(identifier);
673
+ if (!binding)
674
+ continue;
675
+ addOwner(tracked, binding, parents.get(identifier));
676
+ if (candidates.has(identifier))
677
+ addOwner(candidateOwners, binding, parents.get(identifier));
678
+ }
679
+ const isInsideExport = (owner) => {
680
+ if (localDeclarators.has(owner))
681
+ return false;
682
+ for (let node = parents.get(owner); node; node = parents.get(node)) {
683
+ if (localDeclarators.has(node))
684
+ return false;
685
+ if (isExportWrapper(node) && !unexported.has(node))
686
+ return true;
687
+ }
688
+ return false;
689
+ };
690
+ const ownerBindings = new Map();
691
+ const dependencies = new Map();
692
+ const live = new Set();
693
+ for (const [binding, owners] of tracked) {
694
+ dependencies.set(binding, new Set());
695
+ for (const owner of owners) {
696
+ ownerBindings.set(owner, binding);
697
+ if (isInsideExport(owner))
698
+ live.add(binding);
699
+ }
700
+ }
701
+ for (const target of tracked.keys()) {
702
+ const references = target.violations.concat(target.references.filter(reference => !isTypeOnly(reference, tree)));
703
+ for (const reference of references) {
704
+ let node = reference;
705
+ // A reference outside any function body runs when the module evaluates,
706
+ // even if the binding that owns it is never read again.
707
+ let deferred = false;
708
+ while (node && !ownerBindings.has(node)) {
709
+ if (isFunctionLike(node))
710
+ deferred = true;
711
+ node = parents.get(node);
712
+ }
713
+ const ownName = node && (reference === node || reference === node.id || reference === node.left);
714
+ if (node && (deferred || ownName || isFunctionLike(node))) {
715
+ dependencies.get(ownerBindings.get(node)).add(target);
716
+ }
717
+ else
718
+ live.add(target);
719
+ }
720
+ }
721
+ const pending = [...live];
722
+ while (pending.length) {
723
+ for (const dependency of dependencies.get(pending.pop()) ?? []) {
724
+ if (live.has(dependency))
725
+ continue;
726
+ live.add(dependency);
727
+ pending.push(dependency);
728
+ }
729
+ }
730
+ const dead = new Set([...candidateOwners.keys()].filter(binding => !live.has(binding)));
731
+ if (!dead.size)
732
+ return;
733
+ const removeDeadDeclarators = (node) => {
734
+ if (removed.has(node))
735
+ return;
736
+ if (node.type === "VariableDeclarator" && node.id.type === "Identifier") {
737
+ const binding = bindingOf(node.id);
738
+ if (binding && dead.has(binding))
739
+ removeDeclarator(node, tree);
740
+ }
741
+ forEachChild(node, removeDeadDeclarators);
742
+ };
743
+ removeDeadDeclarators(program);
744
+ for (const binding of dead) {
745
+ for (const owner of candidateOwners.get(binding)) {
746
+ if (owner.type !== "VariableDeclarator" && !removed.has(owner))
747
+ removeStatement(owner, tree);
748
+ }
749
+ }
750
+ }
751
+ function sweepObjectPattern(declarator, isDead, tree) {
752
+ const { removed } = tree;
753
+ const { properties } = declarator.id;
754
+ const rest = properties.at(-1);
755
+ let changed = false;
756
+ for (const [slot, local] of patternLocals(declarator.id)) {
757
+ if (removed.has(slot) || !isDead(local))
758
+ continue;
759
+ // Dropping a sibling of a kept `...rest` would change what `rest` collects.
760
+ if (rest?.type === "RestElement" && rest !== slot && !removed.has(rest))
761
+ continue;
762
+ removed.add(slot);
763
+ changed = true;
764
+ }
765
+ if (changed && properties.every((property) => removed.has(property))) {
766
+ removeDeclarator(declarator, tree);
767
+ }
768
+ return changed;
769
+ }
770
+ function sweepArrayPattern(declarator, isDead, tree) {
771
+ const { removed } = tree;
772
+ let changed = false;
773
+ for (const [slot, local] of patternLocals(declarator.id)) {
774
+ if (!removed.has(slot) && isDead(local)) {
775
+ removed.add(slot);
776
+ changed = true;
777
+ }
778
+ }
779
+ if (changed && declarator.id.elements.every((e) => e && removed.has(e))) {
780
+ removeDeclarator(declarator, tree);
781
+ }
782
+ return changed;
783
+ }
784
+ function nextToken(code, position) {
785
+ for (;;) {
786
+ while (/\s/.test(code[position] ?? ""))
787
+ position++;
788
+ if (code.startsWith("//", position)) {
789
+ const end = code.indexOf("\n", position);
790
+ position = end < 0 ? code.length : end;
791
+ }
792
+ else if (code.startsWith("/*", position)) {
793
+ position = code.indexOf("*/", position) + 2;
794
+ }
795
+ else {
796
+ return position;
797
+ }
798
+ }
799
+ }
800
+ function removeFromList(output, items, removed) {
801
+ let previousKept;
802
+ let runStart;
803
+ for (const item of items) {
804
+ if (removed.has(item)) {
805
+ runStart ??= item;
806
+ continue;
807
+ }
808
+ if (runStart)
809
+ output.remove(runStart.start, item.start);
810
+ runStart = undefined;
811
+ previousKept = item;
812
+ }
813
+ if (runStart && previousKept)
814
+ output.remove(previousKept.end, items.at(-1).end);
815
+ }
816
+ function renderImport(node, code, output, removed) {
817
+ const [first, ...rest] = node.specifiers;
818
+ if (first?.type !== "ImportDefaultSpecifier" || !rest.length) {
819
+ removeFromList(output, node.specifiers, removed);
820
+ return;
821
+ }
822
+ if (removed.has(first)) {
823
+ output.remove(first.start, nextToken(code, nextToken(code, first.end) + 1));
824
+ removeFromList(output, rest, removed);
825
+ }
826
+ else if (rest.every(specifier => removed.has(specifier))) {
827
+ let end = rest.at(-1).end;
828
+ if (rest[0].type === "ImportSpecifier") {
829
+ end = nextToken(code, end);
830
+ if (code[end] === ",")
831
+ end = nextToken(code, end + 1);
832
+ end++;
833
+ }
834
+ output.remove(first.end, end);
835
+ }
836
+ else {
837
+ removeFromList(output, rest, removed);
838
+ }
839
+ }
840
+ function renderSplitExport(node, output, tree) {
841
+ const { removed, localDeclarators } = tree;
842
+ const declaration = node.declaration;
843
+ const prefix = (declarator) => `${localDeclarators.has(declarator) ? "" : "export "}${declaration.declare ? "declare " : ""}${declaration.kind} `;
844
+ const kept = declaration.declarations.filter(d => !removed.has(d));
845
+ output.overwrite(node.start, kept[0].start, prefix(kept[0]));
846
+ kept.slice(1).forEach((declarator, index) => {
847
+ output.overwrite(kept[index].end, declarator.start, `;\n${prefix(declarator)}`);
848
+ });
849
+ const lastKept = kept.at(-1);
850
+ const last = declaration.declarations.at(-1);
851
+ if (lastKept !== last)
852
+ output.remove(lastKept.end, last.end);
853
+ }
854
+ function statementStart(node) {
855
+ const decorator = (node.declaration ?? node).decorators?.[0];
856
+ return decorator ? Math.min(node.start, decorator.start) : node.start;
857
+ }
858
+ function renderRemovedStatement(node, parent, code, output, removed) {
859
+ const siblings = Array.isArray(parent.body)
860
+ ? parent.body
861
+ : parent.type === "SwitchCase"
862
+ ? parent.consequent
863
+ : undefined;
864
+ if (!siblings) {
865
+ if (parent.type === "ForStatement" && parent.init === node)
866
+ output.remove(node.start, node.end);
867
+ else
868
+ output.overwrite(statementStart(node), node.end, "{}");
869
+ return;
870
+ }
871
+ const next = siblings.slice(siblings.indexOf(node) + 1).find(sibling => !removed.has(sibling));
872
+ let end = node.end;
873
+ while (code[end] === " " || code[end] === "\t")
874
+ end++;
875
+ if (code[end] === "\r")
876
+ end++;
877
+ end = code[end] === "\n" ? end + 1 : node.end;
878
+ // Keeps the previous statement from continuing into `(`, `[` and friends.
879
+ if (next && /[([`+\-/*<]/.test(code[next.start]))
880
+ output.overwrite(statementStart(node), end, ";");
881
+ else
882
+ output.remove(statementStart(node), end);
883
+ }
884
+ function render(node, code, output, tree) {
885
+ const { removed, unexported, localDeclarators } = tree;
886
+ let ownsRemovedChildren = false;
887
+ switch (node.type) {
888
+ case "ImportDeclaration":
889
+ if (node.specifiers.some((specifier) => removed.has(specifier))) {
890
+ renderImport(node, code, output, removed);
891
+ }
892
+ return;
893
+ case "ExportNamedDeclaration":
894
+ ownsRemovedChildren = true;
895
+ removeFromList(output, node.specifiers, removed);
896
+ if (unexported.has(node)) {
897
+ output.remove(node.start, node.declaration.start);
898
+ }
899
+ else if (node.declaration?.declarations?.some((d) => localDeclarators.has(d))) {
900
+ renderSplitExport(node, output, tree);
901
+ for (const declarator of node.declaration.declarations) {
902
+ if (!removed.has(declarator))
903
+ render(declarator, code, output, tree);
904
+ }
905
+ return;
906
+ }
907
+ break;
908
+ case "ExportDefaultDeclaration":
909
+ if (unexported.has(node))
910
+ output.remove(node.start, node.declaration.start);
911
+ break;
912
+ case "VariableDeclaration":
913
+ ownsRemovedChildren = true;
914
+ removeFromList(output, node.declarations, removed);
915
+ break;
916
+ case "ObjectPattern":
917
+ ownsRemovedChildren = true;
918
+ removeFromList(output, node.properties, removed);
919
+ break;
920
+ case "ArrayPattern":
921
+ ownsRemovedChildren = true;
922
+ // Holes keep the remaining elements at their original positions.
923
+ for (const element of node.elements) {
924
+ if (element && removed.has(element))
925
+ output.remove(element.start, element.end);
926
+ }
927
+ break;
928
+ }
929
+ forEachChild(node, child => {
930
+ if (!removed.has(child))
931
+ render(child, code, output, tree);
932
+ else if (!ownsRemovedChildren)
933
+ renderRemovedStatement(child, node, code, output, removed);
934
+ });
935
+ }