| import {types as tt} from "./tokentype" |
| import {Parser} from "./state" |
| import {lineBreak, skipWhiteSpace} from "./whitespace" |
| import {isIdentifierStart, isIdentifierChar} from "./identifier" |
| 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 first = true |
| if (!node.body) node.body = [] |
| while (this.type !== tt.eof) { |
| let stmt = this.parseStatement(true, true) |
| node.body.push(stmt) |
| if (first) { |
| if (this.isUseStrict(stmt)) this.setStrict(true) |
| first = false |
| } |
| } |
| 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)) { |
| for (var pos = next + 1; isIdentifierChar(this.input.charCodeAt(pos), true); ++pos) {} |
| let ident = this.input.slice(next, pos) |
| if (!this.isKeyword(ident)) return true |
| } |
| return false |
| } |
| |
| // 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) { |
| 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) |
| 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) |
| |
| // 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: |
| 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. |
| for (var i = 0; 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.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.checkPatternErrors(refDestructuringErrors, true) |
| this.toAssignable(init) |
| this.checkLVal(init) |
| return this.parseForIn(node, init) |
| } else { |
| this.checkExpressionErrors(refDestructuringErrors, true) |
| } |
| 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(tt._else) ? this.parseStatement(false) : 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) |
| |
| // Statements under must be grouped (by label) in SwitchCase |
| // nodes. `cur` is used to keep the node that we are currently |
| // adding statements to. |
| |
| for (var cur, 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)) |
| } |
| } |
| 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.checkLVal(clause.param, true) |
| this.expect(tt.parenR) |
| clause.body = this.parseBlock() |
| 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) |
| 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(allowStrict) { |
| let node = this.startNode(), first = true, oldStrict |
| node.body = [] |
| this.expect(tt.braceL) |
| while (!this.eat(tt.braceR)) { |
| let stmt = this.parseStatement(true) |
| node.body.push(stmt) |
| if (first && allowStrict && this.isUseStrict(stmt)) { |
| oldStrict = this.strict |
| this.setStrict(this.strict = true) |
| } |
| first = false |
| } |
| if (oldStrict === false) this.setStrict(false) |
| 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) |
| 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) |
| 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) |
| 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) { |
| decl.id = this.parseBindingAtom() |
| this.checkLVal(decl.id, true) |
| } |
| |
| // Parse a function declaration or literal (depending on the |
| // `isStatement` parameter). |
| |
| pp.parseFunction = function(node, isStatement, allowExpressionBody) { |
| this.initFunction(node) |
| if (this.options.ecmaVersion >= 6) |
| node.generator = this.eat(tt.star) |
| var oldInGen = this.inGenerator |
| this.inGenerator = node.generator |
| if (isStatement || this.type === tt.name) |
| node.id = this.parseIdent() |
| this.parseFunctionParams(node) |
| this.parseFunctionBody(node, allowExpressionBody) |
| this.inGenerator = oldInGen |
| return this.finishNode(node, isStatement ? "FunctionDeclaration" : "FunctionExpression") |
| } |
| |
| pp.parseFunctionParams = function(node) { |
| this.expect(tt.parenL) |
| node.params = this.parseBindingList(tt.parenR, false, false, true) |
| } |
| |
| // 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 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) |
| } |
| method.kind = "method" |
| let isGetSet = false |
| if (!method.computed) { |
| let {key} = method |
| if (!isGenerator && 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") |
| method.kind = "constructor" |
| hadConstructor = true |
| } |
| } |
| this.parseClassMethod(classBody, method, isGenerator) |
| 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") |
| } |
| if (method.kind === "set" && method.value.params[0].type === "RestElement") |
| this.raise(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) { |
| method.value = this.parseMethod(isGenerator) |
| classBody.body.push(this.finishNode(method, "MethodDefinition")) |
| } |
| |
| pp.parseClassId = function(node, isStatement) { |
| node.id = this.type === tt.name ? this.parseIdent() : isStatement ? this.unexpected() : null |
| } |
| |
| pp.parseClassSuper = function(node) { |
| node.superClass = this.eat(tt._extends) ? this.parseExprSubscripts() : null |
| } |
| |
| // Parses module export declaration. |
| |
| pp.parseExport = function(node) { |
| 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 ... |
| let parens = this.type == tt.parenL |
| let expr = this.parseMaybeAssign() |
| let needsSemi = true |
| if (!parens && (expr.type == "FunctionExpression" || |
| expr.type == "ClassExpression")) { |
| needsSemi = false |
| if (expr.id) { |
| expr.type = expr.type == "FunctionExpression" |
| ? "FunctionDeclaration" |
| : "ClassDeclaration" |
| } |
| } |
| node.declaration = expr |
| if (needsSemi) this.semicolon() |
| return this.finishNode(node, "ExportDefaultDeclaration") |
| } |
| // export var|const|let|function|class ... |
| if (this.shouldParseExportStatement()) { |
| node.declaration = this.parseStatement(true) |
| node.specifiers = [] |
| node.source = null |
| } else { // export { x, y as z } [from '...'] |
| node.declaration = null |
| node.specifiers = this.parseExportSpecifiers() |
| 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.shouldParseExportStatement = function() { |
| return this.type.keyword || this.isLet() |
| } |
| |
| // Parses a comma-separated list of module exports. |
| |
| pp.parseExportSpecifiers = function() { |
| 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(this.type === tt._default) |
| node.exported = this.eatContextual("as") ? this.parseIdent(true) : node.local |
| 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, true) |
| 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, true) |
| 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.raise(node.local.start, "The keyword '" + node.local.name + "' is reserved") |
| } |
| this.checkLVal(node.local, true) |
| nodes.push(this.finishNode(node, "ImportSpecifier")) |
| } |
| return nodes |
| } |