1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
|
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
// @flow
import { replaceNode } from "./utils/ast";
import { isTopLevel } from "./utils/helpers";
import generate from "@babel/generator";
import * as t from "@babel/types";
function getAssignmentTarget(node, bindings) {
if (t.isObjectPattern(node)) {
for (const property of node.properties) {
if (t.isRestElement(property)) {
property.argument = getAssignmentTarget(property.argument, bindings);
} else {
property.value = getAssignmentTarget(property.value, bindings);
}
}
return node;
}
if (t.isArrayPattern(node)) {
for (const [i, element] of node.elements.entries()) {
node.elements[i] = getAssignmentTarget(element, bindings);
}
return node;
}
if (t.isAssignmentPattern(node)) {
node.left = getAssignmentTarget(node.left, bindings);
return node;
}
if (t.isRestElement(node)) {
node.argument = getAssignmentTarget(node.argument, bindings);
return node;
}
if (t.isIdentifier(node)) {
return bindings.includes(node.name)
? node
: t.memberExpression(t.identifier("self"), node);
}
return node;
}
// translates new bindings `var a = 3` into `self.a = 3`
// and existing bindings `var a = 3` into `a = 3` for re-assignments
function globalizeDeclaration(node, bindings) {
return node.declarations.map(declaration =>
t.expressionStatement(
t.assignmentExpression(
"=",
getAssignmentTarget(declaration.id, bindings),
declaration.init || t.unaryExpression("void", t.numericLiteral(0))
)
)
);
}
// translates new bindings `a = 3` into `self.a = 3`
// and keeps assignments the same for existing bindings.
function globalizeAssignment(node, bindings) {
return t.assignmentExpression(
node.operator,
getAssignmentTarget(node.left, bindings),
node.right
);
}
export default function mapExpressionBindings(
expression: string,
ast?: Object,
bindings: string[] = []
): string {
let isMapped = false;
let shouldUpdate = true;
t.traverse(ast, (node, ancestors) => {
const parent = ancestors[ancestors.length - 1];
if (t.isWithStatement(node)) {
shouldUpdate = false;
return;
}
if (!isTopLevel(ancestors)) {
return;
}
if (t.isAssignmentExpression(node)) {
if (t.isIdentifier(node.left) || t.isPattern(node.left)) {
const newNode = globalizeAssignment(node, bindings);
isMapped = true;
return replaceNode(ancestors, newNode);
}
return;
}
if (!t.isVariableDeclaration(node)) {
return;
}
if (!t.isForStatement(parent.node)) {
const newNodes = globalizeDeclaration(node, bindings);
isMapped = true;
replaceNode(ancestors, newNodes);
}
});
if (!shouldUpdate || !isMapped) {
return expression;
}
return generate(ast).code;
}
|