Skip to content

Commit c9bba02

Browse files
committed
#181: replace this in object methods
1 parent 2579301 commit c9bba02

File tree

1 file changed

+78
-1
lines changed

1 file changed

+78
-1
lines changed

src/processScript/transform.ts

Lines changed: 78 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { NodePath } from "@babel/traverse"
22
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"
44
import t from "@babel/types"
55
import type { LaxPartial } from "@samual/lib"
66
import { assert } from "@samual/lib/assert"
@@ -697,6 +697,83 @@ export function transform(
697697
}
698698
}
699699
},
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+
},
700777
ClassBody({ node: classBody, scope, parent }) {
701778
assert(t.isClass(parent), HERE)
702779

0 commit comments

Comments
 (0)