TS: Support optional chaining

This commit is contained in:
Asger F
2019-10-30 13:21:07 +00:00
parent f76006e490
commit 869fe4558f
11 changed files with 417 additions and 16 deletions

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -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();
}
}

View File

@@ -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 {

View File

@@ -0,0 +1,3 @@
base?.x.y;
base?.(x).y;
base?.[z].y;

View File

@@ -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")

View File

@@ -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 |

View File

@@ -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 |

View File

@@ -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 |

View File

@@ -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);
});

View File

@@ -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;