2929import com .semmle .js .ast .BreakStatement ;
3030import com .semmle .js .ast .CallExpression ;
3131import com .semmle .js .ast .CatchClause ;
32+ import com .semmle .js .ast .Chainable ;
3233import com .semmle .js .ast .ClassBody ;
3334import com .semmle .js .ast .ClassDeclaration ;
3435import com .semmle .js .ast .ClassExpression ;
@@ -504,6 +505,14 @@ private Token readToken_dot() {
504505 }
505506 }
506507
508+ private Token readToken_question () { // '?'
509+ int next = charAt (this .pos + 1 );
510+ int next2 = charAt (this .pos + 2 );
511+ if (this .options .esnext () && next == '.' && !('0' <= next2 && next2 <= '9' )) // '?.', but not '?.X' where X is a digit
512+ return this .finishOp (TokenType .questiondot , 2 );
513+ return this .finishOp (TokenType .question , 1 );
514+ }
515+
507516 private Token readToken_slash () { // '/'
508517 int next = charAt (this .pos + 1 );
509518 if (this .exprAllowed ) {
@@ -616,7 +625,7 @@ protected Token getTokenFromCode(int code) {
616625 case 123 : ++this .pos ; return this .finishToken (TokenType .braceL );
617626 case 125 : ++this .pos ; return this .finishToken (TokenType .braceR );
618627 case 58 : ++this .pos ; return this .finishToken (TokenType .colon );
619- case 63 : ++ this . pos ; return this .finishToken ( TokenType . question );
628+ case 63 : return this .readToken_question ( );
620629
621630 case 96 : // '`'
622631 if (this .options .ecmaVersion () < 6 ) break ;
@@ -1465,17 +1474,19 @@ protected Expression parseSubscripts(Expression base, int startPos, Position sta
14651474 }
14661475 }
14671476
1477+ private boolean isOnOptionalChain (boolean optional , Expression base ) {
1478+ return optional || base instanceof Chainable && ((Chainable )base ).isOnOptionalChain ();
1479+ }
1480+
14681481 /**
14691482 * Parse a single subscript {@code s}; if more subscripts could follow, return {@code Pair.make(s, true},
14701483 * otherwise return {@code Pair.make(s, false)}.
14711484 */
14721485 protected Pair <Expression , Boolean > parseSubscript (final Expression base , Position startLoc , boolean noCalls ) {
14731486 boolean maybeAsyncArrow = this .options .ecmaVersion () >= 8 && base instanceof Identifier && "async" .equals (((Identifier ) base ).getName ()) && !this .canInsertSemicolon ();
1474- if (this .eat (TokenType .dot )) {
1475- MemberExpression node = new MemberExpression (new SourceLocation (startLoc ), base , this .parseIdent (true ), false );
1476- return Pair .make (this .finishNode (node ), true );
1477- } else if (this .eat (TokenType .bracketL )) {
1478- MemberExpression node = new MemberExpression (new SourceLocation (startLoc ), base , this .parseExpression (false , null ), true );
1487+ boolean optional = this .eat (TokenType .questiondot );
1488+ if (this .eat (TokenType .bracketL )) {
1489+ MemberExpression node = new MemberExpression (new SourceLocation (startLoc ), base , this .parseExpression (false , null ), true , optional , isOnOptionalChain (optional , base ));
14791490 this .expect (TokenType .bracketR );
14801491 return Pair .make (this .finishNode (node ), true );
14811492 } else if (!noCalls && this .eat (TokenType .parenL )) {
@@ -1494,11 +1505,17 @@ protected Pair<Expression, Boolean> parseSubscript(final Expression base, Positi
14941505 this .checkExpressionErrors (refDestructuringErrors , true );
14951506 if (oldYieldPos > 0 ) this .yieldPos = oldYieldPos ;
14961507 if (oldAwaitPos > 0 ) this .awaitPos = oldAwaitPos ;
1497- CallExpression node = new CallExpression (new SourceLocation (startLoc ), base , new ArrayList <>(), exprList );
1508+ CallExpression node = new CallExpression (new SourceLocation (startLoc ), base , new ArrayList <>(), exprList , optional , isOnOptionalChain ( optional , base ) );
14981509 return Pair .make (this .finishNode (node ), true );
14991510 } else if (this .type == TokenType .backQuote ) {
1511+ if (isOnOptionalChain (optional , base )) {
1512+ this .raise (base , "An optional chain may not be used in a tagged template expression." );
1513+ }
15001514 TaggedTemplateExpression node = new TaggedTemplateExpression (new SourceLocation (startLoc ), base , this .parseTemplate (true ));
15011515 return Pair .make (this .finishNode (node ), true );
1516+ } else if (optional || this .eat (TokenType .dot )) {
1517+ MemberExpression node = new MemberExpression (new SourceLocation (startLoc ), base , this .parseIdent (true ), false , optional , isOnOptionalChain (optional , base ));
1518+ return Pair .make (this .finishNode (node ), true );
15021519 } else {
15031520 return Pair .make (base , false );
15041521 }
@@ -1719,6 +1736,10 @@ protected Expression parseNew() {
17191736 int innerStartPos = this .start ;
17201737 Position innerStartLoc = this .startLoc ;
17211738 Expression callee = this .parseSubscripts (this .parseExprAtom (null ), innerStartPos , innerStartLoc , true );
1739+
1740+ if (isOnOptionalChain (false , callee ))
1741+ this .raise (callee , "An optional chain may not be used in a `new` expression." );
1742+
17221743 List <Expression > arguments ;
17231744 if (this .eat (TokenType .parenL ))
17241745 arguments = this .parseExprList (TokenType .parenR , this .options .ecmaVersion () >= 8 , false , null );
@@ -2159,9 +2180,12 @@ protected INode toAssignable(INode node, boolean isBinding) {
21592180 return new ParenthesizedExpression (node .getLoc (), (Expression ) this .toAssignable (expr , isBinding ));
21602181 }
21612182
2162- if (node instanceof MemberExpression )
2183+ if (node instanceof MemberExpression ) {
2184+ if (isOnOptionalChain (false , (MemberExpression )node ))
2185+ this .raise (node , "Invalid left-hand side in assignment" );
21632186 if (!isBinding )
21642187 return node ;
2188+ }
21652189
21662190 this .raise (node , "Assigning to rvalue" );
21672191 }
0 commit comments