source: frontend/node_modules/sucrase/dist/esm/parser/tokenizer/index.js

Last change on this file was 9af201e, checked in by MBK <marija.karapandzova@…>, 2 weeks ago

Fix frontend appearance

  • Property mode set to 100644
File size: 28.6 KB
Line 
1/* eslint max-len: 0 */
2
3import {input, isFlowEnabled, state} from "../traverser/base";
4import {unexpected} from "../traverser/util";
5import {charCodes} from "../util/charcodes";
6import {IS_IDENTIFIER_CHAR, IS_IDENTIFIER_START} from "../util/identifier";
7import {IS_WHITESPACE, skipWhiteSpace} from "../util/whitespace";
8import {ContextualKeyword} from "./keywords";
9import readWord from "./readWord";
10import { TokenType as tt} from "./types";
11
12export var IdentifierRole; (function (IdentifierRole) {
13 const Access = 0; IdentifierRole[IdentifierRole["Access"] = Access] = "Access";
14 const ExportAccess = Access + 1; IdentifierRole[IdentifierRole["ExportAccess"] = ExportAccess] = "ExportAccess";
15 const TopLevelDeclaration = ExportAccess + 1; IdentifierRole[IdentifierRole["TopLevelDeclaration"] = TopLevelDeclaration] = "TopLevelDeclaration";
16 const FunctionScopedDeclaration = TopLevelDeclaration + 1; IdentifierRole[IdentifierRole["FunctionScopedDeclaration"] = FunctionScopedDeclaration] = "FunctionScopedDeclaration";
17 const BlockScopedDeclaration = FunctionScopedDeclaration + 1; IdentifierRole[IdentifierRole["BlockScopedDeclaration"] = BlockScopedDeclaration] = "BlockScopedDeclaration";
18 const ObjectShorthandTopLevelDeclaration = BlockScopedDeclaration + 1; IdentifierRole[IdentifierRole["ObjectShorthandTopLevelDeclaration"] = ObjectShorthandTopLevelDeclaration] = "ObjectShorthandTopLevelDeclaration";
19 const ObjectShorthandFunctionScopedDeclaration = ObjectShorthandTopLevelDeclaration + 1; IdentifierRole[IdentifierRole["ObjectShorthandFunctionScopedDeclaration"] = ObjectShorthandFunctionScopedDeclaration] = "ObjectShorthandFunctionScopedDeclaration";
20 const ObjectShorthandBlockScopedDeclaration = ObjectShorthandFunctionScopedDeclaration + 1; IdentifierRole[IdentifierRole["ObjectShorthandBlockScopedDeclaration"] = ObjectShorthandBlockScopedDeclaration] = "ObjectShorthandBlockScopedDeclaration";
21 const ObjectShorthand = ObjectShorthandBlockScopedDeclaration + 1; IdentifierRole[IdentifierRole["ObjectShorthand"] = ObjectShorthand] = "ObjectShorthand";
22 // Any identifier bound in an import statement, e.g. both A and b from
23 // `import A, * as b from 'A';`
24 const ImportDeclaration = ObjectShorthand + 1; IdentifierRole[IdentifierRole["ImportDeclaration"] = ImportDeclaration] = "ImportDeclaration";
25 const ObjectKey = ImportDeclaration + 1; IdentifierRole[IdentifierRole["ObjectKey"] = ObjectKey] = "ObjectKey";
26 // The `foo` in `import {foo as bar} from "./abc";`.
27 const ImportAccess = ObjectKey + 1; IdentifierRole[IdentifierRole["ImportAccess"] = ImportAccess] = "ImportAccess";
28})(IdentifierRole || (IdentifierRole = {}));
29
30/**
31 * Extra information on jsxTagStart tokens, used to determine which of the three
32 * jsx functions are called in the automatic transform.
33 */
34export var JSXRole; (function (JSXRole) {
35 // The element is self-closing or has a body that resolves to empty. We
36 // shouldn't emit children at all in this case.
37 const NoChildren = 0; JSXRole[JSXRole["NoChildren"] = NoChildren] = "NoChildren";
38 // The element has a single explicit child, which might still be an arbitrary
39 // expression like an array. We should emit that expression as the children.
40 const OneChild = NoChildren + 1; JSXRole[JSXRole["OneChild"] = OneChild] = "OneChild";
41 // The element has at least two explicitly-specified children or has spread
42 // children, so child positions are assumed to be "static". We should wrap
43 // these children in an array.
44 const StaticChildren = OneChild + 1; JSXRole[JSXRole["StaticChildren"] = StaticChildren] = "StaticChildren";
45 // The element has a prop named "key" after a prop spread, so we should fall
46 // back to the createElement function.
47 const KeyAfterPropSpread = StaticChildren + 1; JSXRole[JSXRole["KeyAfterPropSpread"] = KeyAfterPropSpread] = "KeyAfterPropSpread";
48})(JSXRole || (JSXRole = {}));
49
50export function isDeclaration(token) {
51 const role = token.identifierRole;
52 return (
53 role === IdentifierRole.TopLevelDeclaration ||
54 role === IdentifierRole.FunctionScopedDeclaration ||
55 role === IdentifierRole.BlockScopedDeclaration ||
56 role === IdentifierRole.ObjectShorthandTopLevelDeclaration ||
57 role === IdentifierRole.ObjectShorthandFunctionScopedDeclaration ||
58 role === IdentifierRole.ObjectShorthandBlockScopedDeclaration
59 );
60}
61
62export function isNonTopLevelDeclaration(token) {
63 const role = token.identifierRole;
64 return (
65 role === IdentifierRole.FunctionScopedDeclaration ||
66 role === IdentifierRole.BlockScopedDeclaration ||
67 role === IdentifierRole.ObjectShorthandFunctionScopedDeclaration ||
68 role === IdentifierRole.ObjectShorthandBlockScopedDeclaration
69 );
70}
71
72export function isTopLevelDeclaration(token) {
73 const role = token.identifierRole;
74 return (
75 role === IdentifierRole.TopLevelDeclaration ||
76 role === IdentifierRole.ObjectShorthandTopLevelDeclaration ||
77 role === IdentifierRole.ImportDeclaration
78 );
79}
80
81export function isBlockScopedDeclaration(token) {
82 const role = token.identifierRole;
83 // Treat top-level declarations as block scope since the distinction doesn't matter here.
84 return (
85 role === IdentifierRole.TopLevelDeclaration ||
86 role === IdentifierRole.BlockScopedDeclaration ||
87 role === IdentifierRole.ObjectShorthandTopLevelDeclaration ||
88 role === IdentifierRole.ObjectShorthandBlockScopedDeclaration
89 );
90}
91
92export function isFunctionScopedDeclaration(token) {
93 const role = token.identifierRole;
94 return (
95 role === IdentifierRole.FunctionScopedDeclaration ||
96 role === IdentifierRole.ObjectShorthandFunctionScopedDeclaration
97 );
98}
99
100export function isObjectShorthandDeclaration(token) {
101 return (
102 token.identifierRole === IdentifierRole.ObjectShorthandTopLevelDeclaration ||
103 token.identifierRole === IdentifierRole.ObjectShorthandBlockScopedDeclaration ||
104 token.identifierRole === IdentifierRole.ObjectShorthandFunctionScopedDeclaration
105 );
106}
107
108// Object type used to represent tokens. Note that normally, tokens
109// simply exist as properties on the parser object. This is only
110// used for the onToken callback and the external tokenizer.
111export class Token {
112 constructor() {
113 this.type = state.type;
114 this.contextualKeyword = state.contextualKeyword;
115 this.start = state.start;
116 this.end = state.end;
117 this.scopeDepth = state.scopeDepth;
118 this.isType = state.isType;
119 this.identifierRole = null;
120 this.jsxRole = null;
121 this.shadowsGlobal = false;
122 this.isAsyncOperation = false;
123 this.contextId = null;
124 this.rhsEndIndex = null;
125 this.isExpression = false;
126 this.numNullishCoalesceStarts = 0;
127 this.numNullishCoalesceEnds = 0;
128 this.isOptionalChainStart = false;
129 this.isOptionalChainEnd = false;
130 this.subscriptStartIndex = null;
131 this.nullishStartIndex = null;
132 }
133
134
135
136
137
138
139
140
141
142 // Initially false for all tokens, then may be computed in a follow-up step that does scope
143 // analysis.
144
145 // Initially false for all tokens, but may be set during transform to mark it as containing an
146 // await operation.
147
148
149 // For assignments, the index of the RHS. For export tokens, the end of the export.
150
151 // For class tokens, records if the class is a class expression or a class statement.
152
153 // Number of times to insert a `nullishCoalesce(` snippet before this token.
154
155 // Number of times to insert a `)` snippet after this token.
156
157 // If true, insert an `optionalChain([` snippet before this token.
158
159 // If true, insert a `])` snippet after this token.
160
161 // Tag for `.`, `?.`, `[`, `?.[`, `(`, and `?.(` to denote the "root" token for this
162 // subscript chain. This can be used to determine if this chain is an optional chain.
163
164 // Tag for `??` operators to denote the root token for this nullish coalescing call.
165
166}
167
168// ## Tokenizer
169
170// Move to the next token
171export function next() {
172 state.tokens.push(new Token());
173 nextToken();
174}
175
176// Call instead of next when inside a template, since that needs to be handled differently.
177export function nextTemplateToken() {
178 state.tokens.push(new Token());
179 state.start = state.pos;
180 readTmplToken();
181}
182
183// The tokenizer never parses regexes by default. Instead, the parser is responsible for
184// instructing it to parse a regex when we see a slash at the start of an expression.
185export function retokenizeSlashAsRegex() {
186 if (state.type === tt.assign) {
187 --state.pos;
188 }
189 readRegexp();
190}
191
192export function pushTypeContext(existingTokensInType) {
193 for (let i = state.tokens.length - existingTokensInType; i < state.tokens.length; i++) {
194 state.tokens[i].isType = true;
195 }
196 const oldIsType = state.isType;
197 state.isType = true;
198 return oldIsType;
199}
200
201export function popTypeContext(oldIsType) {
202 state.isType = oldIsType;
203}
204
205export function eat(type) {
206 if (match(type)) {
207 next();
208 return true;
209 } else {
210 return false;
211 }
212}
213
214export function eatTypeToken(tokenType) {
215 const oldIsType = state.isType;
216 state.isType = true;
217 eat(tokenType);
218 state.isType = oldIsType;
219}
220
221export function match(type) {
222 return state.type === type;
223}
224
225export function lookaheadType() {
226 const snapshot = state.snapshot();
227 next();
228 const type = state.type;
229 state.restoreFromSnapshot(snapshot);
230 return type;
231}
232
233export class TypeAndKeyword {
234
235
236 constructor(type, contextualKeyword) {
237 this.type = type;
238 this.contextualKeyword = contextualKeyword;
239 }
240}
241
242export function lookaheadTypeAndKeyword() {
243 const snapshot = state.snapshot();
244 next();
245 const type = state.type;
246 const contextualKeyword = state.contextualKeyword;
247 state.restoreFromSnapshot(snapshot);
248 return new TypeAndKeyword(type, contextualKeyword);
249}
250
251export function nextTokenStart() {
252 return nextTokenStartSince(state.pos);
253}
254
255export function nextTokenStartSince(pos) {
256 skipWhiteSpace.lastIndex = pos;
257 const skip = skipWhiteSpace.exec(input);
258 return pos + skip[0].length;
259}
260
261export function lookaheadCharCode() {
262 return input.charCodeAt(nextTokenStart());
263}
264
265// Read a single token, updating the parser object's token-related
266// properties.
267export function nextToken() {
268 skipSpace();
269 state.start = state.pos;
270 if (state.pos >= input.length) {
271 const tokens = state.tokens;
272 // We normally run past the end a bit, but if we're way past the end, avoid an infinite loop.
273 // Also check the token positions rather than the types since sometimes we rewrite the token
274 // type to something else.
275 if (
276 tokens.length >= 2 &&
277 tokens[tokens.length - 1].start >= input.length &&
278 tokens[tokens.length - 2].start >= input.length
279 ) {
280 unexpected("Unexpectedly reached the end of input.");
281 }
282 finishToken(tt.eof);
283 return;
284 }
285 readToken(input.charCodeAt(state.pos));
286}
287
288function readToken(code) {
289 // Identifier or keyword. '\uXXXX' sequences are allowed in
290 // identifiers, so '\' also dispatches to that.
291 if (
292 IS_IDENTIFIER_START[code] ||
293 code === charCodes.backslash ||
294 (code === charCodes.atSign && input.charCodeAt(state.pos + 1) === charCodes.atSign)
295 ) {
296 readWord();
297 } else {
298 getTokenFromCode(code);
299 }
300}
301
302function skipBlockComment() {
303 while (
304 input.charCodeAt(state.pos) !== charCodes.asterisk ||
305 input.charCodeAt(state.pos + 1) !== charCodes.slash
306 ) {
307 state.pos++;
308 if (state.pos > input.length) {
309 unexpected("Unterminated comment", state.pos - 2);
310 return;
311 }
312 }
313 state.pos += 2;
314}
315
316export function skipLineComment(startSkip) {
317 let ch = input.charCodeAt((state.pos += startSkip));
318 if (state.pos < input.length) {
319 while (
320 ch !== charCodes.lineFeed &&
321 ch !== charCodes.carriageReturn &&
322 ch !== charCodes.lineSeparator &&
323 ch !== charCodes.paragraphSeparator &&
324 ++state.pos < input.length
325 ) {
326 ch = input.charCodeAt(state.pos);
327 }
328 }
329}
330
331// Called at the start of the parse and after every token. Skips
332// whitespace and comments.
333export function skipSpace() {
334 while (state.pos < input.length) {
335 const ch = input.charCodeAt(state.pos);
336 switch (ch) {
337 case charCodes.carriageReturn:
338 if (input.charCodeAt(state.pos + 1) === charCodes.lineFeed) {
339 ++state.pos;
340 }
341
342 case charCodes.lineFeed:
343 case charCodes.lineSeparator:
344 case charCodes.paragraphSeparator:
345 ++state.pos;
346 break;
347
348 case charCodes.slash:
349 switch (input.charCodeAt(state.pos + 1)) {
350 case charCodes.asterisk:
351 state.pos += 2;
352 skipBlockComment();
353 break;
354
355 case charCodes.slash:
356 skipLineComment(2);
357 break;
358
359 default:
360 return;
361 }
362 break;
363
364 default:
365 if (IS_WHITESPACE[ch]) {
366 ++state.pos;
367 } else {
368 return;
369 }
370 }
371 }
372}
373
374// Called at the end of every token. Sets various fields, and skips the space after the token, so
375// that the next one's `start` will point at the right position.
376export function finishToken(
377 type,
378 contextualKeyword = ContextualKeyword.NONE,
379) {
380 state.end = state.pos;
381 state.type = type;
382 state.contextualKeyword = contextualKeyword;
383}
384
385// ### Token reading
386
387// This is the function that is called to fetch the next token. It
388// is somewhat obscure, because it works in character codes rather
389// than characters, and because operator parsing has been inlined
390// into it.
391//
392// All in the name of speed.
393function readToken_dot() {
394 const nextChar = input.charCodeAt(state.pos + 1);
395 if (nextChar >= charCodes.digit0 && nextChar <= charCodes.digit9) {
396 readNumber(true);
397 return;
398 }
399
400 if (nextChar === charCodes.dot && input.charCodeAt(state.pos + 2) === charCodes.dot) {
401 state.pos += 3;
402 finishToken(tt.ellipsis);
403 } else {
404 ++state.pos;
405 finishToken(tt.dot);
406 }
407}
408
409function readToken_slash() {
410 const nextChar = input.charCodeAt(state.pos + 1);
411 if (nextChar === charCodes.equalsTo) {
412 finishOp(tt.assign, 2);
413 } else {
414 finishOp(tt.slash, 1);
415 }
416}
417
418function readToken_mult_modulo(code) {
419 // '%*'
420 let tokenType = code === charCodes.asterisk ? tt.star : tt.modulo;
421 let width = 1;
422 let nextChar = input.charCodeAt(state.pos + 1);
423
424 // Exponentiation operator **
425 if (code === charCodes.asterisk && nextChar === charCodes.asterisk) {
426 width++;
427 nextChar = input.charCodeAt(state.pos + 2);
428 tokenType = tt.exponent;
429 }
430
431 // Match *= or %=, disallowing *=> which can be valid in flow.
432 if (
433 nextChar === charCodes.equalsTo &&
434 input.charCodeAt(state.pos + 2) !== charCodes.greaterThan
435 ) {
436 width++;
437 tokenType = tt.assign;
438 }
439
440 finishOp(tokenType, width);
441}
442
443function readToken_pipe_amp(code) {
444 // '|&'
445 const nextChar = input.charCodeAt(state.pos + 1);
446
447 if (nextChar === code) {
448 if (input.charCodeAt(state.pos + 2) === charCodes.equalsTo) {
449 // ||= or &&=
450 finishOp(tt.assign, 3);
451 } else {
452 // || or &&
453 finishOp(code === charCodes.verticalBar ? tt.logicalOR : tt.logicalAND, 2);
454 }
455 return;
456 }
457
458 if (code === charCodes.verticalBar) {
459 // '|>'
460 if (nextChar === charCodes.greaterThan) {
461 finishOp(tt.pipeline, 2);
462 return;
463 } else if (nextChar === charCodes.rightCurlyBrace && isFlowEnabled) {
464 // '|}'
465 finishOp(tt.braceBarR, 2);
466 return;
467 }
468 }
469
470 if (nextChar === charCodes.equalsTo) {
471 finishOp(tt.assign, 2);
472 return;
473 }
474
475 finishOp(code === charCodes.verticalBar ? tt.bitwiseOR : tt.bitwiseAND, 1);
476}
477
478function readToken_caret() {
479 // '^'
480 const nextChar = input.charCodeAt(state.pos + 1);
481 if (nextChar === charCodes.equalsTo) {
482 finishOp(tt.assign, 2);
483 } else {
484 finishOp(tt.bitwiseXOR, 1);
485 }
486}
487
488function readToken_plus_min(code) {
489 // '+-'
490 const nextChar = input.charCodeAt(state.pos + 1);
491
492 if (nextChar === code) {
493 // Tentatively call this a prefix operator, but it might be changed to postfix later.
494 finishOp(tt.preIncDec, 2);
495 return;
496 }
497
498 if (nextChar === charCodes.equalsTo) {
499 finishOp(tt.assign, 2);
500 } else if (code === charCodes.plusSign) {
501 finishOp(tt.plus, 1);
502 } else {
503 finishOp(tt.minus, 1);
504 }
505}
506
507function readToken_lt() {
508 const nextChar = input.charCodeAt(state.pos + 1);
509
510 if (nextChar === charCodes.lessThan) {
511 if (input.charCodeAt(state.pos + 2) === charCodes.equalsTo) {
512 finishOp(tt.assign, 3);
513 return;
514 }
515 // We see <<, but need to be really careful about whether to treat it as a
516 // true left-shift or as two < tokens.
517 if (state.isType) {
518 // Within a type, << might come up in a snippet like `Array<<T>() => void>`,
519 // so treat it as two < tokens. Importantly, this should only override <<
520 // rather than other tokens like <= . If we treated <= as < in a type
521 // context, then the snippet `a as T <= 1` would incorrectly start parsing
522 // a type argument on T. We don't need to worry about `a as T << 1`
523 // because TypeScript disallows that syntax.
524 finishOp(tt.lessThan, 1);
525 } else {
526 // Outside a type, this might be a true left-shift operator, or it might
527 // still be two open-type-arg tokens, such as in `f<<T>() => void>()`. We
528 // look at the token while considering the `f`, so we don't yet know that
529 // we're in a type context. In this case, we initially tokenize as a
530 // left-shift and correct after-the-fact as necessary in
531 // tsParseTypeArgumentsWithPossibleBitshift .
532 finishOp(tt.bitShiftL, 2);
533 }
534 return;
535 }
536
537 if (nextChar === charCodes.equalsTo) {
538 // <=
539 finishOp(tt.relationalOrEqual, 2);
540 } else {
541 finishOp(tt.lessThan, 1);
542 }
543}
544
545function readToken_gt() {
546 if (state.isType) {
547 // Avoid right-shift for things like `Array<Array<string>>` and
548 // greater-than-or-equal for things like `const a: Array<number>=[];`.
549 finishOp(tt.greaterThan, 1);
550 return;
551 }
552
553 const nextChar = input.charCodeAt(state.pos + 1);
554
555 if (nextChar === charCodes.greaterThan) {
556 const size = input.charCodeAt(state.pos + 2) === charCodes.greaterThan ? 3 : 2;
557 if (input.charCodeAt(state.pos + size) === charCodes.equalsTo) {
558 finishOp(tt.assign, size + 1);
559 return;
560 }
561 finishOp(tt.bitShiftR, size);
562 return;
563 }
564
565 if (nextChar === charCodes.equalsTo) {
566 // >=
567 finishOp(tt.relationalOrEqual, 2);
568 } else {
569 finishOp(tt.greaterThan, 1);
570 }
571}
572
573/**
574 * Reinterpret a possible > token when transitioning from a type to a non-type
575 * context.
576 *
577 * This comes up in two situations where >= needs to be treated as one token:
578 * - After an `as` expression, like in the code `a as T >= 1`.
579 * - In a type argument in an expression context, e.g. `f(a < b, c >= d)`, we
580 * need to see the token as >= so that we get an error and backtrack to
581 * normal expression parsing.
582 *
583 * Other situations require >= to be seen as two tokens, e.g.
584 * `const x: Array<T>=[];`, so it's important to treat > as its own token in
585 * typical type parsing situations.
586 */
587export function rescan_gt() {
588 if (state.type === tt.greaterThan) {
589 state.pos -= 1;
590 readToken_gt();
591 }
592}
593
594function readToken_eq_excl(code) {
595 // '=!'
596 const nextChar = input.charCodeAt(state.pos + 1);
597 if (nextChar === charCodes.equalsTo) {
598 finishOp(tt.equality, input.charCodeAt(state.pos + 2) === charCodes.equalsTo ? 3 : 2);
599 return;
600 }
601 if (code === charCodes.equalsTo && nextChar === charCodes.greaterThan) {
602 // '=>'
603 state.pos += 2;
604 finishToken(tt.arrow);
605 return;
606 }
607 finishOp(code === charCodes.equalsTo ? tt.eq : tt.bang, 1);
608}
609
610function readToken_question() {
611 // '?'
612 const nextChar = input.charCodeAt(state.pos + 1);
613 const nextChar2 = input.charCodeAt(state.pos + 2);
614 if (
615 nextChar === charCodes.questionMark &&
616 // In Flow (but not TypeScript), ??string is a valid type that should be
617 // tokenized as two individual ? tokens.
618 !(isFlowEnabled && state.isType)
619 ) {
620 if (nextChar2 === charCodes.equalsTo) {
621 // '??='
622 finishOp(tt.assign, 3);
623 } else {
624 // '??'
625 finishOp(tt.nullishCoalescing, 2);
626 }
627 } else if (
628 nextChar === charCodes.dot &&
629 !(nextChar2 >= charCodes.digit0 && nextChar2 <= charCodes.digit9)
630 ) {
631 // '.' not followed by a number
632 state.pos += 2;
633 finishToken(tt.questionDot);
634 } else {
635 ++state.pos;
636 finishToken(tt.question);
637 }
638}
639
640export function getTokenFromCode(code) {
641 switch (code) {
642 case charCodes.numberSign:
643 ++state.pos;
644 finishToken(tt.hash);
645 return;
646
647 // The interpretation of a dot depends on whether it is followed
648 // by a digit or another two dots.
649
650 case charCodes.dot:
651 readToken_dot();
652 return;
653
654 // Punctuation tokens.
655 case charCodes.leftParenthesis:
656 ++state.pos;
657 finishToken(tt.parenL);
658 return;
659 case charCodes.rightParenthesis:
660 ++state.pos;
661 finishToken(tt.parenR);
662 return;
663 case charCodes.semicolon:
664 ++state.pos;
665 finishToken(tt.semi);
666 return;
667 case charCodes.comma:
668 ++state.pos;
669 finishToken(tt.comma);
670 return;
671 case charCodes.leftSquareBracket:
672 ++state.pos;
673 finishToken(tt.bracketL);
674 return;
675 case charCodes.rightSquareBracket:
676 ++state.pos;
677 finishToken(tt.bracketR);
678 return;
679
680 case charCodes.leftCurlyBrace:
681 if (isFlowEnabled && input.charCodeAt(state.pos + 1) === charCodes.verticalBar) {
682 finishOp(tt.braceBarL, 2);
683 } else {
684 ++state.pos;
685 finishToken(tt.braceL);
686 }
687 return;
688
689 case charCodes.rightCurlyBrace:
690 ++state.pos;
691 finishToken(tt.braceR);
692 return;
693
694 case charCodes.colon:
695 if (input.charCodeAt(state.pos + 1) === charCodes.colon) {
696 finishOp(tt.doubleColon, 2);
697 } else {
698 ++state.pos;
699 finishToken(tt.colon);
700 }
701 return;
702
703 case charCodes.questionMark:
704 readToken_question();
705 return;
706 case charCodes.atSign:
707 ++state.pos;
708 finishToken(tt.at);
709 return;
710
711 case charCodes.graveAccent:
712 ++state.pos;
713 finishToken(tt.backQuote);
714 return;
715
716 case charCodes.digit0: {
717 const nextChar = input.charCodeAt(state.pos + 1);
718 // '0x', '0X', '0o', '0O', '0b', '0B'
719 if (
720 nextChar === charCodes.lowercaseX ||
721 nextChar === charCodes.uppercaseX ||
722 nextChar === charCodes.lowercaseO ||
723 nextChar === charCodes.uppercaseO ||
724 nextChar === charCodes.lowercaseB ||
725 nextChar === charCodes.uppercaseB
726 ) {
727 readRadixNumber();
728 return;
729 }
730 }
731 // Anything else beginning with a digit is an integer, octal
732 // number, or float.
733 case charCodes.digit1:
734 case charCodes.digit2:
735 case charCodes.digit3:
736 case charCodes.digit4:
737 case charCodes.digit5:
738 case charCodes.digit6:
739 case charCodes.digit7:
740 case charCodes.digit8:
741 case charCodes.digit9:
742 readNumber(false);
743 return;
744
745 // Quotes produce strings.
746 case charCodes.quotationMark:
747 case charCodes.apostrophe:
748 readString(code);
749 return;
750
751 // Operators are parsed inline in tiny state machines. '=' (charCodes.equalsTo) is
752 // often referred to. `finishOp` simply skips the amount of
753 // characters it is given as second argument, and returns a token
754 // of the type given by its first argument.
755
756 case charCodes.slash:
757 readToken_slash();
758 return;
759
760 case charCodes.percentSign:
761 case charCodes.asterisk:
762 readToken_mult_modulo(code);
763 return;
764
765 case charCodes.verticalBar:
766 case charCodes.ampersand:
767 readToken_pipe_amp(code);
768 return;
769
770 case charCodes.caret:
771 readToken_caret();
772 return;
773
774 case charCodes.plusSign:
775 case charCodes.dash:
776 readToken_plus_min(code);
777 return;
778
779 case charCodes.lessThan:
780 readToken_lt();
781 return;
782
783 case charCodes.greaterThan:
784 readToken_gt();
785 return;
786
787 case charCodes.equalsTo:
788 case charCodes.exclamationMark:
789 readToken_eq_excl(code);
790 return;
791
792 case charCodes.tilde:
793 finishOp(tt.tilde, 1);
794 return;
795
796 default:
797 break;
798 }
799
800 unexpected(`Unexpected character '${String.fromCharCode(code)}'`, state.pos);
801}
802
803function finishOp(type, size) {
804 state.pos += size;
805 finishToken(type);
806}
807
808function readRegexp() {
809 const start = state.pos;
810 let escaped = false;
811 let inClass = false;
812 for (;;) {
813 if (state.pos >= input.length) {
814 unexpected("Unterminated regular expression", start);
815 return;
816 }
817 const code = input.charCodeAt(state.pos);
818 if (escaped) {
819 escaped = false;
820 } else {
821 if (code === charCodes.leftSquareBracket) {
822 inClass = true;
823 } else if (code === charCodes.rightSquareBracket && inClass) {
824 inClass = false;
825 } else if (code === charCodes.slash && !inClass) {
826 break;
827 }
828 escaped = code === charCodes.backslash;
829 }
830 ++state.pos;
831 }
832 ++state.pos;
833 // Need to use `skipWord` because '\uXXXX' sequences are allowed here (don't ask).
834 skipWord();
835
836 finishToken(tt.regexp);
837}
838
839/**
840 * Read a decimal integer. Note that this can't be unified with the similar code
841 * in readRadixNumber (which also handles hex digits) because "e" needs to be
842 * the end of the integer so that we can properly handle scientific notation.
843 */
844function readInt() {
845 while (true) {
846 const code = input.charCodeAt(state.pos);
847 if ((code >= charCodes.digit0 && code <= charCodes.digit9) || code === charCodes.underscore) {
848 state.pos++;
849 } else {
850 break;
851 }
852 }
853}
854
855function readRadixNumber() {
856 state.pos += 2; // 0x
857
858 // Walk to the end of the number, allowing hex digits.
859 while (true) {
860 const code = input.charCodeAt(state.pos);
861 if (
862 (code >= charCodes.digit0 && code <= charCodes.digit9) ||
863 (code >= charCodes.lowercaseA && code <= charCodes.lowercaseF) ||
864 (code >= charCodes.uppercaseA && code <= charCodes.uppercaseF) ||
865 code === charCodes.underscore
866 ) {
867 state.pos++;
868 } else {
869 break;
870 }
871 }
872
873 const nextChar = input.charCodeAt(state.pos);
874 if (nextChar === charCodes.lowercaseN) {
875 ++state.pos;
876 finishToken(tt.bigint);
877 } else {
878 finishToken(tt.num);
879 }
880}
881
882// Read an integer, octal integer, or floating-point number.
883function readNumber(startsWithDot) {
884 let isBigInt = false;
885 let isDecimal = false;
886
887 if (!startsWithDot) {
888 readInt();
889 }
890
891 let nextChar = input.charCodeAt(state.pos);
892 if (nextChar === charCodes.dot) {
893 ++state.pos;
894 readInt();
895 nextChar = input.charCodeAt(state.pos);
896 }
897
898 if (nextChar === charCodes.uppercaseE || nextChar === charCodes.lowercaseE) {
899 nextChar = input.charCodeAt(++state.pos);
900 if (nextChar === charCodes.plusSign || nextChar === charCodes.dash) {
901 ++state.pos;
902 }
903 readInt();
904 nextChar = input.charCodeAt(state.pos);
905 }
906
907 if (nextChar === charCodes.lowercaseN) {
908 ++state.pos;
909 isBigInt = true;
910 } else if (nextChar === charCodes.lowercaseM) {
911 ++state.pos;
912 isDecimal = true;
913 }
914
915 if (isBigInt) {
916 finishToken(tt.bigint);
917 return;
918 }
919
920 if (isDecimal) {
921 finishToken(tt.decimal);
922 return;
923 }
924
925 finishToken(tt.num);
926}
927
928function readString(quote) {
929 state.pos++;
930 for (;;) {
931 if (state.pos >= input.length) {
932 unexpected("Unterminated string constant");
933 return;
934 }
935 const ch = input.charCodeAt(state.pos);
936 if (ch === charCodes.backslash) {
937 state.pos++;
938 } else if (ch === quote) {
939 break;
940 }
941 state.pos++;
942 }
943 state.pos++;
944 finishToken(tt.string);
945}
946
947// Reads template string tokens.
948function readTmplToken() {
949 for (;;) {
950 if (state.pos >= input.length) {
951 unexpected("Unterminated template");
952 return;
953 }
954 const ch = input.charCodeAt(state.pos);
955 if (
956 ch === charCodes.graveAccent ||
957 (ch === charCodes.dollarSign && input.charCodeAt(state.pos + 1) === charCodes.leftCurlyBrace)
958 ) {
959 if (state.pos === state.start && match(tt.template)) {
960 if (ch === charCodes.dollarSign) {
961 state.pos += 2;
962 finishToken(tt.dollarBraceL);
963 return;
964 } else {
965 ++state.pos;
966 finishToken(tt.backQuote);
967 return;
968 }
969 }
970 finishToken(tt.template);
971 return;
972 }
973 if (ch === charCodes.backslash) {
974 state.pos++;
975 }
976 state.pos++;
977 }
978}
979
980// Skip to the end of the current word. Note that this is the same as the snippet at the end of
981// readWord, but calling skipWord from readWord seems to slightly hurt performance from some rough
982// measurements.
983export function skipWord() {
984 while (state.pos < input.length) {
985 const ch = input.charCodeAt(state.pos);
986 if (IS_IDENTIFIER_CHAR[ch]) {
987 state.pos++;
988 } else if (ch === charCodes.backslash) {
989 // \u
990 state.pos += 2;
991 if (input.charCodeAt(state.pos) === charCodes.leftCurlyBrace) {
992 while (
993 state.pos < input.length &&
994 input.charCodeAt(state.pos) !== charCodes.rightCurlyBrace
995 ) {
996 state.pos++;
997 }
998 state.pos++;
999 }
1000 } else {
1001 break;
1002 }
1003 }
1004}
Note: See TracBrowser for help on using the repository browser.