mirror of
https://github.com/github/codeql.git
synced 2026-05-01 11:45:14 +02: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 {
|
||||
|
||||
3
javascript/extractor/tests/ts/input/optionalChaining.ts
Normal file
3
javascript/extractor/tests/ts/input/optionalChaining.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
base?.x.y;
|
||||
base?.(x).y;
|
||||
base?.[z].y;
|
||||
@@ -0,0 +1,303 @@
|
||||
#10000=@"/optionalChaining.ts;sourcefile"
|
||||
files(#10000,"/optionalChaining.ts","optionalChaining","ts",0)
|
||||
#10001=@"/;folder"
|
||||
folders(#10001,"/","")
|
||||
containerparent(#10001,#10000)
|
||||
#10002=@"loc,{#10000},0,0,0,0"
|
||||
locations_default(#10002,#10000,0,0,0,0)
|
||||
hasLocation(#10000,#10002)
|
||||
#20000=@"global_scope"
|
||||
scopes(#20000,0)
|
||||
#20001=@"script;{#10000},1,1"
|
||||
#20002=*
|
||||
lines(#20002,#20001,"base?.x.y;","
|
||||
")
|
||||
#20003=@"loc,{#10000},1,1,1,10"
|
||||
locations_default(#20003,#10000,1,1,1,10)
|
||||
hasLocation(#20002,#20003)
|
||||
#20004=*
|
||||
lines(#20004,#20001,"base?.(x).y;","
|
||||
")
|
||||
#20005=@"loc,{#10000},2,1,2,12"
|
||||
locations_default(#20005,#10000,2,1,2,12)
|
||||
hasLocation(#20004,#20005)
|
||||
#20006=*
|
||||
lines(#20006,#20001,"base?.[z].y;","
|
||||
")
|
||||
#20007=@"loc,{#10000},3,1,3,12"
|
||||
locations_default(#20007,#10000,3,1,3,12)
|
||||
hasLocation(#20006,#20007)
|
||||
numlines(#20001,3,3,0)
|
||||
#20008=*
|
||||
tokeninfo(#20008,6,#20001,0,"base")
|
||||
#20009=@"loc,{#10000},1,1,1,4"
|
||||
locations_default(#20009,#10000,1,1,1,4)
|
||||
hasLocation(#20008,#20009)
|
||||
#20010=*
|
||||
tokeninfo(#20010,8,#20001,1,"?.")
|
||||
#20011=@"loc,{#10000},1,5,1,6"
|
||||
locations_default(#20011,#10000,1,5,1,6)
|
||||
hasLocation(#20010,#20011)
|
||||
#20012=*
|
||||
tokeninfo(#20012,6,#20001,2,"x")
|
||||
#20013=@"loc,{#10000},1,7,1,7"
|
||||
locations_default(#20013,#10000,1,7,1,7)
|
||||
hasLocation(#20012,#20013)
|
||||
#20014=*
|
||||
tokeninfo(#20014,8,#20001,3,".")
|
||||
#20015=@"loc,{#10000},1,8,1,8"
|
||||
locations_default(#20015,#10000,1,8,1,8)
|
||||
hasLocation(#20014,#20015)
|
||||
#20016=*
|
||||
tokeninfo(#20016,6,#20001,4,"y")
|
||||
#20017=@"loc,{#10000},1,9,1,9"
|
||||
locations_default(#20017,#10000,1,9,1,9)
|
||||
hasLocation(#20016,#20017)
|
||||
#20018=*
|
||||
tokeninfo(#20018,8,#20001,5,";")
|
||||
#20019=@"loc,{#10000},1,10,1,10"
|
||||
locations_default(#20019,#10000,1,10,1,10)
|
||||
hasLocation(#20018,#20019)
|
||||
#20020=*
|
||||
tokeninfo(#20020,6,#20001,6,"base")
|
||||
#20021=@"loc,{#10000},2,1,2,4"
|
||||
locations_default(#20021,#10000,2,1,2,4)
|
||||
hasLocation(#20020,#20021)
|
||||
#20022=*
|
||||
tokeninfo(#20022,8,#20001,7,"?.")
|
||||
#20023=@"loc,{#10000},2,5,2,6"
|
||||
locations_default(#20023,#10000,2,5,2,6)
|
||||
hasLocation(#20022,#20023)
|
||||
#20024=*
|
||||
tokeninfo(#20024,8,#20001,8,"(")
|
||||
#20025=@"loc,{#10000},2,7,2,7"
|
||||
locations_default(#20025,#10000,2,7,2,7)
|
||||
hasLocation(#20024,#20025)
|
||||
#20026=*
|
||||
tokeninfo(#20026,6,#20001,9,"x")
|
||||
#20027=@"loc,{#10000},2,8,2,8"
|
||||
locations_default(#20027,#10000,2,8,2,8)
|
||||
hasLocation(#20026,#20027)
|
||||
#20028=*
|
||||
tokeninfo(#20028,8,#20001,10,")")
|
||||
#20029=@"loc,{#10000},2,9,2,9"
|
||||
locations_default(#20029,#10000,2,9,2,9)
|
||||
hasLocation(#20028,#20029)
|
||||
#20030=*
|
||||
tokeninfo(#20030,8,#20001,11,".")
|
||||
#20031=@"loc,{#10000},2,10,2,10"
|
||||
locations_default(#20031,#10000,2,10,2,10)
|
||||
hasLocation(#20030,#20031)
|
||||
#20032=*
|
||||
tokeninfo(#20032,6,#20001,12,"y")
|
||||
#20033=@"loc,{#10000},2,11,2,11"
|
||||
locations_default(#20033,#10000,2,11,2,11)
|
||||
hasLocation(#20032,#20033)
|
||||
#20034=*
|
||||
tokeninfo(#20034,8,#20001,13,";")
|
||||
#20035=@"loc,{#10000},2,12,2,12"
|
||||
locations_default(#20035,#10000,2,12,2,12)
|
||||
hasLocation(#20034,#20035)
|
||||
#20036=*
|
||||
tokeninfo(#20036,6,#20001,14,"base")
|
||||
#20037=@"loc,{#10000},3,1,3,4"
|
||||
locations_default(#20037,#10000,3,1,3,4)
|
||||
hasLocation(#20036,#20037)
|
||||
#20038=*
|
||||
tokeninfo(#20038,8,#20001,15,"?.")
|
||||
#20039=@"loc,{#10000},3,5,3,6"
|
||||
locations_default(#20039,#10000,3,5,3,6)
|
||||
hasLocation(#20038,#20039)
|
||||
#20040=*
|
||||
tokeninfo(#20040,8,#20001,16,"[")
|
||||
#20041=@"loc,{#10000},3,7,3,7"
|
||||
locations_default(#20041,#10000,3,7,3,7)
|
||||
hasLocation(#20040,#20041)
|
||||
#20042=*
|
||||
tokeninfo(#20042,6,#20001,17,"z")
|
||||
#20043=@"loc,{#10000},3,8,3,8"
|
||||
locations_default(#20043,#10000,3,8,3,8)
|
||||
hasLocation(#20042,#20043)
|
||||
#20044=*
|
||||
tokeninfo(#20044,8,#20001,18,"]")
|
||||
#20045=@"loc,{#10000},3,9,3,9"
|
||||
locations_default(#20045,#10000,3,9,3,9)
|
||||
hasLocation(#20044,#20045)
|
||||
#20046=*
|
||||
tokeninfo(#20046,8,#20001,19,".")
|
||||
#20047=@"loc,{#10000},3,10,3,10"
|
||||
locations_default(#20047,#10000,3,10,3,10)
|
||||
hasLocation(#20046,#20047)
|
||||
#20048=*
|
||||
tokeninfo(#20048,6,#20001,20,"y")
|
||||
#20049=@"loc,{#10000},3,11,3,11"
|
||||
locations_default(#20049,#10000,3,11,3,11)
|
||||
hasLocation(#20048,#20049)
|
||||
#20050=*
|
||||
tokeninfo(#20050,8,#20001,21,";")
|
||||
#20051=@"loc,{#10000},3,12,3,12"
|
||||
locations_default(#20051,#10000,3,12,3,12)
|
||||
hasLocation(#20050,#20051)
|
||||
#20052=*
|
||||
tokeninfo(#20052,0,#20001,22,"")
|
||||
#20053=@"loc,{#10000},4,1,4,0"
|
||||
locations_default(#20053,#10000,4,1,4,0)
|
||||
hasLocation(#20052,#20053)
|
||||
toplevels(#20001,0)
|
||||
#20054=@"loc,{#10000},1,1,4,0"
|
||||
locations_default(#20054,#10000,1,1,4,0)
|
||||
hasLocation(#20001,#20054)
|
||||
#20055=*
|
||||
stmts(#20055,2,#20001,0,"base?.x.y;")
|
||||
hasLocation(#20055,#20003)
|
||||
stmtContainers(#20055,#20001)
|
||||
#20056=*
|
||||
exprs(#20056,14,#20055,0,"base?.x.y")
|
||||
#20057=@"loc,{#10000},1,1,1,9"
|
||||
locations_default(#20057,#10000,1,1,1,9)
|
||||
hasLocation(#20056,#20057)
|
||||
enclosingStmt(#20056,#20055)
|
||||
exprContainers(#20056,#20001)
|
||||
#20058=*
|
||||
exprs(#20058,14,#20056,0,"base?.x")
|
||||
#20059=@"loc,{#10000},1,1,1,7"
|
||||
locations_default(#20059,#10000,1,1,1,7)
|
||||
hasLocation(#20058,#20059)
|
||||
enclosingStmt(#20058,#20055)
|
||||
exprContainers(#20058,#20001)
|
||||
#20060=*
|
||||
exprs(#20060,79,#20058,0,"base")
|
||||
hasLocation(#20060,#20009)
|
||||
enclosingStmt(#20060,#20055)
|
||||
exprContainers(#20060,#20001)
|
||||
literals("base","base",#20060)
|
||||
#20061=@"var;{base};{#20000}"
|
||||
variables(#20061,"base",#20000)
|
||||
bind(#20060,#20061)
|
||||
#20062=*
|
||||
exprs(#20062,0,#20058,1,"x")
|
||||
hasLocation(#20062,#20013)
|
||||
enclosingStmt(#20062,#20055)
|
||||
exprContainers(#20062,#20001)
|
||||
literals("x","x",#20062)
|
||||
isOptionalChaining(#20058)
|
||||
#20063=*
|
||||
exprs(#20063,0,#20056,1,"y")
|
||||
hasLocation(#20063,#20017)
|
||||
enclosingStmt(#20063,#20055)
|
||||
exprContainers(#20063,#20001)
|
||||
literals("y","y",#20063)
|
||||
#20064=*
|
||||
stmts(#20064,2,#20001,1,"base?.(x).y;")
|
||||
hasLocation(#20064,#20005)
|
||||
stmtContainers(#20064,#20001)
|
||||
#20065=*
|
||||
exprs(#20065,14,#20064,0,"base?.(x).y")
|
||||
#20066=@"loc,{#10000},2,1,2,11"
|
||||
locations_default(#20066,#10000,2,1,2,11)
|
||||
hasLocation(#20065,#20066)
|
||||
enclosingStmt(#20065,#20064)
|
||||
exprContainers(#20065,#20001)
|
||||
#20067=*
|
||||
exprs(#20067,13,#20065,0,"base?.(x)")
|
||||
#20068=@"loc,{#10000},2,1,2,9"
|
||||
locations_default(#20068,#10000,2,1,2,9)
|
||||
hasLocation(#20067,#20068)
|
||||
enclosingStmt(#20067,#20064)
|
||||
exprContainers(#20067,#20001)
|
||||
#20069=*
|
||||
exprs(#20069,79,#20067,-1,"base")
|
||||
hasLocation(#20069,#20021)
|
||||
enclosingStmt(#20069,#20064)
|
||||
exprContainers(#20069,#20001)
|
||||
literals("base","base",#20069)
|
||||
bind(#20069,#20061)
|
||||
#20070=*
|
||||
exprs(#20070,79,#20067,0,"x")
|
||||
hasLocation(#20070,#20027)
|
||||
enclosingStmt(#20070,#20064)
|
||||
exprContainers(#20070,#20001)
|
||||
literals("x","x",#20070)
|
||||
#20071=@"var;{x};{#20000}"
|
||||
variables(#20071,"x",#20000)
|
||||
bind(#20070,#20071)
|
||||
isOptionalChaining(#20067)
|
||||
#20072=*
|
||||
exprs(#20072,0,#20065,1,"y")
|
||||
hasLocation(#20072,#20033)
|
||||
enclosingStmt(#20072,#20064)
|
||||
exprContainers(#20072,#20001)
|
||||
literals("y","y",#20072)
|
||||
#20073=*
|
||||
stmts(#20073,2,#20001,2,"base?.[z].y;")
|
||||
hasLocation(#20073,#20007)
|
||||
stmtContainers(#20073,#20001)
|
||||
#20074=*
|
||||
exprs(#20074,14,#20073,0,"base?.[z].y")
|
||||
#20075=@"loc,{#10000},3,1,3,11"
|
||||
locations_default(#20075,#10000,3,1,3,11)
|
||||
hasLocation(#20074,#20075)
|
||||
enclosingStmt(#20074,#20073)
|
||||
exprContainers(#20074,#20001)
|
||||
#20076=*
|
||||
exprs(#20076,15,#20074,0,"base?.[z]")
|
||||
#20077=@"loc,{#10000},3,1,3,9"
|
||||
locations_default(#20077,#10000,3,1,3,9)
|
||||
hasLocation(#20076,#20077)
|
||||
enclosingStmt(#20076,#20073)
|
||||
exprContainers(#20076,#20001)
|
||||
#20078=*
|
||||
exprs(#20078,79,#20076,0,"base")
|
||||
hasLocation(#20078,#20037)
|
||||
enclosingStmt(#20078,#20073)
|
||||
exprContainers(#20078,#20001)
|
||||
literals("base","base",#20078)
|
||||
bind(#20078,#20061)
|
||||
#20079=*
|
||||
exprs(#20079,79,#20076,1,"z")
|
||||
hasLocation(#20079,#20043)
|
||||
enclosingStmt(#20079,#20073)
|
||||
exprContainers(#20079,#20001)
|
||||
literals("z","z",#20079)
|
||||
#20080=@"var;{z};{#20000}"
|
||||
variables(#20080,"z",#20000)
|
||||
bind(#20079,#20080)
|
||||
isOptionalChaining(#20076)
|
||||
#20081=*
|
||||
exprs(#20081,0,#20074,1,"y")
|
||||
hasLocation(#20081,#20049)
|
||||
enclosingStmt(#20081,#20073)
|
||||
exprContainers(#20081,#20001)
|
||||
literals("y","y",#20081)
|
||||
#20082=*
|
||||
entry_cfg_node(#20082,#20001)
|
||||
#20083=@"loc,{#10000},1,1,1,0"
|
||||
locations_default(#20083,#10000,1,1,1,0)
|
||||
hasLocation(#20082,#20083)
|
||||
#20084=*
|
||||
exit_cfg_node(#20084,#20001)
|
||||
hasLocation(#20084,#20053)
|
||||
successor(#20073,#20078)
|
||||
successor(#20081,#20074)
|
||||
successor(#20079,#20076)
|
||||
successor(#20078,#20079)
|
||||
successor(#20076,#20081)
|
||||
successor(#20078,#20084)
|
||||
successor(#20074,#20084)
|
||||
successor(#20064,#20069)
|
||||
successor(#20072,#20065)
|
||||
successor(#20070,#20067)
|
||||
successor(#20069,#20070)
|
||||
successor(#20067,#20072)
|
||||
successor(#20069,#20073)
|
||||
successor(#20065,#20073)
|
||||
successor(#20055,#20060)
|
||||
successor(#20063,#20056)
|
||||
successor(#20062,#20058)
|
||||
successor(#20060,#20062)
|
||||
successor(#20058,#20063)
|
||||
successor(#20060,#20064)
|
||||
successor(#20056,#20064)
|
||||
successor(#20082,#20055)
|
||||
numlines(#10000,3,3,0)
|
||||
filetype(#10000,"typescript")
|
||||
Reference in New Issue
Block a user