Skip to content

Commit c26ae26

Browse files
author
Esben Sparre Andreasen
committed
JS: support explicit type arguments for Flow
1 parent 45a4026 commit c26ae26

16 files changed

+2560
-0
lines changed

javascript/extractor/src/com/semmle/jcorn/Parser.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1744,6 +1744,10 @@ protected Expression parseNew() {
17441744
if (isOnOptionalChain(false, callee))
17451745
this.raise(callee, "An optional chain may not be used in a `new` expression.");
17461746

1747+
return parseNewArguments(startLoc, callee);
1748+
}
1749+
1750+
protected Expression parseNewArguments(Position startLoc, Expression callee) {
17471751
List<Expression> arguments;
17481752
if (this.eat(TokenType.parenL))
17491753
arguments = this.parseExprList(TokenType.parenR, this.options.ecmaVersion() >= 8, false, null);

javascript/extractor/src/com/semmle/jcorn/flow/FlowParser.java

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import com.semmle.jcorn.TokenType;
1616
import com.semmle.jcorn.TokenType.Properties;
1717
import com.semmle.jcorn.Whitespace;
18+
import com.semmle.js.ast.BinaryExpression;
1819
import com.semmle.js.ast.ExportDeclaration;
1920
import com.semmle.js.ast.ExportSpecifier;
2021
import com.semmle.js.ast.Expression;
@@ -23,12 +24,15 @@
2324
import com.semmle.js.ast.Identifier;
2425
import com.semmle.js.ast.ImportDeclaration;
2526
import com.semmle.js.ast.ImportSpecifier;
27+
import com.semmle.js.ast.Literal;
2628
import com.semmle.js.ast.MethodDefinition;
2729
import com.semmle.js.ast.Node;
2830
import com.semmle.js.ast.Position;
2931
import com.semmle.js.ast.SourceLocation;
3032
import com.semmle.js.ast.Statement;
3133
import com.semmle.js.ast.Token;
34+
import com.semmle.js.ast.UnaryExpression;
35+
import com.semmle.util.data.Pair;
3236
import com.semmle.util.exception.Exceptions;
3337

3438
/**
@@ -1203,4 +1207,43 @@ protected boolean atGetterSetterName(PropertyInfo pi) {
12031207
return super.atGetterSetterName(pi);
12041208
}
12051209

1210+
@Override
1211+
protected Pair<Expression, Boolean> parseSubscript(final Expression base, Position startLoc, boolean noCalls) {
1212+
if (!noCalls) {
1213+
maybeFlowParseTypeParameterInstantiation(base, true);
1214+
}
1215+
return super.parseSubscript(base, startLoc, noCalls);
1216+
}
1217+
1218+
private void maybeFlowParseTypeParameterInstantiation(Expression left, boolean requireParenL) {
1219+
if (flow() && this.isRelational("<")) {
1220+
// Ambiguous case: `e1<e2>(e3)` is parsed differently as JS and Flow code:
1221+
// JS: two relational comparisons: `e1 < e2 > e3`
1222+
// Flow: a call `e1(e3)` with explicit type parameter `e2`
1223+
1224+
// Heuristic: if the left operand of the `<` token is a primitive from a literal or unary/binary expression, then it probably isn't a call, as that would always crash
1225+
left = left.stripParens();
1226+
if (left instanceof Literal || left instanceof UnaryExpression || left instanceof BinaryExpression)
1227+
return;
1228+
1229+
// If it can be parsed as Flow, we use that, otherwise we parse it as JS
1230+
State backup = new State();
1231+
try {
1232+
this.flowParseTypeParameterInstantiation();
1233+
if (requireParenL && this.type != TokenType.parenL) {
1234+
unexpected();
1235+
}
1236+
backup.commit();
1237+
} catch (SyntaxError e) {
1238+
Exceptions.ignore(e, "Backtracking parser.");
1239+
backup.reset();
1240+
}
1241+
}
1242+
}
1243+
1244+
@Override
1245+
protected Expression parseNewArguments(Position startLoc, Expression callee) {
1246+
maybeFlowParseTypeParameterInstantiation(callee, false /* case: new e1<e2>e3 */);
1247+
return super.parseNewArguments(startLoc, callee);
1248+
}
12061249
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
var b = ::o.m<T>;
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
new K1<T1>();
2+
f1<T2>();
3+
const f2 = function*(): T3<T4> {
4+
yield* f3<T5>(x);
5+
}
6+
f4<T6>(v1);
7+
f5<_,_,_>();
8+
f6<
9+
|T7
10+
|T8
11+
|T9
12+
>();
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
new K1<T1>;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
1<2>(3);
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
f<g()>(h());
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/x/<2>(3);
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
if(!(0<(jd|0)&0>(Wc|0))){}
2+
g=(g|0)<(e|0)|(g|0)>(b|0)?e:g;
3+
if((I|0)<(D|0)|(N|0)>(P|0)|(M|0)<(F|0)|(L|0)>(H|0)){}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
#10000=@"/boundExplicitTypeParameters.js;sourcefile"
2+
files(#10000,"/boundExplicitTypeParameters.js","boundExplicitTypeParameters","js",0)
3+
#10001=@"/;folder"
4+
folders(#10001,"/","")
5+
containerparent(#10001,#10000)
6+
#10002=@"loc,{#10000},0,0,0,0"
7+
locations_default(#10002,#10000,0,0,0,0)
8+
hasLocation(#10000,#10002)
9+
#20000=@"global_scope"
10+
scopes(#20000,0)
11+
#20001=@"script;{#10000},1,1"
12+
toplevels(#20001,0)
13+
#20002=@"loc,{#10000},1,1,1,1"
14+
locations_default(#20002,#10000,1,1,1,1)
15+
hasLocation(#20001,#20002)
16+
#20003=*
17+
jsParseErrors(#20003,#20001,"Error: Unexpected token","var b = ::o.m<T>;")
18+
#20004=@"loc,{#10000},1,17,1,17"
19+
locations_default(#20004,#10000,1,17,1,17)
20+
hasLocation(#20003,#20004)
21+
#20005=*
22+
lines(#20005,#20001,"var b = ::o.m<T>;","")
23+
#20006=@"loc,{#10000},1,1,1,17"
24+
locations_default(#20006,#10000,1,1,1,17)
25+
hasLocation(#20005,#20006)
26+
numlines(#20001,1,0,0)
27+
numlines(#10000,1,0,0)
28+
filetype(#10000,"javascript")

0 commit comments

Comments
 (0)