JS(extractor): support nullish coalescing operators

This commit is contained in:
Esben Sparre Andreasen
2018-11-26 09:45:02 +01:00
parent 8d7ace25bf
commit a2a798e59c
13 changed files with 2726 additions and 8 deletions

View File

@@ -0,0 +1,21 @@
[[ condition: enterprise-only ]]
# Improvements to JavaScript analysis
> NOTES
>
> Please describe your changes in terms that are suitable for
> customers to read. These notes will have only minor tidying up
> before they are published as part of the release notes.
>
> This file is written for lgtm users and should contain *only*
> notes about changes that affect lgtm enterprise users. Add
> any other customer-facing changes to the `studio-java.md`
> file.
>
## General improvements
## Changes to code extraction
* The extractor now supports [Nullish Coalescing](https://github.com/tc39/proposal-nullish-coalescing) expressions.

View File

@@ -508,8 +508,12 @@ public class Parser {
private Token readToken_question() { // '?'
int next = charAt(this.pos + 1);
int next2 = charAt(this.pos + 2);
if (this.options.esnext() && next == '.' && !('0' <= next2 && next2 <= '9')) // '?.', but not '?.X' where X is a digit
return this.finishOp(TokenType.questiondot, 2);
if (this.options.esnext()) {
if (next == '.' && !('0' <= next2 && next2 <= '9')) // '?.', but not '?.X' where X is a digit
return this.finishOp(TokenType.questiondot, 2);
if (next == '?') // '??'
return this.finishOp(TokenType.questionquestion, 2);
}
return this.finishOp(TokenType.question, 1);
}

View File

@@ -123,6 +123,7 @@ public class TokenType {
}
},
prefix = new TokenType(new Properties("prefix").beforeExpr().prefix().startsExpr()),
questionquestion = new TokenType(binop("??", 1)),
logicalOR = new TokenType(binop("||", 1)),
logicalAND = new TokenType(binop("&&", 2)),
bitwiseOR = new TokenType(binop("|", 3)),

View File

@@ -331,9 +331,11 @@ public class CFGExtractor {
return nd.getKey().accept(this, v);
}
// for binary operators, the operands come first (but not for LogicalExpression, see above)
// for binary operators, the operands come first (but not for short-circuiting expressions), see above)
@Override
public Node visit(BinaryExpression nd, Void v) {
if ("??".equals(nd.getOperator()))
return nd;
return nd.getLeft().accept(this, v);
}
@@ -1583,8 +1585,16 @@ public class CFGExtractor {
@Override
public Void visit(BinaryExpression nd, SuccessorInfo i) {
this.seq(nd.getLeft(), nd.getRight(), nd);
succ(nd, i.getGuardedSuccessors(nd));
if ("??".equals(nd.getOperator())) {
// the nullish coalescing operator is short-circuiting, but we do not add guards for it
succ(nd, First.of(nd.getLeft()));
Object leftSucc = union(First.of(nd.getRight()), i.getAllSuccessors()); // short-circuiting happens with both truthy and falsy values
visit(nd.getLeft(), leftSucc, null);
nd.getRight().accept(this, i);
} else {
this.seq(nd.getLeft(), nd.getRight(), nd);
succ(nd, i.getGuardedSuccessors(nd));
}
return null;
}

View File

@@ -72,6 +72,7 @@ public class ExprKinds {
binOpKinds.put("&=", 58);
binOpKinds.put("**", 87);
binOpKinds.put("**=", 88);
binOpKinds.put("??", 107);
}
private static final Map<String, Integer> unOpKinds = new LinkedHashMap<String, Integer>();

View File

@@ -41,7 +41,7 @@ public class Main {
* such a way that it may produce different tuples for the same file under the same
* {@link ExtractorConfig}.
*/
public static final String EXTRACTOR_VERSION = "2018-11-22_a";
public static final String EXTRACTOR_VERSION = "2018-11-23";
public static final Pattern NEWLINE = Pattern.compile("\n");

View File

@@ -0,0 +1,7 @@
x1 ?? y1;
x2 || y2 ?? z2;
x3 ?? y3 || z3;
x4 && y4 ?? z4;
x5 ?? y5 && z5;

View File

@@ -0,0 +1,494 @@
#10000=@"/nullish-coalescing.js;sourcefile"
files(#10000,"/nullish-coalescing.js","nullish-coalescing","js",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"
toplevels(#20001,0)
#20002=@"loc,{#10000},1,1,7,15"
locations_default(#20002,#10000,1,1,7,15)
hasLocation(#20001,#20002)
#20003=*
stmts(#20003,2,#20001,0,"x1 ?? y1;")
#20004=@"loc,{#10000},1,1,1,9"
locations_default(#20004,#10000,1,1,1,9)
hasLocation(#20003,#20004)
stmtContainers(#20003,#20001)
#20005=*
exprs(#20005,107,#20003,0,"x1 ?? y1")
#20006=@"loc,{#10000},1,1,1,8"
locations_default(#20006,#10000,1,1,1,8)
hasLocation(#20005,#20006)
enclosingStmt(#20005,#20003)
exprContainers(#20005,#20001)
#20007=*
exprs(#20007,79,#20005,0,"x1")
#20008=@"loc,{#10000},1,1,1,2"
locations_default(#20008,#10000,1,1,1,2)
hasLocation(#20007,#20008)
enclosingStmt(#20007,#20003)
exprContainers(#20007,#20001)
literals("x1","x1",#20007)
#20009=@"var;{x1};{#20000}"
variables(#20009,"x1",#20000)
bind(#20007,#20009)
#20010=*
exprs(#20010,79,#20005,1,"y1")
#20011=@"loc,{#10000},1,7,1,8"
locations_default(#20011,#10000,1,7,1,8)
hasLocation(#20010,#20011)
enclosingStmt(#20010,#20003)
exprContainers(#20010,#20001)
literals("y1","y1",#20010)
#20012=@"var;{y1};{#20000}"
variables(#20012,"y1",#20000)
bind(#20010,#20012)
#20013=*
stmts(#20013,2,#20001,1,"x2 || y2 ?? z2;")
#20014=@"loc,{#10000},3,1,3,15"
locations_default(#20014,#10000,3,1,3,15)
hasLocation(#20013,#20014)
stmtContainers(#20013,#20001)
#20015=*
exprs(#20015,107,#20013,0,"x2 || y2 ?? z2")
#20016=@"loc,{#10000},3,1,3,14"
locations_default(#20016,#10000,3,1,3,14)
hasLocation(#20015,#20016)
enclosingStmt(#20015,#20013)
exprContainers(#20015,#20001)
#20017=*
exprs(#20017,45,#20015,0,"x2 || y2")
#20018=@"loc,{#10000},3,1,3,8"
locations_default(#20018,#10000,3,1,3,8)
hasLocation(#20017,#20018)
enclosingStmt(#20017,#20013)
exprContainers(#20017,#20001)
#20019=*
exprs(#20019,79,#20017,0,"x2")
#20020=@"loc,{#10000},3,1,3,2"
locations_default(#20020,#10000,3,1,3,2)
hasLocation(#20019,#20020)
enclosingStmt(#20019,#20013)
exprContainers(#20019,#20001)
literals("x2","x2",#20019)
#20021=@"var;{x2};{#20000}"
variables(#20021,"x2",#20000)
bind(#20019,#20021)
#20022=*
exprs(#20022,79,#20017,1,"y2")
#20023=@"loc,{#10000},3,7,3,8"
locations_default(#20023,#10000,3,7,3,8)
hasLocation(#20022,#20023)
enclosingStmt(#20022,#20013)
exprContainers(#20022,#20001)
literals("y2","y2",#20022)
#20024=@"var;{y2};{#20000}"
variables(#20024,"y2",#20000)
bind(#20022,#20024)
#20025=*
exprs(#20025,79,#20015,1,"z2")
#20026=@"loc,{#10000},3,13,3,14"
locations_default(#20026,#10000,3,13,3,14)
hasLocation(#20025,#20026)
enclosingStmt(#20025,#20013)
exprContainers(#20025,#20001)
literals("z2","z2",#20025)
#20027=@"var;{z2};{#20000}"
variables(#20027,"z2",#20000)
bind(#20025,#20027)
#20028=*
stmts(#20028,2,#20001,2,"x3 ?? y3 || z3;")
#20029=@"loc,{#10000},4,1,4,15"
locations_default(#20029,#10000,4,1,4,15)
hasLocation(#20028,#20029)
stmtContainers(#20028,#20001)
#20030=*
exprs(#20030,45,#20028,0,"x3 ?? y3 || z3")
#20031=@"loc,{#10000},4,1,4,14"
locations_default(#20031,#10000,4,1,4,14)
hasLocation(#20030,#20031)
enclosingStmt(#20030,#20028)
exprContainers(#20030,#20001)
#20032=*
exprs(#20032,107,#20030,0,"x3 ?? y3")
#20033=@"loc,{#10000},4,1,4,8"
locations_default(#20033,#10000,4,1,4,8)
hasLocation(#20032,#20033)
enclosingStmt(#20032,#20028)
exprContainers(#20032,#20001)
#20034=*
exprs(#20034,79,#20032,0,"x3")
#20035=@"loc,{#10000},4,1,4,2"
locations_default(#20035,#10000,4,1,4,2)
hasLocation(#20034,#20035)
enclosingStmt(#20034,#20028)
exprContainers(#20034,#20001)
literals("x3","x3",#20034)
#20036=@"var;{x3};{#20000}"
variables(#20036,"x3",#20000)
bind(#20034,#20036)
#20037=*
exprs(#20037,79,#20032,1,"y3")
#20038=@"loc,{#10000},4,7,4,8"
locations_default(#20038,#10000,4,7,4,8)
hasLocation(#20037,#20038)
enclosingStmt(#20037,#20028)
exprContainers(#20037,#20001)
literals("y3","y3",#20037)
#20039=@"var;{y3};{#20000}"
variables(#20039,"y3",#20000)
bind(#20037,#20039)
#20040=*
exprs(#20040,79,#20030,1,"z3")
#20041=@"loc,{#10000},4,13,4,14"
locations_default(#20041,#10000,4,13,4,14)
hasLocation(#20040,#20041)
enclosingStmt(#20040,#20028)
exprContainers(#20040,#20001)
literals("z3","z3",#20040)
#20042=@"var;{z3};{#20000}"
variables(#20042,"z3",#20000)
bind(#20040,#20042)
#20043=*
stmts(#20043,2,#20001,3,"x4 && y4 ?? z4;")
#20044=@"loc,{#10000},6,1,6,15"
locations_default(#20044,#10000,6,1,6,15)
hasLocation(#20043,#20044)
stmtContainers(#20043,#20001)
#20045=*
exprs(#20045,107,#20043,0,"x4 && y4 ?? z4")
#20046=@"loc,{#10000},6,1,6,14"
locations_default(#20046,#10000,6,1,6,14)
hasLocation(#20045,#20046)
enclosingStmt(#20045,#20043)
exprContainers(#20045,#20001)
#20047=*
exprs(#20047,44,#20045,0,"x4 && y4")
#20048=@"loc,{#10000},6,1,6,8"
locations_default(#20048,#10000,6,1,6,8)
hasLocation(#20047,#20048)
enclosingStmt(#20047,#20043)
exprContainers(#20047,#20001)
#20049=*
exprs(#20049,79,#20047,0,"x4")
#20050=@"loc,{#10000},6,1,6,2"
locations_default(#20050,#10000,6,1,6,2)
hasLocation(#20049,#20050)
enclosingStmt(#20049,#20043)
exprContainers(#20049,#20001)
literals("x4","x4",#20049)
#20051=@"var;{x4};{#20000}"
variables(#20051,"x4",#20000)
bind(#20049,#20051)
#20052=*
exprs(#20052,79,#20047,1,"y4")
#20053=@"loc,{#10000},6,7,6,8"
locations_default(#20053,#10000,6,7,6,8)
hasLocation(#20052,#20053)
enclosingStmt(#20052,#20043)
exprContainers(#20052,#20001)
literals("y4","y4",#20052)
#20054=@"var;{y4};{#20000}"
variables(#20054,"y4",#20000)
bind(#20052,#20054)
#20055=*
exprs(#20055,79,#20045,1,"z4")
#20056=@"loc,{#10000},6,13,6,14"
locations_default(#20056,#10000,6,13,6,14)
hasLocation(#20055,#20056)
enclosingStmt(#20055,#20043)
exprContainers(#20055,#20001)
literals("z4","z4",#20055)
#20057=@"var;{z4};{#20000}"
variables(#20057,"z4",#20000)
bind(#20055,#20057)
#20058=*
stmts(#20058,2,#20001,4,"x5 ?? y5 && z5;")
#20059=@"loc,{#10000},7,1,7,15"
locations_default(#20059,#10000,7,1,7,15)
hasLocation(#20058,#20059)
stmtContainers(#20058,#20001)
#20060=*
exprs(#20060,107,#20058,0,"x5 ?? y5 && z5")
#20061=@"loc,{#10000},7,1,7,14"
locations_default(#20061,#10000,7,1,7,14)
hasLocation(#20060,#20061)
enclosingStmt(#20060,#20058)
exprContainers(#20060,#20001)
#20062=*
exprs(#20062,79,#20060,0,"x5")
#20063=@"loc,{#10000},7,1,7,2"
locations_default(#20063,#10000,7,1,7,2)
hasLocation(#20062,#20063)
enclosingStmt(#20062,#20058)
exprContainers(#20062,#20001)
literals("x5","x5",#20062)
#20064=@"var;{x5};{#20000}"
variables(#20064,"x5",#20000)
bind(#20062,#20064)
#20065=*
exprs(#20065,44,#20060,1,"y5 && z5")
#20066=@"loc,{#10000},7,7,7,14"
locations_default(#20066,#10000,7,7,7,14)
hasLocation(#20065,#20066)
enclosingStmt(#20065,#20058)
exprContainers(#20065,#20001)
#20067=*
exprs(#20067,79,#20065,0,"y5")
#20068=@"loc,{#10000},7,7,7,8"
locations_default(#20068,#10000,7,7,7,8)
hasLocation(#20067,#20068)
enclosingStmt(#20067,#20058)
exprContainers(#20067,#20001)
literals("y5","y5",#20067)
#20069=@"var;{y5};{#20000}"
variables(#20069,"y5",#20000)
bind(#20067,#20069)
#20070=*
exprs(#20070,79,#20065,1,"z5")
#20071=@"loc,{#10000},7,13,7,14"
locations_default(#20071,#10000,7,13,7,14)
hasLocation(#20070,#20071)
enclosingStmt(#20070,#20058)
exprContainers(#20070,#20001)
literals("z5","z5",#20070)
#20072=@"var;{z5};{#20000}"
variables(#20072,"z5",#20000)
bind(#20070,#20072)
#20073=*
lines(#20073,#20001,"x1 ?? y1;","
")
hasLocation(#20073,#20004)
#20074=*
lines(#20074,#20001,"","
")
#20075=@"loc,{#10000},2,1,2,0"
locations_default(#20075,#10000,2,1,2,0)
hasLocation(#20074,#20075)
#20076=*
lines(#20076,#20001,"x2 || y2 ?? z2;","
")
hasLocation(#20076,#20014)
#20077=*
lines(#20077,#20001,"x3 ?? y3 || z3;","
")
hasLocation(#20077,#20029)
#20078=*
lines(#20078,#20001,"","
")
#20079=@"loc,{#10000},5,1,5,0"
locations_default(#20079,#10000,5,1,5,0)
hasLocation(#20078,#20079)
#20080=*
lines(#20080,#20001,"x4 && y4 ?? z4;","
")
hasLocation(#20080,#20044)
#20081=*
lines(#20081,#20001,"x5 ?? y5 && z5;","")
hasLocation(#20081,#20059)
numlines(#20001,7,5,0)
#20082=*
tokeninfo(#20082,6,#20001,0,"x1")
hasLocation(#20082,#20008)
#20083=*
tokeninfo(#20083,8,#20001,1,"??")
#20084=@"loc,{#10000},1,4,1,5"
locations_default(#20084,#10000,1,4,1,5)
hasLocation(#20083,#20084)
#20085=*
tokeninfo(#20085,6,#20001,2,"y1")
hasLocation(#20085,#20011)
#20086=*
tokeninfo(#20086,8,#20001,3,";")
#20087=@"loc,{#10000},1,9,1,9"
locations_default(#20087,#10000,1,9,1,9)
hasLocation(#20086,#20087)
#20088=*
tokeninfo(#20088,6,#20001,4,"x2")
hasLocation(#20088,#20020)
#20089=*
tokeninfo(#20089,8,#20001,5,"||")
#20090=@"loc,{#10000},3,4,3,5"
locations_default(#20090,#10000,3,4,3,5)
hasLocation(#20089,#20090)
#20091=*
tokeninfo(#20091,6,#20001,6,"y2")
hasLocation(#20091,#20023)
#20092=*
tokeninfo(#20092,8,#20001,7,"??")
#20093=@"loc,{#10000},3,10,3,11"
locations_default(#20093,#10000,3,10,3,11)
hasLocation(#20092,#20093)
#20094=*
tokeninfo(#20094,6,#20001,8,"z2")
hasLocation(#20094,#20026)
#20095=*
tokeninfo(#20095,8,#20001,9,";")
#20096=@"loc,{#10000},3,15,3,15"
locations_default(#20096,#10000,3,15,3,15)
hasLocation(#20095,#20096)
#20097=*
tokeninfo(#20097,6,#20001,10,"x3")
hasLocation(#20097,#20035)
#20098=*
tokeninfo(#20098,8,#20001,11,"??")
#20099=@"loc,{#10000},4,4,4,5"
locations_default(#20099,#10000,4,4,4,5)
hasLocation(#20098,#20099)
#20100=*
tokeninfo(#20100,6,#20001,12,"y3")
hasLocation(#20100,#20038)
#20101=*
tokeninfo(#20101,8,#20001,13,"||")
#20102=@"loc,{#10000},4,10,4,11"
locations_default(#20102,#10000,4,10,4,11)
hasLocation(#20101,#20102)
#20103=*
tokeninfo(#20103,6,#20001,14,"z3")
hasLocation(#20103,#20041)
#20104=*
tokeninfo(#20104,8,#20001,15,";")
#20105=@"loc,{#10000},4,15,4,15"
locations_default(#20105,#10000,4,15,4,15)
hasLocation(#20104,#20105)
#20106=*
tokeninfo(#20106,6,#20001,16,"x4")
hasLocation(#20106,#20050)
#20107=*
tokeninfo(#20107,8,#20001,17,"&&")
#20108=@"loc,{#10000},6,4,6,5"
locations_default(#20108,#10000,6,4,6,5)
hasLocation(#20107,#20108)
#20109=*
tokeninfo(#20109,6,#20001,18,"y4")
hasLocation(#20109,#20053)
#20110=*
tokeninfo(#20110,8,#20001,19,"??")
#20111=@"loc,{#10000},6,10,6,11"
locations_default(#20111,#10000,6,10,6,11)
hasLocation(#20110,#20111)
#20112=*
tokeninfo(#20112,6,#20001,20,"z4")
hasLocation(#20112,#20056)
#20113=*
tokeninfo(#20113,8,#20001,21,";")
#20114=@"loc,{#10000},6,15,6,15"
locations_default(#20114,#10000,6,15,6,15)
hasLocation(#20113,#20114)
#20115=*
tokeninfo(#20115,6,#20001,22,"x5")
hasLocation(#20115,#20063)
#20116=*
tokeninfo(#20116,8,#20001,23,"??")
#20117=@"loc,{#10000},7,4,7,5"
locations_default(#20117,#10000,7,4,7,5)
hasLocation(#20116,#20117)
#20118=*
tokeninfo(#20118,6,#20001,24,"y5")
hasLocation(#20118,#20068)
#20119=*
tokeninfo(#20119,8,#20001,25,"&&")
#20120=@"loc,{#10000},7,10,7,11"
locations_default(#20120,#10000,7,10,7,11)
hasLocation(#20119,#20120)
#20121=*
tokeninfo(#20121,6,#20001,26,"z5")
hasLocation(#20121,#20071)
#20122=*
tokeninfo(#20122,8,#20001,27,";")
#20123=@"loc,{#10000},7,15,7,15"
locations_default(#20123,#10000,7,15,7,15)
hasLocation(#20122,#20123)
#20124=*
tokeninfo(#20124,0,#20001,28,"")
#20125=@"loc,{#10000},7,16,7,15"
locations_default(#20125,#10000,7,16,7,15)
hasLocation(#20124,#20125)
#20126=*
entry_cfg_node(#20126,#20001)
#20127=@"loc,{#10000},1,1,1,0"
locations_default(#20127,#10000,1,1,1,0)
hasLocation(#20126,#20127)
#20128=*
exit_cfg_node(#20128,#20001)
hasLocation(#20128,#20125)
successor(#20058,#20060)
successor(#20060,#20062)
successor(#20062,#20065)
successor(#20062,#20128)
successor(#20065,#20067)
#20129=*
guard_node(#20129,1,#20067)
hasLocation(#20129,#20068)
successor(#20129,#20070)
#20130=*
guard_node(#20130,0,#20067)
hasLocation(#20130,#20068)
successor(#20130,#20128)
successor(#20067,#20129)
successor(#20067,#20130)
successor(#20070,#20128)
successor(#20043,#20045)
successor(#20045,#20047)
successor(#20047,#20049)
#20131=*
guard_node(#20131,1,#20049)
hasLocation(#20131,#20050)
successor(#20131,#20052)
#20132=*
guard_node(#20132,0,#20049)
hasLocation(#20132,#20050)
successor(#20132,#20055)
successor(#20132,#20058)
successor(#20049,#20131)
successor(#20049,#20132)
successor(#20052,#20055)
successor(#20052,#20058)
successor(#20055,#20058)
successor(#20028,#20030)
successor(#20030,#20032)
successor(#20032,#20034)
successor(#20034,#20037)
successor(#20034,#20043)
successor(#20034,#20040)
#20133=*
guard_node(#20133,1,#20037)
hasLocation(#20133,#20038)
successor(#20133,#20043)
#20134=*
guard_node(#20134,0,#20037)
hasLocation(#20134,#20038)
successor(#20134,#20040)
successor(#20037,#20133)
successor(#20037,#20134)
successor(#20040,#20043)
successor(#20013,#20015)
successor(#20015,#20017)
successor(#20017,#20019)
#20135=*
guard_node(#20135,1,#20019)
hasLocation(#20135,#20020)
successor(#20135,#20025)
successor(#20135,#20028)
#20136=*
guard_node(#20136,0,#20019)
hasLocation(#20136,#20020)
successor(#20136,#20022)
successor(#20019,#20135)
successor(#20019,#20136)
successor(#20022,#20025)
successor(#20022,#20028)
successor(#20025,#20028)
successor(#20003,#20005)
successor(#20005,#20007)
successor(#20007,#20010)
successor(#20007,#20013)
successor(#20010,#20013)
successor(#20126,#20003)
numlines(#10000,7,5,0)
filetype(#10000,"javascript")

View File

@@ -338,6 +338,7 @@ case @expr.kind of
| 104 = @decorator_list
| 105 = @non_null_assertion
| 106 = @bigintliteral
| 107 = @nullishcoalescingexpr
;
@varaccess = @proper_varaccess | @export_varaccess;
@@ -357,7 +358,7 @@ case @expr.kind of
@comparison = @equalitytest | @ltexpr | @leexpr | @gtexpr | @geexpr;
@binaryexpr = @comparison | @lshiftexpr | @rshiftexpr | @urshiftexpr | @addexpr | @subexpr | @mulexpr | @divexpr | @modexpr | @expexpr | @bitorexpr | @xorexpr | @bitandexpr | @inexpr | @instanceofexpr | @logandexpr | @logorexpr;
@binaryexpr = @comparison | @lshiftexpr | @rshiftexpr | @urshiftexpr | @addexpr | @subexpr | @mulexpr | @divexpr | @modexpr | @expexpr | @bitorexpr | @xorexpr | @bitandexpr | @inexpr | @instanceofexpr | @logandexpr | @logorexpr | @nullishcoalescingexpr;
@assignment = @assignexpr | @assignaddexpr | @assignsubexpr | @assignmulexpr | @assigndivexpr | @assignmodexpr | @assignexpexpr | @assignlshiftexpr | @assignrshiftexpr | @assignurshiftexpr | @assignorexpr | @assignxorexpr | @assignandexpr;
@@ -1081,4 +1082,4 @@ xmllocations(
isOptionalChaining(int id: @optionalchainable ref);
/* Last updated 2018/10/22. */
/* Last updated 2018/10/23. */

View File

@@ -1370,6 +1370,10 @@
<v>100</v>
</e>
<e>
<k>@nullishcoalescingexpr</k>
<v>100</v>
</e>
<e>
<k>@xmldtd</k>
<v>1</v>
</e>
@@ -1397,6 +1401,10 @@
<k>@optionalchainable</k>
<v>100</v>
</e>
<e>
<k>@nullishcoalescingexpr</k>
<v>100</v>
</e>
</typesizes>
<stats>
<relation>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,2 @@
description: 'introduce @nullishcoalescingexpr'
compatibility: backwards