Skip to content

Commit 472c042

Browse files
authored
Merge pull request #526 from esben-semmle/js/flow-parsing-improvements
Approved by xiemaisi
2 parents 733acac + 8c7ca38 commit 472c042

File tree

12 files changed

+1636
-20
lines changed

12 files changed

+1636
-20
lines changed

change-notes/1.19/extractor-javascript.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,6 @@
2626
point to an installation of the `typescript` NPM package.
2727

2828
* The extractor now supports [Optional Chaining](https://github.com/tc39/proposal-optional-chaining) expressions.
29+
30+
* The extractor now supports additional [Flow](https://flow.org/) syntax.
31+

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3040,14 +3040,18 @@ protected MemberDefinition<?> parseClassMember(boolean hadConstructor) {
30403040
return parseClassPropertyBody(pi, hadConstructor, isStatic);
30413041
}
30423042

3043+
protected boolean atGetterSetterName(PropertyInfo pi) {
3044+
return !pi.isGenerator && !pi.isAsync && pi.key instanceof Identifier && this.type != TokenType.parenL && (((Identifier) pi.key).getName().equals("get") || ((Identifier) pi.key).getName().equals("set"));
3045+
}
3046+
30433047
/**
30443048
* Parse a method declaration in a class, assuming that its name has already been consumed.
30453049
*/
30463050
protected MemberDefinition<?> parseClassPropertyBody(PropertyInfo pi, boolean hadConstructor, boolean isStatic) {
30473051
pi.kind = "method";
30483052
boolean isGetSet = false;
30493053
if (!pi.computed) {
3050-
if (!pi.isGenerator && !pi.isAsync && pi.key instanceof Identifier && this.type != TokenType.parenL && (((Identifier) pi.key).getName().equals("get") || ((Identifier) pi.key).getName().equals("set"))) {
3054+
if (atGetterSetterName(pi)) {
30513055
isGetSet = true;
30523056
pi.kind = ((Identifier) pi.key).getName();
30533057
this.parsePropertyName(pi);

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

Lines changed: 61 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,10 @@ private void flowParseTypeInitialiser(TokenType tok, boolean allowLeadingPipeOrA
146146
boolean oldInType = inType;
147147
inType = true;
148148
this.expect(tok == null ? TokenType.colon : tok);
149+
if (this.type == TokenType.modulo) {// an annotation like '%checks' without a preceeding type
150+
inType = oldInType;
151+
return;
152+
}
149153
if (allowLeadingPipeOrAnd) {
150154
if (this.type == TokenType.bitwiseAND || this.type == TokenType.bitwiseOR) {
151155
this.next();
@@ -223,14 +227,29 @@ private void flowParseDeclareModule(Position start) {
223227
while (this.type != TokenType.braceR) {
224228
Position stmtStart = startLoc;
225229

226-
// todo: declare check
227-
this.next();
230+
if (this.eat(TokenType._import)) {
231+
this.flowParseDeclareImport(stmtStart);
232+
} else {
233+
// todo: declare check
234+
this.next();
228235

229-
this.flowParseDeclare(stmtStart);
236+
this.flowParseDeclare(stmtStart);
237+
}
230238
}
231239
this.expect(TokenType.braceR);
232240
}
233241

242+
private void flowParseDeclareImport(Position stmtStart) {
243+
String kind = flowParseImportSpecifiers();
244+
if (kind == null) {
245+
this.raise(stmtStart, "Imports within a `declare module` body must always be `import type` or `import typeof`.");
246+
}
247+
this.expect(TokenType.name);
248+
this.expectContextual("from");
249+
this.expect(TokenType.string);
250+
this.semicolon();
251+
}
252+
234253
private void flowParseDeclareModuleExports() {
235254
this.expectContextual("module");
236255
this.expect(TokenType.dot);
@@ -737,7 +756,7 @@ private void flowParsePrimaryType() {
737756

738757
private void flowParsePostfixType() {
739758
this.flowParsePrimaryType();
740-
if (this.type == TokenType.bracketL) {
759+
while (this.type == TokenType.bracketL) {
741760
this.expect(TokenType.bracketL);
742761
this.expect(TokenType.bracketR);
743762
}
@@ -807,11 +826,20 @@ protected Node parseFunctionBody(Identifier id, List<Expression> params, boolean
807826
// if allowExpression is true then we're parsing an arrow function and if
808827
// there's a return type then it's been handled elsewhere
809828
this.flowParseTypeAnnotation();
829+
this.flowParseChecksAnnotation();
810830
}
811831

812832
return super.parseFunctionBody(id, params, isArrowFunction);
813833
}
814834

835+
private void flowParseChecksAnnotation() {
836+
// predicate functions with the special '%checks' annotation
837+
if (this.type == TokenType.modulo && lookaheadIsIdent("checks", true)) {
838+
this.next();
839+
this.next();
840+
}
841+
}
842+
815843
// interfaces
816844
@Override
817845
protected Statement parseStatement(boolean declaration, boolean topLevel, Set<String> exports) {
@@ -975,24 +1003,30 @@ protected Expression processBindingListItem(Expression param) {
9751003
return param;
9761004
}
9771005

1006+
private String flowParseImportSpecifiers() {
1007+
String kind = null;
1008+
if (this.type == TokenType._typeof) {
1009+
kind = "typeof";
1010+
} else if (this.isContextual("type")) {
1011+
kind = "type";
1012+
}
1013+
if (kind != null) {
1014+
String lh = lookahead(4);
1015+
if (!lh.isEmpty()) {
1016+
int c = lh.codePointAt(0);
1017+
if ((Identifiers.isIdentifierStart(c, true) && !"from".equals(lh)) || c == '{' || c == '*') {
1018+
this.next();
1019+
}
1020+
}
1021+
}
1022+
return kind;
1023+
}
1024+
9781025
@Override
9791026
protected List<ImportSpecifier> parseImportSpecifiers() {
9801027
String kind = null;
9811028
if (flow()) {
982-
if (this.type == TokenType._typeof) {
983-
kind = "typeof";
984-
} else if (this.isContextual("type")) {
985-
kind = "type";
986-
}
987-
if (kind != null) {
988-
String lh = lookahead(4);
989-
if (!lh.isEmpty()) {
990-
int c = lh.codePointAt(0);
991-
if ((Identifiers.isIdentifierStart(c, true) && !"from".equals(lh)) || c == '{' || c == '*') {
992-
this.next();
993-
}
994-
}
995-
}
1029+
kind = flowParseImportSpecifiers();
9961030
}
9971031

9981032
List<ImportSpecifier> specs = super.parseImportSpecifiers();
@@ -1102,6 +1136,7 @@ protected ParenthesisedExpressions parseParenthesisedExpressions(DestructuringEr
11021136
boolean oldNoAnonFunctionType = noAnonFunctionType;
11031137
noAnonFunctionType = true;
11041138
flowParseTypeAnnotation();
1139+
flowParseChecksAnnotation();
11051140
noAnonFunctionType = oldNoAnonFunctionType;
11061141
if (this.type != TokenType.arrow)
11071142
unexpected();
@@ -1158,4 +1193,12 @@ protected void parsePropertyName(PropertyInfo result) {
11581193
this.eat(TokenType.plusMin);
11591194
super.parsePropertyName(result);
11601195
}
1196+
1197+
@Override
1198+
protected boolean atGetterSetterName(PropertyInfo pi) {
1199+
if (flow() && this.isRelational("<"))
1200+
return false;
1201+
return super.atGetterSetterName(pi);
1202+
}
1203+
11611204
}

javascript/extractor/src/com/semmle/js/extractor/Main.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ public class Main {
4141
* such a way that it may produce different tuples for the same file under the same
4242
* {@link ExtractorConfig}.
4343
*/
44-
public static final String EXTRACTOR_VERSION = "2018-11-20";
44+
public static final String EXTRACTOR_VERSION = "2018-11-22_a";
4545

4646
public static final Pattern NEWLINE = Pattern.compile("\n");
4747

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
function f(p: T) {}
2+
function f(p: T[]) {}
3+
function f(p: T[][]) {}
4+
function f(p: T[][][]) {}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
declare module "m1" {
2+
import type t from "m2";
3+
import typeof t from "m3";
4+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
class C {
2+
get<T>(v: T) { }
3+
set<T>(v: T) { }
4+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
function f(a): boolean %checks {
2+
return a;
3+
}
4+
function g(): %checks {} {
5+
return b;
6+
}
7+
8+
(c): boolean %checks => c;
9+
(d): %checks => d;

0 commit comments

Comments
 (0)