-
-
Notifications
You must be signed in to change notification settings - Fork 5.6k
/
shadow-functions.js
119 lines (91 loc) 路 3.41 KB
/
shadow-functions.js
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
import Plugin from "../plugin";
import * as t from "babel-types";
const SUPER_THIS_BOUND = Symbol("super this bound");
const superVisitor = {
CallExpression(path) {
if (!path.get("callee").isSuper()) return;
const { node } = path;
if (node[SUPER_THIS_BOUND]) return;
node[SUPER_THIS_BOUND] = true;
path.replaceWith(t.assignmentExpression("=", this.id, node));
}
};
export default new Plugin({
name: "internal.shadowFunctions",
visitor: {
ThisExpression(path) {
remap(path, "this");
},
ReferencedIdentifier(path) {
if (path.node.name === "arguments") {
remap(path, "arguments");
}
}
}
});
function shouldShadow(path, shadowPath) {
if (path.is("_forceShadow")) {
return true;
} else {
return shadowPath;
}
}
function remap(path, key) {
// ensure that we're shadowed
const shadowPath = path.inShadow(key);
if (!shouldShadow(path, shadowPath)) return;
const shadowFunction = path.node._shadowedFunctionLiteral;
let currentFunction;
let passedShadowFunction = false;
let fnPath = path.find(function (innerPath) {
if (innerPath.parentPath && innerPath.parentPath.isClassProperty() && innerPath.key === "value") {
return true;
}
if (path === innerPath) return false;
if (innerPath.isProgram() || innerPath.isFunction()) {
// catch current function in case this is the shadowed one and we can ignore it
currentFunction = currentFunction || innerPath;
}
if (innerPath.isProgram()) {
passedShadowFunction = true;
return true;
} else if (innerPath.isFunction() && !innerPath.isArrowFunctionExpression()) {
if (shadowFunction) {
if (innerPath === shadowFunction || innerPath.node === shadowFunction.node) return true;
} else {
if (!innerPath.is("shadow")) return true;
}
passedShadowFunction = true;
return false;
}
return false;
});
if (shadowFunction && fnPath.isProgram() && !shadowFunction.isProgram()) {
// If the shadow wasn't found, take the closest function as a backup.
// This is a bit of a hack, but it will allow the parameter transforms to work properly
// without introducing yet another shadow-controlling flag.
fnPath = path.findParent((p) => p.isProgram() || p.isFunction());
}
// no point in realiasing if we're in this function
if (fnPath === currentFunction) return;
// If the only functions that were encountered are arrow functions, skip remapping the
// binding since arrow function syntax already does that.
if (!passedShadowFunction) return;
const cached = fnPath.getData(key);
if (cached) return path.replaceWith(cached);
const id = path.scope.generateUidIdentifier(key);
fnPath.setData(key, id);
const classPath = fnPath.findParent((p) => p.isClass());
const hasSuperClass = !!(classPath && classPath.node && classPath.node.superClass);
if (key === "this" && fnPath.isMethod({ kind: "constructor" }) && hasSuperClass) {
fnPath.scope.push({ id });
fnPath.traverse(superVisitor, { id });
} else {
const init = key === "this" ? t.thisExpression() : t.identifier(key);
// Forward the shadowed function, so that the identifiers do not get hoisted
// up to the first non shadow function but rather up to the bound shadow function
if (shadowFunction) init._shadowedFunctionLiteral = shadowFunction;
fnPath.scope.push({ id, init });
}
return path.replaceWith(id);
}