mirror of
https://github.com/github/codeql.git
synced 2026-01-06 03:00:24 +01:00
TS: Support optional chaining
This commit is contained in:
@@ -7,6 +7,7 @@ import com.semmle.js.ast.AssignmentExpression;
|
||||
import com.semmle.js.ast.BlockStatement;
|
||||
import com.semmle.js.ast.CallExpression;
|
||||
import com.semmle.js.ast.CatchClause;
|
||||
import com.semmle.js.ast.Chainable;
|
||||
import com.semmle.js.ast.ClassExpression;
|
||||
import com.semmle.js.ast.ComprehensionBlock;
|
||||
import com.semmle.js.ast.ComprehensionExpression;
|
||||
@@ -470,7 +471,8 @@ public class CustomParser extends FlowParser {
|
||||
|
||||
Expression property = this.parsePropertyIdentifierOrIdentifier();
|
||||
MemberExpression node =
|
||||
new MemberExpression(start, base, property, false, false, isOnOptionalChain(false, base));
|
||||
new MemberExpression(
|
||||
start, base, property, false, false, Chainable.isOnOptionalChain(false, base));
|
||||
return Pair.make(this.finishNode(node), true);
|
||||
} else if (this.eat(doubleDot)) {
|
||||
SourceLocation start = new SourceLocation(startLoc);
|
||||
|
||||
@@ -1520,10 +1520,6 @@ public class Parser {
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean isOnOptionalChain(boolean optional, Expression base) {
|
||||
return optional || base instanceof Chainable && ((Chainable) base).isOnOptionalChain();
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a single subscript {@code s}; if more subscripts could follow, return {@code Pair.make(s,
|
||||
* true}, otherwise return {@code Pair.make(s, false)}.
|
||||
@@ -1544,7 +1540,7 @@ public class Parser {
|
||||
this.parseExpression(false, null),
|
||||
true,
|
||||
optional,
|
||||
isOnOptionalChain(optional, base));
|
||||
Chainable.isOnOptionalChain(optional, base));
|
||||
this.expect(TokenType.bracketR);
|
||||
return Pair.make(this.finishNode(node), true);
|
||||
} else if (!noCalls && this.eat(TokenType.parenL)) {
|
||||
@@ -1572,10 +1568,10 @@ public class Parser {
|
||||
new ArrayList<>(),
|
||||
exprList,
|
||||
optional,
|
||||
isOnOptionalChain(optional, base));
|
||||
Chainable.isOnOptionalChain(optional, base));
|
||||
return Pair.make(this.finishNode(node), true);
|
||||
} else if (this.type == TokenType.backQuote) {
|
||||
if (isOnOptionalChain(optional, base)) {
|
||||
if (Chainable.isOnOptionalChain(optional, base)) {
|
||||
this.raise(base, "An optional chain may not be used in a tagged template expression.");
|
||||
}
|
||||
TaggedTemplateExpression node =
|
||||
@@ -1590,7 +1586,7 @@ public class Parser {
|
||||
this.parseIdent(true),
|
||||
false,
|
||||
optional,
|
||||
isOnOptionalChain(optional, base));
|
||||
Chainable.isOnOptionalChain(optional, base));
|
||||
return Pair.make(this.finishNode(node), true);
|
||||
} else {
|
||||
return Pair.make(base, false);
|
||||
@@ -1832,7 +1828,7 @@ public class Parser {
|
||||
Expression callee =
|
||||
this.parseSubscripts(this.parseExprAtom(null), innerStartPos, innerStartLoc, true);
|
||||
|
||||
if (isOnOptionalChain(false, callee))
|
||||
if (Chainable.isOnOptionalChain(false, callee))
|
||||
this.raise(callee, "An optional chain may not be used in a `new` expression.");
|
||||
|
||||
return parseNewArguments(startLoc, callee);
|
||||
@@ -2314,7 +2310,7 @@ public class Parser {
|
||||
}
|
||||
|
||||
if (node instanceof MemberExpression) {
|
||||
if (isOnOptionalChain(false, (MemberExpression) node))
|
||||
if (Chainable.isOnOptionalChain(false, (MemberExpression) node))
|
||||
this.raise(node, "Invalid left-hand side in assignment");
|
||||
if (!isBinding) return node;
|
||||
}
|
||||
|
||||
@@ -7,4 +7,14 @@ public interface Chainable {
|
||||
|
||||
/** Is this on an optional chain? */
|
||||
abstract boolean isOnOptionalChain();
|
||||
|
||||
/**
|
||||
* Returns true if a chainable node is on an optional chain.
|
||||
*
|
||||
* @param optional true if the node in question is itself optional (has the ?. token)
|
||||
* @param base the calle or base of the optional access
|
||||
*/
|
||||
public static boolean isOnOptionalChain(boolean optional, Expression base) {
|
||||
return optional || base instanceof Chainable && ((Chainable) base).isOnOptionalChain();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import com.semmle.js.ast.BlockStatement;
|
||||
import com.semmle.js.ast.BreakStatement;
|
||||
import com.semmle.js.ast.CallExpression;
|
||||
import com.semmle.js.ast.CatchClause;
|
||||
import com.semmle.js.ast.Chainable;
|
||||
import com.semmle.js.ast.ClassBody;
|
||||
import com.semmle.js.ast.ClassDeclaration;
|
||||
import com.semmle.js.ast.ClassExpression;
|
||||
@@ -877,7 +878,10 @@ public class TypeScriptASTConverter {
|
||||
}
|
||||
Expression callee = convertChild(node, "expression");
|
||||
List<ITypeExpression> typeArguments = convertChildrenAsTypes(node, "typeArguments");
|
||||
CallExpression call = new CallExpression(loc, callee, typeArguments, arguments, false, false);
|
||||
boolean optional = node.has("questionDotToken");
|
||||
boolean onOptionalChain = Chainable.isOnOptionalChain(optional, callee);
|
||||
CallExpression call =
|
||||
new CallExpression(loc, callee, typeArguments, arguments, optional, onOptionalChain);
|
||||
attachResolvedSignature(call, node);
|
||||
return call;
|
||||
}
|
||||
@@ -1108,7 +1112,9 @@ public class TypeScriptASTConverter {
|
||||
throws ParseError {
|
||||
Expression object = convertChild(node, "expression");
|
||||
Expression property = convertChild(node, "argumentExpression");
|
||||
return new MemberExpression(loc, object, property, true, false, false);
|
||||
boolean optional = node.has("questionDotToken");
|
||||
boolean onOptionalChain = Chainable.isOnOptionalChain(optional, object);
|
||||
return new MemberExpression(loc, object, property, true, optional, onOptionalChain);
|
||||
}
|
||||
|
||||
private Node convertEmptyStatement(SourceLocation loc) {
|
||||
@@ -1584,10 +1590,14 @@ public class TypeScriptASTConverter {
|
||||
|
||||
private Node convertMetaProperty(JsonObject node, SourceLocation loc) throws ParseError {
|
||||
Position metaStart = loc.getStart();
|
||||
String keywordKind = syntaxKinds.get(node.getAsJsonPrimitive("keywordToken").getAsInt() + "").getAsString();
|
||||
String keywordKind =
|
||||
syntaxKinds.get(node.getAsJsonPrimitive("keywordToken").getAsInt() + "").getAsString();
|
||||
String identifier = keywordKind.equals("ImportKeyword") ? "import" : "new";
|
||||
Position metaEnd =
|
||||
new Position(metaStart.getLine(), metaStart.getColumn() + identifier.length(), metaStart.getOffset() + identifier.length());
|
||||
new Position(
|
||||
metaStart.getLine(),
|
||||
metaStart.getColumn() + identifier.length(),
|
||||
metaStart.getOffset() + identifier.length());
|
||||
SourceLocation metaLoc = new SourceLocation(identifier, metaStart, metaEnd);
|
||||
Identifier meta = new Identifier(metaLoc, identifier);
|
||||
return new MetaProperty(loc, meta, convertChild(node, "name"));
|
||||
@@ -1967,8 +1977,11 @@ public class TypeScriptASTConverter {
|
||||
|
||||
private Node convertPropertyAccessExpression(JsonObject node, SourceLocation loc)
|
||||
throws ParseError {
|
||||
Expression base = convertChild(node, "expression");
|
||||
boolean optional = node.has("questionDotToken");
|
||||
boolean onOptionalChain = Chainable.isOnOptionalChain(optional, base);
|
||||
return new MemberExpression(
|
||||
loc, convertChild(node, "expression"), convertChild(node, "name"), false, false, false);
|
||||
loc, base, convertChild(node, "name"), false, optional, onOptionalChain);
|
||||
}
|
||||
|
||||
private Node convertPropertyAssignment(JsonObject node, SourceLocation loc) throws ParseError {
|
||||
|
||||
Reference in New Issue
Block a user