UNPKG

9.08 kBJavaScriptView Raw
1"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
2
3var _keywords = require('../parser/tokenizer/keywords');
4var _types = require('../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 * Get information about the class fields for this class, given a token processor pointing to the
46 * open-brace at the start of the class.
47 */
48 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(_types.TokenType.braceR, classContextId)) {
71 if (tokens.matchesContextual(_keywords.ContextualKeyword._constructor) && !tokens.currentToken().isType) {
72 ({constructorInitializerStatements, constructorInsertPos} = processConstructor(tokens));
73 } else if (tokens.matches1(_types.TokenType.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 // Either a method or a field. Skip to the identifier part.
80 const statementStartIndex = tokens.currentIndex();
81 let isStatic = false;
82 while (isAccessModifier(tokens.currentToken())) {
83 if (tokens.matches1(_types.TokenType._static)) {
84 isStatic = true;
85 }
86 tokens.nextToken();
87 }
88 if (
89 tokens.matchesContextual(_keywords.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(_types.TokenType.lessThan) || tokens.matches1(_types.TokenType.parenL)) {
98 // This is a method, so just skip to the next method/field. To do that, we seek forward to
99 // the next start of a class name (either an open bracket or an identifier, or the closing
100 // curly brace), then seek backward to include any access modifiers.
101 while (tokens.currentToken().contextId !== classContextId) {
102 tokens.nextToken();
103 }
104 while (isAccessModifier(tokens.tokenAtRelativeIndex(-1))) {
105 tokens.previousToken();
106 }
107 continue;
108 }
109 // There might be a type annotation that we need to skip.
110 while (tokens.currentToken().isType) {
111 tokens.nextToken();
112 }
113 if (tokens.matches1(_types.TokenType.eq)) {
114 const equalsIndex = tokens.currentIndex();
115 // This is an initializer, so we need to wrap in an initializer method.
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 // Fields start at the name, so `static x = 1;` has a field range of `x = 1;`.
133 fields.push({
134 initializerName,
135 equalsIndex,
136 start: nameStartIndex,
137 end: tokens.currentIndex(),
138 });
139 } else {
140 // This is just a declaration, so doesn't need to produce any code in the output.
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} exports.default = getClassInfo;
157
158function 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(_types.TokenType.name)) {
172 className = tokens.identifierName();
173 }
174 while (!tokens.matchesContextIdAndLabel(_types.TokenType.braceL, contextId)) {
175 // If this has a superclass, there will always be an `extends` token. If it doesn't have a
176 // superclass, only type parameters and `implements` clauses can show up here, all of which
177 // consist only of type tokens. A declaration like `class A<B extends C> {` should *not* count
178 // as having a superclass.
179 if (tokens.matches1(_types.TokenType._extends) && !tokens.currentToken().isType) {
180 hasSuperclass = true;
181 }
182 tokens.nextToken();
183 }
184 return {isExpression, className, hasSuperclass};
185}
186
187/**
188 * Extract useful information out of a constructor, starting at the "constructor" name.
189 */
190function 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 // Advance through parameters looking for access modifiers.
201 while (!tokens.matchesContextIdAndLabel(_types.TokenType.parenR, constructorContextId)) {
202 if (tokens.currentToken().contextId === constructorContextId) {
203 // Current token is an open paren or comma just before a param, so check
204 // that param for access modifiers.
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 !== _types.TokenType.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 // Advance through body looking for a super call.
227 let foundSuperCall = false;
228 while (!tokens.matchesContextIdAndLabel(_types.TokenType.braceR, constructorContextId)) {
229 if (!foundSuperCall && tokens.matches2(_types.TokenType._super, _types.TokenType.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(_types.TokenType.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 * Determine if this is any token that can go before the name in a method/field.
251 */
252function isAccessModifier(token) {
253 return [
254 _types.TokenType._async,
255 _types.TokenType._get,
256 _types.TokenType._set,
257 _types.TokenType.plus,
258 _types.TokenType.minus,
259 _types.TokenType._readonly,
260 _types.TokenType._static,
261 _types.TokenType._public,
262 _types.TokenType._private,
263 _types.TokenType._protected,
264 _types.TokenType._abstract,
265 _types.TokenType.star,
266 ].includes(token.type);
267}
268
269/**
270 * The next token or set of tokens is either an identifier or an expression in square brackets, for
271 * a method or field name.
272 */
273function skipFieldName(tokens) {
274 if (tokens.matches1(_types.TokenType.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(_types.TokenType.bracketR, classContextId)) {
281 tokens.nextToken();
282 }
283 tokens.nextToken();
284 } else {
285 tokens.nextToken();
286 }
287}