Skip to content

Commit 3165803

Browse files
committed
TS: Fix extraction of default-exported class
1 parent abdf7ce commit 3165803

File tree

8 files changed

+46
-11
lines changed

8 files changed

+46
-11
lines changed

javascript/extractor/src/com/semmle/js/parser/TypeScriptASTConverter.java

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
package com.semmle.js.parser;
22

3+
import java.util.ArrayList;
4+
import java.util.Collections;
5+
import java.util.LinkedHashMap;
6+
import java.util.List;
7+
import java.util.Map;
8+
import java.util.regex.Matcher;
9+
import java.util.regex.Pattern;
10+
311
import com.google.gson.JsonArray;
412
import com.google.gson.JsonElement;
513
import com.google.gson.JsonNull;
@@ -142,13 +150,6 @@
142150
import com.semmle.ts.ast.UnaryTypeExpr;
143151
import com.semmle.ts.ast.UnionTypeExpr;
144152
import com.semmle.util.collections.CollectionUtil;
145-
import java.util.ArrayList;
146-
import java.util.Collections;
147-
import java.util.LinkedHashMap;
148-
import java.util.List;
149-
import java.util.Map;
150-
import java.util.regex.Matcher;
151-
import java.util.regex.Pattern;
152153

153154
/**
154155
* Utility class for converting a <a
@@ -942,11 +943,13 @@ private Node convertClass(JsonObject node, String kind, SourceLocation loc) thro
942943
SourceLocation bodyLoc = new SourceLocation(loc.getSource(), loc.getStart(), loc.getEnd());
943944
advance(bodyLoc, skip);
944945
ClassBody body = new ClassBody(bodyLoc, convertChildren(node, "members"));
945-
if ("ClassExpression".equals(kind)) {
946+
if ("ClassExpression".equals(kind) || id == null) {
947+
// Note that `export default class {}` is represented as a ClassDeclaration
948+
// in TypeScript but we treat this as a ClassExpression.
946949
ClassExpression classExpr =
947950
new ClassExpression(loc, id, typeParameters, superClass, superInterfaces, body);
948951
attachSymbolInformation(classExpr.getClassDef(), node);
949-
return classExpr;
952+
return fixExports(loc, classExpr);
950953
}
951954
boolean hasDeclareKeyword = hasModifier(node, "DeclareKeyword");
952955
boolean hasAbstractKeyword = hasModifier(node, "AbstractKeyword");
@@ -1225,6 +1228,11 @@ private Node convertForOfStatement(JsonObject node, SourceLocation loc) throws P
12251228
private Node convertFunctionDeclaration(JsonObject node, SourceLocation loc) throws ParseError {
12261229
List<Expression> params = convertParameters(node);
12271230
Identifier fnId = convertChild(node, "name", "Identifier");
1231+
if (fnId == null) {
1232+
// Anonymous function declarations may occur as part of default exported functions.
1233+
// We represent these as function expressions.
1234+
return fixExports(loc, convertFunctionExpression(node, loc));
1235+
}
12281236
BlockStatement fnbody = convertChild(node, "body");
12291237
boolean generator = hasChild(node, "asteriskToken");
12301238
boolean async = hasModifier(node, "AsyncKeyword");
@@ -2305,15 +2313,15 @@ private IJSXName convertJSXName(Expression e) {
23052313
* <p>If the declared statement has decorators, the {@code loc} should first be advanced past
23062314
* these using {@link #advanceUntilAfter}.
23072315
*/
2308-
private Node fixExports(SourceLocation loc, Statement decl) {
2316+
private Node fixExports(SourceLocation loc, Node decl) {
23092317
Matcher m = EXPORT_DECL_START.matcher(loc.getSource());
23102318
if (m.find()) {
23112319
String skipped = m.group(0);
23122320
SourceLocation outerLoc = new SourceLocation(loc.getSource(), loc.getStart(), loc.getEnd());
23132321
advance(loc, skipped);
23142322
// capture group 1 is `default`, if present
23152323
if (m.group(1) == null)
2316-
return new ExportNamedDeclaration(outerLoc, decl, new ArrayList<>(), null);
2324+
return new ExportNamedDeclaration(outerLoc, (Statement) decl, new ArrayList<>(), null);
23172325
return new ExportDefaultDeclaration(outerLoc, decl);
23182326
}
23192327
return decl;
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { Foo } from "somwhere";
2+
3+
export default class extends Foo {}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { Foo } from "somwhere";
2+
3+
export default function(x=Foo) {}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
classExprs
2+
| exportClass.ts:3:16:3:35 | class extends Foo {} |
3+
functionExprs
4+
| exportClass.ts:3:34:3:33 | (...arg ... rgs); } |
5+
| exportFunction.ts:3:16:3:33 | function(x=Foo) {} |
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import javascript
2+
3+
query ClassExpr classExprs() { any() }
4+
5+
query FunctionExpr functionExprs() { any() }
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { Foo } from "./node_modules/somwhere";
2+
3+
export default function(x=Foo) {}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
var C1 = global.C1; // OK
2+
var C2 = global.C2; // OK
3+
4+
class C extends C1 {}
5+
export default class extends C2 {}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
var C1 = global.C1; // OK
2+
3+
export default function(x=C1) {}

0 commit comments

Comments
 (0)