Skip to content

Commit

Permalink
Fix regression with let (babel#9477)
Browse files Browse the repository at this point in the history
* Fix corner cases with let

* Handle generators correctly

* Fix flow plugin

* Fix typescript plugin
  • Loading branch information
danez authored Feb 8, 2019
1 parent 7943a48 commit 2817844
Show file tree
Hide file tree
Showing 20 changed files with 728 additions and 242 deletions.
95 changes: 49 additions & 46 deletions packages/babel-parser/src/parser/statement.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand All @@ -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;
Expand All @@ -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";
}
Expand All @@ -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:
Expand All @@ -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:
Expand Down Expand Up @@ -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",
Expand All @@ -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);
}
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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");
}

Expand Down Expand Up @@ -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();
}
Expand Down Expand Up @@ -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();
Expand All @@ -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");
Expand All @@ -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) {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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 {
Expand Down
4 changes: 2 additions & 2 deletions packages/babel-parser/src/plugins/flow.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 &&
Expand All @@ -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;
Expand Down
7 changes: 2 additions & 5 deletions packages/babel-parser/src/plugins/typescript.js
Original file line number Diff line number Diff line change
Expand Up @@ -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") {
Expand All @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
if (false) {
L: let // ASI
{}
}
Loading

0 comments on commit 2817844

Please sign in to comment.