source: frontend/node_modules/sucrase/dist/esm/parser/traverser/expression.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.3 KB
Line 
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 tsStartParseObjPropValue,
43} from "../plugins/typescript";
44import {
45 eat,
46 IdentifierRole,
47 lookaheadCharCode,
48 lookaheadType,
49 match,
50 next,
51 nextTemplateToken,
52 popTypeContext,
53 pushTypeContext,
54 rescan_gt,
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) || eatContextual(ContextualKeyword._satisfies))
204 ) {
205 const oldIsType = pushTypeContext(1);
206 tsParseType();
207 popTypeContext(oldIsType);
208 rescan_gt();
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) || match(tt.jsxEmptyText)) {
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 if (state.error) {
668 // Nevermind! This must have been something that looks very much like an
669 // arrow function but where its "parameter list" isn't actually a valid
670 // parameter list. Force non-arrow parsing.
671 // See https://github.com/alangpierce/sucrase/issues/666 for an example.
672 state.restoreFromSnapshot(snapshot);
673 parseParenAndDistinguishExpression(false);
674 return false;
675 }
676 return true;
677 }
678 }
679
680 return false;
681}
682
683function shouldParseArrow() {
684 return match(tt.colon) || !canInsertSemicolon();
685}
686
687// Returns whether there was an arrow token.
688export function parseArrow() {
689 if (isTypeScriptEnabled) {
690 return tsParseArrow();
691 } else if (isFlowEnabled) {
692 return flowParseArrow();
693 } else {
694 return eat(tt.arrow);
695 }
696}
697
698function parseParenItem() {
699 if (isTypeScriptEnabled || isFlowEnabled) {
700 typedParseParenItem();
701 }
702}
703
704// New's precedence is slightly tricky. It must allow its argument to
705// be a `[]` or dot subscript expression, but not a call — at least,
706// not without wrapping it in parentheses. Thus, it uses the noCalls
707// argument to parseSubscripts to prevent it from consuming the
708// argument list.
709function parseNew() {
710 expect(tt._new);
711 if (eat(tt.dot)) {
712 // new.target
713 parseIdentifier();
714 return;
715 }
716 parseNewCallee();
717 if (isFlowEnabled) {
718 flowStartParseNewArguments();
719 }
720 if (eat(tt.parenL)) {
721 parseExprList(tt.parenR);
722 }
723}
724
725function parseNewCallee() {
726 parseNoCallExpr();
727 eat(tt.questionDot);
728}
729
730export function parseTemplate() {
731 // Finish `, read quasi
732 nextTemplateToken();
733 // Finish quasi, read ${
734 nextTemplateToken();
735 while (!match(tt.backQuote) && !state.error) {
736 expect(tt.dollarBraceL);
737 parseExpression();
738 // Finish }, read quasi
739 nextTemplateToken();
740 // Finish quasi, read either ${ or `
741 nextTemplateToken();
742 }
743 next();
744}
745
746// Parse an object literal or binding pattern.
747export function parseObj(isPattern, isBlockScope) {
748 // Attach a context ID to the object open and close brace and each object key.
749 const contextId = getNextContextId();
750 let first = true;
751
752 next();
753 state.tokens[state.tokens.length - 1].contextId = contextId;
754
755 while (!eat(tt.braceR) && !state.error) {
756 if (first) {
757 first = false;
758 } else {
759 expect(tt.comma);
760 if (eat(tt.braceR)) {
761 break;
762 }
763 }
764
765 let isGenerator = false;
766 if (match(tt.ellipsis)) {
767 const previousIndex = state.tokens.length;
768 parseSpread();
769 if (isPattern) {
770 // Mark role when the only thing being spread over is an identifier.
771 if (state.tokens.length === previousIndex + 2) {
772 markPriorBindingIdentifier(isBlockScope);
773 }
774 if (eat(tt.braceR)) {
775 break;
776 }
777 }
778 continue;
779 }
780
781 if (!isPattern) {
782 isGenerator = eat(tt.star);
783 }
784
785 if (!isPattern && isContextual(ContextualKeyword._async)) {
786 if (isGenerator) unexpected();
787
788 parseIdentifier();
789 if (
790 match(tt.colon) ||
791 match(tt.parenL) ||
792 match(tt.braceR) ||
793 match(tt.eq) ||
794 match(tt.comma)
795 ) {
796 // This is a key called "async" rather than an async function.
797 } else {
798 if (match(tt.star)) {
799 next();
800 isGenerator = true;
801 }
802 parsePropertyName(contextId);
803 }
804 } else {
805 parsePropertyName(contextId);
806 }
807
808 parseObjPropValue(isPattern, isBlockScope, contextId);
809 }
810
811 state.tokens[state.tokens.length - 1].contextId = contextId;
812}
813
814function isGetterOrSetterMethod(isPattern) {
815 // We go off of the next and don't bother checking if the node key is actually "get" or "set".
816 // This lets us avoid generating a node, and should only make the validation worse.
817 return (
818 !isPattern &&
819 (match(tt.string) || // get "string"() {}
820 match(tt.num) || // get 1() {}
821 match(tt.bracketL) || // get ["string"]() {}
822 match(tt.name) || // get foo() {}
823 !!(state.type & TokenType.IS_KEYWORD)) // get debugger() {}
824 );
825}
826
827// Returns true if this was a method.
828function parseObjectMethod(isPattern, objectContextId) {
829 // We don't need to worry about modifiers because object methods can't have optional bodies, so
830 // the start will never be used.
831 const functionStart = state.start;
832 if (match(tt.parenL)) {
833 if (isPattern) unexpected();
834 parseMethod(functionStart, /* isConstructor */ false);
835 return true;
836 }
837
838 if (isGetterOrSetterMethod(isPattern)) {
839 parsePropertyName(objectContextId);
840 parseMethod(functionStart, /* isConstructor */ false);
841 return true;
842 }
843 return false;
844}
845
846function parseObjectProperty(isPattern, isBlockScope) {
847 if (eat(tt.colon)) {
848 if (isPattern) {
849 parseMaybeDefault(isBlockScope);
850 } else {
851 parseMaybeAssign(false);
852 }
853 return;
854 }
855
856 // Since there's no colon, we assume this is an object shorthand.
857
858 // If we're in a destructuring, we've now discovered that the key was actually an assignee, so
859 // we need to tag it as a declaration with the appropriate scope. Otherwise, we might need to
860 // transform it on access, so mark it as a normal object shorthand.
861 let identifierRole;
862 if (isPattern) {
863 if (state.scopeDepth === 0) {
864 identifierRole = IdentifierRole.ObjectShorthandTopLevelDeclaration;
865 } else if (isBlockScope) {
866 identifierRole = IdentifierRole.ObjectShorthandBlockScopedDeclaration;
867 } else {
868 identifierRole = IdentifierRole.ObjectShorthandFunctionScopedDeclaration;
869 }
870 } else {
871 identifierRole = IdentifierRole.ObjectShorthand;
872 }
873 state.tokens[state.tokens.length - 1].identifierRole = identifierRole;
874
875 // Regardless of whether we know this to be a pattern or if we're in an ambiguous context, allow
876 // parsing as if there's a default value.
877 parseMaybeDefault(isBlockScope, true);
878}
879
880function parseObjPropValue(
881 isPattern,
882 isBlockScope,
883 objectContextId,
884) {
885 if (isTypeScriptEnabled) {
886 tsStartParseObjPropValue();
887 } else if (isFlowEnabled) {
888 flowStartParseObjPropValue();
889 }
890 const wasMethod = parseObjectMethod(isPattern, objectContextId);
891 if (!wasMethod) {
892 parseObjectProperty(isPattern, isBlockScope);
893 }
894}
895
896export function parsePropertyName(objectContextId) {
897 if (isFlowEnabled) {
898 flowParseVariance();
899 }
900 if (eat(tt.bracketL)) {
901 state.tokens[state.tokens.length - 1].contextId = objectContextId;
902 parseMaybeAssign();
903 expect(tt.bracketR);
904 state.tokens[state.tokens.length - 1].contextId = objectContextId;
905 } else {
906 if (match(tt.num) || match(tt.string) || match(tt.bigint) || match(tt.decimal)) {
907 parseExprAtom();
908 } else {
909 parseMaybePrivateName();
910 }
911
912 state.tokens[state.tokens.length - 1].identifierRole = IdentifierRole.ObjectKey;
913 state.tokens[state.tokens.length - 1].contextId = objectContextId;
914 }
915}
916
917// Parse object or class method.
918export function parseMethod(functionStart, isConstructor) {
919 const funcContextId = getNextContextId();
920
921 state.scopeDepth++;
922 const startTokenIndex = state.tokens.length;
923 const allowModifiers = isConstructor; // For TypeScript parameter properties
924 parseFunctionParams(allowModifiers, funcContextId);
925 parseFunctionBodyAndFinish(functionStart, funcContextId);
926 const endTokenIndex = state.tokens.length;
927 state.scopes.push(new Scope(startTokenIndex, endTokenIndex, true));
928 state.scopeDepth--;
929}
930
931// Parse arrow function expression.
932// If the parameters are provided, they will be converted to an
933// assignable list.
934export function parseArrowExpression(startTokenIndex) {
935 parseFunctionBody(true);
936 const endTokenIndex = state.tokens.length;
937 state.scopes.push(new Scope(startTokenIndex, endTokenIndex, true));
938 state.scopeDepth--;
939}
940
941export function parseFunctionBodyAndFinish(functionStart, funcContextId = 0) {
942 if (isTypeScriptEnabled) {
943 tsParseFunctionBodyAndFinish(functionStart, funcContextId);
944 } else if (isFlowEnabled) {
945 flowParseFunctionBodyAndFinish(funcContextId);
946 } else {
947 parseFunctionBody(false, funcContextId);
948 }
949}
950
951export function parseFunctionBody(allowExpression, funcContextId = 0) {
952 const isExpression = allowExpression && !match(tt.braceL);
953
954 if (isExpression) {
955 parseMaybeAssign();
956 } else {
957 parseBlock(true /* isFunctionScope */, funcContextId);
958 }
959}
960
961// Parses a comma-separated list of expressions, and returns them as
962// an array. `close` is the token type that ends the list, and
963// `allowEmpty` can be turned on to allow subsequent commas with
964// nothing in between them to be parsed as `null` (which is needed
965// for array literals).
966
967function parseExprList(close, allowEmpty = false) {
968 let first = true;
969 while (!eat(close) && !state.error) {
970 if (first) {
971 first = false;
972 } else {
973 expect(tt.comma);
974 if (eat(close)) break;
975 }
976 parseExprListItem(allowEmpty);
977 }
978}
979
980function parseExprListItem(allowEmpty) {
981 if (allowEmpty && match(tt.comma)) {
982 // Empty item; nothing more to parse for this item.
983 } else if (match(tt.ellipsis)) {
984 parseSpread();
985 parseParenItem();
986 } else if (match(tt.question)) {
987 // Partial function application proposal.
988 next();
989 } else {
990 parseMaybeAssign(false, true);
991 }
992}
993
994// Parse the next token as an identifier.
995export function parseIdentifier() {
996 next();
997 state.tokens[state.tokens.length - 1].type = tt.name;
998}
999
1000// Parses await expression inside async function.
1001function parseAwait() {
1002 parseMaybeUnary();
1003}
1004
1005// Parses yield expression inside generator.
1006function parseYield() {
1007 next();
1008 if (!match(tt.semi) && !canInsertSemicolon()) {
1009 eat(tt.star);
1010 parseMaybeAssign();
1011 }
1012}
1013
1014// https://github.com/tc39/proposal-js-module-blocks
1015function parseModuleExpression() {
1016 expectContextual(ContextualKeyword._module);
1017 expect(tt.braceL);
1018 // For now, just call parseBlockBody to parse the block. In the future when we
1019 // implement full support, we'll want to emit scopes and possibly other
1020 // information.
1021 parseBlockBody(tt.braceR);
1022}
Note: See TracBrowser for help on using the repository browser.