Fix regression with let (#9477)

* Fix corner cases with let

* Handle generators correctly

* Fix flow plugin

* Fix typescript plugin
This commit is contained in:
Daniel Tschinder
2019-02-08 13:36:37 -08:00
committed by GitHub
parent 7943a48cc3
commit 2817844e89
20 changed files with 728 additions and 242 deletions

View File

@@ -76,7 +76,7 @@ export default class StatementParser extends ExpressionParser {
return this.finishNode(node, "InterpreterDirective");
}
isLet(declaration?: boolean): boolean {
isLet(context: ?string): boolean {
if (!this.isContextual("let")) {
return false;
}
@@ -85,20 +85,16 @@ export default class StatementParser extends ExpressionParser {
// $FlowIgnore
const next = this.state.pos + skip[0].length;
const nextCh = this.state.input.charCodeAt(next);
if (
(nextCh === charCodes.leftCurlyBrace &&
!lineBreak.test(this.state.input.slice(this.state.end, next))) ||
nextCh === charCodes.leftSquareBracket
) {
return true;
}
// For ambiguous cases, determine if a LexicalDeclaration (or only a
// Statement) is allowed here. If context is not empty then only a Statement
// is allowed. However, `let [` is an explicit negative lookahead for
// ExpressionStatement, so special-case it first.
if (nextCh === charCodes.leftSquareBracket) return true;
if (context) return false;
if (nextCh === charCodes.leftCurlyBrace) return true;
if (isIdentifierStart(nextCh)) {
if (
!declaration &&
lineBreak.test(this.state.input.slice(this.state.end, next))
) {
return false;
}
let pos = next + 1;
while (isIdentifierChar(this.state.input.charCodeAt(pos))) {
++pos;
@@ -116,19 +112,19 @@ export default class StatementParser extends ExpressionParser {
// `if (foo) /blah/.exec(foo)`, where looking at the previous token
// does not help.
parseStatement(declaration: boolean, topLevel?: boolean): N.Statement {
parseStatement(context: ?string, topLevel?: boolean): N.Statement {
if (this.match(tt.at)) {
this.parseDecorators(true);
}
return this.parseStatementContent(declaration, topLevel);
return this.parseStatementContent(context, topLevel);
}
parseStatementContent(declaration: boolean, topLevel: ?boolean): N.Statement {
parseStatementContent(context: ?string, topLevel: ?boolean): N.Statement {
let starttype = this.state.type;
const node = this.startNode();
let kind;
if (this.isLet(declaration)) {
if (this.isLet(context)) {
starttype = tt._var;
kind = "let";
}
@@ -148,18 +144,28 @@ export default class StatementParser extends ExpressionParser {
return this.parseDoStatement(node);
case tt._for:
return this.parseForStatement(node);
case tt._function:
case tt._function: {
if (this.lookahead().type === tt.dot) break;
if (!declaration) {
if (
context &&
(this.state.strict || (context !== "if" && context !== "label"))
) {
this.raise(
this.state.start,
"Function declaration not allowed in this context",
);
}
return this.parseFunctionStatement(node);
const result = this.parseFunctionStatement(node);
// TODO: Remove this once we have proper scope tracking in place.
if (context && result.generator) {
this.unexpected(node.start);
}
return result;
}
case tt._class:
if (!declaration) this.unexpected();
if (context) this.unexpected();
return this.parseClass(node, true);
case tt._if:
@@ -176,7 +182,7 @@ export default class StatementParser extends ExpressionParser {
case tt._const:
case tt._var:
kind = kind || this.state.value;
if (!declaration && kind !== "var") this.unexpected();
if (context && kind !== "var") this.unexpected();
return this.parseVarStatement(node, kind);
case tt._while:
@@ -237,7 +243,7 @@ export default class StatementParser extends ExpressionParser {
const state = this.state.clone();
this.next();
if (this.match(tt._function) && !this.canInsertSemicolon()) {
if (!declaration) {
if (context) {
this.raise(
this.state.lastTokStart,
"Function declaration not allowed in this context",
@@ -264,7 +270,7 @@ export default class StatementParser extends ExpressionParser {
expr.type === "Identifier" &&
this.eat(tt.colon)
) {
return this.parseLabeledStatement(node, maybeName, expr, declaration);
return this.parseLabeledStatement(node, maybeName, expr, context);
} else {
return this.parseExpressionStatement(node, expr);
}
@@ -430,7 +436,7 @@ export default class StatementParser extends ExpressionParser {
// outside of the loop body.
this.withTopicForbiddingContext(() =>
// Parse the loop body's body.
this.parseStatement(false),
this.parseStatement("do"),
);
this.state.labels.pop();
@@ -526,8 +532,8 @@ export default class StatementParser extends ExpressionParser {
parseIfStatement(node: N.IfStatement): N.IfStatement {
this.next();
node.test = this.parseParenExpression();
node.consequent = this.parseStatement(false);
node.alternate = this.eat(tt._else) ? this.parseStatement(false) : null;
node.consequent = this.parseStatement("if");
node.alternate = this.eat(tt._else) ? this.parseStatement("if") : null;
return this.finishNode(node, "IfStatement");
}
@@ -583,7 +589,7 @@ export default class StatementParser extends ExpressionParser {
this.expect(tt.colon);
} else {
if (cur) {
cur.consequent.push(this.parseStatement(true));
cur.consequent.push(this.parseStatement(null));
} else {
this.unexpected();
}
@@ -672,7 +678,7 @@ export default class StatementParser extends ExpressionParser {
// They are permitted in test expressions, outside of the loop body.
this.withTopicForbiddingContext(() =>
// Parse loop body.
this.parseStatement(false),
this.parseStatement("while"),
);
this.state.labels.pop();
@@ -694,7 +700,7 @@ export default class StatementParser extends ExpressionParser {
// part of the outer context, outside of the function body.
this.withTopicForbiddingContext(() =>
// Parse the statement body.
this.parseStatement(false),
this.parseStatement("with"),
);
return this.finishNode(node, "WithStatement");
@@ -709,7 +715,7 @@ export default class StatementParser extends ExpressionParser {
node: N.LabeledStatement,
maybeName: string,
expr: N.Identifier,
declaration: boolean,
context: ?string,
): N.LabeledStatement {
for (const label of this.state.labels) {
if (label.name === maybeName) {
@@ -737,16 +743,13 @@ export default class StatementParser extends ExpressionParser {
kind: kind,
statementStart: this.state.start,
});
node.body = this.parseStatement(declaration);
if (
node.body.type === "ClassDeclaration" ||
(node.body.type === "VariableDeclaration" && node.body.kind !== "var") ||
(node.body.type === "FunctionDeclaration" &&
(this.state.strict || node.body.generator || node.body.async))
) {
this.raise(node.body.start, "Invalid labeled declaration");
}
node.body = this.parseStatement(
context
? context.indexOf("label") === -1
? context + "label"
: context
: "label",
);
this.state.labels.pop();
node.label = expr;
@@ -813,7 +816,7 @@ export default class StatementParser extends ExpressionParser {
octalPosition = this.state.octalPosition;
}
const stmt = this.parseStatement(true, topLevel);
const stmt = this.parseStatement(null, topLevel);
if (directives && !parsedNonDirective && this.isValidDirective(stmt)) {
const directive = this.stmtToDirective(stmt);
@@ -861,7 +864,7 @@ export default class StatementParser extends ExpressionParser {
// outside of the loop body.
this.withTopicForbiddingContext(() =>
// Parse the loop body.
this.parseStatement(false),
this.parseStatement("for"),
);
this.state.labels.pop();
@@ -896,7 +899,7 @@ export default class StatementParser extends ExpressionParser {
// They are permitted in test expressions, outside of the loop body.
this.withTopicForbiddingContext(() =>
// Parse loop body.
this.parseStatement(false),
this.parseStatement("for"),
);
this.state.labels.pop();
@@ -1700,7 +1703,7 @@ export default class StatementParser extends ExpressionParser {
// eslint-disable-next-line no-unused-vars
parseExportDeclaration(node: N.ExportNamedDeclaration): ?N.Declaration {
return this.parseStatement(true);
return this.parseStatement(null);
}
isExportDefaultSpecifier(): boolean {

View File

@@ -1566,7 +1566,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
}
// interfaces
parseStatement(declaration: boolean, topLevel?: boolean): N.Statement {
parseStatement(context: ?string, topLevel?: boolean): N.Statement {
// strict mode handling of `interface` since it's a reserved word
if (
this.state.strict &&
@@ -1577,7 +1577,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
this.next();
return this.flowParseInterface(node);
} else {
const stmt = super.parseStatement(declaration, topLevel);
const stmt = super.parseStatement(context, topLevel);
// We will parse a flow pragma in any comment before the first statement.
if (this.flowPragma === undefined && !this.isValidDirective(stmt)) {
this.flowPragma = null;

View File

@@ -1699,10 +1699,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
return super.parseExportDefaultExpression();
}
parseStatementContent(
declaration: boolean,
topLevel: ?boolean,
): N.Statement {
parseStatementContent(context: ?string, topLevel: ?boolean): N.Statement {
if (this.state.type === tt._const) {
const ahead = this.lookahead();
if (ahead.type === tt.name && ahead.value === "enum") {
@@ -1712,7 +1709,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
return this.tsParseEnumDeclaration(node, /* isConst */ true);
}
}
return super.parseStatementContent(declaration, topLevel);
return super.parseStatementContent(context, topLevel);
}
parseAccessModifier(): ?N.Accessibility {