UNPKG

22.9 kBJavaScriptView Raw
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} 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 const ObjectKey = ObjectShorthand + 1; IdentifierRole[IdentifierRole["ObjectKey"] = ObjectKey] = "ObjectKey";
23})(IdentifierRole || (IdentifierRole = {}));
24
25export function isDeclaration(token) {
26 const role = token.identifierRole;
27 return (
28 role === IdentifierRole.TopLevelDeclaration ||
29 role === IdentifierRole.FunctionScopedDeclaration ||
30 role === IdentifierRole.BlockScopedDeclaration ||
31 role === IdentifierRole.ObjectShorthandTopLevelDeclaration ||
32 role === IdentifierRole.ObjectShorthandFunctionScopedDeclaration ||
33 role === IdentifierRole.ObjectShorthandBlockScopedDeclaration
34 );
35}
36
37export function isNonTopLevelDeclaration(token) {
38 const role = token.identifierRole;
39 return (
40 role === IdentifierRole.FunctionScopedDeclaration ||
41 role === IdentifierRole.BlockScopedDeclaration ||
42 role === IdentifierRole.ObjectShorthandFunctionScopedDeclaration ||
43 role === IdentifierRole.ObjectShorthandBlockScopedDeclaration
44 );
45}
46
47export function isBlockScopedDeclaration(token) {
48 const role = token.identifierRole;
49 // Treat top-level declarations as block scope since the distinction doesn't matter here.
50 return (
51 role === IdentifierRole.TopLevelDeclaration ||
52 role === IdentifierRole.BlockScopedDeclaration ||
53 role === IdentifierRole.ObjectShorthandTopLevelDeclaration ||
54 role === IdentifierRole.ObjectShorthandBlockScopedDeclaration
55 );
56}
57
58export function isFunctionScopedDeclaration(token) {
59 const role = token.identifierRole;
60 return (
61 role === IdentifierRole.FunctionScopedDeclaration ||
62 role === IdentifierRole.ObjectShorthandFunctionScopedDeclaration
63 );
64}
65
66export function isObjectShorthandDeclaration(token) {
67 return (
68 token.identifierRole === IdentifierRole.ObjectShorthandTopLevelDeclaration ||
69 token.identifierRole === IdentifierRole.ObjectShorthandBlockScopedDeclaration ||
70 token.identifierRole === IdentifierRole.ObjectShorthandFunctionScopedDeclaration
71 );
72}
73
74// Object type used to represent tokens. Note that normally, tokens
75// simply exist as properties on the parser object. This is only
76// used for the onToken callback and the external tokenizer.
77export class Token {
78 constructor() {
79 this.type = state.type;
80 this.contextualKeyword = state.contextualKeyword;
81 this.start = state.start;
82 this.end = state.end;
83 this.isType = state.isType;
84 this.identifierRole = null;
85 this.shadowsGlobal = false;
86 this.contextId = null;
87 this.rhsEndIndex = null;
88 this.isExpression = false;
89 }
90
91
92
93
94
95
96
97 // Initially false for all tokens, then may be computed in a follow-up step that does scope
98 // analysis.
99
100
101
102 // For class tokens, records if the class is a class expression or a class statement.
103
104}
105
106// ## Tokenizer
107
108// Move to the next token
109export function next() {
110 state.tokens.push(new Token());
111 nextToken();
112}
113
114// Call instead of next when inside a template, since that needs to be handled differently.
115export function nextTemplateToken() {
116 state.tokens.push(new Token());
117 state.start = state.pos;
118 readTmplToken();
119}
120
121// The tokenizer never parses regexes by default. Instead, the parser is responsible for
122// instructing it to parse a regex when we see a slash at the start of an expression.
123export function retokenizeSlashAsRegex() {
124 if (state.type === tt.assign) {
125 --state.pos;
126 }
127 readRegexp();
128}
129
130export function pushTypeContext(existingTokensInType) {
131 for (let i = state.tokens.length - existingTokensInType; i < state.tokens.length; i++) {
132 state.tokens[i].isType = true;
133 }
134 const oldIsType = state.isType;
135 state.isType = true;
136 return oldIsType;
137}
138
139export function popTypeContext(oldIsType) {
140 state.isType = oldIsType;
141}
142
143export function eat(type) {
144 if (match(type)) {
145 next();
146 return true;
147 } else {
148 return false;
149 }
150}
151
152export function match(type) {
153 return state.type === type;
154}
155
156export function lookaheadType() {
157 const snapshot = state.snapshot();
158 next();
159 const type = state.type;
160 state.restoreFromSnapshot(snapshot);
161 return type;
162}
163
164export class TypeAndKeyword {
165
166
167 constructor(type, contextualKeyword) {
168 this.type = type;
169 this.contextualKeyword = contextualKeyword;
170 }
171}
172
173export function lookaheadTypeAndKeyword() {
174 const snapshot = state.snapshot();
175 next();
176 const type = state.type;
177 const contextualKeyword = state.contextualKeyword;
178 state.restoreFromSnapshot(snapshot);
179 return new TypeAndKeyword(type, contextualKeyword);
180}
181
182// Read a single token, updating the parser object's token-related
183// properties.
184export function nextToken() {
185 skipSpace();
186 state.start = state.pos;
187 if (state.pos >= input.length) {
188 const tokens = state.tokens;
189 // We normally run past the end a bit, but if we're way past the end, avoid an infinite loop.
190 // Also check the token positions rather than the types since sometimes we rewrite the token
191 // type to something else.
192 if (
193 tokens.length >= 2 &&
194 tokens[tokens.length - 1].start >= input.length &&
195 tokens[tokens.length - 2].start >= input.length
196 ) {
197 unexpected("Unexpectedly reached the end of input.");
198 }
199 finishToken(tt.eof);
200 return;
201 }
202 readToken(input.charCodeAt(state.pos));
203}
204
205function readToken(code) {
206 // Identifier or keyword. '\uXXXX' sequences are allowed in
207 // identifiers, so '\' also dispatches to that.
208 if (
209 IS_IDENTIFIER_START[code] ||
210 code === charCodes.backslash ||
211 (code === charCodes.atSign && input.charCodeAt(state.pos + 1) === charCodes.atSign)
212 ) {
213 readWord();
214 } else {
215 getTokenFromCode(code);
216 }
217}
218
219function skipBlockComment() {
220 while (
221 input.charCodeAt(state.pos) !== charCodes.asterisk ||
222 input.charCodeAt(state.pos + 1) !== charCodes.slash
223 ) {
224 state.pos++;
225 if (state.pos > input.length) {
226 unexpected("Unterminated comment", state.pos - 2);
227 return;
228 }
229 }
230 state.pos += 2;
231}
232
233export function skipLineComment(startSkip) {
234 let ch = input.charCodeAt((state.pos += startSkip));
235 if (state.pos < input.length) {
236 while (
237 ch !== charCodes.lineFeed &&
238 ch !== charCodes.carriageReturn &&
239 ch !== charCodes.lineSeparator &&
240 ch !== charCodes.paragraphSeparator &&
241 ++state.pos < input.length
242 ) {
243 ch = input.charCodeAt(state.pos);
244 }
245 }
246}
247
248// Called at the start of the parse and after every token. Skips
249// whitespace and comments.
250export function skipSpace() {
251 while (state.pos < input.length) {
252 const ch = input.charCodeAt(state.pos);
253 switch (ch) {
254 case charCodes.carriageReturn:
255 if (input.charCodeAt(state.pos + 1) === charCodes.lineFeed) {
256 ++state.pos;
257 }
258
259 case charCodes.lineFeed:
260 case charCodes.lineSeparator:
261 case charCodes.paragraphSeparator:
262 ++state.pos;
263 break;
264
265 case charCodes.slash:
266 switch (input.charCodeAt(state.pos + 1)) {
267 case charCodes.asterisk:
268 skipBlockComment();
269 break;
270
271 case charCodes.slash:
272 skipLineComment(2);
273 break;
274
275 default:
276 return;
277 }
278 break;
279
280 default:
281 if (IS_WHITESPACE[ch]) {
282 ++state.pos;
283 } else {
284 return;
285 }
286 }
287 }
288}
289
290// Called at the end of every token. Sets various fields, and skips the space after the token, so
291// that the next one's `start` will point at the right position.
292export function finishToken(
293 type,
294 contextualKeyword = ContextualKeyword.NONE,
295) {
296 state.end = state.pos;
297 state.type = type;
298 state.contextualKeyword = contextualKeyword;
299}
300
301// ### Token reading
302
303// This is the function that is called to fetch the next token. It
304// is somewhat obscure, because it works in character codes rather
305// than characters, and because operator parsing has been inlined
306// into it.
307//
308// All in the name of speed.
309function readToken_dot() {
310 const nextChar = input.charCodeAt(state.pos + 1);
311 if (nextChar >= charCodes.digit0 && nextChar <= charCodes.digit9) {
312 readNumber(true);
313 return;
314 }
315
316 const next2 = input.charCodeAt(state.pos + 2);
317 if (nextChar === charCodes.dot && next2 === charCodes.dot) {
318 state.pos += 3;
319 finishToken(tt.ellipsis);
320 } else {
321 ++state.pos;
322 finishToken(tt.dot);
323 }
324}
325
326function readToken_slash() {
327 const nextChar = input.charCodeAt(state.pos + 1);
328 if (nextChar === charCodes.equalsTo) {
329 finishOp(tt.assign, 2);
330 } else {
331 finishOp(tt.slash, 1);
332 }
333}
334
335function readToken_mult_modulo(code) {
336 // '%*'
337 let tokenType = code === charCodes.asterisk ? tt.star : tt.modulo;
338 let width = 1;
339 let nextChar = input.charCodeAt(state.pos + 1);
340
341 // Exponentiation operator **
342 if (code === charCodes.asterisk && nextChar === charCodes.asterisk) {
343 width++;
344 nextChar = input.charCodeAt(state.pos + 2);
345 tokenType = tt.exponent;
346 }
347
348 // Match *= or %=, disallowing *=> which can be valid in flow.
349 if (
350 nextChar === charCodes.equalsTo &&
351 input.charCodeAt(state.pos + 2) !== charCodes.greaterThan
352 ) {
353 width++;
354 tokenType = tt.assign;
355 }
356
357 finishOp(tokenType, width);
358}
359
360function readToken_pipe_amp(code) {
361 // '|&'
362 const nextChar = input.charCodeAt(state.pos + 1);
363
364 if (nextChar === code) {
365 if (input.charCodeAt(state.pos + 2) === charCodes.equalsTo) {
366 // ||= or &&=
367 finishOp(tt.assign, 3);
368 } else {
369 // || or &&
370 finishOp(code === charCodes.verticalBar ? tt.logicalOR : tt.logicalAND, 2);
371 }
372 return;
373 }
374
375 if (code === charCodes.verticalBar) {
376 // '|>'
377 if (nextChar === charCodes.greaterThan) {
378 finishOp(tt.pipeline, 2);
379 return;
380 } else if (nextChar === charCodes.rightCurlyBrace && isFlowEnabled) {
381 // '|}'
382 finishOp(tt.braceBarR, 2);
383 return;
384 }
385 }
386
387 if (nextChar === charCodes.equalsTo) {
388 finishOp(tt.assign, 2);
389 return;
390 }
391
392 finishOp(code === charCodes.verticalBar ? tt.bitwiseOR : tt.bitwiseAND, 1);
393}
394
395function readToken_caret() {
396 // '^'
397 const nextChar = input.charCodeAt(state.pos + 1);
398 if (nextChar === charCodes.equalsTo) {
399 finishOp(tt.assign, 2);
400 } else {
401 finishOp(tt.bitwiseXOR, 1);
402 }
403}
404
405function readToken_plus_min(code) {
406 // '+-'
407 const nextChar = input.charCodeAt(state.pos + 1);
408
409 if (nextChar === code) {
410 // Tentatively call this a prefix operator, but it might be changed to postfix later.
411 finishOp(tt.preIncDec, 2);
412 return;
413 }
414
415 if (nextChar === charCodes.equalsTo) {
416 finishOp(tt.assign, 2);
417 } else if (code === charCodes.plusSign) {
418 finishOp(tt.plus, 1);
419 } else {
420 finishOp(tt.minus, 1);
421 }
422}
423
424// '<>'
425function readToken_lt_gt(code) {
426 // Avoid right-shift for things like Array<Array<string>>.
427 if (code === charCodes.greaterThan && state.isType) {
428 finishOp(tt.greaterThan, 1);
429 return;
430 }
431 const nextChar = input.charCodeAt(state.pos + 1);
432
433 if (nextChar === code) {
434 const size =
435 code === charCodes.greaterThan && input.charCodeAt(state.pos + 2) === charCodes.greaterThan
436 ? 3
437 : 2;
438 if (input.charCodeAt(state.pos + size) === charCodes.equalsTo) {
439 finishOp(tt.assign, size + 1);
440 return;
441 }
442 finishOp(tt.bitShift, size);
443 return;
444 }
445
446 if (nextChar === charCodes.equalsTo) {
447 // <= | >=
448 finishOp(tt.relationalOrEqual, 2);
449 } else if (code === charCodes.lessThan) {
450 finishOp(tt.lessThan, 1);
451 } else {
452 finishOp(tt.greaterThan, 1);
453 }
454}
455
456function readToken_eq_excl(code) {
457 // '=!'
458 const nextChar = input.charCodeAt(state.pos + 1);
459 if (nextChar === charCodes.equalsTo) {
460 finishOp(tt.equality, input.charCodeAt(state.pos + 2) === charCodes.equalsTo ? 3 : 2);
461 return;
462 }
463 if (code === charCodes.equalsTo && nextChar === charCodes.greaterThan) {
464 // '=>'
465 state.pos += 2;
466 finishToken(tt.arrow);
467 return;
468 }
469 finishOp(code === charCodes.equalsTo ? tt.eq : tt.bang, 1);
470}
471
472function readToken_question() {
473 // '?'
474 const nextChar = input.charCodeAt(state.pos + 1);
475 const nextChar2 = input.charCodeAt(state.pos + 2);
476 if (nextChar === charCodes.questionMark && !state.isType) {
477 if (nextChar2 === charCodes.equalsTo) {
478 // '??='
479 finishOp(tt.assign, 3);
480 } else {
481 // '??'
482 finishOp(tt.nullishCoalescing, 2);
483 }
484 } else if (
485 nextChar === charCodes.dot &&
486 !(nextChar2 >= charCodes.digit0 && nextChar2 <= charCodes.digit9)
487 ) {
488 // '.' not followed by a number
489 state.pos += 2;
490 finishToken(tt.questionDot);
491 } else {
492 ++state.pos;
493 finishToken(tt.question);
494 }
495}
496
497export function getTokenFromCode(code) {
498 switch (code) {
499 case charCodes.numberSign:
500 ++state.pos;
501 finishToken(tt.hash);
502 return;
503
504 // The interpretation of a dot depends on whether it is followed
505 // by a digit or another two dots.
506
507 case charCodes.dot:
508 readToken_dot();
509 return;
510
511 // Punctuation tokens.
512 case charCodes.leftParenthesis:
513 ++state.pos;
514 finishToken(tt.parenL);
515 return;
516 case charCodes.rightParenthesis:
517 ++state.pos;
518 finishToken(tt.parenR);
519 return;
520 case charCodes.semicolon:
521 ++state.pos;
522 finishToken(tt.semi);
523 return;
524 case charCodes.comma:
525 ++state.pos;
526 finishToken(tt.comma);
527 return;
528 case charCodes.leftSquareBracket:
529 ++state.pos;
530 finishToken(tt.bracketL);
531 return;
532 case charCodes.rightSquareBracket:
533 ++state.pos;
534 finishToken(tt.bracketR);
535 return;
536
537 case charCodes.leftCurlyBrace:
538 if (isFlowEnabled && input.charCodeAt(state.pos + 1) === charCodes.verticalBar) {
539 finishOp(tt.braceBarL, 2);
540 } else {
541 ++state.pos;
542 finishToken(tt.braceL);
543 }
544 return;
545
546 case charCodes.rightCurlyBrace:
547 ++state.pos;
548 finishToken(tt.braceR);
549 return;
550
551 case charCodes.colon:
552 if (input.charCodeAt(state.pos + 1) === charCodes.colon) {
553 finishOp(tt.doubleColon, 2);
554 } else {
555 ++state.pos;
556 finishToken(tt.colon);
557 }
558 return;
559
560 case charCodes.questionMark:
561 readToken_question();
562 return;
563 case charCodes.atSign:
564 ++state.pos;
565 finishToken(tt.at);
566 return;
567
568 case charCodes.graveAccent:
569 ++state.pos;
570 finishToken(tt.backQuote);
571 return;
572
573 case charCodes.digit0: {
574 const nextChar = input.charCodeAt(state.pos + 1);
575 // '0x', '0X', '0o', '0O', '0b', '0B'
576 if (
577 nextChar === charCodes.lowercaseX ||
578 nextChar === charCodes.uppercaseX ||
579 nextChar === charCodes.lowercaseO ||
580 nextChar === charCodes.uppercaseO ||
581 nextChar === charCodes.lowercaseB ||
582 nextChar === charCodes.uppercaseB
583 ) {
584 readRadixNumber();
585 return;
586 }
587 }
588 // Anything else beginning with a digit is an integer, octal
589 // number, or float.
590 case charCodes.digit1:
591 case charCodes.digit2:
592 case charCodes.digit3:
593 case charCodes.digit4:
594 case charCodes.digit5:
595 case charCodes.digit6:
596 case charCodes.digit7:
597 case charCodes.digit8:
598 case charCodes.digit9:
599 readNumber(false);
600 return;
601
602 // Quotes produce strings.
603 case charCodes.quotationMark:
604 case charCodes.apostrophe:
605 readString(code);
606 return;
607
608 // Operators are parsed inline in tiny state machines. '=' (charCodes.equalsTo) is
609 // often referred to. `finishOp` simply skips the amount of
610 // characters it is given as second argument, and returns a token
611 // of the type given by its first argument.
612
613 case charCodes.slash:
614 readToken_slash();
615 return;
616
617 case charCodes.percentSign:
618 case charCodes.asterisk:
619 readToken_mult_modulo(code);
620 return;
621
622 case charCodes.verticalBar:
623 case charCodes.ampersand:
624 readToken_pipe_amp(code);
625 return;
626
627 case charCodes.caret:
628 readToken_caret();
629 return;
630
631 case charCodes.plusSign:
632 case charCodes.dash:
633 readToken_plus_min(code);
634 return;
635
636 case charCodes.lessThan:
637 case charCodes.greaterThan:
638 readToken_lt_gt(code);
639 return;
640
641 case charCodes.equalsTo:
642 case charCodes.exclamationMark:
643 readToken_eq_excl(code);
644 return;
645
646 case charCodes.tilde:
647 finishOp(tt.tilde, 1);
648 return;
649
650 default:
651 break;
652 }
653
654 unexpected(`Unexpected character '${String.fromCharCode(code)}'`, state.pos);
655}
656
657function finishOp(type, size) {
658 state.pos += size;
659 finishToken(type);
660}
661
662function readRegexp() {
663 const start = state.pos;
664 let escaped = false;
665 let inClass = false;
666 for (;;) {
667 if (state.pos >= input.length) {
668 unexpected("Unterminated regular expression", start);
669 return;
670 }
671 const code = input.charCodeAt(state.pos);
672 if (escaped) {
673 escaped = false;
674 } else {
675 if (code === charCodes.leftSquareBracket) {
676 inClass = true;
677 } else if (code === charCodes.rightSquareBracket && inClass) {
678 inClass = false;
679 } else if (code === charCodes.slash && !inClass) {
680 break;
681 }
682 escaped = code === charCodes.backslash;
683 }
684 ++state.pos;
685 }
686 ++state.pos;
687 // Need to use `skipWord` because '\uXXXX' sequences are allowed here (don't ask).
688 skipWord();
689
690 finishToken(tt.regexp);
691}
692
693// Read an integer. We allow any valid digit, including hex digits, plus numeric separators, and
694// stop at any other character.
695function readInt() {
696 while (true) {
697 const code = input.charCodeAt(state.pos);
698 if (
699 (code >= charCodes.digit0 && code <= charCodes.digit9) ||
700 (code >= charCodes.lowercaseA && code <= charCodes.lowercaseF) ||
701 (code >= charCodes.uppercaseA && code <= charCodes.uppercaseF) ||
702 code === charCodes.underscore
703 ) {
704 state.pos++;
705 } else {
706 break;
707 }
708 }
709}
710
711function readRadixNumber() {
712 let isBigInt = false;
713
714 state.pos += 2; // 0x
715 readInt();
716
717 if (input.charCodeAt(state.pos) === charCodes.lowercaseN) {
718 ++state.pos;
719 isBigInt = true;
720 }
721
722 if (isBigInt) {
723 finishToken(tt.bigint);
724 return;
725 }
726
727 finishToken(tt.num);
728}
729
730// Read an integer, octal integer, or floating-point number.
731function readNumber(startsWithDot) {
732 let isBigInt = false;
733
734 if (!startsWithDot) {
735 readInt();
736 }
737
738 let nextChar = input.charCodeAt(state.pos);
739 if (nextChar === charCodes.dot) {
740 ++state.pos;
741 readInt();
742 nextChar = input.charCodeAt(state.pos);
743 }
744
745 if (nextChar === charCodes.uppercaseE || nextChar === charCodes.lowercaseE) {
746 nextChar = input.charCodeAt(++state.pos);
747 if (nextChar === charCodes.plusSign || nextChar === charCodes.dash) {
748 ++state.pos;
749 }
750 readInt();
751 nextChar = input.charCodeAt(state.pos);
752 }
753
754 if (nextChar === charCodes.lowercaseN) {
755 ++state.pos;
756 isBigInt = true;
757 }
758
759 if (isBigInt) {
760 finishToken(tt.bigint);
761 return;
762 }
763 finishToken(tt.num);
764}
765
766function readString(quote) {
767 state.pos++;
768 for (;;) {
769 if (state.pos >= input.length) {
770 unexpected("Unterminated string constant");
771 return;
772 }
773 const ch = input.charCodeAt(state.pos);
774 if (ch === charCodes.backslash) {
775 state.pos++;
776 } else if (ch === quote) {
777 break;
778 }
779 state.pos++;
780 }
781 state.pos++;
782 finishToken(tt.string);
783}
784
785// Reads template string tokens.
786function readTmplToken() {
787 for (;;) {
788 if (state.pos >= input.length) {
789 unexpected("Unterminated template");
790 return;
791 }
792 const ch = input.charCodeAt(state.pos);
793 if (
794 ch === charCodes.graveAccent ||
795 (ch === charCodes.dollarSign && input.charCodeAt(state.pos + 1) === charCodes.leftCurlyBrace)
796 ) {
797 if (state.pos === state.start && match(tt.template)) {
798 if (ch === charCodes.dollarSign) {
799 state.pos += 2;
800 finishToken(tt.dollarBraceL);
801 return;
802 } else {
803 ++state.pos;
804 finishToken(tt.backQuote);
805 return;
806 }
807 }
808 finishToken(tt.template);
809 return;
810 }
811 if (ch === charCodes.backslash) {
812 state.pos++;
813 }
814 state.pos++;
815 }
816}
817
818// Skip to the end of the current word. Note that this is the same as the snippet at the end of
819// readWord, but calling skipWord from readWord seems to slightly hurt performance from some rough
820// measurements.
821export function skipWord() {
822 while (state.pos < input.length) {
823 const ch = input.charCodeAt(state.pos);
824 if (IS_IDENTIFIER_CHAR[ch]) {
825 state.pos++;
826 } else if (ch === charCodes.backslash) {
827 // \u
828 state.pos += 2;
829 if (input.charCodeAt(state.pos) === charCodes.leftCurlyBrace) {
830 while (
831 state.pos < input.length &&
832 input.charCodeAt(state.pos) !== charCodes.rightCurlyBrace
833 ) {
834 state.pos++;
835 }
836 state.pos++;
837 }
838 } else {
839 break;
840 }
841 }
842}