JS(extractor): support optional chaining

This commit is contained in:
Esben Sparre Andreasen
2018-11-20 14:03:04 +01:00
parent 165bb8b6b8
commit 00587ba7b4
37 changed files with 4908 additions and 31 deletions

View File

@@ -24,3 +24,5 @@
* The TypeScript compiler is now bundled with the distribution, and no longer needs to be installed manually. * The TypeScript compiler is now bundled with the distribution, and no longer needs to be installed manually.
Should the compiler version need to be overridden, set the `SEMMLE_TYPESCRIPT_HOME` environment variable to Should the compiler version need to be overridden, set the `SEMMLE_TYPESCRIPT_HOME` environment variable to
point to an installation of the `typescript` NPM package. point to an installation of the `typescript` NPM package.
* The extractor now supports [Optional Chaining](https://github.com/tc39/proposal-optional-chaining) expressions.

View File

@@ -153,7 +153,7 @@ public class CustomParser extends FlowParser {
Identifier name = this.parseIdent(true); Identifier name = this.parseIdent(true);
this.expect(TokenType.parenL); this.expect(TokenType.parenL);
List<Expression> args = this.parseExprList(TokenType.parenR, false, false, null); List<Expression> args = this.parseExprList(TokenType.parenR, false, false, null);
CallExpression node = new CallExpression(new SourceLocation(startLoc), name, new ArrayList<>(), args); CallExpression node = new CallExpression(new SourceLocation(startLoc), name, new ArrayList<>(), args, false, false);
return this.finishNode(node); return this.finishNode(node);
} else { } else {
return super.parseExprAtom(refDestructuringErrors); return super.parseExprAtom(refDestructuringErrors);
@@ -212,7 +212,7 @@ public class CustomParser extends FlowParser {
* A.f = function f(...) { ... }; * A.f = function f(...) { ... };
*/ */
SourceLocation memloc = new SourceLocation(iface.getName() + "::" + id.getName(), iface.getLoc().getStart(), id.getLoc().getEnd()); SourceLocation memloc = new SourceLocation(iface.getName() + "::" + id.getName(), iface.getLoc().getStart(), id.getLoc().getEnd());
MemberExpression mem = new MemberExpression(memloc, iface, new Identifier(id.getLoc(), id.getName()), false); MemberExpression mem = new MemberExpression(memloc, iface, new Identifier(id.getLoc(), id.getName()), false, false, false);
AssignmentExpression assgn = new AssignmentExpression(result.getLoc(), "=", mem, ((FunctionDeclaration)result).asFunctionExpression()); AssignmentExpression assgn = new AssignmentExpression(result.getLoc(), "=", mem, ((FunctionDeclaration)result).asFunctionExpression());
return new ExpressionStatement(result.getLoc(), assgn); return new ExpressionStatement(result.getLoc(), assgn);
} }

View File

@@ -29,6 +29,7 @@ import com.semmle.js.ast.BlockStatement;
import com.semmle.js.ast.BreakStatement; import com.semmle.js.ast.BreakStatement;
import com.semmle.js.ast.CallExpression; import com.semmle.js.ast.CallExpression;
import com.semmle.js.ast.CatchClause; import com.semmle.js.ast.CatchClause;
import com.semmle.js.ast.Chainable;
import com.semmle.js.ast.ClassBody; import com.semmle.js.ast.ClassBody;
import com.semmle.js.ast.ClassDeclaration; import com.semmle.js.ast.ClassDeclaration;
import com.semmle.js.ast.ClassExpression; import com.semmle.js.ast.ClassExpression;
@@ -504,6 +505,14 @@ public class Parser {
} }
} }
private Token readToken_question() { // '?'
int next = charAt(this.pos + 1);
int next2 = charAt(this.pos + 2);
if (this.options.esnext() && next == '.' && !('0' <= next2 && next2 <= '9')) // '?.', but not '?.X' where X is a digit
return this.finishOp(TokenType.questiondot, 2);
return this.finishOp(TokenType.question, 1);
}
private Token readToken_slash() { // '/' private Token readToken_slash() { // '/'
int next = charAt(this.pos + 1); int next = charAt(this.pos + 1);
if (this.exprAllowed) { if (this.exprAllowed) {
@@ -616,7 +625,7 @@ public class Parser {
case 123: ++this.pos; return this.finishToken(TokenType.braceL); case 123: ++this.pos; return this.finishToken(TokenType.braceL);
case 125: ++this.pos; return this.finishToken(TokenType.braceR); case 125: ++this.pos; return this.finishToken(TokenType.braceR);
case 58: ++this.pos; return this.finishToken(TokenType.colon); case 58: ++this.pos; return this.finishToken(TokenType.colon);
case 63: ++this.pos; return this.finishToken(TokenType.question); case 63: return this.readToken_question();
case 96: // '`' case 96: // '`'
if (this.options.ecmaVersion() < 6) break; if (this.options.ecmaVersion() < 6) break;
@@ -1465,17 +1474,19 @@ public class Parser {
} }
} }
private boolean isOnOptionalChain(boolean optional, Expression base) {
return optional || base instanceof Chainable && ((Chainable)base).isOnOptionalChain();
}
/** /**
* Parse a single subscript {@code s}; if more subscripts could follow, return {@code Pair.make(s, true}, * Parse a single subscript {@code s}; if more subscripts could follow, return {@code Pair.make(s, true},
* otherwise return {@code Pair.make(s, false)}. * otherwise return {@code Pair.make(s, false)}.
*/ */
protected Pair<Expression, Boolean> parseSubscript(final Expression base, Position startLoc, boolean noCalls) { protected Pair<Expression, Boolean> parseSubscript(final Expression base, Position startLoc, boolean noCalls) {
boolean maybeAsyncArrow = this.options.ecmaVersion() >= 8 && base instanceof Identifier && "async".equals(((Identifier) base).getName()) && !this.canInsertSemicolon(); boolean maybeAsyncArrow = this.options.ecmaVersion() >= 8 && base instanceof Identifier && "async".equals(((Identifier) base).getName()) && !this.canInsertSemicolon();
if (this.eat(TokenType.dot)) { boolean optional = this.eat(TokenType.questiondot);
MemberExpression node = new MemberExpression(new SourceLocation(startLoc), base, this.parseIdent(true), false); if (this.eat(TokenType.bracketL)) {
return Pair.make(this.finishNode(node), true); MemberExpression node = new MemberExpression(new SourceLocation(startLoc), base, this.parseExpression(false, null), true, optional, isOnOptionalChain(optional, base));
} else if (this.eat(TokenType.bracketL)) {
MemberExpression node = new MemberExpression(new SourceLocation(startLoc), base, this.parseExpression(false, null), true);
this.expect(TokenType.bracketR); this.expect(TokenType.bracketR);
return Pair.make(this.finishNode(node), true); return Pair.make(this.finishNode(node), true);
} else if (!noCalls && this.eat(TokenType.parenL)) { } else if (!noCalls && this.eat(TokenType.parenL)) {
@@ -1494,11 +1505,17 @@ public class Parser {
this.checkExpressionErrors(refDestructuringErrors, true); this.checkExpressionErrors(refDestructuringErrors, true);
if (oldYieldPos > 0) this.yieldPos = oldYieldPos; if (oldYieldPos > 0) this.yieldPos = oldYieldPos;
if (oldAwaitPos > 0) this.awaitPos = oldAwaitPos; if (oldAwaitPos > 0) this.awaitPos = oldAwaitPos;
CallExpression node = new CallExpression(new SourceLocation(startLoc), base, new ArrayList<>(), exprList); CallExpression node = new CallExpression(new SourceLocation(startLoc), base, new ArrayList<>(), exprList, optional, isOnOptionalChain(optional, base));
return Pair.make(this.finishNode(node), true); return Pair.make(this.finishNode(node), true);
} else if (this.type == TokenType.backQuote) { } else if (this.type == TokenType.backQuote) {
if (isOnOptionalChain(optional, base)) {
this.raise(base, "An optional chain may not be used in a tagged template expression.");
}
TaggedTemplateExpression node = new TaggedTemplateExpression(new SourceLocation(startLoc), base, this.parseTemplate(true)); TaggedTemplateExpression node = new TaggedTemplateExpression(new SourceLocation(startLoc), base, this.parseTemplate(true));
return Pair.make(this.finishNode(node), true); return Pair.make(this.finishNode(node), true);
} else if (optional || this.eat(TokenType.dot)) {
MemberExpression node = new MemberExpression(new SourceLocation(startLoc), base, this.parseIdent(true), false, optional, isOnOptionalChain(optional, base));
return Pair.make(this.finishNode(node), true);
} else { } else {
return Pair.make(base, false); return Pair.make(base, false);
} }
@@ -1719,6 +1736,10 @@ public class Parser {
int innerStartPos = this.start; int innerStartPos = this.start;
Position innerStartLoc = this.startLoc; Position innerStartLoc = this.startLoc;
Expression callee = this.parseSubscripts(this.parseExprAtom(null), innerStartPos, innerStartLoc, true); Expression callee = this.parseSubscripts(this.parseExprAtom(null), innerStartPos, innerStartLoc, true);
if (isOnOptionalChain(false, callee))
this.raise(callee, "An optional chain may not be used in a `new` expression.");
List<Expression> arguments; List<Expression> arguments;
if (this.eat(TokenType.parenL)) if (this.eat(TokenType.parenL))
arguments = this.parseExprList(TokenType.parenR, this.options.ecmaVersion() >= 8, false, null); arguments = this.parseExprList(TokenType.parenR, this.options.ecmaVersion() >= 8, false, null);
@@ -2159,9 +2180,12 @@ public class Parser {
return new ParenthesizedExpression(node.getLoc(), (Expression) this.toAssignable(expr, isBinding)); return new ParenthesizedExpression(node.getLoc(), (Expression) this.toAssignable(expr, isBinding));
} }
if (node instanceof MemberExpression) if (node instanceof MemberExpression) {
if (isOnOptionalChain(false, (MemberExpression)node))
this.raise(node, "Invalid left-hand side in assignment");
if (!isBinding) if (!isBinding)
return node; return node;
}
this.raise(node, "Assigning to rvalue"); this.raise(node, "Assigning to rvalue");
} }

View File

@@ -76,6 +76,7 @@ public class TokenType {
semi = new TokenType(new Properties(";").beforeExpr()), semi = new TokenType(new Properties(";").beforeExpr()),
colon = new TokenType(new Properties(":").beforeExpr()), colon = new TokenType(new Properties(":").beforeExpr()),
dot = new TokenType(new Properties(".")), dot = new TokenType(new Properties(".")),
questiondot = new TokenType(new Properties("?.")),
question = new TokenType(new Properties("?").beforeExpr()), question = new TokenType(new Properties("?").beforeExpr()),
arrow = new TokenType(new Properties("=>").beforeExpr()), arrow = new TokenType(new Properties("=>").beforeExpr()),
template = new TokenType(new Properties("template")), template = new TokenType(new Properties("template")),

View File

@@ -215,6 +215,7 @@ public class AST2JSON extends DefaultVisitor<Void, JsonElement> {
JsonObject result = this.mkNode(nd); JsonObject result = this.mkNode(nd);
result.add("callee", visit(nd.getCallee())); result.add("callee", visit(nd.getCallee()));
result.add("arguments", visit(nd.getArguments())); result.add("arguments", visit(nd.getArguments()));
result.add("optional", new JsonPrimitive(nd.isOptional()));
return result; return result;
} }
@@ -424,6 +425,7 @@ public class AST2JSON extends DefaultVisitor<Void, JsonElement> {
result.add("object", visit(nd.getObject())); result.add("object", visit(nd.getObject()));
result.add("property", visit(nd.getProperty())); result.add("property", visit(nd.getProperty()));
result.add("computed", new JsonPrimitive(nd.isComputed())); result.add("computed", new JsonPrimitive(nd.isComputed()));
result.add("optional", new JsonPrimitive(nd.isOptional()));
return result; return result;
} }

View File

@@ -8,8 +8,8 @@ import com.semmle.ts.ast.ITypeExpression;
* A function call expression such as <code>f(1, 1)</code>. * A function call expression such as <code>f(1, 1)</code>.
*/ */
public class CallExpression extends InvokeExpression { public class CallExpression extends InvokeExpression {
public CallExpression(SourceLocation loc, Expression callee, List<ITypeExpression> typeArguments, List<Expression> arguments) { public CallExpression(SourceLocation loc, Expression callee, List<ITypeExpression> typeArguments, List<Expression> arguments, Boolean optional, Boolean onOptionalChain) {
super("CallExpression", loc, callee, typeArguments, arguments); super("CallExpression", loc, callee, typeArguments, arguments, optional, onOptionalChain);
} }
@Override @Override

View File

@@ -0,0 +1,16 @@
package com.semmle.js.ast;
/**
* A chainable expression, such as a member access or function call.
*/
public interface Chainable {
/**
* Is this step of the chain optional?
*/
abstract boolean isOptional();
/**
* Is this on an optional chain?
*/
abstract boolean isOnOptionalChain();
}

View File

@@ -8,20 +8,24 @@ import com.semmle.ts.ast.ITypeExpression;
/** /**
* An invocation, that is, either a {@link CallExpression} or a {@link NewExpression}. * An invocation, that is, either a {@link CallExpression} or a {@link NewExpression}.
*/ */
public abstract class InvokeExpression extends Expression implements INodeWithSymbol { public abstract class InvokeExpression extends Expression implements INodeWithSymbol, Chainable {
private final Expression callee; private final Expression callee;
private final List<ITypeExpression> typeArguments; private final List<ITypeExpression> typeArguments;
private final List<Expression> arguments; private final List<Expression> arguments;
private final boolean optional;
private final boolean onOptionalChain;
private int resolvedSignatureId = -1; private int resolvedSignatureId = -1;
private int overloadIndex = -1; private int overloadIndex = -1;
private int symbol = -1; private int symbol = -1;
public InvokeExpression(String type, SourceLocation loc, Expression callee, List<ITypeExpression> typeArguments, public InvokeExpression(String type, SourceLocation loc, Expression callee, List<ITypeExpression> typeArguments,
List<Expression> arguments) { List<Expression> arguments, Boolean optional, Boolean onOptionalChain) {
super(type, loc); super(type, loc);
this.callee = callee; this.callee = callee;
this.typeArguments = typeArguments; this.typeArguments = typeArguments;
this.arguments = arguments; this.arguments = arguments;
this.optional = optional == Boolean.TRUE;
this.onOptionalChain = onOptionalChain == Boolean.TRUE;
} }
/** /**
@@ -45,6 +49,16 @@ public abstract class InvokeExpression extends Expression implements INodeWithSy
return arguments; return arguments;
} }
@Override
public boolean isOptional() {
return optional;
}
@Override
public boolean isOnOptionalChain() {
return onOptionalChain;
}
public int getResolvedSignatureId() { public int getResolvedSignatureId() {
return resolvedSignatureId; return resolvedSignatureId;
} }
@@ -70,4 +84,4 @@ public abstract class InvokeExpression extends Expression implements INodeWithSy
public void setSymbol(int symbol) { public void setSymbol(int symbol) {
this.symbol = symbol; this.symbol = symbol;
} }
} }

View File

@@ -6,16 +6,20 @@ import com.semmle.ts.ast.ITypeExpression;
/** /**
* A member expression, either computed (<code>e[f]</code>) or static (<code>e.f</code>). * A member expression, either computed (<code>e[f]</code>) or static (<code>e.f</code>).
*/ */
public class MemberExpression extends Expression implements ITypeExpression, INodeWithSymbol { public class MemberExpression extends Expression implements ITypeExpression, INodeWithSymbol, Chainable {
private final Expression object, property; private final Expression object, property;
private final boolean computed; private final boolean computed;
private final boolean optional;
private final boolean onOptionalChain;
private int symbol = -1; private int symbol = -1;
public MemberExpression(SourceLocation loc, Expression object, Expression property, Boolean computed) { public MemberExpression(SourceLocation loc, Expression object, Expression property, Boolean computed, Boolean optional, Boolean onOptionalChain) {
super("MemberExpression", loc); super("MemberExpression", loc);
this.object = object; this.object = object;
this.property = property; this.property = property;
this.computed = computed == Boolean.TRUE; this.computed = computed == Boolean.TRUE;
this.optional = optional == Boolean.TRUE;
this.onOptionalChain = onOptionalChain == Boolean.TRUE;
} }
@Override @Override
@@ -45,6 +49,16 @@ public class MemberExpression extends Expression implements ITypeExpression, INo
return computed; return computed;
} }
@Override
public boolean isOptional() {
return optional;
}
@Override
public boolean isOnOptionalChain() {
return onOptionalChain;
}
@Override @Override
public int getSymbol() { public int getSymbol() {
return symbol; return symbol;

View File

@@ -9,7 +9,7 @@ import com.semmle.ts.ast.ITypeExpression;
*/ */
public class NewExpression extends InvokeExpression { public class NewExpression extends InvokeExpression {
public NewExpression(SourceLocation loc, Expression callee, List<ITypeExpression> typeArguments, List<Expression> arguments) { public NewExpression(SourceLocation loc, Expression callee, List<ITypeExpression> typeArguments, List<Expression> arguments) {
super("NewExpression", loc, callee, typeArguments, arguments); super("NewExpression", loc, callee, typeArguments, arguments, false, false);
} }
@Override @Override

View File

@@ -97,7 +97,7 @@ public class NodeCopier implements Visitor<Void, INode> {
@Override @Override
public CallExpression visit(CallExpression nd, Void q) { public CallExpression visit(CallExpression nd, Void q) {
return new CallExpression(visit(nd.getLoc()), copy(nd.getCallee()), copy(nd.getTypeArguments()), copy(nd.getArguments())); return new CallExpression(visit(nd.getLoc()), copy(nd.getCallee()), copy(nd.getTypeArguments()), copy(nd.getArguments()), nd.isOptional(), nd.isOnOptionalChain());
} }
@Override @Override
@@ -140,7 +140,7 @@ public class NodeCopier implements Visitor<Void, INode> {
@Override @Override
public MemberExpression visit(MemberExpression nd, Void q) { public MemberExpression visit(MemberExpression nd, Void q) {
return new MemberExpression(visit(nd.getLoc()), copy(nd.getObject()), copy(nd.getProperty()), nd.isComputed()); return new MemberExpression(visit(nd.getLoc()), copy(nd.getObject()), copy(nd.getProperty()), nd.isComputed(), nd.isOptional(), nd.isOnOptionalChain());
} }
@Override @Override

View File

@@ -405,6 +405,9 @@ public class ASTExtractor {
if (nd.getOverloadIndex() != -1) { if (nd.getOverloadIndex() != -1) {
trapwriter.addTuple("invoke_expr_overload_index", key, nd.getOverloadIndex()); trapwriter.addTuple("invoke_expr_overload_index", key, nd.getOverloadIndex());
} }
if (nd.isOptional()) {
trapwriter.addTuple("isOptionalChaining", key);
}
emitNodeSymbol(nd, key); emitNodeSymbol(nd, key);
return key; return key;
} }
@@ -531,6 +534,9 @@ public class ASTExtractor {
visit(nd.getObject(), key, 0, baseIdContext); visit(nd.getObject(), key, 0, baseIdContext);
visit(nd.getProperty(), key, 1, nd.isComputed() ? IdContext.varBind : IdContext.label); visit(nd.getProperty(), key, 1, nd.isComputed() ? IdContext.varBind : IdContext.label);
} }
if (nd.isOptional()) {
trapwriter.addTuple("isOptionalChaining", key);
}
return key; return key;
} }
@@ -1245,7 +1251,7 @@ public class ASTExtractor {
Super superExpr = new Super(fakeLoc("super", loc)); Super superExpr = new Super(fakeLoc("super", loc));
CallExpression superCall = new CallExpression( CallExpression superCall = new CallExpression(
fakeLoc("super(...args)", loc), fakeLoc("super(...args)", loc),
superExpr, new ArrayList<>(), CollectionUtil.makeList(spreadArgs)); superExpr, new ArrayList<>(), CollectionUtil.makeList(spreadArgs), false, false);
ExpressionStatement superCallStmt = new ExpressionStatement( ExpressionStatement superCallStmt = new ExpressionStatement(
fakeLoc("super(...args);", loc), superCall); fakeLoc("super(...args);", loc), superCall);
body.getBody().add(superCallStmt); body.getBody().add(superCallStmt);

View File

@@ -24,6 +24,7 @@ import com.semmle.js.ast.BlockStatement;
import com.semmle.js.ast.BreakStatement; import com.semmle.js.ast.BreakStatement;
import com.semmle.js.ast.CallExpression; import com.semmle.js.ast.CallExpression;
import com.semmle.js.ast.CatchClause; import com.semmle.js.ast.CatchClause;
import com.semmle.js.ast.Chainable;
import com.semmle.js.ast.ClassBody; import com.semmle.js.ast.ClassBody;
import com.semmle.js.ast.ClassDeclaration; import com.semmle.js.ast.ClassDeclaration;
import com.semmle.js.ast.ClassExpression; import com.semmle.js.ast.ClassExpression;
@@ -776,6 +777,9 @@ public class CFGExtractor {
// cache the set of normal control flow successors // cache the set of normal control flow successors
private final Map<Node, Object> followingCache = new LinkedHashMap<Node, Object>(); private final Map<Node, Object> followingCache = new LinkedHashMap<Node, Object>();
// map from a node in a chain of property accesses or calls to the successor info for the first node in the chain
private final Map<Chainable, SuccessorInfo> chainRootSuccessors = new LinkedHashMap<Chainable, SuccessorInfo>();
/** /**
* Generate entry node. * Generate entry node.
*/ */
@@ -1637,16 +1641,36 @@ public class CFGExtractor {
return null; return null;
} }
private void preVisitChainable(Chainable chainable, Expression base, SuccessorInfo i) {
if (!chainable.isOnOptionalChain()) // optimization: bookkeeping is only needed for optional chains
return;
// start of chain
chainRootSuccessors.putIfAbsent(chainable, i);
// next step in chain
if (base instanceof Chainable)
chainRootSuccessors.put((Chainable)base, chainRootSuccessors.get(chainable));
}
private void postVisitChainable(Chainable chainable, Expression base, boolean optional) {
if (optional) {
succ(base, chainRootSuccessors.get(chainable).getSuccessors(false));
}
chainRootSuccessors.remove(chainable);
}
@Override @Override
public Void visit(MemberExpression nd, SuccessorInfo i) { public Void visit(MemberExpression nd, SuccessorInfo i) {
preVisitChainable(nd, nd.getObject(), i);
seq(nd.getObject(), nd.getProperty(), nd); seq(nd.getObject(), nd.getProperty(), nd);
// property accesses may throw // property accesses may throw
succ(nd, union(this.findTarget(JumpType.THROW, null), i.getGuardedSuccessors(nd))); succ(nd, union(this.findTarget(JumpType.THROW, null), i.getGuardedSuccessors(nd)));
postVisitChainable(nd, nd.getObject(), nd.isOptional());
return null; return null;
} }
@Override @Override
public Void visit(InvokeExpression nd, SuccessorInfo i) { public Void visit(InvokeExpression nd, SuccessorInfo i) {
preVisitChainable(nd, nd.getCallee(), i);
seq(nd.getCallee(), nd.getArguments(), nd); seq(nd.getCallee(), nd.getArguments(), nd);
Object succs = i.getGuardedSuccessors(nd); Object succs = i.getGuardedSuccessors(nd);
if (nd instanceof CallExpression && nd.getCallee() instanceof Super && !instanceFields.isEmpty()) { if (nd instanceof CallExpression && nd.getCallee() instanceof Super && !instanceFields.isEmpty()) {
@@ -1660,6 +1684,7 @@ public class CFGExtractor {
} }
// calls may throw // calls may throw
succ(nd, union(this.findTarget(JumpType.THROW, null), succs)); succ(nd, union(this.findTarget(JumpType.THROW, null), succs));
postVisitChainable(nd, nd.getCallee(), nd.isOptional());
return null; return null;
} }

View File

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

View File

@@ -4,6 +4,9 @@ import java.io.File;
import java.io.StringWriter; import java.io.StringWriter;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.Map.Entry; import java.util.Map.Entry;
@@ -40,7 +43,9 @@ public class TrapTests {
List<Object[]> testData = new ArrayList<Object[]>(); List<Object[]> testData = new ArrayList<Object[]>();
// iterate over all test groups // iterate over all test groups
for (String testgroup : BASE.list()) { List<String> testGroups = Arrays.asList(BASE.list());
testGroups.sort(Comparator.naturalOrder());
for (String testgroup : testGroups) {
File root = new File(BASE, testgroup); File root = new File(BASE, testgroup);
if (root.isDirectory()) { if (root.isDirectory()) {
// check for options.json file and process it if it exists // check for options.json file and process it if it exists
@@ -78,7 +83,9 @@ public class TrapTests {
testData.add(new Object[] { testgroup, "tsconfig", new ArrayList<String>(options) }); testData.add(new Object[] { testgroup, "tsconfig", new ArrayList<String>(options) });
} else { } else {
// create isolated tests for each input file in the group // create isolated tests for each input file in the group
for (String testfile : inputDir.list()) { List<String> tests = Arrays.asList(inputDir.list());
tests.sort(Comparator.naturalOrder());
for (String testfile : tests) {
testData.add(new Object[] { testgroup, testfile, new ArrayList<String>(options) }); testData.add(new Object[] { testgroup, testfile, new ArrayList<String>(options) });
} }
} }
@@ -149,7 +156,13 @@ public class TrapTests {
// convert to and from UTF-8 to mimick treatment of unencodable characters // convert to and from UTF-8 to mimick treatment of unencodable characters
byte[] actual_utf8_bytes = StringUtil.stringToBytes(sw.toString()); byte[] actual_utf8_bytes = StringUtil.stringToBytes(sw.toString());
String actual = new String(actual_utf8_bytes, Charset.forName("UTF-8")); String actual = new String(actual_utf8_bytes, Charset.forName("UTF-8"));
String expected = new WholeIO().strictreadText(new File(outputDir, f.getName() + ".trap")); File trap = new File(outputDir, f.getName() + ".trap");
boolean replaceExpectedOutput = false;
if (replaceExpectedOutput) {
System.out.println("Replacing expected output for " + trap);
new WholeIO().strictwrite(trap, actual);
}
String expected = new WholeIO().strictreadText(trap);
expectedVsActual.add(Pair.make(expected, actual)); expectedVsActual.add(Pair.make(expected, actual));
} }
}; };

View File

@@ -830,7 +830,7 @@ public class TypeScriptASTConverter {
} }
Expression callee = convertChild(node, "expression"); Expression callee = convertChild(node, "expression");
List<ITypeExpression> typeArguments = convertChildrenAsTypes(node, "typeArguments"); List<ITypeExpression> typeArguments = convertChildrenAsTypes(node, "typeArguments");
CallExpression call = new CallExpression(loc, callee, typeArguments, arguments); CallExpression call = new CallExpression(loc, callee, typeArguments, arguments, false, false);
attachResolvedSignature(call, node); attachResolvedSignature(call, node);
return call; return call;
} }
@@ -1027,7 +1027,7 @@ public class TypeScriptASTConverter {
SourceLocation loc) throws ParseError { SourceLocation loc) throws ParseError {
Expression object = convertChild(node, "expression"); Expression object = convertChild(node, "expression");
Expression property = convertChild(node, "argumentExpression"); Expression property = convertChild(node, "argumentExpression");
return new MemberExpression(loc, object, property, true); return new MemberExpression(loc, object, property, true, false, false);
} }
private Node convertEmptyStatement(SourceLocation loc) { private Node convertEmptyStatement(SourceLocation loc) {
@@ -1311,7 +1311,7 @@ public class TypeScriptASTConverter {
} else { } else {
throw new ParseError("Unsupported syntax in import type", getSourceLocation(node).getStart()); throw new ParseError("Unsupported syntax in import type", getSourceLocation(node).getStart());
} }
MemberExpression member = new MemberExpression(getSourceLocation(node), (Expression) base, name, false); MemberExpression member = new MemberExpression(getSourceLocation(node), (Expression) base, name, false, false, false);
attachSymbolInformation(member, node); attachSymbolInformation(member, node);
return member; return member;
} }
@@ -1797,7 +1797,7 @@ public class TypeScriptASTConverter {
private Node convertPropertyAccessExpression(JsonObject node, private Node convertPropertyAccessExpression(JsonObject node,
SourceLocation loc) throws ParseError { SourceLocation loc) throws ParseError {
return new MemberExpression(loc, convertChild(node, "expression"), convertChild(node, "name"), false); return new MemberExpression(loc, convertChild(node, "expression"), convertChild(node, "name"), false, false, false);
} }
private Node convertPropertyAssignment(JsonObject node, private Node convertPropertyAssignment(JsonObject node,
@@ -1838,7 +1838,7 @@ public class TypeScriptASTConverter {
} }
private Node convertQualifiedName(JsonObject node, SourceLocation loc) throws ParseError { private Node convertQualifiedName(JsonObject node, SourceLocation loc) throws ParseError {
MemberExpression expr = new MemberExpression(loc, convertChild(node, "left"), convertChild(node, "right"), false); MemberExpression expr = new MemberExpression(loc, convertChild(node, "left"), convertChild(node, "right"), false, false, false);
attachSymbolInformation(expr, node); attachSymbolInformation(expr, node);
return expr; return expr;
} }

View File

@@ -0,0 +1,13 @@
a1?.b1;
a2?.[x2];
a3?.b3();
a4?.();
o5?.3:2;
a6?.b6[x6].c6?.(y6).d6;
delete a?.b

View File

@@ -0,0 +1 @@
new a?.();

View File

@@ -0,0 +1 @@
a?.`{b}`;

View File

@@ -0,0 +1 @@
new a?.b();

View File

@@ -0,0 +1 @@
a?.b`{c}`;

View File

@@ -0,0 +1 @@
a?.b = c;

View File

@@ -0,0 +1 @@
new a.b?.c();

View File

@@ -0,0 +1,29 @@
a?.b.c(++x).d;
a?.b[3].c?.(x).d;
(a?.b).c;
(a?.b.c).d;
a?.[b?.c?.d].e?.f;
a?.()[b?.().c?.().d].e?.().f;
if (a?.b) {
true;
} else {
false;
}
if (!a?.b) {
true;
} else {
false;
}
if (a?.b && c?.d) {
true;
} else {
false;
}

View File

@@ -0,0 +1,655 @@
#10000=@"/optional-chaining.js;sourcefile"
files(#10000,"/optional-chaining.js","optional-chaining","js",0)
#10001=@"/;folder"
folders(#10001,"/","")
containerparent(#10001,#10000)
#10002=@"loc,{#10000},0,0,0,0"
locations_default(#10002,#10000,0,0,0,0)
hasLocation(#10000,#10002)
#20000=@"global_scope"
scopes(#20000,0)
#20001=@"script;{#10000},1,1"
toplevels(#20001,0)
#20002=@"loc,{#10000},1,1,13,11"
locations_default(#20002,#10000,1,1,13,11)
hasLocation(#20001,#20002)
#20003=*
stmts(#20003,2,#20001,0,"a1?.b1;")
#20004=@"loc,{#10000},1,1,1,7"
locations_default(#20004,#10000,1,1,1,7)
hasLocation(#20003,#20004)
stmtContainers(#20003,#20001)
#20005=*
exprs(#20005,14,#20003,0,"a1?.b1")
#20006=@"loc,{#10000},1,1,1,6"
locations_default(#20006,#10000,1,1,1,6)
hasLocation(#20005,#20006)
enclosingStmt(#20005,#20003)
exprContainers(#20005,#20001)
#20007=*
exprs(#20007,79,#20005,0,"a1")
#20008=@"loc,{#10000},1,1,1,2"
locations_default(#20008,#10000,1,1,1,2)
hasLocation(#20007,#20008)
enclosingStmt(#20007,#20003)
exprContainers(#20007,#20001)
literals("a1","a1",#20007)
#20009=@"var;{a1};{#20000}"
variables(#20009,"a1",#20000)
bind(#20007,#20009)
#20010=*
exprs(#20010,0,#20005,1,"b1")
#20011=@"loc,{#10000},1,5,1,6"
locations_default(#20011,#10000,1,5,1,6)
hasLocation(#20010,#20011)
enclosingStmt(#20010,#20003)
exprContainers(#20010,#20001)
literals("b1","b1",#20010)
isOptionalChaining(#20005)
#20012=*
stmts(#20012,2,#20001,1,"a2?.[x2];")
#20013=@"loc,{#10000},3,1,3,9"
locations_default(#20013,#10000,3,1,3,9)
hasLocation(#20012,#20013)
stmtContainers(#20012,#20001)
#20014=*
exprs(#20014,15,#20012,0,"a2?.[x2]")
#20015=@"loc,{#10000},3,1,3,8"
locations_default(#20015,#10000,3,1,3,8)
hasLocation(#20014,#20015)
enclosingStmt(#20014,#20012)
exprContainers(#20014,#20001)
#20016=*
exprs(#20016,79,#20014,0,"a2")
#20017=@"loc,{#10000},3,1,3,2"
locations_default(#20017,#10000,3,1,3,2)
hasLocation(#20016,#20017)
enclosingStmt(#20016,#20012)
exprContainers(#20016,#20001)
literals("a2","a2",#20016)
#20018=@"var;{a2};{#20000}"
variables(#20018,"a2",#20000)
bind(#20016,#20018)
#20019=*
exprs(#20019,79,#20014,1,"x2")
#20020=@"loc,{#10000},3,6,3,7"
locations_default(#20020,#10000,3,6,3,7)
hasLocation(#20019,#20020)
enclosingStmt(#20019,#20012)
exprContainers(#20019,#20001)
literals("x2","x2",#20019)
#20021=@"var;{x2};{#20000}"
variables(#20021,"x2",#20000)
bind(#20019,#20021)
isOptionalChaining(#20014)
#20022=*
stmts(#20022,2,#20001,2,"a3?.b3();")
#20023=@"loc,{#10000},5,1,5,9"
locations_default(#20023,#10000,5,1,5,9)
hasLocation(#20022,#20023)
stmtContainers(#20022,#20001)
#20024=*
exprs(#20024,13,#20022,0,"a3?.b3()")
#20025=@"loc,{#10000},5,1,5,8"
locations_default(#20025,#10000,5,1,5,8)
hasLocation(#20024,#20025)
enclosingStmt(#20024,#20022)
exprContainers(#20024,#20001)
#20026=*
exprs(#20026,14,#20024,-1,"a3?.b3")
#20027=@"loc,{#10000},5,1,5,6"
locations_default(#20027,#10000,5,1,5,6)
hasLocation(#20026,#20027)
enclosingStmt(#20026,#20022)
exprContainers(#20026,#20001)
#20028=*
exprs(#20028,79,#20026,0,"a3")
#20029=@"loc,{#10000},5,1,5,2"
locations_default(#20029,#10000,5,1,5,2)
hasLocation(#20028,#20029)
enclosingStmt(#20028,#20022)
exprContainers(#20028,#20001)
literals("a3","a3",#20028)
#20030=@"var;{a3};{#20000}"
variables(#20030,"a3",#20000)
bind(#20028,#20030)
#20031=*
exprs(#20031,0,#20026,1,"b3")
#20032=@"loc,{#10000},5,5,5,6"
locations_default(#20032,#10000,5,5,5,6)
hasLocation(#20031,#20032)
enclosingStmt(#20031,#20022)
exprContainers(#20031,#20001)
literals("b3","b3",#20031)
isOptionalChaining(#20026)
#20033=*
stmts(#20033,2,#20001,3,"a4?.();")
#20034=@"loc,{#10000},7,1,7,7"
locations_default(#20034,#10000,7,1,7,7)
hasLocation(#20033,#20034)
stmtContainers(#20033,#20001)
#20035=*
exprs(#20035,13,#20033,0,"a4?.()")
#20036=@"loc,{#10000},7,1,7,6"
locations_default(#20036,#10000,7,1,7,6)
hasLocation(#20035,#20036)
enclosingStmt(#20035,#20033)
exprContainers(#20035,#20001)
#20037=*
exprs(#20037,79,#20035,-1,"a4")
#20038=@"loc,{#10000},7,1,7,2"
locations_default(#20038,#10000,7,1,7,2)
hasLocation(#20037,#20038)
enclosingStmt(#20037,#20033)
exprContainers(#20037,#20001)
literals("a4","a4",#20037)
#20039=@"var;{a4};{#20000}"
variables(#20039,"a4",#20000)
bind(#20037,#20039)
isOptionalChaining(#20035)
#20040=*
stmts(#20040,2,#20001,4,"o5?.3:2;")
#20041=@"loc,{#10000},9,1,9,8"
locations_default(#20041,#10000,9,1,9,8)
hasLocation(#20040,#20041)
stmtContainers(#20040,#20001)
#20042=*
exprs(#20042,11,#20040,0,"o5?.3:2")
#20043=@"loc,{#10000},9,1,9,7"
locations_default(#20043,#10000,9,1,9,7)
hasLocation(#20042,#20043)
enclosingStmt(#20042,#20040)
exprContainers(#20042,#20001)
#20044=*
exprs(#20044,79,#20042,0,"o5")
#20045=@"loc,{#10000},9,1,9,2"
locations_default(#20045,#10000,9,1,9,2)
hasLocation(#20044,#20045)
enclosingStmt(#20044,#20040)
exprContainers(#20044,#20001)
literals("o5","o5",#20044)
#20046=@"var;{o5};{#20000}"
variables(#20046,"o5",#20000)
bind(#20044,#20046)
#20047=*
exprs(#20047,3,#20042,1,".3")
#20048=@"loc,{#10000},9,4,9,5"
locations_default(#20048,#10000,9,4,9,5)
hasLocation(#20047,#20048)
enclosingStmt(#20047,#20040)
exprContainers(#20047,#20001)
literals("0.3",".3",#20047)
#20049=*
exprs(#20049,3,#20042,2,"2")
#20050=@"loc,{#10000},9,7,9,7"
locations_default(#20050,#10000,9,7,9,7)
hasLocation(#20049,#20050)
enclosingStmt(#20049,#20040)
exprContainers(#20049,#20001)
literals("2","2",#20049)
#20051=*
stmts(#20051,2,#20001,5,"a6?.b6[ ... y6).d6;")
#20052=@"loc,{#10000},11,1,11,23"
locations_default(#20052,#10000,11,1,11,23)
hasLocation(#20051,#20052)
stmtContainers(#20051,#20001)
#20053=*
exprs(#20053,14,#20051,0,"a6?.b6[ ... (y6).d6")
#20054=@"loc,{#10000},11,1,11,22"
locations_default(#20054,#10000,11,1,11,22)
hasLocation(#20053,#20054)
enclosingStmt(#20053,#20051)
exprContainers(#20053,#20001)
#20055=*
exprs(#20055,13,#20053,0,"a6?.b6[x6].c6?.(y6)")
#20056=@"loc,{#10000},11,1,11,19"
locations_default(#20056,#10000,11,1,11,19)
hasLocation(#20055,#20056)
enclosingStmt(#20055,#20051)
exprContainers(#20055,#20001)
#20057=*
exprs(#20057,14,#20055,-1,"a6?.b6[x6].c6")
#20058=@"loc,{#10000},11,1,11,13"
locations_default(#20058,#10000,11,1,11,13)
hasLocation(#20057,#20058)
enclosingStmt(#20057,#20051)
exprContainers(#20057,#20001)
#20059=*
exprs(#20059,15,#20057,0,"a6?.b6[x6]")
#20060=@"loc,{#10000},11,1,11,10"
locations_default(#20060,#10000,11,1,11,10)
hasLocation(#20059,#20060)
enclosingStmt(#20059,#20051)
exprContainers(#20059,#20001)
#20061=*
exprs(#20061,14,#20059,0,"a6?.b6")
#20062=@"loc,{#10000},11,1,11,6"
locations_default(#20062,#10000,11,1,11,6)
hasLocation(#20061,#20062)
enclosingStmt(#20061,#20051)
exprContainers(#20061,#20001)
#20063=*
exprs(#20063,79,#20061,0,"a6")
#20064=@"loc,{#10000},11,1,11,2"
locations_default(#20064,#10000,11,1,11,2)
hasLocation(#20063,#20064)
enclosingStmt(#20063,#20051)
exprContainers(#20063,#20001)
literals("a6","a6",#20063)
#20065=@"var;{a6};{#20000}"
variables(#20065,"a6",#20000)
bind(#20063,#20065)
#20066=*
exprs(#20066,0,#20061,1,"b6")
#20067=@"loc,{#10000},11,5,11,6"
locations_default(#20067,#10000,11,5,11,6)
hasLocation(#20066,#20067)
enclosingStmt(#20066,#20051)
exprContainers(#20066,#20001)
literals("b6","b6",#20066)
isOptionalChaining(#20061)
#20068=*
exprs(#20068,79,#20059,1,"x6")
#20069=@"loc,{#10000},11,8,11,9"
locations_default(#20069,#10000,11,8,11,9)
hasLocation(#20068,#20069)
enclosingStmt(#20068,#20051)
exprContainers(#20068,#20001)
literals("x6","x6",#20068)
#20070=@"var;{x6};{#20000}"
variables(#20070,"x6",#20000)
bind(#20068,#20070)
#20071=*
exprs(#20071,0,#20057,1,"c6")
#20072=@"loc,{#10000},11,12,11,13"
locations_default(#20072,#10000,11,12,11,13)
hasLocation(#20071,#20072)
enclosingStmt(#20071,#20051)
exprContainers(#20071,#20001)
literals("c6","c6",#20071)
#20073=*
exprs(#20073,79,#20055,0,"y6")
#20074=@"loc,{#10000},11,17,11,18"
locations_default(#20074,#10000,11,17,11,18)
hasLocation(#20073,#20074)
enclosingStmt(#20073,#20051)
exprContainers(#20073,#20001)
literals("y6","y6",#20073)
#20075=@"var;{y6};{#20000}"
variables(#20075,"y6",#20000)
bind(#20073,#20075)
isOptionalChaining(#20055)
#20076=*
exprs(#20076,0,#20053,1,"d6")
#20077=@"loc,{#10000},11,21,11,22"
locations_default(#20077,#10000,11,21,11,22)
hasLocation(#20076,#20077)
enclosingStmt(#20076,#20051)
exprContainers(#20076,#20001)
literals("d6","d6",#20076)
#20078=*
stmts(#20078,2,#20001,6,"delete a?.b")
#20079=@"loc,{#10000},13,1,13,11"
locations_default(#20079,#10000,13,1,13,11)
hasLocation(#20078,#20079)
stmtContainers(#20078,#20001)
#20080=*
exprs(#20080,22,#20078,0,"delete a?.b")
hasLocation(#20080,#20079)
enclosingStmt(#20080,#20078)
exprContainers(#20080,#20001)
#20081=*
exprs(#20081,14,#20080,0,"a?.b")
#20082=@"loc,{#10000},13,8,13,11"
locations_default(#20082,#10000,13,8,13,11)
hasLocation(#20081,#20082)
enclosingStmt(#20081,#20078)
exprContainers(#20081,#20001)
#20083=*
exprs(#20083,79,#20081,0,"a")
#20084=@"loc,{#10000},13,8,13,8"
locations_default(#20084,#10000,13,8,13,8)
hasLocation(#20083,#20084)
enclosingStmt(#20083,#20078)
exprContainers(#20083,#20001)
literals("a","a",#20083)
#20085=@"var;{a};{#20000}"
variables(#20085,"a",#20000)
bind(#20083,#20085)
#20086=*
exprs(#20086,0,#20081,1,"b")
#20087=@"loc,{#10000},13,11,13,11"
locations_default(#20087,#10000,13,11,13,11)
hasLocation(#20086,#20087)
enclosingStmt(#20086,#20078)
exprContainers(#20086,#20001)
literals("b","b",#20086)
isOptionalChaining(#20081)
#20088=*
lines(#20088,#20001,"a1?.b1;","
")
hasLocation(#20088,#20004)
#20089=*
lines(#20089,#20001,"","
")
#20090=@"loc,{#10000},2,1,2,0"
locations_default(#20090,#10000,2,1,2,0)
hasLocation(#20089,#20090)
#20091=*
lines(#20091,#20001,"a2?.[x2];","
")
hasLocation(#20091,#20013)
#20092=*
lines(#20092,#20001,"","
")
#20093=@"loc,{#10000},4,1,4,0"
locations_default(#20093,#10000,4,1,4,0)
hasLocation(#20092,#20093)
#20094=*
lines(#20094,#20001,"a3?.b3();","
")
hasLocation(#20094,#20023)
#20095=*
lines(#20095,#20001,"","
")
#20096=@"loc,{#10000},6,1,6,0"
locations_default(#20096,#10000,6,1,6,0)
hasLocation(#20095,#20096)
#20097=*
lines(#20097,#20001,"a4?.();","
")
hasLocation(#20097,#20034)
#20098=*
lines(#20098,#20001,"","
")
#20099=@"loc,{#10000},8,1,8,0"
locations_default(#20099,#10000,8,1,8,0)
hasLocation(#20098,#20099)
#20100=*
lines(#20100,#20001,"o5?.3:2;","
")
hasLocation(#20100,#20041)
#20101=*
lines(#20101,#20001,"","
")
#20102=@"loc,{#10000},10,1,10,0"
locations_default(#20102,#10000,10,1,10,0)
hasLocation(#20101,#20102)
#20103=*
lines(#20103,#20001,"a6?.b6[x6].c6?.(y6).d6;","
")
hasLocation(#20103,#20052)
#20104=*
lines(#20104,#20001,"","
")
#20105=@"loc,{#10000},12,1,12,0"
locations_default(#20105,#10000,12,1,12,0)
hasLocation(#20104,#20105)
#20106=*
lines(#20106,#20001,"delete a?.b","")
hasLocation(#20106,#20079)
numlines(#20001,13,7,0)
#20107=*
tokeninfo(#20107,6,#20001,0,"a1")
hasLocation(#20107,#20008)
#20108=*
tokeninfo(#20108,8,#20001,1,"?.")
#20109=@"loc,{#10000},1,3,1,4"
locations_default(#20109,#10000,1,3,1,4)
hasLocation(#20108,#20109)
#20110=*
tokeninfo(#20110,6,#20001,2,"b1")
hasLocation(#20110,#20011)
#20111=*
tokeninfo(#20111,8,#20001,3,";")
#20112=@"loc,{#10000},1,7,1,7"
locations_default(#20112,#10000,1,7,1,7)
hasLocation(#20111,#20112)
#20113=*
tokeninfo(#20113,6,#20001,4,"a2")
hasLocation(#20113,#20017)
#20114=*
tokeninfo(#20114,8,#20001,5,"?.")
#20115=@"loc,{#10000},3,3,3,4"
locations_default(#20115,#10000,3,3,3,4)
hasLocation(#20114,#20115)
#20116=*
tokeninfo(#20116,8,#20001,6,"[")
#20117=@"loc,{#10000},3,5,3,5"
locations_default(#20117,#10000,3,5,3,5)
hasLocation(#20116,#20117)
#20118=*
tokeninfo(#20118,6,#20001,7,"x2")
hasLocation(#20118,#20020)
#20119=*
tokeninfo(#20119,8,#20001,8,"]")
#20120=@"loc,{#10000},3,8,3,8"
locations_default(#20120,#10000,3,8,3,8)
hasLocation(#20119,#20120)
#20121=*
tokeninfo(#20121,8,#20001,9,";")
#20122=@"loc,{#10000},3,9,3,9"
locations_default(#20122,#10000,3,9,3,9)
hasLocation(#20121,#20122)
#20123=*
tokeninfo(#20123,6,#20001,10,"a3")
hasLocation(#20123,#20029)
#20124=*
tokeninfo(#20124,8,#20001,11,"?.")
#20125=@"loc,{#10000},5,3,5,4"
locations_default(#20125,#10000,5,3,5,4)
hasLocation(#20124,#20125)
#20126=*
tokeninfo(#20126,6,#20001,12,"b3")
hasLocation(#20126,#20032)
#20127=*
tokeninfo(#20127,8,#20001,13,"(")
#20128=@"loc,{#10000},5,7,5,7"
locations_default(#20128,#10000,5,7,5,7)
hasLocation(#20127,#20128)
#20129=*
tokeninfo(#20129,8,#20001,14,")")
#20130=@"loc,{#10000},5,8,5,8"
locations_default(#20130,#10000,5,8,5,8)
hasLocation(#20129,#20130)
#20131=*
tokeninfo(#20131,8,#20001,15,";")
#20132=@"loc,{#10000},5,9,5,9"
locations_default(#20132,#10000,5,9,5,9)
hasLocation(#20131,#20132)
#20133=*
tokeninfo(#20133,6,#20001,16,"a4")
hasLocation(#20133,#20038)
#20134=*
tokeninfo(#20134,8,#20001,17,"?.")
#20135=@"loc,{#10000},7,3,7,4"
locations_default(#20135,#10000,7,3,7,4)
hasLocation(#20134,#20135)
#20136=*
tokeninfo(#20136,8,#20001,18,"(")
#20137=@"loc,{#10000},7,5,7,5"
locations_default(#20137,#10000,7,5,7,5)
hasLocation(#20136,#20137)
#20138=*
tokeninfo(#20138,8,#20001,19,")")
#20139=@"loc,{#10000},7,6,7,6"
locations_default(#20139,#10000,7,6,7,6)
hasLocation(#20138,#20139)
#20140=*
tokeninfo(#20140,8,#20001,20,";")
#20141=@"loc,{#10000},7,7,7,7"
locations_default(#20141,#10000,7,7,7,7)
hasLocation(#20140,#20141)
#20142=*
tokeninfo(#20142,6,#20001,21,"o5")
hasLocation(#20142,#20045)
#20143=*
tokeninfo(#20143,8,#20001,22,"?")
#20144=@"loc,{#10000},9,3,9,3"
locations_default(#20144,#10000,9,3,9,3)
hasLocation(#20143,#20144)
#20145=*
tokeninfo(#20145,3,#20001,23,".3")
hasLocation(#20145,#20048)
#20146=*
tokeninfo(#20146,8,#20001,24,":")
#20147=@"loc,{#10000},9,6,9,6"
locations_default(#20147,#10000,9,6,9,6)
hasLocation(#20146,#20147)
#20148=*
tokeninfo(#20148,3,#20001,25,"2")
hasLocation(#20148,#20050)
#20149=*
tokeninfo(#20149,8,#20001,26,";")
#20150=@"loc,{#10000},9,8,9,8"
locations_default(#20150,#10000,9,8,9,8)
hasLocation(#20149,#20150)
#20151=*
tokeninfo(#20151,6,#20001,27,"a6")
hasLocation(#20151,#20064)
#20152=*
tokeninfo(#20152,8,#20001,28,"?.")
#20153=@"loc,{#10000},11,3,11,4"
locations_default(#20153,#10000,11,3,11,4)
hasLocation(#20152,#20153)
#20154=*
tokeninfo(#20154,6,#20001,29,"b6")
hasLocation(#20154,#20067)
#20155=*
tokeninfo(#20155,8,#20001,30,"[")
#20156=@"loc,{#10000},11,7,11,7"
locations_default(#20156,#10000,11,7,11,7)
hasLocation(#20155,#20156)
#20157=*
tokeninfo(#20157,6,#20001,31,"x6")
hasLocation(#20157,#20069)
#20158=*
tokeninfo(#20158,8,#20001,32,"]")
#20159=@"loc,{#10000},11,10,11,10"
locations_default(#20159,#10000,11,10,11,10)
hasLocation(#20158,#20159)
#20160=*
tokeninfo(#20160,8,#20001,33,".")
#20161=@"loc,{#10000},11,11,11,11"
locations_default(#20161,#10000,11,11,11,11)
hasLocation(#20160,#20161)
#20162=*
tokeninfo(#20162,6,#20001,34,"c6")
hasLocation(#20162,#20072)
#20163=*
tokeninfo(#20163,8,#20001,35,"?.")
#20164=@"loc,{#10000},11,14,11,15"
locations_default(#20164,#10000,11,14,11,15)
hasLocation(#20163,#20164)
#20165=*
tokeninfo(#20165,8,#20001,36,"(")
#20166=@"loc,{#10000},11,16,11,16"
locations_default(#20166,#10000,11,16,11,16)
hasLocation(#20165,#20166)
#20167=*
tokeninfo(#20167,6,#20001,37,"y6")
hasLocation(#20167,#20074)
#20168=*
tokeninfo(#20168,8,#20001,38,")")
#20169=@"loc,{#10000},11,19,11,19"
locations_default(#20169,#10000,11,19,11,19)
hasLocation(#20168,#20169)
#20170=*
tokeninfo(#20170,8,#20001,39,".")
#20171=@"loc,{#10000},11,20,11,20"
locations_default(#20171,#10000,11,20,11,20)
hasLocation(#20170,#20171)
#20172=*
tokeninfo(#20172,6,#20001,40,"d6")
hasLocation(#20172,#20077)
#20173=*
tokeninfo(#20173,8,#20001,41,";")
#20174=@"loc,{#10000},11,23,11,23"
locations_default(#20174,#10000,11,23,11,23)
hasLocation(#20173,#20174)
#20175=*
tokeninfo(#20175,7,#20001,42,"delete")
#20176=@"loc,{#10000},13,1,13,6"
locations_default(#20176,#10000,13,1,13,6)
hasLocation(#20175,#20176)
#20177=*
tokeninfo(#20177,6,#20001,43,"a")
hasLocation(#20177,#20084)
#20178=*
tokeninfo(#20178,8,#20001,44,"?.")
#20179=@"loc,{#10000},13,9,13,10"
locations_default(#20179,#10000,13,9,13,10)
hasLocation(#20178,#20179)
#20180=*
tokeninfo(#20180,6,#20001,45,"b")
hasLocation(#20180,#20087)
#20181=*
tokeninfo(#20181,0,#20001,46,"")
#20182=@"loc,{#10000},13,12,13,11"
locations_default(#20182,#10000,13,12,13,11)
hasLocation(#20181,#20182)
#20183=*
entry_cfg_node(#20183,#20001)
#20184=@"loc,{#10000},1,1,1,0"
locations_default(#20184,#10000,1,1,1,0)
hasLocation(#20183,#20184)
#20185=*
exit_cfg_node(#20185,#20001)
hasLocation(#20185,#20182)
successor(#20078,#20083)
successor(#20086,#20081)
successor(#20083,#20086)
successor(#20081,#20080)
successor(#20083,#20080)
successor(#20080,#20185)
successor(#20051,#20063)
successor(#20076,#20053)
successor(#20073,#20055)
successor(#20071,#20057)
successor(#20068,#20059)
successor(#20066,#20061)
successor(#20063,#20066)
successor(#20061,#20068)
successor(#20063,#20078)
successor(#20059,#20071)
successor(#20057,#20073)
successor(#20055,#20076)
successor(#20057,#20078)
successor(#20053,#20078)
successor(#20040,#20042)
successor(#20042,#20044)
#20186=*
guard_node(#20186,1,#20044)
hasLocation(#20186,#20045)
successor(#20186,#20047)
#20187=*
guard_node(#20187,0,#20044)
hasLocation(#20187,#20045)
successor(#20187,#20049)
successor(#20044,#20186)
successor(#20044,#20187)
successor(#20047,#20051)
successor(#20049,#20051)
successor(#20033,#20037)
successor(#20037,#20035)
successor(#20035,#20040)
successor(#20037,#20040)
successor(#20022,#20028)
successor(#20031,#20026)
successor(#20028,#20031)
successor(#20026,#20024)
successor(#20028,#20033)
successor(#20024,#20033)
successor(#20012,#20016)
successor(#20019,#20014)
successor(#20016,#20019)
successor(#20014,#20022)
successor(#20016,#20022)
successor(#20003,#20007)
successor(#20010,#20005)
successor(#20007,#20010)
successor(#20005,#20012)
successor(#20007,#20012)
successor(#20183,#20003)
numlines(#10000,13,7,0)
filetype(#10000,"javascript")

View File

@@ -0,0 +1,28 @@
#10000=@"/optional-chaining_bad1.js;sourcefile"
files(#10000,"/optional-chaining_bad1.js","optional-chaining_bad1","js",0)
#10001=@"/;folder"
folders(#10001,"/","")
containerparent(#10001,#10000)
#10002=@"loc,{#10000},0,0,0,0"
locations_default(#10002,#10000,0,0,0,0)
hasLocation(#10000,#10002)
#20000=@"global_scope"
scopes(#20000,0)
#20001=@"script;{#10000},1,1"
toplevels(#20001,0)
#20002=@"loc,{#10000},1,1,1,1"
locations_default(#20002,#10000,1,1,1,1)
hasLocation(#20001,#20002)
#20003=*
jsParseErrors(#20003,#20001,"Error: Unexpected token","new a?.();")
#20004=@"loc,{#10000},1,8,1,8"
locations_default(#20004,#10000,1,8,1,8)
hasLocation(#20003,#20004)
#20005=*
lines(#20005,#20001,"new a?.();","")
#20006=@"loc,{#10000},1,1,1,10"
locations_default(#20006,#10000,1,1,1,10)
hasLocation(#20005,#20006)
numlines(#20001,1,0,0)
numlines(#10000,1,0,0)
filetype(#10000,"javascript")

View File

@@ -0,0 +1,26 @@
#10000=@"/optional-chaining_bad2.js;sourcefile"
files(#10000,"/optional-chaining_bad2.js","optional-chaining_bad2","js",0)
#10001=@"/;folder"
folders(#10001,"/","")
containerparent(#10001,#10000)
#10002=@"loc,{#10000},0,0,0,0"
locations_default(#10002,#10000,0,0,0,0)
hasLocation(#10000,#10002)
#20000=@"global_scope"
scopes(#20000,0)
#20001=@"script;{#10000},1,1"
toplevels(#20001,0)
#20002=@"loc,{#10000},1,1,1,1"
locations_default(#20002,#10000,1,1,1,1)
hasLocation(#20001,#20002)
#20003=*
jsParseErrors(#20003,#20001,"Error: An optional chain may not be used in a tagged template expression.","a?.`{b}`;")
hasLocation(#20003,#20002)
#20004=*
lines(#20004,#20001,"a?.`{b}`;","")
#20005=@"loc,{#10000},1,1,1,9"
locations_default(#20005,#10000,1,1,1,9)
hasLocation(#20004,#20005)
numlines(#20001,1,0,0)
numlines(#10000,1,0,0)
filetype(#10000,"javascript")

View File

@@ -0,0 +1,28 @@
#10000=@"/optional-chaining_bad3.js;sourcefile"
files(#10000,"/optional-chaining_bad3.js","optional-chaining_bad3","js",0)
#10001=@"/;folder"
folders(#10001,"/","")
containerparent(#10001,#10000)
#10002=@"loc,{#10000},0,0,0,0"
locations_default(#10002,#10000,0,0,0,0)
hasLocation(#10000,#10002)
#20000=@"global_scope"
scopes(#20000,0)
#20001=@"script;{#10000},1,1"
toplevels(#20001,0)
#20002=@"loc,{#10000},1,1,1,1"
locations_default(#20002,#10000,1,1,1,1)
hasLocation(#20001,#20002)
#20003=*
jsParseErrors(#20003,#20001,"Error: An optional chain may not be used in a `new` expression.","new a?.b();")
#20004=@"loc,{#10000},1,5,1,5"
locations_default(#20004,#10000,1,5,1,5)
hasLocation(#20003,#20004)
#20005=*
lines(#20005,#20001,"new a?.b();","")
#20006=@"loc,{#10000},1,1,1,11"
locations_default(#20006,#10000,1,1,1,11)
hasLocation(#20005,#20006)
numlines(#20001,1,0,0)
numlines(#10000,1,0,0)
filetype(#10000,"javascript")

View File

@@ -0,0 +1,26 @@
#10000=@"/optional-chaining_bad4.js;sourcefile"
files(#10000,"/optional-chaining_bad4.js","optional-chaining_bad4","js",0)
#10001=@"/;folder"
folders(#10001,"/","")
containerparent(#10001,#10000)
#10002=@"loc,{#10000},0,0,0,0"
locations_default(#10002,#10000,0,0,0,0)
hasLocation(#10000,#10002)
#20000=@"global_scope"
scopes(#20000,0)
#20001=@"script;{#10000},1,1"
toplevels(#20001,0)
#20002=@"loc,{#10000},1,1,1,1"
locations_default(#20002,#10000,1,1,1,1)
hasLocation(#20001,#20002)
#20003=*
jsParseErrors(#20003,#20001,"Error: An optional chain may not be used in a tagged template expression.","a?.b`{c}`;")
hasLocation(#20003,#20002)
#20004=*
lines(#20004,#20001,"a?.b`{c}`;","")
#20005=@"loc,{#10000},1,1,1,10"
locations_default(#20005,#10000,1,1,1,10)
hasLocation(#20004,#20005)
numlines(#20001,1,0,0)
numlines(#10000,1,0,0)
filetype(#10000,"javascript")

View File

@@ -0,0 +1,26 @@
#10000=@"/optional-chaining_bad5.js;sourcefile"
files(#10000,"/optional-chaining_bad5.js","optional-chaining_bad5","js",0)
#10001=@"/;folder"
folders(#10001,"/","")
containerparent(#10001,#10000)
#10002=@"loc,{#10000},0,0,0,0"
locations_default(#10002,#10000,0,0,0,0)
hasLocation(#10000,#10002)
#20000=@"global_scope"
scopes(#20000,0)
#20001=@"script;{#10000},1,1"
toplevels(#20001,0)
#20002=@"loc,{#10000},1,1,1,1"
locations_default(#20002,#10000,1,1,1,1)
hasLocation(#20001,#20002)
#20003=*
jsParseErrors(#20003,#20001,"Error: Invalid left-hand side in assignment","a?.b = c;")
hasLocation(#20003,#20002)
#20004=*
lines(#20004,#20001,"a?.b = c;","")
#20005=@"loc,{#10000},1,1,1,9"
locations_default(#20005,#10000,1,1,1,9)
hasLocation(#20004,#20005)
numlines(#20001,1,0,0)
numlines(#10000,1,0,0)
filetype(#10000,"javascript")

View File

@@ -0,0 +1,30 @@
#10000=@"/optional-chaining_bad6.js;sourcefile"
files(#10000,"/optional-chaining_bad6.js","optional-chaining_bad6","js",0)
#10001=@"/;folder"
folders(#10001,"/","")
containerparent(#10001,#10000)
#10002=@"loc,{#10000},0,0,0,0"
locations_default(#10002,#10000,0,0,0,0)
hasLocation(#10000,#10002)
#20000=@"global_scope"
scopes(#20000,0)
#20001=@"script;{#10000},1,1"
toplevels(#20001,0)
#20002=@"loc,{#10000},1,1,1,1"
locations_default(#20002,#10000,1,1,1,1)
hasLocation(#20001,#20002)
#20003=*
jsParseErrors(#20003,#20001,"Error: An optional chain may not be used in a `new` expression.","new a.b?.c();
")
#20004=@"loc,{#10000},1,5,1,5"
locations_default(#20004,#10000,1,5,1,5)
hasLocation(#20003,#20004)
#20005=*
lines(#20005,#20001,"new a.b?.c();","
")
#20006=@"loc,{#10000},1,1,1,13"
locations_default(#20006,#10000,1,1,1,13)
hasLocation(#20005,#20006)
numlines(#20001,1,0,0)
numlines(#10000,1,0,0)
filetype(#10000,"javascript")

View File

@@ -1077,4 +1077,8 @@ xmllocations(
@dataflownode = @expr | @functiondeclstmt | @classdeclstmt | @namespacedeclaration | @enumdeclaration | @property; @dataflownode = @expr | @functiondeclstmt | @classdeclstmt | @namespacedeclaration | @enumdeclaration | @property;
/* Last updated 2017/07/11. */ @optionalchainable = @callexpr | @propaccess;
isOptionalChaining(int id: @optionalchainable ref);
/* Last updated 2018/10/22. */

View File

@@ -1393,6 +1393,10 @@
<k>@xmlcharacters</k> <k>@xmlcharacters</k>
<v>439958</v> <v>439958</v>
</e> </e>
<e>
<k>@optionalchainable</k>
<v>100</v>
</e>
</typesizes> </typesizes>
<stats> <stats>
<relation> <relation>
@@ -19773,6 +19777,18 @@
</columnsizes> </columnsizes>
<dependencies/> <dependencies/>
</relation> </relation>
<relation>
<name>isOptionalChaining</name>
<cardinality>100</cardinality>
<columnsizes>
<e>
<k>id</k>
<v>100</v>
</e>
</columnsizes>
<dependencies/>
</relation>
<relation> <relation>
<name>rangeQuantifierLowerBound</name> <name>rangeQuantifierLowerBound</name>
<cardinality>146</cardinality> <cardinality>146</cardinality>

File diff suppressed because it is too large Load Diff

View File

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