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