/
misc.ts
154 lines (136 loc) 路 4.18 KB
/
misc.ts
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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
import { template, traverse, types as t } from "@babel/core";
import type { File } from "@babel/core";
import type { NodePath, Scope, Visitor, Binding } from "@babel/traverse";
import environmentVisitor from "@babel/helper-environment-visitor";
const findBareSupers = traverse.visitors.merge<NodePath<t.CallExpression>[]>([
{
Super(path) {
const { node, parentPath } = path;
if (parentPath.isCallExpression({ callee: node })) {
this.push(parentPath);
}
},
},
environmentVisitor,
]);
const referenceVisitor: Visitor<{ scope: Scope }> = {
"TSTypeAnnotation|TypeAnnotation"(
path: NodePath<t.TSTypeAnnotation | t.TypeAnnotation>,
) {
path.skip();
},
ReferencedIdentifier(path: NodePath<t.Identifier>, { scope }) {
if (scope.hasOwnBinding(path.node.name)) {
scope.rename(path.node.name);
path.skip();
}
},
};
type HandleClassTDZState = {
classBinding: Binding;
file: File;
};
function handleClassTDZ(
path: NodePath<t.Identifier>,
state: HandleClassTDZState,
) {
if (
state.classBinding &&
state.classBinding === path.scope.getBinding(path.node.name)
) {
const classNameTDZError = state.file.addHelper("classNameTDZError");
const throwNode = t.callExpression(classNameTDZError, [
t.stringLiteral(path.node.name),
]);
path.replaceWith(t.sequenceExpression([throwNode, path.node]));
path.skip();
}
}
const classFieldDefinitionEvaluationTDZVisitor: Visitor<HandleClassTDZState> = {
ReferencedIdentifier: handleClassTDZ,
};
interface RenamerState {
scope: Scope;
}
export function injectInitialization(
path: NodePath<t.Class>,
constructor: NodePath<t.ClassMethod> | undefined,
nodes: t.Statement[],
renamer?: (visitor: Visitor<RenamerState>, state: RenamerState) => void,
) {
if (!nodes.length) return;
const isDerived = !!path.node.superClass;
if (!constructor) {
const newConstructor = t.classMethod(
"constructor",
t.identifier("constructor"),
[],
t.blockStatement([]),
);
if (isDerived) {
newConstructor.params = [t.restElement(t.identifier("args"))];
newConstructor.body.body.push(template.statement.ast`super(...args)`);
}
[constructor] = path
.get("body")
.unshiftContainer("body", newConstructor) as NodePath<t.ClassMethod>[];
}
if (renamer) {
renamer(referenceVisitor, { scope: constructor.scope });
}
if (isDerived) {
const bareSupers: NodePath<t.CallExpression>[] = [];
constructor.traverse(findBareSupers, bareSupers);
let isFirst = true;
for (const bareSuper of bareSupers) {
if (isFirst) {
bareSuper.insertAfter(nodes);
isFirst = false;
} else {
bareSuper.insertAfter(nodes.map(n => t.cloneNode(n)));
}
}
} else {
constructor.get("body").unshiftContainer("body", nodes);
}
}
export function extractComputedKeys(
path: NodePath<t.Class>,
computedPaths: NodePath<t.ClassProperty | t.ClassMethod>[],
file: File,
) {
const declarations: t.ExpressionStatement[] = [];
const state = {
classBinding: path.node.id && path.scope.getBinding(path.node.id.name),
file,
};
for (const computedPath of computedPaths) {
const computedKey = computedPath.get("key");
if (computedKey.isReferencedIdentifier()) {
handleClassTDZ(computedKey, state);
} else {
computedKey.traverse(classFieldDefinitionEvaluationTDZVisitor, state);
}
const computedNode = computedPath.node;
// Make sure computed property names are only evaluated once (upon class definition)
// and in the right order in combination with static properties
if (!computedKey.isConstantExpression()) {
const ident = path.scope.generateUidIdentifierBasedOnNode(
computedNode.key,
);
// Declaring in the same block scope
// Ref: https://github.com/babel/babel/pull/10029/files#diff-fbbdd83e7a9c998721c1484529c2ce92
path.scope.push({
id: ident,
kind: "let",
});
declarations.push(
t.expressionStatement(
t.assignmentExpression("=", t.cloneNode(ident), computedNode.key),
),
);
computedNode.key = t.cloneNode(ident);
}
}
return declarations;
}