1107 lines
34 KiB
JavaScript
1107 lines
34 KiB
JavaScript
"use strict";
|
|
|
|
var _types = require("../tokenizer/types");
|
|
|
|
var _index = require("./index");
|
|
|
|
var _index2 = _interopRequireDefault(_index);
|
|
|
|
var _whitespace = require("../util/whitespace");
|
|
|
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
|
|
var pp = _index2.default.prototype;
|
|
|
|
// ### 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.
|
|
|
|
/* eslint indent: 0 */
|
|
/* eslint max-len: 0 */
|
|
|
|
pp.parseTopLevel = function (file, program) {
|
|
program.sourceType = this.options.sourceType;
|
|
|
|
this.parseBlockBody(program, true, true, _types.types.eof);
|
|
|
|
file.program = this.finishNode(program, "Program");
|
|
file.comments = this.state.comments;
|
|
file.tokens = this.state.tokens;
|
|
|
|
return this.finishNode(file, "File");
|
|
};
|
|
|
|
var loopLabel = { kind: "loop" },
|
|
switchLabel = { kind: "switch" };
|
|
|
|
// TODO
|
|
|
|
pp.stmtToDirective = function (stmt) {
|
|
var expr = stmt.expression;
|
|
|
|
var directiveLiteral = this.startNodeAt(expr.start, expr.loc.start);
|
|
var directive = this.startNodeAt(stmt.start, stmt.loc.start);
|
|
|
|
var raw = this.input.slice(expr.start, expr.end);
|
|
var 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.
|
|
|
|
pp.parseStatement = function (declaration, topLevel) {
|
|
if (this.match(_types.types.at)) {
|
|
this.parseDecorators(true);
|
|
}
|
|
|
|
var starttype = this.state.type,
|
|
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 _types.types._break:case _types.types._continue:
|
|
return this.parseBreakContinueStatement(node, starttype.keyword);
|
|
case _types.types._debugger:
|
|
return this.parseDebuggerStatement(node);
|
|
case _types.types._do:
|
|
return this.parseDoStatement(node);
|
|
case _types.types._for:
|
|
return this.parseForStatement(node);
|
|
case _types.types._function:
|
|
if (!declaration) this.unexpected();
|
|
return this.parseFunctionStatement(node);
|
|
|
|
case _types.types._class:
|
|
if (!declaration) this.unexpected();
|
|
this.takeDecorators(node);
|
|
return this.parseClass(node, true);
|
|
|
|
case _types.types._if:
|
|
return this.parseIfStatement(node);
|
|
case _types.types._return:
|
|
return this.parseReturnStatement(node);
|
|
case _types.types._switch:
|
|
return this.parseSwitchStatement(node);
|
|
case _types.types._throw:
|
|
return this.parseThrowStatement(node);
|
|
case _types.types._try:
|
|
return this.parseTryStatement(node);
|
|
|
|
case _types.types._let:
|
|
case _types.types._const:
|
|
if (!declaration) this.unexpected(); // NOTE: falls through to _var
|
|
|
|
case _types.types._var:
|
|
return this.parseVarStatement(node, starttype);
|
|
|
|
case _types.types._while:
|
|
return this.parseWhileStatement(node);
|
|
case _types.types._with:
|
|
return this.parseWithStatement(node);
|
|
case _types.types.braceL:
|
|
return this.parseBlock();
|
|
case _types.types.semi:
|
|
return this.parseEmptyStatement(node);
|
|
case _types.types._export:
|
|
case _types.types._import:
|
|
if (!this.options.allowImportExportEverywhere) {
|
|
if (!topLevel) {
|
|
this.raise(this.state.start, "'import' and 'export' may only appear at the top level");
|
|
}
|
|
|
|
if (!this.inModule) {
|
|
this.raise(this.state.start, "'import' and 'export' may appear only with 'sourceType: module'");
|
|
}
|
|
}
|
|
return starttype === _types.types._import ? this.parseImport(node) : this.parseExport(node);
|
|
|
|
case _types.types.name:
|
|
if (this.state.value === "async") {
|
|
// peek ahead and see if next token is a function
|
|
var state = this.state.clone();
|
|
this.next();
|
|
if (this.match(_types.types._function) && !this.canInsertSemicolon()) {
|
|
this.expect(_types.types._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.
|
|
var maybeName = this.state.value;
|
|
var expr = this.parseExpression();
|
|
|
|
if (starttype === _types.types.name && expr.type === "Identifier" && this.eat(_types.types.colon)) {
|
|
return this.parseLabeledStatement(node, maybeName, expr);
|
|
} else {
|
|
return this.parseExpressionStatement(node, expr);
|
|
}
|
|
};
|
|
|
|
pp.takeDecorators = function (node) {
|
|
if (this.state.decorators.length) {
|
|
node.decorators = this.state.decorators;
|
|
this.state.decorators = [];
|
|
}
|
|
};
|
|
|
|
pp.parseDecorators = function (allowExport) {
|
|
while (this.match(_types.types.at)) {
|
|
this.state.decorators.push(this.parseDecorator());
|
|
}
|
|
|
|
if (allowExport && this.match(_types.types._export)) {
|
|
return;
|
|
}
|
|
|
|
if (!this.match(_types.types._class)) {
|
|
this.raise(this.state.start, "Leading decorators must be attached to a class declaration");
|
|
}
|
|
};
|
|
|
|
pp.parseDecorator = function () {
|
|
if (!this.hasPlugin("decorators")) {
|
|
this.unexpected();
|
|
}
|
|
var node = this.startNode();
|
|
this.next();
|
|
node.expression = this.parseMaybeAssign();
|
|
return this.finishNode(node, "Decorator");
|
|
};
|
|
|
|
pp.parseBreakContinueStatement = function (node, keyword) {
|
|
var isBreak = keyword === "break";
|
|
this.next();
|
|
|
|
if (this.isLineTerminator()) {
|
|
node.label = null;
|
|
} else if (!this.match(_types.types.name)) {
|
|
this.unexpected();
|
|
} else {
|
|
node.label = this.parseIdentifier();
|
|
this.semicolon();
|
|
}
|
|
|
|
// Verify that there is an actual destination to break or
|
|
// continue to.
|
|
var i = void 0;
|
|
for (i = 0; i < this.state.labels.length; ++i) {
|
|
var 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");
|
|
};
|
|
|
|
pp.parseDebuggerStatement = function (node) {
|
|
this.next();
|
|
this.semicolon();
|
|
return this.finishNode(node, "DebuggerStatement");
|
|
};
|
|
|
|
pp.parseDoStatement = function (node) {
|
|
this.next();
|
|
this.state.labels.push(loopLabel);
|
|
node.body = this.parseStatement(false);
|
|
this.state.labels.pop();
|
|
this.expect(_types.types._while);
|
|
node.test = this.parseParenExpression();
|
|
this.eat(_types.types.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.
|
|
|
|
pp.parseForStatement = function (node) {
|
|
this.next();
|
|
this.state.labels.push(loopLabel);
|
|
|
|
var forAwait = false;
|
|
if (this.hasPlugin("asyncGenerators") && this.state.inAsync && this.isContextual("await")) {
|
|
forAwait = true;
|
|
this.next();
|
|
}
|
|
this.expect(_types.types.parenL);
|
|
|
|
if (this.match(_types.types.semi)) {
|
|
if (forAwait) {
|
|
this.unexpected();
|
|
}
|
|
return this.parseFor(node, null);
|
|
}
|
|
|
|
if (this.match(_types.types._var) || this.match(_types.types._let) || this.match(_types.types._const)) {
|
|
var _init = this.startNode(),
|
|
varKind = this.state.type;
|
|
this.next();
|
|
this.parseVar(_init, true, varKind);
|
|
this.finishNode(_init, "VariableDeclaration");
|
|
|
|
if (this.match(_types.types._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);
|
|
}
|
|
|
|
var refShorthandDefaultPos = { start: 0 };
|
|
var init = this.parseExpression(true, refShorthandDefaultPos);
|
|
if (this.match(_types.types._in) || this.isContextual("of")) {
|
|
var 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);
|
|
};
|
|
|
|
pp.parseFunctionStatement = function (node) {
|
|
this.next();
|
|
return this.parseFunction(node, true);
|
|
};
|
|
|
|
pp.parseIfStatement = function (node) {
|
|
this.next();
|
|
node.test = this.parseParenExpression();
|
|
node.consequent = this.parseStatement(false);
|
|
node.alternate = this.eat(_types.types._else) ? this.parseStatement(false) : null;
|
|
return this.finishNode(node, "IfStatement");
|
|
};
|
|
|
|
pp.parseReturnStatement = function (node) {
|
|
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");
|
|
};
|
|
|
|
pp.parseSwitchStatement = function (node) {
|
|
this.next();
|
|
node.discriminant = this.parseParenExpression();
|
|
node.cases = [];
|
|
this.expect(_types.types.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.
|
|
|
|
var cur = void 0;
|
|
for (var sawDefault; !this.match(_types.types.braceR);) {
|
|
if (this.match(_types.types._case) || this.match(_types.types._default)) {
|
|
var isCase = this.match(_types.types._case);
|
|
if (cur) this.finishNode(cur, "SwitchCase");
|
|
node.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(_types.types.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");
|
|
};
|
|
|
|
pp.parseThrowStatement = function (node) {
|
|
this.next();
|
|
if (_whitespace.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");
|
|
};
|
|
|
|
// Reused empty array added for node fields that are always empty.
|
|
|
|
var empty = [];
|
|
|
|
pp.parseTryStatement = function (node) {
|
|
this.next();
|
|
|
|
node.block = this.parseBlock();
|
|
node.handler = null;
|
|
|
|
if (this.match(_types.types._catch)) {
|
|
var clause = this.startNode();
|
|
this.next();
|
|
|
|
this.expect(_types.types.parenL);
|
|
clause.param = this.parseBindingAtom();
|
|
this.checkLVal(clause.param, true, Object.create(null), "catch clause");
|
|
this.expect(_types.types.parenR);
|
|
|
|
clause.body = this.parseBlock();
|
|
node.handler = this.finishNode(clause, "CatchClause");
|
|
}
|
|
|
|
node.guardedHandlers = empty;
|
|
node.finalizer = this.eat(_types.types._finally) ? this.parseBlock() : null;
|
|
|
|
if (!node.handler && !node.finalizer) {
|
|
this.raise(node.start, "Missing catch or finally clause");
|
|
}
|
|
|
|
return this.finishNode(node, "TryStatement");
|
|
};
|
|
|
|
pp.parseVarStatement = function (node, kind) {
|
|
this.next();
|
|
this.parseVar(node, false, kind);
|
|
this.semicolon();
|
|
return this.finishNode(node, "VariableDeclaration");
|
|
};
|
|
|
|
pp.parseWhileStatement = function (node) {
|
|
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");
|
|
};
|
|
|
|
pp.parseWithStatement = function (node) {
|
|
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");
|
|
};
|
|
|
|
pp.parseEmptyStatement = function (node) {
|
|
this.next();
|
|
return this.finishNode(node, "EmptyStatement");
|
|
};
|
|
|
|
pp.parseLabeledStatement = function (node, maybeName, expr) {
|
|
for (var _iterator = this.state.labels, _isArray = Array.isArray(_iterator), _i = 0, _iterator = _isArray ? _iterator : _iterator[Symbol.iterator]();;) {
|
|
var _ref;
|
|
|
|
if (_isArray) {
|
|
if (_i >= _iterator.length) break;
|
|
_ref = _iterator[_i++];
|
|
} else {
|
|
_i = _iterator.next();
|
|
if (_i.done) break;
|
|
_ref = _i.value;
|
|
}
|
|
|
|
var _label = _ref;
|
|
|
|
if (_label.name === maybeName) {
|
|
this.raise(expr.start, "Label '" + maybeName + "' is already declared");
|
|
}
|
|
}
|
|
|
|
var kind = this.state.type.isLoop ? "loop" : this.match(_types.types._switch) ? "switch" : null;
|
|
for (var i = this.state.labels.length - 1; i >= 0; i--) {
|
|
var 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);
|
|
this.state.labels.pop();
|
|
node.label = expr;
|
|
return this.finishNode(node, "LabeledStatement");
|
|
};
|
|
|
|
pp.parseExpressionStatement = function (node, expr) {
|
|
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).
|
|
|
|
pp.parseBlock = function (allowDirectives) {
|
|
var node = this.startNode();
|
|
this.expect(_types.types.braceL);
|
|
this.parseBlockBody(node, allowDirectives, false, _types.types.braceR);
|
|
return this.finishNode(node, "BlockStatement");
|
|
};
|
|
|
|
// TODO
|
|
|
|
pp.parseBlockBody = function (node, allowDirectives, topLevel, end) {
|
|
node.body = [];
|
|
node.directives = [];
|
|
|
|
var parsedNonDirective = false;
|
|
var oldStrict = void 0;
|
|
var octalPosition = void 0;
|
|
|
|
while (!this.eat(end)) {
|
|
if (!parsedNonDirective && this.state.containsOctal && !octalPosition) {
|
|
octalPosition = this.state.octalPosition;
|
|
}
|
|
|
|
var stmt = this.parseStatement(true, topLevel);
|
|
|
|
if (allowDirectives && !parsedNonDirective && stmt.type === "ExpressionStatement" && stmt.expression.type === "StringLiteral" && !stmt.expression.extra.parenthesized) {
|
|
var directive = this.stmtToDirective(stmt);
|
|
node.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;
|
|
node.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.
|
|
|
|
pp.parseFor = function (node, init) {
|
|
node.init = init;
|
|
this.expect(_types.types.semi);
|
|
node.test = this.match(_types.types.semi) ? null : this.parseExpression();
|
|
this.expect(_types.types.semi);
|
|
node.update = this.match(_types.types.parenR) ? null : this.parseExpression();
|
|
this.expect(_types.types.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.
|
|
|
|
pp.parseForIn = function (node, init, forAwait) {
|
|
var type = void 0;
|
|
if (forAwait) {
|
|
this.eatContextual("of");
|
|
type = "ForAwaitStatement";
|
|
} else {
|
|
type = this.match(_types.types._in) ? "ForInStatement" : "ForOfStatement";
|
|
this.next();
|
|
}
|
|
node.left = init;
|
|
node.right = this.parseExpression();
|
|
this.expect(_types.types.parenR);
|
|
node.body = this.parseStatement(false);
|
|
this.state.labels.pop();
|
|
return this.finishNode(node, type);
|
|
};
|
|
|
|
// Parse a list of variable declarations.
|
|
|
|
pp.parseVar = function (node, isFor, kind) {
|
|
node.declarations = [];
|
|
node.kind = kind.keyword;
|
|
for (;;) {
|
|
var decl = this.startNode();
|
|
this.parseVarHead(decl);
|
|
if (this.eat(_types.types.eq)) {
|
|
decl.init = this.parseMaybeAssign(isFor);
|
|
} else if (kind === _types.types._const && !(this.match(_types.types._in) || this.isContextual("of"))) {
|
|
this.unexpected();
|
|
} else if (decl.id.type !== "Identifier" && !(isFor && (this.match(_types.types._in) || this.isContextual("of")))) {
|
|
this.raise(this.state.lastTokEnd, "Complex binding patterns require an initialization value");
|
|
} else {
|
|
decl.init = null;
|
|
}
|
|
node.declarations.push(this.finishNode(decl, "VariableDeclarator"));
|
|
if (!this.eat(_types.types.comma)) break;
|
|
}
|
|
return node;
|
|
};
|
|
|
|
pp.parseVarHead = function (decl) {
|
|
decl.id = this.parseBindingAtom();
|
|
this.checkLVal(decl.id, true, undefined, "variable declaration");
|
|
};
|
|
|
|
// Parse a function declaration or literal (depending on the
|
|
// `isStatement` parameter).
|
|
|
|
pp.parseFunction = function (node, isStatement, allowExpressionBody, isAsync, optionalId) {
|
|
var oldInMethod = this.state.inMethod;
|
|
this.state.inMethod = false;
|
|
|
|
this.initFunction(node, isAsync);
|
|
|
|
if (this.match(_types.types.star)) {
|
|
if (node.async && !this.hasPlugin("asyncGenerators")) {
|
|
this.unexpected();
|
|
} else {
|
|
node.generator = true;
|
|
this.next();
|
|
}
|
|
}
|
|
|
|
if (isStatement && !optionalId && !this.match(_types.types.name) && !this.match(_types.types._yield)) {
|
|
this.unexpected();
|
|
}
|
|
|
|
if (this.match(_types.types.name) || this.match(_types.types._yield)) {
|
|
node.id = this.parseBindingIdentifier();
|
|
}
|
|
|
|
this.parseFunctionParams(node);
|
|
this.parseFunctionBody(node, allowExpressionBody);
|
|
|
|
this.state.inMethod = oldInMethod;
|
|
|
|
return this.finishNode(node, isStatement ? "FunctionDeclaration" : "FunctionExpression");
|
|
};
|
|
|
|
pp.parseFunctionParams = function (node) {
|
|
this.expect(_types.types.parenL);
|
|
node.params = this.parseBindingList(_types.types.parenR);
|
|
};
|
|
|
|
// Parse a class declaration or literal (depending on the
|
|
// `isStatement` parameter).
|
|
|
|
pp.parseClass = function (node, isStatement, optionalId) {
|
|
this.next();
|
|
this.parseClassId(node, isStatement, optionalId);
|
|
this.parseClassSuper(node);
|
|
this.parseClassBody(node);
|
|
return this.finishNode(node, isStatement ? "ClassDeclaration" : "ClassExpression");
|
|
};
|
|
|
|
pp.isClassProperty = function () {
|
|
return this.match(_types.types.eq) || this.isLineTerminator();
|
|
};
|
|
|
|
pp.isClassMutatorStarter = function () {
|
|
return false;
|
|
};
|
|
|
|
pp.parseClassBody = function (node) {
|
|
// class bodies are implicitly strict
|
|
var oldStrict = this.state.strict;
|
|
this.state.strict = true;
|
|
|
|
var hadConstructorCall = false;
|
|
var hadConstructor = false;
|
|
var decorators = [];
|
|
var classBody = this.startNode();
|
|
|
|
classBody.body = [];
|
|
|
|
this.expect(_types.types.braceL);
|
|
|
|
while (!this.eat(_types.types.braceR)) {
|
|
if (this.eat(_types.types.semi)) {
|
|
continue;
|
|
}
|
|
|
|
if (this.match(_types.types.at)) {
|
|
decorators.push(this.parseDecorator());
|
|
continue;
|
|
}
|
|
|
|
var method = this.startNode();
|
|
|
|
// steal the decorators if there are any
|
|
if (decorators.length) {
|
|
method.decorators = decorators;
|
|
decorators = [];
|
|
}
|
|
|
|
var isConstructorCall = false;
|
|
var isMaybeStatic = this.match(_types.types.name) && this.state.value === "static";
|
|
var isGenerator = this.eat(_types.types.star);
|
|
var isGetSet = false;
|
|
var isAsync = false;
|
|
|
|
this.parsePropertyName(method);
|
|
|
|
method.static = isMaybeStatic && !this.match(_types.types.parenL);
|
|
if (method.static) {
|
|
if (isGenerator) this.unexpected();
|
|
isGenerator = this.eat(_types.types.star);
|
|
this.parsePropertyName(method);
|
|
}
|
|
|
|
if (!isGenerator) {
|
|
if (this.isClassProperty()) {
|
|
classBody.body.push(this.parseClassProperty(method));
|
|
continue;
|
|
}
|
|
|
|
if (method.key.type === "Identifier" && !method.computed && this.hasPlugin("classConstructorCall") && method.key.name === "call" && this.match(_types.types.name) && this.state.value === "constructor") {
|
|
isConstructorCall = true;
|
|
this.parsePropertyName(method);
|
|
}
|
|
}
|
|
|
|
var isAsyncMethod = !this.match(_types.types.parenL) && !method.computed && method.key.type === "Identifier" && method.key.name === "async";
|
|
if (isAsyncMethod) {
|
|
if (this.hasPlugin("asyncGenerators") && this.eat(_types.types.star)) isGenerator = true;
|
|
isAsync = true;
|
|
this.parsePropertyName(method);
|
|
}
|
|
|
|
method.kind = "method";
|
|
|
|
if (!method.computed) {
|
|
var key = method.key;
|
|
|
|
// handle get/set methods
|
|
// eg. class Foo { get bar() {} set bar() {} }
|
|
|
|
if (!isAsync && !isGenerator && !this.isClassMutatorStarter() && key.type === "Identifier" && !this.match(_types.types.parenL) && (key.name === "get" || key.name === "set")) {
|
|
isGetSet = true;
|
|
method.kind = key.name;
|
|
key = this.parsePropertyName(method);
|
|
}
|
|
|
|
// disallow invalid constructors
|
|
var isConstructor = !isConstructorCall && !method.static && (key.type === "Identifier" && key.name === "constructor" || key.type === "StringLiteral" && key.value === "constructor");
|
|
if (isConstructor) {
|
|
if (hadConstructor) this.raise(key.start, "Duplicate constructor in the same class");
|
|
if (isGetSet) this.raise(key.start, "Constructor can't have get/set modifier");
|
|
if (isGenerator) this.raise(key.start, "Constructor can't be a generator");
|
|
if (isAsync) this.raise(key.start, "Constructor can't be an async function");
|
|
method.kind = "constructor";
|
|
hadConstructor = true;
|
|
}
|
|
|
|
// disallow static prototype method
|
|
var isStaticPrototype = method.static && (key.type === "Identifier" && key.name === "prototype" || key.type === "StringLiteral" && key.value === "prototype");
|
|
if (isStaticPrototype) {
|
|
this.raise(key.start, "Classes may not have static property named prototype");
|
|
}
|
|
}
|
|
|
|
// convert constructor to a constructor call
|
|
if (isConstructorCall) {
|
|
if (hadConstructorCall) this.raise(method.start, "Duplicate constructor call in the same class");
|
|
method.kind = "constructorCall";
|
|
hadConstructorCall = true;
|
|
}
|
|
|
|
// disallow decorators on class constructors
|
|
if ((method.kind === "constructor" || method.kind === "constructorCall") && method.decorators) {
|
|
this.raise(method.start, "You can't attach decorators to a class constructor");
|
|
}
|
|
|
|
this.parseClassMethod(classBody, method, isGenerator, isAsync);
|
|
|
|
// get methods aren't allowed to have any parameters
|
|
// set methods must have exactly 1 parameter
|
|
if (isGetSet) {
|
|
var paramCount = method.kind === "get" ? 0 : 1;
|
|
if (method.params.length !== paramCount) {
|
|
var start = method.start;
|
|
if (method.kind === "get") {
|
|
this.raise(start, "getter should have no params");
|
|
} else {
|
|
this.raise(start, "setter should have exactly one param");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (decorators.length) {
|
|
this.raise(this.state.start, "You have trailing decorators with no method");
|
|
}
|
|
|
|
node.body = this.finishNode(classBody, "ClassBody");
|
|
|
|
this.state.strict = oldStrict;
|
|
};
|
|
|
|
pp.parseClassProperty = function (node) {
|
|
if (this.match(_types.types.eq)) {
|
|
if (!this.hasPlugin("classProperties")) this.unexpected();
|
|
this.next();
|
|
node.value = this.parseMaybeAssign();
|
|
} else {
|
|
node.value = null;
|
|
}
|
|
this.semicolon();
|
|
return this.finishNode(node, "ClassProperty");
|
|
};
|
|
|
|
pp.parseClassMethod = function (classBody, method, isGenerator, isAsync) {
|
|
this.parseMethod(method, isGenerator, isAsync);
|
|
classBody.body.push(this.finishNode(method, "ClassMethod"));
|
|
};
|
|
|
|
pp.parseClassId = function (node, isStatement, optionalId) {
|
|
if (this.match(_types.types.name)) {
|
|
node.id = this.parseIdentifier();
|
|
} else {
|
|
if (optionalId || !isStatement) {
|
|
node.id = null;
|
|
} else {
|
|
this.unexpected();
|
|
}
|
|
}
|
|
};
|
|
|
|
pp.parseClassSuper = function (node) {
|
|
node.superClass = this.eat(_types.types._extends) ? this.parseExprSubscripts() : null;
|
|
};
|
|
|
|
// Parses module export declaration.
|
|
|
|
pp.parseExport = function (node) {
|
|
this.next();
|
|
// export * from '...'
|
|
if (this.match(_types.types.star)) {
|
|
var specifier = this.startNode();
|
|
this.next();
|
|
if (this.hasPlugin("exportExtensions") && this.eatContextual("as")) {
|
|
specifier.exported = this.parseIdentifier();
|
|
node.specifiers = [this.finishNode(specifier, "ExportNamespaceSpecifier")];
|
|
this.parseExportSpecifiersMaybe(node);
|
|
this.parseExportFrom(node, true);
|
|
} else {
|
|
this.parseExportFrom(node, true);
|
|
return this.finishNode(node, "ExportAllDeclaration");
|
|
}
|
|
} else if (this.hasPlugin("exportExtensions") && this.isExportDefaultSpecifier()) {
|
|
var _specifier = this.startNode();
|
|
_specifier.exported = this.parseIdentifier(true);
|
|
node.specifiers = [this.finishNode(_specifier, "ExportDefaultSpecifier")];
|
|
if (this.match(_types.types.comma) && this.lookahead().type === _types.types.star) {
|
|
this.expect(_types.types.comma);
|
|
var _specifier2 = this.startNode();
|
|
this.expect(_types.types.star);
|
|
this.expectContextual("as");
|
|
_specifier2.exported = this.parseIdentifier();
|
|
node.specifiers.push(this.finishNode(_specifier2, "ExportNamespaceSpecifier"));
|
|
} else {
|
|
this.parseExportSpecifiersMaybe(node);
|
|
}
|
|
this.parseExportFrom(node, true);
|
|
} else if (this.eat(_types.types._default)) {
|
|
// export default ...
|
|
var expr = this.startNode();
|
|
var needsSemi = false;
|
|
if (this.eat(_types.types._function)) {
|
|
expr = this.parseFunction(expr, true, false, false, true);
|
|
} else if (this.match(_types.types._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.state.type.keyword || this.shouldParseExportDeclaration()) {
|
|
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");
|
|
};
|
|
|
|
pp.parseExportDeclaration = function () {
|
|
return this.parseStatement(true);
|
|
};
|
|
|
|
pp.isExportDefaultSpecifier = function () {
|
|
if (this.match(_types.types.name)) {
|
|
return this.state.value !== "type" && this.state.value !== "async" && this.state.value !== "interface";
|
|
}
|
|
|
|
if (!this.match(_types.types._default)) {
|
|
return false;
|
|
}
|
|
|
|
var lookahead = this.lookahead();
|
|
return lookahead.type === _types.types.comma || lookahead.type === _types.types.name && lookahead.value === "from";
|
|
};
|
|
|
|
pp.parseExportSpecifiersMaybe = function (node) {
|
|
if (this.eat(_types.types.comma)) {
|
|
node.specifiers = node.specifiers.concat(this.parseExportSpecifiers());
|
|
}
|
|
};
|
|
|
|
pp.parseExportFrom = function (node, expect) {
|
|
if (this.eatContextual("from")) {
|
|
node.source = this.match(_types.types.string) ? this.parseExprAtom() : this.unexpected();
|
|
this.checkExport(node);
|
|
} else {
|
|
if (expect) {
|
|
this.unexpected();
|
|
} else {
|
|
node.source = null;
|
|
}
|
|
}
|
|
|
|
this.semicolon();
|
|
};
|
|
|
|
pp.shouldParseExportDeclaration = function () {
|
|
return this.isContextual("async");
|
|
};
|
|
|
|
pp.checkExport = function (node, checkNames, isDefault) {
|
|
if (checkNames) {
|
|
// Check for duplicate exports
|
|
if (isDefault) {
|
|
// Default exports
|
|
this.checkDuplicateExports(node, "default", isDefault);
|
|
} else if (node.specifiers && node.specifiers.length) {
|
|
// Named exports
|
|
for (var _iterator2 = node.specifiers, _isArray2 = Array.isArray(_iterator2), _i2 = 0, _iterator2 = _isArray2 ? _iterator2 : _iterator2[Symbol.iterator]();;) {
|
|
var _ref2;
|
|
|
|
if (_isArray2) {
|
|
if (_i2 >= _iterator2.length) break;
|
|
_ref2 = _iterator2[_i2++];
|
|
} else {
|
|
_i2 = _iterator2.next();
|
|
if (_i2.done) break;
|
|
_ref2 = _i2.value;
|
|
}
|
|
|
|
var specifier = _ref2;
|
|
|
|
var name = specifier.exported.name;
|
|
if (name === "default") isDefault = true;
|
|
this.checkDuplicateExports(specifier, name, isDefault);
|
|
}
|
|
} else if (node.declaration) {
|
|
// Exported declarations
|
|
if (node.declaration.type === "FunctionDeclaration" || node.declaration.type === "ClassDeclaration") {
|
|
this.checkDuplicateExports(node, node.declaration.id.name, isDefault);
|
|
} else if (node.declaration.type === "VariableDeclaration") {
|
|
for (var _iterator3 = node.declaration.declarations, _isArray3 = Array.isArray(_iterator3), _i3 = 0, _iterator3 = _isArray3 ? _iterator3 : _iterator3[Symbol.iterator]();;) {
|
|
var _ref3;
|
|
|
|
if (_isArray3) {
|
|
if (_i3 >= _iterator3.length) break;
|
|
_ref3 = _iterator3[_i3++];
|
|
} else {
|
|
_i3 = _iterator3.next();
|
|
if (_i3.done) break;
|
|
_ref3 = _i3.value;
|
|
}
|
|
|
|
var declaration = _ref3;
|
|
|
|
if (declaration.id.name) {
|
|
this.checkDuplicateExports(declaration, declaration.id.name, isDefault);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (this.state.decorators.length) {
|
|
var isClass = node.declaration && (node.declaration.type === "ClassDeclaration" || node.declaration.type === "ClassExpression");
|
|
if (!node.declaration || !isClass) {
|
|
this.raise(node.start, "You can only use decorators on an export when exporting a class");
|
|
}
|
|
this.takeDecorators(node.declaration);
|
|
}
|
|
};
|
|
|
|
pp.checkDuplicateExports = function (node, name, isDefault) {
|
|
if (this.state.exportedIdentifiers.indexOf(name) > -1) {
|
|
this.raiseDuplicateExportError(node, name, isDefault);
|
|
}
|
|
this.state.exportedIdentifiers.push(name);
|
|
};
|
|
|
|
pp.raiseDuplicateExportError = function (node, name, isDefault) {
|
|
this.raise(node.start, isDefault ? "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.
|
|
|
|
pp.parseExportSpecifiers = function () {
|
|
var nodes = [];
|
|
var first = true;
|
|
var needsFrom = void 0;
|
|
|
|
// export { x, y as z } [from '...']
|
|
this.expect(_types.types.braceL);
|
|
|
|
while (!this.eat(_types.types.braceR)) {
|
|
if (first) {
|
|
first = false;
|
|
} else {
|
|
this.expect(_types.types.comma);
|
|
if (this.eat(_types.types.braceR)) break;
|
|
}
|
|
|
|
var isDefault = this.match(_types.types._default);
|
|
if (isDefault && !needsFrom) needsFrom = true;
|
|
|
|
var 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.
|
|
|
|
pp.parseImport = function (node) {
|
|
this.next();
|
|
|
|
// import '...'
|
|
if (this.match(_types.types.string)) {
|
|
node.specifiers = [];
|
|
node.source = this.parseExprAtom();
|
|
} else {
|
|
node.specifiers = [];
|
|
this.parseImportSpecifiers(node);
|
|
this.expectContextual("from");
|
|
node.source = this.match(_types.types.string) ? this.parseExprAtom() : this.unexpected();
|
|
}
|
|
this.semicolon();
|
|
return this.finishNode(node, "ImportDeclaration");
|
|
};
|
|
|
|
// Parses a comma-separated list of module imports.
|
|
|
|
pp.parseImportSpecifiers = function (node) {
|
|
var first = true;
|
|
if (this.match(_types.types.name)) {
|
|
// import defaultObj, { x, y as z } from '...'
|
|
var startPos = this.state.start,
|
|
startLoc = this.state.startLoc;
|
|
node.specifiers.push(this.parseImportSpecifierDefault(this.parseIdentifier(), startPos, startLoc));
|
|
if (!this.eat(_types.types.comma)) return;
|
|
}
|
|
|
|
if (this.match(_types.types.star)) {
|
|
var specifier = this.startNode();
|
|
this.next();
|
|
this.expectContextual("as");
|
|
specifier.local = this.parseIdentifier();
|
|
this.checkLVal(specifier.local, true, undefined, "import namespace specifier");
|
|
node.specifiers.push(this.finishNode(specifier, "ImportNamespaceSpecifier"));
|
|
return;
|
|
}
|
|
|
|
this.expect(_types.types.braceL);
|
|
while (!this.eat(_types.types.braceR)) {
|
|
if (first) {
|
|
first = false;
|
|
} else {
|
|
this.expect(_types.types.comma);
|
|
if (this.eat(_types.types.braceR)) break;
|
|
}
|
|
|
|
var _specifier3 = this.startNode();
|
|
_specifier3.imported = this.parseIdentifier(true);
|
|
_specifier3.local = this.eatContextual("as") ? this.parseIdentifier() : _specifier3.imported.__clone();
|
|
this.checkLVal(_specifier3.local, true, undefined, "import specifier");
|
|
node.specifiers.push(this.finishNode(_specifier3, "ImportSpecifier"));
|
|
}
|
|
};
|
|
|
|
pp.parseImportSpecifierDefault = function (id, startPos, startLoc) {
|
|
var node = this.startNodeAt(startPos, startLoc);
|
|
node.local = id;
|
|
this.checkLVal(node.local, true, undefined, "default import specifier");
|
|
return this.finishNode(node, "ImportDefaultSpecifier");
|
|
}; |