Skip to content

Commit a1c35c2

Browse files
committed
Replace this in array elements too
1 parent 359d05a commit a1c35c2

File tree

1 file changed

+106
-77
lines changed

1 file changed

+106
-77
lines changed

src/processScript/transform.ts

Lines changed: 106 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import type { NodePath } from "@babel/traverse"
1+
import type { NodePath, Scope } from "@babel/traverse"
22
import traverse from "@babel/traverse"
3-
import type { BlockStatement, CallExpression, File, FunctionDeclaration, Identifier } from "@babel/types"
3+
import type { ArrayExpression, Block, BlockStatement, CallExpression, File, FunctionDeclaration, Identifier, Node, ObjectExpression } from "@babel/types"
44
import t from "@babel/types"
55
import type { LaxPartial } from "@samual/lib"
66
import { assert } from "@samual/lib/assert"
@@ -675,6 +675,106 @@ export function transform(
675675
))
676676
}
677677

678+
const getFirstParentBlock = (path: NodePath): Block => {
679+
let someBlock: Block | null = null
680+
let currentParent: NodePath<any> | null = path
681+
while (currentParent) {
682+
if (!currentParent || !currentParent.node) break
683+
684+
if (t.isBlock(currentParent.node)) {
685+
someBlock = currentParent.node
686+
break
687+
} else if (t.isArrowFunctionExpression(currentParent.parentPath?.node)) {
688+
// This means we're in an arrow function like () => 1.
689+
// The arrow function can have a block, as a treat
690+
currentParent.replaceWith(
691+
t.blockStatement([
692+
t.returnStatement(
693+
currentParent.node,
694+
),
695+
]),
696+
)
697+
someBlock = currentParent.node
698+
break
699+
}
700+
701+
currentParent = currentParent.parentPath
702+
}
703+
704+
// Technically, this can't happen, since the Program node will be a block.
705+
assert(someBlock != null, HERE)
706+
return someBlock;
707+
}
708+
709+
const replaceAllThisWith = (node: Node, scope: Scope, thisId: string): boolean => {
710+
let thisIsReferenced = false
711+
traverse(node, {
712+
ThisExpression(path) {
713+
thisIsReferenced = true
714+
path.replaceWith(t.identifier(thisId))
715+
},
716+
Function(path) {
717+
if (path.node.type != `ArrowFunctionExpression`) {
718+
path.skip()
719+
}
720+
}
721+
}, scope)
722+
723+
return thisIsReferenced
724+
}
725+
726+
type ObjectLikeExpression = ObjectExpression | ArrayExpression
727+
const replaceThisInObjectLikeDefinition = <T extends ObjectLikeExpression>(path: NodePath<T>) => {
728+
const { node: object, scope, parent } = path
729+
730+
const evenMoreUniqueId = Math.floor(Math.random() * (2 ** 52)).toString(36).padStart(11, `0`)
731+
732+
// This removes the additional let that would normally be inserted from this sort of construct:
733+
// const foo = {
734+
// bar() { this.whatever = 1 }
735+
// }
736+
const reuseDeclaredName = parent.type == `VariableDeclarator`
737+
&& path.parentPath?.parentPath?.node?.type == `VariableDeclaration`
738+
&& path.parentPath?.parentPath?.node?.kind == `const` // This is only safe if it's not redeclared!
739+
&& parent.id.type == `Identifier`
740+
741+
let thisId = reuseDeclaredName ? (parent.id as Identifier).name : `_${evenMoreUniqueId}_THIS_`
742+
743+
let thisIsReferenced = false
744+
if (path.type == `ObjectExpression`) {
745+
for (const property of (object as ObjectExpression).properties) {
746+
if (property.type != `ObjectMethod`)
747+
continue
748+
749+
thisIsReferenced ||= replaceAllThisWith(object, scope, thisId)
750+
}
751+
} else {
752+
for (const element of (object as ArrayExpression).elements) {
753+
if (element == null)
754+
continue
755+
756+
thisIsReferenced ||= replaceAllThisWith(element, scope, thisId)
757+
}
758+
}
759+
760+
if (!thisIsReferenced) return
761+
if (reuseDeclaredName) return
762+
763+
path.replaceWith(
764+
t.assignmentExpression(`=`, t.identifier(thisId), object)
765+
)
766+
767+
const parentBlock = getFirstParentBlock(path);
768+
parentBlock.body.unshift(
769+
t.variableDeclaration(`let`, [
770+
t.variableDeclarator(
771+
t.identifier(thisId),
772+
null
773+
),
774+
]),
775+
)
776+
}
777+
678778
traverse(file, {
679779
BlockStatement({ node: blockStatement }) {
680780
for (const [ index, functionDeclaration ] of blockStatement.body.entries()) {
@@ -695,81 +795,10 @@ export function transform(
695795
}
696796
},
697797
ObjectExpression(path) {
698-
const { node: object, scope, parent } = path
699-
700-
const evenMoreUniqueId = Math.floor(Math.random() * (2 ** 52)).toString(36).padStart(11, `0`)
701-
702-
// This removes the additional let that would normally be inserted from this sort of construct:
703-
// const foo = {
704-
// bar() { this.whatever = 1 }
705-
// }
706-
const reuseDeclaredName = parent.type == `VariableDeclarator`
707-
&& path.parentPath?.parentPath?.node?.type == `VariableDeclaration`
708-
&& path.parentPath?.parentPath?.node?.kind == `const` // This is only safe if it's not redeclared!
709-
&& parent.id.type == `Identifier`
710-
711-
let thisId = reuseDeclaredName ? (parent.id as Identifier).name : `_${evenMoreUniqueId}_THIS_`
712-
713-
let thisIsReferenced = false
714-
for (const property of object.properties) {
715-
if (property.type != `ObjectMethod`)
716-
continue
717-
718-
traverse(property.body, {
719-
ThisExpression(path) {
720-
thisIsReferenced = true
721-
path.replaceWith(t.identifier(thisId))
722-
},
723-
Function(path) {
724-
if (path.node.type != `ArrowFunctionExpression`) {
725-
path.skip()
726-
}
727-
}
728-
}, scope);
729-
}
730-
731-
if (!thisIsReferenced) return
732-
if (reuseDeclaredName) return
733-
734-
path.replaceWith(
735-
t.assignmentExpression(`=`, t.identifier(thisId), object)
736-
)
737-
738-
let someBlock = null
739-
let currentParent: NodePath<any> | null = path
740-
while (currentParent) {
741-
if (!currentParent || !currentParent.node) break
742-
743-
if (t.isBlock(currentParent.node)) {
744-
someBlock = currentParent.node
745-
break
746-
} else if (t.isArrowFunctionExpression(currentParent.parentPath?.node)) {
747-
// This means we're in an arrow function like () => 1.
748-
// The arrow function can have a block, as a treat
749-
currentParent.replaceWith(
750-
t.blockStatement([
751-
t.returnStatement(
752-
currentParent.node,
753-
),
754-
]),
755-
)
756-
someBlock = currentParent.node
757-
break
758-
}
759-
760-
currentParent = currentParent.parentPath
761-
}
762-
763-
assert(someBlock != null, HERE)
764-
765-
someBlock!.body.unshift(
766-
t.variableDeclaration(`let`, [
767-
t.variableDeclarator(
768-
t.identifier(thisId),
769-
null
770-
),
771-
]),
772-
)
798+
replaceThisInObjectLikeDefinition(path)
799+
},
800+
ArrayExpression(path) {
801+
replaceThisInObjectLikeDefinition(path)
773802
},
774803
ClassBody({ node: classBody, scope, parent }) {
775804
assert(t.isClass(parent), HERE)

0 commit comments

Comments
 (0)