@tanstack/router-cli 0.0.1-beta.29

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,857 @@
1
+ import * as babel from '@babel/core'
2
+ import * as t from '@babel/types'
3
+ // @ts-ignore
4
+ import syntaxTS from '@babel/plugin-syntax-typescript'
5
+ import {
6
+ IsolatedExport,
7
+ removeExt,
8
+ rootRouteName,
9
+ rootRouteClientName,
10
+ RouteNode,
11
+ } from './generator'
12
+ import path from 'path'
13
+ import { Config } from './config'
14
+ import { isVariableDeclaration } from '@babel/types'
15
+
16
+ export const isolatedProperties = [
17
+ 'loader',
18
+ 'action',
19
+ 'component',
20
+ 'errorComponent',
21
+ 'pendingComponent',
22
+ ] as const
23
+
24
+ export type IsolatedProperty = typeof isolatedProperties[number]
25
+
26
+ type Opts = {
27
+ isolate: IsolatedProperty
28
+ }
29
+
30
+ const getBasePlugins = () => [
31
+ [
32
+ syntaxTS,
33
+ {
34
+ isTSX: true,
35
+ // disallowAmbiguousJSXLike: true,
36
+ },
37
+ ],
38
+ ]
39
+
40
+ export async function ensureBoilerplate(node: RouteNode, code: string) {
41
+ const relativeImportPath = path.relative(node.fullDir, node.genPathNoExt)
42
+
43
+ const originalFile = await babel.transformAsync(code, {
44
+ configFile: false,
45
+ babelrc: false,
46
+ plugins: [...getBasePlugins()],
47
+ })
48
+
49
+ const file = await babel.transformAsync(code, {
50
+ configFile: false,
51
+ babelrc: false,
52
+ plugins: [
53
+ ...getBasePlugins(),
54
+ {
55
+ visitor: {
56
+ Program: {
57
+ enter(programPath) {
58
+ // Remove all properties except for our isolated one
59
+
60
+ if (node.isRoot) {
61
+ let foundImport = false
62
+
63
+ programPath.traverse({
64
+ ImportSpecifier(importPath) {
65
+ if (
66
+ t.isIdentifier(importPath.node.imported) &&
67
+ importPath.node.imported.name === 'createRouteConfig'
68
+ ) {
69
+ foundImport = true
70
+ }
71
+ },
72
+ })
73
+
74
+ if (!foundImport) {
75
+ programPath.node.body.unshift(
76
+ babel.template.statement(
77
+ `import { createRouteConfig } from '@tanstack/react-router'`,
78
+ )(),
79
+ )
80
+ }
81
+ } else {
82
+ let foundImport = false
83
+ programPath.traverse({
84
+ ImportSpecifier(importPath) {
85
+ if (
86
+ t.isIdentifier(importPath.node.imported) &&
87
+ importPath.node.imported.name === 'routeConfig'
88
+ ) {
89
+ foundImport = true
90
+ if (t.isImportDeclaration(importPath.parentPath.node)) {
91
+ if (
92
+ importPath.parentPath.node.source.value !==
93
+ relativeImportPath
94
+ ) {
95
+ importPath.parentPath.node.source.value =
96
+ relativeImportPath
97
+ }
98
+ }
99
+ }
100
+ },
101
+ })
102
+ if (!foundImport) {
103
+ programPath.node.body.unshift(
104
+ babel.template.statement(
105
+ `import { routeConfig } from '${relativeImportPath}'`,
106
+ )(),
107
+ )
108
+ }
109
+ }
110
+ },
111
+ },
112
+ },
113
+ },
114
+ ],
115
+ })
116
+
117
+ const separator = node.isRoot ? 'createRouteConfig(' : 'routeConfig.generate('
118
+
119
+ if (!originalFile?.code) {
120
+ return `${file?.code}\n\n${separator}{\n\n})`
121
+ }
122
+
123
+ const originalHead = originalFile?.code?.substring(
124
+ 0,
125
+ originalFile?.code?.indexOf(separator),
126
+ )
127
+
128
+ const generatedHead = file?.code?.substring(0, file?.code?.indexOf(separator))
129
+
130
+ if (originalHead !== generatedHead) {
131
+ return `${generatedHead}\n\n${originalFile?.code?.substring(
132
+ originalFile?.code?.indexOf(separator),
133
+ )}`
134
+ }
135
+
136
+ return
137
+ }
138
+
139
+ export async function isolateOptionToExport(
140
+ node: RouteNode,
141
+ code: string,
142
+ opts: Opts,
143
+ ) {
144
+ return (
145
+ await babel.transformAsync(code, {
146
+ configFile: false,
147
+ babelrc: false,
148
+ plugins: [...getBasePlugins(), plugin()],
149
+ ast: true,
150
+ })
151
+ )?.code
152
+
153
+ function plugin(): babel.PluginItem {
154
+ return {
155
+ visitor: {
156
+ Program: {
157
+ enter(programPath, state) {
158
+ // If we're the root, handle things a bit differently
159
+ if (node.isRoot) {
160
+ programPath.traverse({
161
+ Identifier(path) {
162
+ if (
163
+ path.node.name === 'createRouteConfig' &&
164
+ t.isCallExpression(path.parentPath.node)
165
+ ) {
166
+ const options = getCreateRouteConfigOptions(path)
167
+
168
+ if (options) {
169
+ const property = options.properties.find((property) => {
170
+ return (
171
+ t.isObjectProperty(property) &&
172
+ t.isIdentifier(property.key) &&
173
+ property.key.name === opts.isolate
174
+ )
175
+ })
176
+
177
+ if (t.isObjectProperty(property)) {
178
+ const program = path.findParent((d) => d.isProgram())
179
+
180
+ if (program?.isProgram()) {
181
+ program.node.body.push(
182
+ babel.template.statement(
183
+ `export const ${opts.isolate} = $LOADER`,
184
+ )({
185
+ $LOADER: property.value,
186
+ }),
187
+ )
188
+ }
189
+ }
190
+
191
+ path
192
+ .findParent((d) => d.isExpressionStatement())
193
+ ?.remove()
194
+ }
195
+ }
196
+ },
197
+ })
198
+ }
199
+
200
+ // We're not in the root, handle things normally
201
+ if (!node.isRoot) {
202
+ // Remove all properties except for our isolated one
203
+ programPath.traverse({
204
+ Identifier(path) {
205
+ if (path.node.name === 'generate') {
206
+ const options = getRouteConfigGenerateOptions(path)
207
+
208
+ if (options) {
209
+ const property = options.properties.find((property) => {
210
+ return (
211
+ t.isObjectProperty(property) &&
212
+ t.isIdentifier(property.key) &&
213
+ property.key.name === opts.isolate
214
+ )
215
+ })
216
+
217
+ if (
218
+ t.isObjectProperty(property) &&
219
+ t.isIdentifier(property.key)
220
+ ) {
221
+ if (property.key.name === opts.isolate) {
222
+ const program = path.findParent((d) => d.isProgram())
223
+
224
+ if (program?.isProgram()) {
225
+ program.node.body.push(
226
+ babel.template.statement(
227
+ `export const ${opts.isolate} = $LOADER`,
228
+ )({
229
+ $LOADER: property.value,
230
+ }),
231
+ )
232
+ }
233
+ }
234
+ }
235
+
236
+ path
237
+ .findParent((d) => d.isExpressionStatement())
238
+ ?.remove()
239
+ }
240
+ }
241
+ },
242
+ })
243
+ }
244
+
245
+ cleanUnusedCode(programPath, state, [opts.isolate])
246
+ },
247
+ },
248
+ },
249
+ }
250
+ }
251
+ }
252
+
253
+ export async function detectExports(code: string) {
254
+ let exported: string[] = []
255
+
256
+ // try {
257
+ await babel.transformAsync(code, {
258
+ configFile: false,
259
+ babelrc: false,
260
+ plugins: [
261
+ ...getBasePlugins(),
262
+ {
263
+ visitor: {
264
+ ExportNamedDeclaration(path) {
265
+ if (t.isVariableDeclaration(path.node.declaration)) {
266
+ if (
267
+ t.isVariableDeclarator(path.node.declaration.declarations?.[0])
268
+ ) {
269
+ if (t.isIdentifier(path.node.declaration.declarations[0].id)) {
270
+ exported.push(path.node.declaration.declarations[0].id.name)
271
+ }
272
+ }
273
+ }
274
+ },
275
+ },
276
+ },
277
+ ],
278
+ ast: true,
279
+ })
280
+
281
+ return exported
282
+ }
283
+
284
+ export async function generateRouteConfig(
285
+ node: RouteNode,
286
+ routeCode: string,
287
+ imports: IsolatedExport[],
288
+ clientOnly: boolean,
289
+ ) {
290
+ const relativeParentRoutePath = clientOnly
291
+ ? node.parent
292
+ ? removeExt(
293
+ path.relative(
294
+ node.genDir,
295
+ path.resolve(node.parent?.genDir, node.parent?.clientFilename),
296
+ ),
297
+ )
298
+ : `./${rootRouteClientName}`
299
+ : node.parent
300
+ ? removeExt(
301
+ path.relative(
302
+ node.genDir,
303
+ path.resolve(node.parent?.genDir, node.parent?.filename),
304
+ ),
305
+ )
306
+ : `./${rootRouteName}`
307
+
308
+ const pathName = node.isRoot
309
+ ? undefined
310
+ : node.fileNameNoExt.startsWith('__')
311
+ ? undefined
312
+ : node.fileNameNoExt === 'index'
313
+ ? '/'
314
+ : node.fileNameNoExt
315
+
316
+ const routeId = node.isRoot ? undefined : node.fileNameNoExt
317
+
318
+ function plugin(): babel.PluginItem {
319
+ return {
320
+ visitor: {
321
+ Program: {
322
+ enter(programPath, state) {
323
+ // Remove all of the isolated import properties from the config
324
+ programPath.traverse({
325
+ ImportSpecifier(path) {
326
+ if (t.isIdentifier(path.node.imported)) {
327
+ if (!node.isRoot) {
328
+ if (path.node.imported.name === 'routeConfig') {
329
+ path.parentPath.remove()
330
+
331
+ const program = path.findParent((d) => d.isProgram())
332
+
333
+ if (program?.isProgram()) {
334
+ program.node.body.unshift(
335
+ babel.template.statement(
336
+ `import { routeConfig as parentRouteConfig } from '$IMPORT'`,
337
+ )({
338
+ $IMPORT: relativeParentRoutePath,
339
+ }),
340
+ )
341
+ }
342
+ }
343
+ }
344
+ }
345
+ },
346
+ Identifier(iPath) {
347
+ let options
348
+
349
+ if (node.isRoot) {
350
+ if (iPath.node.name === 'createRouteConfig') {
351
+ if (t.isCallExpression(iPath.parentPath.node)) {
352
+ if (
353
+ t.isExpressionStatement(
354
+ iPath.parentPath.parentPath?.node,
355
+ )
356
+ ) {
357
+ iPath.parentPath.parentPath?.replaceWith(
358
+ t.variableDeclaration('const', [
359
+ t.variableDeclarator(
360
+ t.identifier('routeConfig'),
361
+ iPath.parentPath.node,
362
+ ),
363
+ ]) as any,
364
+ )
365
+ }
366
+ }
367
+ }
368
+ } else {
369
+ if (iPath.node.name === 'generate') {
370
+ if (t.isMemberExpression(iPath.parentPath.node)) {
371
+ if (t.isIdentifier(iPath.parentPath.node.object)) {
372
+ iPath.node.name = 'createRoute'
373
+ iPath.parentPath.node.object.name = 'parentRouteConfig'
374
+
375
+ options = getRouteConfigGenerateOptions(iPath)
376
+ }
377
+ }
378
+ }
379
+ }
380
+
381
+ if (options) {
382
+ options.properties = [
383
+ ...(pathName
384
+ ? [
385
+ t.objectProperty(
386
+ t.identifier('path'),
387
+ t.stringLiteral(pathName),
388
+ ),
389
+ ]
390
+ : routeId
391
+ ? [
392
+ t.objectProperty(
393
+ t.identifier('id'),
394
+ t.stringLiteral(routeId),
395
+ ),
396
+ ]
397
+ : []),
398
+ ...options.properties.map((property) => {
399
+ if (
400
+ t.isObjectProperty(property) &&
401
+ t.isIdentifier(property.key) &&
402
+ isolatedProperties.includes(
403
+ property.key.name as IsolatedProperty,
404
+ )
405
+ ) {
406
+ const key = property.key.name
407
+
408
+ if (key === 'loader') {
409
+ if (clientOnly) {
410
+ return t.objectProperty(
411
+ t.identifier('loader'),
412
+ t.tSAsExpression(
413
+ t.booleanLiteral(true),
414
+ t.tsAnyKeyword(),
415
+ ),
416
+ )
417
+ }
418
+ return t.objectProperty(
419
+ t.identifier(key),
420
+ babel.template.expression(
421
+ `(...args) => import('./${path.relative(
422
+ node.genDir,
423
+ node.genPathNoExt,
424
+ )}-${key}').then(d => d.${key}.apply(d.${key}, (args as any)))`,
425
+ {
426
+ plugins: ['typescript'],
427
+ },
428
+ )({}),
429
+ )
430
+ }
431
+
432
+ if (key === 'action') {
433
+ if (clientOnly) {
434
+ return t.objectProperty(
435
+ t.identifier('action'),
436
+ t.tSAsExpression(
437
+ t.booleanLiteral(true),
438
+ t.tSAnyKeyword(),
439
+ ),
440
+ )
441
+ }
442
+ return t.objectProperty(
443
+ t.identifier(key),
444
+ babel.template.expression(
445
+ `(...payload: Parameters<typeof import('./${path.relative(
446
+ node.genDir,
447
+ node.genPathNoExt,
448
+ )}-${key}').action>) => import('./${path.relative(
449
+ node.genDir,
450
+ node.genPathNoExt,
451
+ )}-${key}').then(d => d.${key}.apply(d.${key}, (payload as any)))`,
452
+ {
453
+ plugins: ['typescript'],
454
+ },
455
+ )({}),
456
+ )
457
+ }
458
+
459
+ return t.objectProperty(
460
+ t.identifier(key),
461
+ babel.template.expression(`
462
+ lazy(() => import('./${path.relative(
463
+ node.genDir,
464
+ node.genPathNoExt,
465
+ )}-${key}').then(d => ({ default: d.${key} }) ))`)(),
466
+ )
467
+ }
468
+
469
+ return property
470
+ }),
471
+ ]
472
+
473
+ const program = iPath.findParent((d) => d.isProgram())
474
+
475
+ if (program?.isProgram() && options) {
476
+ const index = program.node.body.findIndex(
477
+ (d) =>
478
+ d.start === iPath.parentPath.parentPath?.node.start,
479
+ )
480
+
481
+ if (node.isRoot) {
482
+ program.node.body[index] = babel.template.statement(
483
+ `const routeConfig = createRouteConfig(
484
+ $OPTIONS
485
+ )`,
486
+ )({
487
+ $OPTIONS: options,
488
+ })
489
+ } else {
490
+ program.node.body[index] = babel.template.statement(
491
+ `const routeConfig = parentRouteConfig.createRoute(
492
+ $OPTIONS
493
+ )`,
494
+ )({
495
+ $OPTIONS: options,
496
+ })
497
+ }
498
+ }
499
+ }
500
+ },
501
+ })
502
+
503
+ programPath.node.body.unshift(
504
+ babel.template.statement(
505
+ `import { lazy } from '@tanstack/react-router'`,
506
+ )(),
507
+ )
508
+
509
+ // Add the routeConfig exports
510
+ programPath.node.body.push(
511
+ babel.template.statement(
512
+ clientOnly
513
+ ? `export { routeConfig, routeConfig as ${node.variable}Route }`
514
+ : `export { routeConfig }`,
515
+ )(),
516
+ )
517
+
518
+ cleanUnusedCode(programPath, state, [
519
+ 'routeConfig',
520
+ `${node.variable}Route`,
521
+ ])
522
+ },
523
+ },
524
+ },
525
+ }
526
+ }
527
+
528
+ const code = (
529
+ await babel.transformAsync(routeCode, {
530
+ configFile: false,
531
+ babelrc: false,
532
+ plugins: [...getBasePlugins(), plugin()],
533
+ ast: true,
534
+ })
535
+ )?.code
536
+
537
+ if (!code) {
538
+ // console.log(code, node, imports)
539
+ throw new Error('Error while generating a route file!')
540
+ }
541
+
542
+ return code
543
+ }
544
+
545
+ function getIdentifier(path: any) {
546
+ const parentPath = path.parentPath
547
+ if (parentPath.type === 'VariableDeclarator') {
548
+ const pp = parentPath
549
+ const name = pp.get('id')
550
+ return name.node.type === 'Identifier' ? name : null
551
+ }
552
+ if (parentPath.type === 'AssignmentExpression') {
553
+ const pp = parentPath
554
+ const name = pp.get('left')
555
+ return name.node.type === 'Identifier' ? name : null
556
+ }
557
+ if (path.node.type === 'ArrowFunctionExpression') {
558
+ return null
559
+ }
560
+ return path.node.id && path.node.id.type === 'Identifier'
561
+ ? path.get('id')
562
+ : null
563
+ }
564
+
565
+ function isIdentifierReferenced(ident: any) {
566
+ const b = ident.scope.getBinding(ident.node.name)
567
+ if (b && b.referenced) {
568
+ if (b.path.type === 'FunctionDeclaration') {
569
+ return !b.constantViolations
570
+ .concat(b.referencePaths)
571
+ .every((ref: any) => ref.findParent((p: any) => p === b.path))
572
+ }
573
+ return true
574
+ }
575
+ return false
576
+ }
577
+ function markFunction(path: any, state: any) {
578
+ const ident = getIdentifier(path)
579
+ if (ident && ident.node && isIdentifierReferenced(ident)) {
580
+ state.refs.add(ident)
581
+ }
582
+ }
583
+ function markImport(path: any, state: any) {
584
+ const local = path.get('local')
585
+ if (isIdentifierReferenced(local)) {
586
+ state.refs.add(local)
587
+ }
588
+ }
589
+
590
+ function getRouteConfigGenerateOptions(path: any): t.ObjectExpression | void {
591
+ const tryOptions = (node: any): t.ObjectExpression | void => {
592
+ if (t.isIdentifier(node)) {
593
+ const initNode = path.scope.getBinding(node.name)?.path.node
594
+ if (t.isVariableDeclarator(initNode)) {
595
+ return tryOptions(initNode.init)
596
+ }
597
+ } else if (t.isObjectExpression(node)) {
598
+ return node
599
+ }
600
+
601
+ return
602
+ }
603
+
604
+ if (
605
+ t.isMemberExpression(path.parentPath.node) &&
606
+ t.isCallExpression(path.parentPath.parentPath?.node)
607
+ ) {
608
+ const options = path.parentPath.parentPath?.node.arguments[0]
609
+
610
+ return tryOptions(options)
611
+ }
612
+ }
613
+
614
+ function getCreateRouteConfigOptions(path: any): t.ObjectExpression | void {
615
+ const tryOptions = (node: any): t.ObjectExpression | void => {
616
+ if (t.isIdentifier(node)) {
617
+ const initNode = path.scope.getBinding(node.name)?.path.node
618
+ if (t.isVariableDeclarator(initNode)) {
619
+ return tryOptions(initNode.init)
620
+ }
621
+ } else if (t.isObjectExpression(node)) {
622
+ return node
623
+ }
624
+
625
+ return
626
+ }
627
+
628
+ if (t.isCallExpression(path.parentPath?.node)) {
629
+ const options = path.parentPath?.node.arguments[0]
630
+
631
+ return tryOptions(options)
632
+ }
633
+ }
634
+
635
+ // All credit for this amazing function goes to the Next.js team
636
+ // (and the Solid.js team for their derivative work).
637
+ // https://github.com/vercel/next.js/blob/canary/packages/next/build/babel/plugins/next-ssg-transform.ts
638
+ // https://github.com/solidjs/solid-start/blob/main/packages/start/server/routeData.js
639
+
640
+ function cleanUnusedCode(
641
+ programPath: babel.NodePath<babel.types.Program>,
642
+ state: any,
643
+ keepExports: string[] = [],
644
+ ) {
645
+ state.refs = new Set()
646
+ state.done = false
647
+
648
+ function markVariable(variablePath: any, variableState: any) {
649
+ if (variablePath.node.id.type === 'Identifier') {
650
+ const local = variablePath.get('id')
651
+ if (isIdentifierReferenced(local)) {
652
+ variableState.refs.add(local)
653
+ }
654
+ } else if (variablePath.node.id.type === 'ObjectPattern') {
655
+ const pattern = variablePath.get('id')
656
+ const properties: any = pattern.get('properties')
657
+ properties.forEach((p: any) => {
658
+ const local = p.get(
659
+ p.node.type === 'ObjectProperty'
660
+ ? 'value'
661
+ : p.node.type === 'RestElement'
662
+ ? 'argument'
663
+ : (function () {
664
+ throw new Error('invariant')
665
+ })(),
666
+ )
667
+ if (isIdentifierReferenced(local)) {
668
+ variableState.refs.add(local)
669
+ }
670
+ })
671
+ } else if (variablePath.node.id.type === 'ArrayPattern') {
672
+ const pattern = variablePath.get('id')
673
+ const elements: any = pattern.get('elements')
674
+ elements.forEach((e: any) => {
675
+ let local
676
+ if (e.node && e.node.type === 'Identifier') {
677
+ local = e
678
+ } else if (e.node && e.node.type === 'RestElement') {
679
+ local = e.get('argument')
680
+ } else {
681
+ return
682
+ }
683
+ if (isIdentifierReferenced(local)) {
684
+ variableState.refs.add(local)
685
+ }
686
+ })
687
+ }
688
+ }
689
+
690
+ // Mark all variables and functions if used
691
+ programPath.traverse(
692
+ {
693
+ VariableDeclarator: markVariable,
694
+ FunctionDeclaration: markFunction,
695
+ FunctionExpression: markFunction,
696
+ ArrowFunctionExpression: markFunction,
697
+ ImportSpecifier: markImport,
698
+ ImportDefaultSpecifier: markImport,
699
+ ImportNamespaceSpecifier: markImport,
700
+ ExportDefaultDeclaration: markImport,
701
+ // ExportNamedDeclaration(path, state) {
702
+ // if (t.isVariableDeclaration(path.node.declaration)) {
703
+ // if (t.isVariableDeclarator(path.node.declaration.declarations?.[0])) {
704
+ // if (t.isIdentifier(path.node.declaration.declarations[0].id)) {
705
+ // if (
706
+ // keepExports.includes(
707
+ // path.node.declaration.declarations[0].id.name,
708
+ // )
709
+ // ) {
710
+ // return
711
+ // }
712
+ // }
713
+ // path.replaceWith(path.node.declaration.declarations[0])
714
+ // return
715
+ // }
716
+ // }
717
+ // path.remove()
718
+ // },
719
+ ImportDeclaration: (path) => {
720
+ if (path.node.source.value.endsWith('.css')) {
721
+ path.remove()
722
+ }
723
+ },
724
+ },
725
+ state,
726
+ )
727
+
728
+ // Sweet all of the remaining references and remove unused ones
729
+ const refs: any = state.refs
730
+ let count: number
731
+ function sweepFunction(sweepPath: any) {
732
+ const ident = getIdentifier(sweepPath)
733
+ if (
734
+ ident &&
735
+ ident.node &&
736
+ refs.has(ident) &&
737
+ !isIdentifierReferenced(ident)
738
+ ) {
739
+ ++count
740
+ if (
741
+ t.isAssignmentExpression(sweepPath.parentPath) ||
742
+ t.isVariableDeclarator(sweepPath.parentPath)
743
+ ) {
744
+ sweepPath.parentPath.remove()
745
+ } else {
746
+ sweepPath.remove()
747
+ }
748
+ }
749
+ }
750
+ function sweepImport(sweepPath: any) {
751
+ const local = sweepPath.get('local')
752
+ if (refs.has(local) && !isIdentifierReferenced(local)) {
753
+ ++count
754
+ sweepPath.remove()
755
+ if (sweepPath.parent.specifiers.length === 0) {
756
+ sweepPath.parentPath.remove()
757
+ }
758
+ }
759
+ }
760
+ do {
761
+ programPath.scope.crawl()
762
+ count = 0
763
+ programPath.traverse({
764
+ VariableDeclarator(variablePath) {
765
+ if (variablePath.node.id.type === 'Identifier') {
766
+ const local = variablePath.get('id')
767
+ if (refs.has(local) && !isIdentifierReferenced(local)) {
768
+ ++count
769
+
770
+ variablePath.remove()
771
+ }
772
+ } else if (variablePath.node.id.type === 'ObjectPattern') {
773
+ const pattern = variablePath.get('id')
774
+ const beforeCount = count
775
+ const properties: any = pattern.get('properties')
776
+ properties.forEach((p: any) => {
777
+ const local = p.get(
778
+ p.node.type === 'ObjectProperty'
779
+ ? 'value'
780
+ : p.node.type === 'RestElement'
781
+ ? 'argument'
782
+ : (function () {
783
+ throw new Error('invariant')
784
+ })(),
785
+ )
786
+ if (refs.has(local) && !isIdentifierReferenced(local)) {
787
+ ++count
788
+ p.remove()
789
+ }
790
+ })
791
+ if (
792
+ beforeCount !== count &&
793
+ (pattern.get('properties') as any).length < 1
794
+ ) {
795
+ variablePath.remove()
796
+ }
797
+ } else if (variablePath.node.id.type === 'ArrayPattern') {
798
+ const pattern = variablePath.get('id')
799
+ const beforeCount = count
800
+ const elements: any = pattern.get('elements')
801
+ elements.forEach((e: any) => {
802
+ let local
803
+ if (e.node && e.node.type === 'Identifier') {
804
+ local = e
805
+ } else if (e.node && e.node.type === 'RestElement') {
806
+ local = e.get('argument')
807
+ } else {
808
+ return
809
+ }
810
+ if (refs.has(local) && !isIdentifierReferenced(local)) {
811
+ ++count
812
+
813
+ e.remove()
814
+ }
815
+ })
816
+ if (
817
+ beforeCount !== count &&
818
+ (pattern.get('elements') as any).length < 1
819
+ ) {
820
+ variablePath.remove()
821
+ }
822
+ }
823
+ },
824
+ FunctionDeclaration: sweepFunction,
825
+ FunctionExpression: sweepFunction,
826
+ ArrowFunctionExpression: sweepFunction,
827
+ ImportSpecifier: sweepImport,
828
+ ImportDefaultSpecifier: sweepImport,
829
+ ImportNamespaceSpecifier: sweepImport,
830
+ })
831
+ } while (count)
832
+
833
+ // Do we need the * import for react?
834
+ let hasReact = false
835
+
836
+ // Mark react elements as having react
837
+ programPath.traverse({
838
+ JSXElement(path) {
839
+ hasReact = true
840
+ },
841
+ })
842
+
843
+ if (!hasReact) {
844
+ // Mark all variables and functions if used
845
+ programPath.traverse({
846
+ ImportDeclaration(path) {
847
+ if (
848
+ t.isStringLiteral(path.node.source) &&
849
+ path.node.source.value === 'react' &&
850
+ t.isImportNamespaceSpecifier(path.node.specifiers[0])
851
+ ) {
852
+ path.remove()
853
+ }
854
+ },
855
+ })
856
+ }
857
+ }