1 |
|
2 |
|
3 | import {ContextualKeyword} from "../parser/tokenizer/keywords";
|
4 | import {TokenType as tt} from "../parser/tokenizer/types";
|
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 | export default function getClassInfo(
|
49 | rootTransformer,
|
50 | tokens,
|
51 | nameManager,
|
52 | ) {
|
53 | const snapshot = tokens.snapshot();
|
54 |
|
55 | const headerInfo = processClassHeader(tokens);
|
56 |
|
57 | let constructorInitializerStatements = [];
|
58 | const instanceInitializerNames = [];
|
59 | const staticInitializerNames = [];
|
60 | let constructorInsertPos = null;
|
61 | const fields = [];
|
62 | const rangesToRemove = [];
|
63 |
|
64 | const classContextId = tokens.currentToken().contextId;
|
65 | if (classContextId == null) {
|
66 | throw new Error("Expected non-null class context ID on class open-brace.");
|
67 | }
|
68 |
|
69 | tokens.nextToken();
|
70 | while (!tokens.matchesContextIdAndLabel(tt.braceR, classContextId)) {
|
71 | if (tokens.matchesContextual(ContextualKeyword._constructor) && !tokens.currentToken().isType) {
|
72 | ({constructorInitializerStatements, constructorInsertPos} = processConstructor(tokens));
|
73 | } else if (tokens.matches1(tt.semi)) {
|
74 | rangesToRemove.push({start: tokens.currentIndex(), end: tokens.currentIndex() + 1});
|
75 | tokens.nextToken();
|
76 | } else if (tokens.currentToken().isType) {
|
77 | tokens.nextToken();
|
78 | } else {
|
79 |
|
80 | const statementStartIndex = tokens.currentIndex();
|
81 | let isStatic = false;
|
82 | let isESPrivate = false;
|
83 | while (isAccessModifier(tokens.currentToken())) {
|
84 | if (tokens.matches1(tt._static)) {
|
85 | isStatic = true;
|
86 | }
|
87 | if (tokens.matches1(tt.hash)) {
|
88 | isESPrivate = true;
|
89 | }
|
90 | tokens.nextToken();
|
91 | }
|
92 | if (isStatic && tokens.matches1(tt.braceL)) {
|
93 |
|
94 | skipToNextClassElement(tokens, classContextId);
|
95 | continue;
|
96 | }
|
97 | if (isESPrivate) {
|
98 |
|
99 | skipToNextClassElement(tokens, classContextId);
|
100 | continue;
|
101 | }
|
102 | if (
|
103 | tokens.matchesContextual(ContextualKeyword._constructor) &&
|
104 | !tokens.currentToken().isType
|
105 | ) {
|
106 | ({constructorInitializerStatements, constructorInsertPos} = processConstructor(tokens));
|
107 | continue;
|
108 | }
|
109 |
|
110 | const nameStartIndex = tokens.currentIndex();
|
111 | skipFieldName(tokens);
|
112 | if (tokens.matches1(tt.lessThan) || tokens.matches1(tt.parenL)) {
|
113 |
|
114 | skipToNextClassElement(tokens, classContextId);
|
115 | continue;
|
116 | }
|
117 |
|
118 | while (tokens.currentToken().isType) {
|
119 | tokens.nextToken();
|
120 | }
|
121 | if (tokens.matches1(tt.eq)) {
|
122 | const equalsIndex = tokens.currentIndex();
|
123 |
|
124 | const valueEnd = tokens.currentToken().rhsEndIndex;
|
125 | if (valueEnd == null) {
|
126 | throw new Error("Expected rhsEndIndex on class field assignment.");
|
127 | }
|
128 | tokens.nextToken();
|
129 | while (tokens.currentIndex() < valueEnd) {
|
130 | rootTransformer.processToken();
|
131 | }
|
132 | let initializerName;
|
133 | if (isStatic) {
|
134 | initializerName = nameManager.claimFreeName("__initStatic");
|
135 | staticInitializerNames.push(initializerName);
|
136 | } else {
|
137 | initializerName = nameManager.claimFreeName("__init");
|
138 | instanceInitializerNames.push(initializerName);
|
139 | }
|
140 |
|
141 | fields.push({
|
142 | initializerName,
|
143 | equalsIndex,
|
144 | start: nameStartIndex,
|
145 | end: tokens.currentIndex(),
|
146 | });
|
147 | } else {
|
148 |
|
149 | rangesToRemove.push({start: statementStartIndex, end: tokens.currentIndex()});
|
150 | }
|
151 | }
|
152 | }
|
153 |
|
154 | tokens.restoreToSnapshot(snapshot);
|
155 | return {
|
156 | headerInfo,
|
157 | constructorInitializerStatements,
|
158 | instanceInitializerNames,
|
159 | staticInitializerNames,
|
160 | constructorInsertPos,
|
161 | fields,
|
162 | rangesToRemove,
|
163 | };
|
164 | }
|
165 |
|
166 |
|
167 |
|
168 |
|
169 |
|
170 |
|
171 |
|
172 |
|
173 | function skipToNextClassElement(tokens, classContextId) {
|
174 | tokens.nextToken();
|
175 | while (tokens.currentToken().contextId !== classContextId) {
|
176 | tokens.nextToken();
|
177 | }
|
178 | while (isAccessModifier(tokens.tokenAtRelativeIndex(-1))) {
|
179 | tokens.previousToken();
|
180 | }
|
181 | }
|
182 |
|
183 | function processClassHeader(tokens) {
|
184 | const classToken = tokens.currentToken();
|
185 | const contextId = classToken.contextId;
|
186 | if (contextId == null) {
|
187 | throw new Error("Expected context ID on class token.");
|
188 | }
|
189 | const isExpression = classToken.isExpression;
|
190 | if (isExpression == null) {
|
191 | throw new Error("Expected isExpression on class token.");
|
192 | }
|
193 | let className = null;
|
194 | let hasSuperclass = false;
|
195 | tokens.nextToken();
|
196 | if (tokens.matches1(tt.name)) {
|
197 | className = tokens.identifierName();
|
198 | }
|
199 | while (!tokens.matchesContextIdAndLabel(tt.braceL, contextId)) {
|
200 |
|
201 |
|
202 |
|
203 |
|
204 | if (tokens.matches1(tt._extends) && !tokens.currentToken().isType) {
|
205 | hasSuperclass = true;
|
206 | }
|
207 | tokens.nextToken();
|
208 | }
|
209 | return {isExpression, className, hasSuperclass};
|
210 | }
|
211 |
|
212 |
|
213 |
|
214 |
|
215 | function processConstructor(
|
216 | tokens,
|
217 | ) {
|
218 | const constructorInitializerStatements = [];
|
219 |
|
220 | tokens.nextToken();
|
221 | const constructorContextId = tokens.currentToken().contextId;
|
222 | if (constructorContextId == null) {
|
223 | throw new Error("Expected context ID on open-paren starting constructor params.");
|
224 | }
|
225 |
|
226 | while (!tokens.matchesContextIdAndLabel(tt.parenR, constructorContextId)) {
|
227 | if (tokens.currentToken().contextId === constructorContextId) {
|
228 |
|
229 |
|
230 | tokens.nextToken();
|
231 | if (isAccessModifier(tokens.currentToken())) {
|
232 | tokens.nextToken();
|
233 | while (isAccessModifier(tokens.currentToken())) {
|
234 | tokens.nextToken();
|
235 | }
|
236 | const token = tokens.currentToken();
|
237 | if (token.type !== tt.name) {
|
238 | throw new Error("Expected identifier after access modifiers in constructor arg.");
|
239 | }
|
240 | const name = tokens.identifierNameForToken(token);
|
241 | constructorInitializerStatements.push(`this.${name} = ${name}`);
|
242 | }
|
243 | } else {
|
244 | tokens.nextToken();
|
245 | }
|
246 | }
|
247 |
|
248 | tokens.nextToken();
|
249 | let constructorInsertPos = tokens.currentIndex();
|
250 |
|
251 |
|
252 | let foundSuperCall = false;
|
253 | while (!tokens.matchesContextIdAndLabel(tt.braceR, constructorContextId)) {
|
254 | if (!foundSuperCall && tokens.matches2(tt._super, tt.parenL)) {
|
255 | tokens.nextToken();
|
256 | const superCallContextId = tokens.currentToken().contextId;
|
257 | if (superCallContextId == null) {
|
258 | throw new Error("Expected a context ID on the super call");
|
259 | }
|
260 | while (!tokens.matchesContextIdAndLabel(tt.parenR, superCallContextId)) {
|
261 | tokens.nextToken();
|
262 | }
|
263 | constructorInsertPos = tokens.currentIndex();
|
264 | foundSuperCall = true;
|
265 | }
|
266 | tokens.nextToken();
|
267 | }
|
268 |
|
269 | tokens.nextToken();
|
270 |
|
271 | return {constructorInitializerStatements, constructorInsertPos};
|
272 | }
|
273 |
|
274 |
|
275 |
|
276 |
|
277 | function isAccessModifier(token) {
|
278 | return [
|
279 | tt._async,
|
280 | tt._get,
|
281 | tt._set,
|
282 | tt.plus,
|
283 | tt.minus,
|
284 | tt._readonly,
|
285 | tt._static,
|
286 | tt._public,
|
287 | tt._private,
|
288 | tt._protected,
|
289 | tt._abstract,
|
290 | tt.star,
|
291 | tt._declare,
|
292 | tt.hash,
|
293 | ].includes(token.type);
|
294 | }
|
295 |
|
296 |
|
297 |
|
298 |
|
299 |
|
300 | function skipFieldName(tokens) {
|
301 | if (tokens.matches1(tt.bracketL)) {
|
302 | const startToken = tokens.currentToken();
|
303 | const classContextId = startToken.contextId;
|
304 | if (classContextId == null) {
|
305 | throw new Error("Expected class context ID on computed name open bracket.");
|
306 | }
|
307 | while (!tokens.matchesContextIdAndLabel(tt.bracketR, classContextId)) {
|
308 | tokens.nextToken();
|
309 | }
|
310 | tokens.nextToken();
|
311 | } else {
|
312 | tokens.nextToken();
|
313 | }
|
314 | }
|