diff options
Diffstat (limited to 'node_modules/acorn/src/statement.js')
-rw-r--r-- | node_modules/acorn/src/statement.js | 765 |
1 files changed, 765 insertions, 0 deletions
diff --git a/node_modules/acorn/src/statement.js b/node_modules/acorn/src/statement.js new file mode 100644 index 000000000..eb2f18e8b --- /dev/null +++ b/node_modules/acorn/src/statement.js @@ -0,0 +1,765 @@ +import {types as tt} from "./tokentype" +import {Parser} from "./state" +import {lineBreak, skipWhiteSpace} from "./whitespace" +import {isIdentifierStart, isIdentifierChar} from "./identifier" +import {has} from "./util" +import {DestructuringErrors} from "./parseutil" + +const pp = Parser.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. + +pp.parseTopLevel = function(node) { + let exports = {} + if (!node.body) node.body = [] + while (this.type !== tt.eof) { + let stmt = this.parseStatement(true, true, exports) + node.body.push(stmt) + } + this.next() + if (this.options.ecmaVersion >= 6) { + node.sourceType = this.options.sourceType + } + return this.finishNode(node, "Program") +} + +const loopLabel = {kind: "loop"}, switchLabel = {kind: "switch"} + +pp.isLet = function() { + if (this.type !== tt.name || this.options.ecmaVersion < 6 || this.value != "let") return false + skipWhiteSpace.lastIndex = this.pos + let skip = skipWhiteSpace.exec(this.input) + let next = this.pos + skip[0].length, nextCh = this.input.charCodeAt(next) + if (nextCh === 91 || nextCh == 123) return true // '{' and '[' + if (isIdentifierStart(nextCh, true)) { + let pos = next + 1 + while (isIdentifierChar(this.input.charCodeAt(pos), true)) ++pos + let ident = this.input.slice(next, pos) + if (!this.isKeyword(ident)) return true + } + return false +} + +// check 'async [no LineTerminator here] function' +// - 'async /*foo*/ function' is OK. +// - 'async /*\n*/ function' is invalid. +pp.isAsyncFunction = function() { + if (this.type !== tt.name || this.options.ecmaVersion < 8 || this.value != "async") + return false + + skipWhiteSpace.lastIndex = this.pos + let skip = skipWhiteSpace.exec(this.input) + let next = this.pos + skip[0].length + return !lineBreak.test(this.input.slice(this.pos, next)) && + this.input.slice(next, next + 8) === "function" && + (next + 8 == this.input.length || !isIdentifierChar(this.input.charAt(next + 8))) +} + +// 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, exports) { + let starttype = this.type, node = this.startNode(), kind + + if (this.isLet()) { + starttype = tt._var + kind = "let" + } + + // 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: 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 (!declaration && this.options.ecmaVersion >= 6) this.unexpected() + return this.parseFunctionStatement(node, false) + 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._const: case tt._var: + kind = kind || this.value + if (!declaration && kind != "var") this.unexpected() + return this.parseVarStatement(node, kind) + 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.options.allowImportExportEverywhere) { + if (!topLevel) + this.raise(this.start, "'import' and 'export' may only appear at the top level") + if (!this.inModule) + this.raise(this.start, "'import' and 'export' may appear only with 'sourceType: module'") + } + return starttype === tt._import ? this.parseImport(node) : this.parseExport(node, exports) + + // 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. + default: + if (this.isAsyncFunction() && declaration) { + this.next() + return this.parseFunctionStatement(node, true) + } + + let maybeName = this.value, 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) + } +} + +pp.parseBreakContinueStatement = function(node, keyword) { + let isBreak = keyword == "break" + this.next() + if (this.eat(tt.semi) || this.insertSemicolon()) node.label = null + else if (this.type !== tt.name) this.unexpected() + else { + node.label = this.parseIdent() + this.semicolon() + } + + // Verify that there is an actual destination to break or + // continue to. + let i = 0 + for (; i < this.labels.length; ++i) { + let lab = this.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.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.labels.push(loopLabel) + node.body = this.parseStatement(false) + this.labels.pop() + this.expect(tt._while) + node.test = this.parseParenExpression() + if (this.options.ecmaVersion >= 6) + this.eat(tt.semi) + else + this.semicolon() + 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.labels.push(loopLabel) + this.enterLexicalScope() + this.expect(tt.parenL) + if (this.type === tt.semi) return this.parseFor(node, null) + let isLet = this.isLet() + if (this.type === tt._var || this.type === tt._const || isLet) { + let init = this.startNode(), kind = isLet ? "let" : this.value + this.next() + this.parseVar(init, true, kind) + this.finishNode(init, "VariableDeclaration") + if ((this.type === tt._in || (this.options.ecmaVersion >= 6 && this.isContextual("of"))) && init.declarations.length === 1 && + !(kind !== "var" && init.declarations[0].init)) + return this.parseForIn(node, init) + return this.parseFor(node, init) + } + let refDestructuringErrors = new DestructuringErrors + let init = this.parseExpression(true, refDestructuringErrors) + if (this.type === tt._in || (this.options.ecmaVersion >= 6 && this.isContextual("of"))) { + this.toAssignable(init) + this.checkLVal(init) + this.checkPatternErrors(refDestructuringErrors, true) + return this.parseForIn(node, init) + } else { + this.checkExpressionErrors(refDestructuringErrors, true) + } + return this.parseFor(node, init) +} + +pp.parseFunctionStatement = function(node, isAsync) { + this.next() + return this.parseFunction(node, true, false, isAsync) +} + +pp.isFunction = function() { + return this.type === tt._function || this.isAsyncFunction() +} + +pp.parseIfStatement = function(node) { + this.next() + node.test = this.parseParenExpression() + // allow function declarations in branches, but only in non-strict mode + node.consequent = this.parseStatement(!this.strict && this.isFunction()) + node.alternate = this.eat(tt._else) ? this.parseStatement(!this.strict && this.isFunction()) : null + return this.finishNode(node, "IfStatement") +} + +pp.parseReturnStatement = function(node) { + if (!this.inFunction && !this.options.allowReturnOutsideFunction) + this.raise(this.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.eat(tt.semi) || this.insertSemicolon()) 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(tt.braceL) + this.labels.push(switchLabel) + this.enterLexicalScope() + + // 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 = false; this.type != tt.braceR;) { + if (this.type === tt._case || this.type === tt._default) { + let isCase = this.type === tt._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.raiseRecoverable(this.lastTokStart, "Multiple default clauses") + sawDefault = true + cur.test = null + } + this.expect(tt.colon) + } else { + if (!cur) this.unexpected() + cur.consequent.push(this.parseStatement(true)) + } + } + this.exitLexicalScope() + if (cur) this.finishNode(cur, "SwitchCase") + this.next() // Closing brace + this.labels.pop() + return this.finishNode(node, "SwitchStatement") +} + +pp.parseThrowStatement = function(node) { + this.next() + if (lineBreak.test(this.input.slice(this.lastTokEnd, this.start))) + this.raise(this.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. + +const empty = [] + +pp.parseTryStatement = function(node) { + this.next() + node.block = this.parseBlock() + node.handler = null + if (this.type === tt._catch) { + let clause = this.startNode() + this.next() + this.expect(tt.parenL) + clause.param = this.parseBindingAtom() + this.enterLexicalScope() + this.checkLVal(clause.param, "let") + this.expect(tt.parenR) + clause.body = this.parseBlock(false) + this.exitLexicalScope() + node.handler = this.finishNode(clause, "CatchClause") + } + 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") +} + +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.labels.push(loopLabel) + node.body = this.parseStatement(false) + this.labels.pop() + return this.finishNode(node, "WhileStatement") +} + +pp.parseWithStatement = function(node) { + if (this.strict) this.raise(this.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 (let i = 0; i < this.labels.length; ++i) + if (this.labels[i].name === maybeName) this.raise(expr.start, "Label '" + maybeName + "' is already declared") + let kind = this.type.isLoop ? "loop" : this.type === tt._switch ? "switch" : null + for (let i = this.labels.length - 1; i >= 0; i--) { + let label = this.labels[i] + if (label.statementStart == node.start) { + label.statementStart = this.start + label.kind = kind + } else break + } + this.labels.push({name: maybeName, kind: kind, statementStart: this.start}) + node.body = this.parseStatement(true) + if (node.body.type == "ClassDeclaration" || + node.body.type == "VariableDeclaration" && node.body.kind != "var" || + node.body.type == "FunctionDeclaration" && (this.strict || node.body.generator)) + this.raiseRecoverable(node.body.start, "Invalid labeled declaration") + this.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(createNewLexicalScope = true) { + let node = this.startNode() + node.body = [] + this.expect(tt.braceL) + if (createNewLexicalScope) { + this.enterLexicalScope() + } + while (!this.eat(tt.braceR)) { + let stmt = this.parseStatement(true) + node.body.push(stmt) + } + if (createNewLexicalScope) { + this.exitLexicalScope() + } + return this.finishNode(node, "BlockStatement") +} + +// 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(tt.semi) + node.test = this.type === tt.semi ? null : this.parseExpression() + this.expect(tt.semi) + node.update = this.type === tt.parenR ? null : this.parseExpression() + this.expect(tt.parenR) + this.exitLexicalScope() + node.body = this.parseStatement(false) + this.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) { + let type = this.type === tt._in ? "ForInStatement" : "ForOfStatement" + this.next() + node.left = init + node.right = this.parseExpression() + this.expect(tt.parenR) + this.exitLexicalScope() + node.body = this.parseStatement(false) + this.labels.pop() + return this.finishNode(node, type) +} + +// Parse a list of variable declarations. + +pp.parseVar = function(node, isFor, kind) { + node.declarations = [] + node.kind = kind + for (;;) { + let decl = this.startNode() + this.parseVarId(decl, kind) + if (this.eat(tt.eq)) { + decl.init = this.parseMaybeAssign(isFor) + } else if (kind === "const" && !(this.type === tt._in || (this.options.ecmaVersion >= 6 && this.isContextual("of")))) { + this.unexpected() + } else if (decl.id.type != "Identifier" && !(isFor && (this.type === tt._in || this.isContextual("of")))) { + this.raise(this.lastTokEnd, "Complex binding patterns require an initialization value") + } else { + decl.init = null + } + node.declarations.push(this.finishNode(decl, "VariableDeclarator")) + if (!this.eat(tt.comma)) break + } + return node +} + +pp.parseVarId = function(decl, kind) { + decl.id = this.parseBindingAtom(kind) + this.checkLVal(decl.id, kind, false) +} + +// Parse a function declaration or literal (depending on the +// `isStatement` parameter). + +pp.parseFunction = function(node, isStatement, allowExpressionBody, isAsync) { + this.initFunction(node) + if (this.options.ecmaVersion >= 6 && !isAsync) + node.generator = this.eat(tt.star) + if (this.options.ecmaVersion >= 8) + node.async = !!isAsync + + if (isStatement) { + node.id = isStatement === "nullableID" && this.type != tt.name ? null : this.parseIdent() + if (node.id) { + this.checkLVal(node.id, "var") + } + } + + let oldInGen = this.inGenerator, oldInAsync = this.inAsync, + oldYieldPos = this.yieldPos, oldAwaitPos = this.awaitPos, oldInFunc = this.inFunction + this.inGenerator = node.generator + this.inAsync = node.async + this.yieldPos = 0 + this.awaitPos = 0 + this.inFunction = true + this.enterFunctionScope() + + if (!isStatement) + node.id = this.type == tt.name ? this.parseIdent() : null + + this.parseFunctionParams(node) + this.parseFunctionBody(node, allowExpressionBody) + + this.inGenerator = oldInGen + this.inAsync = oldInAsync + this.yieldPos = oldYieldPos + this.awaitPos = oldAwaitPos + this.inFunction = oldInFunc + return this.finishNode(node, isStatement ? "FunctionDeclaration" : "FunctionExpression") +} + +pp.parseFunctionParams = function(node) { + this.expect(tt.parenL) + node.params = this.parseBindingList(tt.parenR, false, this.options.ecmaVersion >= 8, true) + this.checkYieldAwaitInDefaultParams() +} + +// Parse a class declaration or literal (depending on the +// `isStatement` parameter). + +pp.parseClass = function(node, isStatement) { + this.next() + + this.parseClassId(node, isStatement) + this.parseClassSuper(node) + let classBody = this.startNode() + let hadConstructor = false + classBody.body = [] + this.expect(tt.braceL) + while (!this.eat(tt.braceR)) { + if (this.eat(tt.semi)) continue + let method = this.startNode() + let isGenerator = this.eat(tt.star) + let isAsync = false + let isMaybeStatic = this.type === tt.name && this.value === "static" + this.parsePropertyName(method) + method.static = isMaybeStatic && this.type !== tt.parenL + if (method.static) { + if (isGenerator) this.unexpected() + isGenerator = this.eat(tt.star) + this.parsePropertyName(method) + } + if (this.options.ecmaVersion >= 8 && !isGenerator && !method.computed && + method.key.type === "Identifier" && method.key.name === "async" && this.type !== tt.parenL && + !this.canInsertSemicolon()) { + isAsync = true + this.parsePropertyName(method) + } + method.kind = "method" + let isGetSet = false + if (!method.computed) { + let {key} = method + if (!isGenerator && !isAsync && key.type === "Identifier" && this.type !== tt.parenL && (key.name === "get" || key.name === "set")) { + isGetSet = true + method.kind = key.name + key = this.parsePropertyName(method) + } + if (!method.static && (key.type === "Identifier" && key.name === "constructor" || + key.type === "Literal" && key.value === "constructor")) { + 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 method") + method.kind = "constructor" + hadConstructor = true + } + } + this.parseClassMethod(classBody, method, isGenerator, isAsync) + if (isGetSet) { + let paramCount = method.kind === "get" ? 0 : 1 + if (method.value.params.length !== paramCount) { + let start = method.value.start + if (method.kind === "get") + this.raiseRecoverable(start, "getter should have no params") + else + this.raiseRecoverable(start, "setter should have exactly one param") + } else { + if (method.kind === "set" && method.value.params[0].type === "RestElement") + this.raiseRecoverable(method.value.params[0].start, "Setter cannot use rest params") + } + } + } + node.body = this.finishNode(classBody, "ClassBody") + return this.finishNode(node, isStatement ? "ClassDeclaration" : "ClassExpression") +} + +pp.parseClassMethod = function(classBody, method, isGenerator, isAsync) { + method.value = this.parseMethod(isGenerator, isAsync) + classBody.body.push(this.finishNode(method, "MethodDefinition")) +} + +pp.parseClassId = function(node, isStatement) { + node.id = this.type === tt.name ? this.parseIdent() : isStatement === true ? this.unexpected() : null +} + +pp.parseClassSuper = function(node) { + node.superClass = this.eat(tt._extends) ? this.parseExprSubscripts() : null +} + +// Parses module export declaration. + +pp.parseExport = function(node, exports) { + this.next() + // export * from '...' + if (this.eat(tt.star)) { + this.expectContextual("from") + node.source = this.type === tt.string ? this.parseExprAtom() : this.unexpected() + this.semicolon() + return this.finishNode(node, "ExportAllDeclaration") + } + if (this.eat(tt._default)) { // export default ... + this.checkExport(exports, "default", this.lastTokStart) + let isAsync + if (this.type === tt._function || (isAsync = this.isAsyncFunction())) { + let fNode = this.startNode() + this.next() + if (isAsync) this.next() + node.declaration = this.parseFunction(fNode, "nullableID", false, isAsync) + } else if (this.type === tt._class) { + let cNode = this.startNode() + node.declaration = this.parseClass(cNode, "nullableID") + } else { + node.declaration = this.parseMaybeAssign() + this.semicolon() + } + return this.finishNode(node, "ExportDefaultDeclaration") + } + // export var|const|let|function|class ... + if (this.shouldParseExportStatement()) { + node.declaration = this.parseStatement(true) + if (node.declaration.type === "VariableDeclaration") + this.checkVariableExport(exports, node.declaration.declarations) + else + this.checkExport(exports, node.declaration.id.name, node.declaration.id.start) + node.specifiers = [] + node.source = null + } else { // export { x, y as z } [from '...'] + node.declaration = null + node.specifiers = this.parseExportSpecifiers(exports) + if (this.eatContextual("from")) { + node.source = this.type === tt.string ? this.parseExprAtom() : this.unexpected() + } else { + // check for keywords used as local names + for (let i = 0; i < node.specifiers.length; i++) { + if (this.keywords.test(node.specifiers[i].local.name) || this.reservedWords.test(node.specifiers[i].local.name)) { + this.unexpected(node.specifiers[i].local.start) + } + } + + node.source = null + } + this.semicolon() + } + return this.finishNode(node, "ExportNamedDeclaration") +} + +pp.checkExport = function(exports, name, pos) { + if (!exports) return + if (has(exports, name)) + this.raiseRecoverable(pos, "Duplicate export '" + name + "'") + exports[name] = true +} + +pp.checkPatternExport = function(exports, pat) { + let type = pat.type + if (type == "Identifier") + this.checkExport(exports, pat.name, pat.start) + else if (type == "ObjectPattern") + for (let i = 0; i < pat.properties.length; ++i) + this.checkPatternExport(exports, pat.properties[i].value) + else if (type == "ArrayPattern") + for (let i = 0; i < pat.elements.length; ++i) { + let elt = pat.elements[i] + if (elt) this.checkPatternExport(exports, elt) + } + else if (type == "AssignmentPattern") + this.checkPatternExport(exports, pat.left) + else if (type == "ParenthesizedExpression") + this.checkPatternExport(exports, pat.expression) +} + +pp.checkVariableExport = function(exports, decls) { + if (!exports) return + for (let i = 0; i < decls.length; i++) + this.checkPatternExport(exports, decls[i].id) +} + +pp.shouldParseExportStatement = function() { + return this.type.keyword === "var" || + this.type.keyword === "const" || + this.type.keyword === "class" || + this.type.keyword === "function" || + this.isLet() || + this.isAsyncFunction() +} + +// Parses a comma-separated list of module exports. + +pp.parseExportSpecifiers = function(exports) { + let nodes = [], first = true + // export { x, y as z } [from '...'] + this.expect(tt.braceL) + while (!this.eat(tt.braceR)) { + if (!first) { + this.expect(tt.comma) + if (this.afterTrailingComma(tt.braceR)) break + } else first = false + + let node = this.startNode() + node.local = this.parseIdent(true) + node.exported = this.eatContextual("as") ? this.parseIdent(true) : node.local + this.checkExport(exports, node.exported.name, node.exported.start) + nodes.push(this.finishNode(node, "ExportSpecifier")) + } + return nodes +} + +// Parses import declaration. + +pp.parseImport = function(node) { + this.next() + // import '...' + if (this.type === tt.string) { + node.specifiers = empty + node.source = this.parseExprAtom() + } else { + node.specifiers = this.parseImportSpecifiers() + this.expectContextual("from") + node.source = this.type === tt.string ? this.parseExprAtom() : this.unexpected() + } + this.semicolon() + return this.finishNode(node, "ImportDeclaration") +} + +// Parses a comma-separated list of module imports. + +pp.parseImportSpecifiers = function() { + let nodes = [], first = true + if (this.type === tt.name) { + // import defaultObj, { x, y as z } from '...' + let node = this.startNode() + node.local = this.parseIdent() + this.checkLVal(node.local, "let") + nodes.push(this.finishNode(node, "ImportDefaultSpecifier")) + if (!this.eat(tt.comma)) return nodes + } + if (this.type === tt.star) { + let node = this.startNode() + this.next() + this.expectContextual("as") + node.local = this.parseIdent() + this.checkLVal(node.local, "let") + nodes.push(this.finishNode(node, "ImportNamespaceSpecifier")) + return nodes + } + this.expect(tt.braceL) + while (!this.eat(tt.braceR)) { + if (!first) { + this.expect(tt.comma) + if (this.afterTrailingComma(tt.braceR)) break + } else first = false + + let node = this.startNode() + node.imported = this.parseIdent(true) + if (this.eatContextual("as")) { + node.local = this.parseIdent() + } else { + node.local = node.imported + if (this.isKeyword(node.local.name)) this.unexpected(node.local.start) + if (this.reservedWordsStrict.test(node.local.name)) this.raiseRecoverable(node.local.start, "The keyword '" + node.local.name + "' is reserved") + } + this.checkLVal(node.local, "let") + nodes.push(this.finishNode(node, "ImportSpecifier")) + } + return nodes +} |