|
1 | 1 | import type { NodePath } from "@babel/traverse" |
2 | 2 | import babelTraverse from "@babel/traverse" |
3 | | -import type { BlockStatement, CallExpression, File, FunctionDeclaration } from "@babel/types" |
| 3 | +import type { BlockStatement, CallExpression, File, FunctionDeclaration, Identifier } from "@babel/types" |
4 | 4 | import t from "@babel/types" |
5 | 5 | import type { LaxPartial } from "@samual/lib" |
6 | 6 | import { assert } from "@samual/lib/assert" |
@@ -697,6 +697,83 @@ export function transform( |
697 | 697 | } |
698 | 698 | } |
699 | 699 | }, |
| 700 | + ObjectExpression(path) { |
| 701 | + const { node: object, scope, parent } = path |
| 702 | + |
| 703 | + const evenMoreUniqueId = Math.floor(Math.random() * (2 ** 52)).toString(36).padStart(11, `0`) |
| 704 | + |
| 705 | + // This removes the additional let that would normally be inserted from this sort of construct: |
| 706 | + // const foo = { |
| 707 | + // bar() { this.whatever = 1 } |
| 708 | + // } |
| 709 | + const reuseDeclaredName = parent.type == `VariableDeclarator` |
| 710 | + && path.parentPath?.parentPath?.node?.type == `VariableDeclaration` |
| 711 | + && path.parentPath?.parentPath?.node?.kind == `const` // This is only safe if it's not redeclared! |
| 712 | + && parent.id.type == `Identifier` |
| 713 | + |
| 714 | + let thisId = reuseDeclaredName ? (parent.id as Identifier).name : `_${evenMoreUniqueId}_THIS_` |
| 715 | + |
| 716 | + let thisIsReferenced = false |
| 717 | + for (const property of object.properties) { |
| 718 | + if (property.type != `ObjectMethod`) |
| 719 | + continue |
| 720 | + |
| 721 | + traverse(property.body, { |
| 722 | + ThisExpression(path) { |
| 723 | + thisIsReferenced = true |
| 724 | + path.replaceWith(t.identifier(thisId)) |
| 725 | + }, |
| 726 | + Function(path) { |
| 727 | + if (path.node.type != `ArrowFunctionExpression`) { |
| 728 | + path.skip() |
| 729 | + } |
| 730 | + } |
| 731 | + }, scope); |
| 732 | + } |
| 733 | + |
| 734 | + if (!thisIsReferenced) return |
| 735 | + if (reuseDeclaredName) return |
| 736 | + |
| 737 | + path.replaceWith( |
| 738 | + t.assignmentExpression(`=`, t.identifier(thisId), object) |
| 739 | + ) |
| 740 | + |
| 741 | + let someBlock = null |
| 742 | + let currentParent: NodePath<any> | null = path |
| 743 | + while (currentParent) { |
| 744 | + if (!currentParent || !currentParent.node) break |
| 745 | + |
| 746 | + if (t.isBlock(currentParent.node)) { |
| 747 | + someBlock = currentParent.node |
| 748 | + break |
| 749 | + } else if (t.isArrowFunctionExpression(currentParent.parentPath?.node)) { |
| 750 | + // This means we're in an arrow function like () => 1. |
| 751 | + // The arrow function can have a block, as a treat |
| 752 | + currentParent.replaceWith( |
| 753 | + t.blockStatement([ |
| 754 | + t.returnStatement( |
| 755 | + currentParent.node, |
| 756 | + ), |
| 757 | + ]), |
| 758 | + ) |
| 759 | + someBlock = currentParent.node |
| 760 | + break |
| 761 | + } |
| 762 | + |
| 763 | + currentParent = currentParent.parentPath |
| 764 | + } |
| 765 | + |
| 766 | + assert(someBlock != null, HERE) |
| 767 | + |
| 768 | + someBlock!.body.unshift( |
| 769 | + t.variableDeclaration(`let`, [ |
| 770 | + t.variableDeclarator( |
| 771 | + t.identifier(thisId), |
| 772 | + null |
| 773 | + ), |
| 774 | + ]), |
| 775 | + ) |
| 776 | + }, |
700 | 777 | ClassBody({ node: classBody, scope, parent }) { |
701 | 778 | assert(t.isClass(parent), HERE) |
702 | 779 |
|
|
0 commit comments