jsx_readChunk(StringBuilder out, int chunkStart, int ch) {
+ // skip XML comments, processing instructions and CDATA (which are allowed in E4X,
+ // but not in JSX)
+ // unlike in `readToken` above, we know that we're inside JSX/E4X code, so there is
+ // no ambiguity with Flow type annotations
+ if (this.options.e4x() && ch == '<') {
+ if (inputSubstring(this.pos + 1, this.pos + 4).equals("!--")) {
+ out.append(inputSubstring(chunkStart, this.pos));
+ this.pos += 4;
+ jsx_readUntil("-->");
+ return Either.left(this.pos);
+ } else if (charAt(this.pos + 1) == '?') {
+ out.append(inputSubstring(chunkStart, this.pos));
+ this.pos += 2;
+ jsx_readUntil("?>");
+ return Either.left(this.pos);
+ } else if (inputSubstring(this.pos + 1, this.pos + 9).equals("![CDATA[")) {
+ out.append(inputSubstring(chunkStart, this.pos));
+ this.pos += 9;
+ int cdataStart = this.pos;
+ jsx_readUntil("]]>");
+ out.append(inputSubstring(cdataStart, this.pos - 3));
+ return Either.left(this.pos);
+ }
+ }
+
+ return super.jsx_readChunk(out, chunkStart, ch);
+ }
+
+ private boolean jsx_readUntil(String terminator) {
+ char fst = terminator.charAt(0);
+ while (this.pos + terminator.length() <= this.input.length()) {
+ if (charAt(this.pos) == fst
+ && inputSubstring(this.pos, this.pos + terminator.length()).equals(terminator)) {
+ this.pos += terminator.length();
+ return true;
+ }
+ ++this.pos;
+ }
+ return false;
+ }
}
diff --git a/javascript/extractor/src/com/semmle/jcorn/ESNextParser.java b/javascript/extractor/src/com/semmle/jcorn/ESNextParser.java
index df6eec3df8fe..ca1a748bf075 100644
--- a/javascript/extractor/src/com/semmle/jcorn/ESNextParser.java
+++ b/javascript/extractor/src/com/semmle/jcorn/ESNextParser.java
@@ -1,10 +1,5 @@
package com.semmle.jcorn;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
import com.semmle.jcorn.TokenType.Properties;
import com.semmle.jcorn.jsx.JSXParser;
import com.semmle.js.ast.BindExpression;
@@ -42,457 +37,460 @@
import com.semmle.js.ast.Token;
import com.semmle.util.collections.CollectionUtil;
import com.semmle.util.data.Pair;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
/**
- * An extension of the {@link JSXParser} with support for various
- * unfinished ECMAScript proposals that are not supported by
- * Acorn/jcorn yet.
+ * An extension of the {@link JSXParser} with support for various unfinished ECMAScript proposals
+ * that are not supported by Acorn/jcorn yet.
*
- * Once support becomes available, they should be removed from
- * this class.
+ * Once support becomes available, they should be removed from this class.
*/
public class ESNextParser extends JSXParser {
- public ESNextParser(Options options, String input, int startPos) {
- super(options.allowImportExportEverywhere(true), input, startPos);
- }
-
- /*
- * Support for proposed language feature "Object Rest/Spread Properties"
- * (http://sebmarkbage.github.io/ecmascript-rest-spread/).
- */
-
- @Override
- protected Property parseProperty(boolean isPattern, DestructuringErrors refDestructuringErrors,
- Map propHash) {
- Position start = this.startLoc;
-
- List decorators = parseDecorators();
-
- Property prop = null;
- if (this.type == TokenType.ellipsis) {
- SpreadElement spread = this.parseSpread(null);
- Expression val;
- if (isPattern)
- val = new RestElement(spread.getLoc(), spread.getArgument());
- else
- val = spread;
- prop = this.finishNode(new Property(new SourceLocation(start), null, val, Property.Kind.INIT.name(), false, false));
- }
-
- if (prop == null)
- prop = super.parseProperty(isPattern, refDestructuringErrors, propHash);
-
- prop.addDecorators(decorators);
-
- return prop;
- }
-
- @Override
- protected INode toAssignable(INode node, boolean isBinding) {
- if (node instanceof SpreadElement)
- return new RestElement(node.getLoc(), ((SpreadElement) node).getArgument());
- return super.toAssignable(node, isBinding);
- }
-
- @Override
- protected void checkLVal(INode expr, boolean isBinding, Set checkClashes) {
- super.checkLVal(expr, isBinding, checkClashes);
- if (expr instanceof ObjectPattern) {
- ObjectPattern op = (ObjectPattern) expr;
- if (op.hasRest())
- checkLVal(op.getRest(), isBinding, checkClashes);
- }
- }
-
- /*
- * Support for proposed language feature "Public Class Fields"
- * (http://jeffmo.github.io/es-class-public-fields/).
- */
-
- private boolean classProperties() {
- return options.esnext();
- }
-
- @Override
- protected MemberDefinition> parseClassPropertyBody(PropertyInfo pi,
- boolean hadConstructor, boolean isStatic) {
- if (classProperties() && !pi.isGenerator && this.isClassProperty())
- return this.parseFieldDefinition(pi, isStatic);
- return super.parseClassPropertyBody(pi, hadConstructor, isStatic);
- }
-
- protected boolean isClassProperty() {
- return this.type == TokenType.eq || this.type == TokenType.semi || this.canInsertSemicolon();
- }
-
- protected FieldDefinition parseFieldDefinition(PropertyInfo pi, boolean isStatic) {
- Expression value = null;
- if (this.type == TokenType.eq) {
- this.next();
- boolean oldInFunc = this.inFunction;
- this.inFunction = true;
- value = parseMaybeAssign(false, null, null);
- this.inFunction = oldInFunc;
- }
- this.semicolon();
- int flags = DeclarationFlags.getStatic(isStatic) | DeclarationFlags.getComputed(pi.computed);
- return this.finishNode(new FieldDefinition(new SourceLocation(pi.startLoc), flags, pi.key, value));
- }
-
- /*
- * Support for proposed language feature "Generator function.sent Meta Property"
- * (https://github.com/allenwb/ESideas/blob/master/Generator%20metaproperty.md)
- */
- private boolean functionSent() {
- return options.esnext();
- }
-
- @Override
- protected INode parseFunction(Position startLoc, boolean isStatement,
- boolean allowExpressionBody, boolean isAsync) {
- if (isFunctionSent(isStatement)) {
- Identifier meta = this.finishNode(new Identifier(new SourceLocation(startLoc), "function"));
- this.next();
- Identifier property = parseIdent(true);
- if (!property.getName().equals("sent"))
- this.raiseRecoverable(property, "The only valid meta property for function is function.sent");
- return this.finishNode(new MetaProperty(new SourceLocation(startLoc), meta, property));
- }
-
- return super.parseFunction(startLoc, isStatement, allowExpressionBody, isAsync);
- }
-
- protected boolean isFunctionSent(boolean isStatement) {
- return functionSent() && !isStatement && inGenerator && !inAsync && this.type == TokenType.dot;
- }
-
- /*
- * Support for proposed language feature "Class and Property Decorators"
- * (https://github.com/wycats/javascript-decorators)
- */
- private boolean decorators() {
- return options.esnext();
- }
-
- protected TokenType at = new TokenType(new Properties("@").beforeExpr());
-
- @Override
- protected Token getTokenFromCode(int code) {
- if (decorators() && code == 64) {
- ++this.pos;
- return this.finishToken(at);
- }
- if (functionBind() && code == 58 && charAt(this.pos+1) == 58) {
- this.pos += 2;
- return this.finishToken(doubleColon);
- }
- return super.getTokenFromCode(code);
- }
-
- @Override
- protected Statement parseStatement(boolean declaration, boolean topLevel,
- Set exports) {
- List decorators = this.parseDecorators();
- Statement stmt = super.parseStatement(declaration, topLevel, exports);
-
- if (!decorators.isEmpty()) {
- if (stmt instanceof ExportDeclaration) {
- Node exported = null;
- if (stmt instanceof ExportDefaultDeclaration) {
- exported = ((ExportDefaultDeclaration) stmt).getDeclaration();
- } else if (stmt instanceof ExportNamedDeclaration) {
- exported = ((ExportNamedDeclaration) stmt).getDeclaration();
- }
- if (exported instanceof ClassDeclaration) {
- ((ClassDeclaration) exported).addDecorators(decorators);
- } else if (exported instanceof ClassExpression) {
- ((ClassExpression) exported).addDecorators(decorators);
- } else {
- this.raise(stmt, "Decorators can only be attached to class exports");
- }
- } else if (stmt instanceof ClassDeclaration) {
- ((ClassDeclaration) stmt).addDecorators(decorators);
- } else if (stmt != null) {
- this.raise(stmt, "Leading decorators must be attached to a class declaration");
- }
- }
-
- return stmt;
- }
-
- @Override
- protected Expression parseExprAtom(DestructuringErrors refDestructuringErrors) {
- if (this.type == at) {
- List decorators = parseDecorators();
- ClassExpression ce = (ClassExpression) this.parseClass(startLoc, false);
- ce.addDecorators(decorators);
- return ce;
- }
- if (this.type == doubleColon) {
- SourceLocation startLoc = new SourceLocation(this.startLoc);
- this.next();
- int innerStart = this.start;
- Position innerStartLoc = this.startLoc;
- Expression callee = parseSubscripts(parseExprAtom(null), innerStart, innerStartLoc, true);
- if (!(callee instanceof MemberExpression))
- this.raiseRecoverable(callee, "Binding should be performed on a member expression.");
- return this.finishNode(new BindExpression(startLoc, null, callee));
- }
- if (this.type == TokenType._import) {
- Position startLoc = this.startLoc;
- this.next();
- this.expect(TokenType.parenL);
- return parseDynamicImport(startLoc);
- }
- return super.parseExprAtom(refDestructuringErrors);
- }
-
- @Override
- protected MemberDefinition> parseClassMember(boolean hadConstructor) {
- List decorators = parseDecorators();
- MemberDefinition> member = super.parseClassMember(hadConstructor);
- if (!decorators.isEmpty() && member.isConstructor())
- this.raiseRecoverable(member, "Decorators cannot be attached to class constructors.");
- member.addDecorators(decorators);
- return member;
- }
-
- public List parseDecorators() {
- List result = new ArrayList();
- while (this.type == at)
- result.add(this.parseDecorator());
- return result;
- }
-
- private Decorator parseDecorator() {
- Position start = startLoc;
- this.next();
- Expression body = parseDecoratorBody();
- Decorator decorator = new Decorator(new SourceLocation(start), body);
- return this.finishNode(decorator);
- }
-
- protected Expression parseDecoratorBody() {
- Expression base;
- int startPos = this.start;
- Position startLoc = this.startLoc;
- if (this.type == TokenType.parenL) {
- base = parseParenExpression();
- } else {
- base = parseIdent(true);
- }
- return parseSubscripts(base, startPos, startLoc, false);
- }
-
- /*
- * Support for proposed extensions to `export`
- * (http://leebyron.com/ecmascript-export-ns-from and http://leebyron.com/ecmascript-export-default-from)
- */
- private boolean exportExtensions() {
- return options.esnext();
- }
-
- @Override
- protected ExportDeclaration parseExportRest(SourceLocation exportStart, Set exports) {
- if (exportExtensions() && this.isExportDefaultSpecifier()) {
- Position specStart = this.startLoc;
- Identifier exported = this.parseIdent(true);
- ExportDefaultSpecifier defaultSpec = this.finishNode(new ExportDefaultSpecifier(new SourceLocation(specStart), exported));
- List specifiers = CollectionUtil.makeList(defaultSpec);
- if (this.type == TokenType.comma && this.lookahead(1, true).equals("*")) {
- this.next();
- specStart = this.startLoc;
- this.expect(TokenType.star);
- this.expectContextual("as");
- exported = this.parseIdent(false);
- ExportNamespaceSpecifier nsSpec = this.finishNode(new ExportNamespaceSpecifier(new SourceLocation(specStart), exported));
- specifiers.add(nsSpec);
- } else {
- this.parseExportSpecifiersMaybe(specifiers, exports);
- }
- Literal source = (Literal) this.parseExportFrom(specifiers, null, true);
- return this.finishNode(new ExportNamedDeclaration(exportStart, null, specifiers, source));
- }
-
- return super.parseExportRest(exportStart, exports);
- }
-
- @Override
- protected ExportDeclaration parseExportAll(SourceLocation exportStart, Position starLoc, Set exports) {
- if (exportExtensions() && this.eatContextual("as")) {
- Identifier exported = this.parseIdent(false);
- ExportNamespaceSpecifier nsSpec = this.finishNode(new ExportNamespaceSpecifier(new SourceLocation(starLoc), exported));
- List specifiers = CollectionUtil.makeList(nsSpec);
- this.parseExportSpecifiersMaybe(specifiers, exports);
- Literal source = (Literal) this.parseExportFrom(specifiers, null, true);
- return this.finishNode(new ExportNamedDeclaration(exportStart, null, specifiers, source));
- }
-
- return super.parseExportAll(exportStart, starLoc, exports);
- }
-
- private boolean isExportDefaultSpecifier() {
- if (this.type == TokenType.name) {
- return !this.value.equals("type") &&
- !this.value.equals("async") &&
- !this.value.equals("interface") &&
- !this.value.equals("let");
- }
-
- if (this.type != TokenType._default)
- return false;
-
- return this.lookahead(1, true).equals(",") || this.lookaheadIsIdent("from", true);
- }
-
- private void parseExportSpecifiersMaybe(List specifiers, Set exports) {
- if (this.eat(TokenType.comma)) {
- specifiers.addAll(this.parseExportSpecifiers(exports));
- }
- }
-
- /*
- * Support for proposed language feature "Function Bind Syntax"
- * (https://github.com/tc39/proposal-bind-operator)
- */
- private boolean functionBind() {
- return options.esnext();
- }
-
- protected TokenType doubleColon = new TokenType(new Properties("::").beforeExpr());
-
- @Override
- protected Pair parseSubscript(Expression base, Position startLoc, boolean noCalls) {
- if (!noCalls && this.eat(doubleColon)) {
- Expression callee = parseSubscripts(parseExprAtom(null), this.start, this.startLoc, true);
- BindExpression bind = new BindExpression(new SourceLocation(startLoc), base, callee);
- return Pair.make(this.finishNode(bind), true);
- }
- return super.parseSubscript(base, startLoc, noCalls);
- }
-
- /*
- * Support for proposed language feature "Optional Catch Binding"
- * (https://github.com/tc39/proposal-optional-catch-binding)
- */
- @Override
- protected CatchClause parseCatchClause(Position startLoc) {
- this.next();
- Expression param = null;
- if (this.eat(TokenType.parenL)) {
- param = this.parseBindingAtom();
- this.checkLVal(param, true, null);
- this.expect(TokenType.parenR);
- } else if (!options.esnext()) {
- this.unexpected();
- }
- BlockStatement catchBody = this.parseBlock(false);
- return this.finishNode(new CatchClause(new SourceLocation(startLoc), (IPattern)param, null, catchBody));
- }
-
- /*
- * Support for proposed language feature "Dynamic import"
- * (https://github.com/tc39/proposal-dynamic-import).
- */
- @Override
- protected Statement parseImport(Position startLoc) {
- if (!options.esnext())
- return super.parseImport(startLoc);
-
- int startPos = this.start;
- SourceLocation loc = new SourceLocation(startLoc);
- this.next();
- if (this.eat(TokenType.parenL)) {
- DynamicImport di = parseDynamicImport(startLoc);
- Expression expr = parseSubscripts(di, startPos, startLoc, false);
- return parseExpressionStatement(false, startLoc, expr);
- } else {
- return super.parseImportRest(loc);
- }
- }
-
- /**
- * Parses a dynamic import, assuming that the keyword `import` and the
- * opening parenthesis have already been consumed.
- */
- private DynamicImport parseDynamicImport(Position startLoc) {
- Expression source = parseMaybeAssign(false, null, null);
- this.expect(TokenType.parenR);
- DynamicImport di = this.finishNode(new DynamicImport(new SourceLocation(startLoc), source));
- return di;
- }
-
- /*
- * Support for proposed language feature "Asynchronous iteration"
- * (https://github.com/tc39/proposal-async-iteration)
- */
- @Override
- protected Statement parseForStatement(Position startLoc) {
- int startPos = this.start;
- boolean isAwait = false;
- if (this.inAsync && this.eatContextual("await"))
- isAwait = true;
- Statement forStmt = super.parseForStatement(startLoc);
- if (isAwait) {
- if (forStmt instanceof ForOfStatement)
- ((ForOfStatement) forStmt).setAwait(true);
- else
- this.raiseRecoverable(startPos, "Only for-of statements can be annotated with 'await'.");
- }
- return forStmt;
- }
-
- @Override
- protected boolean parseGeneratorMarker(boolean isAsync) {
- // always allow `*`, even if `isAsync` is true
- return this.eat(TokenType.star);
- }
-
- /*
- * Support for proposed language feature "Numeric separators"
- * (https://github.com/tc39/proposal-numeric-separator)
- */
-
- @Override
- protected Number readInt(int radix, Integer len) {
- // implementation mostly copied from super class
- int start = this.pos, code = -1;
- double total = 0;
- // no leading underscore
- boolean underscoreAllowed = false;
-
- for (int i = 0, e = len == null ? Integer.MAX_VALUE : len; i < e; ++i) {
- if (this.pos >= this.input.length())
- break;
- code = this.input.charAt(this.pos);
-
- if (code == '_') {
- if (underscoreAllowed) {
- // no adjacent underscores
- underscoreAllowed = false;
- ++this.pos;
- continue;
- }
- } else {
- underscoreAllowed = true;
- }
-
- int val;
- if (code >= 97) val = code - 97 + 10; // a
- else if (code >= 65) val = code - 65 + 10; // A
- else if (code >= 48 && code <= 57) val = code - 48; // 0-9
- else val = Integer.MAX_VALUE;
- if (val >= radix) break;
-
- ++this.pos;
- total = total * radix + val;
- }
- if (this.pos == start || len != null && this.pos - start != len) return null;
-
- if (code == '_')
- // no trailing underscore
- return null;
-
- return total;
- }
+ public ESNextParser(Options options, String input, int startPos) {
+ super(options.allowImportExportEverywhere(true), input, startPos);
+ }
+
+ /*
+ * Support for proposed language feature "Object Rest/Spread Properties"
+ * (http://sebmarkbage.github.io/ecmascript-rest-spread/).
+ */
+
+ @Override
+ protected Property parseProperty(
+ boolean isPattern,
+ DestructuringErrors refDestructuringErrors,
+ Map propHash) {
+ Position start = this.startLoc;
+
+ List decorators = parseDecorators();
+
+ Property prop = null;
+ if (this.type == TokenType.ellipsis) {
+ SpreadElement spread = this.parseSpread(null);
+ Expression val;
+ if (isPattern) val = new RestElement(spread.getLoc(), spread.getArgument());
+ else val = spread;
+ prop =
+ this.finishNode(
+ new Property(
+ new SourceLocation(start), null, val, Property.Kind.INIT.name(), false, false));
+ }
+
+ if (prop == null) prop = super.parseProperty(isPattern, refDestructuringErrors, propHash);
+
+ prop.addDecorators(decorators);
+
+ return prop;
+ }
+
+ @Override
+ protected INode toAssignable(INode node, boolean isBinding) {
+ if (node instanceof SpreadElement)
+ return new RestElement(node.getLoc(), ((SpreadElement) node).getArgument());
+ return super.toAssignable(node, isBinding);
+ }
+
+ @Override
+ protected void checkLVal(INode expr, boolean isBinding, Set checkClashes) {
+ super.checkLVal(expr, isBinding, checkClashes);
+ if (expr instanceof ObjectPattern) {
+ ObjectPattern op = (ObjectPattern) expr;
+ if (op.hasRest()) checkLVal(op.getRest(), isBinding, checkClashes);
+ }
+ }
+
+ /*
+ * Support for proposed language feature "Public Class Fields"
+ * (http://jeffmo.github.io/es-class-public-fields/).
+ */
+
+ private boolean classProperties() {
+ return options.esnext();
+ }
+
+ @Override
+ protected MemberDefinition> parseClassPropertyBody(
+ PropertyInfo pi, boolean hadConstructor, boolean isStatic) {
+ if (classProperties() && !pi.isGenerator && this.isClassProperty())
+ return this.parseFieldDefinition(pi, isStatic);
+ return super.parseClassPropertyBody(pi, hadConstructor, isStatic);
+ }
+
+ protected boolean isClassProperty() {
+ return this.type == TokenType.eq || this.type == TokenType.semi || this.canInsertSemicolon();
+ }
+
+ protected FieldDefinition parseFieldDefinition(PropertyInfo pi, boolean isStatic) {
+ Expression value = null;
+ if (this.type == TokenType.eq) {
+ this.next();
+ boolean oldInFunc = this.inFunction;
+ this.inFunction = true;
+ value = parseMaybeAssign(false, null, null);
+ this.inFunction = oldInFunc;
+ }
+ this.semicolon();
+ int flags = DeclarationFlags.getStatic(isStatic) | DeclarationFlags.getComputed(pi.computed);
+ return this.finishNode(
+ new FieldDefinition(new SourceLocation(pi.startLoc), flags, pi.key, value));
+ }
+
+ /*
+ * Support for proposed language feature "Generator function.sent Meta Property"
+ * (https://github.com/allenwb/ESideas/blob/master/Generator%20metaproperty.md)
+ */
+ private boolean functionSent() {
+ return options.esnext();
+ }
+
+ @Override
+ protected INode parseFunction(
+ Position startLoc, boolean isStatement, boolean allowExpressionBody, boolean isAsync) {
+ if (isFunctionSent(isStatement)) {
+ Identifier meta = this.finishNode(new Identifier(new SourceLocation(startLoc), "function"));
+ this.next();
+ Identifier property = parseIdent(true);
+ if (!property.getName().equals("sent"))
+ this.raiseRecoverable(
+ property, "The only valid meta property for function is function.sent");
+ return this.finishNode(new MetaProperty(new SourceLocation(startLoc), meta, property));
+ }
+
+ return super.parseFunction(startLoc, isStatement, allowExpressionBody, isAsync);
+ }
+
+ protected boolean isFunctionSent(boolean isStatement) {
+ return functionSent() && !isStatement && inGenerator && !inAsync && this.type == TokenType.dot;
+ }
+
+ /*
+ * Support for proposed language feature "Class and Property Decorators"
+ * (https://github.com/wycats/javascript-decorators)
+ */
+ private boolean decorators() {
+ return options.esnext();
+ }
+
+ protected TokenType at = new TokenType(new Properties("@").beforeExpr());
+
+ @Override
+ protected Token getTokenFromCode(int code) {
+ if (decorators() && code == 64) {
+ ++this.pos;
+ return this.finishToken(at);
+ }
+ if (functionBind() && code == 58 && charAt(this.pos + 1) == 58) {
+ this.pos += 2;
+ return this.finishToken(doubleColon);
+ }
+ return super.getTokenFromCode(code);
+ }
+
+ @Override
+ protected Statement parseStatement(boolean declaration, boolean topLevel, Set exports) {
+ List decorators = this.parseDecorators();
+ Statement stmt = super.parseStatement(declaration, topLevel, exports);
+
+ if (!decorators.isEmpty()) {
+ if (stmt instanceof ExportDeclaration) {
+ Node exported = null;
+ if (stmt instanceof ExportDefaultDeclaration) {
+ exported = ((ExportDefaultDeclaration) stmt).getDeclaration();
+ } else if (stmt instanceof ExportNamedDeclaration) {
+ exported = ((ExportNamedDeclaration) stmt).getDeclaration();
+ }
+ if (exported instanceof ClassDeclaration) {
+ ((ClassDeclaration) exported).addDecorators(decorators);
+ } else if (exported instanceof ClassExpression) {
+ ((ClassExpression) exported).addDecorators(decorators);
+ } else {
+ this.raise(stmt, "Decorators can only be attached to class exports");
+ }
+ } else if (stmt instanceof ClassDeclaration) {
+ ((ClassDeclaration) stmt).addDecorators(decorators);
+ } else if (stmt != null) {
+ this.raise(stmt, "Leading decorators must be attached to a class declaration");
+ }
+ }
+
+ return stmt;
+ }
+
+ @Override
+ protected Expression parseExprAtom(DestructuringErrors refDestructuringErrors) {
+ if (this.type == at) {
+ List decorators = parseDecorators();
+ ClassExpression ce = (ClassExpression) this.parseClass(startLoc, false);
+ ce.addDecorators(decorators);
+ return ce;
+ }
+ if (this.type == doubleColon) {
+ SourceLocation startLoc = new SourceLocation(this.startLoc);
+ this.next();
+ int innerStart = this.start;
+ Position innerStartLoc = this.startLoc;
+ Expression callee = parseSubscripts(parseExprAtom(null), innerStart, innerStartLoc, true);
+ if (!(callee instanceof MemberExpression))
+ this.raiseRecoverable(callee, "Binding should be performed on a member expression.");
+ return this.finishNode(new BindExpression(startLoc, null, callee));
+ }
+ if (this.type == TokenType._import) {
+ Position startLoc = this.startLoc;
+ this.next();
+ this.expect(TokenType.parenL);
+ return parseDynamicImport(startLoc);
+ }
+ return super.parseExprAtom(refDestructuringErrors);
+ }
+
+ @Override
+ protected MemberDefinition> parseClassMember(boolean hadConstructor) {
+ List decorators = parseDecorators();
+ MemberDefinition> member = super.parseClassMember(hadConstructor);
+ if (!decorators.isEmpty() && member.isConstructor())
+ this.raiseRecoverable(member, "Decorators cannot be attached to class constructors.");
+ member.addDecorators(decorators);
+ return member;
+ }
+
+ public List parseDecorators() {
+ List result = new ArrayList();
+ while (this.type == at) result.add(this.parseDecorator());
+ return result;
+ }
+
+ private Decorator parseDecorator() {
+ Position start = startLoc;
+ this.next();
+ Expression body = parseDecoratorBody();
+ Decorator decorator = new Decorator(new SourceLocation(start), body);
+ return this.finishNode(decorator);
+ }
+
+ protected Expression parseDecoratorBody() {
+ Expression base;
+ int startPos = this.start;
+ Position startLoc = this.startLoc;
+ if (this.type == TokenType.parenL) {
+ base = parseParenExpression();
+ } else {
+ base = parseIdent(true);
+ }
+ return parseSubscripts(base, startPos, startLoc, false);
+ }
+
+ /*
+ * Support for proposed extensions to `export`
+ * (http://leebyron.com/ecmascript-export-ns-from and http://leebyron.com/ecmascript-export-default-from)
+ */
+ private boolean exportExtensions() {
+ return options.esnext();
+ }
+
+ @Override
+ protected ExportDeclaration parseExportRest(SourceLocation exportStart, Set exports) {
+ if (exportExtensions() && this.isExportDefaultSpecifier()) {
+ Position specStart = this.startLoc;
+ Identifier exported = this.parseIdent(true);
+ ExportDefaultSpecifier defaultSpec =
+ this.finishNode(new ExportDefaultSpecifier(new SourceLocation(specStart), exported));
+ List specifiers = CollectionUtil.makeList(defaultSpec);
+ if (this.type == TokenType.comma && this.lookahead(1, true).equals("*")) {
+ this.next();
+ specStart = this.startLoc;
+ this.expect(TokenType.star);
+ this.expectContextual("as");
+ exported = this.parseIdent(false);
+ ExportNamespaceSpecifier nsSpec =
+ this.finishNode(new ExportNamespaceSpecifier(new SourceLocation(specStart), exported));
+ specifiers.add(nsSpec);
+ } else {
+ this.parseExportSpecifiersMaybe(specifiers, exports);
+ }
+ Literal source = (Literal) this.parseExportFrom(specifiers, null, true);
+ return this.finishNode(new ExportNamedDeclaration(exportStart, null, specifiers, source));
+ }
+
+ return super.parseExportRest(exportStart, exports);
+ }
+
+ @Override
+ protected ExportDeclaration parseExportAll(
+ SourceLocation exportStart, Position starLoc, Set exports) {
+ if (exportExtensions() && this.eatContextual("as")) {
+ Identifier exported = this.parseIdent(false);
+ ExportNamespaceSpecifier nsSpec =
+ this.finishNode(new ExportNamespaceSpecifier(new SourceLocation(starLoc), exported));
+ List specifiers = CollectionUtil.makeList(nsSpec);
+ this.parseExportSpecifiersMaybe(specifiers, exports);
+ Literal source = (Literal) this.parseExportFrom(specifiers, null, true);
+ return this.finishNode(new ExportNamedDeclaration(exportStart, null, specifiers, source));
+ }
+
+ return super.parseExportAll(exportStart, starLoc, exports);
+ }
+
+ private boolean isExportDefaultSpecifier() {
+ if (this.type == TokenType.name) {
+ return !this.value.equals("type")
+ && !this.value.equals("async")
+ && !this.value.equals("interface")
+ && !this.value.equals("let");
+ }
+
+ if (this.type != TokenType._default) return false;
+
+ return this.lookahead(1, true).equals(",") || this.lookaheadIsIdent("from", true);
+ }
+
+ private void parseExportSpecifiersMaybe(List specifiers, Set exports) {
+ if (this.eat(TokenType.comma)) {
+ specifiers.addAll(this.parseExportSpecifiers(exports));
+ }
+ }
+
+ /*
+ * Support for proposed language feature "Function Bind Syntax"
+ * (https://github.com/tc39/proposal-bind-operator)
+ */
+ private boolean functionBind() {
+ return options.esnext();
+ }
+
+ protected TokenType doubleColon = new TokenType(new Properties("::").beforeExpr());
+
+ @Override
+ protected Pair parseSubscript(
+ Expression base, Position startLoc, boolean noCalls) {
+ if (!noCalls && this.eat(doubleColon)) {
+ Expression callee = parseSubscripts(parseExprAtom(null), this.start, this.startLoc, true);
+ BindExpression bind = new BindExpression(new SourceLocation(startLoc), base, callee);
+ return Pair.make(this.finishNode(bind), true);
+ }
+ return super.parseSubscript(base, startLoc, noCalls);
+ }
+
+ /*
+ * Support for proposed language feature "Optional Catch Binding"
+ * (https://github.com/tc39/proposal-optional-catch-binding)
+ */
+ @Override
+ protected CatchClause parseCatchClause(Position startLoc) {
+ this.next();
+ Expression param = null;
+ if (this.eat(TokenType.parenL)) {
+ param = this.parseBindingAtom();
+ this.checkLVal(param, true, null);
+ this.expect(TokenType.parenR);
+ } else if (!options.esnext()) {
+ this.unexpected();
+ }
+ BlockStatement catchBody = this.parseBlock(false);
+ return this.finishNode(
+ new CatchClause(new SourceLocation(startLoc), (IPattern) param, null, catchBody));
+ }
+
+ /*
+ * Support for proposed language feature "Dynamic import"
+ * (https://github.com/tc39/proposal-dynamic-import).
+ */
+ @Override
+ protected Statement parseImport(Position startLoc) {
+ if (!options.esnext()) return super.parseImport(startLoc);
+
+ int startPos = this.start;
+ SourceLocation loc = new SourceLocation(startLoc);
+ this.next();
+ if (this.eat(TokenType.parenL)) {
+ DynamicImport di = parseDynamicImport(startLoc);
+ Expression expr = parseSubscripts(di, startPos, startLoc, false);
+ return parseExpressionStatement(false, startLoc, expr);
+ } else {
+ return super.parseImportRest(loc);
+ }
+ }
+
+ /**
+ * Parses a dynamic import, assuming that the keyword `import` and the opening parenthesis have
+ * already been consumed.
+ */
+ private DynamicImport parseDynamicImport(Position startLoc) {
+ Expression source = parseMaybeAssign(false, null, null);
+ this.expect(TokenType.parenR);
+ DynamicImport di = this.finishNode(new DynamicImport(new SourceLocation(startLoc), source));
+ return di;
+ }
+
+ /*
+ * Support for proposed language feature "Asynchronous iteration"
+ * (https://github.com/tc39/proposal-async-iteration)
+ */
+ @Override
+ protected Statement parseForStatement(Position startLoc) {
+ int startPos = this.start;
+ boolean isAwait = false;
+ if (this.inAsync && this.eatContextual("await")) isAwait = true;
+ Statement forStmt = super.parseForStatement(startLoc);
+ if (isAwait) {
+ if (forStmt instanceof ForOfStatement) ((ForOfStatement) forStmt).setAwait(true);
+ else this.raiseRecoverable(startPos, "Only for-of statements can be annotated with 'await'.");
+ }
+ return forStmt;
+ }
+
+ @Override
+ protected boolean parseGeneratorMarker(boolean isAsync) {
+ // always allow `*`, even if `isAsync` is true
+ return this.eat(TokenType.star);
+ }
+
+ /*
+ * Support for proposed language feature "Numeric separators"
+ * (https://github.com/tc39/proposal-numeric-separator)
+ */
+
+ @Override
+ protected Number readInt(int radix, Integer len) {
+ // implementation mostly copied from super class
+ int start = this.pos, code = -1;
+ double total = 0;
+ // no leading underscore
+ boolean underscoreAllowed = false;
+
+ for (int i = 0, e = len == null ? Integer.MAX_VALUE : len; i < e; ++i) {
+ if (this.pos >= this.input.length()) break;
+ code = this.input.charAt(this.pos);
+
+ if (code == '_') {
+ if (underscoreAllowed) {
+ // no adjacent underscores
+ underscoreAllowed = false;
+ ++this.pos;
+ continue;
+ }
+ } else {
+ underscoreAllowed = true;
+ }
+
+ int val;
+ if (code >= 97) val = code - 97 + 10; // a
+ else if (code >= 65) val = code - 65 + 10; // A
+ else if (code >= 48 && code <= 57) val = code - 48; // 0-9
+ else val = Integer.MAX_VALUE;
+ if (val >= radix) break;
+
+ ++this.pos;
+ total = total * radix + val;
+ }
+ if (this.pos == start || len != null && this.pos - start != len) return null;
+
+ if (code == '_')
+ // no trailing underscore
+ return null;
+
+ return total;
+ }
}
diff --git a/javascript/extractor/src/com/semmle/jcorn/Identifiers.java b/javascript/extractor/src/com/semmle/jcorn/Identifiers.java
index 363ab88eb422..6cda9c40a88b 100644
--- a/javascript/extractor/src/com/semmle/jcorn/Identifiers.java
+++ b/javascript/extractor/src/com/semmle/jcorn/Identifiers.java
@@ -8,110 +8,149 @@
/// identifier.js
public class Identifiers {
- public static enum Dialect {
- ECMA_3, ECMA_5, ECMA_6, ECMA_7, ECMA_8, STRICT, STRICT_BIND
- }
-
- // Reserved word lists for various dialects of the language
- public static final Map> reservedWords = new LinkedHashMap<>();
- static {
- reservedWords.put(Dialect.ECMA_3, stringSet("abstract boolean byte char class double enum export extends final float goto implements import int interface long native package private protected public short static super synchronized throws transient volatile"));
- reservedWords.put(Dialect.ECMA_5, stringSet("class enum extends super const export import"));
- reservedWords.put(Dialect.ECMA_6, stringSet("enum"));
- reservedWords.put(Dialect.ECMA_7, stringSet("enum"));
- reservedWords.put(Dialect.ECMA_8, stringSet("enum"));
- reservedWords.put(Dialect.STRICT, stringSet("implements interface let package private protected public static yield"));
- reservedWords.put(Dialect.STRICT_BIND, stringSet("eval arguments"));
- }
-
- // And the keywords
- private static final String ecma5AndLessKeywords = "break case catch continue debugger default do else finally for function if return switch throw try var while with null true false instanceof typeof void delete new in this";
- private static final String ecma6Keywords = ecma5AndLessKeywords + " const class extends export import super";
-
- public static final Map> keywords = new LinkedHashMap<>();
- static {
- keywords.put(Dialect.ECMA_5, stringSet(ecma5AndLessKeywords));
- keywords.put(Dialect.ECMA_6, stringSet(ecma6Keywords));
- }
-
- private static Set stringSet(String words) {
- Set result = new LinkedHashSet();
- for (String word : words.split(" "))
- result.add(word);
- return result;
- }
-
- // ## Character categories
-
- private static final String nonASCIIidentifierStartChars =
- "\\xaa\\xb5\\xba\\xc0-\\xd6\\xd8-\\xf6\\xf8-\\u02c1\\u02c6-\\u02d1\\u02e0-\\u02e4\\u02ec\\u02ee\\u0370-\\u0374\\u0376\\u0377\\u037a-\\u037d\\u037f\\u0386\\u0388-\\u038a\\u038c\\u038e-\\u03a1\\u03a3-\\u03f5\\u03f7-\\u0481\\u048a-\\u052f\\u0531-\\u0556\\u0559\\u0561-\\u0587\\u05d0-\\u05ea\\u05f0-\\u05f2\\u0620-\\u064a\\u066e\\u066f\\u0671-\\u06d3\\u06d5\\u06e5\\u06e6\\u06ee\\u06ef\\u06fa-\\u06fc\\u06ff\\u0710\\u0712-\\u072f\\u074d-\\u07a5\\u07b1\\u07ca-\\u07ea\\u07f4\\u07f5\\u07fa\\u0800-\\u0815\\u081a\\u0824\\u0828\\u0840-\\u0858\\u08a0-\\u08b4\\u08b6-\\u08bd\\u0904-\\u0939\\u093d\\u0950\\u0958-\\u0961\\u0971-\\u0980\\u0985-\\u098c\\u098f\\u0990\\u0993-\\u09a8\\u09aa-\\u09b0\\u09b2\\u09b6-\\u09b9\\u09bd\\u09ce\\u09dc\\u09dd\\u09df-\\u09e1\\u09f0\\u09f1\\u0a05-\\u0a0a\\u0a0f\\u0a10\\u0a13-\\u0a28\\u0a2a-\\u0a30\\u0a32\\u0a33\\u0a35\\u0a36\\u0a38\\u0a39\\u0a59-\\u0a5c\\u0a5e\\u0a72-\\u0a74\\u0a85-\\u0a8d\\u0a8f-\\u0a91\\u0a93-\\u0aa8\\u0aaa-\\u0ab0\\u0ab2\\u0ab3\\u0ab5-\\u0ab9\\u0abd\\u0ad0\\u0ae0\\u0ae1\\u0af9\\u0b05-\\u0b0c\\u0b0f\\u0b10\\u0b13-\\u0b28\\u0b2a-\\u0b30\\u0b32\\u0b33\\u0b35-\\u0b39\\u0b3d\\u0b5c\\u0b5d\\u0b5f-\\u0b61\\u0b71\\u0b83\\u0b85-\\u0b8a\\u0b8e-\\u0b90\\u0b92-\\u0b95\\u0b99\\u0b9a\\u0b9c\\u0b9e\\u0b9f\\u0ba3\\u0ba4\\u0ba8-\\u0baa\\u0bae-\\u0bb9\\u0bd0\\u0c05-\\u0c0c\\u0c0e-\\u0c10\\u0c12-\\u0c28\\u0c2a-\\u0c39\\u0c3d\\u0c58-\\u0c5a\\u0c60\\u0c61\\u0c80\\u0c85-\\u0c8c\\u0c8e-\\u0c90\\u0c92-\\u0ca8\\u0caa-\\u0cb3\\u0cb5-\\u0cb9\\u0cbd\\u0cde\\u0ce0\\u0ce1\\u0cf1\\u0cf2\\u0d05-\\u0d0c\\u0d0e-\\u0d10\\u0d12-\\u0d3a\\u0d3d\\u0d4e\\u0d54-\\u0d56\\u0d5f-\\u0d61\\u0d7a-\\u0d7f\\u0d85-\\u0d96\\u0d9a-\\u0db1\\u0db3-\\u0dbb\\u0dbd\\u0dc0-\\u0dc6\\u0e01-\\u0e30\\u0e32\\u0e33\\u0e40-\\u0e46\\u0e81\\u0e82\\u0e84\\u0e87\\u0e88\\u0e8a\\u0e8d\\u0e94-\\u0e97\\u0e99-\\u0e9f\\u0ea1-\\u0ea3\\u0ea5\\u0ea7\\u0eaa\\u0eab\\u0ead-\\u0eb0\\u0eb2\\u0eb3\\u0ebd\\u0ec0-\\u0ec4\\u0ec6\\u0edc-\\u0edf\\u0f00\\u0f40-\\u0f47\\u0f49-\\u0f6c\\u0f88-\\u0f8c\\u1000-\\u102a\\u103f\\u1050-\\u1055\\u105a-\\u105d\\u1061\\u1065\\u1066\\u106e-\\u1070\\u1075-\\u1081\\u108e\\u10a0-\\u10c5\\u10c7\\u10cd\\u10d0-\\u10fa\\u10fc-\\u1248\\u124a-\\u124d\\u1250-\\u1256\\u1258\\u125a-\\u125d\\u1260-\\u1288\\u128a-\\u128d\\u1290-\\u12b0\\u12b2-\\u12b5\\u12b8-\\u12be\\u12c0\\u12c2-\\u12c5\\u12c8-\\u12d6\\u12d8-\\u1310\\u1312-\\u1315\\u1318-\\u135a\\u1380-\\u138f\\u13a0-\\u13f5\\u13f8-\\u13fd\\u1401-\\u166c\\u166f-\\u167f\\u1681-\\u169a\\u16a0-\\u16ea\\u16ee-\\u16f8\\u1700-\\u170c\\u170e-\\u1711\\u1720-\\u1731\\u1740-\\u1751\\u1760-\\u176c\\u176e-\\u1770\\u1780-\\u17b3\\u17d7\\u17dc\\u1820-\\u1877\\u1880-\\u18a8\\u18aa\\u18b0-\\u18f5\\u1900-\\u191e\\u1950-\\u196d\\u1970-\\u1974\\u1980-\\u19ab\\u19b0-\\u19c9\\u1a00-\\u1a16\\u1a20-\\u1a54\\u1aa7\\u1b05-\\u1b33\\u1b45-\\u1b4b\\u1b83-\\u1ba0\\u1bae\\u1baf\\u1bba-\\u1be5\\u1c00-\\u1c23\\u1c4d-\\u1c4f\\u1c5a-\\u1c7d\\u1c80-\\u1c88\\u1ce9-\\u1cec\\u1cee-\\u1cf1\\u1cf5\\u1cf6\\u1d00-\\u1dbf\\u1e00-\\u1f15\\u1f18-\\u1f1d\\u1f20-\\u1f45\\u1f48-\\u1f4d\\u1f50-\\u1f57\\u1f59\\u1f5b\\u1f5d\\u1f5f-\\u1f7d\\u1f80-\\u1fb4\\u1fb6-\\u1fbc\\u1fbe\\u1fc2-\\u1fc4\\u1fc6-\\u1fcc\\u1fd0-\\u1fd3\\u1fd6-\\u1fdb\\u1fe0-\\u1fec\\u1ff2-\\u1ff4\\u1ff6-\\u1ffc\\u2071\\u207f\\u2090-\\u209c\\u2102\\u2107\\u210a-\\u2113\\u2115\\u2118-\\u211d\\u2124\\u2126\\u2128\\u212a-\\u2139\\u213c-\\u213f\\u2145-\\u2149\\u214e\\u2160-\\u2188\\u2c00-\\u2c2e\\u2c30-\\u2c5e\\u2c60-\\u2ce4\\u2ceb-\\u2cee\\u2cf2\\u2cf3\\u2d00-\\u2d25\\u2d27\\u2d2d\\u2d30-\\u2d67\\u2d6f\\u2d80-\\u2d96\\u2da0-\\u2da6\\u2da8-\\u2dae\\u2db0-\\u2db6\\u2db8-\\u2dbe\\u2dc0-\\u2dc6\\u2dc8-\\u2dce\\u2dd0-\\u2dd6\\u2dd8-\\u2dde\\u3005-\\u3007\\u3021-\\u3029\\u3031-\\u3035\\u3038-\\u303c\\u3041-\\u3096\\u309b-\\u309f\\u30a1-\\u30fa\\u30fc-\\u30ff\\u3105-\\u312d\\u3131-\\u318e\\u31a0-\\u31ba\\u31f0-\\u31ff\\u3400-\\u4db5\\u4e00-\\u9fd5\\ua000-\\ua48c\\ua4d0-\\ua4fd\\ua500-\\ua60c\\ua610-\\ua61f\\ua62a\\ua62b\\ua640-\\ua66e\\ua67f-\\ua69d\\ua6a0-\\ua6ef\\ua717-\\ua71f\\ua722-\\ua788\\ua78b-\\ua7ae\\ua7b0-\\ua7b7\\ua7f7-\\ua801\\ua803-\\ua805\\ua807-\\ua80a\\ua80c-\\ua822\\ua840-\\ua873\\ua882-\\ua8b3\\ua8f2-\\ua8f7\\ua8fb\\ua8fd\\ua90a-\\ua925\\ua930-\\ua946\\ua960-\\ua97c\\ua984-\\ua9b2\\ua9cf\\ua9e0-\\ua9e4\\ua9e6-\\ua9ef\\ua9fa-\\ua9fe\\uaa00-\\uaa28\\uaa40-\\uaa42\\uaa44-\\uaa4b\\uaa60-\\uaa76\\uaa7a\\uaa7e-\\uaaaf\\uaab1\\uaab5\\uaab6\\uaab9-\\uaabd\\uaac0\\uaac2\\uaadb-\\uaadd\\uaae0-\\uaaea\\uaaf2-\\uaaf4\\uab01-\\uab06\\uab09-\\uab0e\\uab11-\\uab16\\uab20-\\uab26\\uab28-\\uab2e\\uab30-\\uab5a\\uab5c-\\uab65\\uab70-\\uabe2\\uac00-\\ud7a3\\ud7b0-\\ud7c6\\ud7cb-\\ud7fb\\uf900-\\ufa6d\\ufa70-\\ufad9\\ufb00-\\ufb06\\ufb13-\\ufb17\\ufb1d\\ufb1f-\\ufb28\\ufb2a-\\ufb36\\ufb38-\\ufb3c\\ufb3e\\ufb40\\ufb41\\ufb43\\ufb44\\ufb46-\\ufbb1\\ufbd3-\\ufd3d\\ufd50-\\ufd8f\\ufd92-\\ufdc7\\ufdf0-\\ufdfb\\ufe70-\\ufe74\\ufe76-\\ufefc\\uff21-\\uff3a\\uff41-\\uff5a\\uff66-\\uffbe\\uffc2-\\uffc7\\uffca-\\uffcf\\uffd2-\\uffd7\\uffda-\\uffdc";
- private static final String nonASCIIidentifierChars =
- "\\u200c\\u200d\\xb7\\u0300-\\u036f\\u0387\\u0483-\\u0487\\u0591-\\u05bd\\u05bf\\u05c1\\u05c2\\u05c4\\u05c5\\u05c7\\u0610-\\u061a\\u064b-\\u0669\\u0670\\u06d6-\\u06dc\\u06df-\\u06e4\\u06e7\\u06e8\\u06ea-\\u06ed\\u06f0-\\u06f9\\u0711\\u0730-\\u074a\\u07a6-\\u07b0\\u07c0-\\u07c9\\u07eb-\\u07f3\\u0816-\\u0819\\u081b-\\u0823\\u0825-\\u0827\\u0829-\\u082d\\u0859-\\u085b\\u08d4-\\u08e1\\u08e3-\\u0903\\u093a-\\u093c\\u093e-\\u094f\\u0951-\\u0957\\u0962\\u0963\\u0966-\\u096f\\u0981-\\u0983\\u09bc\\u09be-\\u09c4\\u09c7\\u09c8\\u09cb-\\u09cd\\u09d7\\u09e2\\u09e3\\u09e6-\\u09ef\\u0a01-\\u0a03\\u0a3c\\u0a3e-\\u0a42\\u0a47\\u0a48\\u0a4b-\\u0a4d\\u0a51\\u0a66-\\u0a71\\u0a75\\u0a81-\\u0a83\\u0abc\\u0abe-\\u0ac5\\u0ac7-\\u0ac9\\u0acb-\\u0acd\\u0ae2\\u0ae3\\u0ae6-\\u0aef\\u0b01-\\u0b03\\u0b3c\\u0b3e-\\u0b44\\u0b47\\u0b48\\u0b4b-\\u0b4d\\u0b56\\u0b57\\u0b62\\u0b63\\u0b66-\\u0b6f\\u0b82\\u0bbe-\\u0bc2\\u0bc6-\\u0bc8\\u0bca-\\u0bcd\\u0bd7\\u0be6-\\u0bef\\u0c00-\\u0c03\\u0c3e-\\u0c44\\u0c46-\\u0c48\\u0c4a-\\u0c4d\\u0c55\\u0c56\\u0c62\\u0c63\\u0c66-\\u0c6f\\u0c81-\\u0c83\\u0cbc\\u0cbe-\\u0cc4\\u0cc6-\\u0cc8\\u0cca-\\u0ccd\\u0cd5\\u0cd6\\u0ce2\\u0ce3\\u0ce6-\\u0cef\\u0d01-\\u0d03\\u0d3e-\\u0d44\\u0d46-\\u0d48\\u0d4a-\\u0d4d\\u0d57\\u0d62\\u0d63\\u0d66-\\u0d6f\\u0d82\\u0d83\\u0dca\\u0dcf-\\u0dd4\\u0dd6\\u0dd8-\\u0ddf\\u0de6-\\u0def\\u0df2\\u0df3\\u0e31\\u0e34-\\u0e3a\\u0e47-\\u0e4e\\u0e50-\\u0e59\\u0eb1\\u0eb4-\\u0eb9\\u0ebb\\u0ebc\\u0ec8-\\u0ecd\\u0ed0-\\u0ed9\\u0f18\\u0f19\\u0f20-\\u0f29\\u0f35\\u0f37\\u0f39\\u0f3e\\u0f3f\\u0f71-\\u0f84\\u0f86\\u0f87\\u0f8d-\\u0f97\\u0f99-\\u0fbc\\u0fc6\\u102b-\\u103e\\u1040-\\u1049\\u1056-\\u1059\\u105e-\\u1060\\u1062-\\u1064\\u1067-\\u106d\\u1071-\\u1074\\u1082-\\u108d\\u108f-\\u109d\\u135d-\\u135f\\u1369-\\u1371\\u1712-\\u1714\\u1732-\\u1734\\u1752\\u1753\\u1772\\u1773\\u17b4-\\u17d3\\u17dd\\u17e0-\\u17e9\\u180b-\\u180d\\u1810-\\u1819\\u18a9\\u1920-\\u192b\\u1930-\\u193b\\u1946-\\u194f\\u19d0-\\u19da\\u1a17-\\u1a1b\\u1a55-\\u1a5e\\u1a60-\\u1a7c\\u1a7f-\\u1a89\\u1a90-\\u1a99\\u1ab0-\\u1abd\\u1b00-\\u1b04\\u1b34-\\u1b44\\u1b50-\\u1b59\\u1b6b-\\u1b73\\u1b80-\\u1b82\\u1ba1-\\u1bad\\u1bb0-\\u1bb9\\u1be6-\\u1bf3\\u1c24-\\u1c37\\u1c40-\\u1c49\\u1c50-\\u1c59\\u1cd0-\\u1cd2\\u1cd4-\\u1ce8\\u1ced\\u1cf2-\\u1cf4\\u1cf8\\u1cf9\\u1dc0-\\u1df5\\u1dfb-\\u1dff\\u203f\\u2040\\u2054\\u20d0-\\u20dc\\u20e1\\u20e5-\\u20f0\\u2cef-\\u2cf1\\u2d7f\\u2de0-\\u2dff\\u302a-\\u302f\\u3099\\u309a\\ua620-\\ua629\\ua66f\\ua674-\\ua67d\\ua69e\\ua69f\\ua6f0\\ua6f1\\ua802\\ua806\\ua80b\\ua823-\\ua827\\ua880\\ua881\\ua8b4-\\ua8c5\\ua8d0-\\ua8d9\\ua8e0-\\ua8f1\\ua900-\\ua909\\ua926-\\ua92d\\ua947-\\ua953\\ua980-\\ua983\\ua9b3-\\ua9c0\\ua9d0-\\ua9d9\\ua9e5\\ua9f0-\\ua9f9\\uaa29-\\uaa36\\uaa43\\uaa4c\\uaa4d\\uaa50-\\uaa59\\uaa7b-\\uaa7d\\uaab0\\uaab2-\\uaab4\\uaab7\\uaab8\\uaabe\\uaabf\\uaac1\\uaaeb-\\uaaef\\uaaf5\\uaaf6\\uabe3-\\uabea\\uabec\\uabed\\uabf0-\\uabf9\\ufb1e\\ufe00-\\ufe0f\\ufe20-\\ufe2f\\ufe33\\ufe34\\ufe4d-\\ufe4f\\uff10-\\uff19\\uff3f";
-
- private static Pattern nonASCIIidentifierStartPattern;
- private static Pattern nonASCIIidentifierPattern;
-
- private static Pattern nonASCIIidentifierStart() {
- if (nonASCIIidentifierStartPattern == null)
- nonASCIIidentifierStartPattern = Pattern.compile("[" + nonASCIIidentifierStartChars + "]");
- return nonASCIIidentifierStartPattern;
- }
-
- private static Pattern nonASCIIidentifier() {
- if (nonASCIIidentifierPattern == null)
- nonASCIIidentifierPattern = Pattern.compile("[" + nonASCIIidentifierStartChars + nonASCIIidentifierChars + "]");
- return nonASCIIidentifierPattern;
- }
-
- // These are a run-length and offset encoded representation of the
- // >0xffff code points that are a valid part of identifiers. The
- // offset starts at 0x10000, and each pair of numbers represents an
- // offset to the next range, and then a size of the range. They were
- // generated by bin/generate-identifier-regex.js
- private static final int[] astralIdentifierStartCodes = {0,11,2,25,2,18,2,1,2,14,3,13,35,122,70,52,268,28,4,48,48,31,17,26,6,37,11,29,3,35,5,7,2,4,43,157,19,35,5,35,5,39,9,51,157,310,10,21,11,7,153,5,3,0,2,43,2,1,4,0,3,22,11,22,10,30,66,18,2,1,11,21,11,25,71,55,7,1,65,0,16,3,2,2,2,26,45,28,4,28,36,7,2,27,28,53,11,21,11,18,14,17,111,72,56,50,14,50,785,52,76,44,33,24,27,35,42,34,4,0,13,47,15,3,22,0,2,0,36,17,2,24,85,6,2,0,2,3,2,14,2,9,8,46,39,7,3,1,3,21,2,6,2,1,2,4,4,0,19,0,13,4,159,52,19,3,54,47,21,1,2,0,185,46,42,3,37,47,21,0,60,42,86,25,391,63,32,0,449,56,264,8,2,36,18,0,50,29,881,921,103,110,18,195,2749,1070,4050,582,8634,568,8,30,114,29,19,47,17,3,32,20,6,18,881,68,12,0,67,12,65,0,32,6124,20,754,9486,1,3071,106,6,12,4,8,8,9,5991,84,2,70,2,1,3,0,3,1,3,3,2,11,2,0,2,6,2,64,2,3,3,7,2,6,2,27,2,3,2,4,2,0,4,6,2,339,3,24,2,24,2,30,2,24,2,30,2,24,2,30,2,24,2,30,2,24,2,7,4149,196,60,67,1213,3,2,26,2,1,2,0,3,0,2,9,2,3,2,0,2,0,7,0,5,0,2,0,2,0,2,2,2,1,2,0,3,0,2,0,2,0,2,0,2,0,2,1,2,0,3,3,2,6,2,3,2,3,2,0,2,9,2,16,6,2,2,4,2,16,4421,42710,42,4148,12,221,3,5761,10591,541};
- private static final int[] astralIdentifierCodes = {509,0,227,0,150,4,294,9,1368,2,2,1,6,3,41,2,5,0,166,1,1306,2,54,14,32,9,16,3,46,10,54,9,7,2,37,13,2,9,52,0,13,2,49,13,10,2,4,9,83,11,7,0,161,11,6,9,7,3,57,0,2,6,3,1,3,2,10,0,11,1,3,6,4,4,193,17,10,9,87,19,13,9,214,6,3,8,28,1,83,16,16,9,82,12,9,9,84,14,5,9,423,9,838,7,2,7,17,9,57,21,2,13,19882,9,135,4,60,6,26,9,1016,45,17,3,19723,1,5319,4,4,5,9,7,3,6,31,3,149,2,1418,49,513,54,5,49,9,0,15,0,23,4,2,14,1361,6,2,16,3,6,2,1,2,4,2214,6,110,6,6,9,792487,239};
-
- // This has a complexity linear to the value of the code. The
- // assumption is that looking up astral identifier characters is
- // rare.
- private static boolean isInAstralSet(int code, int[] set) {
- int pos = 0x10000;
- for (int i = 0; i < set.length; i += 2) {
- pos += set[i];
- if (pos > code)
- return false;
- pos += set[i + 1];
- if (pos >= code)
- return true;
- }
- return false;
- }
-
- // Test whether a given character code starts an identifier.
-
- public static boolean isIdentifierStart(int code, boolean astral) {
- if (code < 65) return code == 36;
- if (code < 91) return true;
- if (code < 97) return code == 95;
- if (code < 123) return true;
- if (code <= 0xffff) return code >= 0xaa && nonASCIIidentifierStart().matcher(str(code)).matches();
- if (!astral) return false;
- return isInAstralSet(code, astralIdentifierStartCodes);
- }
-
- public static boolean isIdentifierChar(int code, boolean astral) {
- if (code < 48) return code == 36;
- if (code < 58) return true;
- if (code < 65) return false;
- if (code < 91) return true;
- if (code < 97) return code == 95;
- if (code < 123) return true;
- if (code <= 0xffff) return code >= 0xaa && nonASCIIidentifier().matcher(str(code)).matches();
- if (!astral) return false;
- return isInAstralSet(code, astralIdentifierStartCodes) || isInAstralSet(code, astralIdentifierCodes);
- }
-
- private static String str(int i) {
- return new String(Character.toChars(i));
- }
+ public static enum Dialect {
+ ECMA_3,
+ ECMA_5,
+ ECMA_6,
+ ECMA_7,
+ ECMA_8,
+ STRICT,
+ STRICT_BIND
+ }
+
+ // Reserved word lists for various dialects of the language
+ public static final Map> reservedWords = new LinkedHashMap<>();
+
+ static {
+ reservedWords.put(
+ Dialect.ECMA_3,
+ stringSet(
+ "abstract boolean byte char class double enum export extends final float goto implements import int interface long native package private protected public short static super synchronized throws transient volatile"));
+ reservedWords.put(Dialect.ECMA_5, stringSet("class enum extends super const export import"));
+ reservedWords.put(Dialect.ECMA_6, stringSet("enum"));
+ reservedWords.put(Dialect.ECMA_7, stringSet("enum"));
+ reservedWords.put(Dialect.ECMA_8, stringSet("enum"));
+ reservedWords.put(
+ Dialect.STRICT,
+ stringSet("implements interface let package private protected public static yield"));
+ reservedWords.put(Dialect.STRICT_BIND, stringSet("eval arguments"));
+ }
+
+ // And the keywords
+ private static final String ecma5AndLessKeywords =
+ "break case catch continue debugger default do else finally for function if return switch throw try var while with null true false instanceof typeof void delete new in this";
+ private static final String ecma6Keywords =
+ ecma5AndLessKeywords + " const class extends export import super";
+
+ public static final Map> keywords = new LinkedHashMap<>();
+
+ static {
+ keywords.put(Dialect.ECMA_5, stringSet(ecma5AndLessKeywords));
+ keywords.put(Dialect.ECMA_6, stringSet(ecma6Keywords));
+ }
+
+ private static Set stringSet(String words) {
+ Set result = new LinkedHashSet();
+ for (String word : words.split(" ")) result.add(word);
+ return result;
+ }
+
+ // ## Character categories
+
+ private static final String nonASCIIidentifierStartChars =
+ "\\xaa\\xb5\\xba\\xc0-\\xd6\\xd8-\\xf6\\xf8-\\u02c1\\u02c6-\\u02d1\\u02e0-\\u02e4\\u02ec\\u02ee\\u0370-\\u0374\\u0376\\u0377\\u037a-\\u037d\\u037f\\u0386\\u0388-\\u038a\\u038c\\u038e-\\u03a1\\u03a3-\\u03f5\\u03f7-\\u0481\\u048a-\\u052f\\u0531-\\u0556\\u0559\\u0561-\\u0587\\u05d0-\\u05ea\\u05f0-\\u05f2\\u0620-\\u064a\\u066e\\u066f\\u0671-\\u06d3\\u06d5\\u06e5\\u06e6\\u06ee\\u06ef\\u06fa-\\u06fc\\u06ff\\u0710\\u0712-\\u072f\\u074d-\\u07a5\\u07b1\\u07ca-\\u07ea\\u07f4\\u07f5\\u07fa\\u0800-\\u0815\\u081a\\u0824\\u0828\\u0840-\\u0858\\u08a0-\\u08b4\\u08b6-\\u08bd\\u0904-\\u0939\\u093d\\u0950\\u0958-\\u0961\\u0971-\\u0980\\u0985-\\u098c\\u098f\\u0990\\u0993-\\u09a8\\u09aa-\\u09b0\\u09b2\\u09b6-\\u09b9\\u09bd\\u09ce\\u09dc\\u09dd\\u09df-\\u09e1\\u09f0\\u09f1\\u0a05-\\u0a0a\\u0a0f\\u0a10\\u0a13-\\u0a28\\u0a2a-\\u0a30\\u0a32\\u0a33\\u0a35\\u0a36\\u0a38\\u0a39\\u0a59-\\u0a5c\\u0a5e\\u0a72-\\u0a74\\u0a85-\\u0a8d\\u0a8f-\\u0a91\\u0a93-\\u0aa8\\u0aaa-\\u0ab0\\u0ab2\\u0ab3\\u0ab5-\\u0ab9\\u0abd\\u0ad0\\u0ae0\\u0ae1\\u0af9\\u0b05-\\u0b0c\\u0b0f\\u0b10\\u0b13-\\u0b28\\u0b2a-\\u0b30\\u0b32\\u0b33\\u0b35-\\u0b39\\u0b3d\\u0b5c\\u0b5d\\u0b5f-\\u0b61\\u0b71\\u0b83\\u0b85-\\u0b8a\\u0b8e-\\u0b90\\u0b92-\\u0b95\\u0b99\\u0b9a\\u0b9c\\u0b9e\\u0b9f\\u0ba3\\u0ba4\\u0ba8-\\u0baa\\u0bae-\\u0bb9\\u0bd0\\u0c05-\\u0c0c\\u0c0e-\\u0c10\\u0c12-\\u0c28\\u0c2a-\\u0c39\\u0c3d\\u0c58-\\u0c5a\\u0c60\\u0c61\\u0c80\\u0c85-\\u0c8c\\u0c8e-\\u0c90\\u0c92-\\u0ca8\\u0caa-\\u0cb3\\u0cb5-\\u0cb9\\u0cbd\\u0cde\\u0ce0\\u0ce1\\u0cf1\\u0cf2\\u0d05-\\u0d0c\\u0d0e-\\u0d10\\u0d12-\\u0d3a\\u0d3d\\u0d4e\\u0d54-\\u0d56\\u0d5f-\\u0d61\\u0d7a-\\u0d7f\\u0d85-\\u0d96\\u0d9a-\\u0db1\\u0db3-\\u0dbb\\u0dbd\\u0dc0-\\u0dc6\\u0e01-\\u0e30\\u0e32\\u0e33\\u0e40-\\u0e46\\u0e81\\u0e82\\u0e84\\u0e87\\u0e88\\u0e8a\\u0e8d\\u0e94-\\u0e97\\u0e99-\\u0e9f\\u0ea1-\\u0ea3\\u0ea5\\u0ea7\\u0eaa\\u0eab\\u0ead-\\u0eb0\\u0eb2\\u0eb3\\u0ebd\\u0ec0-\\u0ec4\\u0ec6\\u0edc-\\u0edf\\u0f00\\u0f40-\\u0f47\\u0f49-\\u0f6c\\u0f88-\\u0f8c\\u1000-\\u102a\\u103f\\u1050-\\u1055\\u105a-\\u105d\\u1061\\u1065\\u1066\\u106e-\\u1070\\u1075-\\u1081\\u108e\\u10a0-\\u10c5\\u10c7\\u10cd\\u10d0-\\u10fa\\u10fc-\\u1248\\u124a-\\u124d\\u1250-\\u1256\\u1258\\u125a-\\u125d\\u1260-\\u1288\\u128a-\\u128d\\u1290-\\u12b0\\u12b2-\\u12b5\\u12b8-\\u12be\\u12c0\\u12c2-\\u12c5\\u12c8-\\u12d6\\u12d8-\\u1310\\u1312-\\u1315\\u1318-\\u135a\\u1380-\\u138f\\u13a0-\\u13f5\\u13f8-\\u13fd\\u1401-\\u166c\\u166f-\\u167f\\u1681-\\u169a\\u16a0-\\u16ea\\u16ee-\\u16f8\\u1700-\\u170c\\u170e-\\u1711\\u1720-\\u1731\\u1740-\\u1751\\u1760-\\u176c\\u176e-\\u1770\\u1780-\\u17b3\\u17d7\\u17dc\\u1820-\\u1877\\u1880-\\u18a8\\u18aa\\u18b0-\\u18f5\\u1900-\\u191e\\u1950-\\u196d\\u1970-\\u1974\\u1980-\\u19ab\\u19b0-\\u19c9\\u1a00-\\u1a16\\u1a20-\\u1a54\\u1aa7\\u1b05-\\u1b33\\u1b45-\\u1b4b\\u1b83-\\u1ba0\\u1bae\\u1baf\\u1bba-\\u1be5\\u1c00-\\u1c23\\u1c4d-\\u1c4f\\u1c5a-\\u1c7d\\u1c80-\\u1c88\\u1ce9-\\u1cec\\u1cee-\\u1cf1\\u1cf5\\u1cf6\\u1d00-\\u1dbf\\u1e00-\\u1f15\\u1f18-\\u1f1d\\u1f20-\\u1f45\\u1f48-\\u1f4d\\u1f50-\\u1f57\\u1f59\\u1f5b\\u1f5d\\u1f5f-\\u1f7d\\u1f80-\\u1fb4\\u1fb6-\\u1fbc\\u1fbe\\u1fc2-\\u1fc4\\u1fc6-\\u1fcc\\u1fd0-\\u1fd3\\u1fd6-\\u1fdb\\u1fe0-\\u1fec\\u1ff2-\\u1ff4\\u1ff6-\\u1ffc\\u2071\\u207f\\u2090-\\u209c\\u2102\\u2107\\u210a-\\u2113\\u2115\\u2118-\\u211d\\u2124\\u2126\\u2128\\u212a-\\u2139\\u213c-\\u213f\\u2145-\\u2149\\u214e\\u2160-\\u2188\\u2c00-\\u2c2e\\u2c30-\\u2c5e\\u2c60-\\u2ce4\\u2ceb-\\u2cee\\u2cf2\\u2cf3\\u2d00-\\u2d25\\u2d27\\u2d2d\\u2d30-\\u2d67\\u2d6f\\u2d80-\\u2d96\\u2da0-\\u2da6\\u2da8-\\u2dae\\u2db0-\\u2db6\\u2db8-\\u2dbe\\u2dc0-\\u2dc6\\u2dc8-\\u2dce\\u2dd0-\\u2dd6\\u2dd8-\\u2dde\\u3005-\\u3007\\u3021-\\u3029\\u3031-\\u3035\\u3038-\\u303c\\u3041-\\u3096\\u309b-\\u309f\\u30a1-\\u30fa\\u30fc-\\u30ff\\u3105-\\u312d\\u3131-\\u318e\\u31a0-\\u31ba\\u31f0-\\u31ff\\u3400-\\u4db5\\u4e00-\\u9fd5\\ua000-\\ua48c\\ua4d0-\\ua4fd\\ua500-\\ua60c\\ua610-\\ua61f\\ua62a\\ua62b\\ua640-\\ua66e\\ua67f-\\ua69d\\ua6a0-\\ua6ef\\ua717-\\ua71f\\ua722-\\ua788\\ua78b-\\ua7ae\\ua7b0-\\ua7b7\\ua7f7-\\ua801\\ua803-\\ua805\\ua807-\\ua80a\\ua80c-\\ua822\\ua840-\\ua873\\ua882-\\ua8b3\\ua8f2-\\ua8f7\\ua8fb\\ua8fd\\ua90a-\\ua925\\ua930-\\ua946\\ua960-\\ua97c\\ua984-\\ua9b2\\ua9cf\\ua9e0-\\ua9e4\\ua9e6-\\ua9ef\\ua9fa-\\ua9fe\\uaa00-\\uaa28\\uaa40-\\uaa42\\uaa44-\\uaa4b\\uaa60-\\uaa76\\uaa7a\\uaa7e-\\uaaaf\\uaab1\\uaab5\\uaab6\\uaab9-\\uaabd\\uaac0\\uaac2\\uaadb-\\uaadd\\uaae0-\\uaaea\\uaaf2-\\uaaf4\\uab01-\\uab06\\uab09-\\uab0e\\uab11-\\uab16\\uab20-\\uab26\\uab28-\\uab2e\\uab30-\\uab5a\\uab5c-\\uab65\\uab70-\\uabe2\\uac00-\\ud7a3\\ud7b0-\\ud7c6\\ud7cb-\\ud7fb\\uf900-\\ufa6d\\ufa70-\\ufad9\\ufb00-\\ufb06\\ufb13-\\ufb17\\ufb1d\\ufb1f-\\ufb28\\ufb2a-\\ufb36\\ufb38-\\ufb3c\\ufb3e\\ufb40\\ufb41\\ufb43\\ufb44\\ufb46-\\ufbb1\\ufbd3-\\ufd3d\\ufd50-\\ufd8f\\ufd92-\\ufdc7\\ufdf0-\\ufdfb\\ufe70-\\ufe74\\ufe76-\\ufefc\\uff21-\\uff3a\\uff41-\\uff5a\\uff66-\\uffbe\\uffc2-\\uffc7\\uffca-\\uffcf\\uffd2-\\uffd7\\uffda-\\uffdc";
+ private static final String nonASCIIidentifierChars =
+ "\\u200c\\u200d\\xb7\\u0300-\\u036f\\u0387\\u0483-\\u0487\\u0591-\\u05bd\\u05bf\\u05c1\\u05c2\\u05c4\\u05c5\\u05c7\\u0610-\\u061a\\u064b-\\u0669\\u0670\\u06d6-\\u06dc\\u06df-\\u06e4\\u06e7\\u06e8\\u06ea-\\u06ed\\u06f0-\\u06f9\\u0711\\u0730-\\u074a\\u07a6-\\u07b0\\u07c0-\\u07c9\\u07eb-\\u07f3\\u0816-\\u0819\\u081b-\\u0823\\u0825-\\u0827\\u0829-\\u082d\\u0859-\\u085b\\u08d4-\\u08e1\\u08e3-\\u0903\\u093a-\\u093c\\u093e-\\u094f\\u0951-\\u0957\\u0962\\u0963\\u0966-\\u096f\\u0981-\\u0983\\u09bc\\u09be-\\u09c4\\u09c7\\u09c8\\u09cb-\\u09cd\\u09d7\\u09e2\\u09e3\\u09e6-\\u09ef\\u0a01-\\u0a03\\u0a3c\\u0a3e-\\u0a42\\u0a47\\u0a48\\u0a4b-\\u0a4d\\u0a51\\u0a66-\\u0a71\\u0a75\\u0a81-\\u0a83\\u0abc\\u0abe-\\u0ac5\\u0ac7-\\u0ac9\\u0acb-\\u0acd\\u0ae2\\u0ae3\\u0ae6-\\u0aef\\u0b01-\\u0b03\\u0b3c\\u0b3e-\\u0b44\\u0b47\\u0b48\\u0b4b-\\u0b4d\\u0b56\\u0b57\\u0b62\\u0b63\\u0b66-\\u0b6f\\u0b82\\u0bbe-\\u0bc2\\u0bc6-\\u0bc8\\u0bca-\\u0bcd\\u0bd7\\u0be6-\\u0bef\\u0c00-\\u0c03\\u0c3e-\\u0c44\\u0c46-\\u0c48\\u0c4a-\\u0c4d\\u0c55\\u0c56\\u0c62\\u0c63\\u0c66-\\u0c6f\\u0c81-\\u0c83\\u0cbc\\u0cbe-\\u0cc4\\u0cc6-\\u0cc8\\u0cca-\\u0ccd\\u0cd5\\u0cd6\\u0ce2\\u0ce3\\u0ce6-\\u0cef\\u0d01-\\u0d03\\u0d3e-\\u0d44\\u0d46-\\u0d48\\u0d4a-\\u0d4d\\u0d57\\u0d62\\u0d63\\u0d66-\\u0d6f\\u0d82\\u0d83\\u0dca\\u0dcf-\\u0dd4\\u0dd6\\u0dd8-\\u0ddf\\u0de6-\\u0def\\u0df2\\u0df3\\u0e31\\u0e34-\\u0e3a\\u0e47-\\u0e4e\\u0e50-\\u0e59\\u0eb1\\u0eb4-\\u0eb9\\u0ebb\\u0ebc\\u0ec8-\\u0ecd\\u0ed0-\\u0ed9\\u0f18\\u0f19\\u0f20-\\u0f29\\u0f35\\u0f37\\u0f39\\u0f3e\\u0f3f\\u0f71-\\u0f84\\u0f86\\u0f87\\u0f8d-\\u0f97\\u0f99-\\u0fbc\\u0fc6\\u102b-\\u103e\\u1040-\\u1049\\u1056-\\u1059\\u105e-\\u1060\\u1062-\\u1064\\u1067-\\u106d\\u1071-\\u1074\\u1082-\\u108d\\u108f-\\u109d\\u135d-\\u135f\\u1369-\\u1371\\u1712-\\u1714\\u1732-\\u1734\\u1752\\u1753\\u1772\\u1773\\u17b4-\\u17d3\\u17dd\\u17e0-\\u17e9\\u180b-\\u180d\\u1810-\\u1819\\u18a9\\u1920-\\u192b\\u1930-\\u193b\\u1946-\\u194f\\u19d0-\\u19da\\u1a17-\\u1a1b\\u1a55-\\u1a5e\\u1a60-\\u1a7c\\u1a7f-\\u1a89\\u1a90-\\u1a99\\u1ab0-\\u1abd\\u1b00-\\u1b04\\u1b34-\\u1b44\\u1b50-\\u1b59\\u1b6b-\\u1b73\\u1b80-\\u1b82\\u1ba1-\\u1bad\\u1bb0-\\u1bb9\\u1be6-\\u1bf3\\u1c24-\\u1c37\\u1c40-\\u1c49\\u1c50-\\u1c59\\u1cd0-\\u1cd2\\u1cd4-\\u1ce8\\u1ced\\u1cf2-\\u1cf4\\u1cf8\\u1cf9\\u1dc0-\\u1df5\\u1dfb-\\u1dff\\u203f\\u2040\\u2054\\u20d0-\\u20dc\\u20e1\\u20e5-\\u20f0\\u2cef-\\u2cf1\\u2d7f\\u2de0-\\u2dff\\u302a-\\u302f\\u3099\\u309a\\ua620-\\ua629\\ua66f\\ua674-\\ua67d\\ua69e\\ua69f\\ua6f0\\ua6f1\\ua802\\ua806\\ua80b\\ua823-\\ua827\\ua880\\ua881\\ua8b4-\\ua8c5\\ua8d0-\\ua8d9\\ua8e0-\\ua8f1\\ua900-\\ua909\\ua926-\\ua92d\\ua947-\\ua953\\ua980-\\ua983\\ua9b3-\\ua9c0\\ua9d0-\\ua9d9\\ua9e5\\ua9f0-\\ua9f9\\uaa29-\\uaa36\\uaa43\\uaa4c\\uaa4d\\uaa50-\\uaa59\\uaa7b-\\uaa7d\\uaab0\\uaab2-\\uaab4\\uaab7\\uaab8\\uaabe\\uaabf\\uaac1\\uaaeb-\\uaaef\\uaaf5\\uaaf6\\uabe3-\\uabea\\uabec\\uabed\\uabf0-\\uabf9\\ufb1e\\ufe00-\\ufe0f\\ufe20-\\ufe2f\\ufe33\\ufe34\\ufe4d-\\ufe4f\\uff10-\\uff19\\uff3f";
+
+ private static Pattern nonASCIIidentifierStartPattern;
+ private static Pattern nonASCIIidentifierPattern;
+
+ private static Pattern nonASCIIidentifierStart() {
+ if (nonASCIIidentifierStartPattern == null)
+ nonASCIIidentifierStartPattern = Pattern.compile("[" + nonASCIIidentifierStartChars + "]");
+ return nonASCIIidentifierStartPattern;
+ }
+
+ private static Pattern nonASCIIidentifier() {
+ if (nonASCIIidentifierPattern == null)
+ nonASCIIidentifierPattern =
+ Pattern.compile("[" + nonASCIIidentifierStartChars + nonASCIIidentifierChars + "]");
+ return nonASCIIidentifierPattern;
+ }
+
+ // These are a run-length and offset encoded representation of the
+ // >0xffff code points that are a valid part of identifiers. The
+ // offset starts at 0x10000, and each pair of numbers represents an
+ // offset to the next range, and then a size of the range. They were
+ // generated by bin/generate-identifier-regex.js
+ private static final int[] astralIdentifierStartCodes = {
+ 0, 11, 2, 25, 2, 18, 2, 1, 2, 14, 3, 13, 35, 122, 70, 52, 268, 28, 4, 48, 48, 31, 17, 26, 6, 37,
+ 11, 29, 3, 35, 5, 7, 2, 4, 43, 157, 19, 35, 5, 35, 5, 39, 9, 51, 157, 310, 10, 21, 11, 7, 153,
+ 5, 3, 0, 2, 43, 2, 1, 4, 0, 3, 22, 11, 22, 10, 30, 66, 18, 2, 1, 11, 21, 11, 25, 71, 55, 7, 1,
+ 65, 0, 16, 3, 2, 2, 2, 26, 45, 28, 4, 28, 36, 7, 2, 27, 28, 53, 11, 21, 11, 18, 14, 17, 111, 72,
+ 56, 50, 14, 50, 785, 52, 76, 44, 33, 24, 27, 35, 42, 34, 4, 0, 13, 47, 15, 3, 22, 0, 2, 0, 36,
+ 17, 2, 24, 85, 6, 2, 0, 2, 3, 2, 14, 2, 9, 8, 46, 39, 7, 3, 1, 3, 21, 2, 6, 2, 1, 2, 4, 4, 0,
+ 19, 0, 13, 4, 159, 52, 19, 3, 54, 47, 21, 1, 2, 0, 185, 46, 42, 3, 37, 47, 21, 0, 60, 42, 86,
+ 25, 391, 63, 32, 0, 449, 56, 264, 8, 2, 36, 18, 0, 50, 29, 881, 921, 103, 110, 18, 195, 2749,
+ 1070, 4050, 582, 8634, 568, 8, 30, 114, 29, 19, 47, 17, 3, 32, 20, 6, 18, 881, 68, 12, 0, 67,
+ 12, 65, 0, 32, 6124, 20, 754, 9486, 1, 3071, 106, 6, 12, 4, 8, 8, 9, 5991, 84, 2, 70, 2, 1, 3,
+ 0, 3, 1, 3, 3, 2, 11, 2, 0, 2, 6, 2, 64, 2, 3, 3, 7, 2, 6, 2, 27, 2, 3, 2, 4, 2, 0, 4, 6, 2,
+ 339, 3, 24, 2, 24, 2, 30, 2, 24, 2, 30, 2, 24, 2, 30, 2, 24, 2, 30, 2, 24, 2, 7, 4149, 196, 60,
+ 67, 1213, 3, 2, 26, 2, 1, 2, 0, 3, 0, 2, 9, 2, 3, 2, 0, 2, 0, 7, 0, 5, 0, 2, 0, 2, 0, 2, 2, 2,
+ 1, 2, 0, 3, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 1, 2, 0, 3, 3, 2, 6, 2, 3, 2, 3, 2, 0, 2, 9, 2, 16, 6,
+ 2, 2, 4, 2, 16, 4421, 42710, 42, 4148, 12, 221, 3, 5761, 10591, 541
+ };
+ private static final int[] astralIdentifierCodes = {
+ 509, 0, 227, 0, 150, 4, 294, 9, 1368, 2, 2, 1, 6, 3, 41, 2, 5, 0, 166, 1, 1306, 2, 54, 14, 32,
+ 9, 16, 3, 46, 10, 54, 9, 7, 2, 37, 13, 2, 9, 52, 0, 13, 2, 49, 13, 10, 2, 4, 9, 83, 11, 7, 0,
+ 161, 11, 6, 9, 7, 3, 57, 0, 2, 6, 3, 1, 3, 2, 10, 0, 11, 1, 3, 6, 4, 4, 193, 17, 10, 9, 87, 19,
+ 13, 9, 214, 6, 3, 8, 28, 1, 83, 16, 16, 9, 82, 12, 9, 9, 84, 14, 5, 9, 423, 9, 838, 7, 2, 7, 17,
+ 9, 57, 21, 2, 13, 19882, 9, 135, 4, 60, 6, 26, 9, 1016, 45, 17, 3, 19723, 1, 5319, 4, 4, 5, 9,
+ 7, 3, 6, 31, 3, 149, 2, 1418, 49, 513, 54, 5, 49, 9, 0, 15, 0, 23, 4, 2, 14, 1361, 6, 2, 16, 3,
+ 6, 2, 1, 2, 4, 2214, 6, 110, 6, 6, 9, 792487, 239
+ };
+
+ // This has a complexity linear to the value of the code. The
+ // assumption is that looking up astral identifier characters is
+ // rare.
+ private static boolean isInAstralSet(int code, int[] set) {
+ int pos = 0x10000;
+ for (int i = 0; i < set.length; i += 2) {
+ pos += set[i];
+ if (pos > code) return false;
+ pos += set[i + 1];
+ if (pos >= code) return true;
+ }
+ return false;
+ }
+
+ // Test whether a given character code starts an identifier.
+
+ public static boolean isIdentifierStart(int code, boolean astral) {
+ if (code < 65) return code == 36;
+ if (code < 91) return true;
+ if (code < 97) return code == 95;
+ if (code < 123) return true;
+ if (code <= 0xffff)
+ return code >= 0xaa && nonASCIIidentifierStart().matcher(str(code)).matches();
+ if (!astral) return false;
+ return isInAstralSet(code, astralIdentifierStartCodes);
+ }
+
+ public static boolean isIdentifierChar(int code, boolean astral) {
+ if (code < 48) return code == 36;
+ if (code < 58) return true;
+ if (code < 65) return false;
+ if (code < 91) return true;
+ if (code < 97) return code == 95;
+ if (code < 123) return true;
+ if (code <= 0xffff) return code >= 0xaa && nonASCIIidentifier().matcher(str(code)).matches();
+ if (!astral) return false;
+ return isInAstralSet(code, astralIdentifierStartCodes)
+ || isInAstralSet(code, astralIdentifierCodes);
+ }
+
+ private static String str(int i) {
+ return new String(Character.toChars(i));
+ }
}
diff --git a/javascript/extractor/src/com/semmle/jcorn/Locutil.java b/javascript/extractor/src/com/semmle/jcorn/Locutil.java
index 3ef911da6e9b..901d0ef3f9cb 100644
--- a/javascript/extractor/src/com/semmle/jcorn/Locutil.java
+++ b/javascript/extractor/src/com/semmle/jcorn/Locutil.java
@@ -1,27 +1,24 @@
package com.semmle.jcorn;
-import java.util.regex.Matcher;
-
import com.semmle.js.ast.Position;
+import java.util.regex.Matcher;
/// locutil.js
public class Locutil {
- /**
- * The `getLineInfo` function is mostly useful when the
- * `locations` option is off (for performance reasons) and you
- * want to find the line/column position for a given character
- * offset. `input` should be the code string that the offset refers
- * into.
- */
- public static Position getLineInfo(String input, int offset) {
- Matcher lineBreakG = Whitespace.lineBreakG.matcher(input);
- for (int line = 1, cur = 0;;) {
- if (lineBreakG.find(cur) && lineBreakG.start() < offset) {
- ++line;
- cur = lineBreakG.end();
- } else {
- return new Position(line, offset - cur, offset);
- }
- }
- }
+ /**
+ * The `getLineInfo` function is mostly useful when the `locations` option is off (for performance
+ * reasons) and you want to find the line/column position for a given character offset. `input`
+ * should be the code string that the offset refers into.
+ */
+ public static Position getLineInfo(String input, int offset) {
+ Matcher lineBreakG = Whitespace.lineBreakG.matcher(input);
+ for (int line = 1, cur = 0; ; ) {
+ if (lineBreakG.find(cur) && lineBreakG.start() < offset) {
+ ++line;
+ cur = lineBreakG.end();
+ } else {
+ return new Position(line, offset - cur, offset);
+ }
+ }
+ }
}
diff --git a/javascript/extractor/src/com/semmle/jcorn/Options.java b/javascript/extractor/src/com/semmle/jcorn/Options.java
index 417a81fec70b..63da1ee6bcb1 100644
--- a/javascript/extractor/src/com/semmle/jcorn/Options.java
+++ b/javascript/extractor/src/com/semmle/jcorn/Options.java
@@ -1,248 +1,259 @@
package com.semmle.jcorn;
-import java.util.List;
-import java.util.function.BiFunction;
-import java.util.function.Function;
-
import com.semmle.jcorn.Identifiers.Dialect;
import com.semmle.js.ast.Comment;
import com.semmle.js.ast.Position;
import com.semmle.js.ast.Program;
import com.semmle.js.ast.SourceLocation;
import com.semmle.js.ast.Token;
+import java.util.List;
+import java.util.function.BiFunction;
+import java.util.function.Function;
/// options.js
public class Options {
- public enum AllowReserved {
- YES(true), NO(false), NEVER(false);
-
- private final boolean isTrue;
-
- private AllowReserved(boolean isTrue) {
- this.isTrue = isTrue;
- }
-
- public boolean isTrue() {
- return isTrue;
- }
- }
-
- public interface OnCommentCallback {
- public void call(boolean block, String input, String text, int start, int end, Position startLoc, Position endLoc);
- }
-
- private boolean allowHashBang, allowReturnOutsideFunction, allowImportExportEverywhere;
- private boolean preserveParens, mozExtensions, jscript, esnext, v8Extensions, e4x;
- private int ecmaVersion;
- private AllowReserved allowReserved;
- private String sourceType;
- private BiFunction onInsertedSemicolon, onTrailingComma;
- private Function onToken;
- private OnCommentCallback onComment;
- private Program program;
- private Function onRecoverableError;
-
- public Options() {
- this.ecmaVersion = 7;
- this.sourceType = "script";
- this.onInsertedSemicolon = null;
- this.onTrailingComma = null;
- this.allowReserved = AllowReserved.YES;
- this.allowReturnOutsideFunction = false;
- this.allowImportExportEverywhere = false;
- this.allowHashBang = false;
- this.onToken = null;
- this.onComment = null;
- this.program = null;
- this.preserveParens = false;
- this.mozExtensions = false;
- this.jscript = false;
- this.esnext = false;
- this.v8Extensions = false;
- this.e4x = false;
- this.onRecoverableError = null;
- }
-
- public Options(Options that) {
- this.allowHashBang = that.allowHashBang;
- this.allowReturnOutsideFunction = that.allowReturnOutsideFunction;
- this.allowImportExportEverywhere = that.allowImportExportEverywhere;
- this.preserveParens = that.preserveParens;
- this.mozExtensions = that.mozExtensions;
- this.jscript = that.jscript;
- this.esnext = that.esnext;
- this.v8Extensions = that.v8Extensions;
- this.e4x = that.e4x;
- this.ecmaVersion = that.ecmaVersion;
- this.allowReserved = that.allowReserved;
- this.sourceType = that.sourceType;
- this.onInsertedSemicolon = that.onInsertedSemicolon;
- this.onTrailingComma = that.onTrailingComma;
- this.onToken = that.onToken;
- this.onComment = that.onComment;
- this.program = that.program;
- this.onRecoverableError = that.onRecoverableError;
- }
-
- public boolean allowHashBang() {
- return allowHashBang;
- }
-
- public boolean allowReturnOutsideFunction() {
- return allowReturnOutsideFunction;
- }
-
- public boolean allowImportExportEverywhere() {
- return allowImportExportEverywhere;
- }
-
- public boolean preserveParens() {
- return preserveParens;
- }
-
- public boolean mozExtensions() {
- return mozExtensions;
- }
-
- public boolean jscript() {
- return jscript;
- }
-
- public boolean esnext() {
- return esnext;
- }
-
- public boolean v8Extensions() {
- return v8Extensions;
- }
-
- public boolean e4x() {
- return e4x;
- }
-
- public Identifiers.Dialect getDialect() {
- switch (ecmaVersion) {
- case 3:
- return Dialect.ECMA_3;
- case 5:
- return Dialect.ECMA_5;
- case 6:
- return Dialect.ECMA_6;
- case 8:
- return Dialect.ECMA_8;
- default:
- return Dialect.ECMA_7;
- }
- }
-
- public int ecmaVersion() {
- return ecmaVersion;
- }
-
- public Options ecmaVersion(int ecmaVersion) {
- if (ecmaVersion >= 2015)
- ecmaVersion -= 2009;
-
- this.ecmaVersion = ecmaVersion;
- if (ecmaVersion >= 5)
- this.allowReserved = AllowReserved.NO;
- return this;
- }
-
- public AllowReserved allowReserved() {
- return allowReserved;
- }
-
- public Options onComment(List comments) {
- this.onComment = (block, input, text, start, end, startLoc, endLoc) -> {
- String src = input.substring(start, end);
- comments.add(new Comment(new SourceLocation(src, startLoc, endLoc), text));
- };
- return this;
- }
-
- public String sourceType() {
- return sourceType;
- }
-
- public Options sourceType(String sourceType) {
- this.sourceType = sourceType;
- return this;
- }
-
- public Options mozExtensions(boolean mozExtensions) {
- this.mozExtensions = mozExtensions;
- return this;
- }
-
- public Options jscript(boolean jscript) {
- this.jscript = jscript;
- return this;
- }
-
- public Options esnext(boolean esnext) {
- this.esnext = esnext;
- return this;
- }
-
- public void v8Extensions(boolean v8Extensions) {
- this.v8Extensions = v8Extensions;
- }
-
- public void e4x(boolean e4x) {
- this.e4x = e4x;
- }
-
- public Options preserveParens(boolean preserveParens) {
- this.preserveParens = preserveParens;
- return this;
- }
-
- public Options allowReturnOutsideFunction(boolean allowReturnOutsideFunction) {
- this.allowReturnOutsideFunction = allowReturnOutsideFunction;
- return this;
- }
-
- public Options allowImportExportEverywhere(boolean allowImportExportEverywhere) {
- this.allowImportExportEverywhere = allowImportExportEverywhere;
- return this;
- }
-
- public BiFunction onInsertedSemicolon() {
- return onInsertedSemicolon;
- }
-
- public BiFunction onTrailingComma() {
- return onTrailingComma;
- }
-
- public Function onToken() {
- return onToken;
- }
-
- public Options onToken(List tokens) {
- return onToken((tk) -> { tokens.add(tk); return null; });
- }
-
- public Options onToken(Function tmp) {
- this.onToken = tmp;
- return this;
- }
-
- public OnCommentCallback onComment() {
- return onComment;
- }
-
- public Program program() {
- return program;
- }
-
- public Options onRecoverableError(Function onRecoverableError) {
- this.onRecoverableError = onRecoverableError;
- return this;
- }
-
- public Function onRecoverableError() {
- return onRecoverableError;
- }
+ public enum AllowReserved {
+ YES(true),
+ NO(false),
+ NEVER(false);
+
+ private final boolean isTrue;
+
+ private AllowReserved(boolean isTrue) {
+ this.isTrue = isTrue;
+ }
+
+ public boolean isTrue() {
+ return isTrue;
+ }
+ }
+
+ public interface OnCommentCallback {
+ public void call(
+ boolean block,
+ String input,
+ String text,
+ int start,
+ int end,
+ Position startLoc,
+ Position endLoc);
+ }
+
+ private boolean allowHashBang, allowReturnOutsideFunction, allowImportExportEverywhere;
+ private boolean preserveParens, mozExtensions, jscript, esnext, v8Extensions, e4x;
+ private int ecmaVersion;
+ private AllowReserved allowReserved;
+ private String sourceType;
+ private BiFunction onInsertedSemicolon, onTrailingComma;
+ private Function onToken;
+ private OnCommentCallback onComment;
+ private Program program;
+ private Function onRecoverableError;
+
+ public Options() {
+ this.ecmaVersion = 7;
+ this.sourceType = "script";
+ this.onInsertedSemicolon = null;
+ this.onTrailingComma = null;
+ this.allowReserved = AllowReserved.YES;
+ this.allowReturnOutsideFunction = false;
+ this.allowImportExportEverywhere = false;
+ this.allowHashBang = false;
+ this.onToken = null;
+ this.onComment = null;
+ this.program = null;
+ this.preserveParens = false;
+ this.mozExtensions = false;
+ this.jscript = false;
+ this.esnext = false;
+ this.v8Extensions = false;
+ this.e4x = false;
+ this.onRecoverableError = null;
+ }
+
+ public Options(Options that) {
+ this.allowHashBang = that.allowHashBang;
+ this.allowReturnOutsideFunction = that.allowReturnOutsideFunction;
+ this.allowImportExportEverywhere = that.allowImportExportEverywhere;
+ this.preserveParens = that.preserveParens;
+ this.mozExtensions = that.mozExtensions;
+ this.jscript = that.jscript;
+ this.esnext = that.esnext;
+ this.v8Extensions = that.v8Extensions;
+ this.e4x = that.e4x;
+ this.ecmaVersion = that.ecmaVersion;
+ this.allowReserved = that.allowReserved;
+ this.sourceType = that.sourceType;
+ this.onInsertedSemicolon = that.onInsertedSemicolon;
+ this.onTrailingComma = that.onTrailingComma;
+ this.onToken = that.onToken;
+ this.onComment = that.onComment;
+ this.program = that.program;
+ this.onRecoverableError = that.onRecoverableError;
+ }
+
+ public boolean allowHashBang() {
+ return allowHashBang;
+ }
+
+ public boolean allowReturnOutsideFunction() {
+ return allowReturnOutsideFunction;
+ }
+
+ public boolean allowImportExportEverywhere() {
+ return allowImportExportEverywhere;
+ }
+
+ public boolean preserveParens() {
+ return preserveParens;
+ }
+
+ public boolean mozExtensions() {
+ return mozExtensions;
+ }
+
+ public boolean jscript() {
+ return jscript;
+ }
+
+ public boolean esnext() {
+ return esnext;
+ }
+
+ public boolean v8Extensions() {
+ return v8Extensions;
+ }
+
+ public boolean e4x() {
+ return e4x;
+ }
+
+ public Identifiers.Dialect getDialect() {
+ switch (ecmaVersion) {
+ case 3:
+ return Dialect.ECMA_3;
+ case 5:
+ return Dialect.ECMA_5;
+ case 6:
+ return Dialect.ECMA_6;
+ case 8:
+ return Dialect.ECMA_8;
+ default:
+ return Dialect.ECMA_7;
+ }
+ }
+
+ public int ecmaVersion() {
+ return ecmaVersion;
+ }
+
+ public Options ecmaVersion(int ecmaVersion) {
+ if (ecmaVersion >= 2015) ecmaVersion -= 2009;
+
+ this.ecmaVersion = ecmaVersion;
+ if (ecmaVersion >= 5) this.allowReserved = AllowReserved.NO;
+ return this;
+ }
+
+ public AllowReserved allowReserved() {
+ return allowReserved;
+ }
+
+ public Options onComment(List comments) {
+ this.onComment =
+ (block, input, text, start, end, startLoc, endLoc) -> {
+ String src = input.substring(start, end);
+ comments.add(new Comment(new SourceLocation(src, startLoc, endLoc), text));
+ };
+ return this;
+ }
+
+ public String sourceType() {
+ return sourceType;
+ }
+
+ public Options sourceType(String sourceType) {
+ this.sourceType = sourceType;
+ return this;
+ }
+
+ public Options mozExtensions(boolean mozExtensions) {
+ this.mozExtensions = mozExtensions;
+ return this;
+ }
+
+ public Options jscript(boolean jscript) {
+ this.jscript = jscript;
+ return this;
+ }
+
+ public Options esnext(boolean esnext) {
+ this.esnext = esnext;
+ return this;
+ }
+
+ public void v8Extensions(boolean v8Extensions) {
+ this.v8Extensions = v8Extensions;
+ }
+
+ public void e4x(boolean e4x) {
+ this.e4x = e4x;
+ }
+
+ public Options preserveParens(boolean preserveParens) {
+ this.preserveParens = preserveParens;
+ return this;
+ }
+
+ public Options allowReturnOutsideFunction(boolean allowReturnOutsideFunction) {
+ this.allowReturnOutsideFunction = allowReturnOutsideFunction;
+ return this;
+ }
+
+ public Options allowImportExportEverywhere(boolean allowImportExportEverywhere) {
+ this.allowImportExportEverywhere = allowImportExportEverywhere;
+ return this;
+ }
+
+ public BiFunction onInsertedSemicolon() {
+ return onInsertedSemicolon;
+ }
+
+ public BiFunction onTrailingComma() {
+ return onTrailingComma;
+ }
+
+ public Function onToken() {
+ return onToken;
+ }
+
+ public Options onToken(List tokens) {
+ return onToken(
+ (tk) -> {
+ tokens.add(tk);
+ return null;
+ });
+ }
+
+ public Options onToken(Function tmp) {
+ this.onToken = tmp;
+ return this;
+ }
+
+ public OnCommentCallback onComment() {
+ return onComment;
+ }
+
+ public Program program() {
+ return program;
+ }
+
+ public Options onRecoverableError(Function onRecoverableError) {
+ this.onRecoverableError = onRecoverableError;
+ return this;
+ }
+
+ public Function onRecoverableError() {
+ return onRecoverableError;
+ }
}
diff --git a/javascript/extractor/src/com/semmle/jcorn/Parser.java b/javascript/extractor/src/com/semmle/jcorn/Parser.java
index b99dbe0b1d53..6062c86167c0 100644
--- a/javascript/extractor/src/com/semmle/jcorn/Parser.java
+++ b/javascript/extractor/src/com/semmle/jcorn/Parser.java
@@ -3,19 +3,6 @@
import static com.semmle.jcorn.Whitespace.isNewLine;
import static com.semmle.jcorn.Whitespace.lineBreak;
-import java.io.File;
-import java.util.ArrayList;
-import java.util.LinkedHashMap;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Set;
-import java.util.Stack;
-import java.util.function.Function;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
import com.semmle.jcorn.Identifiers.Dialect;
import com.semmle.jcorn.Options.AllowReserved;
import com.semmle.js.ast.ArrayExpression;
@@ -36,6 +23,7 @@
import com.semmle.js.ast.ConditionalExpression;
import com.semmle.js.ast.ContinueStatement;
import com.semmle.js.ast.DebuggerStatement;
+import com.semmle.js.ast.DeclarationFlags;
import com.semmle.js.ast.DoWhileStatement;
import com.semmle.js.ast.EmptyStatement;
import com.semmle.js.ast.EnhancedForStatement;
@@ -65,7 +53,6 @@
import com.semmle.js.ast.LogicalExpression;
import com.semmle.js.ast.MemberDefinition;
import com.semmle.js.ast.MemberExpression;
-import com.semmle.js.ast.DeclarationFlags;
import com.semmle.js.ast.MetaProperty;
import com.semmle.js.ast.MethodDefinition;
import com.semmle.js.ast.NewExpression;
@@ -103,3492 +90,3601 @@
import com.semmle.util.collections.CollectionUtil;
import com.semmle.util.data.Pair;
import com.semmle.util.data.StringUtil;
-import com.semmle.util.exception.Exceptions;
import com.semmle.util.exception.CatastrophicError;
+import com.semmle.util.exception.Exceptions;
import com.semmle.util.io.WholeIO;
+import java.io.File;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.Stack;
+import java.util.function.Function;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
/**
* Java port of Acorn.
*
- * This version corresponds to Acorn 4.0.3,
- * but does not support plugins, and always tracks full source locations.
+ * This version corresponds to Acorn
+ * 4.0.3, but does not support plugins, and always tracks full source locations.
*/
public class Parser {
- protected final Options options;
- protected final Set keywords;
- private final Set reservedWords, reservedWordsStrict, reservedWordsStrictBind;
- protected final String input;
- private boolean containsEsc;
- protected boolean exprAllowed;
- protected boolean strict;
- private boolean inModule;
- protected boolean inFunction;
- protected boolean inGenerator;
- protected boolean inAsync;
- protected boolean inTemplateElement;
- protected int pos;
- protected int lineStart;
- protected int curLine;
- protected int start;
- protected int end;
- protected TokenType type;
- protected Object value;
- protected Position startLoc;
- protected Position endLoc;
- protected Position lastTokEndLoc, lastTokStartLoc;
- protected int lastTokStart, lastTokEnd;
- protected Stack context;
- protected int potentialArrowAt;
- private Stack labels;
- protected int yieldPos, awaitPos;
-
- /**
- * For readability purposes, we pass this instead of false as the argument to
- * the hasDeclareKeyword parameter (which only exists in TypeScript).
- */
- private static final boolean noDeclareKeyword = false;
-
- /**
- * For readability purposes, we pass this instead of false as the argument to
- * the isAbstract parameter (which only exists in TypeScript).
- */
- protected static final boolean notAbstract = false;
-
- /**
- * For readability purposes, we pass this instead of null as the argument to the
- * type annotation parameters (which only exists in TypeScript).
- */
- private static final ITypeExpression noTypeAnnotation = null;
-
- protected static class LabelInfo {
- String name, kind;
- int statementStart;
-
- public LabelInfo(String name, String kind, int statementStart) {
- this.name = name;
- this.kind = kind;
- this.statementStart = statementStart;
- }
- }
-
- public static void main(String[] args) {
- new Parser(new Options(), new WholeIO().strictread(new File(args[0])), 0).parse();
- }
-
- /// begin state.js
-
- public Parser(Options options, String input, int startPos) {
- this.options = options;
- this.keywords = new LinkedHashSet(Identifiers.keywords.get(options.ecmaVersion() >= 6 ? Identifiers.Dialect.ECMA_6 : Identifiers.Dialect.ECMA_5));
- this.reservedWords = new LinkedHashSet();
- if (!options.allowReserved().isTrue()) {
- this.reservedWords.addAll(Identifiers.reservedWords.get(options.getDialect()));
- if (options.sourceType().equals("module"))
- this.reservedWords.add("await");
- }
- this.reservedWordsStrict = new LinkedHashSet(this.reservedWords);
- this.reservedWordsStrict.addAll(Identifiers.reservedWords.get(Dialect.STRICT));
- this.reservedWordsStrictBind = new LinkedHashSet(this.reservedWordsStrict);
- this.reservedWordsStrictBind.addAll(Identifiers.reservedWords.get(Dialect.STRICT_BIND));
- this.input = input;
-
- // Used to signal to callers of `readWord1` whether the word
- // contained any escape sequences. This is needed because words with
- // escape sequences must not be interpreted as keywords.
- this.containsEsc = false;
-
- // Set up token state
-
- // The current position of the tokenizer in the input.
- if (startPos != 0) {
- this.pos = startPos;
- this.lineStart = this.input.lastIndexOf("\n", startPos - 1) + 1;
- this.curLine = inputSubstring(0, this.lineStart).split(Whitespace.lineBreak).length;
- } else {
- this.pos = this.lineStart = 0;
- this.curLine = 1;
- }
-
- // Properties of the current token:
- // Its type
- this.type = TokenType.eof;
- // For tokens that include more information than their type, the value
- this.value = null;
- // Its start and end offset
- this.start = this.end = this.pos;
- // And, if locations are used, the {line, column} object
- // corresponding to those offsets
- this.startLoc = this.endLoc = this.curPosition();
-
- // Position information for the previous token
- this.lastTokEndLoc = this.lastTokStartLoc = null;
- this.lastTokStart = this.lastTokEnd = this.pos;
-
- // The context stack is used to superficially track syntactic
- // context to predict whether a regular expression is allowed in a
- // given position.
- this.context = this.initialContext();
- this.exprAllowed = true;
-
- // Figure out if it's a module code.
- this.strict = this.inModule = options.sourceType().equals("module");
-
- // Used to signify the start of a potential arrow function
- this.potentialArrowAt = -1;
-
- // Flags to track whether we are in a function, a generator, an async function.
- this.inFunction = this.inGenerator = this.inAsync = false;
- // Positions to delayed-check that yield/await does not exist in default parameters.
- this.yieldPos = this.awaitPos = 0;
- // Labels in scope.
- this.labels = new Stack();
-
- // If enabled, skip leading hashbang line.
- if (this.pos == 0 && options.allowHashBang() && this.input.startsWith("#!"))
- this.skipLineComment(2);
- }
-
- public Program parse() {
- Position startLoc = this.startLoc;
- this.nextToken();
- return this.parseTopLevel(startLoc, this.options.program());
- }
-
- /// end state.js
-
- /// begin location.js
- protected void raise(int pos, String msg, boolean recoverable) {
- Position loc = Locutil.getLineInfo(input, pos);
- raise(loc, msg, recoverable);
- }
-
- protected void raise(int pos, String msg) {
- raise(pos, msg, false);
- }
-
- protected void raise(Position loc, String msg, boolean recoverable) {
- msg += " (" + loc.getLine() + ":" + loc.getColumn() + ")";
- SyntaxError err = new SyntaxError(msg, loc, this.pos);
- if (recoverable && options.onRecoverableError() != null)
- options.onRecoverableError().apply(err);
- else
- throw err;
- }
-
- protected void raise(Position loc, String msg) {
- raise(loc, msg, false);
- }
-
- protected void raise(INode nd, String msg) {
- raise(nd.getLoc().getStart(), msg, false);
- }
-
- protected void raiseRecoverable(int pos, String msg) {
- raise(pos, msg, true);
- }
-
- protected void raiseRecoverable(INode nd, String msg) {
- raise(nd.getLoc().getStart(), msg, true);
- }
-
- protected Position curPosition() {
- return new Position(curLine, pos - lineStart, pos);
- }
-
- /// end location.js
-
- /// begin tokenize.js
-
- // Move to the next token
-
- protected void next() {
- if (this.options.onToken() != null)
- this.options.onToken().apply(mkToken());
-
- this.lastTokEnd = this.end;
- this.lastTokStart = this.start;
- this.lastTokEndLoc = this.endLoc;
- this.lastTokStartLoc = this.startLoc;
- this.nextToken();
- }
-
- // Toggle strict mode. Re-reads the next number or string to please
- // pedantic tests (`"use strict"; 010;` should fail).
-
- public void setStrict(boolean strict) {
- this.strict = strict;
- if (this.type != TokenType.num && this.type != TokenType.string) return;
- this.pos = this.start;
- while (this.pos < this.lineStart) {
- this.lineStart = this.input.lastIndexOf("\n", this.lineStart - 2) + 1;
- --this.curLine;
- }
- this.nextToken();
- }
-
- public TokContext curContext() {
- return context.peek();
- }
-
- // Read a single token, updating the parser object's token-related
- // properties.
-
- public Token nextToken() {
- TokContext curContext = this.curContext();
- if (curContext == null || !curContext.preserveSpace)
- this.skipSpace();
-
- this.start = this.pos;
- this.startLoc = this.curPosition();
- if (this.pos >= this.input.length())
- return this.finishToken(TokenType.eof);
-
- if (curContext != null && curContext.override != null)
- return curContext.override.apply(this);
- else
- return this.readToken(this.fullCharCodeAtPos());
- }
-
- protected Token readToken(int code) {
- // Identifier or keyword. '\\uXXXX' sequences are allowed in
- // identifiers, so '\' also dispatches to that.
- if (Identifiers.isIdentifierStart(code, this.options.ecmaVersion() >= 6) || code == 92 /* '\' */)
- return this.readWord();
-
- return this.getTokenFromCode(code);
- }
-
- protected int fullCharCodeAtPos() {
- int code = charAt(this.pos);
- if (code <= 0xd7ff || code >= 0xe000)
- return code;
- int next = charAt(this.pos + 1);
- return (code << 10) + next - 0x35fdc00;
- }
-
- protected void skipBlockComment() {
- Position startLoc = this.options.onComment() != null ? this.curPosition() : null;
- int start = this.pos, end = this.input.indexOf("*/", this.pos += 2);
- if (end == -1)
- this.raise(this.pos - 2, "Unterminated comment");
- this.pos = end + 2;
- Matcher m = Whitespace.lineBreakG.matcher(this.input);
- int next = start;
- while (m.find(next) && m.start() < this.pos) {
- ++this.curLine;
- lineStart = m.end();
- next = lineStart;
- }
- if (this.options.onComment() != null)
- this.options.onComment().call(true, this.input, inputSubstring(start + 2, end), start, this.pos,
- startLoc, this.curPosition());
- }
-
- protected void skipLineComment(int startSkip) {
- int start = this.pos;
- Position startLoc = this.options.onComment() != null ? this.curPosition() : null;
- this.pos += startSkip;
- int ch = charAt(this.pos);
- while (this.pos < this.input.length() && ch != 10 && ch != 13 && ch != 8232 && ch != 8233) {
- ++this.pos;
- ch = charAt(this.pos);
- }
- if (this.options.onComment() != null)
- this.options.onComment().call(false, this.input, inputSubstring(start + startSkip, this.pos), start, this.pos,
- startLoc, this.curPosition());
- }
-
- // Called at the start of the parse and after every token. Skips
- // whitespace and comments, and.
-
- protected void skipSpace() {
- loop: while (this.pos < this.input.length()) {
- int ch = this.input.charAt(this.pos);
- switch (ch) {
- case 32: case 160: // ' '
- ++this.pos;
- break;
- case 13:
- if (charAt(this.pos + 1) == 10) {
- ++this.pos;
- }
- case 10: case 8232: case 8233:
- ++this.pos;
- ++this.curLine;
- this.lineStart = this.pos;
- break;
- case 47: // '/'
- switch (charAt(this.pos + 1)) {
- case 42: // '*'
- this.skipBlockComment();
- break;
- case 47:
- this.skipLineComment(2);
- break;
- default:
- break loop;
- }
- break;
- default:
- if (ch > 8 && ch < 14 || ch >= 5760 && Whitespace.nonASCIIwhitespace.indexOf(ch) > -1) {
- ++this.pos;
- } else {
- break loop;
- }
- }
- }
- }
-
- // Called at the end of every token. Sets `end`, `val`, and
- // maintains `context` and `exprAllowed`, and skips the space after
- // the token, so that the next one's `start` will point at the
- // right position.
-
- protected Token finishToken(TokenType type, Object val) {
- this.end = this.pos;
- this.endLoc = this.curPosition();
- TokenType prevType = this.type;
- this.type = type;
- this.value = val;
- this.updateContext(prevType);
- return mkToken();
- }
-
- private Token mkToken() {
- String src = inputSubstring(start, end);
- SourceLocation loc = new SourceLocation(src, startLoc, endLoc);
- String label, keyword;
- if (isKeyword(src)) {
- label = keyword = src;
- } else {
- label = type.label;
- keyword = type.keyword;
- }
- return new Token(loc, label, keyword);
- }
-
- protected boolean isKeyword(String src) {
- if (type.keyword != null)
- return true;
- if (type == TokenType.name) {
- if (keywords.contains(src))
- return true;
- if (options.ecmaVersion() >= 6 && ("let".equals(src) || "yield".equals(src)))
- return true;
- }
- return false;
- }
-
- protected Token finishToken(TokenType type) {
- return finishToken(type, null);
- }
-
- // ### Token reading
-
- // This is the function that is called to fetch the next token. It
- // is somewhat obscure, because it works in character codes rather
- // than characters, and because operator parsing has been inlined
- // into it.
- //
- // All in the name of speed.
- //
- private Token readToken_dot() {
- int next = charAt(this.pos + 1);
- if (next >= 48 && next <= 57) return this.readNumber(true);
- int next2 = charAt(this.pos + 2);
- if (this.options.ecmaVersion() >= 6 && next == 46 && next2 == 46) { // 46 = dot '.'
- this.pos += 3;
- return this.finishToken(TokenType.ellipsis);
- } else {
- ++this.pos;
- return this.finishToken(TokenType.dot);
- }
- }
-
- private Token readToken_question() { // '?'
- int next = charAt(this.pos + 1);
- int next2 = charAt(this.pos + 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);
- }
-
- private Token readToken_slash() { // '/'
- int next = charAt(this.pos + 1);
- if (this.exprAllowed) {
- ++this.pos;
- return this.readRegexp();
- }
- if (next == 61)
- return this.finishOp(TokenType.assign, 2);
- return this.finishOp(TokenType.slash, 1);
- }
-
- private Token readToken_mult_modulo_exp(int code) { // '%*'
- int next = charAt(this.pos + 1);
- int size = 1;
- TokenType tokentype = code == 42 ? TokenType.star : TokenType.modulo;
-
- // exponentiation operator ** and **=
- if (this.options.ecmaVersion() >= 7 && code == 42 && next == 42) {
- ++size;
- tokentype = TokenType.starstar;
- next = charAt(this.pos + 2);
- }
-
- if (next == 61)
- return this.finishOp(TokenType.assign, size + 1);
- return this.finishOp(tokentype, size);
- }
-
- private Token readToken_pipe_amp(int code) { // '|&'
- int next = charAt(this.pos + 1);
- if (next == code)
- return this.finishOp(code == 124 ? TokenType.logicalOR : TokenType.logicalAND, 2);
- if (next == 61)
- return this.finishOp(TokenType.assign, 2);
- return this.finishOp(code == 124 ? TokenType.bitwiseOR : TokenType.bitwiseAND, 1);
- }
-
- private Token readToken_caret() { // '^'
- int next = charAt(this.pos + 1);
- if (next == 61)
- return this.finishOp(TokenType.assign, 2);
- return this.finishOp(TokenType.bitwiseXOR, 1);
- }
-
- private Token readToken_plus_min(int code) { // '+-'
- int next = charAt(this.pos + 1);
- if (next == code) {
- if (next == 45 && charAt(this.pos + 2) == 62 &&
- inputSubstring(this.lastTokEnd, this.pos).matches("(?s).*(?:" + lineBreak + ").*")) {
- // A `-->` line comment
- this.skipLineComment(3);
- this.skipSpace();
- return this.nextToken();
- }
- return this.finishOp(TokenType.incDec, 2);
- }
- if (next == 61)
- return this.finishOp(TokenType.assign, 2);
- return this.finishOp(TokenType.plusMin, 1);
- }
-
- private Token readToken_lt_gt(int code) { // '<>'
- int next = charAt(this.pos + 1);
- int size = 1;
- if (next == code) {
- size = code == 62 && charAt(this.pos + 2) == 62 ? 3 : 2;
- if (charAt(this.pos + size) == 61)
- return this.finishOp(TokenType.assign, size + 1);
- return this.finishOp(TokenType.bitShift, size);
- }
- if (next == 33 && code == 60 && charAt(this.pos + 2) == 45 &&
- charAt(this.pos + 3) == 45) {
- if (this.inModule)
- this.unexpected();
- // `` line comment
+ this.skipLineComment(3);
+ this.skipSpace();
+ return this.nextToken();
+ }
+ return this.finishOp(TokenType.incDec, 2);
+ }
+ if (next == 61) return this.finishOp(TokenType.assign, 2);
+ return this.finishOp(TokenType.plusMin, 1);
+ }
+
+ private Token readToken_lt_gt(int code) { // '<>'
+ int next = charAt(this.pos + 1);
+ int size = 1;
+ if (next == code) {
+ size = code == 62 && charAt(this.pos + 2) == 62 ? 3 : 2;
+ if (charAt(this.pos + size) == 61) return this.finishOp(TokenType.assign, size + 1);
+ return this.finishOp(TokenType.bitShift, size);
+ }
+ if (next == 33 && code == 60 && charAt(this.pos + 2) == 45 && charAt(this.pos + 3) == 45) {
+ if (this.inModule) this.unexpected();
+ // `0) {
- res.append(readHexDigit());
- }
- if (res.length() == 0)
- return "0";
- return res.toString();
- }
-
- private String readDigits(boolean opt) {
- StringBuilder res = new StringBuilder();
- for (char c=peekChar(true); c >= '0' && c <= '9'; nextChar(), c=peekChar(true))
- res.append(c);
- if (res.length() == 0 && !opt)
- this.error(Error.EXPECTED_DIGIT);
- return res.toString();
- }
-
- private Double toNumber(String s) {
- if (s.isEmpty())
- return 0.0;
- return Double.valueOf(s);
- }
-
- private String readIdentifier() {
- StringBuilder res = new StringBuilder();
- for (char c=peekChar(true);
- c != '\0' && Character.isJavaIdentifierPart(c);
- nextChar(), c=peekChar(true))
- res.append(c);
- if (res.length() == 0)
- this.error(Error.EXPECTED_IDENTIFIER);
- return res.toString();
- }
-
- private void expectRParen() {
- if (!this.match(")"))
- this.error(Error.EXPECTED_CLOSING_PAREN, this.pos-1);
- }
-
- private void expectRBrace() {
- if (!this.match("}"))
- this.error(Error.EXPECTED_CLOSING_BRACE, this.pos-1);
- }
-
- private void expectRAngle() {
- if (!this.match(">"))
- this.error(Error.EXPECTED_CLOSING_ANGLE, this.pos-1);
- }
-
- private boolean lookahead(String... arguments) {
- for (String prefix : arguments) {
- if (prefix == null) {
- if (atEOS())
- return true;
- } else if (inputSubstring(pos, pos+prefix.length()).equals(prefix)) {
- return true;
- }
- }
- return false;
- }
-
- private boolean match(String... arguments) {
- for (String prefix : arguments) {
- if (this.lookahead(prefix)) {
- if (prefix == null)
- prefix = "";
- this.pos += prefix.length();
- return true;
- }
- }
- return false;
- }
-
- private RegExpTerm parsePattern() {
- RegExpTerm res = parseDisjunction();
- if (!this.atEOS())
- this.error(Error.EXPECTED_EOS);
- return res;
- }
-
- protected String inputSubstring(int start, int end) {
- if (start >= src.length())
- return "";
- if (end > src.length())
- end = src.length();
- return src.substring(start, end);
- }
-
- private T finishTerm(T term) {
- SourceLocation loc = term.getLoc();
- Position end = pos();
- loc.setSource(inputSubstring(loc.getStart().getOffset(), end.getOffset()));
- loc.setEnd(end);
- return term;
- }
-
- private RegExpTerm parseDisjunction() {
- SourceLocation loc = new SourceLocation(pos());
- List disjuncts = new ArrayList<>();
- disjuncts.add(this.parseAlternative());
- while (this.match("|"))
- disjuncts.add(this.parseAlternative());
- if (disjuncts.size() == 1)
- return disjuncts.get(0);
- return this.finishTerm(new Disjunction(loc, disjuncts));
- }
-
- private RegExpTerm parseAlternative() {
- SourceLocation loc = new SourceLocation(pos());
- List elements = new ArrayList<>();
- while (!this.lookahead(null, "|", ")"))
- elements.add(this.parseTerm());
- if (elements.size() == 1)
- return elements.get(0);
- return this.finishTerm(new Sequence(loc, elements));
- }
-
- private RegExpTerm parseTerm() {
- SourceLocation loc = new SourceLocation(pos());
-
- if (this.match("^"))
- return this.finishTerm(new Caret(loc));
-
- if (this.match("$"))
- return this.finishTerm(new Dollar(loc));
-
- if (this.match("\\b"))
- return this.finishTerm(new WordBoundary(loc));
-
- if (this.match("\\B"))
- return this.finishTerm(new NonWordBoundary(loc));
-
- if (this.match("(?=")) {
- RegExpTerm dis = this.parseDisjunction();
- this.expectRParen();
- return this.finishTerm(new ZeroWidthPositiveLookahead(loc, dis));
- }
-
- if (this.match("(?!")) {
- RegExpTerm dis = this.parseDisjunction();
- this.expectRParen();
- return this.finishTerm(new ZeroWidthNegativeLookahead(loc, dis));
- }
-
- if (this.match("(?<=")) {
- RegExpTerm dis = this.parseDisjunction();
- this.expectRParen();
- return this.finishTerm(new ZeroWidthPositiveLookbehind(loc, dis));
- }
-
- if (this.match("(?"));
- }
-
- if (this.match("p{", "P{")) {
- String name = this.readIdentifier();
- if (this.match("=")) {
- value = this.readIdentifier();
- raw = "\\p{" + name + "=" + value + "}";
- } else {
- value = null;
- raw = "\\p{" + name + "}";
- }
- this.expectRBrace();
- return this.finishTerm(new UnicodePropertyEscape(loc, name, value, raw));
- }
-
- int startpos = this.pos-1;
- char c = this.nextChar();
-
- if (c >= '0' && c <= '9') {
- raw = c + this.readDigits(true);
- if (c == '0' || inCharClass) {
- int base = c == '0' && raw.length() > 1 ? 8 : 10;
- try {
- codepoint = Long.parseLong(raw, base);
- value = fromCodePoint((int) codepoint);
- } catch (NumberFormatException nfe) {
- codepoint = 0;
- value = "\0";
- }
- if (base == 8) {
- this.error(Error.OCTAL_ESCAPE, startpos, this.pos);
- return this.finishTerm(new OctalEscape(loc, value, (double)codepoint, "\\" + raw));
- } else {
- return this.finishTerm(new DecimalEscape(loc, value, (double)codepoint, "\\" + raw));
- }
- } else {
- try {
- codepoint = Long.parseLong(raw, 10);
- } catch (NumberFormatException nfe) {
- codepoint = 0;
- }
- BackReference br = this.finishTerm(new BackReference(loc, (double)codepoint, "\\" + raw));
- this.backrefs.add(br);
- return br;
- }
- }
-
- String ctrltab = "f\fn\nr\rt\tv\u000b";
- int idx;
- if ((idx=ctrltab.indexOf(c)) % 2 == 0) {
- codepoint = ctrltab.charAt(idx+1);
- value = String.valueOf((char)codepoint);
- return this.finishTerm(new ControlEscape(loc, value, codepoint, "\\" + c));
- }
-
- if (c == 'c') {
- c = this.nextChar();
- if (!(c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z'))
- this.error(Error.EXPECTED_CONTROL_LETTER, this.pos-1);
- codepoint = c % 32;
- value = String.valueOf((char)codepoint);
- return this.finishTerm(new ControlLetter(loc, value, codepoint, "\\c" + c));
- }
-
- if ("dDsSwW".indexOf(c) >= 0) {
- return this.finishTerm(new CharacterClassEscape(loc, String.valueOf(c), "\\" + c));
- }
-
- codepoint = c;
- value = String.valueOf((char)codepoint);
- return this.finishTerm(new IdentityEscape(loc, value, codepoint, "\\" + c));
- }
-
- private RegExpTerm parseCharacterClass() {
- SourceLocation loc = new SourceLocation(pos());
- List elements = new ArrayList<>();
-
- this.match("[");
- boolean inverted = this.match("^");
- while (!this.match("]")) {
- if (this.atEOS()) {
- this.error(Error.EXPECTED_RBRACKET);
- break;
- }
- elements.add(this.parseCharacterClassElement());
- }
- return this.finishTerm(new CharacterClass(loc, elements, inverted));
- }
-
- private RegExpTerm parseCharacterClassElement() {
- SourceLocation loc = new SourceLocation(pos());
- RegExpTerm atom = this.parseCharacterClassAtom();
- if (!this.lookahead("-]") && this.match("-"))
- return this.finishTerm(new CharacterClassRange(loc, atom, this.parseCharacterClassAtom()));
- return atom;
- }
-
- private RegExpTerm parseCharacterClassAtom() {
- SourceLocation loc = new SourceLocation(pos());
- char c = this.nextChar();
- if (c == '\\') {
- if (this.match("b"))
- return this.finishTerm(new ControlEscape(loc, "\b", 8, "\\b"));
- return this.finishTerm(this.parseAtomEscape(loc, true));
- }
- return this.finishTerm(new Constant(loc, String.valueOf(c)));
- }
+ /** The result of a parse. */
+ public static class Result {
+ /** The root of the parsed AST. */
+ public final RegExpTerm ast;
+
+ /** A list of errors encountered during parsing. */
+ public final List errors;
+
+ public Result(RegExpTerm ast, List errors) {
+ this.ast = ast;
+ this.errors = errors;
+ }
+
+ public RegExpTerm getAST() {
+ return ast;
+ }
+
+ public List getErrors() {
+ return errors;
+ }
+ }
+
+ private String src;
+ private int pos;
+ private List errors;
+ private List backrefs;
+ private int maxbackref;
+
+ /** Parse the given string as a regular expression. */
+ public Result parse(String src) {
+ this.src = src;
+ this.pos = 0;
+ this.errors = new ArrayList<>();
+ this.backrefs = new ArrayList<>();
+ this.maxbackref = 0;
+ RegExpTerm root = parsePattern();
+ for (BackReference backref : backrefs)
+ if (backref.getValue() > maxbackref)
+ errors.add(new Error(backref.getLoc(), Error.INVALID_BACKREF));
+ return new Result(root, errors);
+ }
+
+ private static String fromCodePoint(int codepoint) {
+ if (Character.isValidCodePoint(codepoint)) return new String(Character.toChars(codepoint));
+ // replacement character
+ return "\ufffd";
+ }
+
+ private Position pos() {
+ return new Position(1, pos, pos);
+ }
+
+ private void error(int code, int start, int end) {
+ Position startPos, endPos;
+ startPos = new Position(1, start, start);
+ endPos = new Position(1, end, end);
+ this.errors.add(
+ new Error(new SourceLocation(inputSubstring(start, end), startPos, endPos), code));
+ }
+
+ private void error(int code, int start) {
+ error(code, start, start + 1);
+ }
+
+ private void error(int code) {
+ error(code, this.pos);
+ }
+
+ private boolean atEOS() {
+ return pos >= src.length();
+ }
+
+ private char peekChar(boolean opt) {
+ if (this.atEOS()) {
+ if (!opt) this.error(Error.UNEXPECTED_EOS);
+ return '\0';
+ } else {
+ return this.src.charAt(this.pos);
+ }
+ }
+
+ private char nextChar() {
+ char c = peekChar(false);
+ if (this.pos < src.length()) ++this.pos;
+ return c;
+ }
+
+ private String readHexDigit() {
+ char c = this.peekChar(false);
+ if (c >= '0' && c <= '9' || c >= 'a' && c <= 'f' || c >= 'A' && c <= 'F') {
+ ++this.pos;
+ return String.valueOf(c);
+ }
+ if (c != '\0') this.error(Error.EXPECTED_HEX_DIGIT, this.pos);
+ return "";
+ }
+
+ private String readHexDigits(int n) {
+ StringBuilder res = new StringBuilder();
+ while (n-- > 0) {
+ res.append(readHexDigit());
+ }
+ if (res.length() == 0) return "0";
+ return res.toString();
+ }
+
+ private String readDigits(boolean opt) {
+ StringBuilder res = new StringBuilder();
+ for (char c = peekChar(true); c >= '0' && c <= '9'; nextChar(), c = peekChar(true))
+ res.append(c);
+ if (res.length() == 0 && !opt) this.error(Error.EXPECTED_DIGIT);
+ return res.toString();
+ }
+
+ private Double toNumber(String s) {
+ if (s.isEmpty()) return 0.0;
+ return Double.valueOf(s);
+ }
+
+ private String readIdentifier() {
+ StringBuilder res = new StringBuilder();
+ for (char c = peekChar(true);
+ c != '\0' && Character.isJavaIdentifierPart(c);
+ nextChar(), c = peekChar(true)) res.append(c);
+ if (res.length() == 0) this.error(Error.EXPECTED_IDENTIFIER);
+ return res.toString();
+ }
+
+ private void expectRParen() {
+ if (!this.match(")")) this.error(Error.EXPECTED_CLOSING_PAREN, this.pos - 1);
+ }
+
+ private void expectRBrace() {
+ if (!this.match("}")) this.error(Error.EXPECTED_CLOSING_BRACE, this.pos - 1);
+ }
+
+ private void expectRAngle() {
+ if (!this.match(">")) this.error(Error.EXPECTED_CLOSING_ANGLE, this.pos - 1);
+ }
+
+ private boolean lookahead(String... arguments) {
+ for (String prefix : arguments) {
+ if (prefix == null) {
+ if (atEOS()) return true;
+ } else if (inputSubstring(pos, pos + prefix.length()).equals(prefix)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean match(String... arguments) {
+ for (String prefix : arguments) {
+ if (this.lookahead(prefix)) {
+ if (prefix == null) prefix = "";
+ this.pos += prefix.length();
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private RegExpTerm parsePattern() {
+ RegExpTerm res = parseDisjunction();
+ if (!this.atEOS()) this.error(Error.EXPECTED_EOS);
+ return res;
+ }
+
+ protected String inputSubstring(int start, int end) {
+ if (start >= src.length()) return "";
+ if (end > src.length()) end = src.length();
+ return src.substring(start, end);
+ }
+
+ private T finishTerm(T term) {
+ SourceLocation loc = term.getLoc();
+ Position end = pos();
+ loc.setSource(inputSubstring(loc.getStart().getOffset(), end.getOffset()));
+ loc.setEnd(end);
+ return term;
+ }
+
+ private RegExpTerm parseDisjunction() {
+ SourceLocation loc = new SourceLocation(pos());
+ List disjuncts = new ArrayList<>();
+ disjuncts.add(this.parseAlternative());
+ while (this.match("|")) disjuncts.add(this.parseAlternative());
+ if (disjuncts.size() == 1) return disjuncts.get(0);
+ return this.finishTerm(new Disjunction(loc, disjuncts));
+ }
+
+ private RegExpTerm parseAlternative() {
+ SourceLocation loc = new SourceLocation(pos());
+ List elements = new ArrayList<>();
+ while (!this.lookahead(null, "|", ")")) elements.add(this.parseTerm());
+ if (elements.size() == 1) return elements.get(0);
+ return this.finishTerm(new Sequence(loc, elements));
+ }
+
+ private RegExpTerm parseTerm() {
+ SourceLocation loc = new SourceLocation(pos());
+
+ if (this.match("^")) return this.finishTerm(new Caret(loc));
+
+ if (this.match("$")) return this.finishTerm(new Dollar(loc));
+
+ if (this.match("\\b")) return this.finishTerm(new WordBoundary(loc));
+
+ if (this.match("\\B")) return this.finishTerm(new NonWordBoundary(loc));
+
+ if (this.match("(?=")) {
+ RegExpTerm dis = this.parseDisjunction();
+ this.expectRParen();
+ return this.finishTerm(new ZeroWidthPositiveLookahead(loc, dis));
+ }
+
+ if (this.match("(?!")) {
+ RegExpTerm dis = this.parseDisjunction();
+ this.expectRParen();
+ return this.finishTerm(new ZeroWidthNegativeLookahead(loc, dis));
+ }
+
+ if (this.match("(?<=")) {
+ RegExpTerm dis = this.parseDisjunction();
+ this.expectRParen();
+ return this.finishTerm(new ZeroWidthPositiveLookbehind(loc, dis));
+ }
+
+ if (this.match("(?"));
+ }
+
+ if (this.match("p{", "P{")) {
+ String name = this.readIdentifier();
+ if (this.match("=")) {
+ value = this.readIdentifier();
+ raw = "\\p{" + name + "=" + value + "}";
+ } else {
+ value = null;
+ raw = "\\p{" + name + "}";
+ }
+ this.expectRBrace();
+ return this.finishTerm(new UnicodePropertyEscape(loc, name, value, raw));
+ }
+
+ int startpos = this.pos - 1;
+ char c = this.nextChar();
+
+ if (c >= '0' && c <= '9') {
+ raw = c + this.readDigits(true);
+ if (c == '0' || inCharClass) {
+ int base = c == '0' && raw.length() > 1 ? 8 : 10;
+ try {
+ codepoint = Long.parseLong(raw, base);
+ value = fromCodePoint((int) codepoint);
+ } catch (NumberFormatException nfe) {
+ codepoint = 0;
+ value = "\0";
+ }
+ if (base == 8) {
+ this.error(Error.OCTAL_ESCAPE, startpos, this.pos);
+ return this.finishTerm(new OctalEscape(loc, value, (double) codepoint, "\\" + raw));
+ } else {
+ return this.finishTerm(new DecimalEscape(loc, value, (double) codepoint, "\\" + raw));
+ }
+ } else {
+ try {
+ codepoint = Long.parseLong(raw, 10);
+ } catch (NumberFormatException nfe) {
+ codepoint = 0;
+ }
+ BackReference br = this.finishTerm(new BackReference(loc, (double) codepoint, "\\" + raw));
+ this.backrefs.add(br);
+ return br;
+ }
+ }
+
+ String ctrltab = "f\fn\nr\rt\tv\u000b";
+ int idx;
+ if ((idx = ctrltab.indexOf(c)) % 2 == 0) {
+ codepoint = ctrltab.charAt(idx + 1);
+ value = String.valueOf((char) codepoint);
+ return this.finishTerm(new ControlEscape(loc, value, codepoint, "\\" + c));
+ }
+
+ if (c == 'c') {
+ c = this.nextChar();
+ if (!(c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z'))
+ this.error(Error.EXPECTED_CONTROL_LETTER, this.pos - 1);
+ codepoint = c % 32;
+ value = String.valueOf((char) codepoint);
+ return this.finishTerm(new ControlLetter(loc, value, codepoint, "\\c" + c));
+ }
+
+ if ("dDsSwW".indexOf(c) >= 0) {
+ return this.finishTerm(new CharacterClassEscape(loc, String.valueOf(c), "\\" + c));
+ }
+
+ codepoint = c;
+ value = String.valueOf((char) codepoint);
+ return this.finishTerm(new IdentityEscape(loc, value, codepoint, "\\" + c));
+ }
+
+ private RegExpTerm parseCharacterClass() {
+ SourceLocation loc = new SourceLocation(pos());
+ List elements = new ArrayList<>();
+
+ this.match("[");
+ boolean inverted = this.match("^");
+ while (!this.match("]")) {
+ if (this.atEOS()) {
+ this.error(Error.EXPECTED_RBRACKET);
+ break;
+ }
+ elements.add(this.parseCharacterClassElement());
+ }
+ return this.finishTerm(new CharacterClass(loc, elements, inverted));
+ }
+
+ private RegExpTerm parseCharacterClassElement() {
+ SourceLocation loc = new SourceLocation(pos());
+ RegExpTerm atom = this.parseCharacterClassAtom();
+ if (!this.lookahead("-]") && this.match("-"))
+ return this.finishTerm(new CharacterClassRange(loc, atom, this.parseCharacterClassAtom()));
+ return atom;
+ }
+
+ private RegExpTerm parseCharacterClassAtom() {
+ SourceLocation loc = new SourceLocation(pos());
+ char c = this.nextChar();
+ if (c == '\\') {
+ if (this.match("b")) return this.finishTerm(new ControlEscape(loc, "\b", 8, "\\b"));
+ return this.finishTerm(this.parseAtomEscape(loc, true));
+ }
+ return this.finishTerm(new Constant(loc, String.valueOf(c)));
+ }
}
diff --git a/javascript/extractor/src/com/semmle/js/parser/TypeScriptASTConverter.java b/javascript/extractor/src/com/semmle/js/parser/TypeScriptASTConverter.java
index 1e68f5dd812d..8ce33fb4b465 100644
--- a/javascript/extractor/src/com/semmle/js/parser/TypeScriptASTConverter.java
+++ b/javascript/extractor/src/com/semmle/js/parser/TypeScriptASTConverter.java
@@ -1,13 +1,5 @@
package com.semmle.js.parser;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonNull;
@@ -32,6 +24,7 @@
import com.semmle.js.ast.ConditionalExpression;
import com.semmle.js.ast.ContinueStatement;
import com.semmle.js.ast.DebuggerStatement;
+import com.semmle.js.ast.DeclarationFlags;
import com.semmle.js.ast.Decorator;
import com.semmle.js.ast.DoWhileStatement;
import com.semmle.js.ast.DynamicImport;
@@ -63,7 +56,6 @@
import com.semmle.js.ast.LogicalExpression;
import com.semmle.js.ast.MemberDefinition;
import com.semmle.js.ast.MemberExpression;
-import com.semmle.js.ast.DeclarationFlags;
import com.semmle.js.ast.MetaProperty;
import com.semmle.js.ast.MethodDefinition;
import com.semmle.js.ast.MethodDefinition.Kind;
@@ -149,2268 +141,2357 @@
import com.semmle.ts.ast.TypeofTypeExpr;
import com.semmle.ts.ast.UnionTypeExpr;
import com.semmle.util.collections.CollectionUtil;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
/**
- * Utility class for converting a
- * TypeScript AST node
- * into a {@link Result}.
+ * Utility class for converting a TypeScript AST
+ * node into a {@link Result}.
*
- * TypeScript AST nodes that have no JavaScript equivalent are omitted.
+ * TypeScript AST nodes that have no JavaScript equivalent are omitted.
*/
public class TypeScriptASTConverter {
- private String source;
- private final JsonObject nodeFlags;
- private final JsonObject syntaxKinds;
- private final Map nodeFlagMap = new LinkedHashMap<>();
- private final Map syntaxKindMap = new LinkedHashMap<>();
- private int[] lineStarts;
-
- private int syntaxKindExtends;
-
- private final static Pattern LINE_TERMINATOR = Pattern.compile("\\n|\\r\\n|\\r|\\u2028|\\u2029");
- private static final String WHITESPACE_CHAR = "(?:\\s|//.*|/\\*(?:[^*]|\\*(?!/))*\\*/)";
- private static final Pattern WHITESPACE = Pattern.compile("^" + WHITESPACE_CHAR + "*");
- private static final Pattern EXPORT_DECL_START = Pattern.compile("^export" +
- "(" + WHITESPACE_CHAR + "+default)?" +
- WHITESPACE_CHAR + "+");
- private static final Pattern TYPEOF_START = Pattern.compile("^typeof" + WHITESPACE_CHAR + "+");
- private static final Pattern WHITESPACE_END_PAREN = Pattern.compile("^" + WHITESPACE_CHAR + "*\\)");
-
- TypeScriptASTConverter(JsonObject nodeFlags, JsonObject syntaxKinds) {
- this.nodeFlags = nodeFlags;
- this.syntaxKinds = syntaxKinds;
- makeEnumIdMap(nodeFlags, nodeFlagMap);
- makeEnumIdMap(syntaxKinds, syntaxKindMap);
- this.syntaxKindExtends = getSyntaxKind("ExtendsKeyword");
- }
-
- /**
- * Builds a mapping from ID to name given a TypeScript enum object.
- */
- private void makeEnumIdMap(JsonObject enumObject, Map idToName) {
- for (Map.Entry entry : enumObject.entrySet()) {
- JsonPrimitive prim = entry.getValue().getAsJsonPrimitive();
- if (prim.isNumber() && !idToName.containsKey(prim.getAsInt())) {
- idToName.put(prim.getAsInt(), entry.getKey());
- }
- }
- }
-
- /**
- * Convert the given TypeScript AST (which was parsed from {@code source})
- * into a parser {@link Result}.
- */
- public Result convertAST(JsonObject ast, String source) {
- this.lineStarts = toIntArray(ast.getAsJsonArray("$lineStarts"));
-
- List errors = new ArrayList();
-
- // process parse diagnostics (i.e., syntax errors) reported by the TypeScript compiler
- JsonArray parseDiagnostics = ast.get("parseDiagnostics").getAsJsonArray();
- if (parseDiagnostics.size() > 0) {
- for (JsonElement elt : parseDiagnostics) {
- JsonObject parseDiagnostic = elt.getAsJsonObject();
- String message = parseDiagnostic.get("messageText").getAsString();
- Position pos = getPosition(parseDiagnostic.get("$pos"));
- errors.add(new ParseError(message, pos.getLine(), pos.getColumn(), pos.getOffset()));
- }
- return new Result(source, null, new ArrayList<>(), new ArrayList<>(), errors);
- }
-
- this.source = source;
-
- List tokens = new ArrayList<>();
- List comments = new ArrayList<>();
- extractTokensAndComments(ast, tokens, comments);
- Node converted;
- try {
- converted = convertNode(ast);
- } catch (ParseError e) {
- converted = null;
- errors.add(e);
- }
- return new Result(source, converted, tokens, comments, errors);
- }
-
- /**
- * Converts a JSON array to an int array.
- * The array is assumed to only contain integers.
- */
- private static int[] toIntArray(JsonArray array) {
- int[] result = new int[array.size()];
- for (int i = 0; i < array.size(); ++i) {
- result[i] = array.get(i).getAsInt();
- }
- return result;
- }
-
- private int getLineFromPos(int pos) {
- int low = 0, high = this.lineStarts.length - 1;
- while (low < high) {
- int mid = high - ((high - low) >> 1); // Get middle, rounding up.
- int startOfLine = lineStarts[mid];
- if (startOfLine <= pos) {
- low = mid;
- } else {
- high = mid - 1;
- }
- }
- return low;
- }
-
- private int getColumnFromLinePos(int line, int pos) {
- return pos - lineStarts[line];
- }
-
- /**
- * Extract tokens and comments from the given TypeScript AST.
- */
- private void extractTokensAndComments(JsonObject ast, List tokens, List comments) {
- for (JsonElement elt : ast.get("$tokens").getAsJsonArray()) {
- JsonObject token = elt.getAsJsonObject();
- String text = token.get("text").getAsString();
- Position start = getPosition(token.get("tokenPos"));
- Position end = advance(start, text);
- SourceLocation loc = new SourceLocation(text, start, end);
- String kind = getKind(token);
- switch (kind) {
- case "EndOfFileToken":
- tokens.add(new Token(loc, Token.Type.EOF));
- break;
- case "SingleLineCommentTrivia":
- case "MultiLineCommentTrivia":
- String cookedText;
- if (text.startsWith("//"))
- cookedText = text.substring(2);
- else
- cookedText = text.substring(2, text.length()-2);
- comments.add(new Comment(loc, cookedText));
- break;
- case "TemplateHead":
- case "TemplateMiddle":
- case "TemplateTail":
- case "NoSubstitutionTemplateLiteral":
- tokens.add(new Token(loc, Token.Type.STRING));
- break;
- case "Identifier":
- tokens.add(new Token(loc, Token.Type.NAME));
- break;
- case "NumericLiteral":
- tokens.add(new Token(loc, Token.Type.NUM));
- break;
- case "StringLiteral":
- tokens.add(new Token(loc, Token.Type.STRING));
- break;
- case "RegularExpressionLiteral":
- tokens.add(new Token(loc, Token.Type.REGEXP));
- break;
- default:
- Token.Type tp;
- if (kind.endsWith("Token")) {
- tp = Token.Type.PUNCTUATOR;
- } else if (kind.endsWith("Keyword")) {
- if (text.equals("null"))
- tp = Token.Type.NULL;
- else if (text.equals("true"))
- tp = Token.Type.TRUE;
- else if (text.equals("false"))
- tp = Token.Type.FALSE;
- else
- tp = Token.Type.KEYWORD;
- } else {
- continue;
- }
- tokens.add(new Token(loc, tp));
- }
- }
- }
-
- /**
- * Convert the given TypeScript node and its children into a JavaScript {@link Node}.
- */
- private Node convertNode(JsonObject node) throws ParseError {
- return convertNode(node, null);
- }
-
- /**
- * Convert the given TypeScript node and its children into a JavaScript
- * {@link Node}. If the TypesScript node has no explicit {@code kind}, it is
- * assumed to be {@code defaultKind}.
- */
- private Node convertNode(JsonObject node, String defaultKind) throws ParseError {
- Node astNode = convertNodeUntyped(node, defaultKind);
- attachStaticType(astNode, node);
- return astNode;
- }
-
- /**
- * Helper method for `convertNode` that does everything except attaching type
- * information.
- */
- private Node convertNodeUntyped(JsonObject node, String defaultKind) throws ParseError {
- String kind = getKind(node);
- if (kind == null)
- kind = defaultKind;
- if (kind == null)
- kind = "Identifier";
- SourceLocation loc = getSourceLocation(node);
- switch (kind) {
- case "AnyKeyword":
- return convertKeywordTypeExpr(node, loc, "any");
- case "ArrayBindingPattern":
- return convertArrayBindingPattern(node, loc);
- case "ArrayLiteralExpression":
- return convertArrayLiteralExpression(node, loc);
- case "ArrayType":
- return convertArrayType(node, loc);
- case "ArrowFunction":
- return convertArrowFunction(node, loc);
- case "AsExpression":
- return convertAsExpression(node, loc);
- case "AwaitExpression":
- return convertAwaitExpression(node, loc);
- case "BigIntKeyword":
- return convertKeywordTypeExpr(node, loc, "bigint");
- case "BigIntLiteral":
- return convertBigIntLiteral(node, loc);
- case "BinaryExpression":
- return convertBinaryExpression(node, loc);
- case "Block":
- return convertBlock(node, loc);
- case "BooleanKeyword":
- return convertKeywordTypeExpr(node, loc, "boolean");
- case "BreakStatement":
- return convertBreakStatement(node, loc);
- case "CallExpression":
- return convertCallExpression(node, loc);
- case "CallSignature":
- return convertCallSignature(node, loc);
- case "CaseClause":
- return convertCaseClause(node, loc);
- case "CatchClause":
- return convertCatchClause(node, loc);
- case "ClassDeclaration":
- case "ClassExpression":
- return convertClass(node, kind, loc);
- case "CommaListExpression":
- return convertCommaListExpression(node, loc);
- case "ComputedPropertyName":
- return convertComputedPropertyName(node);
- case "ConditionalExpression":
- return convertConditionalExpression(node, loc);
- case "ConditionalType":
- return convertConditionalType(node, loc);
- case "Constructor":
- return convertConstructor(node, loc);
- case "ConstructSignature":
- return convertConstructSignature(node, loc);
- case "ConstructorType":
- return convertConstructorType(node, loc);
- case "ContinueStatement":
- return convertContinueStatement(node, loc);
- case "DebuggerStatement":
- return convertDebuggerStatement(loc);
- case "Decorator":
- return convertDecorator(node, loc);
- case "DefaultClause":
- return convertCaseClause(node, loc);
- case "DeleteExpression":
- return convertDeleteExpression(node, loc);
- case "DoStatement":
- return convertDoStatement(node, loc);
- case "ElementAccessExpression":
- return convertElementAccessExpression(node, loc);
- case "EmptyStatement":
- return convertEmptyStatement(loc);
- case "EnumDeclaration":
- return convertEnumDeclaration(node, loc);
- case "EnumMember":
- return convertEnumMember(node, loc);
- case "ExportAssignment":
- return convertExportAssignment(node, loc);
- case "ExportDeclaration":
- return convertExportDeclaration(node, loc);
- case "ExportSpecifier":
- return convertExportSpecifier(node, loc);
- case "ExpressionStatement":
- return convertExpressionStatement(node, loc);
- case "ExpressionWithTypeArguments":
- return convertExpressionWithTypeArguments(node, loc);
- case "ExternalModuleReference":
- return convertExternalModuleReference(node, loc);
- case "FalseKeyword":
- return convertFalseKeyword(loc);
- case "NeverKeyword":
- return convertKeywordTypeExpr(node, loc, "never");
- case "NumberKeyword":
- return convertKeywordTypeExpr(node, loc, "number");
- case "NumericLiteral":
- return convertNumericLiteral(node, loc);
- case "ForStatement":
- return convertForStatement(node, loc);
- case "ForInStatement":
- return convertForInStatement(node, loc);
- case "ForOfStatement":
- return convertForOfStatement(node, loc);
- case "FunctionDeclaration":
- return convertFunctionDeclaration(node, loc);
- case "FunctionExpression":
- return convertFunctionExpression(node, loc);
- case "FunctionType":
- return convertFunctionType(node, loc);
- case "Identifier":
- return convertIdentifier(node, loc);
- case "IfStatement":
- return convertIfStatement(node, loc);
- case "ImportClause":
- return convertImportClause(node, loc);
- case "ImportDeclaration":
- return convertImportDeclaration(node, loc);
- case "ImportEqualsDeclaration":
- return convertImportEqualsDeclaration(node, loc);
- case "ImportKeyword":
- return convertImportKeyword(loc);
- case "ImportSpecifier":
- return convertImportSpecifier(node, loc);
- case "ImportType":
- return convertImportType(node, loc);
- case "IndexSignature":
- return convertIndexSignature(node, loc);
- case "IndexedAccessType":
- return convertIndexedAccessType(node, loc);
- case "InferType":
- return convertInferType(node, loc);
- case "InterfaceDeclaration":
- return convertInterfaceDeclaration(node, loc);
- case "IntersectionType":
- return convertIntersectionType(node, loc);
- case "JsxAttribute":
- return convertJsxAttribute(node, loc);
- case "JsxClosingElement":
- return convertJsxClosingElement(node, loc);
- case "JsxElement":
- return convertJsxElement(node, loc);
- case "JsxExpression":
- return convertJsxExpression(node, loc);
- case "JsxFragment":
- return convertJsxFragment(node, loc);
- case "JsxOpeningElement":
- return convertJsxOpeningElement(node, loc);
- case "JsxOpeningFragment":
- return convertJsxOpeningFragment(node, loc);
- case "JsxSelfClosingElement":
- return convertJsxSelfClosingElement(node, loc);
- case "JsxClosingFragment":
- return convertJsxClosingFragment(node, loc);
- case "JsxSpreadAttribute":
- return convertJsxSpreadAttribute(node, loc);
- case "JsxText":
- case "JsxTextAllWhiteSpaces":
- return convertJsxText(node, loc);
- case "LabeledStatement":
- return convertLabeledStatement(node, loc);
- case "LiteralType":
- return convertLiteralType(node, loc);
- case "MappedType":
- return convertMappedType(node, loc);
- case "MetaProperty":
- return convertMetaProperty(node, loc);
- case "GetAccessor":
- case "SetAccessor":
- case "MethodDeclaration":
- case "MethodSignature":
- return convertMethodDeclaration(node, kind, loc);
- case "ModuleDeclaration":
- case "NamespaceDeclaration":
- return convertNamespaceDeclaration(node, loc);
- case "ModuleBlock":
- return convertModuleBlock(node, loc);
- case "NamespaceExportDeclaration":
- return convertNamespaceExportDeclaration(node, loc);
- case "NamespaceImport":
- return convertNamespaceImport(node, loc);
- case "NewExpression":
- return convertNewExpression(node, loc);
- case "NonNullExpression":
- return convertNonNullExpression(node, loc);
- case "NoSubstitutionTemplateLiteral":
- return convertNoSubstitutionTemplateLiteral(node, loc);
- case "NullKeyword":
- return convertNullKeyword(loc);
- case "ObjectBindingPattern":
- return convertObjectBindingPattern(node, loc);
- case "ObjectKeyword":
- return convertKeywordTypeExpr(node, loc, "object");
- case "ObjectLiteralExpression":
- return convertObjectLiteralExpression(node, loc);
- case "OmittedExpression":
- return convertOmittedExpression();
- case "OptionalType":
- return convertOptionalType(node, loc);
- case "Parameter":
- return convertParameter(node, loc);
- case "ParenthesizedExpression":
- return convertParenthesizedExpression(node, loc);
- case "ParenthesizedType":
- return convertParenthesizedType(node, loc);
- case "PostfixUnaryExpression":
- return convertPostfixUnaryExpression(node, loc);
- case "PrefixUnaryExpression":
- return convertPrefixUnaryExpression(node, loc);
- case "PropertyAccessExpression":
- return convertPropertyAccessExpression(node, loc);
- case "PropertyAssignment":
- return convertPropertyAssignment(node, loc);
- case "PropertyDeclaration":
- case "PropertySignature":
- return convertPropertyDeclaration(node, kind, loc);
- case "RegularExpressionLiteral":
- return convertRegularExpressionLiteral(loc);
- case "RestType":
- return convertRestType(node, loc);
- case "QualifiedName":
- return convertQualifiedName(node, loc);
- case "ReturnStatement":
- return convertReturnStatement(node, loc);
- case "SemicolonClassElement":
- return convertSemicolonClassElement();
- case "SourceFile":
- return convertSourceFile(node, loc);
- case "ShorthandPropertyAssignment":
- return convertShorthandPropertyAssignment(node, loc);
- case "SpreadAssignment":
- case "SpreadElement":
- case "SpreadElementExpression":
- return convertSpreadElement(node, loc);
- case "StringKeyword":
- return convertKeywordTypeExpr(node, loc, "string");
- case "StringLiteral":
- return convertStringLiteral(node, loc);
- case "SuperKeyword":
- return convertSuperKeyword(loc);
- case "SwitchStatement":
- return convertSwitchStatement(node, loc);
- case "SymbolKeyword":
- return convertKeywordTypeExpr(node, loc, "symbol");
- case "TaggedTemplateExpression":
- return convertTaggedTemplateExpression(node, loc);
- case "TemplateExpression":
- return convertTemplateExpression(node, loc);
- case "TemplateHead":
- case "TemplateMiddle":
- case "TemplateTail":
- return convertTemplateElement(node, kind, loc);
- case "ThisKeyword":
- return convertThisKeyword(loc);
- case "ThisType":
- return convertKeywordTypeExpr(node, loc, "this");
- case "ThrowStatement":
- return convertThrowStatement(node, loc);
- case "TrueKeyword":
- return convertTrueKeyword(loc);
- case "TryStatement":
- return convertTryStatement(node, loc);
- case "TupleType":
- return convertTupleType(node, loc);
- case "TypeAliasDeclaration":
- return convertTypeAliasDeclaration(node, loc);
- case "TypeAssertionExpression":
- return convertTypeAssertionExpression(node, loc);
- case "TypeLiteral":
- return convertTypeLiteral(node, loc);
- case "TypeOfExpression":
- return convertTypeOfExpression(node, loc);
- case "TypeOperator":
- return convertTypeOperator(node, loc);
- case "TypeParameter":
- return convertTypeParameter(node, loc);
- case "TypePredicate":
- return convertTypePredicate(node, loc);
- case "TypeReference":
- return convertTypeReference(node, loc);
- case "TypeQuery":
- return convertTypeQuery(node, loc);
- case "UndefinedKeyword":
- return convertKeywordTypeExpr(node, loc, "undefined");
- case "UnionType":
- return convertUnionType(node, loc);
- case "UnknownKeyword":
- return convertKeywordTypeExpr(node, loc, "unknown");
- case "VariableDeclaration":
- return convertVariableDeclaration(node, loc);
- case "VariableDeclarationList":
- return convertVariableDeclarationList(node, loc);
- case "VariableStatement":
- return convertVariableStatement(node, loc);
- case "VoidExpression":
- return convertVoidExpression(node, loc);
- case "VoidKeyword":
- return convertKeywordTypeExpr(node, loc, "void");
- case "WhileStatement":
- return convertWhileStatement(node, loc);
- case "WithStatement":
- return convertWithStatement(node, loc);
- case "YieldExpression":
- return convertYieldExpression(node, loc);
- default:
- throw new ParseError("Unsupported TypeScript syntax " + kind, getSourceLocation(node).getStart());
- }
- }
-
- /**
- * Attaches type information from the JSON object to the given AST node, if
- * applicable. This is called from {@link #convertNode}.
- */
- private void attachStaticType(Node astNode, JsonObject json) {
- if (astNode instanceof ITypedAstNode && json.has("$type")) {
- ITypedAstNode typedAstNode = (ITypedAstNode) astNode;
- int typeId = json.get("$type").getAsInt();
- typedAstNode.setStaticTypeId(typeId);
- }
- }
-
- /**
- * Attaches a TypeScript compiler symbol to the given node, if any was provided.
- */
- private void attachSymbolInformation(INodeWithSymbol node, JsonObject json) {
- if (json.has("$symbol")) {
- int symbol = json.get("$symbol").getAsInt();
- node.setSymbol(symbol);
- }
- }
-
- /**
- * Attaches call signatures and related symbol information to a call site.
- */
- private void attachResolvedSignature(InvokeExpression node, JsonObject json) {
- if (json.has("$resolvedSignature")) {
- int id = json.get("$resolvedSignature").getAsInt();
- node.setResolvedSignatureId(id);
- }
- if (json.has("$overloadIndex")) {
- int id = json.get("$overloadIndex").getAsInt();
- node.setOverloadIndex(id);
- }
- attachSymbolInformation(node, json);
- }
-
- /**
- * Convert the given array of TypeScript AST nodes into a list of JavaScript AST nodes,
- * skipping any {@code null} elements.
- */
- private List convertNodes(Iterable nodes) throws ParseError {
- return convertNodes(nodes, true);
- }
-
- /**
- * Convert the given array of TypeScript AST nodes into a list of JavaScript AST nodes,
- * where {@code skipNull} indicates whether {@code null} elements should be skipped or not.
- */
- @SuppressWarnings("unchecked")
- private List convertNodes(Iterable nodes,
- boolean skipNull) throws ParseError {
- List res = new ArrayList();
- for (JsonElement elt : nodes) {
- T converted = (T)convertNode(elt.getAsJsonObject());
- if (!skipNull || converted != null)
- res.add(converted);
- }
- return res;
- }
-
- /**
- * Converts the given child to an AST node of the given type or null.
- * A ParseError is thrown if a different type of node was found.
- *
- * This is used to detect syntax errors that are not reported as syntax errors
- * by the TypeScript parser. Usually they are reported as errors in a later
- * compiler stage, which the extractor does not run.
- *
- * Returns null if the child is absent.
- */
- @SuppressWarnings("unchecked")
- private T tryConvertChild(JsonObject node, String prop, Class expectedType) throws ParseError {
- Node child = convertChild(node, prop);
- if (child == null || expectedType.isInstance(child)) {
- return (T) child;
- } else {
- throw new ParseError("Unsupported TypeScript syntax", getSourceLocation(node).getStart());
- }
- }
-
- /**
- * Convert the child node named {@code prop} of AST node {@code node}.
- */
- private T convertChild(JsonObject node, String prop) throws ParseError {
- return convertChild(node, prop, null);
- }
-
- /**
- * Convert the child node named {@code prop} of AST node {@code node}, with
- * {@code kind} as its default kind.
- */
- @SuppressWarnings("unchecked")
- private T convertChild(JsonObject node, String prop, String kind) throws ParseError {
- JsonElement child = node.get(prop);
- if (child == null)
- return null;
- return (T)convertNode(child.getAsJsonObject(), kind);
- }
-
- /**
- * Convert the child nodes named {@code prop} of AST node {@code node}.
- */
- private List convertChildren(JsonObject node, String prop) throws ParseError {
- return convertChildren(node, prop, true);
- }
-
- /**
- * Like convertChildren but returns an empty list if the property is missing.
- */
- private List convertChildrenNotNull(JsonObject node, String prop) throws ParseError {
- List nodes = convertChildren(node, prop, true);
- if (nodes == null) {
- return Collections.emptyList();
- }
- return nodes;
- }
-
- /**
- * Convert the child nodes named {@code prop} of AST node {@code node}, where
- * {@code skipNull} indicates whether or not to skip null children.
- */
- private List convertChildren(JsonObject node, String prop,
- boolean skipNull) throws ParseError {
- JsonElement child = node.get(prop);
- if (child == null)
- return null;
- return convertNodes(child.getAsJsonArray(), skipNull);
- }
-
- /* Converter methods for the individual TypeScript AST node types. */
-
- private Node convertArrayBindingPattern(JsonObject array, SourceLocation loc) throws ParseError {
- List elements = new ArrayList<>();
- for (JsonElement elt : array.get("elements").getAsJsonArray()) {
- JsonObject element = (JsonObject) elt;
- SourceLocation eltLoc = getSourceLocation(element);
- Expression convertedElt = convertChild(element, "name");
- if (hasChild(element, "initializer"))
- convertedElt = new AssignmentPattern(eltLoc, "=", convertedElt, convertChild(element, "initializer"));
- else if (hasChild(element, "dotDotDotToken"))
- convertedElt = new RestElement(eltLoc, convertedElt);
- elements.add(convertedElt);
- }
- return new ArrayPattern(loc, elements);
- }
-
- private Node convertArrayLiteralExpression(JsonObject node, SourceLocation loc) throws ParseError {
- return new ArrayExpression(loc, convertChildren(node, "elements", false));
- }
-
- private Node convertArrayType(JsonObject node, SourceLocation loc) throws ParseError {
- return new ArrayTypeExpr(loc, convertChildAsType(node, "elementType"));
- }
-
- private Node convertArrowFunction(JsonObject node, SourceLocation loc) throws ParseError {
- return new ArrowFunctionExpression(loc, convertParameters(node), convertChild(node, "body"), false,
- hasModifier(node, "AsyncKeyword"), convertChildrenNotNull(node, "typeParameters"), convertParameterTypes(node),
- convertChildAsType(node, "type"));
- }
-
- private Node convertAsExpression(JsonObject node, SourceLocation loc) throws ParseError {
- return new TypeAssertion(loc, convertChild(node, "expression"), convertChildAsType(node, "type"), true);
- }
-
- private Node convertAwaitExpression(JsonObject node, SourceLocation loc) throws ParseError {
- return new AwaitExpression(loc, convertChild(node, "expression"));
- }
-
- private Node convertBigIntLiteral(JsonObject node, SourceLocation loc) throws ParseError {
- String text = node.get("text").getAsString();
- String value = text.substring(0, text.length() - 1); // Remove the 'n' suffix.
- return new Literal(loc, TokenType.bigint, value);
- }
-
- private Node convertBinaryExpression(JsonObject node, SourceLocation loc) throws ParseError {
- Expression left = convertChild(node, "left");
- Expression right = convertChild(node, "right");
- JsonObject operatorToken = node.get("operatorToken").getAsJsonObject();
- String operator = getSourceLocation(operatorToken).getSource();
- switch (operator) {
- case ",":
- List expressions = new ArrayList();
- if (left instanceof SequenceExpression)
- expressions.addAll(((SequenceExpression) left).getExpressions());
- else
- expressions.add(left);
- if (right instanceof SequenceExpression)
- expressions.addAll(((SequenceExpression) right).getExpressions());
- else
- expressions.add(right);
- return new SequenceExpression(loc, expressions);
-
- case "||":
- case "&&":
- return new LogicalExpression(loc, operator, left, right);
-
- case "=":
- left = convertLValue(left); // For plain assignments, the lhs can be a destructuring pattern.
- return new AssignmentExpression(loc, operator, left, right);
-
- case "+=":
- case "-=":
- case "*=":
- case "**=":
- case "/=":
- case "%=":
- case "^=":
- case "&=":
- case "|=":
- case ">>=":
- case "<<=":
- case ">>>=":
- return new AssignmentExpression(loc, operator, convertLValue(left), right);
-
- default:
- return new BinaryExpression(loc, operator, left, right);
- }
- }
-
- private Node convertBlock(JsonObject node, SourceLocation loc) throws ParseError {
- return new BlockStatement(loc, convertChildren(node, "statements"));
- }
-
- private Node convertBreakStatement(JsonObject node, SourceLocation loc) throws ParseError {
- return new BreakStatement(loc, convertChild(node, "label"));
- }
-
- private Node convertCallExpression(JsonObject node, SourceLocation loc) throws ParseError {
- List arguments = convertChildren(node, "arguments");
- if (arguments.size() == 1 && hasKind(node.get("expression"), "ImportKeyword")) {
- return new DynamicImport(loc, arguments.get(0));
- }
- Expression callee = convertChild(node, "expression");
- List typeArguments = convertChildrenAsTypes(node, "typeArguments");
- CallExpression call = new CallExpression(loc, callee, typeArguments, arguments, false, false);
- attachResolvedSignature(call, node);
- return call;
- }
-
- private MethodDefinition convertCallSignature(JsonObject node, SourceLocation loc) throws ParseError {
- FunctionExpression function = convertImplicitFunction(node, loc);
- int flags = getMemberModifierKeywords(node) | DeclarationFlags.abstract_;
- return new MethodDefinition(loc, flags, Kind.FUNCTION_CALL_SIGNATURE, null, function);
- }
-
- private Node convertCaseClause(JsonObject node, SourceLocation loc) throws ParseError {
- return convertDefaultClause(node, loc);
- }
-
- private Node convertCatchClause(JsonObject node, SourceLocation loc) throws ParseError {
- IPattern pattern = null;
- JsonElement variableDecl = node.get("variableDeclaration");
- if (variableDecl != null)
- pattern = convertChild(variableDecl.getAsJsonObject(), "name");
- return new CatchClause(loc, pattern, null, convertChild(node, "block"));
- }
-
- private List convertSuperInterfaceClause(JsonArray supers) throws ParseError {
- List result = new ArrayList<>();
- for (JsonElement elt : supers) {
- JsonObject superType = elt.getAsJsonObject();
- ITypeExpression objectType = convertChildAsType(superType, "expression");
- if (objectType == null)
- continue;
- List typeArguments = convertChildrenAsTypes(superType, "typeArguments");
- if (typeArguments.isEmpty()) {
- result.add(objectType);
- } else {
- result.add(new GenericTypeExpr(getSourceLocation(superType), objectType, typeArguments));
- }
- }
- return result;
- }
-
- private Node convertClass(JsonObject node, String kind, SourceLocation loc) throws ParseError {
- Identifier id = convertChild(node, "name");
- List typeParameters = convertChildrenNotNull(node, "typeParameters");
- Expression superClass = null;
- List superInterfaces = null;
- int afterHead = id == null ? loc.getStart().getOffset() + 5 : id.getLoc().getEnd().getOffset();
- for (JsonElement elt : getChildIterable(node, "heritageClauses")) {
- JsonObject heritageClause = elt.getAsJsonObject();
- JsonArray supers = heritageClause.get("types").getAsJsonArray();
- if (heritageClause.get("token").getAsInt() == syntaxKindExtends) {
- if (supers.size() > 0) {
- superClass = (Expression) convertNode(supers.get(0).getAsJsonObject());
- }
- } else {
- superInterfaces = convertSuperInterfaceClause(supers);
- }
- afterHead = heritageClause.get("$end").getAsInt();
- }
- if (superInterfaces == null) {
- superInterfaces = new ArrayList<>();
- }
- String skip = source.substring(loc.getStart().getOffset(), afterHead) + matchWhitespace(afterHead);
- SourceLocation bodyLoc = new SourceLocation(loc.getSource(), loc.getStart(), loc.getEnd());
- advance(bodyLoc, skip);
- ClassBody body = new ClassBody(bodyLoc, convertChildren(node, "members"));
- if ("ClassExpression".equals(kind)) {
- ClassExpression classExpr = new ClassExpression(loc, id, typeParameters, superClass, superInterfaces, body);
- attachSymbolInformation(classExpr.getClassDef(), node);
- return classExpr;
- }
- boolean hasDeclareKeyword = hasModifier(node, "DeclareKeyword");
- boolean hasAbstractKeyword = hasModifier(node, "AbstractKeyword");
- ClassDeclaration classDecl = new ClassDeclaration(loc, id, typeParameters, superClass, superInterfaces, body, hasDeclareKeyword,
- hasAbstractKeyword);
- attachSymbolInformation(classDecl.getClassDef(), node);
- if (node.has("decorators")) {
- classDecl.addDecorators(convertChildren(node, "decorators"));
- advanceUntilAfter(loc, classDecl.getDecorators());
- }
- return fixExports(loc, classDecl);
- }
-
- private Node convertCommaListExpression(JsonObject node,
- SourceLocation loc) throws ParseError {
- return new SequenceExpression(loc, convertChildren(node, "elements"));
- }
-
- private Node convertComputedPropertyName(JsonObject node) throws ParseError {
- return convertChild(node, "expression");
- }
-
- private Node convertConditionalExpression(JsonObject node,
- SourceLocation loc) throws ParseError {
- return new ConditionalExpression(loc, convertChild(node, "condition"), convertChild(node, "whenTrue"), convertChild(node, "whenFalse"));
- }
-
- private Node convertConditionalType(JsonObject node, SourceLocation loc) throws ParseError {
- return new ConditionalTypeExpr(loc, convertChild(node, "checkType"), convertChild(node, "extendsType"),
- convertChild(node, "trueType"), convertChild(node, "falseType"));
- }
-
- private SourceLocation getSourceRange(Position from, Position to) {
- return new SourceLocation(source.substring(from.getOffset(), to.getOffset()), from, to);
- }
-
- private DecoratorList makeDecoratorList(JsonElement decorators) throws ParseError {
- if (!(decorators instanceof JsonArray)) return null;
- JsonArray array = decorators.getAsJsonArray();
- SourceLocation firstLoc = null, lastLoc = null;
- List list = new ArrayList<>();
- for (JsonElement decoratorElm : array) {
- JsonObject decorator = decoratorElm.getAsJsonObject();
- if (hasKind(decorator, "Decorator")) {
- SourceLocation location = getSourceLocation(decorator);
- list.add(convertDecorator(decorator, location));
- if (firstLoc == null) {
- firstLoc = location;
- }
- lastLoc = location;
- }
- }
- if (firstLoc == null)
- return null;
- return new DecoratorList(getSourceRange(firstLoc.getStart(), lastLoc.getEnd()), list);
- }
-
- private List convertParameterDecorators(JsonObject function) throws ParseError {
- List decoratorLists = new ArrayList<>();
- for (JsonElement parameter : getProperParameters(function)) {
- decoratorLists.add(makeDecoratorList(parameter.getAsJsonObject().get("decorators")));
- }
- return decoratorLists;
- }
-
- private Node convertConstructor(JsonObject node, SourceLocation loc) throws ParseError {
- int flags = getMemberModifierKeywords(node);
- boolean isComputed = hasComputedName(node);
- boolean isStatic = DeclarationFlags.isStatic(flags);
- if (isComputed) {
- flags |= DeclarationFlags.computed;
- }
- // for some reason, the TypeScript compiler treats static methods named "constructor"
- // and methods with computed name "constructor" as constructors, even though they aren't
- MethodDefinition.Kind methodKind = isStatic || isComputed ? Kind.METHOD : Kind.CONSTRUCTOR;
- Expression key;
- if (isComputed)
- key = convertChild((JsonObject) node.get("name"), "expression");
- else
- key = new Identifier(loc, "constructor");
- List params = convertParameters(node);
- List paramTypes = convertParameterTypes(node);
- List paramDecorators = convertParameterDecorators(node);
- FunctionExpression value = new FunctionExpression(loc, null, params, convertChild(node, "body"), false, false,
- Collections.emptyList(), paramTypes, paramDecorators, null, null);
- attachSymbolInformation(value, node);
- List parameterFields = convertParameterFields(node);
- return new MethodDefinition(loc, flags, methodKind, key, value, parameterFields);
- }
-
- private MethodDefinition convertConstructSignature(JsonObject node, SourceLocation loc) throws ParseError {
- FunctionExpression function = convertImplicitFunction(node, loc);
- int flags = getMemberModifierKeywords(node) | DeclarationFlags.abstract_;
- return new MethodDefinition(loc, flags, Kind.CONSTRUCTOR_CALL_SIGNATURE, null, function);
- }
-
- private Node convertConstructorType(JsonObject node, SourceLocation loc) throws ParseError {
- return new FunctionTypeExpr(loc, convertImplicitFunction(node, loc), true);
- }
-
- private Node convertContinueStatement(JsonObject node, SourceLocation loc) throws ParseError {
- return new ContinueStatement(loc, convertChild(node, "label"));
- }
-
- private Node convertDebuggerStatement(SourceLocation loc) {
- return new DebuggerStatement(loc);
- }
-
- private Decorator convertDecorator(JsonObject node, SourceLocation loc) throws ParseError {
- return new Decorator(loc, convertChild(node, "expression"));
- }
-
- private Node convertDefaultClause(JsonObject node, SourceLocation loc) throws ParseError {
- return new SwitchCase(loc, convertChild(node, "expression"), convertChildren(node, "statements"));
- }
-
- private Node convertDeleteExpression(JsonObject node, SourceLocation loc) throws ParseError {
- return new UnaryExpression(loc, "delete", convertChild(node, "expression"), true);
- }
-
- private Node convertDoStatement(JsonObject node, SourceLocation loc) throws ParseError {
- return new DoWhileStatement(loc, convertChild(node, "expression"), convertChild(node, "statement"));
- }
-
- private Node convertElementAccessExpression(JsonObject node,
- SourceLocation loc) throws ParseError {
- Expression object = convertChild(node, "expression");
- Expression property = convertChild(node, "argumentExpression");
- return new MemberExpression(loc, object, property, true, false, false);
- }
-
- private Node convertEmptyStatement(SourceLocation loc) {
- return new EmptyStatement(loc);
- }
-
- private Node convertEnumDeclaration(JsonObject node, SourceLocation loc) throws ParseError {
- EnumDeclaration enumDeclaration = new EnumDeclaration(loc, hasModifier(node, "ConstKeyword"), hasModifier(node, "DeclareKeyword"),
- convertChildrenNotNull(node, "decorators"), convertChild(node, "name"), convertChildren(node, "members"));
- attachSymbolInformation(enumDeclaration, node);
- advanceUntilAfter(loc, enumDeclaration.getDecorators());
- return fixExports(loc, enumDeclaration);
- }
-
- /**
- * Converts a TypeScript Identifier or StringLiteral node to an Identifier AST
- * node, or {@code null} if the given node is not of the expected kind.
- */
- private Identifier convertNodeAsIdentifier(JsonObject node) throws ParseError {
- SourceLocation loc = getSourceLocation(node);
- if (isIdentifier(node)) {
- return convertIdentifier(node, loc);
- } else if (hasKind(node, "StringLiteral")) {
- return new Identifier(loc, node.get("text").getAsString());
- } else {
- return null;
- }
- }
-
- private Node convertEnumMember(JsonObject node, SourceLocation loc) throws ParseError {
- Identifier name = convertNodeAsIdentifier(node.get("name").getAsJsonObject());
- if (name == null)
- return null;
- EnumMember member = new EnumMember(loc, name, convertChild(node, "initializer"));
- attachSymbolInformation(member, node);
- return member;
- }
-
- private Node convertExportAssignment(JsonObject node, SourceLocation loc) throws ParseError {
- if (hasChild(node, "isExportEquals") && node.get("isExportEquals").getAsBoolean())
- return new ExportWholeDeclaration(loc, convertChild(node, "expression"));
- return new ExportDefaultDeclaration(loc, convertChild(node, "expression"));
- }
-
- private Node convertExportDeclaration(JsonObject node, SourceLocation loc) throws ParseError {
- Literal source = tryConvertChild(node, "moduleSpecifier", Literal.class);
- if (hasChild(node, "exportClause")) {
- return new ExportNamedDeclaration(loc, null, convertChildren(node.get("exportClause").getAsJsonObject(), "elements"), source);
- } else {
- return new ExportAllDeclaration(loc, source);
- }
- }
-
- private Node convertExportSpecifier(JsonObject node, SourceLocation loc) throws ParseError {
- return new ExportSpecifier(loc, convertChild(node, hasChild(node, "propertyName") ? "propertyName" : "name"), convertChild(node, "name"));
- }
-
- private Node convertExpressionStatement(JsonObject node,
- SourceLocation loc) throws ParseError {
- Expression expression = convertChild(node, "expression");
- return new ExpressionStatement(loc, expression);
- }
-
- private Node convertExpressionWithTypeArguments(JsonObject node, SourceLocation loc) throws ParseError {
- Expression expression = convertChild(node, "expression");
- List typeArguments = convertChildrenAsTypes(node, "typeArguments");
- if (typeArguments.isEmpty())
- return expression;
- return new ExpressionWithTypeArguments(loc, expression, typeArguments);
- }
-
- private Node convertExternalModuleReference(JsonObject node, SourceLocation loc) throws ParseError {
- return new ExternalModuleReference(loc, convertChild(node, "expression"));
- }
-
- private Node convertFalseKeyword(SourceLocation loc) {
- return new Literal(loc, TokenType._false, false);
- }
-
- private Node convertNumericLiteral(JsonObject node, SourceLocation loc)
- throws NumberFormatException {
- return new Literal(loc, TokenType.num, Double.valueOf(node.get("text").getAsString()));
- }
-
- private Node convertForStatement(JsonObject node, SourceLocation loc) throws ParseError {
- return new ForStatement(loc, convertChild(node, "initializer"), convertChild(node, "condition"), convertChild(node, "incrementor"), convertChild(node, "statement"));
- }
-
- private Node convertForInStatement(JsonObject node, SourceLocation loc) throws ParseError {
- Node initializer = convertChild(node, "initializer");
- if (initializer instanceof Expression)
- initializer = convertLValue((Expression) initializer);
- return new ForInStatement(loc, initializer, convertChild(node, "expression"), convertChild(node, "statement"), false);
- }
-
- private Node convertForOfStatement(JsonObject node, SourceLocation loc) throws ParseError {
- Node initializer = convertChild(node, "initializer");
- if (initializer instanceof Expression)
- initializer = convertLValue((Expression) initializer);
- return new ForOfStatement(loc, initializer, convertChild(node, "expression"), convertChild(node, "statement"));
- }
-
- private Node convertFunctionDeclaration(JsonObject node,
- SourceLocation loc) throws ParseError {
- List params = convertParameters(node);
- Identifier fnId = convertChild(node, "name", "Identifier");
- BlockStatement fnbody = convertChild(node, "body");
- boolean generator = hasChild(node, "asteriskToken");
- boolean async = hasModifier(node, "AsyncKeyword");
- boolean hasDeclareKeyword = hasModifier(node, "DeclareKeyword");
- List paramTypes = convertParameterTypes(node);
- List typeParameters = convertChildrenNotNull(node, "typeParameters");
- ITypeExpression returnType = convertChildAsType(node, "type");
- ITypeExpression thisParam = convertThisParameterType(node);
- FunctionDeclaration function = new FunctionDeclaration(loc, fnId, params, fnbody, generator, async, hasDeclareKeyword,
- typeParameters, paramTypes, returnType, thisParam);
- attachSymbolInformation(function, node);
- return fixExports(loc, function);
- }
-
- private Node convertFunctionExpression(JsonObject node,
- SourceLocation loc) throws ParseError {
- Identifier fnId = convertChild(node, "name", "Identifier");
- List params = convertParameters(node);
- BlockStatement fnbody = convertChild(node, "body");
- boolean generator = hasChild(node, "asteriskToken");
- boolean async = hasModifier(node, "AsyncKeyword");
- List paramTypes = convertParameterTypes(node);
- List paramDecorators = convertParameterDecorators(node);
- ITypeExpression returnType = convertChildAsType(node, "type");
- ITypeExpression thisParam = convertThisParameterType(node);
- return new FunctionExpression(loc, fnId, params, fnbody, generator, async, convertChildrenNotNull(node, "typeParameters"),
- paramTypes, paramDecorators, returnType, thisParam);
- }
-
- private Node convertFunctionType(JsonObject node, SourceLocation loc) throws ParseError {
- return new FunctionTypeExpr(loc, convertImplicitFunction(node, loc), false);
- }
-
- /**
- * Gets the original text out of an Identifier's "escapedText" field.
- */
- private String unescapeLeadingUnderscores(String text) {
- // The TypeScript compiler inserts an additional underscore in front of
- // identifiers that begin with two underscores.
- if (text.startsWith("___")) {
- return text.substring(1);
- } else {
- return text;
- }
- }
-
- /** Returns the contents of the given identifier as a string. */
- private String getIdentifierText(JsonObject identifierNode) {
- if (identifierNode.has("text"))
- return identifierNode.get("text").getAsString();
- else
- return unescapeLeadingUnderscores(identifierNode.get("escapedText").getAsString());
- }
-
- private Identifier convertIdentifier(JsonObject node, SourceLocation loc) {
- Identifier id = new Identifier(loc, getIdentifierText(node));
- attachSymbolInformation(id, node);
- return id;
- }
-
- private Node convertKeywordTypeExpr(JsonObject node, SourceLocation loc, String text) {
- return new KeywordTypeExpr(loc, text);
- }
-
- private Node convertUnionType(JsonObject node, SourceLocation loc) throws ParseError {
- return new UnionTypeExpr(loc, convertChildrenAsTypes(node, "types"));
- }
-
- private Node convertIfStatement(JsonObject node, SourceLocation loc) throws ParseError {
- return new IfStatement(loc, convertChild(node, "expression"), convertChild(node, "thenStatement"), convertChild(node, "elseStatement"));
- }
-
- private Node convertImportClause(JsonObject node, SourceLocation loc) throws ParseError {
- return new ImportDefaultSpecifier(loc, convertChild(node, "name"));
- }
-
- private Node convertImportDeclaration(JsonObject node, SourceLocation loc) throws ParseError {
- Literal src = tryConvertChild(node, "moduleSpecifier", Literal.class);
- List specifiers = new ArrayList<>();
- if (hasChild(node, "importClause")) {
- JsonObject importClause = node.get("importClause").getAsJsonObject();
- if (hasChild(importClause, "name")) {
- specifiers.add(convertChild(node, "importClause"));
- }
- if (hasChild(importClause, "namedBindings")) {
- JsonObject namedBindings = importClause.get("namedBindings").getAsJsonObject();
- if (hasKind(namedBindings, "NamespaceImport")) {
- specifiers.add(convertChild(importClause, "namedBindings"));
- } else {
- specifiers.addAll(convertChildren(namedBindings, "elements"));
- }
- }
- }
- return new ImportDeclaration(loc, specifiers, src);
- }
-
- private Node convertImportEqualsDeclaration(JsonObject node, SourceLocation loc) throws ParseError {
- return fixExports(loc, new ImportWholeDeclaration(loc, convertChild(node, "name"), convertChild(node, "moduleReference")));
- }
-
- private Node convertImportKeyword(SourceLocation loc) {
- return new Identifier(loc, "import");
- }
-
- private Node convertImportSpecifier(JsonObject node, SourceLocation loc) throws ParseError {
- boolean hasImported = hasChild(node, "propertyName");
- Identifier imported = convertChild(node, hasImported ? "propertyName" : "name");
- Identifier local = convertChild(node, "name");
- return new ImportSpecifier(loc, imported, local);
- }
-
- private Node convertImportType(JsonObject node, SourceLocation loc) throws ParseError {
- // This is a type such as `import("./foo").bar.Baz`.
- //
- // The TypeScript AST represents import types as the root of a qualified name,
- // whereas we represent them as the leftmost qualifier.
- //
- // So in our AST, ImportTypeExpr just represents `import("./foo")`, and `.bar.Baz`
- // is represented by nested MemberExpr nodes.
- //
- // Additionally, an import type can be prefixed by `typeof`, such as `typeof import("foo")`.
- // We convert these to TypeofTypeExpr.
-
- // Get the source range of the `import(path)` part.
- Position importStart = loc.getStart();
- Position importEnd = loc.getEnd();
- boolean isTypeof = false;
- if (node.has("isTypeOf") && node.get("isTypeOf").getAsBoolean() == true) {
- isTypeof = true;
- Matcher m = TYPEOF_START.matcher(loc.getSource());
- if (m.find()) {
- importStart = advance(importStart, m.group(0));
- }
- }
- // Find the ending parenthesis in `import(path)` by skipping whitespace after `path`.
- ITypeExpression path = convertChild(node, "argument");
- String endSrc = loc.getSource().substring(path.getLoc().getEnd().getOffset() - loc.getStart().getOffset());
- Matcher m = WHITESPACE_END_PAREN.matcher(endSrc);
- if (m.find()) {
- importEnd = advance(path.getLoc().getEnd(), m.group(0));
- }
- SourceLocation importLoc = getSourceRange(importStart, importEnd);
- ImportTypeExpr imprt = new ImportTypeExpr(importLoc, path);
-
- ITypeExpression typeName = buildQualifiedTypeAccess(imprt, (JsonObject) node.get("qualifier"));
- if (isTypeof) {
- return new TypeofTypeExpr(loc, typeName);
- }
-
- List typeArguments = convertChildrenAsTypes(node, "typeArguments");
- if (!typeArguments.isEmpty()) {
- return new GenericTypeExpr(loc, typeName, typeArguments);
- }
- return (Node) typeName;
- }
-
- /**
- * Converts the given JSON to a qualified name with `root` as the base.
- *
- * For example, `a.b.c` is converted to the AST corresponding to `root.a.b.c`.
- */
- private ITypeExpression buildQualifiedTypeAccess(ITypeExpression root, JsonObject node) throws ParseError {
- if (node == null) {
- return root;
- }
- String kind = getKind(node);
- ITypeExpression base;
- Expression name;
- if (kind == null || kind.equals("Identifier")) {
- base = root;
- name = convertIdentifier(node, getSourceLocation(node));
- } else if (kind.equals("QualifiedName")) {
- base = buildQualifiedTypeAccess(root, (JsonObject) node.get("left"));
- name = convertChild(node, "right");
- } else {
- throw new ParseError("Unsupported syntax in import type", getSourceLocation(node).getStart());
- }
- MemberExpression member = new MemberExpression(getSourceLocation(node), (Expression) base, name, false, false, false);
- attachSymbolInformation(member, node);
- return member;
- }
-
- private Node convertIndexSignature(JsonObject node, SourceLocation loc) throws ParseError {
- FunctionExpression function = convertImplicitFunction(node, loc);
- int flags = getMemberModifierKeywords(node) | DeclarationFlags.abstract_;
- return new MethodDefinition(loc, flags, Kind.INDEX_SIGNATURE, null, function);
- }
-
- private Node convertIndexedAccessType(JsonObject node, SourceLocation loc) throws ParseError {
- return new IndexedAccessTypeExpr(loc, convertChildAsType(node, "objectType"), convertChildAsType(node, "indexType"));
- }
-
- private Node convertInferType(JsonObject node, SourceLocation loc) throws ParseError {
- return new InferTypeExpr(loc, convertChild(node, "typeParameter"));
- }
-
- private Node convertInterfaceDeclaration(JsonObject node, SourceLocation loc) throws ParseError {
- Identifier name = convertChild(node, "name");
- List typeParameters = convertChildrenNotNull(node, "typeParameters");
- List> members = convertChildren(node, "members");
- List superInterfaces = null;
- for (JsonElement elt : getChildIterable(node, "heritageClauses")) {
- JsonObject heritageClause = elt.getAsJsonObject();
- if (heritageClause.get("token").getAsInt() == syntaxKindExtends) {
- superInterfaces = convertSuperInterfaceClause(heritageClause.get("types").getAsJsonArray());
- break;
- }
- }
- if (superInterfaces == null) {
- superInterfaces = new ArrayList<>();
- }
- InterfaceDeclaration iface = new InterfaceDeclaration(loc, name, typeParameters, superInterfaces, members);
- attachSymbolInformation(iface, node);
- return fixExports(loc, iface);
- }
-
- private Node convertIntersectionType(JsonObject node, SourceLocation loc) throws ParseError {
- return new IntersectionTypeExpr(loc, convertChildrenAsTypes(node, "types"));
- }
-
- private Node convertJsxAttribute(JsonObject node, SourceLocation loc) throws ParseError {
- return new JSXAttribute(loc, convertJSXName(convertChild(node, "name")), convertChild(node, "initializer"));
- }
-
- private Node convertJsxClosingElement(JsonObject node, SourceLocation loc) throws ParseError {
- return new JSXClosingElement(loc, convertJSXName(convertChild(node, "tagName")));
- }
-
- private Node convertJsxElement(JsonObject node, SourceLocation loc) throws ParseError {
- return new JSXElement(loc, convertChild(node, "openingElement"), convertChildren(node, "children"), convertChild(node, "closingElement"));
- }
-
- private Node convertJsxExpression(JsonObject node, SourceLocation loc) throws ParseError {
- if (hasChild(node, "expression"))
- return new JSXExpressionContainer(loc, convertChild(node, "expression"));
- return new JSXExpressionContainer(loc, new JSXEmptyExpression(loc));
- }
-
- private Node convertJsxFragment(JsonObject node, SourceLocation loc) throws ParseError {
- return new JSXElement(loc, convertChild(node, "openingFragment"), convertChildren(node, "children"),
- convertChild(node, "closingFragment"));
- }
-
- private Node convertJsxOpeningFragment(JsonObject node, SourceLocation loc) {
- return new JSXOpeningElement(loc, null, Collections.emptyList(), false);
- }
-
- private Node convertJsxClosingFragment(JsonObject node, SourceLocation loc) {
- return new JSXClosingElement(loc, null);
- }
-
- private List convertJsxAttributes(JsonObject node) throws ParseError {
- JsonElement attributes = node.get("attributes");
- List convertedAttributes;
- if (attributes.isJsonArray()) {
- convertedAttributes = convertNodes(attributes.getAsJsonArray());
- } else {
- convertedAttributes = convertChildren(attributes.getAsJsonObject(), "properties");
- }
- return convertedAttributes;
- }
-
- private Node convertJsxOpeningElement(JsonObject node, SourceLocation loc) throws ParseError {
- List convertedAttributes = convertJsxAttributes(node);
- return new JSXOpeningElement(loc, convertJSXName(convertChild(node, "tagName")), convertedAttributes, hasChild(node, "selfClosing"));
- }
-
- private Node convertJsxSelfClosingElement(JsonObject node, SourceLocation loc) throws ParseError {
- List convertedAttributes = convertJsxAttributes(node);
- JSXOpeningElement opening = new JSXOpeningElement(loc, convertJSXName(convertChild(node, "tagName")), convertedAttributes, true);
- return new JSXElement(loc, opening, new ArrayList<>(), null);
- }
-
- private Node convertJsxSpreadAttribute(JsonObject node,
- SourceLocation loc) throws ParseError {
- return new JSXSpreadAttribute(loc, convertChild(node, "expression"));
- }
-
- private Node convertJsxText(JsonObject node, SourceLocation loc) {
- String text;
- if (hasChild(node, "text"))
- text = node.get("text").getAsString();
- else
- text = "";
- return new Literal(loc, TokenType.string, text);
- }
-
- private Node convertLabeledStatement(JsonObject node, SourceLocation loc) throws ParseError {
- return new LabeledStatement(loc, convertChild(node, "label"), convertChild(node, "statement"));
- }
-
- private Node convertLiteralType(JsonObject node, SourceLocation loc) throws ParseError {
- return convertChild(node, "literal");
- }
-
- private Node convertMappedType(JsonObject node, SourceLocation loc) throws ParseError {
- return new MappedTypeExpr(loc, convertChild(node, "typeParameter"), convertChildAsType(node, "type"));
- }
-
- private Node convertMetaProperty(JsonObject node, SourceLocation loc) throws ParseError {
- Position metaStart = loc.getStart();
- Position metaEnd = new Position(metaStart.getLine(), metaStart.getColumn()+3, metaStart.getOffset()+3);
- SourceLocation metaLoc = new SourceLocation("new", metaStart, metaEnd);
- Identifier meta = new Identifier(metaLoc, "new");
- return new MetaProperty(loc, meta, convertChild(node, "name"));
- }
-
- private Node convertMethodDeclaration(JsonObject node, String kind,
- SourceLocation loc) throws ParseError {
- int flags = getMemberModifierKeywords(node);
- if (hasComputedName(node)) {
- flags |= DeclarationFlags.computed;
- }
- if (kind.equals("MethodSignature")) {
- flags |= DeclarationFlags.abstract_;
- }
- MethodDefinition.Kind methodKind;
- if ("GetAccessor".equals(kind))
- methodKind = Kind.GET;
- else if ("SetAccessor".equals(kind))
- methodKind = Kind.SET;
- else
- methodKind = Kind.METHOD;
- FunctionExpression method = convertImplicitFunction(node, loc);
- MethodDefinition methodDefinition = new MethodDefinition(loc, flags, methodKind, convertChild(node, "name"), method);
- if (node.has("decorators")) {
- methodDefinition.addDecorators(convertChildren(node, "decorators"));
- advanceUntilAfter(loc, methodDefinition.getDecorators());
- }
- return methodDefinition;
- }
-
- private FunctionExpression convertImplicitFunction(JsonObject node, SourceLocation loc) throws ParseError {
- ITypeExpression returnType = convertChildAsType(node, "type");
- List paramTypes = convertParameterTypes(node);
- List paramDecorators = convertParameterDecorators(node);
- List typeParameters = convertChildrenNotNull(node, "typeParameters");
- FunctionExpression method = new FunctionExpression(loc, null, convertParameters(node), convertChild(node, "body"),
- hasChild(node, "asteriskToken"), hasModifier(node, "AsyncKeyword"), typeParameters, paramTypes,
- paramDecorators, returnType, null);
- attachSymbolInformation(method, node);
- return method;
- }
-
- private Node convertNamespaceDeclaration(JsonObject node, SourceLocation loc) throws ParseError {
- Node nameNode = convertChild(node, "name");
- List body;
- Statement b = convertChild(node, "body");
- if (b instanceof BlockStatement) {
- body = ((BlockStatement) b).getBody();
- } else {
- body = new ArrayList<>();
- body.add(b);
- }
- if (nameNode instanceof Literal) {
- // Declaration of form: declare module "X" {...}
- return new ExternalModuleDeclaration(loc, (Literal) nameNode, body);
- }
- if (hasFlag(node, "GlobalAugmentation")) {
- // Declaration of form: declare global {...}
- return new GlobalAugmentationDeclaration(loc, body);
- }
- Identifier name = (Identifier) nameNode;
- boolean isInstantiated = false;
- for (Statement stmt : body) {
- isInstantiated = isInstantiated || isInstantiatingNamespaceMember(stmt);
- }
- boolean hasDeclareKeyword = hasModifier(node, "DeclareKeyword");
- NamespaceDeclaration decl = new NamespaceDeclaration(loc, name, body, isInstantiated, hasDeclareKeyword);
- attachSymbolInformation(decl, node);
- if (hasFlag(node, "NestedNamespace")) {
- // In a nested namespace declaration `namespace A.B`, the nested namespace `B`
- // is implicitly exported.
- return new ExportNamedDeclaration(loc, decl, new ArrayList<>(), null);
- } else {
- return fixExports(loc, decl);
- }
- }
-
- private boolean isInstantiatingNamespaceMember(Statement node) {
- if (node instanceof ExportNamedDeclaration) {
- // Ignore 'export' modifiers.
- return isInstantiatingNamespaceMember(((ExportNamedDeclaration) node).getDeclaration());
- }
- if (node instanceof NamespaceDeclaration) {
- return ((NamespaceDeclaration) node).isInstantiated();
- }
- if (node instanceof InterfaceDeclaration) {
- return false;
- }
- if (node instanceof TypeAliasDeclaration) {
- return false;
- }
- return true;
- }
-
- private Node convertModuleBlock(JsonObject node, SourceLocation loc) throws ParseError {
- return convertBlock(node, loc);
- }
-
- private Node convertNamespaceExportDeclaration(JsonObject node, SourceLocation loc) throws ParseError {
- return new ExportAsNamespaceDeclaration(loc, convertChild(node, "name"));
- }
-
- private Node convertNamespaceImport(JsonObject node, SourceLocation loc) throws ParseError {
- return new ImportNamespaceSpecifier(loc, convertChild(node, "name"));
- }
-
- private Node convertNewExpression(JsonObject node, SourceLocation loc) throws ParseError {
- List arguments;
- if (hasChild(node, "arguments"))
- arguments = convertChildren(node, "arguments");
- else
- arguments = new ArrayList<>();
- List typeArguments = convertChildrenAsTypes(node, "typeArguments");
- NewExpression result = new NewExpression(loc, convertChild(node, "expression"), typeArguments, arguments);
- attachResolvedSignature(result, node);
- return result;
- }
-
- private Node convertNonNullExpression(JsonObject node, SourceLocation loc) throws ParseError {
- return new NonNullAssertion(loc, convertChild(node, "expression"));
- }
-
- private Node convertNoSubstitutionTemplateLiteral(JsonObject node,
- SourceLocation loc) {
- List quasis = new ArrayList<>();
- TemplateElement elm = new TemplateElement(loc, node.get("text").getAsString(), loc.getSource().substring(1, loc.getSource().length()-1), true);
- quasis.add(elm);
- attachStaticType(elm, node);
- return new TemplateLiteral(loc, new ArrayList<>(), quasis);
- }
-
- private Node convertNullKeyword(SourceLocation loc) {
- return new Literal(loc, TokenType._null, null);
- }
-
- private Node convertObjectBindingPattern(JsonObject node,
- SourceLocation loc) throws ParseError {
- List properties = new ArrayList<>();
- for (JsonElement elt : node.get("elements").getAsJsonArray()) {
- JsonObject element = elt.getAsJsonObject();
- SourceLocation eltLoc = getSourceLocation(element);
- Expression propKey = hasChild(element, "propertyName") ? convertChild(element, "propertyName") : convertChild(element, "name");
- Expression propVal;
- if (hasChild(element, "dotDotDotToken")) {
- propVal = new RestElement(eltLoc, propKey);
- } else if (hasChild(element, "initializer")) {
- propVal = new AssignmentPattern(eltLoc, "=", convertChild(element, "name"), convertChild(element, "initializer"));
- } else {
- propVal = convertChild(element, "name");
- }
- properties.add(new Property(eltLoc, propKey, propVal, "init", hasComputedName(element, "propertyName"), false));
- }
- return new ObjectPattern(loc, properties);
- }
-
- private Node convertObjectLiteralExpression(JsonObject node,
- SourceLocation loc) throws ParseError {
- List properties;
- properties = new ArrayList();
- for (INode e : convertChildren(node, "properties")) {
- if (e instanceof SpreadElement) {
- properties.add(new Property(e.getLoc(), null, (Expression)e, Property.Kind.INIT.name(), false, false));
- } else if (e instanceof MethodDefinition) {
- MethodDefinition md = (MethodDefinition) e;
- Property.Kind kind = Property.Kind.INIT;
- if (md.getKind() == Kind.GET) {
- kind = Property.Kind.GET;
- } else if (md.getKind() == Kind.SET) {
- kind = Property.Kind.SET;
- }
- properties.add(new Property(e.getLoc(), md.getKey(), md.getValue(), kind.name(), md.isComputed(), true));
- } else {
- properties.add((Property)e);
- }
- }
- return new ObjectExpression(loc, properties);
- }
-
- private Node convertOmittedExpression() {
- return null;
- }
-
- private Node convertOptionalType(JsonObject node, SourceLocation loc) throws ParseError {
- return new OptionalTypeExpr(loc, convertChild(node, "type"));
- }
-
- private ITypeExpression asType(Node node) {
- return node instanceof ITypeExpression ? (ITypeExpression) node : null;
- }
-
- private List convertChildrenAsTypes(JsonObject node, String child) throws ParseError {
- List result = new ArrayList<>();
- JsonElement children = node.get(child);
- if (!(children instanceof JsonArray))
- return result;
- for (JsonElement childNode : children.getAsJsonArray()) {
- ITypeExpression type = asType(convertNode(childNode.getAsJsonObject()));
- if (type != null)
- result.add(type);
- }
- return result;
- }
-
- private ITypeExpression convertChildAsType(JsonObject node, String child) throws ParseError {
- return asType(convertChild(node, child));
- }
-
- /**
- * True if the given node is an Identifier node.
- */
- private boolean isIdentifier(JsonElement node) {
- if (node == null)
- return false;
- JsonObject object = node.getAsJsonObject();
- if (object == null)
- return false;
- String kind = getKind(object);
- return kind == null || kind.equals("Identifier");
- }
-
- /**
- * Returns true if this is the JSON object for the special "this" parameter.
- *
- * It should be given the JSON object of kind "Parameter".
- */
- private boolean isThisParameter(JsonElement parameter) {
- JsonObject name = parameter.getAsJsonObject().get("name").getAsJsonObject();
- return isIdentifier(name) && getIdentifierText(name).equals("this");
- }
-
- /**
- * Returns the parameters of the given function, omitting the special "this"
- * parameter, which we do not consider to be a proper parameter.
- */
- private Iterable getProperParameters(JsonObject function) {
- if (!function.has("parameters"))
- return Collections.emptyList();
- JsonArray parameters = function.get("parameters").getAsJsonArray();
- if (parameters.size() > 0 && isThisParameter(parameters.get(0))) {
- return CollectionUtil.skipIterable(parameters, 1);
- } else {
- return parameters;
- }
- }
-
- /**
- * Returns the special "this" parameter of the given function, or {@code null}
- * if the function does not declare a "this" parameter.
- */
- private ITypeExpression convertThisParameterType(JsonObject function) throws ParseError {
- if (!function.has("parameters"))
- return null;
- JsonArray parameters = function.get("parameters").getAsJsonArray();
- if (parameters.size() > 0 && isThisParameter(parameters.get(0))) {
- return convertChildAsType(parameters.get(0).getAsJsonObject(), "type");
- } else {
- return null;
- }
- }
-
- private List convertParameters(JsonObject function) throws ParseError {
- return convertNodes(getProperParameters(function), true);
- }
-
- private List convertParameterTypes(JsonObject function) throws ParseError {
- List result = new ArrayList<>();
- for (JsonElement param : getProperParameters(function)) {
- result.add(convertChildAsType(param.getAsJsonObject(), "type"));
- }
- return result;
- }
-
- private List convertParameterFields(JsonObject function) throws ParseError {
- List result = new ArrayList<>();
- int index = -1;
- for (JsonElement paramElm : getProperParameters(function)) {
- ++index;
- JsonObject param = paramElm.getAsJsonObject();
- int flags = getMemberModifierKeywords(param);
- if (flags == DeclarationFlags.none) {
- // If there are no flags, this is not a field parameter.
- continue;
- }
- // We generate a synthetic field node, but do not copy any of the AST nodes from
- // the parameter. The QL library overrides accessors to the name and type
- // annotation to return those from the corresponding parameter.
- SourceLocation loc = getSourceLocation(param);
- if (param.has("initializer")) {
- // Do not include the default parameter value in the source range for the field.
- SourceLocation endLoc;
- if (param.has("type")) {
- endLoc = getSourceLocation(param.get("type").getAsJsonObject());
- } else {
- endLoc = getSourceLocation(param.get("name").getAsJsonObject());
- }
- loc.setEnd(endLoc.getEnd());
- loc.setSource(source.substring(loc.getStart().getOffset(), loc.getEnd().getOffset()));
- }
- FieldDefinition field = new FieldDefinition(loc, flags, null, null, null, index);
- result.add(field);
- }
- return result;
- }
-
- private Node convertParameter(JsonObject node, SourceLocation loc) throws ParseError {
- // Note that type annotations are not extracted in this function, but in a
- // separate pass in convertParameterTypes above.
- Expression name = convertChild(node, "name", "Identifier");
- if (hasChild(node, "dotDotDotToken"))
- return new RestElement(loc, name);
- if (hasChild(node, "initializer"))
- return new AssignmentPattern(loc, "=", name, convertChild(node, "initializer"));
- return name;
- }
-
- private Node convertParenthesizedExpression(JsonObject node,
- SourceLocation loc) throws ParseError {
- return new ParenthesizedExpression(loc, convertChild(node, "expression"));
- }
-
- private Node convertParenthesizedType(JsonObject node, SourceLocation loc) throws ParseError {
- return new ParenthesizedTypeExpr(loc, convertChildAsType(node, "type"));
- }
-
- private Node convertPostfixUnaryExpression(JsonObject node,
- SourceLocation loc) throws ParseError {
- String operator = getOperator(node);
- return new UpdateExpression(loc, operator, convertChild(node, "operand"), false);
- }
-
- private Node convertPrefixUnaryExpression(JsonObject node,
- SourceLocation loc) throws ParseError {
- String operator = getOperator(node);
- if ("++".equals(operator) || "--".equals(operator))
- return new UpdateExpression(loc, operator, convertChild(node, "operand"), true);
- else
- return new UnaryExpression(loc, operator, convertChild(node, "operand"), true);
- }
-
- private String getOperator(JsonObject node) throws ParseError {
- int operatorId = node.get("operator").getAsInt();
- switch (syntaxKindMap.get(operatorId)) {
- case "PlusPlusToken":
- return "++";
- case "MinusMinusToken":
- return "--";
- case "PlusToken":
- return "+";
- case "MinusToken":
- return "-";
- case "TildeToken":
- return "~";
- case "ExclamationToken":
- return "!";
- default:
- throw new ParseError("Unsupported TypeScript operator " + operatorId, getSourceLocation(node).getStart());
- }
- }
-
- private Node convertPropertyAccessExpression(JsonObject node,
- SourceLocation loc) throws ParseError {
- return new MemberExpression(loc, convertChild(node, "expression"), convertChild(node, "name"), false, false, false);
- }
-
- private Node convertPropertyAssignment(JsonObject node,
- SourceLocation loc) throws ParseError {
- return new Property(loc, convertChild(node, "name"), convertChild(node, "initializer"), "init", hasComputedName(node), false);
- }
-
- private Node convertPropertyDeclaration(JsonObject node, String kind,
- SourceLocation loc) throws ParseError {
- int flags = getMemberModifierKeywords(node);
- if (hasComputedName(node)) {
- flags |= DeclarationFlags.computed;
- }
- if (kind.equals("PropertySignature")) {
- flags |= DeclarationFlags.abstract_;
- }
- if (node.get("questionToken") != null) {
- flags |= DeclarationFlags.optional;
- }
- if (node.get("exclamationToken") != null) {
- flags |= DeclarationFlags.definiteAssignmentAssertion;
- }
- FieldDefinition fieldDefinition = new FieldDefinition(loc, flags, convertChild(node, "name"),
- convertChild(node, "initializer"), convertChildAsType(node, "type"));
- if (node.has("decorators")) {
- fieldDefinition.addDecorators(convertChildren(node, "decorators"));
- advanceUntilAfter(loc, fieldDefinition.getDecorators());
- }
- return fieldDefinition;
- }
-
- private Node convertRegularExpressionLiteral(SourceLocation loc) {
- return new Literal(loc, TokenType.regexp, null);
- }
-
- private Node convertRestType(JsonObject node, SourceLocation loc) throws ParseError {
- return new RestTypeExpr(loc, convertChild(node, "type"));
- }
-
- private Node convertQualifiedName(JsonObject node, SourceLocation loc) throws ParseError {
- MemberExpression expr = new MemberExpression(loc, convertChild(node, "left"), convertChild(node, "right"), false, false, false);
- attachSymbolInformation(expr, node);
- return expr;
- }
-
- private Node convertReturnStatement(JsonObject node, SourceLocation loc) throws ParseError {
- return new ReturnStatement(loc, convertChild(node, "expression"));
- }
-
- private Node convertSemicolonClassElement() {
- return null;
- }
-
- private Node convertSourceFile(JsonObject node, SourceLocation loc) throws ParseError {
- List statements = convertNodes(node.get("statements").getAsJsonArray());
- Program program = new Program(loc, statements, "module");
- attachSymbolInformation(program, node);
- return program;
- }
-
- private Node convertShorthandPropertyAssignment(JsonObject node,
- SourceLocation loc) throws ParseError {
- return new Property(loc, convertChild(node, "name"), convertChild(node, "name"), "init", false, false);
- }
-
- private Node convertSpreadElement(JsonObject node, SourceLocation loc) throws ParseError {
- return new SpreadElement(loc, convertChild(node, "expression"));
- }
-
- private Node convertStringLiteral(JsonObject node, SourceLocation loc) {
- return new Literal(loc, TokenType.string, node.get("text").getAsString());
- }
-
- private Node convertSuperKeyword(SourceLocation loc) {
- return new Super(loc);
- }
-
- private Node convertSwitchStatement(JsonObject node, SourceLocation loc) throws ParseError {
- JsonObject caseBlock = node.get("caseBlock").getAsJsonObject();
- return new SwitchStatement(loc, convertChild(node, "expression"), convertChildren(caseBlock, "clauses"));
- }
-
- private Node convertTaggedTemplateExpression(JsonObject node,
- SourceLocation loc) throws ParseError {
- return new TaggedTemplateExpression(loc, convertChild(node, "tag"), convertChild(node, "template"));
- }
-
- private Node convertTemplateExpression(JsonObject node,
- SourceLocation loc) throws ParseError {
- List quasis;
- List expressions = new ArrayList<>();
- quasis = new ArrayList<>();
- quasis.add(convertChild(node, "head"));
- for (JsonElement elt : node.get("templateSpans").getAsJsonArray()) {
- JsonObject templateSpan = (JsonObject) elt;
- expressions.add(convertChild(templateSpan, "expression"));
- quasis.add(convertChild(templateSpan, "literal"));
- }
- return new TemplateLiteral(loc, expressions, quasis);
- }
-
- private Node convertTemplateElement(JsonObject node, String kind,
- SourceLocation loc) {
- boolean tail = "TemplateTail".equals(kind);
- if (loc.getSource().startsWith("`") || loc.getSource().startsWith("}")) {
- loc.setSource(loc.getSource().substring(1));
- Position start = loc.getStart();
- loc.setStart(new Position(start.getLine(), start.getColumn()+1, start.getColumn()+1));
- }
- if (loc.getSource().endsWith("${")) {
- loc.setSource(loc.getSource().substring(0, loc.getSource().length()-2));
- Position end = loc.getEnd();
- loc.setEnd(new Position(end.getLine(), end.getColumn()-2, end.getColumn()-2));
- }
- if (loc.getSource().endsWith("`")) {
- loc.setSource(loc.getSource().substring(0, loc.getSource().length()-1));
- Position end = loc.getEnd();
- loc.setEnd(new Position(end.getLine(), end.getColumn()-1, end.getColumn()-1));
- }
- return new TemplateElement(loc, node.get("text").getAsString(), loc.getSource(), tail);
- }
-
- private Node convertThisKeyword(SourceLocation loc) {
- return new ThisExpression(loc);
- }
-
- private Node convertThrowStatement(JsonObject node, SourceLocation loc) throws ParseError {
- Expression expr = convertChild(node, "expression");
- if (expr == null)
- return convertEmptyStatement(loc);
- return new ThrowStatement(loc, expr);
- }
-
- private Node convertTrueKeyword(SourceLocation loc) {
- return new Literal(loc, TokenType._true, true);
- }
-
- private Node convertTryStatement(JsonObject node, SourceLocation loc) throws ParseError {
- return new TryStatement(loc, convertChild(node, "tryBlock"), convertChild(node, "catchClause"), null,
- convertChild(node, "finallyBlock"));
- }
-
- private Node convertTupleType(JsonObject node, SourceLocation loc) throws ParseError {
- return new TupleTypeExpr(loc, convertChildrenAsTypes(node, "elementTypes"));
- }
-
- private Node convertTypeAliasDeclaration(JsonObject node, SourceLocation loc) throws ParseError {
- TypeAliasDeclaration typeAlias = new TypeAliasDeclaration(loc, convertChild(node, "name"),
- convertChildrenNotNull(node, "typeParameters"), convertChildAsType(node, "type"));
- attachSymbolInformation(typeAlias, node);
- return fixExports(loc, typeAlias);
- }
-
- private Node convertTypeAssertionExpression(JsonObject node, SourceLocation loc) throws ParseError {
- return new TypeAssertion(loc, convertChild(node, "expression"), convertChildAsType(node, "type"), false);
- }
-
- private Node convertTypeLiteral(JsonObject obj, SourceLocation loc) throws ParseError {
- return new InterfaceTypeExpr(loc, convertChildren(obj, "members"));
- }
-
- private Node convertTypeOfExpression(JsonObject node, SourceLocation loc) throws ParseError {
- return new UnaryExpression(loc, "typeof", convertChild(node, "expression"), true);
- }
-
- private Node convertTypeOperator(JsonObject node, SourceLocation loc) throws ParseError {
- String operator = syntaxKinds.get("" + node.get("operator").getAsInt()).getAsString();
- if (operator.equals("KeyOfKeyword")) {
- return new KeyofTypeExpr(loc, convertChildAsType(node, "type"));
- }
- if (operator.equals("UniqueKeyword")) {
- return new KeywordTypeExpr(loc, "unique symbol");
- }
- throw new ParseError("Unsupported TypeScript syntax", loc.getStart());
- }
-
- private Node convertTypeParameter(JsonObject node, SourceLocation loc) throws ParseError {
- return new TypeParameter(loc, convertChild(node, "name"), convertChildAsType(node, "constraint"),
- convertChildAsType(node, "default"));
- }
-
- private Node convertTypePredicate(JsonObject node, SourceLocation loc) throws ParseError {
- return new IsTypeExpr(loc, convertChildAsType(node, "parameterName"), convertChildAsType(node, "type"));
- }
-
- private Node convertTypeReference(JsonObject node, SourceLocation loc) throws ParseError {
- ITypeExpression typeName = convertChild(node, "typeName");
- List typeArguments = convertChildrenAsTypes(node, "typeArguments");
- if (typeArguments.isEmpty())
- return (Node) typeName;
- return new GenericTypeExpr(loc, typeName, typeArguments);
- }
-
- private Node convertTypeQuery(JsonObject node, SourceLocation loc) throws ParseError {
- return new TypeofTypeExpr(loc, convertChildAsType(node, "exprName"));
- }
-
- private Node convertVariableDeclaration(JsonObject node,
- SourceLocation loc) throws ParseError {
- return new VariableDeclarator(loc, convertChild(node, "name"), convertChild(node, "initializer"),
- convertChildAsType(node, "type"), DeclarationFlags.getDefiniteAssignmentAssertion(node.get("exclamationToken") != null));
- }
-
- private Node convertVariableDeclarationList(JsonObject node,
- SourceLocation loc) throws ParseError {
- return new VariableDeclaration(loc, getDeclarationKind(node), convertVariableDeclarations(node), false);
- }
-
- private List convertVariableDeclarations(JsonObject node) throws ParseError {
- if (node.get("declarations").getAsJsonArray().size() == 0)
- throw new ParseError("Unexpected token", getSourceLocation(node).getEnd());
- return convertChildren(node, "declarations");
- }
-
- private Node convertVariableStatement(JsonObject node, SourceLocation loc) throws ParseError {
- JsonObject declarationList = node.get("declarationList").getAsJsonObject();
- String declarationKind = getDeclarationKind(declarationList);
- List declarations = convertVariableDeclarations(declarationList);
- boolean hasDeclareKeyword = hasModifier(node, "DeclareKeyword");
- VariableDeclaration vd = new VariableDeclaration(loc, declarationKind, declarations, hasDeclareKeyword);
- return fixExports(loc, vd);
- }
-
- private Node convertVoidExpression(JsonObject node, SourceLocation loc) throws ParseError {
- return new UnaryExpression(loc, "void", convertChild(node, "expression"), true);
- }
-
- private Node convertWhileStatement(JsonObject node, SourceLocation loc) throws ParseError {
- return new WhileStatement(loc, convertChild(node, "expression"), convertChild(node, "statement"));
- }
-
- private Node convertWithStatement(JsonObject node, SourceLocation loc) throws ParseError {
- return new WithStatement(loc, convertChild(node, "expression"), convertChild(node, "statement"));
- }
-
- private Node convertYieldExpression(JsonObject node, SourceLocation loc) throws ParseError {
- return new YieldExpression(loc, convertChild(node, "expression"), hasChild(node, "asteriskToken"));
- }
-
- /**
- * Convert {@code e} to an lvalue expression, replacing {@link ArrayExpression} with
- * {@link ArrayPattern}, {@link AssignmentExpression} with {@link AssignmentPattern},
- * {@link ObjectExpression} with {@link ObjectPattern} and {@link SpreadElement} with
- * {@link RestElement}.
- */
- private Expression convertLValue(Expression e) {
- if (e == null)
- return null;
-
- SourceLocation loc = e.getLoc();
- if (e instanceof ArrayExpression) {
- List elts = new ArrayList();
- for (Expression elt : ((ArrayExpression) e).getElements())
- elts.add(convertLValue(elt));
- return new ArrayPattern(loc, elts);
- }
- if (e instanceof AssignmentExpression) {
- AssignmentExpression a = (AssignmentExpression)e;
- return new AssignmentPattern(loc, a.getOperator(), convertLValue(a.getLeft()), a.getRight());
- }
- if (e instanceof ObjectExpression) {
- List props = new ArrayList();
- for (Property prop : ((ObjectExpression) e).getProperties()) {
- Expression key = prop.getKey();
- Expression rawValue = prop.getRawValue();
- String kind = prop.getKind().name();
- boolean isComputed = prop.isComputed();
- boolean isMethod = prop.isMethod();
- props.add(new Property(prop.getLoc(), key, convertLValue(rawValue), kind, isComputed, isMethod));
- }
- return new ObjectPattern(loc, props);
- }
- if (e instanceof ParenthesizedExpression)
- return new ParenthesizedExpression(loc, convertLValue(((ParenthesizedExpression) e).getExpression()));
- if (e instanceof SpreadElement)
- return new RestElement(e.getLoc(), convertLValue(((SpreadElement) e).getArgument()));
- return e;
- }
-
- /**
- * Convert {@code e} to an {@link IJSXName}.
- */
- private IJSXName convertJSXName(Expression e) {
- if (e instanceof Identifier)
- return new JSXIdentifier(e.getLoc(), ((Identifier) e).getName());
- if (e instanceof MemberExpression) {
- MemberExpression me = (MemberExpression) e;
- return new JSXMemberExpression(e.getLoc(), convertJSXName(me.getObject()), (JSXIdentifier) convertJSXName(me.getProperty()));
- }
- if (e instanceof ThisExpression)
- return new JSXIdentifier(e.getLoc(), "this");
- return (IJSXName) e;
- }
-
- /**
- * Check whether {@code decl} has an {@code export} annotation, and if so wrap
- * it inside an {@link ExportDeclaration}.
- *
- * If the declared statement has decorators, the {@code loc} should first be
- * advanced past these using {@link #advanceUntilAfter}.
- */
- private Node fixExports(SourceLocation loc, Statement decl) {
- Matcher m = EXPORT_DECL_START.matcher(loc.getSource());
- if (m.find()) {
- String skipped = m.group(0);
- SourceLocation outerLoc = new SourceLocation(loc.getSource(), loc.getStart(), loc.getEnd());
- advance(loc, skipped);
- // capture group 1 is `default`, if present
- if (m.group(1) == null)
- return new ExportNamedDeclaration(outerLoc, decl, new ArrayList<>(), null);
- return new ExportDefaultDeclaration(outerLoc, decl);
- }
- return decl;
- }
-
- /**
- * Holds if the {@code name} property of the given AST node is a computed
- * property name.
- */
- private boolean hasComputedName(JsonObject node) {
- return hasComputedName(node, "name");
- }
-
- /**
- * Holds if the given property of the given AST node is a computed property name.
- */
- private boolean hasComputedName(JsonObject node, String propName) {
- return hasKind(node.get(propName), "ComputedPropertyName");
- }
-
- /**
- * Update the start position and source text of {@code loc} by skipping over the string
- * {@code skipped}.
- */
- private void advance(SourceLocation loc, String skipped) {
- loc.setStart(advance(loc.getStart(), skipped));
- loc.setSource(loc.getSource().substring(skipped.length()));
- }
-
- /**
- * Update the start position of @{code loc} by skipping over the given children
- * and any following whitespace and comments, provided they are contained in the
- * source location.
- */
- private void advanceUntilAfter(SourceLocation loc, List extends INode> nodes) {
- if (nodes.isEmpty())
- return;
- INode last = nodes.get(nodes.size() - 1);
- int offset = last.getLoc().getEnd().getOffset() - loc.getStart().getOffset();
- if (offset <= 0)
- return;
- offset += matchWhitespace(last.getLoc().getEnd().getOffset()).length();
- if (offset >= loc.getSource().length())
- return;
- loc.setStart(advance(loc.getStart(), loc.getSource().substring(0, offset)));
- loc.setSource(loc.getSource().substring(offset));
- }
-
- /**
- * Get the longest sequence of whitespace or comment characters starting at the
- * given offset.
- */
- private String matchWhitespace(int offset) {
- Matcher m = WHITESPACE.matcher(source.substring(offset));
- m.find();
- return m.group(0);
- }
-
- /**
- * Create a position corresponding to {@code pos}, but updated by skipping over the
- * string {@code skipped}.
- */
- private Position advance(Position pos, String skipped) {
- int innerStartOffset = pos.getOffset() + skipped.length();
- int innerStartLine = pos.getLine(), innerStartColumn = pos.getColumn();
- Matcher m = LINE_TERMINATOR.matcher(skipped);
- int lastEnd = 0;
- while (m.find()) {
- ++innerStartLine;
- innerStartColumn = 1;
- lastEnd = m.end();
- }
- innerStartColumn += skipped.length() - lastEnd;
- if (lastEnd > 0)
- --innerStartColumn;
- Position innerStart = new Position(innerStartLine, innerStartColumn, innerStartOffset);
- return innerStart;
- }
-
- /**
- * Get the source location of the given AST node.
- */
- private SourceLocation getSourceLocation(JsonObject node) {
- Position start = getPosition(node.get("$pos"));
- Position end = getPosition(node.get("$end"));
- int startOffset = start.getOffset();
- int endOffset = end.getOffset();
- if (startOffset > endOffset)
- startOffset = endOffset;
- if (endOffset > source.length())
- endOffset = source.length();
- return new SourceLocation(source.substring(startOffset, endOffset), start, end);
- }
-
- /**
- * Convert the given position object into a {@link Position}.
- * For start positions, we need to skip over whitespace, which is included in
- * the positions reported by the TypeScript compiler.
- */
- private Position getPosition(JsonElement elm) {
- int offset = elm.getAsInt();
- int line = getLineFromPos(offset);
- int column = getColumnFromLinePos(line, offset);
- return new Position(line + 1, column, offset);
- }
-
- private Iterable getModifiers(JsonObject node) {
- JsonElement mods = node.get("modifiers");
- if (!(mods instanceof JsonArray))
- return Collections.emptyList();
- return (JsonArray) mods;
- }
-
- /**
- * Returns a specific modifier from the given node (or null if absent),
- * as defined by its modifiers property and the kind property
- * of the modifier AST node.
- */
- private JsonObject getModifier(JsonObject node, String modKind) {
- for (JsonElement mod : getModifiers(node))
- if (mod instanceof JsonObject)
- if (hasKind((JsonObject) mod, modKind))
- return (JsonObject) mod;
- return null;
- }
-
- /**
- * Check whether a node has a particular modifier, as defined by its
- * modifiers property and the kind property of the modifier
- * AST node.
- */
- private boolean hasModifier(JsonObject node, String modKind) {
- return getModifier(node, modKind) != null;
- }
-
- private int getDeclarationModifierFromKeyword(String kind) {
- switch (kind) {
- case "AbstractKeyword":
- return DeclarationFlags.abstract_;
- case "StaticKeyword":
- return DeclarationFlags.static_;
- case "ReadonlyKeyword":
- return DeclarationFlags.readonly;
- case "PublicKeyword":
- return DeclarationFlags.public_;
- case "PrivateKeyword":
- return DeclarationFlags.private_;
- case "ProtectedKeyword":
- return DeclarationFlags.protected_;
- default:
- return DeclarationFlags.none;
- }
- }
-
- /**
- * Returns the set of member flags corresponding to the modifier keywords
- * present on the given node.
- */
- private int getMemberModifierKeywords(JsonObject node) {
- int flags = DeclarationFlags.none;
- for (JsonElement mod : getModifiers(node)) {
- if (mod instanceof JsonObject) {
- JsonObject modObject = (JsonObject) mod;
- flags |= getDeclarationModifierFromKeyword(getKind(modObject));
- }
- }
- return flags;
- }
-
- /**
- * Check whether a node has a particular flag, as defined by its flags
- * property and the ts.NodeFlags in enum.
- */
- private boolean hasFlag(JsonObject node, String flagName) {
- JsonElement flagDescriptor = this.nodeFlags.get(flagName);
- if (flagDescriptor == null) {
- throw new RuntimeException("Incompatible version of TypeScript installed. Missing node flag " + flagName);
- }
- int flagId = flagDescriptor.getAsInt();
- JsonElement flags = node.get("flags");
- if (flags instanceof JsonPrimitive) {
- return (flags.getAsInt() & flagId) != 0;
- }
- return false;
- }
-
- /**
- * Gets the numeric value of the syntax kind enum with the given name.
- */
- private int getSyntaxKind(String syntaxKind) {
- JsonElement descriptor = this.syntaxKinds.get(syntaxKind);
- if (descriptor == null) {
- throw new RuntimeException("Incompatible version of TypeScript installed. Missing syntax kind " + syntaxKind);
- }
- return descriptor.getAsInt();
- }
-
- /**
- * Check whether a node has a child with a given name.
- */
- private boolean hasChild(JsonObject node, String prop) {
- if (!node.has(prop))
- return false;
- return !(node.get(prop) instanceof JsonNull);
- }
-
- /**
- * Returns an iterator over the elements of the given child array, or an empty
- * iterator if the given child is not an array.
- */
- private Iterable getChildIterable(JsonObject node, String child) {
- JsonElement elt = node.get(child);
- if (!(elt instanceof JsonArray))
- return Collections.emptyList();
- return (JsonArray) elt;
- }
-
- /**
- * Gets the kind of the given node.
- */
- private String getKind(JsonElement node) {
- if (node instanceof JsonObject) {
- JsonElement kind = ((JsonObject) node).get("kind");
- if (kind instanceof JsonPrimitive && ((JsonPrimitive) kind).isNumber())
- return syntaxKindMap.get(kind.getAsInt());
- }
- return null;
- }
-
- /**
- * Holds if the given node has the given kind.
- */
- private boolean hasKind(JsonElement node, String kind) {
- return kind.equals(getKind(node));
- }
-
- /**
- * Gets the declaration kind of the given node, which is one of {@code "var"},
- * {@code "let"} or {@code "const"}.
- */
- private String getDeclarationKind(JsonObject declarationList) {
- return declarationList.get("$declarationKind").getAsString();
- }
+ private String source;
+ private final JsonObject nodeFlags;
+ private final JsonObject syntaxKinds;
+ private final Map nodeFlagMap = new LinkedHashMap<>();
+ private final Map syntaxKindMap = new LinkedHashMap<>();
+ private int[] lineStarts;
+
+ private int syntaxKindExtends;
+
+ private static final Pattern LINE_TERMINATOR = Pattern.compile("\\n|\\r\\n|\\r|\\u2028|\\u2029");
+ private static final String WHITESPACE_CHAR = "(?:\\s|//.*|/\\*(?:[^*]|\\*(?!/))*\\*/)";
+ private static final Pattern WHITESPACE = Pattern.compile("^" + WHITESPACE_CHAR + "*");
+ private static final Pattern EXPORT_DECL_START =
+ Pattern.compile("^export" + "(" + WHITESPACE_CHAR + "+default)?" + WHITESPACE_CHAR + "+");
+ private static final Pattern TYPEOF_START = Pattern.compile("^typeof" + WHITESPACE_CHAR + "+");
+ private static final Pattern WHITESPACE_END_PAREN =
+ Pattern.compile("^" + WHITESPACE_CHAR + "*\\)");
+
+ TypeScriptASTConverter(JsonObject nodeFlags, JsonObject syntaxKinds) {
+ this.nodeFlags = nodeFlags;
+ this.syntaxKinds = syntaxKinds;
+ makeEnumIdMap(nodeFlags, nodeFlagMap);
+ makeEnumIdMap(syntaxKinds, syntaxKindMap);
+ this.syntaxKindExtends = getSyntaxKind("ExtendsKeyword");
+ }
+
+ /** Builds a mapping from ID to name given a TypeScript enum object. */
+ private void makeEnumIdMap(JsonObject enumObject, Map idToName) {
+ for (Map.Entry entry : enumObject.entrySet()) {
+ JsonPrimitive prim = entry.getValue().getAsJsonPrimitive();
+ if (prim.isNumber() && !idToName.containsKey(prim.getAsInt())) {
+ idToName.put(prim.getAsInt(), entry.getKey());
+ }
+ }
+ }
+
+ /**
+ * Convert the given TypeScript AST (which was parsed from {@code source}) into a parser {@link
+ * Result}.
+ */
+ public Result convertAST(JsonObject ast, String source) {
+ this.lineStarts = toIntArray(ast.getAsJsonArray("$lineStarts"));
+
+ List errors = new ArrayList();
+
+ // process parse diagnostics (i.e., syntax errors) reported by the TypeScript compiler
+ JsonArray parseDiagnostics = ast.get("parseDiagnostics").getAsJsonArray();
+ if (parseDiagnostics.size() > 0) {
+ for (JsonElement elt : parseDiagnostics) {
+ JsonObject parseDiagnostic = elt.getAsJsonObject();
+ String message = parseDiagnostic.get("messageText").getAsString();
+ Position pos = getPosition(parseDiagnostic.get("$pos"));
+ errors.add(new ParseError(message, pos.getLine(), pos.getColumn(), pos.getOffset()));
+ }
+ return new Result(source, null, new ArrayList<>(), new ArrayList<>(), errors);
+ }
+
+ this.source = source;
+
+ List tokens = new ArrayList<>();
+ List comments = new ArrayList<>();
+ extractTokensAndComments(ast, tokens, comments);
+ Node converted;
+ try {
+ converted = convertNode(ast);
+ } catch (ParseError e) {
+ converted = null;
+ errors.add(e);
+ }
+ return new Result(source, converted, tokens, comments, errors);
+ }
+
+ /** Converts a JSON array to an int array. The array is assumed to only contain integers. */
+ private static int[] toIntArray(JsonArray array) {
+ int[] result = new int[array.size()];
+ for (int i = 0; i < array.size(); ++i) {
+ result[i] = array.get(i).getAsInt();
+ }
+ return result;
+ }
+
+ private int getLineFromPos(int pos) {
+ int low = 0, high = this.lineStarts.length - 1;
+ while (low < high) {
+ int mid = high - ((high - low) >> 1); // Get middle, rounding up.
+ int startOfLine = lineStarts[mid];
+ if (startOfLine <= pos) {
+ low = mid;
+ } else {
+ high = mid - 1;
+ }
+ }
+ return low;
+ }
+
+ private int getColumnFromLinePos(int line, int pos) {
+ return pos - lineStarts[line];
+ }
+
+ /** Extract tokens and comments from the given TypeScript AST. */
+ private void extractTokensAndComments(
+ JsonObject ast, List tokens, List comments) {
+ for (JsonElement elt : ast.get("$tokens").getAsJsonArray()) {
+ JsonObject token = elt.getAsJsonObject();
+ String text = token.get("text").getAsString();
+ Position start = getPosition(token.get("tokenPos"));
+ Position end = advance(start, text);
+ SourceLocation loc = new SourceLocation(text, start, end);
+ String kind = getKind(token);
+ switch (kind) {
+ case "EndOfFileToken":
+ tokens.add(new Token(loc, Token.Type.EOF));
+ break;
+ case "SingleLineCommentTrivia":
+ case "MultiLineCommentTrivia":
+ String cookedText;
+ if (text.startsWith("//")) cookedText = text.substring(2);
+ else cookedText = text.substring(2, text.length() - 2);
+ comments.add(new Comment(loc, cookedText));
+ break;
+ case "TemplateHead":
+ case "TemplateMiddle":
+ case "TemplateTail":
+ case "NoSubstitutionTemplateLiteral":
+ tokens.add(new Token(loc, Token.Type.STRING));
+ break;
+ case "Identifier":
+ tokens.add(new Token(loc, Token.Type.NAME));
+ break;
+ case "NumericLiteral":
+ tokens.add(new Token(loc, Token.Type.NUM));
+ break;
+ case "StringLiteral":
+ tokens.add(new Token(loc, Token.Type.STRING));
+ break;
+ case "RegularExpressionLiteral":
+ tokens.add(new Token(loc, Token.Type.REGEXP));
+ break;
+ default:
+ Token.Type tp;
+ if (kind.endsWith("Token")) {
+ tp = Token.Type.PUNCTUATOR;
+ } else if (kind.endsWith("Keyword")) {
+ if (text.equals("null")) tp = Token.Type.NULL;
+ else if (text.equals("true")) tp = Token.Type.TRUE;
+ else if (text.equals("false")) tp = Token.Type.FALSE;
+ else tp = Token.Type.KEYWORD;
+ } else {
+ continue;
+ }
+ tokens.add(new Token(loc, tp));
+ }
+ }
+ }
+
+ /** Convert the given TypeScript node and its children into a JavaScript {@link Node}. */
+ private Node convertNode(JsonObject node) throws ParseError {
+ return convertNode(node, null);
+ }
+
+ /**
+ * Convert the given TypeScript node and its children into a JavaScript {@link Node}. If the
+ * TypesScript node has no explicit {@code kind}, it is assumed to be {@code defaultKind}.
+ */
+ private Node convertNode(JsonObject node, String defaultKind) throws ParseError {
+ Node astNode = convertNodeUntyped(node, defaultKind);
+ attachStaticType(astNode, node);
+ return astNode;
+ }
+
+ /** Helper method for `convertNode` that does everything except attaching type information. */
+ private Node convertNodeUntyped(JsonObject node, String defaultKind) throws ParseError {
+ String kind = getKind(node);
+ if (kind == null) kind = defaultKind;
+ if (kind == null) kind = "Identifier";
+ SourceLocation loc = getSourceLocation(node);
+ switch (kind) {
+ case "AnyKeyword":
+ return convertKeywordTypeExpr(node, loc, "any");
+ case "ArrayBindingPattern":
+ return convertArrayBindingPattern(node, loc);
+ case "ArrayLiteralExpression":
+ return convertArrayLiteralExpression(node, loc);
+ case "ArrayType":
+ return convertArrayType(node, loc);
+ case "ArrowFunction":
+ return convertArrowFunction(node, loc);
+ case "AsExpression":
+ return convertAsExpression(node, loc);
+ case "AwaitExpression":
+ return convertAwaitExpression(node, loc);
+ case "BigIntKeyword":
+ return convertKeywordTypeExpr(node, loc, "bigint");
+ case "BigIntLiteral":
+ return convertBigIntLiteral(node, loc);
+ case "BinaryExpression":
+ return convertBinaryExpression(node, loc);
+ case "Block":
+ return convertBlock(node, loc);
+ case "BooleanKeyword":
+ return convertKeywordTypeExpr(node, loc, "boolean");
+ case "BreakStatement":
+ return convertBreakStatement(node, loc);
+ case "CallExpression":
+ return convertCallExpression(node, loc);
+ case "CallSignature":
+ return convertCallSignature(node, loc);
+ case "CaseClause":
+ return convertCaseClause(node, loc);
+ case "CatchClause":
+ return convertCatchClause(node, loc);
+ case "ClassDeclaration":
+ case "ClassExpression":
+ return convertClass(node, kind, loc);
+ case "CommaListExpression":
+ return convertCommaListExpression(node, loc);
+ case "ComputedPropertyName":
+ return convertComputedPropertyName(node);
+ case "ConditionalExpression":
+ return convertConditionalExpression(node, loc);
+ case "ConditionalType":
+ return convertConditionalType(node, loc);
+ case "Constructor":
+ return convertConstructor(node, loc);
+ case "ConstructSignature":
+ return convertConstructSignature(node, loc);
+ case "ConstructorType":
+ return convertConstructorType(node, loc);
+ case "ContinueStatement":
+ return convertContinueStatement(node, loc);
+ case "DebuggerStatement":
+ return convertDebuggerStatement(loc);
+ case "Decorator":
+ return convertDecorator(node, loc);
+ case "DefaultClause":
+ return convertCaseClause(node, loc);
+ case "DeleteExpression":
+ return convertDeleteExpression(node, loc);
+ case "DoStatement":
+ return convertDoStatement(node, loc);
+ case "ElementAccessExpression":
+ return convertElementAccessExpression(node, loc);
+ case "EmptyStatement":
+ return convertEmptyStatement(loc);
+ case "EnumDeclaration":
+ return convertEnumDeclaration(node, loc);
+ case "EnumMember":
+ return convertEnumMember(node, loc);
+ case "ExportAssignment":
+ return convertExportAssignment(node, loc);
+ case "ExportDeclaration":
+ return convertExportDeclaration(node, loc);
+ case "ExportSpecifier":
+ return convertExportSpecifier(node, loc);
+ case "ExpressionStatement":
+ return convertExpressionStatement(node, loc);
+ case "ExpressionWithTypeArguments":
+ return convertExpressionWithTypeArguments(node, loc);
+ case "ExternalModuleReference":
+ return convertExternalModuleReference(node, loc);
+ case "FalseKeyword":
+ return convertFalseKeyword(loc);
+ case "NeverKeyword":
+ return convertKeywordTypeExpr(node, loc, "never");
+ case "NumberKeyword":
+ return convertKeywordTypeExpr(node, loc, "number");
+ case "NumericLiteral":
+ return convertNumericLiteral(node, loc);
+ case "ForStatement":
+ return convertForStatement(node, loc);
+ case "ForInStatement":
+ return convertForInStatement(node, loc);
+ case "ForOfStatement":
+ return convertForOfStatement(node, loc);
+ case "FunctionDeclaration":
+ return convertFunctionDeclaration(node, loc);
+ case "FunctionExpression":
+ return convertFunctionExpression(node, loc);
+ case "FunctionType":
+ return convertFunctionType(node, loc);
+ case "Identifier":
+ return convertIdentifier(node, loc);
+ case "IfStatement":
+ return convertIfStatement(node, loc);
+ case "ImportClause":
+ return convertImportClause(node, loc);
+ case "ImportDeclaration":
+ return convertImportDeclaration(node, loc);
+ case "ImportEqualsDeclaration":
+ return convertImportEqualsDeclaration(node, loc);
+ case "ImportKeyword":
+ return convertImportKeyword(loc);
+ case "ImportSpecifier":
+ return convertImportSpecifier(node, loc);
+ case "ImportType":
+ return convertImportType(node, loc);
+ case "IndexSignature":
+ return convertIndexSignature(node, loc);
+ case "IndexedAccessType":
+ return convertIndexedAccessType(node, loc);
+ case "InferType":
+ return convertInferType(node, loc);
+ case "InterfaceDeclaration":
+ return convertInterfaceDeclaration(node, loc);
+ case "IntersectionType":
+ return convertIntersectionType(node, loc);
+ case "JsxAttribute":
+ return convertJsxAttribute(node, loc);
+ case "JsxClosingElement":
+ return convertJsxClosingElement(node, loc);
+ case "JsxElement":
+ return convertJsxElement(node, loc);
+ case "JsxExpression":
+ return convertJsxExpression(node, loc);
+ case "JsxFragment":
+ return convertJsxFragment(node, loc);
+ case "JsxOpeningElement":
+ return convertJsxOpeningElement(node, loc);
+ case "JsxOpeningFragment":
+ return convertJsxOpeningFragment(node, loc);
+ case "JsxSelfClosingElement":
+ return convertJsxSelfClosingElement(node, loc);
+ case "JsxClosingFragment":
+ return convertJsxClosingFragment(node, loc);
+ case "JsxSpreadAttribute":
+ return convertJsxSpreadAttribute(node, loc);
+ case "JsxText":
+ case "JsxTextAllWhiteSpaces":
+ return convertJsxText(node, loc);
+ case "LabeledStatement":
+ return convertLabeledStatement(node, loc);
+ case "LiteralType":
+ return convertLiteralType(node, loc);
+ case "MappedType":
+ return convertMappedType(node, loc);
+ case "MetaProperty":
+ return convertMetaProperty(node, loc);
+ case "GetAccessor":
+ case "SetAccessor":
+ case "MethodDeclaration":
+ case "MethodSignature":
+ return convertMethodDeclaration(node, kind, loc);
+ case "ModuleDeclaration":
+ case "NamespaceDeclaration":
+ return convertNamespaceDeclaration(node, loc);
+ case "ModuleBlock":
+ return convertModuleBlock(node, loc);
+ case "NamespaceExportDeclaration":
+ return convertNamespaceExportDeclaration(node, loc);
+ case "NamespaceImport":
+ return convertNamespaceImport(node, loc);
+ case "NewExpression":
+ return convertNewExpression(node, loc);
+ case "NonNullExpression":
+ return convertNonNullExpression(node, loc);
+ case "NoSubstitutionTemplateLiteral":
+ return convertNoSubstitutionTemplateLiteral(node, loc);
+ case "NullKeyword":
+ return convertNullKeyword(loc);
+ case "ObjectBindingPattern":
+ return convertObjectBindingPattern(node, loc);
+ case "ObjectKeyword":
+ return convertKeywordTypeExpr(node, loc, "object");
+ case "ObjectLiteralExpression":
+ return convertObjectLiteralExpression(node, loc);
+ case "OmittedExpression":
+ return convertOmittedExpression();
+ case "OptionalType":
+ return convertOptionalType(node, loc);
+ case "Parameter":
+ return convertParameter(node, loc);
+ case "ParenthesizedExpression":
+ return convertParenthesizedExpression(node, loc);
+ case "ParenthesizedType":
+ return convertParenthesizedType(node, loc);
+ case "PostfixUnaryExpression":
+ return convertPostfixUnaryExpression(node, loc);
+ case "PrefixUnaryExpression":
+ return convertPrefixUnaryExpression(node, loc);
+ case "PropertyAccessExpression":
+ return convertPropertyAccessExpression(node, loc);
+ case "PropertyAssignment":
+ return convertPropertyAssignment(node, loc);
+ case "PropertyDeclaration":
+ case "PropertySignature":
+ return convertPropertyDeclaration(node, kind, loc);
+ case "RegularExpressionLiteral":
+ return convertRegularExpressionLiteral(loc);
+ case "RestType":
+ return convertRestType(node, loc);
+ case "QualifiedName":
+ return convertQualifiedName(node, loc);
+ case "ReturnStatement":
+ return convertReturnStatement(node, loc);
+ case "SemicolonClassElement":
+ return convertSemicolonClassElement();
+ case "SourceFile":
+ return convertSourceFile(node, loc);
+ case "ShorthandPropertyAssignment":
+ return convertShorthandPropertyAssignment(node, loc);
+ case "SpreadAssignment":
+ case "SpreadElement":
+ case "SpreadElementExpression":
+ return convertSpreadElement(node, loc);
+ case "StringKeyword":
+ return convertKeywordTypeExpr(node, loc, "string");
+ case "StringLiteral":
+ return convertStringLiteral(node, loc);
+ case "SuperKeyword":
+ return convertSuperKeyword(loc);
+ case "SwitchStatement":
+ return convertSwitchStatement(node, loc);
+ case "SymbolKeyword":
+ return convertKeywordTypeExpr(node, loc, "symbol");
+ case "TaggedTemplateExpression":
+ return convertTaggedTemplateExpression(node, loc);
+ case "TemplateExpression":
+ return convertTemplateExpression(node, loc);
+ case "TemplateHead":
+ case "TemplateMiddle":
+ case "TemplateTail":
+ return convertTemplateElement(node, kind, loc);
+ case "ThisKeyword":
+ return convertThisKeyword(loc);
+ case "ThisType":
+ return convertKeywordTypeExpr(node, loc, "this");
+ case "ThrowStatement":
+ return convertThrowStatement(node, loc);
+ case "TrueKeyword":
+ return convertTrueKeyword(loc);
+ case "TryStatement":
+ return convertTryStatement(node, loc);
+ case "TupleType":
+ return convertTupleType(node, loc);
+ case "TypeAliasDeclaration":
+ return convertTypeAliasDeclaration(node, loc);
+ case "TypeAssertionExpression":
+ return convertTypeAssertionExpression(node, loc);
+ case "TypeLiteral":
+ return convertTypeLiteral(node, loc);
+ case "TypeOfExpression":
+ return convertTypeOfExpression(node, loc);
+ case "TypeOperator":
+ return convertTypeOperator(node, loc);
+ case "TypeParameter":
+ return convertTypeParameter(node, loc);
+ case "TypePredicate":
+ return convertTypePredicate(node, loc);
+ case "TypeReference":
+ return convertTypeReference(node, loc);
+ case "TypeQuery":
+ return convertTypeQuery(node, loc);
+ case "UndefinedKeyword":
+ return convertKeywordTypeExpr(node, loc, "undefined");
+ case "UnionType":
+ return convertUnionType(node, loc);
+ case "UnknownKeyword":
+ return convertKeywordTypeExpr(node, loc, "unknown");
+ case "VariableDeclaration":
+ return convertVariableDeclaration(node, loc);
+ case "VariableDeclarationList":
+ return convertVariableDeclarationList(node, loc);
+ case "VariableStatement":
+ return convertVariableStatement(node, loc);
+ case "VoidExpression":
+ return convertVoidExpression(node, loc);
+ case "VoidKeyword":
+ return convertKeywordTypeExpr(node, loc, "void");
+ case "WhileStatement":
+ return convertWhileStatement(node, loc);
+ case "WithStatement":
+ return convertWithStatement(node, loc);
+ case "YieldExpression":
+ return convertYieldExpression(node, loc);
+ default:
+ throw new ParseError(
+ "Unsupported TypeScript syntax " + kind, getSourceLocation(node).getStart());
+ }
+ }
+
+ /**
+ * Attaches type information from the JSON object to the given AST node, if applicable. This is
+ * called from {@link #convertNode}.
+ */
+ private void attachStaticType(Node astNode, JsonObject json) {
+ if (astNode instanceof ITypedAstNode && json.has("$type")) {
+ ITypedAstNode typedAstNode = (ITypedAstNode) astNode;
+ int typeId = json.get("$type").getAsInt();
+ typedAstNode.setStaticTypeId(typeId);
+ }
+ }
+
+ /** Attaches a TypeScript compiler symbol to the given node, if any was provided. */
+ private void attachSymbolInformation(INodeWithSymbol node, JsonObject json) {
+ if (json.has("$symbol")) {
+ int symbol = json.get("$symbol").getAsInt();
+ node.setSymbol(symbol);
+ }
+ }
+
+ /** Attaches call signatures and related symbol information to a call site. */
+ private void attachResolvedSignature(InvokeExpression node, JsonObject json) {
+ if (json.has("$resolvedSignature")) {
+ int id = json.get("$resolvedSignature").getAsInt();
+ node.setResolvedSignatureId(id);
+ }
+ if (json.has("$overloadIndex")) {
+ int id = json.get("$overloadIndex").getAsInt();
+ node.setOverloadIndex(id);
+ }
+ attachSymbolInformation(node, json);
+ }
+
+ /**
+ * Convert the given array of TypeScript AST nodes into a list of JavaScript AST nodes, skipping
+ * any {@code null} elements.
+ */
+ private List convertNodes(Iterable nodes) throws ParseError {
+ return convertNodes(nodes, true);
+ }
+
+ /**
+ * Convert the given array of TypeScript AST nodes into a list of JavaScript AST nodes, where
+ * {@code skipNull} indicates whether {@code null} elements should be skipped or not.
+ */
+ @SuppressWarnings("unchecked")
+ private List convertNodes(Iterable nodes, boolean skipNull)
+ throws ParseError {
+ List res = new ArrayList();
+ for (JsonElement elt : nodes) {
+ T converted = (T) convertNode(elt.getAsJsonObject());
+ if (!skipNull || converted != null) res.add(converted);
+ }
+ return res;
+ }
+
+ /**
+ * Converts the given child to an AST node of the given type or