UNPKG

28.6 kBJavaScriptView Raw
1/* eslint max-len: 0 */
2
3// A recursive descent parser operates by defining functions for all
4// syntactic elements, and recursively calling those, each function
5// advancing the input stream and returning an AST node. Precedence
6// of constructs (for example, the fact that `!x[1]` means `!(x[1])`
7// instead of `(!x)[1]` is handled by the fact that the parser
8// function that parses unary prefix operators is called first, and
9// in turn calls the function that parses `[]` subscripts — that
10// way, it'll receive the node for `x[1]` already parsed, and wraps
11// *that* in the unary operator node.
12//
13// Acorn uses an [operator precedence parser][opp] to handle binary
14// operator precedence, because it is much more compact than using
15// the technique outlined above, which uses different, nesting
16// functions to specify precedence, for all of the ten binary
17// precedence levels that JavaScript defines.
18//
19// [opp]: http://en.wikipedia.org/wiki/Operator-precedence_parser
20
21import {
22 flowParseArrow,
23 flowParseFunctionBodyAndFinish,
24 flowParseMaybeAssign,
25 flowParseSubscript,
26 flowParseSubscripts,
27 flowParseVariance,
28 flowStartParseAsyncArrowFromCallExpression,
29 flowStartParseNewArguments,
30 flowStartParseObjPropValue,
31} from "../plugins/flow";
32import {jsxParseElement} from "../plugins/jsx/index";
33import {typedParseConditional, typedParseParenItem} from "../plugins/types";
34import {
35 tsParseArrow,
36 tsParseFunctionBodyAndFinish,
37 tsParseMaybeAssign,
38 tsParseSubscript,
39 tsParseType,
40 tsParseTypeAssertion,
41 tsStartParseAsyncArrowFromCallExpression,
42 tsStartParseNewArguments,
43 tsStartParseObjPropValue,
44} from "../plugins/typescript";
45import {
46 eat,
47 IdentifierRole,
48 lookaheadCharCode,
49 lookaheadType,
50 match,
51 next,
52 nextTemplateToken,
53 popTypeContext,
54 pushTypeContext,
55 retokenizeSlashAsRegex,
56} from "../tokenizer/index";
57import {ContextualKeyword} from "../tokenizer/keywords";
58import {Scope} from "../tokenizer/state";
59import {TokenType, TokenType as tt} from "../tokenizer/types";
60import {charCodes} from "../util/charcodes";
61import {IS_IDENTIFIER_START} from "../util/identifier";
62import {getNextContextId, isFlowEnabled, isJSXEnabled, isTypeScriptEnabled, state} from "./base";
63import {
64 markPriorBindingIdentifier,
65 parseBindingIdentifier,
66 parseMaybeDefault,
67 parseRest,
68 parseSpread,
69} from "./lval";
70import {
71 parseBlock,
72 parseBlockBody,
73 parseClass,
74 parseDecorators,
75 parseFunction,
76 parseFunctionParams,
77} from "./statement";
78import {
79 canInsertSemicolon,
80 eatContextual,
81 expect,
82 expectContextual,
83 hasFollowingLineBreak,
84 hasPrecedingLineBreak,
85 isContextual,
86 unexpected,
87} from "./util";
88
89export class StopState {
90
91 constructor(stop) {
92 this.stop = stop;
93 }
94}
95
96// ### Expression parsing
97
98// These nest, from the most general expression type at the top to
99// 'atomic', nondivisible expression types at the bottom. Most of
100// the functions will simply let the function (s) below them parse,
101// and, *if* the syntactic construct they handle is present, wrap
102// the AST node that the inner parser gave them in another node.
103export function parseExpression(noIn = false) {
104 parseMaybeAssign(noIn);
105 if (match(tt.comma)) {
106 while (eat(tt.comma)) {
107 parseMaybeAssign(noIn);
108 }
109 }
110}
111
112/**
113 * noIn is used when parsing a for loop so that we don't interpret a following "in" as the binary
114 * operatior.
115 * isWithinParens is used to indicate that we're parsing something that might be a comma expression
116 * or might be an arrow function or might be a Flow type assertion (which requires explicit parens).
117 * In these cases, we should allow : and ?: after the initial "left" part.
118 */
119export function parseMaybeAssign(noIn = false, isWithinParens = false) {
120 if (isTypeScriptEnabled) {
121 return tsParseMaybeAssign(noIn, isWithinParens);
122 } else if (isFlowEnabled) {
123 return flowParseMaybeAssign(noIn, isWithinParens);
124 } else {
125 return baseParseMaybeAssign(noIn, isWithinParens);
126 }
127}
128
129// Parse an assignment expression. This includes applications of
130// operators like `+=`.
131// Returns true if the expression was an arrow function.
132export function baseParseMaybeAssign(noIn, isWithinParens) {
133 if (match(tt._yield)) {
134 parseYield();
135 return false;
136 }
137
138 if (match(tt.parenL) || match(tt.name) || match(tt._yield)) {
139 state.potentialArrowAt = state.start;
140 }
141
142 const wasArrow = parseMaybeConditional(noIn);
143 if (isWithinParens) {
144 parseParenItem();
145 }
146 if (state.type & TokenType.IS_ASSIGN) {
147 next();
148 parseMaybeAssign(noIn);
149 return false;
150 }
151 return wasArrow;
152}
153
154// Parse a ternary conditional (`?:`) operator.
155// Returns true if the expression was an arrow function.
156function parseMaybeConditional(noIn) {
157 const wasArrow = parseExprOps(noIn);
158 if (wasArrow) {
159 return true;
160 }
161 parseConditional(noIn);
162 return false;
163}
164
165function parseConditional(noIn) {
166 if (isTypeScriptEnabled || isFlowEnabled) {
167 typedParseConditional(noIn);
168 } else {
169 baseParseConditional(noIn);
170 }
171}
172
173export function baseParseConditional(noIn) {
174 if (eat(tt.question)) {
175 parseMaybeAssign();
176 expect(tt.colon);
177 parseMaybeAssign(noIn);
178 }
179}
180
181// Start the precedence parser.
182// Returns true if this was an arrow function
183function parseExprOps(noIn) {
184 const startTokenIndex = state.tokens.length;
185 const wasArrow = parseMaybeUnary();
186 if (wasArrow) {
187 return true;
188 }
189 parseExprOp(startTokenIndex, -1, noIn);
190 return false;
191}
192
193// Parse binary operators with the operator precedence parsing
194// algorithm. `left` is the left-hand side of the operator.
195// `minPrec` provides context that allows the function to stop and
196// defer further parser to one of its callers when it encounters an
197// operator that has a lower precedence than the set it is parsing.
198function parseExprOp(startTokenIndex, minPrec, noIn) {
199 if (
200 isTypeScriptEnabled &&
201 (tt._in & TokenType.PRECEDENCE_MASK) > minPrec &&
202 !hasPrecedingLineBreak() &&
203 eatContextual(ContextualKeyword._as)
204 ) {
205 state.tokens[state.tokens.length - 1].type = tt._as;
206 const oldIsType = pushTypeContext(1);
207 tsParseType();
208 popTypeContext(oldIsType);
209 parseExprOp(startTokenIndex, minPrec, noIn);
210 return;
211 }
212
213 const prec = state.type & TokenType.PRECEDENCE_MASK;
214 if (prec > 0 && (!noIn || !match(tt._in))) {
215 if (prec > minPrec) {
216 const op = state.type;
217 next();
218 if (op === tt.nullishCoalescing) {
219 state.tokens[state.tokens.length - 1].nullishStartIndex = startTokenIndex;
220 }
221
222 const rhsStartTokenIndex = state.tokens.length;
223 parseMaybeUnary();
224 // Extend the right operand of this operator if possible.
225 parseExprOp(rhsStartTokenIndex, op & TokenType.IS_RIGHT_ASSOCIATIVE ? prec - 1 : prec, noIn);
226 if (op === tt.nullishCoalescing) {
227 state.tokens[startTokenIndex].numNullishCoalesceStarts++;
228 state.tokens[state.tokens.length - 1].numNullishCoalesceEnds++;
229 }
230 // Continue with any future operator holding this expression as the left operand.
231 parseExprOp(startTokenIndex, minPrec, noIn);
232 }
233 }
234}
235
236// Parse unary operators, both prefix and postfix.
237// Returns true if this was an arrow function.
238export function parseMaybeUnary() {
239 if (isTypeScriptEnabled && !isJSXEnabled && eat(tt.lessThan)) {
240 tsParseTypeAssertion();
241 return false;
242 }
243 if (
244 isContextual(ContextualKeyword._module) &&
245 lookaheadCharCode() === charCodes.leftCurlyBrace &&
246 !hasFollowingLineBreak()
247 ) {
248 parseModuleExpression();
249 return false;
250 }
251 if (state.type & TokenType.IS_PREFIX) {
252 next();
253 parseMaybeUnary();
254 return false;
255 }
256
257 const wasArrow = parseExprSubscripts();
258 if (wasArrow) {
259 return true;
260 }
261 while (state.type & TokenType.IS_POSTFIX && !canInsertSemicolon()) {
262 // The tokenizer calls everything a preincrement, so make it a postincrement when
263 // we see it in that context.
264 if (state.type === tt.preIncDec) {
265 state.type = tt.postIncDec;
266 }
267 next();
268 }
269 return false;
270}
271
272// Parse call, dot, and `[]`-subscript expressions.
273// Returns true if this was an arrow function.
274export function parseExprSubscripts() {
275 const startTokenIndex = state.tokens.length;
276 const wasArrow = parseExprAtom();
277 if (wasArrow) {
278 return true;
279 }
280 parseSubscripts(startTokenIndex);
281 // If there was any optional chain operation, the start token would be marked
282 // as such, so also mark the end now.
283 if (state.tokens.length > startTokenIndex && state.tokens[startTokenIndex].isOptionalChainStart) {
284 state.tokens[state.tokens.length - 1].isOptionalChainEnd = true;
285 }
286 return false;
287}
288
289function parseSubscripts(startTokenIndex, noCalls = false) {
290 if (isFlowEnabled) {
291 flowParseSubscripts(startTokenIndex, noCalls);
292 } else {
293 baseParseSubscripts(startTokenIndex, noCalls);
294 }
295}
296
297export function baseParseSubscripts(startTokenIndex, noCalls = false) {
298 const stopState = new StopState(false);
299 do {
300 parseSubscript(startTokenIndex, noCalls, stopState);
301 } while (!stopState.stop && !state.error);
302}
303
304function parseSubscript(startTokenIndex, noCalls, stopState) {
305 if (isTypeScriptEnabled) {
306 tsParseSubscript(startTokenIndex, noCalls, stopState);
307 } else if (isFlowEnabled) {
308 flowParseSubscript(startTokenIndex, noCalls, stopState);
309 } else {
310 baseParseSubscript(startTokenIndex, noCalls, stopState);
311 }
312}
313
314/** Set 'state.stop = true' to indicate that we should stop parsing subscripts. */
315export function baseParseSubscript(
316 startTokenIndex,
317 noCalls,
318 stopState,
319) {
320 if (!noCalls && eat(tt.doubleColon)) {
321 parseNoCallExpr();
322 stopState.stop = true;
323 // Propagate startTokenIndex so that `a::b?.()` will keep `a` as the first token. We may want
324 // to revisit this in the future when fully supporting bind syntax.
325 parseSubscripts(startTokenIndex, noCalls);
326 } else if (match(tt.questionDot)) {
327 state.tokens[startTokenIndex].isOptionalChainStart = true;
328 if (noCalls && lookaheadType() === tt.parenL) {
329 stopState.stop = true;
330 return;
331 }
332 next();
333 state.tokens[state.tokens.length - 1].subscriptStartIndex = startTokenIndex;
334
335 if (eat(tt.bracketL)) {
336 parseExpression();
337 expect(tt.bracketR);
338 } else if (eat(tt.parenL)) {
339 parseCallExpressionArguments();
340 } else {
341 parseMaybePrivateName();
342 }
343 } else if (eat(tt.dot)) {
344 state.tokens[state.tokens.length - 1].subscriptStartIndex = startTokenIndex;
345 parseMaybePrivateName();
346 } else if (eat(tt.bracketL)) {
347 state.tokens[state.tokens.length - 1].subscriptStartIndex = startTokenIndex;
348 parseExpression();
349 expect(tt.bracketR);
350 } else if (!noCalls && match(tt.parenL)) {
351 if (atPossibleAsync()) {
352 // We see "async", but it's possible it's a usage of the name "async". Parse as if it's a
353 // function call, and if we see an arrow later, backtrack and re-parse as a parameter list.
354 const snapshot = state.snapshot();
355 const asyncStartTokenIndex = state.tokens.length;
356 next();
357 state.tokens[state.tokens.length - 1].subscriptStartIndex = startTokenIndex;
358
359 const callContextId = getNextContextId();
360
361 state.tokens[state.tokens.length - 1].contextId = callContextId;
362 parseCallExpressionArguments();
363 state.tokens[state.tokens.length - 1].contextId = callContextId;
364
365 if (shouldParseAsyncArrow()) {
366 // We hit an arrow, so backtrack and start again parsing function parameters.
367 state.restoreFromSnapshot(snapshot);
368 stopState.stop = true;
369 state.scopeDepth++;
370
371 parseFunctionParams();
372 parseAsyncArrowFromCallExpression(asyncStartTokenIndex);
373 }
374 } else {
375 next();
376 state.tokens[state.tokens.length - 1].subscriptStartIndex = startTokenIndex;
377 const callContextId = getNextContextId();
378 state.tokens[state.tokens.length - 1].contextId = callContextId;
379 parseCallExpressionArguments();
380 state.tokens[state.tokens.length - 1].contextId = callContextId;
381 }
382 } else if (match(tt.backQuote)) {
383 // Tagged template expression.
384 parseTemplate();
385 } else {
386 stopState.stop = true;
387 }
388}
389
390export function atPossibleAsync() {
391 // This was made less strict than the original version to avoid passing around nodes, but it
392 // should be safe to have rare false positives here.
393 return (
394 state.tokens[state.tokens.length - 1].contextualKeyword === ContextualKeyword._async &&
395 !canInsertSemicolon()
396 );
397}
398
399export function parseCallExpressionArguments() {
400 let first = true;
401 while (!eat(tt.parenR) && !state.error) {
402 if (first) {
403 first = false;
404 } else {
405 expect(tt.comma);
406 if (eat(tt.parenR)) {
407 break;
408 }
409 }
410
411 parseExprListItem(false);
412 }
413}
414
415function shouldParseAsyncArrow() {
416 return match(tt.colon) || match(tt.arrow);
417}
418
419function parseAsyncArrowFromCallExpression(startTokenIndex) {
420 if (isTypeScriptEnabled) {
421 tsStartParseAsyncArrowFromCallExpression();
422 } else if (isFlowEnabled) {
423 flowStartParseAsyncArrowFromCallExpression();
424 }
425 expect(tt.arrow);
426 parseArrowExpression(startTokenIndex);
427}
428
429// Parse a no-call expression (like argument of `new` or `::` operators).
430
431function parseNoCallExpr() {
432 const startTokenIndex = state.tokens.length;
433 parseExprAtom();
434 parseSubscripts(startTokenIndex, true);
435}
436
437// Parse an atomic expression — either a single token that is an
438// expression, an expression started by a keyword like `function` or
439// `new`, or an expression wrapped in punctuation like `()`, `[]`,
440// or `{}`.
441// Returns true if the parsed expression was an arrow function.
442export function parseExprAtom() {
443 if (eat(tt.modulo)) {
444 // V8 intrinsic expression. Just parse the identifier, and the function invocation is parsed
445 // naturally.
446 parseIdentifier();
447 return false;
448 }
449
450 if (match(tt.jsxText)) {
451 parseLiteral();
452 return false;
453 } else if (match(tt.lessThan) && isJSXEnabled) {
454 state.type = tt.jsxTagStart;
455 jsxParseElement();
456 next();
457 return false;
458 }
459
460 const canBeArrow = state.potentialArrowAt === state.start;
461 switch (state.type) {
462 case tt.slash:
463 case tt.assign:
464 retokenizeSlashAsRegex();
465 // Fall through.
466
467 case tt._super:
468 case tt._this:
469 case tt.regexp:
470 case tt.num:
471 case tt.bigint:
472 case tt.decimal:
473 case tt.string:
474 case tt._null:
475 case tt._true:
476 case tt._false:
477 next();
478 return false;
479
480 case tt._import:
481 next();
482 if (match(tt.dot)) {
483 // import.meta
484 state.tokens[state.tokens.length - 1].type = tt.name;
485 next();
486 parseIdentifier();
487 }
488 return false;
489
490 case tt.name: {
491 const startTokenIndex = state.tokens.length;
492 const functionStart = state.start;
493 const contextualKeyword = state.contextualKeyword;
494 parseIdentifier();
495 if (contextualKeyword === ContextualKeyword._await) {
496 parseAwait();
497 return false;
498 } else if (
499 contextualKeyword === ContextualKeyword._async &&
500 match(tt._function) &&
501 !canInsertSemicolon()
502 ) {
503 next();
504 parseFunction(functionStart, false);
505 return false;
506 } else if (
507 canBeArrow &&
508 contextualKeyword === ContextualKeyword._async &&
509 !canInsertSemicolon() &&
510 match(tt.name)
511 ) {
512 state.scopeDepth++;
513 parseBindingIdentifier(false);
514 expect(tt.arrow);
515 // let foo = async bar => {};
516 parseArrowExpression(startTokenIndex);
517 return true;
518 } else if (match(tt._do) && !canInsertSemicolon()) {
519 next();
520 parseBlock();
521 return false;
522 }
523
524 if (canBeArrow && !canInsertSemicolon() && match(tt.arrow)) {
525 state.scopeDepth++;
526 markPriorBindingIdentifier(false);
527 expect(tt.arrow);
528 parseArrowExpression(startTokenIndex);
529 return true;
530 }
531
532 state.tokens[state.tokens.length - 1].identifierRole = IdentifierRole.Access;
533 return false;
534 }
535
536 case tt._do: {
537 next();
538 parseBlock();
539 return false;
540 }
541
542 case tt.parenL: {
543 const wasArrow = parseParenAndDistinguishExpression(canBeArrow);
544 return wasArrow;
545 }
546
547 case tt.bracketL:
548 next();
549 parseExprList(tt.bracketR, true);
550 return false;
551
552 case tt.braceL:
553 parseObj(false, false);
554 return false;
555
556 case tt._function:
557 parseFunctionExpression();
558 return false;
559
560 case tt.at:
561 parseDecorators();
562 // Fall through.
563
564 case tt._class:
565 parseClass(false);
566 return false;
567
568 case tt._new:
569 parseNew();
570 return false;
571
572 case tt.backQuote:
573 parseTemplate();
574 return false;
575
576 case tt.doubleColon: {
577 next();
578 parseNoCallExpr();
579 return false;
580 }
581
582 case tt.hash: {
583 const code = lookaheadCharCode();
584 if (IS_IDENTIFIER_START[code] || code === charCodes.backslash) {
585 parseMaybePrivateName();
586 } else {
587 next();
588 }
589 // Smart pipeline topic reference.
590 return false;
591 }
592
593 default:
594 unexpected();
595 return false;
596 }
597}
598
599function parseMaybePrivateName() {
600 eat(tt.hash);
601 parseIdentifier();
602}
603
604function parseFunctionExpression() {
605 const functionStart = state.start;
606 parseIdentifier();
607 if (eat(tt.dot)) {
608 // function.sent
609 parseIdentifier();
610 }
611 parseFunction(functionStart, false);
612}
613
614export function parseLiteral() {
615 next();
616}
617
618export function parseParenExpression() {
619 expect(tt.parenL);
620 parseExpression();
621 expect(tt.parenR);
622}
623
624// Returns true if this was an arrow expression.
625function parseParenAndDistinguishExpression(canBeArrow) {
626 // Assume this is a normal parenthesized expression, but if we see an arrow, we'll bail and
627 // start over as a parameter list.
628 const snapshot = state.snapshot();
629
630 const startTokenIndex = state.tokens.length;
631 expect(tt.parenL);
632
633 let first = true;
634
635 while (!match(tt.parenR) && !state.error) {
636 if (first) {
637 first = false;
638 } else {
639 expect(tt.comma);
640 if (match(tt.parenR)) {
641 break;
642 }
643 }
644
645 if (match(tt.ellipsis)) {
646 parseRest(false /* isBlockScope */);
647 parseParenItem();
648 break;
649 } else {
650 parseMaybeAssign(false, true);
651 }
652 }
653
654 expect(tt.parenR);
655
656 if (canBeArrow && shouldParseArrow()) {
657 const wasArrow = parseArrow();
658 if (wasArrow) {
659 // It was an arrow function this whole time, so start over and parse it as params so that we
660 // get proper token annotations.
661 state.restoreFromSnapshot(snapshot);
662 state.scopeDepth++;
663 // Don't specify a context ID because arrow functions don't need a context ID.
664 parseFunctionParams();
665 parseArrow();
666 parseArrowExpression(startTokenIndex);
667 return true;
668 }
669 }
670
671 return false;
672}
673
674function shouldParseArrow() {
675 return match(tt.colon) || !canInsertSemicolon();
676}
677
678// Returns whether there was an arrow token.
679export function parseArrow() {
680 if (isTypeScriptEnabled) {
681 return tsParseArrow();
682 } else if (isFlowEnabled) {
683 return flowParseArrow();
684 } else {
685 return eat(tt.arrow);
686 }
687}
688
689function parseParenItem() {
690 if (isTypeScriptEnabled || isFlowEnabled) {
691 typedParseParenItem();
692 }
693}
694
695// New's precedence is slightly tricky. It must allow its argument to
696// be a `[]` or dot subscript expression, but not a call — at least,
697// not without wrapping it in parentheses. Thus, it uses the noCalls
698// argument to parseSubscripts to prevent it from consuming the
699// argument list.
700function parseNew() {
701 expect(tt._new);
702 if (eat(tt.dot)) {
703 // new.target
704 parseIdentifier();
705 return;
706 }
707 parseNoCallExpr();
708 eat(tt.questionDot);
709 parseNewArguments();
710}
711
712function parseNewArguments() {
713 if (isTypeScriptEnabled) {
714 tsStartParseNewArguments();
715 } else if (isFlowEnabled) {
716 flowStartParseNewArguments();
717 }
718 if (eat(tt.parenL)) {
719 parseExprList(tt.parenR);
720 }
721}
722
723export function parseTemplate() {
724 // Finish `, read quasi
725 nextTemplateToken();
726 // Finish quasi, read ${
727 nextTemplateToken();
728 while (!match(tt.backQuote) && !state.error) {
729 expect(tt.dollarBraceL);
730 parseExpression();
731 // Finish }, read quasi
732 nextTemplateToken();
733 // Finish quasi, read either ${ or `
734 nextTemplateToken();
735 }
736 next();
737}
738
739// Parse an object literal or binding pattern.
740export function parseObj(isPattern, isBlockScope) {
741 // Attach a context ID to the object open and close brace and each object key.
742 const contextId = getNextContextId();
743 let first = true;
744
745 next();
746 state.tokens[state.tokens.length - 1].contextId = contextId;
747
748 while (!eat(tt.braceR) && !state.error) {
749 if (first) {
750 first = false;
751 } else {
752 expect(tt.comma);
753 if (eat(tt.braceR)) {
754 break;
755 }
756 }
757
758 let isGenerator = false;
759 if (match(tt.ellipsis)) {
760 const previousIndex = state.tokens.length;
761 parseSpread();
762 if (isPattern) {
763 // Mark role when the only thing being spread over is an identifier.
764 if (state.tokens.length === previousIndex + 2) {
765 markPriorBindingIdentifier(isBlockScope);
766 }
767 if (eat(tt.braceR)) {
768 break;
769 }
770 }
771 continue;
772 }
773
774 if (!isPattern) {
775 isGenerator = eat(tt.star);
776 }
777
778 if (!isPattern && isContextual(ContextualKeyword._async)) {
779 if (isGenerator) unexpected();
780
781 parseIdentifier();
782 if (
783 match(tt.colon) ||
784 match(tt.parenL) ||
785 match(tt.braceR) ||
786 match(tt.eq) ||
787 match(tt.comma)
788 ) {
789 // This is a key called "async" rather than an async function.
790 } else {
791 if (match(tt.star)) {
792 next();
793 isGenerator = true;
794 }
795 parsePropertyName(contextId);
796 }
797 } else {
798 parsePropertyName(contextId);
799 }
800
801 parseObjPropValue(isPattern, isBlockScope, contextId);
802 }
803
804 state.tokens[state.tokens.length - 1].contextId = contextId;
805}
806
807function isGetterOrSetterMethod(isPattern) {
808 // We go off of the next and don't bother checking if the node key is actually "get" or "set".
809 // This lets us avoid generating a node, and should only make the validation worse.
810 return (
811 !isPattern &&
812 (match(tt.string) || // get "string"() {}
813 match(tt.num) || // get 1() {}
814 match(tt.bracketL) || // get ["string"]() {}
815 match(tt.name) || // get foo() {}
816 !!(state.type & TokenType.IS_KEYWORD)) // get debugger() {}
817 );
818}
819
820// Returns true if this was a method.
821function parseObjectMethod(isPattern, objectContextId) {
822 // We don't need to worry about modifiers because object methods can't have optional bodies, so
823 // the start will never be used.
824 const functionStart = state.start;
825 if (match(tt.parenL)) {
826 if (isPattern) unexpected();
827 parseMethod(functionStart, /* isConstructor */ false);
828 return true;
829 }
830
831 if (isGetterOrSetterMethod(isPattern)) {
832 parsePropertyName(objectContextId);
833 parseMethod(functionStart, /* isConstructor */ false);
834 return true;
835 }
836 return false;
837}
838
839function parseObjectProperty(isPattern, isBlockScope) {
840 if (eat(tt.colon)) {
841 if (isPattern) {
842 parseMaybeDefault(isBlockScope);
843 } else {
844 parseMaybeAssign(false);
845 }
846 return;
847 }
848
849 // Since there's no colon, we assume this is an object shorthand.
850
851 // If we're in a destructuring, we've now discovered that the key was actually an assignee, so
852 // we need to tag it as a declaration with the appropriate scope. Otherwise, we might need to
853 // transform it on access, so mark it as a normal object shorthand.
854 let identifierRole;
855 if (isPattern) {
856 if (state.scopeDepth === 0) {
857 identifierRole = IdentifierRole.ObjectShorthandTopLevelDeclaration;
858 } else if (isBlockScope) {
859 identifierRole = IdentifierRole.ObjectShorthandBlockScopedDeclaration;
860 } else {
861 identifierRole = IdentifierRole.ObjectShorthandFunctionScopedDeclaration;
862 }
863 } else {
864 identifierRole = IdentifierRole.ObjectShorthand;
865 }
866 state.tokens[state.tokens.length - 1].identifierRole = identifierRole;
867
868 // Regardless of whether we know this to be a pattern or if we're in an ambiguous context, allow
869 // parsing as if there's a default value.
870 parseMaybeDefault(isBlockScope, true);
871}
872
873function parseObjPropValue(
874 isPattern,
875 isBlockScope,
876 objectContextId,
877) {
878 if (isTypeScriptEnabled) {
879 tsStartParseObjPropValue();
880 } else if (isFlowEnabled) {
881 flowStartParseObjPropValue();
882 }
883 const wasMethod = parseObjectMethod(isPattern, objectContextId);
884 if (!wasMethod) {
885 parseObjectProperty(isPattern, isBlockScope);
886 }
887}
888
889export function parsePropertyName(objectContextId) {
890 if (isFlowEnabled) {
891 flowParseVariance();
892 }
893 if (eat(tt.bracketL)) {
894 state.tokens[state.tokens.length - 1].contextId = objectContextId;
895 parseMaybeAssign();
896 expect(tt.bracketR);
897 state.tokens[state.tokens.length - 1].contextId = objectContextId;
898 } else {
899 if (match(tt.num) || match(tt.string) || match(tt.bigint) || match(tt.decimal)) {
900 parseExprAtom();
901 } else {
902 parseMaybePrivateName();
903 }
904
905 state.tokens[state.tokens.length - 1].identifierRole = IdentifierRole.ObjectKey;
906 state.tokens[state.tokens.length - 1].contextId = objectContextId;
907 }
908}
909
910// Parse object or class method.
911export function parseMethod(functionStart, isConstructor) {
912 const funcContextId = getNextContextId();
913
914 state.scopeDepth++;
915 const startTokenIndex = state.tokens.length;
916 const allowModifiers = isConstructor; // For TypeScript parameter properties
917 parseFunctionParams(allowModifiers, funcContextId);
918 parseFunctionBodyAndFinish(functionStart, funcContextId);
919 const endTokenIndex = state.tokens.length;
920 state.scopes.push(new Scope(startTokenIndex, endTokenIndex, true));
921 state.scopeDepth--;
922}
923
924// Parse arrow function expression.
925// If the parameters are provided, they will be converted to an
926// assignable list.
927export function parseArrowExpression(startTokenIndex) {
928 parseFunctionBody(true);
929 const endTokenIndex = state.tokens.length;
930 state.scopes.push(new Scope(startTokenIndex, endTokenIndex, true));
931 state.scopeDepth--;
932}
933
934export function parseFunctionBodyAndFinish(functionStart, funcContextId = 0) {
935 if (isTypeScriptEnabled) {
936 tsParseFunctionBodyAndFinish(functionStart, funcContextId);
937 } else if (isFlowEnabled) {
938 flowParseFunctionBodyAndFinish(funcContextId);
939 } else {
940 parseFunctionBody(false, funcContextId);
941 }
942}
943
944export function parseFunctionBody(allowExpression, funcContextId = 0) {
945 const isExpression = allowExpression && !match(tt.braceL);
946
947 if (isExpression) {
948 parseMaybeAssign();
949 } else {
950 parseBlock(true /* isFunctionScope */, funcContextId);
951 }
952}
953
954// Parses a comma-separated list of expressions, and returns them as
955// an array. `close` is the token type that ends the list, and
956// `allowEmpty` can be turned on to allow subsequent commas with
957// nothing in between them to be parsed as `null` (which is needed
958// for array literals).
959
960function parseExprList(close, allowEmpty = false) {
961 let first = true;
962 while (!eat(close) && !state.error) {
963 if (first) {
964 first = false;
965 } else {
966 expect(tt.comma);
967 if (eat(close)) break;
968 }
969 parseExprListItem(allowEmpty);
970 }
971}
972
973function parseExprListItem(allowEmpty) {
974 if (allowEmpty && match(tt.comma)) {
975 // Empty item; nothing more to parse for this item.
976 } else if (match(tt.ellipsis)) {
977 parseSpread();
978 parseParenItem();
979 } else if (match(tt.question)) {
980 // Partial function application proposal.
981 next();
982 } else {
983 parseMaybeAssign(false, true);
984 }
985}
986
987// Parse the next token as an identifier.
988export function parseIdentifier() {
989 next();
990 state.tokens[state.tokens.length - 1].type = tt.name;
991}
992
993// Parses await expression inside async function.
994function parseAwait() {
995 parseMaybeUnary();
996}
997
998// Parses yield expression inside generator.
999function parseYield() {
1000 next();
1001 if (!match(tt.semi) && !canInsertSemicolon()) {
1002 eat(tt.star);
1003 parseMaybeAssign();
1004 }
1005}
1006
1007// https://github.com/tc39/proposal-js-module-blocks
1008function parseModuleExpression() {
1009 expectContextual(ContextualKeyword._module);
1010 expect(tt.braceL);
1011 // For now, just call parseBlockBody to parse the block. In the future when we
1012 // implement full support, we'll want to emit scopes and possibly other
1013 // information.
1014 parseBlockBody(tt.braceR);
1015}