@hapico/cli 0.0.16 → 0.0.17

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,1588 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ import * as Babel from "@babel/standalone";
3
+ import * as uuid from "uuid";
4
+ import * as prettier from "prettier/standalone";
5
+ import parserBabel from "prettier/plugins/babel";
6
+ import estree from "prettier/plugins/estree";
7
+ import { includes, map } from "lodash";
8
+
9
+ const generator = Babel.packages.generator.default;
10
+ const traverse = Babel.packages.traverse.default;
11
+ const parser = Babel.packages.parser;
12
+ const parse = Babel.packages.parser.parse;
13
+
14
+ export interface IFile {
15
+ path: string;
16
+ content: string;
17
+ es5?: string;
18
+ }
19
+
20
+ export interface ImportStatement {
21
+ default: string;
22
+ named: Array<string>;
23
+ from: string;
24
+ code: string;
25
+ }
26
+
27
+ export type LiteralChangeRequest = {
28
+ id: string;
29
+ replacement: string;
30
+ };
31
+
32
+ type WrapComponentRequest = {
33
+ id: string;
34
+ wrapper: string; // Name of the wrapper component
35
+ attributes: Record<string, string>; // Attributes to pass to the wrapper component
36
+ };
37
+
38
+ export const removeExtension = (filename: string) => {
39
+ return filename.replace(/\.[^/.]+$/, "");
40
+ };
41
+
42
+ /**
43
+ * This tool is used to modify the code of a component.
44
+ * JSX React code can be modified using this tool.
45
+ */
46
+ export const ID_ATTRIBUTE = "_id";
47
+ class CodeMod {
48
+ public code: string;
49
+ private presets: Array<any> = [
50
+ // Keep the code as is
51
+ ["typescript", { allExtensions: true, isTSX: true }], // Handle TypeScript syntax
52
+ ];
53
+ constructor(code: string) {
54
+ this.code = code;
55
+ }
56
+
57
+ getCode() {
58
+ return this.code;
59
+ }
60
+
61
+ isValid() {
62
+ try {
63
+ Babel.transform(this.code, {
64
+ presets: this.presets,
65
+ filename: "file.tsx", // Ensure it's treated as TSX file
66
+ });
67
+ return true;
68
+ } catch (error) {
69
+ console.error("IS_VALID_ERROR", error);
70
+ return false;
71
+ }
72
+ }
73
+
74
+ /**
75
+ * Return es5 code
76
+ * @returns
77
+ */
78
+ compile() {
79
+ try {
80
+ // Transform the TypeScript React.js code into ES5
81
+ const result = Babel.transform(this.code, {
82
+ presets: [
83
+ ["env", { targets: { esmodules: false } }], // Compiles to ES5
84
+ "typescript", // Handles TypeScript syntax
85
+ "react", // Handles JSX syntax in TSX files
86
+ ],
87
+ filename: "file.tsx", // Ensure the file is treated as TSX
88
+ });
89
+ return result.code;
90
+ } catch (error) {
91
+ // console.error("COMPILE_ERROR", error);
92
+ return "";
93
+ }
94
+ }
95
+
96
+ /**
97
+ * Encode
98
+ * Mean every JSX tag will be injected with ID_ATTRIBUTE={uuidv4()}
99
+ * @param params
100
+ */
101
+ encode() {
102
+ const ast = this.ast();
103
+ // traverse the code and add ID_ATTRIBUTE={uuidv4()} to all JSX elements
104
+ traverse(ast, {
105
+ JSXOpeningElement(path: any) {
106
+ if (!path.node.attributes) {
107
+ path.node.attributes = [];
108
+ }
109
+ if (
110
+ path.node.attributes.find(
111
+ (attr: any) => attr.name?.name === ID_ATTRIBUTE
112
+ )
113
+ ) {
114
+ return;
115
+ }
116
+ path.node.attributes.push({
117
+ type: "JSXAttribute",
118
+ name: {
119
+ type: "JSXIdentifier",
120
+ name: ID_ATTRIBUTE,
121
+ },
122
+ value: {
123
+ type: "JSXExpressionContainer",
124
+ expression: {
125
+ type: "StringLiteral",
126
+ value: uuid.v4(),
127
+ },
128
+ },
129
+ });
130
+ },
131
+ });
132
+ // convert the AST back to code
133
+ this.astToCode(ast);
134
+ return this;
135
+ }
136
+
137
+ /**
138
+ * AST
139
+ * @returns
140
+ */
141
+ ast() {
142
+ const ast = parser.parse(this.code, {
143
+ sourceType: "module",
144
+ plugins: ["jsx", "typescript"],
145
+ });
146
+ return ast as any;
147
+ }
148
+
149
+ /**
150
+ * AST to Code
151
+ * @param ast
152
+ * @returns
153
+ */
154
+ astToCode(ast: any) {
155
+ const { code } = generator(ast, {}, this.code);
156
+ this.code = code;
157
+ return this;
158
+ }
159
+
160
+ /**
161
+ * Decode
162
+ * Remove all _____id attributes from JSX elements
163
+ * @param params
164
+ */
165
+ decode(options?: {
166
+ exceptIds: Array<string>;
167
+ }) {
168
+ const { exceptIds = [] } = options || { exceptIds: [] };
169
+ const ast = this.ast();
170
+ // traverse the code and remove ID_ATTRIBUTE from all JSX elements
171
+ traverse(ast, {
172
+ JSXOpeningElement(path: any) {
173
+ if (!path.node.attributes) {
174
+ return;
175
+ }
176
+ path.node.attributes = path.node.attributes.filter(
177
+ (attr: any) => attr.name?.name !== ID_ATTRIBUTE || exceptIds.includes(attr.value?.expression?.value)
178
+ );
179
+ },
180
+ });
181
+ // convert the AST back to code
182
+ this.astToCode(ast);
183
+ return this;
184
+ }
185
+
186
+ /**
187
+ * Prettify the code
188
+ */
189
+ async prettify() {
190
+ try {
191
+ this.code = await prettier.format(this.code, {
192
+ parser: "babel",
193
+ plugins: [parserBabel, estree],
194
+ });
195
+ } catch (error) {
196
+ console.error("PRETTIFY_ERROR", this.code);
197
+ console.error(error);
198
+ }
199
+ return this;
200
+ }
201
+
202
+ public async output() {
203
+ return this.code;
204
+ }
205
+
206
+ /**
207
+ * Modify JSX Text Content
208
+ * @param request
209
+ * @returns
210
+ */
211
+ modifyJSXTextContent(request: LiteralChangeRequest) {
212
+ this.code =
213
+ Babel.transform(this.code, {
214
+ presets: [
215
+ // "react", // Handle JSX syntax as is (no React.createElement transformation),
216
+ ["typescript", { allExtensions: true, isTSX: true }], // Handle TypeScript syntax
217
+ ],
218
+ filename: "file.tsx", // Ensure it's treated as TSX file
219
+ plugins: [
220
+ () => ({
221
+ visitor: {
222
+ JSXElement(path: any) {
223
+ const openingElement = path.node.openingElement;
224
+ const openingAttribute = openingElement.attributes.find(
225
+ (attr: any) => attr?.name?.name === ID_ATTRIBUTE
226
+ );
227
+ if (openingAttribute?.value?.expression?.value === request.id) {
228
+ path.node.children = [
229
+ {
230
+ type: "JSXText",
231
+ value: request.replacement,
232
+ },
233
+ ];
234
+ }
235
+ },
236
+ },
237
+ }),
238
+ ],
239
+ })?.code ?? "";
240
+
241
+ return this;
242
+ }
243
+
244
+ /**
245
+ * Check if the code has a React import
246
+ * @returns {boolean}
247
+ */
248
+ hasReactImport() {
249
+ const ast = this.ast();
250
+ let hasReact = false;
251
+ traverse(ast, {
252
+ ImportDeclaration(path: any) {
253
+ if (path.node.source.value === "react") {
254
+ hasReact = true;
255
+ }
256
+ },
257
+ });
258
+ return hasReact;
259
+ }
260
+
261
+ /**
262
+ * wrap component with another component
263
+ */
264
+ wrapJSXComponent(request: WrapComponentRequest) {
265
+ const { id, wrapper, attributes } = request;
266
+ let isDone = false;
267
+ this.code =
268
+ Babel.transform(this.code, {
269
+ presets: this.presets,
270
+ filename: "file.tsx", // Ensure it's treated as TSX file
271
+ plugins: [
272
+ () => ({
273
+ visitor: {
274
+ JSXElement(path: any) {
275
+ if (isDone) {
276
+ return;
277
+ }
278
+ console.log("PATH", path.node);
279
+ const openingElement = path.node.openingElement;
280
+ const closingElement = path.node.closingElement;
281
+
282
+ const openingAttribute = openingElement.attributes.find(
283
+ (attr: any) => attr?.name?.name === ID_ATTRIBUTE
284
+ );
285
+ if (
286
+ openingAttribute?.value?.expression?.value === id ||
287
+ openingAttribute?.value?.value === id
288
+ ) {
289
+ const wrapperAttributes = map(attributes, (value, key) => ({
290
+ type: "JSXAttribute",
291
+ name: {
292
+ type: "JSXIdentifier",
293
+ name: key,
294
+ },
295
+ value: {
296
+ type: "StringLiteral",
297
+ value,
298
+ },
299
+ }));
300
+
301
+ // Insert wrapper around the current node
302
+ path.replaceWith({
303
+ type: "JSXElement",
304
+ openingElement: {
305
+ type: "JSXOpeningElement",
306
+ name: {
307
+ type: "JSXIdentifier",
308
+ name: wrapper,
309
+ },
310
+ attributes: wrapperAttributes,
311
+ selfClosing: false,
312
+ },
313
+ closingElement: {
314
+ type: "JSXClosingElement",
315
+ name: {
316
+ type: "JSXIdentifier",
317
+ name: wrapper,
318
+ },
319
+ },
320
+ children: [
321
+ {
322
+ type: "JSXElement",
323
+ openingElement,
324
+ closingElement,
325
+ children: path.node.children || [],
326
+ },
327
+ ],
328
+ });
329
+ isDone = true;
330
+ }
331
+ },
332
+ },
333
+ }),
334
+ ],
335
+ })?.code ?? "";
336
+ return this;
337
+ }
338
+
339
+ getAttributes(id: string) {
340
+ const attributes: any = {};
341
+ Babel.transform(this.code, {
342
+ presets: this.presets,
343
+ filename: "file.tsx", // Ensure it's treated as TSX file
344
+ plugins: [
345
+ () => ({
346
+ visitor: {
347
+ JSXElement(path: any) {
348
+ const openingElement = path.node.openingElement;
349
+ if (!openingElement?.attributes) {
350
+ return;
351
+ }
352
+ const openingAttribute = openingElement.attributes.find(
353
+ (attr: any) => attr?.name?.name === ID_ATTRIBUTE
354
+ );
355
+ if (
356
+ openingAttribute?.value?.expression?.value === id ||
357
+ openingAttribute?.value?.value === id
358
+ ) {
359
+ openingElement.attributes.forEach((attr: any) => {
360
+ if (attr?.name?.name !== ID_ATTRIBUTE) {
361
+ if (attr?.value?.type === "JSXExpressionContainer") {
362
+ if (attr?.value?.expression?.type === "StringLiteral") {
363
+ attributes[attr?.name?.name] =
364
+ attr.value.expression.value;
365
+ } else if (
366
+ attr?.value?.expression?.type === "NumericLiteral"
367
+ ) {
368
+ attributes[attr?.name?.name] =
369
+ attr.value.expression.value;
370
+ }
371
+ } else if (
372
+ attr?.value?.type === "StringLiteral" ||
373
+ attr?.value?.type === "NumericLiteral"
374
+ ) {
375
+ attributes[attr?.name?.name] = attr.value.value;
376
+ }
377
+ }
378
+ });
379
+ }
380
+ },
381
+ },
382
+ }),
383
+ ],
384
+ });
385
+ return attributes;
386
+ }
387
+
388
+ removeJSXElement(id: string) {
389
+ this.code =
390
+ Babel.transform(this.code, {
391
+ presets: this.presets,
392
+ filename: "file.tsx", // Ensure it's treated as TSX file
393
+ plugins: [
394
+ () => ({
395
+ visitor: {
396
+ JSXElement(path: any) {
397
+ const openingElement = path.node.openingElement;
398
+ const openingAttribute = openingElement.attributes.find(
399
+ (attr: any) => attr?.name?.name === ID_ATTRIBUTE
400
+ );
401
+ if (
402
+ openingAttribute?.value?.expression?.value === id ||
403
+ openingAttribute?.value?.value === id
404
+ ) {
405
+ path.remove();
406
+ }
407
+ },
408
+ },
409
+ }),
410
+ ],
411
+ })?.code || "";
412
+ return this;
413
+ }
414
+
415
+ moveJSXElement(id: string, direction: "up" | "down") {
416
+ this.code =
417
+ Babel.transform(this.code, {
418
+ presets: this.presets,
419
+ filename: "file.tsx", // Ensure it's treated as TSX file
420
+ plugins: [
421
+ () => ({
422
+ visitor: {
423
+ JSXElement(path: any) {
424
+ // move up means check if the current JSX element is in children array
425
+ // if it is, then move it up
426
+ const openingElement = path.node.openingElement;
427
+ const openingAttribute = openingElement.attributes.find(
428
+ (attr: any) => attr?.name?.name === ID_ATTRIBUTE
429
+ );
430
+ if (
431
+ openingAttribute?.value?.expression?.value === id ||
432
+ openingAttribute?.value?.value === id
433
+ ) {
434
+ const parentPath = path.parentPath;
435
+ if (parentPath?.node?.type === "JSXElement") {
436
+ const parentChildren = parentPath.node.children;
437
+ if (Array.isArray(parentChildren)) {
438
+ const currentIndex = parentChildren.findIndex(
439
+ (child: any) => child === path.node
440
+ );
441
+ if (direction === "down") {
442
+ if (currentIndex > 0) {
443
+ // find the previous JSX element
444
+ let index = currentIndex - 1;
445
+ while (
446
+ index >= 0 &&
447
+ parentChildren[index].type !== "JSXElement"
448
+ ) {
449
+ index--;
450
+ }
451
+ if (index >= 0) {
452
+ const previousElement = parentChildren[index];
453
+ parentChildren[index] = path.node;
454
+ parentChildren[currentIndex] = previousElement;
455
+ }
456
+ }
457
+ } else if (direction === "up") {
458
+ console.log(
459
+ "DOWN",
460
+ currentIndex,
461
+ parentChildren.length
462
+ );
463
+ if (currentIndex < parentChildren.length - 1) {
464
+ // find the next JSX element
465
+ let index = currentIndex + 1;
466
+ while (
467
+ index < parentChildren.length &&
468
+ parentChildren[index].type !== "JSXElement"
469
+ ) {
470
+ index++;
471
+ }
472
+ if (index < parentChildren.length) {
473
+ const nextElement = parentChildren[index];
474
+ parentChildren[index] = path.node;
475
+ parentChildren[currentIndex] = nextElement;
476
+ }
477
+ }
478
+ }
479
+ }
480
+ }
481
+ }
482
+ },
483
+ },
484
+ }),
485
+ ],
486
+ })?.code || "";
487
+ return this;
488
+ }
489
+
490
+ getClassName(id: string) {
491
+ let className = "";
492
+ const ast = this.ast();
493
+ traverse(ast, {
494
+ JSXElement(path: any) {
495
+ const openingElement = path.node.openingElement;
496
+ const openingAttribute = openingElement.attributes.find(
497
+ (attr: any) => attr?.name?.name === ID_ATTRIBUTE
498
+ );
499
+ if (
500
+ openingAttribute?.value?.expression?.value === id ||
501
+ openingAttribute?.value?.value === id
502
+ ) {
503
+ const classNameAttribute = openingElement.attributes.find(
504
+ (attr: any) => attr?.name?.name === "className"
505
+ );
506
+ className =
507
+ classNameAttribute?.value?.value ??
508
+ classNameAttribute?.value?.expression.value;
509
+ }
510
+ },
511
+ });
512
+
513
+ return className;
514
+ }
515
+
516
+ updateClassName(id: string, className: string) {
517
+ this.code =
518
+ Babel.transform(this.code, {
519
+ presets: this.presets,
520
+ filename: "file.tsx", // Ensure it's treated as TSX file
521
+ plugins: [
522
+ () => ({
523
+ visitor: {
524
+ JSXElement(path: any) {
525
+ const openingElement = path.node.openingElement;
526
+ console.log("UPDATE_CLASS_NAME", id, openingElement.attributes);
527
+ const openingAttribute = openingElement.attributes.find(
528
+ (attr: any) => attr?.name?.name === ID_ATTRIBUTE
529
+ );
530
+
531
+ if (
532
+ openingAttribute?.value?.expression?.value === id ||
533
+ openingAttribute?.value?.value === id
534
+ ) {
535
+ const classNameAttribute = openingElement.attributes.find(
536
+ (attr: any) => attr?.name?.name === "className"
537
+ );
538
+ if (classNameAttribute && classNameAttribute?.value) {
539
+ classNameAttribute.value = {
540
+ type: "StringLiteral",
541
+ value: className,
542
+ };
543
+ }
544
+ }
545
+ },
546
+ },
547
+ }),
548
+ ],
549
+ })?.code || "";
550
+ return this;
551
+ }
552
+
553
+ removeImport(source: string) {
554
+ const newCode = Babel.transform(this.code + "", {
555
+ presets: this.presets,
556
+ filename: "file.tsx", // Ensure it's treated as TSX file
557
+ plugins: [
558
+ () => ({
559
+ visitor: {
560
+ ImportDeclaration(path: any) {
561
+ if (path.node?.source?.value === source) {
562
+ path.remove();
563
+ }
564
+ },
565
+ },
566
+ }),
567
+ ],
568
+ })?.code;
569
+ this.code = newCode || "";
570
+ return this;
571
+ }
572
+
573
+ /**
574
+ * Check if the code has import statement from a specific source
575
+ * @param from
576
+ * @returns
577
+ */
578
+ isImported(from: string) {
579
+ const ast = this.ast();
580
+ let isImported = false;
581
+ traverse(ast, {
582
+ ImportDeclaration(path: any) {
583
+ if (path.node.source.value === from) {
584
+ isImported = true;
585
+ }
586
+ },
587
+ });
588
+ return isImported;
589
+ }
590
+
591
+ /**
592
+ * Add import statement to the code
593
+ * @param importStatement
594
+ * @returns
595
+ */
596
+ /**
597
+ * Add import statement to the code
598
+ * @param importStatement
599
+ * @returns
600
+ */
601
+ addImport(params: { default?: string; named?: string[]; from: string }) {
602
+ if (!params.from) {
603
+ throw new Error("Import source cannot be empty");
604
+ }
605
+
606
+ const { default: defaultImport, named = [], from } = params;
607
+ const ast = this.ast();
608
+
609
+ // Check if import already exists
610
+ let importExists = false;
611
+ traverse(ast, {
612
+ ImportDeclaration(path: any) {
613
+ if (path.node.source.value === from) {
614
+ importExists = true;
615
+ // Add new specifiers to existing import
616
+ if (defaultImport) {
617
+ const hasDefault = path.node.specifiers.some(
618
+ (spec: any) => spec.type === "ImportDefaultSpecifier"
619
+ );
620
+ if (!hasDefault) {
621
+ path.node.specifiers.unshift({
622
+ type: "ImportDefaultSpecifier",
623
+ local: {
624
+ type: "Identifier",
625
+ name: defaultImport,
626
+ },
627
+ });
628
+ }
629
+ }
630
+ if (named.length > 0) {
631
+ named.forEach((name) => {
632
+ const hasNamed = path.node.specifiers.some(
633
+ (spec: any) =>
634
+ spec.local.name === name && spec.type === "ImportSpecifier"
635
+ );
636
+ if (!hasNamed) {
637
+ path.node.specifiers.push({
638
+ type: "ImportSpecifier",
639
+ local: {
640
+ type: "Identifier",
641
+ name,
642
+ },
643
+ imported: {
644
+ type: "Identifier",
645
+ name,
646
+ },
647
+ });
648
+ }
649
+ });
650
+ }
651
+ }
652
+ },
653
+ });
654
+
655
+ // If import doesn't exist, create new import declaration
656
+ if (!importExists) {
657
+ const specifiers: any[] = [];
658
+
659
+ if (defaultImport) {
660
+ specifiers.push({
661
+ type: "ImportDefaultSpecifier",
662
+ local: {
663
+ type: "Identifier",
664
+ name: defaultImport,
665
+ },
666
+ });
667
+ }
668
+
669
+ if (named.length > 0) {
670
+ named.forEach((name) => {
671
+ specifiers.push({
672
+ type: "ImportSpecifier",
673
+ local: {
674
+ type: "Identifier",
675
+ name,
676
+ },
677
+ imported: {
678
+ type: "Identifier",
679
+ name,
680
+ },
681
+ });
682
+ });
683
+ }
684
+
685
+ ast.program.body.unshift({
686
+ type: "ImportDeclaration",
687
+ specifiers,
688
+ source: {
689
+ type: "StringLiteral",
690
+ value: from,
691
+ },
692
+ });
693
+ }
694
+
695
+ this.astToCode(ast);
696
+ return this;
697
+ }
698
+
699
+
700
+ /**
701
+ * Add import
702
+ * @param importStatements
703
+ * @returns
704
+ */
705
+ addImportStatements(importStatements: Array<string>) {
706
+ const ast = this.ast();
707
+ importStatements.forEach((importStatement) => {
708
+ const importAst = parse(importStatement, {
709
+ sourceType: "module",
710
+ plugins: ["jsx", "typescript"],
711
+ });
712
+ ast.program.body.push(...importAst.program.body);
713
+ });
714
+ this.astToCode(ast);
715
+ return this;
716
+ }
717
+
718
+
719
+ /**
720
+ * List all imports
721
+ */
722
+ listImports(): Array<ImportStatement> {
723
+ const imports: Array<ImportStatement> = [];
724
+ Babel.transform(this.code, {
725
+ presets: this.presets,
726
+ filename: "file.tsx", // Ensure it's treated as TSX file
727
+ plugins: [
728
+ () => ({
729
+ visitor: {
730
+ ImportDeclaration(path: any) {
731
+ const defaultImport = path.node.specifiers.find(
732
+ (specifier: any) => specifier.type === "ImportDefaultSpecifier"
733
+ );
734
+ const namedImports = path.node.specifiers.filter(
735
+ (specifier: any) => specifier.type === "ImportSpecifier"
736
+ );
737
+ imports.push({
738
+ default: defaultImport?.local.name,
739
+ named: namedImports.map(
740
+ (specifier: any) => specifier.local.name
741
+ ),
742
+ from: path.node.source.value,
743
+ code: path.toString(),
744
+ });
745
+ },
746
+ },
747
+ }),
748
+ ],
749
+ });
750
+ return imports;
751
+ }
752
+
753
+ /**
754
+ * Now only support @/.. import path
755
+ * @param importPath
756
+ */
757
+ getFileContent(
758
+ importPath: string,
759
+ files: IFile[]
760
+ // currentIndexPath: string
761
+ ) {
762
+ if (importPath.startsWith("@/")) {
763
+ const importFile = files.find((file) =>
764
+ file.path.includes(importPath.replace("@/", "") + ".")
765
+ );
766
+ if (importFile) {
767
+ return importFile.content;
768
+ }
769
+ }
770
+ return "";
771
+ }
772
+
773
+ /**
774
+ * List all ids where the JSX element has attribute ID_ATTRIBUTE with value
775
+ */
776
+ listAllIds = (files: IFile[]) => {
777
+ const ids: string[] = [];
778
+ Babel.transform(this.code, {
779
+ presets: this.presets,
780
+ filename: "file.tsx", // Ensure it's treated as TSX file
781
+ plugins: [
782
+ () => ({
783
+ visitor: {
784
+ JSXElement(path: any) {
785
+ const openingElement = path.node.openingElement;
786
+ const openingAttribute = openingElement.attributes.find(
787
+ (attr: any) => attr?.name?.name === ID_ATTRIBUTE
788
+ );
789
+ if (openingAttribute?.value?.expression?.value) {
790
+ ids.push(openingAttribute?.value?.expression?.value);
791
+ }
792
+ },
793
+ },
794
+ }),
795
+ ],
796
+ });
797
+
798
+ // list all imports
799
+ const imports = this.listImports();
800
+ const importFiles = map(imports, (item) => {
801
+ return this.getFileContent(item.from, files);
802
+ });
803
+
804
+ // list all ids
805
+ map(importFiles, (file) => {
806
+ const codeMode = new CodeMod(file);
807
+ const idsInFile = codeMode.listAllIds(files);
808
+ ids.push(...idsInFile);
809
+ });
810
+
811
+ return ids;
812
+ };
813
+
814
+ /**
815
+ * Get the props of a JSX element
816
+ * @param id
817
+ * @returns
818
+ */
819
+ getItemProps(id: string) {
820
+ const attributes = this.getAttributes(id);
821
+ const className = this.getClassName(id);
822
+ return {
823
+ id,
824
+ className,
825
+ ...attributes,
826
+ };
827
+ }
828
+
829
+ /**
830
+ * Update the props of a JSX element
831
+ * Update keeps the existing props and only updates the new props
832
+ * @param id
833
+ * @param props
834
+ * @returns
835
+ */
836
+ updateItemProps(id: string, props: any) {
837
+ const { ...attributes } = props;
838
+ this.code =
839
+ Babel.transform(this.code, {
840
+ presets: this.presets,
841
+ filename: "file.tsx", // Ensure it's treated as TSX file
842
+ plugins: [
843
+ () => ({
844
+ visitor: {
845
+ JSXElement(path: any) {
846
+ const openingElement = path.node.openingElement;
847
+ const openingAttribute = openingElement.attributes.find(
848
+ (attr: any) => attr?.name?.name === ID_ATTRIBUTE
849
+ );
850
+ if (
851
+ openingAttribute?.value?.expression?.value === id ||
852
+ openingAttribute?.value?.value === id
853
+ ) {
854
+ // remove already existing attributes
855
+ for (const key in attributes) {
856
+ openingElement.attributes =
857
+ openingElement.attributes.filter(
858
+ (attr: any) => attr?.name?.name !== key
859
+ );
860
+ }
861
+ for (const key in attributes) {
862
+ openingElement.attributes.push({
863
+ type: "JSXAttribute",
864
+ name: {
865
+ type: "JSXIdentifier",
866
+ name: key,
867
+ },
868
+ value: {
869
+ type: "StringLiteral",
870
+ value: attributes[key],
871
+ },
872
+ });
873
+ }
874
+ }
875
+ },
876
+ },
877
+ }),
878
+ ],
879
+ })?.code || "";
880
+ }
881
+
882
+ updateItemText(id: string, html: string) {
883
+ this.code =
884
+ Babel.transform(this.code, {
885
+ presets: this.presets,
886
+ filename: "file.tsx", // Ensure it's treated as TSX file
887
+ plugins: [
888
+ () => ({
889
+ visitor: {
890
+ JSXElement(path: any) {
891
+ const openingElement = path.node.openingElement;
892
+ const openingAttribute = openingElement.attributes.find(
893
+ (attr: any) => attr?.name?.name === ID_ATTRIBUTE
894
+ );
895
+ if (
896
+ openingAttribute?.value?.expression?.value === id ||
897
+ openingAttribute?.value?.value === id
898
+ ) {
899
+ path.node.children = [
900
+ {
901
+ type: "JSXText",
902
+ value: html,
903
+ },
904
+ ];
905
+ }
906
+ },
907
+ },
908
+ }),
909
+ ],
910
+ })?.code || "";
911
+
912
+ console.log("UPDATE_ITEM_TEXT", id, html, this.code);
913
+ }
914
+
915
+ /**
916
+ * Check if code has logic like
917
+ * Identifier, BlockStatement,..
918
+ * @returns
919
+ */
920
+ checkHasLogicCode() {
921
+ let hasLogicCode = false;
922
+ Babel.transform(this.code, {
923
+ presets: this.presets,
924
+ filename: "file.tsx", // Ensure it's treated as TSX file
925
+ plugins: [
926
+ () => ({
927
+ visitor: {
928
+ Identifier() {
929
+ hasLogicCode = true;
930
+ },
931
+ BlockStatement() {
932
+ hasLogicCode = true;
933
+ },
934
+ },
935
+ }),
936
+ ],
937
+ });
938
+ return hasLogicCode;
939
+ }
940
+
941
+ checkChildrenIsContentEditable(id: string) {
942
+ let isContentEditable = false;
943
+ // isContentEditable it means code of children has only text
944
+ // p, h1, h2, h3, h4, h5, h6, span, div, a, button, label, li, td, th, textarea
945
+
946
+ const ALOWED_TAGS = [
947
+ "p",
948
+ "h1",
949
+ "h2",
950
+ "h3",
951
+ "h4",
952
+ "h5",
953
+ "h6",
954
+ "span",
955
+ "div",
956
+ "a",
957
+ "button",
958
+ "label",
959
+ "li",
960
+ "td",
961
+ "th",
962
+ "textarea",
963
+ ];
964
+
965
+ Babel.transform(this.code, {
966
+ presets: this.presets,
967
+ filename: "file.tsx", // Ensure it's treated as TSX file
968
+ plugins: [
969
+ () => ({
970
+ visitor: {
971
+ JSXElement(path: any) {
972
+ const openingElement = path.node.openingElement;
973
+ const openingAttribute = openingElement.attributes.find(
974
+ (attr: any) => attr?.name?.name === ID_ATTRIBUTE
975
+ );
976
+ if (
977
+ openingAttribute?.value?.expression?.value === id ||
978
+ openingAttribute?.value?.value === id
979
+ ) {
980
+ const tagName = openingElement.name.name;
981
+ if (includes(ALOWED_TAGS, tagName)) {
982
+ isContentEditable = true;
983
+ }
984
+ }
985
+ },
986
+ },
987
+ }),
988
+ ],
989
+ });
990
+
991
+ // And it must have not include any logic code
992
+ if (isContentEditable) {
993
+ const childrenCode = this.getChildrenContent(id);
994
+ const hasLogicCode = new CodeMod(childrenCode).checkHasLogicCode();
995
+ if (hasLogicCode) {
996
+ isContentEditable = false;
997
+ }
998
+ }
999
+
1000
+ return isContentEditable;
1001
+ }
1002
+
1003
+ /**
1004
+ * Get all code of children of a JSX element
1005
+ * @param elementId
1006
+ */
1007
+ getChildrenContent(elementId: string) {
1008
+ let code = "";
1009
+ Babel.transform(this.code, {
1010
+ presets: this.presets,
1011
+ filename: "file.tsx", // Ensure it's treated as TSX file
1012
+ plugins: [
1013
+ () => ({
1014
+ visitor: {
1015
+ JSXElement(path: any) {
1016
+ const openingElement = path.node.openingElement;
1017
+ const openingAttribute = openingElement.attributes.find(
1018
+ (attr: any) => attr?.name?.name === ID_ATTRIBUTE
1019
+ );
1020
+ if (
1021
+ openingAttribute?.value?.expression?.value === elementId ||
1022
+ openingAttribute?.value?.value === elementId
1023
+ ) {
1024
+ code = path.toString();
1025
+ }
1026
+ },
1027
+ },
1028
+ }),
1029
+ ],
1030
+ });
1031
+ return code;
1032
+ }
1033
+
1034
+ /**
1035
+ * Remove a JSX element from the code
1036
+ */
1037
+ removeItem(id: string) {
1038
+ this.code =
1039
+ Babel.transform(this.code, {
1040
+ presets: this.presets,
1041
+ filename: "file.tsx", // Ensure it's treated as TSX file
1042
+ plugins: [
1043
+ () => ({
1044
+ visitor: {
1045
+ JSXElement(path: any) {
1046
+ const openingElement = path.node.openingElement;
1047
+ const openingAttribute = openingElement.attributes.find(
1048
+ (attr: any) => attr?.name?.name === ID_ATTRIBUTE
1049
+ );
1050
+ if (
1051
+ openingAttribute?.value?.expression?.value === id ||
1052
+ openingAttribute?.value?.value === id
1053
+ ) {
1054
+ path.remove();
1055
+ }
1056
+ },
1057
+ },
1058
+ }),
1059
+ ],
1060
+ })?.code || "";
1061
+ }
1062
+
1063
+ /**
1064
+ * Remove all imports
1065
+ * @param oldPath
1066
+ * @param newPath
1067
+ */
1068
+ renameImportSource(oldPath: string, newPath: string) {
1069
+ this.code =
1070
+ Babel.transform(this.code, {
1071
+ presets: this.presets,
1072
+ filename: "file.tsx", // Ensure it's treated as TSX file
1073
+ plugins: [
1074
+ () => ({
1075
+ visitor: {
1076
+ ImportDeclaration(path: any) {
1077
+ if (
1078
+ removeExtension(path.node.source.value) ===
1079
+ removeExtension(oldPath)
1080
+ ) {
1081
+ path.node.source.value = removeExtension(newPath);
1082
+ }
1083
+ },
1084
+ },
1085
+ }),
1086
+ ],
1087
+ })?.code || "";
1088
+ console.log("RENAME_IMPORT_SOURCE_AFTER", this.code);
1089
+ return this;
1090
+ }
1091
+ renameDefaultImport(oldName: string, newName: string) {
1092
+ this.code =
1093
+ Babel.transform(this.code, {
1094
+ presets: this.presets,
1095
+ filename: "file.tsx", // Ensure it's treated as TSX file
1096
+ plugins: [
1097
+ () => ({
1098
+ visitor: {
1099
+ ImportDefaultSpecifier(path: any) {
1100
+ if (path.node.local.name === oldName) {
1101
+ path.node.local.name = newName;
1102
+ }
1103
+ },
1104
+ },
1105
+ }),
1106
+ ],
1107
+ })?.code || "";
1108
+ return this;
1109
+ }
1110
+
1111
+ /**
1112
+ * Only Custom Component
1113
+ * @returns
1114
+ */
1115
+ refactor_RenameAllComponentNames(key: string) {
1116
+ const firstLetterIsUpperCase = (str: string) => {
1117
+ return str[0] === str[0].toUpperCase();
1118
+ };
1119
+
1120
+ // eslint-disable-next-line @typescript-eslint/no-this-alias
1121
+ this.code =
1122
+ Babel.transform(this.code, {
1123
+ presets: this.presets,
1124
+ filename: "file.tsx",
1125
+ plugins: [
1126
+ () => ({
1127
+ visitor: {
1128
+ JSXElement(path: any) {
1129
+ const openingElement = path.node.openingElement;
1130
+ const closingElement = path.node.closingElement;
1131
+ const componentName = openingElement.name.name;
1132
+ console.log("COMPONENT_NAME___", componentName);
1133
+ if (openingElement && firstLetterIsUpperCase(componentName)) {
1134
+ console.log(
1135
+ "FOUND_COMPONENT",
1136
+ openingElement,
1137
+ closingElement
1138
+ );
1139
+ openingElement.name.name = `${componentName}${key}`;
1140
+ if (closingElement)
1141
+ closingElement.name.name = `${componentName}${key}`;
1142
+ }
1143
+ },
1144
+ ImportDeclaration(path: any) {
1145
+ // Refactor all imports
1146
+ // Example: import A from "./A" => import NewA from "./A"
1147
+ // import { A } from "./A" => import { A as NewA } from "./A"
1148
+ console.log("IMPORT_DECLARATION", path.node);
1149
+ const specifiers = path.node.specifiers;
1150
+
1151
+ if (specifiers && specifiers.length > 0) {
1152
+ for (let i = 0; i < specifiers.length; i++) {
1153
+ const specifier = specifiers[i];
1154
+ if (specifier.type === "ImportDefaultSpecifier") {
1155
+ const localName = specifier.local.name;
1156
+ if (firstLetterIsUpperCase(localName)) {
1157
+ specifier.local.name = `${localName}${key}`;
1158
+ }
1159
+ } else if (specifier.type === "ImportSpecifier") {
1160
+ const localName = specifier.local.name;
1161
+ if (firstLetterIsUpperCase(localName)) {
1162
+ specifier.local.name = `${localName}${key}`;
1163
+ }
1164
+ }
1165
+ }
1166
+ }
1167
+ },
1168
+ },
1169
+ }),
1170
+ ],
1171
+ })?.code || "";
1172
+ return this;
1173
+ }
1174
+
1175
+ /**
1176
+ * Refactor all component names
1177
+ * @param attribute
1178
+ */
1179
+ refreshIDAttributes() {
1180
+ this.code =
1181
+ Babel.transform(this.code, {
1182
+ presets: this.presets,
1183
+ filename: "file.tsx",
1184
+ plugins: [
1185
+ () => ({
1186
+ visitor: {
1187
+ JSXElement(path: any) {
1188
+ const openingElement = path.node.openingElement;
1189
+ if (openingElement) {
1190
+ // update ID_ATTRIBUTE to new value
1191
+ const idAttribute = openingElement.attributes.find(
1192
+ (attr: any) => attr?.name?.name === ID_ATTRIBUTE
1193
+ );
1194
+ if (idAttribute) {
1195
+ idAttribute.value = {
1196
+ type: "JSXExpressionContainer",
1197
+ expression: {
1198
+ type: "StringLiteral",
1199
+ value: uuid.v4(),
1200
+ },
1201
+ };
1202
+ }
1203
+ }
1204
+ },
1205
+ },
1206
+ }),
1207
+ ],
1208
+ })?.code || "";
1209
+ return this;
1210
+ }
1211
+
1212
+ /**
1213
+ * Get the tag name of a JSX element
1214
+ * @param id
1215
+ * @returns
1216
+ */
1217
+ getTagName(id: string) {
1218
+ let tagName = "";
1219
+ Babel.transform(this.code, {
1220
+ presets: this.presets,
1221
+ filename: "file.tsx",
1222
+ plugins: [
1223
+ () => ({
1224
+ visitor: {
1225
+ JSXElement(path: any) {
1226
+ const openingElement = path.node.openingElement;
1227
+ const openingAttribute = openingElement.attributes.find(
1228
+ (attr: any) => attr?.name?.name === ID_ATTRIBUTE
1229
+ );
1230
+ if (
1231
+ openingAttribute?.value?.expression?.value === id ||
1232
+ openingAttribute?.value?.value === id
1233
+ ) {
1234
+ tagName = openingElement.name.name;
1235
+ }
1236
+ },
1237
+ },
1238
+ }),
1239
+ ],
1240
+ });
1241
+ return tagName;
1242
+ }
1243
+
1244
+ getRelatedFiles(elementId: string, files: IFile[]) {
1245
+ const allRelatedFiles: IFile[] = [];
1246
+ // find file that contains the elementId
1247
+ const currentFile = files.find((file) => includes(file.content, elementId));
1248
+ const listRelatedFiles = (
1249
+ elementId: string | undefined,
1250
+ file?: IFile
1251
+ ): Array<string> => {
1252
+ if (!file) {
1253
+ return [];
1254
+ }
1255
+ const codeMode = new CodeMod(file.content);
1256
+ const allImports = codeMode.listImports();
1257
+ // list all Component between ID_ATTRIBUTE={elementId}
1258
+ const codeBlock = elementId
1259
+ ? codeMode.extractComponentCode(elementId)
1260
+ : file.content;
1261
+ // List all components in the code block
1262
+ // const codeMod = new CodeMod(codeBlock);
1263
+ const importString: Array<string> = [];
1264
+
1265
+ allImports.forEach((item) => {
1266
+ if (elementId === undefined) {
1267
+ // import all
1268
+ importString.push(item.from);
1269
+ }
1270
+ // if import is in codeBlock
1271
+ if (
1272
+ includes(codeBlock, `<${item.default}`) ||
1273
+ includes(codeBlock, `<${item.named[0]}`)
1274
+ ) {
1275
+ importString.push(item.from);
1276
+ }
1277
+ });
1278
+ if (importString.length > 0) {
1279
+ importString.forEach((importPath) => {
1280
+ const file = files.find((file) => {
1281
+ return "@/" + removeExtension(file.path) === importPath;
1282
+ });
1283
+ if (file) {
1284
+ importString.push(...listRelatedFiles(undefined, file));
1285
+ }
1286
+ });
1287
+ }
1288
+ return importString;
1289
+ };
1290
+
1291
+ const allRelatedPaths = listRelatedFiles(elementId, currentFile);
1292
+ for (const path of allRelatedPaths) {
1293
+ const file = files.find((file) => {
1294
+ return "@/" + removeExtension(file.path) === path;
1295
+ });
1296
+ if (file) {
1297
+ allRelatedFiles.push(file);
1298
+ }
1299
+ }
1300
+
1301
+ return {
1302
+ currentFile,
1303
+ files: allRelatedFiles,
1304
+ };
1305
+ }
1306
+
1307
+ /**
1308
+ * Extract the code of a JSX element
1309
+ * @param elementId
1310
+ * @returns
1311
+ */
1312
+ extractComponentCode(elementId: string) {
1313
+ let code = "";
1314
+ Babel.transform(this.code, {
1315
+ presets: this.presets,
1316
+ filename: "file.tsx",
1317
+ plugins: [
1318
+ () => ({
1319
+ visitor: {
1320
+ JSXElement(path: any) {
1321
+ const openingElement = path.node.openingElement;
1322
+ const openingAttribute = openingElement.attributes.find(
1323
+ (attr: any) => attr?.name?.name === ID_ATTRIBUTE
1324
+ );
1325
+ if (
1326
+ openingAttribute?.value?.expression?.value === elementId ||
1327
+ openingAttribute?.value?.value === elementId
1328
+ ) {
1329
+ code = path.toString();
1330
+ }
1331
+ },
1332
+ },
1333
+ }),
1334
+ ],
1335
+ });
1336
+ return code;
1337
+ }
1338
+
1339
+ /**
1340
+ * List all ids where the JSX element has attribute ID_ATTRIBUTE with value
1341
+ * @returns
1342
+ */
1343
+ listIdAttributes() {
1344
+ const ids: string[] = [];
1345
+ Babel.transform(this.code, {
1346
+ presets: this.presets,
1347
+ filename: "file.tsx",
1348
+ plugins: [
1349
+ () => ({
1350
+ visitor: {
1351
+ JSXElement(path: any) {
1352
+ const openingElement = path.node.openingElement;
1353
+ const openingAttribute = openingElement.attributes.find(
1354
+ (attr: any) => attr?.name?.name === ID_ATTRIBUTE
1355
+ );
1356
+ if (openingAttribute?.value?.expression?.value) {
1357
+ ids.push(openingAttribute?.value?.expression?.value);
1358
+ }
1359
+ },
1360
+ },
1361
+ }),
1362
+ ],
1363
+ });
1364
+ return ids;
1365
+ }
1366
+
1367
+ replaceLiteral(from: string, to: string) {
1368
+ this.code =
1369
+ Babel.transform(this.code, {
1370
+ presets: this.presets,
1371
+ filename: "file.tsx",
1372
+ plugins: [
1373
+ () => ({
1374
+ visitor: {
1375
+ StringLiteral(path: any) {
1376
+ // update the value of the string literal
1377
+ if (path.node.value.includes(from)) {
1378
+ console.log(
1379
+ "===> REPLACE_LITERAL",
1380
+ path.node.value,
1381
+ path.node.value.replace(from, to)
1382
+ );
1383
+ path.node.value = path.node.value.replace(from, to);
1384
+ }
1385
+ },
1386
+ },
1387
+ }),
1388
+ ],
1389
+ })?.code || "";
1390
+ console.log("REPLACE_LITERAL", from, to, this.code);
1391
+ }
1392
+
1393
+ /**
1394
+ * List all identifiers in the code
1395
+ * @returns
1396
+ */
1397
+ listAllIndentifiers() {
1398
+ const identifiers: string[] = [];
1399
+ Babel.transform(this.code, {
1400
+ presets: this.presets,
1401
+ filename: "file.tsx",
1402
+ plugins: [
1403
+ () => ({
1404
+ visitor: {
1405
+ Identifier(path: any) {
1406
+ identifiers.push(path.node.name);
1407
+ },
1408
+ },
1409
+ }),
1410
+ ],
1411
+ });
1412
+ return identifiers;
1413
+ }
1414
+
1415
+ /**
1416
+ * List all component names in the code
1417
+ * @returns
1418
+ */
1419
+ listAllComponentNames() {
1420
+ const ComponentNames: string[] = [];
1421
+ Babel.transform(this.code, {
1422
+ presets: this.presets,
1423
+ filename: "file.tsx",
1424
+ plugins: [
1425
+ () => ({
1426
+ visitor: {
1427
+ JSXElement(path: any) {
1428
+ const openingElement = path.node.openingElement;
1429
+ const componentName = openingElement.name.name;
1430
+ if (
1431
+ componentName &&
1432
+ componentName[0] === componentName[0].toUpperCase()
1433
+ ) {
1434
+ ComponentNames.push(componentName);
1435
+ }
1436
+ },
1437
+ },
1438
+ }),
1439
+ ],
1440
+ });
1441
+ return ComponentNames;
1442
+ }
1443
+
1444
+ listAllComponentIds(componentName: string) {
1445
+ const ComponentIds: string[] = [];
1446
+ Babel.transform(this.code, {
1447
+ presets: this.presets,
1448
+ filename: "file.tsx",
1449
+ plugins: [
1450
+ () => ({
1451
+ visitor: {
1452
+ JSXElement(path: any) {
1453
+ const openingElement = path.node.openingElement;
1454
+ const _componentName = openingElement.name.name;
1455
+ if (componentName === _componentName) {
1456
+ const openingAttribute = openingElement.attributes.find(
1457
+ (attr: any) => attr?.name?.name === ID_ATTRIBUTE
1458
+ );
1459
+ if (openingAttribute?.value?.expression?.value) {
1460
+ ComponentIds.push(openingAttribute?.value?.expression?.value);
1461
+ }
1462
+ }
1463
+ },
1464
+ },
1465
+ }),
1466
+ ],
1467
+ });
1468
+ return ComponentIds;
1469
+ }
1470
+
1471
+ createUUID() {
1472
+ return uuid.v4();
1473
+ }
1474
+
1475
+ addChild(payload: { id: string; content?: string }) {
1476
+ const { id } = payload;
1477
+ const ast = this.ast();
1478
+ traverse(ast, {
1479
+ JSXElement(path: any) {
1480
+ const openingElement = path.node.openingElement;
1481
+ const openingAttribute = openingElement.attributes.find(
1482
+ (attr: any) => attr?.name?.name === ID_ATTRIBUTE
1483
+ );
1484
+ if (
1485
+ openingAttribute?.value?.expression?.value === id ||
1486
+ openingAttribute?.value?.value === id
1487
+ ) {
1488
+ // convert content to JSX
1489
+ const content = payload.content
1490
+ ? parse(payload.content, {
1491
+ sourceType: "module",
1492
+ plugins: ["jsx", "typescript"],
1493
+ }).program.body[0]
1494
+ : null;
1495
+
1496
+ if (content) {
1497
+ path.node.children.push((content as any)?.expression);
1498
+ }
1499
+ }
1500
+ },
1501
+ });
1502
+ this.astToCode(ast);
1503
+ return this;
1504
+ }
1505
+
1506
+ updateElementContent(id: string, content: string) {
1507
+ const ast = this.ast();
1508
+ traverse(ast, {
1509
+ JSXElement(path: any) {
1510
+ const openingElement = path.node.openingElement;
1511
+ const openingAttribute = openingElement.attributes.find(
1512
+ (attr: any) => attr?.name?.name === ID_ATTRIBUTE
1513
+ );
1514
+ if (
1515
+ openingAttribute?.value?.expression?.value === id ||
1516
+ openingAttribute?.value?.value === id
1517
+ ) {
1518
+ // update the children of the element
1519
+ path.node.children = [
1520
+ {
1521
+ type: "JSXText",
1522
+ value: content,
1523
+ },
1524
+ ];
1525
+ }
1526
+ },
1527
+ });
1528
+ this.astToCode(ast);
1529
+ }
1530
+
1531
+ removeElement(id: string) {
1532
+ console.log("REMOVE_ELEMENT", id);
1533
+ const ast = this.ast();
1534
+ traverse(ast, {
1535
+ JSXElement(path: any) {
1536
+ const openingElement = path.node.openingElement;
1537
+ const openingAttribute = openingElement.attributes.find(
1538
+ (attr: any) => attr?.name?.name === ID_ATTRIBUTE
1539
+ );
1540
+ if (
1541
+ openingAttribute?.value?.expression?.value === id ||
1542
+ openingAttribute?.value?.value === id
1543
+ ) {
1544
+ path.remove();
1545
+ }
1546
+ },
1547
+ });
1548
+ this.astToCode(ast);
1549
+ return this;
1550
+ }
1551
+
1552
+ /**
1553
+ * Append ui code into return of default export function
1554
+ * @param code
1555
+ */
1556
+ appendDefaultReturn(code: string) {
1557
+ const ast = this.ast();
1558
+ let isHandled = false;
1559
+ traverse(ast, {
1560
+ ExportDefaultDeclaration(path: any) {
1561
+ if (isHandled) {
1562
+ return;
1563
+ }
1564
+ isHandled = true;
1565
+ console.log("EXPORT_DEFAULT_DECLARATION", path.node);
1566
+ if (path.node.declaration.type === "FunctionDeclaration") {
1567
+ // Find the return statement
1568
+ const body = path.node.declaration.body.body;
1569
+ const returnStatement = body.find(
1570
+ (stmt: any) => stmt.type === "ReturnStatement"
1571
+ );
1572
+ if (returnStatement) {
1573
+ // Append the code to the return statement
1574
+ const newCode = parse(code, {
1575
+ sourceType: "module",
1576
+ plugins: ["jsx", "typescript"],
1577
+ }).program.body[0];
1578
+ const expression = (newCode as any).expression || newCode;
1579
+ returnStatement.argument.children.push(expression);
1580
+ }
1581
+ }
1582
+ },
1583
+ });
1584
+ this.astToCode(ast);
1585
+ }
1586
+ }
1587
+
1588
+ export default CodeMod;