1699 lines
48 KiB
JavaScript
1699 lines
48 KiB
JavaScript
/* eslint max-len: 0 */
|
|
|
|
// @flow
|
|
|
|
import * as N from "../types";
|
|
import { types as tt, type TokenType } from "../tokenizer/types";
|
|
import ExpressionParser from "./expression";
|
|
import { lineBreak } from "../util/whitespace";
|
|
|
|
// Reused empty array added for node fields that are always empty.
|
|
|
|
const empty = [];
|
|
|
|
const loopLabel = { kind: "loop" },
|
|
switchLabel = { kind: "switch" };
|
|
|
|
export default class StatementParser extends ExpressionParser {
|
|
// ### Statement parsing
|
|
|
|
// Parse a program. Initializes the parser, reads any number of
|
|
// statements, and wraps them in a Program node. Optionally takes a
|
|
// `program` argument. If present, the statements will be appended
|
|
// to its body instead of creating a new node.
|
|
|
|
parseTopLevel(file: N.File, program: N.Program): N.File {
|
|
program.sourceType = this.options.sourceType;
|
|
|
|
this.parseBlockBody(program, true, true, tt.eof);
|
|
|
|
file.program = this.finishNode(program, "Program");
|
|
file.comments = this.state.comments;
|
|
|
|
if (this.options.tokens) file.tokens = this.state.tokens;
|
|
|
|
return this.finishNode(file, "File");
|
|
}
|
|
|
|
// TODO
|
|
|
|
stmtToDirective(stmt: N.Statement): N.Directive {
|
|
const expr = stmt.expression;
|
|
|
|
const directiveLiteral = this.startNodeAt(expr.start, expr.loc.start);
|
|
const directive = this.startNodeAt(stmt.start, stmt.loc.start);
|
|
|
|
const raw = this.input.slice(expr.start, expr.end);
|
|
const val = (directiveLiteral.value = raw.slice(1, -1)); // remove quotes
|
|
|
|
this.addExtra(directiveLiteral, "raw", raw);
|
|
this.addExtra(directiveLiteral, "rawValue", val);
|
|
|
|
directive.value = this.finishNodeAt(
|
|
directiveLiteral,
|
|
"DirectiveLiteral",
|
|
expr.end,
|
|
expr.loc.end,
|
|
);
|
|
|
|
return this.finishNodeAt(directive, "Directive", stmt.end, stmt.loc.end);
|
|
}
|
|
|
|
// Parse a single statement.
|
|
//
|
|
// If expecting a statement and finding a slash operator, parse a
|
|
// regular expression literal. This is to handle cases like
|
|
// `if (foo) /blah/.exec(foo)`, where looking at the previous token
|
|
// does not help.
|
|
|
|
parseStatement(declaration: boolean, topLevel?: boolean): N.Statement {
|
|
if (this.match(tt.at)) {
|
|
this.parseDecorators(true);
|
|
}
|
|
return this.parseStatementContent(declaration, topLevel);
|
|
}
|
|
|
|
parseStatementContent(declaration: boolean, topLevel: ?boolean): N.Statement {
|
|
const starttype = this.state.type;
|
|
const node = this.startNode();
|
|
|
|
// Most types of statements are recognized by the keyword they
|
|
// start with. Many are trivial to parse, some require a bit of
|
|
// complexity.
|
|
|
|
switch (starttype) {
|
|
case tt._break:
|
|
case tt._continue:
|
|
// $FlowFixMe
|
|
return this.parseBreakContinueStatement(node, starttype.keyword);
|
|
case tt._debugger:
|
|
return this.parseDebuggerStatement(node);
|
|
case tt._do:
|
|
return this.parseDoStatement(node);
|
|
case tt._for:
|
|
return this.parseForStatement(node);
|
|
case tt._function:
|
|
if (this.lookahead().type === tt.dot) break;
|
|
if (!declaration) this.unexpected();
|
|
return this.parseFunctionStatement(node);
|
|
|
|
case tt._class:
|
|
if (!declaration) this.unexpected();
|
|
return this.parseClass(node, true);
|
|
|
|
case tt._if:
|
|
return this.parseIfStatement(node);
|
|
case tt._return:
|
|
return this.parseReturnStatement(node);
|
|
case tt._switch:
|
|
return this.parseSwitchStatement(node);
|
|
case tt._throw:
|
|
return this.parseThrowStatement(node);
|
|
case tt._try:
|
|
return this.parseTryStatement(node);
|
|
|
|
case tt._let:
|
|
case tt._const:
|
|
if (!declaration) this.unexpected(); // NOTE: falls through to _var
|
|
|
|
case tt._var:
|
|
return this.parseVarStatement(node, starttype);
|
|
|
|
case tt._while:
|
|
return this.parseWhileStatement(node);
|
|
case tt._with:
|
|
return this.parseWithStatement(node);
|
|
case tt.braceL:
|
|
return this.parseBlock();
|
|
case tt.semi:
|
|
return this.parseEmptyStatement(node);
|
|
case tt._export:
|
|
case tt._import: {
|
|
if (
|
|
(this.hasPlugin("dynamicImport") &&
|
|
this.lookahead().type === tt.parenL) ||
|
|
(this.hasPlugin("importMeta") && this.lookahead().type === tt.dot)
|
|
) {
|
|
break;
|
|
}
|
|
|
|
if (!this.options.allowImportExportEverywhere && !topLevel) {
|
|
this.raise(
|
|
this.state.start,
|
|
"'import' and 'export' may only appear at the top level",
|
|
);
|
|
}
|
|
|
|
this.next();
|
|
|
|
let result;
|
|
if (starttype == tt._import) {
|
|
result = this.parseImport(node);
|
|
} else {
|
|
result = this.parseExport(node);
|
|
}
|
|
|
|
this.assertModuleNodeAllowed(node);
|
|
|
|
return result;
|
|
}
|
|
case tt.name:
|
|
if (this.state.value === "async") {
|
|
// peek ahead and see if next token is a function
|
|
const state = this.state.clone();
|
|
this.next();
|
|
if (this.match(tt._function) && !this.canInsertSemicolon()) {
|
|
this.expect(tt._function);
|
|
return this.parseFunction(node, true, false, true);
|
|
} else {
|
|
this.state = state;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If the statement does not start with a statement keyword or a
|
|
// brace, it's an ExpressionStatement or LabeledStatement. We
|
|
// simply start parsing an expression, and afterwards, if the
|
|
// next token is a colon and the expression was a simple
|
|
// Identifier node, we switch to interpreting it as a label.
|
|
const maybeName = this.state.value;
|
|
const expr = this.parseExpression();
|
|
|
|
if (
|
|
starttype === tt.name &&
|
|
expr.type === "Identifier" &&
|
|
this.eat(tt.colon)
|
|
) {
|
|
return this.parseLabeledStatement(node, maybeName, expr);
|
|
} else {
|
|
return this.parseExpressionStatement(node, expr);
|
|
}
|
|
}
|
|
|
|
assertModuleNodeAllowed(node: N.Node): void {
|
|
if (!this.options.allowImportExportEverywhere && !this.inModule) {
|
|
this.raise(
|
|
node.start,
|
|
`'import' and 'export' may appear only with 'sourceType: "module"'`,
|
|
);
|
|
}
|
|
}
|
|
|
|
takeDecorators(node: N.HasDecorators): void {
|
|
const decorators = this.state.decoratorStack[
|
|
this.state.decoratorStack.length - 1
|
|
];
|
|
if (decorators.length) {
|
|
node.decorators = decorators;
|
|
this.resetStartLocationFromNode(node, decorators[0]);
|
|
this.state.decoratorStack[this.state.decoratorStack.length - 1] = [];
|
|
}
|
|
}
|
|
|
|
parseDecorators(allowExport?: boolean): void {
|
|
if (this.hasPlugin("decorators2")) {
|
|
allowExport = false;
|
|
}
|
|
|
|
const currentContextDecorators = this.state.decoratorStack[
|
|
this.state.decoratorStack.length - 1
|
|
];
|
|
while (this.match(tt.at)) {
|
|
const decorator = this.parseDecorator();
|
|
currentContextDecorators.push(decorator);
|
|
}
|
|
|
|
if (this.match(tt._export)) {
|
|
if (allowExport) {
|
|
return;
|
|
} else {
|
|
this.raise(
|
|
this.state.start,
|
|
"Using the export keyword between a decorator and a class is not allowed. Please use `export @dec class` instead",
|
|
);
|
|
}
|
|
}
|
|
|
|
if (!this.match(tt._class)) {
|
|
this.raise(
|
|
this.state.start,
|
|
"Leading decorators must be attached to a class declaration",
|
|
);
|
|
}
|
|
}
|
|
|
|
parseDecorator(): N.Decorator {
|
|
this.expectOnePlugin(["decorators", "decorators2"]);
|
|
|
|
const node = this.startNode();
|
|
this.next();
|
|
|
|
if (this.hasPlugin("decorators2")) {
|
|
const startPos = this.state.start;
|
|
const startLoc = this.state.startLoc;
|
|
let expr = this.parseIdentifier(false);
|
|
|
|
while (this.eat(tt.dot)) {
|
|
const node = this.startNodeAt(startPos, startLoc);
|
|
node.object = expr;
|
|
node.property = this.parseIdentifier(true);
|
|
node.computed = false;
|
|
expr = this.finishNode(node, "MemberExpression");
|
|
}
|
|
|
|
if (this.eat(tt.parenL)) {
|
|
const node = this.startNodeAt(startPos, startLoc);
|
|
node.callee = expr;
|
|
// Every time a decorator class expression is evaluated, a new empty array is pushed onto the stack
|
|
// So that the decorators of any nested class expressions will be dealt with separately
|
|
this.state.decoratorStack.push([]);
|
|
node.arguments = this.parseCallExpressionArguments(tt.parenR, false);
|
|
this.state.decoratorStack.pop();
|
|
expr = this.finishNode(node, "CallExpression");
|
|
this.toReferencedList(expr.arguments);
|
|
}
|
|
|
|
node.expression = expr;
|
|
} else {
|
|
node.expression = this.parseMaybeAssign();
|
|
}
|
|
return this.finishNode(node, "Decorator");
|
|
}
|
|
|
|
parseBreakContinueStatement(
|
|
node: N.BreakStatement | N.ContinueStatement,
|
|
keyword: string,
|
|
): N.BreakStatement | N.ContinueStatement {
|
|
const isBreak = keyword === "break";
|
|
this.next();
|
|
|
|
if (this.isLineTerminator()) {
|
|
node.label = null;
|
|
} else if (!this.match(tt.name)) {
|
|
this.unexpected();
|
|
} else {
|
|
node.label = this.parseIdentifier();
|
|
this.semicolon();
|
|
}
|
|
|
|
// Verify that there is an actual destination to break or
|
|
// continue to.
|
|
let i;
|
|
for (i = 0; i < this.state.labels.length; ++i) {
|
|
const lab = this.state.labels[i];
|
|
if (node.label == null || lab.name === node.label.name) {
|
|
if (lab.kind != null && (isBreak || lab.kind === "loop")) break;
|
|
if (node.label && isBreak) break;
|
|
}
|
|
}
|
|
if (i === this.state.labels.length) {
|
|
this.raise(node.start, "Unsyntactic " + keyword);
|
|
}
|
|
return this.finishNode(
|
|
node,
|
|
isBreak ? "BreakStatement" : "ContinueStatement",
|
|
);
|
|
}
|
|
|
|
parseDebuggerStatement(node: N.DebuggerStatement): N.DebuggerStatement {
|
|
this.next();
|
|
this.semicolon();
|
|
return this.finishNode(node, "DebuggerStatement");
|
|
}
|
|
|
|
parseDoStatement(node: N.DoWhileStatement): N.DoWhileStatement {
|
|
this.next();
|
|
this.state.labels.push(loopLabel);
|
|
node.body = this.parseStatement(false);
|
|
this.state.labels.pop();
|
|
this.expect(tt._while);
|
|
node.test = this.parseParenExpression();
|
|
this.eat(tt.semi);
|
|
return this.finishNode(node, "DoWhileStatement");
|
|
}
|
|
|
|
// Disambiguating between a `for` and a `for`/`in` or `for`/`of`
|
|
// loop is non-trivial. Basically, we have to parse the init `var`
|
|
// statement or expression, disallowing the `in` operator (see
|
|
// the second parameter to `parseExpression`), and then check
|
|
// whether the next token is `in` or `of`. When there is no init
|
|
// part (semicolon immediately after the opening parenthesis), it
|
|
// is a regular `for` loop.
|
|
|
|
parseForStatement(node: N.Node): N.ForLike {
|
|
this.next();
|
|
this.state.labels.push(loopLabel);
|
|
|
|
let forAwait = false;
|
|
if (this.state.inAsync && this.isContextual("await")) {
|
|
this.expectPlugin("asyncGenerators");
|
|
forAwait = true;
|
|
this.next();
|
|
}
|
|
this.expect(tt.parenL);
|
|
|
|
if (this.match(tt.semi)) {
|
|
if (forAwait) {
|
|
this.unexpected();
|
|
}
|
|
return this.parseFor(node, null);
|
|
}
|
|
|
|
if (this.match(tt._var) || this.match(tt._let) || this.match(tt._const)) {
|
|
const init = this.startNode();
|
|
const varKind = this.state.type;
|
|
this.next();
|
|
this.parseVar(init, true, varKind);
|
|
this.finishNode(init, "VariableDeclaration");
|
|
|
|
if (this.match(tt._in) || this.isContextual("of")) {
|
|
if (init.declarations.length === 1 && !init.declarations[0].init) {
|
|
return this.parseForIn(node, init, forAwait);
|
|
}
|
|
}
|
|
if (forAwait) {
|
|
this.unexpected();
|
|
}
|
|
return this.parseFor(node, init);
|
|
}
|
|
|
|
const refShorthandDefaultPos = { start: 0 };
|
|
const init = this.parseExpression(true, refShorthandDefaultPos);
|
|
if (this.match(tt._in) || this.isContextual("of")) {
|
|
const description = this.isContextual("of")
|
|
? "for-of statement"
|
|
: "for-in statement";
|
|
this.toAssignable(init, undefined, description);
|
|
this.checkLVal(init, undefined, undefined, description);
|
|
return this.parseForIn(node, init, forAwait);
|
|
} else if (refShorthandDefaultPos.start) {
|
|
this.unexpected(refShorthandDefaultPos.start);
|
|
}
|
|
if (forAwait) {
|
|
this.unexpected();
|
|
}
|
|
return this.parseFor(node, init);
|
|
}
|
|
|
|
parseFunctionStatement(node: N.FunctionDeclaration): N.FunctionDeclaration {
|
|
this.next();
|
|
return this.parseFunction(node, true);
|
|
}
|
|
|
|
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;
|
|
return this.finishNode(node, "IfStatement");
|
|
}
|
|
|
|
parseReturnStatement(node: N.ReturnStatement): N.ReturnStatement {
|
|
if (!this.state.inFunction && !this.options.allowReturnOutsideFunction) {
|
|
this.raise(this.state.start, "'return' outside of function");
|
|
}
|
|
|
|
this.next();
|
|
|
|
// In `return` (and `break`/`continue`), the keywords with
|
|
// optional arguments, we eagerly look for a semicolon or the
|
|
// possibility to insert one.
|
|
|
|
if (this.isLineTerminator()) {
|
|
node.argument = null;
|
|
} else {
|
|
node.argument = this.parseExpression();
|
|
this.semicolon();
|
|
}
|
|
|
|
return this.finishNode(node, "ReturnStatement");
|
|
}
|
|
|
|
parseSwitchStatement(node: N.SwitchStatement): N.SwitchStatement {
|
|
this.next();
|
|
node.discriminant = this.parseParenExpression();
|
|
const cases = (node.cases = []);
|
|
this.expect(tt.braceL);
|
|
this.state.labels.push(switchLabel);
|
|
|
|
// Statements under must be grouped (by label) in SwitchCase
|
|
// nodes. `cur` is used to keep the node that we are currently
|
|
// adding statements to.
|
|
|
|
let cur;
|
|
for (let sawDefault; !this.match(tt.braceR); ) {
|
|
if (this.match(tt._case) || this.match(tt._default)) {
|
|
const isCase = this.match(tt._case);
|
|
if (cur) this.finishNode(cur, "SwitchCase");
|
|
cases.push((cur = this.startNode()));
|
|
cur.consequent = [];
|
|
this.next();
|
|
if (isCase) {
|
|
cur.test = this.parseExpression();
|
|
} else {
|
|
if (sawDefault) {
|
|
this.raise(this.state.lastTokStart, "Multiple default clauses");
|
|
}
|
|
sawDefault = true;
|
|
cur.test = null;
|
|
}
|
|
this.expect(tt.colon);
|
|
} else {
|
|
if (cur) {
|
|
cur.consequent.push(this.parseStatement(true));
|
|
} else {
|
|
this.unexpected();
|
|
}
|
|
}
|
|
}
|
|
if (cur) this.finishNode(cur, "SwitchCase");
|
|
this.next(); // Closing brace
|
|
this.state.labels.pop();
|
|
return this.finishNode(node, "SwitchStatement");
|
|
}
|
|
|
|
parseThrowStatement(node: N.ThrowStatement): N.ThrowStatement {
|
|
this.next();
|
|
if (
|
|
lineBreak.test(this.input.slice(this.state.lastTokEnd, this.state.start))
|
|
) {
|
|
this.raise(this.state.lastTokEnd, "Illegal newline after throw");
|
|
}
|
|
node.argument = this.parseExpression();
|
|
this.semicolon();
|
|
return this.finishNode(node, "ThrowStatement");
|
|
}
|
|
|
|
parseTryStatement(node: N.TryStatement): N.TryStatement {
|
|
this.next();
|
|
|
|
node.block = this.parseBlock();
|
|
node.handler = null;
|
|
|
|
if (this.match(tt._catch)) {
|
|
const clause = this.startNode();
|
|
this.next();
|
|
if (this.match(tt.parenL)) {
|
|
this.expect(tt.parenL);
|
|
clause.param = this.parseBindingAtom();
|
|
const clashes: any = Object.create(null);
|
|
this.checkLVal(clause.param, true, clashes, "catch clause");
|
|
this.expect(tt.parenR);
|
|
} else {
|
|
this.expectPlugin("optionalCatchBinding");
|
|
clause.param = null;
|
|
}
|
|
clause.body = this.parseBlock();
|
|
node.handler = this.finishNode(clause, "CatchClause");
|
|
}
|
|
|
|
node.guardedHandlers = empty;
|
|
node.finalizer = this.eat(tt._finally) ? this.parseBlock() : null;
|
|
|
|
if (!node.handler && !node.finalizer) {
|
|
this.raise(node.start, "Missing catch or finally clause");
|
|
}
|
|
|
|
return this.finishNode(node, "TryStatement");
|
|
}
|
|
|
|
parseVarStatement(
|
|
node: N.VariableDeclaration,
|
|
kind: TokenType,
|
|
): N.VariableDeclaration {
|
|
this.next();
|
|
this.parseVar(node, false, kind);
|
|
this.semicolon();
|
|
return this.finishNode(node, "VariableDeclaration");
|
|
}
|
|
|
|
parseWhileStatement(node: N.WhileStatement): N.WhileStatement {
|
|
this.next();
|
|
node.test = this.parseParenExpression();
|
|
this.state.labels.push(loopLabel);
|
|
node.body = this.parseStatement(false);
|
|
this.state.labels.pop();
|
|
return this.finishNode(node, "WhileStatement");
|
|
}
|
|
|
|
parseWithStatement(node: N.WithStatement): N.WithStatement {
|
|
if (this.state.strict) {
|
|
this.raise(this.state.start, "'with' in strict mode");
|
|
}
|
|
this.next();
|
|
node.object = this.parseParenExpression();
|
|
node.body = this.parseStatement(false);
|
|
return this.finishNode(node, "WithStatement");
|
|
}
|
|
|
|
parseEmptyStatement(node: N.EmptyStatement): N.EmptyStatement {
|
|
this.next();
|
|
return this.finishNode(node, "EmptyStatement");
|
|
}
|
|
|
|
parseLabeledStatement(
|
|
node: N.LabeledStatement,
|
|
maybeName: string,
|
|
expr: N.Identifier,
|
|
): N.LabeledStatement {
|
|
for (const label of this.state.labels) {
|
|
if (label.name === maybeName) {
|
|
this.raise(expr.start, `Label '${maybeName}' is already declared`);
|
|
}
|
|
}
|
|
|
|
const kind = this.state.type.isLoop
|
|
? "loop"
|
|
: this.match(tt._switch) ? "switch" : null;
|
|
for (let i = this.state.labels.length - 1; i >= 0; i--) {
|
|
const label = this.state.labels[i];
|
|
if (label.statementStart === node.start) {
|
|
label.statementStart = this.state.start;
|
|
label.kind = kind;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
this.state.labels.push({
|
|
name: maybeName,
|
|
kind: kind,
|
|
statementStart: this.state.start,
|
|
});
|
|
node.body = this.parseStatement(true);
|
|
|
|
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");
|
|
}
|
|
|
|
this.state.labels.pop();
|
|
node.label = expr;
|
|
return this.finishNode(node, "LabeledStatement");
|
|
}
|
|
|
|
parseExpressionStatement(
|
|
node: N.ExpressionStatement,
|
|
expr: N.Expression,
|
|
): N.ExpressionStatement {
|
|
node.expression = expr;
|
|
this.semicolon();
|
|
return this.finishNode(node, "ExpressionStatement");
|
|
}
|
|
|
|
// Parse a semicolon-enclosed block of statements, handling `"use
|
|
// strict"` declarations when `allowStrict` is true (used for
|
|
// function bodies).
|
|
|
|
parseBlock(allowDirectives?: boolean): N.BlockStatement {
|
|
const node = this.startNode();
|
|
this.expect(tt.braceL);
|
|
this.parseBlockBody(node, allowDirectives, false, tt.braceR);
|
|
return this.finishNode(node, "BlockStatement");
|
|
}
|
|
|
|
isValidDirective(stmt: N.Statement): boolean {
|
|
return (
|
|
stmt.type === "ExpressionStatement" &&
|
|
stmt.expression.type === "StringLiteral" &&
|
|
!stmt.expression.extra.parenthesized
|
|
);
|
|
}
|
|
|
|
parseBlockBody(
|
|
node: N.BlockStatementLike,
|
|
allowDirectives: ?boolean,
|
|
topLevel: boolean,
|
|
end: TokenType,
|
|
): void {
|
|
const body = (node.body = []);
|
|
const directives = (node.directives = []);
|
|
this.parseBlockOrModuleBlockBody(
|
|
body,
|
|
allowDirectives ? directives : undefined,
|
|
topLevel,
|
|
end,
|
|
);
|
|
}
|
|
|
|
// Undefined directives means that directives are not allowed.
|
|
parseBlockOrModuleBlockBody(
|
|
body: N.Statement[],
|
|
directives: ?(N.Directive[]),
|
|
topLevel: boolean,
|
|
end: TokenType,
|
|
): void {
|
|
let parsedNonDirective = false;
|
|
let oldStrict;
|
|
let octalPosition;
|
|
|
|
while (!this.eat(end)) {
|
|
if (!parsedNonDirective && this.state.containsOctal && !octalPosition) {
|
|
octalPosition = this.state.octalPosition;
|
|
}
|
|
|
|
const stmt = this.parseStatement(true, topLevel);
|
|
|
|
if (directives && !parsedNonDirective && this.isValidDirective(stmt)) {
|
|
const directive = this.stmtToDirective(stmt);
|
|
directives.push(directive);
|
|
|
|
if (oldStrict === undefined && directive.value.value === "use strict") {
|
|
oldStrict = this.state.strict;
|
|
this.setStrict(true);
|
|
|
|
if (octalPosition) {
|
|
this.raise(octalPosition, "Octal literal in strict mode");
|
|
}
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
parsedNonDirective = true;
|
|
body.push(stmt);
|
|
}
|
|
|
|
if (oldStrict === false) {
|
|
this.setStrict(false);
|
|
}
|
|
}
|
|
|
|
// Parse a regular `for` loop. The disambiguation code in
|
|
// `parseStatement` will already have parsed the init statement or
|
|
// expression.
|
|
|
|
parseFor(
|
|
node: N.ForStatement,
|
|
init: ?(N.VariableDeclaration | N.Expression),
|
|
): N.ForStatement {
|
|
node.init = init;
|
|
this.expect(tt.semi);
|
|
node.test = this.match(tt.semi) ? null : this.parseExpression();
|
|
this.expect(tt.semi);
|
|
node.update = this.match(tt.parenR) ? null : this.parseExpression();
|
|
this.expect(tt.parenR);
|
|
node.body = this.parseStatement(false);
|
|
this.state.labels.pop();
|
|
return this.finishNode(node, "ForStatement");
|
|
}
|
|
|
|
// Parse a `for`/`in` and `for`/`of` loop, which are almost
|
|
// same from parser's perspective.
|
|
|
|
parseForIn(
|
|
node: N.ForInOf,
|
|
init: N.VariableDeclaration,
|
|
forAwait: boolean,
|
|
): N.ForInOf {
|
|
const type = this.match(tt._in) ? "ForInStatement" : "ForOfStatement";
|
|
if (forAwait) {
|
|
this.eatContextual("of");
|
|
} else {
|
|
this.next();
|
|
}
|
|
if (type === "ForOfStatement") {
|
|
node.await = !!forAwait;
|
|
}
|
|
node.left = init;
|
|
node.right = this.parseExpression();
|
|
this.expect(tt.parenR);
|
|
node.body = this.parseStatement(false);
|
|
this.state.labels.pop();
|
|
return this.finishNode(node, type);
|
|
}
|
|
|
|
// Parse a list of variable declarations.
|
|
|
|
parseVar(
|
|
node: N.VariableDeclaration,
|
|
isFor: boolean,
|
|
kind: TokenType,
|
|
): N.VariableDeclaration {
|
|
const declarations = (node.declarations = []);
|
|
// $FlowFixMe
|
|
node.kind = kind.keyword;
|
|
for (;;) {
|
|
const decl = this.startNode();
|
|
this.parseVarHead(decl);
|
|
if (this.eat(tt.eq)) {
|
|
decl.init = this.parseMaybeAssign(isFor);
|
|
} else {
|
|
if (
|
|
kind === tt._const &&
|
|
!(this.match(tt._in) || this.isContextual("of"))
|
|
) {
|
|
// `const` with no initializer is allowed in TypeScript. It could be a declaration `const x: number;`.
|
|
if (!this.hasPlugin("typescript")) {
|
|
this.unexpected();
|
|
}
|
|
} else if (
|
|
decl.id.type !== "Identifier" &&
|
|
!(isFor && (this.match(tt._in) || this.isContextual("of")))
|
|
) {
|
|
this.raise(
|
|
this.state.lastTokEnd,
|
|
"Complex binding patterns require an initialization value",
|
|
);
|
|
}
|
|
decl.init = null;
|
|
}
|
|
declarations.push(this.finishNode(decl, "VariableDeclarator"));
|
|
if (!this.eat(tt.comma)) break;
|
|
}
|
|
return node;
|
|
}
|
|
|
|
parseVarHead(decl: N.VariableDeclarator): void {
|
|
decl.id = this.parseBindingAtom();
|
|
this.checkLVal(decl.id, true, undefined, "variable declaration");
|
|
}
|
|
|
|
// Parse a function declaration or literal (depending on the
|
|
// `isStatement` parameter).
|
|
|
|
parseFunction<T: N.NormalFunction>(
|
|
node: T,
|
|
isStatement: boolean,
|
|
allowExpressionBody?: boolean,
|
|
isAsync?: boolean,
|
|
optionalId?: boolean,
|
|
): T {
|
|
const oldInFunc = this.state.inFunction;
|
|
const oldInMethod = this.state.inMethod;
|
|
const oldInGenerator = this.state.inGenerator;
|
|
this.state.inFunction = true;
|
|
this.state.inMethod = false;
|
|
|
|
this.initFunction(node, isAsync);
|
|
|
|
if (this.match(tt.star)) {
|
|
if (node.async) {
|
|
this.expectPlugin("asyncGenerators");
|
|
}
|
|
node.generator = true;
|
|
this.next();
|
|
}
|
|
|
|
if (
|
|
isStatement &&
|
|
!optionalId &&
|
|
!this.match(tt.name) &&
|
|
!this.match(tt._yield)
|
|
) {
|
|
this.unexpected();
|
|
}
|
|
|
|
// When parsing function expression, the binding identifier is parsed
|
|
// according to the rules inside the function.
|
|
// e.g. (function* yield() {}) is invalid because "yield" is disallowed in
|
|
// generators.
|
|
// This isn't the case with function declarations: function* yield() {} is
|
|
// valid because yield is parsed as if it was outside the generator.
|
|
// Therefore, this.state.inGenerator is set before or after parsing the
|
|
// function id according to the "isStatement" parameter.
|
|
if (!isStatement) this.state.inGenerator = node.generator;
|
|
if (this.match(tt.name) || this.match(tt._yield)) {
|
|
node.id = this.parseBindingIdentifier();
|
|
}
|
|
if (isStatement) this.state.inGenerator = node.generator;
|
|
|
|
this.parseFunctionParams(node);
|
|
this.parseFunctionBodyAndFinish(
|
|
node,
|
|
isStatement ? "FunctionDeclaration" : "FunctionExpression",
|
|
allowExpressionBody,
|
|
);
|
|
|
|
this.state.inFunction = oldInFunc;
|
|
this.state.inMethod = oldInMethod;
|
|
this.state.inGenerator = oldInGenerator;
|
|
|
|
return node;
|
|
}
|
|
|
|
parseFunctionParams(node: N.Function, allowModifiers?: boolean): void {
|
|
const oldInParameters = this.state.inParameters;
|
|
this.state.inParameters = true;
|
|
|
|
this.expect(tt.parenL);
|
|
node.params = this.parseBindingList(
|
|
tt.parenR,
|
|
/* allowEmpty */ false,
|
|
allowModifiers,
|
|
);
|
|
|
|
this.state.inParameters = oldInParameters;
|
|
}
|
|
|
|
// Parse a class declaration or literal (depending on the
|
|
// `isStatement` parameter).
|
|
|
|
parseClass<T: N.Class>(
|
|
node: T,
|
|
isStatement: /* T === ClassDeclaration */ boolean,
|
|
optionalId?: boolean,
|
|
): T {
|
|
this.next();
|
|
this.takeDecorators(node);
|
|
this.parseClassId(node, isStatement, optionalId);
|
|
this.parseClassSuper(node);
|
|
this.parseClassBody(node);
|
|
return this.finishNode(
|
|
node,
|
|
isStatement ? "ClassDeclaration" : "ClassExpression",
|
|
);
|
|
}
|
|
|
|
isClassProperty(): boolean {
|
|
return this.match(tt.eq) || this.match(tt.semi) || this.match(tt.braceR);
|
|
}
|
|
|
|
isClassMethod(): boolean {
|
|
return this.match(tt.parenL);
|
|
}
|
|
|
|
isNonstaticConstructor(method: N.ClassMethod | N.ClassProperty): boolean {
|
|
return (
|
|
!method.computed &&
|
|
!method.static &&
|
|
(method.key.name === "constructor" || // Identifier
|
|
method.key.value === "constructor") // String literal
|
|
);
|
|
}
|
|
|
|
parseClassBody(node: N.Class): void {
|
|
// class bodies are implicitly strict
|
|
const oldStrict = this.state.strict;
|
|
this.state.strict = true;
|
|
this.state.classLevel++;
|
|
|
|
const state = { hadConstructor: false };
|
|
let decorators: N.Decorator[] = [];
|
|
const classBody: N.ClassBody = this.startNode();
|
|
|
|
classBody.body = [];
|
|
|
|
this.expect(tt.braceL);
|
|
|
|
while (!this.eat(tt.braceR)) {
|
|
if (this.eat(tt.semi)) {
|
|
if (decorators.length > 0) {
|
|
this.raise(
|
|
this.state.lastTokEnd,
|
|
"Decorators must not be followed by a semicolon",
|
|
);
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (this.match(tt.at)) {
|
|
decorators.push(this.parseDecorator());
|
|
continue;
|
|
}
|
|
|
|
const member = this.startNode();
|
|
|
|
// steal the decorators if there are any
|
|
if (decorators.length) {
|
|
member.decorators = decorators;
|
|
this.resetStartLocationFromNode(member, decorators[0]);
|
|
decorators = [];
|
|
}
|
|
|
|
this.parseClassMember(classBody, member, state);
|
|
|
|
if (
|
|
this.hasPlugin("decorators2") &&
|
|
["method", "get", "set"].indexOf(member.kind) === -1 &&
|
|
member.decorators &&
|
|
member.decorators.length > 0
|
|
) {
|
|
this.raise(
|
|
member.start,
|
|
"Stage 2 decorators may only be used with a class or a class method",
|
|
);
|
|
}
|
|
}
|
|
|
|
if (decorators.length) {
|
|
this.raise(
|
|
this.state.start,
|
|
"You have trailing decorators with no method",
|
|
);
|
|
}
|
|
|
|
node.body = this.finishNode(classBody, "ClassBody");
|
|
|
|
this.state.classLevel--;
|
|
this.state.strict = oldStrict;
|
|
}
|
|
|
|
parseClassMember(
|
|
classBody: N.ClassBody,
|
|
member: N.ClassMember,
|
|
state: { hadConstructor: boolean },
|
|
): void {
|
|
let isStatic = false;
|
|
if (this.match(tt.name) && this.state.value === "static") {
|
|
const key = this.parseIdentifier(true); // eats 'static'
|
|
if (this.isClassMethod()) {
|
|
const method: N.ClassMethod = (member: any);
|
|
|
|
// a method named 'static'
|
|
method.kind = "method";
|
|
method.computed = false;
|
|
method.key = key;
|
|
method.static = false;
|
|
this.pushClassMethod(
|
|
classBody,
|
|
method,
|
|
false,
|
|
false,
|
|
/* isConstructor */ false,
|
|
);
|
|
return;
|
|
} else if (this.isClassProperty()) {
|
|
const prop: N.ClassProperty = (member: any);
|
|
|
|
// a property named 'static'
|
|
prop.computed = false;
|
|
prop.key = key;
|
|
prop.static = false;
|
|
classBody.body.push(this.parseClassProperty(prop));
|
|
return;
|
|
}
|
|
// otherwise something static
|
|
isStatic = true;
|
|
}
|
|
|
|
this.parseClassMemberWithIsStatic(classBody, member, state, isStatic);
|
|
}
|
|
|
|
parseClassMemberWithIsStatic(
|
|
classBody: N.ClassBody,
|
|
member: N.ClassMember,
|
|
state: { hadConstructor: boolean },
|
|
isStatic: boolean,
|
|
) {
|
|
const publicMethod: $FlowSubtype<N.ClassMethod> = member;
|
|
const privateMethod: $FlowSubtype<N.ClassPrivateMethod> = member;
|
|
const publicProp: $FlowSubtype<N.ClassMethod> = member;
|
|
const privateProp: $FlowSubtype<N.ClassPrivateMethod> = member;
|
|
|
|
const method: typeof publicMethod | typeof privateMethod = publicMethod;
|
|
const publicMember: typeof publicMethod | typeof publicProp = publicMethod;
|
|
|
|
member.static = isStatic;
|
|
|
|
if (this.eat(tt.star)) {
|
|
// a generator
|
|
method.kind = "method";
|
|
this.parseClassPropertyName(method);
|
|
|
|
if (method.key.type === "PrivateName") {
|
|
// Private generator method
|
|
this.pushClassPrivateMethod(classBody, privateMethod, true, false);
|
|
return;
|
|
}
|
|
|
|
if (this.isNonstaticConstructor(publicMethod)) {
|
|
this.raise(publicMethod.key.start, "Constructor can't be a generator");
|
|
}
|
|
|
|
this.pushClassMethod(
|
|
classBody,
|
|
publicMethod,
|
|
true,
|
|
false,
|
|
/* isConstructor */ false,
|
|
);
|
|
|
|
return;
|
|
}
|
|
|
|
const key = this.parseClassPropertyName(member);
|
|
const isPrivate = key.type === "PrivateName";
|
|
// Check the key is not a computed expression or string literal.
|
|
const isSimple = key.type === "Identifier";
|
|
|
|
this.parsePostMemberNameModifiers(publicMember);
|
|
|
|
if (this.isClassMethod()) {
|
|
method.kind = "method";
|
|
|
|
if (isPrivate) {
|
|
this.pushClassPrivateMethod(classBody, privateMethod, false, false);
|
|
return;
|
|
}
|
|
|
|
// a normal method
|
|
const isConstructor = this.isNonstaticConstructor(publicMethod);
|
|
|
|
if (isConstructor) {
|
|
publicMethod.kind = "constructor";
|
|
|
|
if (publicMethod.decorators) {
|
|
this.raise(
|
|
publicMethod.start,
|
|
"You can't attach decorators to a class constructor",
|
|
);
|
|
}
|
|
|
|
// TypeScript allows multiple overloaded constructor declarations.
|
|
if (state.hadConstructor && !this.hasPlugin("typescript")) {
|
|
this.raise(key.start, "Duplicate constructor in the same class");
|
|
}
|
|
state.hadConstructor = true;
|
|
}
|
|
|
|
this.pushClassMethod(
|
|
classBody,
|
|
publicMethod,
|
|
false,
|
|
false,
|
|
isConstructor,
|
|
);
|
|
} else if (this.isClassProperty()) {
|
|
if (isPrivate) {
|
|
this.pushClassPrivateProperty(classBody, privateProp);
|
|
} else {
|
|
this.pushClassProperty(classBody, publicProp);
|
|
}
|
|
} else if (isSimple && key.name === "async" && !this.isLineTerminator()) {
|
|
// an async method
|
|
const isGenerator = this.match(tt.star);
|
|
if (isGenerator) {
|
|
this.expectPlugin("asyncGenerators");
|
|
this.next();
|
|
}
|
|
|
|
method.kind = "method";
|
|
// The so-called parsed name would have been "async": get the real name.
|
|
this.parseClassPropertyName(method);
|
|
|
|
if (method.key.type === "PrivateName") {
|
|
// private async method
|
|
this.pushClassPrivateMethod(
|
|
classBody,
|
|
privateMethod,
|
|
isGenerator,
|
|
true,
|
|
);
|
|
} else {
|
|
if (this.isNonstaticConstructor(publicMethod)) {
|
|
this.raise(
|
|
publicMethod.key.start,
|
|
"Constructor can't be an async function",
|
|
);
|
|
}
|
|
|
|
this.pushClassMethod(
|
|
classBody,
|
|
publicMethod,
|
|
isGenerator,
|
|
true,
|
|
/* isConstructor */ false,
|
|
);
|
|
}
|
|
} else if (
|
|
isSimple &&
|
|
(key.name === "get" || key.name === "set") &&
|
|
!(this.isLineTerminator() && this.match(tt.star))
|
|
) {
|
|
// `get\n*` is an uninitialized property named 'get' followed by a generator.
|
|
// a getter or setter
|
|
method.kind = key.name;
|
|
// The so-called parsed name would have been "get/set": get the real name.
|
|
this.parseClassPropertyName(publicMethod);
|
|
|
|
if (method.key.type === "PrivateName") {
|
|
// private getter/setter
|
|
this.pushClassPrivateMethod(classBody, privateMethod, false, false);
|
|
} else {
|
|
if (this.isNonstaticConstructor(publicMethod)) {
|
|
this.raise(
|
|
publicMethod.key.start,
|
|
"Constructor can't have get/set modifier",
|
|
);
|
|
}
|
|
this.pushClassMethod(
|
|
classBody,
|
|
publicMethod,
|
|
false,
|
|
false,
|
|
/* isConstructor */ false,
|
|
);
|
|
}
|
|
|
|
this.checkGetterSetterParamCount(publicMethod);
|
|
} else if (this.isLineTerminator()) {
|
|
// an uninitialized class property (due to ASI, since we don't otherwise recognize the next token)
|
|
if (isPrivate) {
|
|
this.pushClassPrivateProperty(classBody, privateProp);
|
|
} else {
|
|
this.pushClassProperty(classBody, publicProp);
|
|
}
|
|
} else {
|
|
this.unexpected();
|
|
}
|
|
}
|
|
|
|
parseClassPropertyName(member: N.ClassMember): N.Expression | N.Identifier {
|
|
const key = this.parsePropertyName(member);
|
|
|
|
if (
|
|
!member.computed &&
|
|
member.static &&
|
|
((key: $FlowSubtype<N.Identifier>).name === "prototype" ||
|
|
(key: $FlowSubtype<N.StringLiteral>).value === "prototype")
|
|
) {
|
|
this.raise(
|
|
key.start,
|
|
"Classes may not have static property named prototype",
|
|
);
|
|
}
|
|
|
|
if (key.type === "PrivateName" && key.id.name === "constructor") {
|
|
this.raise(
|
|
key.start,
|
|
"Classes may not have a private field named '#constructor'",
|
|
);
|
|
}
|
|
|
|
return key;
|
|
}
|
|
|
|
pushClassProperty(classBody: N.ClassBody, prop: N.ClassProperty) {
|
|
// This only affects properties, not methods.
|
|
if (this.isNonstaticConstructor(prop)) {
|
|
this.raise(
|
|
prop.key.start,
|
|
"Classes may not have a non-static field named 'constructor'",
|
|
);
|
|
}
|
|
classBody.body.push(this.parseClassProperty(prop));
|
|
}
|
|
|
|
pushClassPrivateProperty(
|
|
classBody: N.ClassBody,
|
|
prop: N.ClassPrivateProperty,
|
|
) {
|
|
this.expectPlugin("classPrivateProperties", prop.key.start);
|
|
classBody.body.push(this.parseClassPrivateProperty(prop));
|
|
}
|
|
|
|
pushClassMethod(
|
|
classBody: N.ClassBody,
|
|
method: N.ClassMethod,
|
|
isGenerator: boolean,
|
|
isAsync: boolean,
|
|
isConstructor: boolean,
|
|
): void {
|
|
classBody.body.push(
|
|
this.parseMethod(
|
|
method,
|
|
isGenerator,
|
|
isAsync,
|
|
isConstructor,
|
|
"ClassMethod",
|
|
),
|
|
);
|
|
}
|
|
|
|
pushClassPrivateMethod(
|
|
classBody: N.ClassBody,
|
|
method: N.ClassPrivateMethod,
|
|
isGenerator: boolean,
|
|
isAsync: boolean,
|
|
): void {
|
|
this.expectPlugin("classPrivateMethods", method.key.start);
|
|
classBody.body.push(
|
|
this.parseMethod(
|
|
method,
|
|
isGenerator,
|
|
isAsync,
|
|
/* isConstructor */ false,
|
|
"ClassPrivateMethod",
|
|
),
|
|
);
|
|
}
|
|
|
|
// Overridden in typescript.js
|
|
parsePostMemberNameModifiers(
|
|
// eslint-disable-next-line no-unused-vars
|
|
methodOrProp: N.ClassMethod | N.ClassProperty,
|
|
): void {}
|
|
|
|
// Overridden in typescript.js
|
|
parseAccessModifier(): ?N.Accessibility {
|
|
return undefined;
|
|
}
|
|
|
|
parseClassPrivateProperty(
|
|
node: N.ClassPrivateProperty,
|
|
): N.ClassPrivateProperty {
|
|
this.state.inClassProperty = true;
|
|
node.value = this.eat(tt.eq) ? this.parseMaybeAssign() : null;
|
|
this.semicolon();
|
|
this.state.inClassProperty = false;
|
|
return this.finishNode(node, "ClassPrivateProperty");
|
|
}
|
|
|
|
parseClassProperty(node: N.ClassProperty): N.ClassProperty {
|
|
if (!node.typeAnnotation) {
|
|
this.expectPlugin("classProperties");
|
|
}
|
|
|
|
this.state.inClassProperty = true;
|
|
|
|
if (this.match(tt.eq)) {
|
|
this.expectPlugin("classProperties");
|
|
this.next();
|
|
node.value = this.parseMaybeAssign();
|
|
} else {
|
|
node.value = null;
|
|
}
|
|
this.semicolon();
|
|
this.state.inClassProperty = false;
|
|
|
|
return this.finishNode(node, "ClassProperty");
|
|
}
|
|
|
|
parseClassId(
|
|
node: N.Class,
|
|
isStatement: boolean,
|
|
optionalId: ?boolean,
|
|
): void {
|
|
if (this.match(tt.name)) {
|
|
node.id = this.parseIdentifier();
|
|
} else {
|
|
if (optionalId || !isStatement) {
|
|
node.id = null;
|
|
} else {
|
|
this.unexpected(null, "A class name is required");
|
|
}
|
|
}
|
|
}
|
|
|
|
parseClassSuper(node: N.Class): void {
|
|
node.superClass = this.eat(tt._extends) ? this.parseExprSubscripts() : null;
|
|
}
|
|
|
|
// Parses module export declaration.
|
|
|
|
// TODO: better type. Node is an N.AnyExport.
|
|
parseExport(node: N.Node): N.Node {
|
|
// export * from '...'
|
|
if (this.shouldParseExportStar()) {
|
|
this.parseExportStar(node);
|
|
if (node.type === "ExportAllDeclaration") return node;
|
|
} else if (this.isExportDefaultSpecifier()) {
|
|
this.expectPlugin("exportDefaultFrom");
|
|
const specifier = this.startNode();
|
|
specifier.exported = this.parseIdentifier(true);
|
|
const specifiers = [this.finishNode(specifier, "ExportDefaultSpecifier")];
|
|
node.specifiers = specifiers;
|
|
if (this.match(tt.comma) && this.lookahead().type === tt.star) {
|
|
this.expect(tt.comma);
|
|
const specifier = this.startNode();
|
|
this.expect(tt.star);
|
|
this.expectContextual("as");
|
|
specifier.exported = this.parseIdentifier();
|
|
specifiers.push(this.finishNode(specifier, "ExportNamespaceSpecifier"));
|
|
} else {
|
|
this.parseExportSpecifiersMaybe(node);
|
|
}
|
|
this.parseExportFrom(node, true);
|
|
} else if (this.eat(tt._default)) {
|
|
// export default ...
|
|
let expr = this.startNode();
|
|
let needsSemi = false;
|
|
if (this.eat(tt._function)) {
|
|
expr = this.parseFunction(expr, true, false, false, true);
|
|
} else if (
|
|
this.isContextual("async") &&
|
|
this.lookahead().type === tt._function
|
|
) {
|
|
// async function declaration
|
|
this.eatContextual("async");
|
|
this.eat(tt._function);
|
|
expr = this.parseFunction(expr, true, false, true, true);
|
|
} else if (this.match(tt._class)) {
|
|
expr = this.parseClass(expr, true, true);
|
|
} else {
|
|
needsSemi = true;
|
|
expr = this.parseMaybeAssign();
|
|
}
|
|
node.declaration = expr;
|
|
if (needsSemi) this.semicolon();
|
|
this.checkExport(node, true, true);
|
|
return this.finishNode(node, "ExportDefaultDeclaration");
|
|
} else if (this.shouldParseExportDeclaration()) {
|
|
if (this.isContextual("async")) {
|
|
const next = this.lookahead();
|
|
|
|
// export async;
|
|
if (next.type !== tt._function) {
|
|
this.unexpected(next.start, `Unexpected token, expected "function"`);
|
|
}
|
|
}
|
|
|
|
node.specifiers = [];
|
|
node.source = null;
|
|
node.declaration = this.parseExportDeclaration(node);
|
|
} else {
|
|
// export { x, y as z } [from '...']
|
|
node.declaration = null;
|
|
node.specifiers = this.parseExportSpecifiers();
|
|
this.parseExportFrom(node);
|
|
}
|
|
this.checkExport(node, true);
|
|
return this.finishNode(node, "ExportNamedDeclaration");
|
|
}
|
|
|
|
// eslint-disable-next-line no-unused-vars
|
|
parseExportDeclaration(node: N.ExportNamedDeclaration): ?N.Declaration {
|
|
return this.parseStatement(true);
|
|
}
|
|
|
|
isExportDefaultSpecifier(): boolean {
|
|
if (this.match(tt.name)) {
|
|
return this.state.value !== "async";
|
|
}
|
|
|
|
if (!this.match(tt._default)) {
|
|
return false;
|
|
}
|
|
|
|
const lookahead = this.lookahead();
|
|
return (
|
|
lookahead.type === tt.comma ||
|
|
(lookahead.type === tt.name && lookahead.value === "from")
|
|
);
|
|
}
|
|
|
|
parseExportSpecifiersMaybe(node: N.ExportNamedDeclaration): void {
|
|
if (this.eat(tt.comma)) {
|
|
node.specifiers = node.specifiers.concat(this.parseExportSpecifiers());
|
|
}
|
|
}
|
|
|
|
parseExportFrom(node: N.ExportNamedDeclaration, expect?: boolean): void {
|
|
if (this.eatContextual("from")) {
|
|
node.source = this.match(tt.string)
|
|
? this.parseExprAtom()
|
|
: this.unexpected();
|
|
this.checkExport(node);
|
|
} else {
|
|
if (expect) {
|
|
this.unexpected();
|
|
} else {
|
|
node.source = null;
|
|
}
|
|
}
|
|
|
|
this.semicolon();
|
|
}
|
|
|
|
shouldParseExportStar(): boolean {
|
|
return this.match(tt.star);
|
|
}
|
|
|
|
parseExportStar(node: N.ExportNamedDeclaration): void {
|
|
this.expect(tt.star);
|
|
|
|
if (this.isContextual("as")) {
|
|
this.parseExportNamespace(node);
|
|
} else {
|
|
this.parseExportFrom(node, true);
|
|
this.finishNode(node, "ExportAllDeclaration");
|
|
}
|
|
}
|
|
|
|
parseExportNamespace(node: N.ExportNamedDeclaration): void {
|
|
this.expectPlugin("exportNamespaceFrom");
|
|
|
|
const specifier = this.startNodeAt(
|
|
this.state.lastTokStart,
|
|
this.state.lastTokStartLoc,
|
|
);
|
|
|
|
this.next();
|
|
|
|
specifier.exported = this.parseIdentifier(true);
|
|
|
|
node.specifiers = [this.finishNode(specifier, "ExportNamespaceSpecifier")];
|
|
|
|
this.parseExportSpecifiersMaybe(node);
|
|
this.parseExportFrom(node, true);
|
|
}
|
|
|
|
shouldParseExportDeclaration(): boolean {
|
|
return (
|
|
this.state.type.keyword === "var" ||
|
|
this.state.type.keyword === "const" ||
|
|
this.state.type.keyword === "let" ||
|
|
this.state.type.keyword === "function" ||
|
|
this.state.type.keyword === "class" ||
|
|
this.isContextual("async") ||
|
|
(this.match(tt.at) && this.expectPlugin("decorators2"))
|
|
);
|
|
}
|
|
|
|
checkExport(
|
|
node: N.ExportNamedDeclaration,
|
|
checkNames: ?boolean,
|
|
isDefault?: boolean,
|
|
): void {
|
|
if (checkNames) {
|
|
// Check for duplicate exports
|
|
if (isDefault) {
|
|
// Default exports
|
|
this.checkDuplicateExports(node, "default");
|
|
} else if (node.specifiers && node.specifiers.length) {
|
|
// Named exports
|
|
for (const specifier of node.specifiers) {
|
|
this.checkDuplicateExports(specifier, specifier.exported.name);
|
|
}
|
|
} else if (node.declaration) {
|
|
// Exported declarations
|
|
if (
|
|
node.declaration.type === "FunctionDeclaration" ||
|
|
node.declaration.type === "ClassDeclaration"
|
|
) {
|
|
this.checkDuplicateExports(node, node.declaration.id.name);
|
|
} else if (node.declaration.type === "VariableDeclaration") {
|
|
for (const declaration of node.declaration.declarations) {
|
|
this.checkDeclaration(declaration.id);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
const currentContextDecorators = this.state.decoratorStack[
|
|
this.state.decoratorStack.length - 1
|
|
];
|
|
if (currentContextDecorators.length) {
|
|
const isClass =
|
|
node.declaration &&
|
|
(node.declaration.type === "ClassDeclaration" ||
|
|
node.declaration.type === "ClassExpression");
|
|
if (!node.declaration || !isClass) {
|
|
throw this.raise(
|
|
node.start,
|
|
"You can only use decorators on an export when exporting a class",
|
|
);
|
|
}
|
|
this.takeDecorators(node.declaration);
|
|
}
|
|
}
|
|
|
|
checkDeclaration(node: N.Pattern | N.ObjectProperty): void {
|
|
if (node.type === "ObjectPattern") {
|
|
for (const prop of node.properties) {
|
|
this.checkDeclaration(prop);
|
|
}
|
|
} else if (node.type === "ArrayPattern") {
|
|
for (const elem of node.elements) {
|
|
if (elem) {
|
|
this.checkDeclaration(elem);
|
|
}
|
|
}
|
|
} else if (node.type === "ObjectProperty") {
|
|
this.checkDeclaration(node.value);
|
|
} else if (node.type === "RestElement") {
|
|
this.checkDeclaration(node.argument);
|
|
} else if (node.type === "Identifier") {
|
|
this.checkDuplicateExports(node, node.name);
|
|
}
|
|
}
|
|
|
|
checkDuplicateExports(
|
|
node: N.Identifier | N.ExportNamedDeclaration | N.ExportSpecifier,
|
|
name: string,
|
|
): void {
|
|
if (this.state.exportedIdentifiers.indexOf(name) > -1) {
|
|
this.raiseDuplicateExportError(node, name);
|
|
}
|
|
this.state.exportedIdentifiers.push(name);
|
|
}
|
|
|
|
raiseDuplicateExportError(
|
|
node: N.Identifier | N.ExportNamedDeclaration | N.ExportSpecifier,
|
|
name: string,
|
|
): empty {
|
|
throw this.raise(
|
|
node.start,
|
|
name === "default"
|
|
? "Only one default export allowed per module."
|
|
: `\`${name}\` has already been exported. Exported identifiers must be unique.`,
|
|
);
|
|
}
|
|
|
|
// Parses a comma-separated list of module exports.
|
|
|
|
parseExportSpecifiers(): Array<N.ExportSpecifier> {
|
|
const nodes = [];
|
|
let first = true;
|
|
let needsFrom;
|
|
|
|
// export { x, y as z } [from '...']
|
|
this.expect(tt.braceL);
|
|
|
|
while (!this.eat(tt.braceR)) {
|
|
if (first) {
|
|
first = false;
|
|
} else {
|
|
this.expect(tt.comma);
|
|
if (this.eat(tt.braceR)) break;
|
|
}
|
|
|
|
const isDefault = this.match(tt._default);
|
|
if (isDefault && !needsFrom) needsFrom = true;
|
|
|
|
const node = this.startNode();
|
|
node.local = this.parseIdentifier(isDefault);
|
|
node.exported = this.eatContextual("as")
|
|
? this.parseIdentifier(true)
|
|
: node.local.__clone();
|
|
nodes.push(this.finishNode(node, "ExportSpecifier"));
|
|
}
|
|
|
|
// https://github.com/ember-cli/ember-cli/pull/3739
|
|
if (needsFrom && !this.isContextual("from")) {
|
|
this.unexpected();
|
|
}
|
|
|
|
return nodes;
|
|
}
|
|
|
|
// Parses import declaration.
|
|
|
|
parseImport(node: N.Node): N.ImportDeclaration | N.TsImportEqualsDeclaration {
|
|
// import '...'
|
|
if (this.match(tt.string)) {
|
|
node.specifiers = [];
|
|
node.source = this.parseExprAtom();
|
|
} else {
|
|
node.specifiers = [];
|
|
this.parseImportSpecifiers(node);
|
|
this.expectContextual("from");
|
|
node.source = this.match(tt.string)
|
|
? this.parseExprAtom()
|
|
: this.unexpected();
|
|
}
|
|
this.semicolon();
|
|
return this.finishNode(node, "ImportDeclaration");
|
|
}
|
|
|
|
// eslint-disable-next-line no-unused-vars
|
|
shouldParseDefaultImport(node: N.ImportDeclaration): boolean {
|
|
return this.match(tt.name);
|
|
}
|
|
|
|
parseImportSpecifierLocal(
|
|
node: N.ImportDeclaration,
|
|
specifier: N.Node,
|
|
type: string,
|
|
contextDescription: string,
|
|
): void {
|
|
specifier.local = this.parseIdentifier();
|
|
this.checkLVal(specifier.local, true, undefined, contextDescription);
|
|
node.specifiers.push(this.finishNode(specifier, type));
|
|
}
|
|
|
|
// Parses a comma-separated list of module imports.
|
|
parseImportSpecifiers(node: N.ImportDeclaration): void {
|
|
let first = true;
|
|
if (this.shouldParseDefaultImport(node)) {
|
|
// import defaultObj, { x, y as z } from '...'
|
|
this.parseImportSpecifierLocal(
|
|
node,
|
|
this.startNode(),
|
|
"ImportDefaultSpecifier",
|
|
"default import specifier",
|
|
);
|
|
|
|
if (!this.eat(tt.comma)) return;
|
|
}
|
|
|
|
if (this.match(tt.star)) {
|
|
const specifier = this.startNode();
|
|
this.next();
|
|
this.expectContextual("as");
|
|
|
|
this.parseImportSpecifierLocal(
|
|
node,
|
|
specifier,
|
|
"ImportNamespaceSpecifier",
|
|
"import namespace specifier",
|
|
);
|
|
|
|
return;
|
|
}
|
|
|
|
this.expect(tt.braceL);
|
|
while (!this.eat(tt.braceR)) {
|
|
if (first) {
|
|
first = false;
|
|
} else {
|
|
// Detect an attempt to deep destructure
|
|
if (this.eat(tt.colon)) {
|
|
this.unexpected(
|
|
null,
|
|
"ES2015 named imports do not destructure. Use another statement for destructuring after the import.",
|
|
);
|
|
}
|
|
|
|
this.expect(tt.comma);
|
|
if (this.eat(tt.braceR)) break;
|
|
}
|
|
|
|
this.parseImportSpecifier(node);
|
|
}
|
|
}
|
|
|
|
parseImportSpecifier(node: N.ImportDeclaration): void {
|
|
const specifier = this.startNode();
|
|
specifier.imported = this.parseIdentifier(true);
|
|
if (this.eatContextual("as")) {
|
|
specifier.local = this.parseIdentifier();
|
|
} else {
|
|
this.checkReservedWord(
|
|
specifier.imported.name,
|
|
specifier.start,
|
|
true,
|
|
true,
|
|
);
|
|
specifier.local = specifier.imported.__clone();
|
|
}
|
|
this.checkLVal(specifier.local, true, undefined, "import specifier");
|
|
node.specifiers.push(this.finishNode(specifier, "ImportSpecifier"));
|
|
}
|
|
}
|