diff --git a/javascript/extractor/src/com/semmle/jcorn/CustomParser.java b/javascript/extractor/src/com/semmle/jcorn/CustomParser.java index a77548ed21f..888a0aec639 100644 --- a/javascript/extractor/src/com/semmle/jcorn/CustomParser.java +++ b/javascript/extractor/src/com/semmle/jcorn/CustomParser.java @@ -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); diff --git a/javascript/extractor/src/com/semmle/jcorn/Parser.java b/javascript/extractor/src/com/semmle/jcorn/Parser.java index 013f8e57181..f0e3c0d004f 100644 --- a/javascript/extractor/src/com/semmle/jcorn/Parser.java +++ b/javascript/extractor/src/com/semmle/jcorn/Parser.java @@ -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; } diff --git a/javascript/extractor/src/com/semmle/js/ast/Chainable.java b/javascript/extractor/src/com/semmle/js/ast/Chainable.java index a6efe38d1eb..7d6e17d360b 100644 --- a/javascript/extractor/src/com/semmle/js/ast/Chainable.java +++ b/javascript/extractor/src/com/semmle/js/ast/Chainable.java @@ -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(); + } } diff --git a/javascript/extractor/src/com/semmle/js/parser/TypeScriptASTConverter.java b/javascript/extractor/src/com/semmle/js/parser/TypeScriptASTConverter.java index 33d131cbf00..1c39417e02e 100644 --- a/javascript/extractor/src/com/semmle/js/parser/TypeScriptASTConverter.java +++ b/javascript/extractor/src/com/semmle/js/parser/TypeScriptASTConverter.java @@ -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 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 { diff --git a/javascript/extractor/tests/ts/input/optionalChaining.ts b/javascript/extractor/tests/ts/input/optionalChaining.ts new file mode 100644 index 00000000000..5070e38ab24 --- /dev/null +++ b/javascript/extractor/tests/ts/input/optionalChaining.ts @@ -0,0 +1,3 @@ +base?.x.y; +base?.(x).y; +base?.[z].y; diff --git a/javascript/extractor/tests/ts/output/trap/optionalChaining.ts.trap b/javascript/extractor/tests/ts/output/trap/optionalChaining.ts.trap new file mode 100644 index 00000000000..0a414c9da57 --- /dev/null +++ b/javascript/extractor/tests/ts/output/trap/optionalChaining.ts.trap @@ -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") diff --git a/javascript/ql/test/library-tests/OptionalChaining/OptionalChainRoot.expected b/javascript/ql/test/library-tests/OptionalChaining/OptionalChainRoot.expected index 5ec86ee7462..1ff8c71d6db 100644 --- a/javascript/ql/test/library-tests/OptionalChaining/OptionalChainRoot.expected +++ b/javascript/ql/test/library-tests/OptionalChaining/OptionalChainRoot.expected @@ -1,7 +1,25 @@ +| short-circuiting-typescript.ts:3:5:3:18 | x?.(o1 = null) | short-circuiting-typescript.ts:3:5:3:18 | x?.(o1 = null) | +| short-circuiting-typescript.ts:7:5:7:18 | x?.[o2 = null] | short-circuiting-typescript.ts:7:5:7:18 | x?.[o2 = null] | +| short-circuiting-typescript.ts:12:5:12:31 | x?.[o3 ... = null) | short-circuiting-typescript.ts:12:5:12:18 | x?.[o3 = null] | +| short-circuiting-typescript.ts:12:5:12:31 | x?.[o3 ... = null) | short-circuiting-typescript.ts:12:5:12:31 | x?.[o3 ... = null) | | short-circuiting.js:3:5:3:18 | x?.(o1 = null) | short-circuiting.js:3:5:3:18 | x?.(o1 = null) | | short-circuiting.js:7:5:7:18 | x?.[o2 = null] | short-circuiting.js:7:5:7:18 | x?.[o2 = null] | | short-circuiting.js:12:5:12:31 | x?.[o3 ... = null) | short-circuiting.js:12:5:12:18 | x?.[o3 = null] | | short-circuiting.js:12:5:12:31 | x?.[o3 ... = null) | short-circuiting.js:12:5:12:31 | x?.[o3 ... = null) | +| tst-typescript.ts:2:1:2:6 | a?.b.c | tst-typescript.ts:2:1:2:4 | a?.b | +| tst-typescript.ts:3:1:3:6 | a.b?.c | tst-typescript.ts:3:1:3:6 | a.b?.c | +| tst-typescript.ts:4:1:4:7 | a?.b?.c | tst-typescript.ts:4:1:4:4 | a?.b | +| tst-typescript.ts:4:1:4:7 | a?.b?.c | tst-typescript.ts:4:1:4:7 | a?.b?.c | +| tst-typescript.ts:7:1:7:7 | f?.()() | tst-typescript.ts:7:1:7:5 | f?.() | +| tst-typescript.ts:8:1:8:7 | f()?.() | tst-typescript.ts:8:1:8:7 | f()?.() | +| tst-typescript.ts:9:1:9:9 | f?.()?.() | tst-typescript.ts:9:1:9:5 | f?.() | +| tst-typescript.ts:9:1:9:9 | f?.()?.() | tst-typescript.ts:9:1:9:9 | f?.()?.() | +| tst-typescript.ts:12:1:12:8 | a?.m().b | tst-typescript.ts:12:1:12:4 | a?.m | +| tst-typescript.ts:13:1:13:9 | a.m?.().b | tst-typescript.ts:13:1:13:7 | a.m?.() | +| tst-typescript.ts:14:1:14:8 | a.m()?.b | tst-typescript.ts:14:1:14:8 | a.m()?.b | +| tst-typescript.ts:15:1:15:11 | a?.m?.()?.b | tst-typescript.ts:15:1:15:4 | a?.m | +| tst-typescript.ts:15:1:15:11 | a?.m?.()?.b | tst-typescript.ts:15:1:15:8 | a?.m?.() | +| tst-typescript.ts:15:1:15:11 | a?.m?.()?.b | tst-typescript.ts:15:1:15:11 | a?.m?.()?.b | | tst.js:2:1:2:6 | a?.b.c | tst.js:2:1:2:4 | a?.b | | tst.js:3:1:3:6 | a.b?.c | tst.js:3:1:3:6 | a.b?.c | | tst.js:4:1:4:7 | a?.b?.c | tst.js:4:1:4:4 | a?.b | diff --git a/javascript/ql/test/library-tests/OptionalChaining/OptionalUse.expected b/javascript/ql/test/library-tests/OptionalChaining/OptionalUse.expected index 430d4d52299..8358bb0ecde 100644 --- a/javascript/ql/test/library-tests/OptionalChaining/OptionalUse.expected +++ b/javascript/ql/test/library-tests/OptionalChaining/OptionalUse.expected @@ -1,7 +1,25 @@ +| short-circuiting-typescript.ts:3:5:3:18 | x?.(o1 = null) | +| short-circuiting-typescript.ts:7:5:7:18 | x?.[o2 = null] | +| short-circuiting-typescript.ts:12:5:12:18 | x?.[o3 = null] | +| short-circuiting-typescript.ts:12:5:12:31 | x?.[o3 ... = null) | | short-circuiting.js:3:5:3:18 | x?.(o1 = null) | | short-circuiting.js:7:5:7:18 | x?.[o2 = null] | | short-circuiting.js:12:5:12:18 | x?.[o3 = null] | | short-circuiting.js:12:5:12:31 | x?.[o3 ... = null) | +| tst-typescript.ts:2:1:2:4 | a?.b | +| tst-typescript.ts:3:1:3:6 | a.b?.c | +| tst-typescript.ts:4:1:4:4 | a?.b | +| tst-typescript.ts:4:1:4:7 | a?.b?.c | +| tst-typescript.ts:7:1:7:5 | f?.() | +| tst-typescript.ts:8:1:8:7 | f()?.() | +| tst-typescript.ts:9:1:9:5 | f?.() | +| tst-typescript.ts:9:1:9:9 | f?.()?.() | +| tst-typescript.ts:12:1:12:4 | a?.m | +| tst-typescript.ts:13:1:13:7 | a.m?.() | +| tst-typescript.ts:14:1:14:8 | a.m()?.b | +| tst-typescript.ts:15:1:15:4 | a?.m | +| tst-typescript.ts:15:1:15:8 | a?.m?.() | +| tst-typescript.ts:15:1:15:11 | a?.m?.()?.b | | tst.js:2:1:2:4 | a?.b | | tst.js:3:1:3:6 | a.b?.c | | tst.js:4:1:4:4 | a?.b | diff --git a/javascript/ql/test/library-tests/OptionalChaining/ShortCircuiting.expected b/javascript/ql/test/library-tests/OptionalChaining/ShortCircuiting.expected index d61dccc8c5a..6711c704828 100644 --- a/javascript/ql/test/library-tests/OptionalChaining/ShortCircuiting.expected +++ b/javascript/ql/test/library-tests/OptionalChaining/ShortCircuiting.expected @@ -1,3 +1,11 @@ +| short-circuiting-typescript.ts:4:10:4:11 | o1 | file://:0:0:0:0 | null | +| short-circuiting-typescript.ts:4:10:4:11 | o1 | short-circuiting-typescript.ts:2:14:2:15 | object literal | +| short-circuiting-typescript.ts:8:10:8:11 | o2 | file://:0:0:0:0 | null | +| short-circuiting-typescript.ts:8:10:8:11 | o2 | short-circuiting-typescript.ts:6:14:6:15 | object literal | +| short-circuiting-typescript.ts:13:10:13:11 | o3 | file://:0:0:0:0 | null | +| short-circuiting-typescript.ts:13:10:13:11 | o3 | short-circuiting-typescript.ts:10:14:10:15 | object literal | +| short-circuiting-typescript.ts:14:10:14:11 | o4 | file://:0:0:0:0 | null | +| short-circuiting-typescript.ts:14:10:14:11 | o4 | short-circuiting-typescript.ts:11:14:11:15 | object literal | | short-circuiting.js:4:10:4:11 | o1 | file://:0:0:0:0 | null | | short-circuiting.js:4:10:4:11 | o1 | short-circuiting.js:2:14:2:15 | object literal | | short-circuiting.js:8:10:8:11 | o2 | file://:0:0:0:0 | null | diff --git a/javascript/ql/test/library-tests/OptionalChaining/short-circuiting-typescript.ts b/javascript/ql/test/library-tests/OptionalChaining/short-circuiting-typescript.ts new file mode 100644 index 00000000000..7367e64dd4c --- /dev/null +++ b/javascript/ql/test/library-tests/OptionalChaining/short-circuiting-typescript.ts @@ -0,0 +1,15 @@ +(function() { + var o1 = {}; + x?.(o1 = null); + DUMP(o1); + + var o2 = {}; + x?.[o2 = null]; + DUMP(o2); + + var o3 = {}, + o4 = {}; + x?.[o3 = null]?.(o4 = null); + DUMP(o3); + DUMP(o4); +}); diff --git a/javascript/ql/test/library-tests/OptionalChaining/tst-typescript.ts b/javascript/ql/test/library-tests/OptionalChaining/tst-typescript.ts new file mode 100644 index 00000000000..39389024d6d --- /dev/null +++ b/javascript/ql/test/library-tests/OptionalChaining/tst-typescript.ts @@ -0,0 +1,15 @@ +a.b.c; +a?.b.c; +a.b?.c; +a?.b?.c; + +f()(); +f?.()(); +f()?.(); +f?.()?.(); + +a.m().b; +a?.m().b; +a.m?.().b; +a.m()?.b; +a?.m?.()?.b;