import {LooseParser} from "./state" import {isDummy} from "./parseutil" import {tokTypes as tt} from "../index" const lp = LooseParser.prototype lp.checkLVal = function(expr) { if (!expr) return expr switch (expr.type) { case "Identifier": case "MemberExpression": return expr case "ParenthesizedExpression": expr.expression = this.checkLVal(expr.expression) return expr default: return this.dummyIdent() } } lp.parseExpression = function(noIn) { let start = this.storeCurrentPos() let expr = this.parseMaybeAssign(noIn) if (this.tok.type === tt.comma) { let node = this.startNodeAt(start) node.expressions = [expr] while (this.eat(tt.comma)) node.expressions.push(this.parseMaybeAssign(noIn)) return this.finishNode(node, "SequenceExpression") } return expr } lp.parseParenExpression = function() { this.pushCx() this.expect(tt.parenL) let val = this.parseExpression() this.popCx() this.expect(tt.parenR) return val } lp.parseMaybeAssign = function(noIn) { if (this.toks.isContextual("yield")) { let node = this.startNode() this.next() if (this.semicolon() || this.canInsertSemicolon() || (this.tok.type != tt.star && !this.tok.type.startsExpr)) { node.delegate = false node.argument = null } else { node.delegate = this.eat(tt.star) node.argument = this.parseMaybeAssign() } return this.finishNode(node, "YieldExpression") } let start = this.storeCurrentPos() let left = this.parseMaybeConditional(noIn) if (this.tok.type.isAssign) { let node = this.startNodeAt(start) node.operator = this.tok.value node.left = this.tok.type === tt.eq ? this.toAssignable(left) : this.checkLVal(left) this.next() node.right = this.parseMaybeAssign(noIn) return this.finishNode(node, "AssignmentExpression") } return left } lp.parseMaybeConditional = function(noIn) { let start = this.storeCurrentPos() let expr = this.parseExprOps(noIn) if (this.eat(tt.question)) { let node = this.startNodeAt(start) node.test = expr node.consequent = this.parseMaybeAssign() node.alternate = this.expect(tt.colon) ? this.parseMaybeAssign(noIn) : this.dummyIdent() return this.finishNode(node, "ConditionalExpression") } return expr } lp.parseExprOps = function(noIn) { let start = this.storeCurrentPos() let indent = this.curIndent, line = this.curLineStart return this.parseExprOp(this.parseMaybeUnary(false), start, -1, noIn, indent, line) } lp.parseExprOp = function(left, start, minPrec, noIn, indent, line) { if (this.curLineStart != line && this.curIndent < indent && this.tokenStartsLine()) return left let prec = this.tok.type.binop if (prec != null && (!noIn || this.tok.type !== tt._in)) { if (prec > minPrec) { let node = this.startNodeAt(start) node.left = left node.operator = this.tok.value this.next() if (this.curLineStart != line && this.curIndent < indent && this.tokenStartsLine()) { node.right = this.dummyIdent() } else { let rightStart = this.storeCurrentPos() node.right = this.parseExprOp(this.parseMaybeUnary(false), rightStart, prec, noIn, indent, line) } this.finishNode(node, /&&|\|\|/.test(node.operator) ? "LogicalExpression" : "BinaryExpression") return this.parseExprOp(node, start, minPrec, noIn, indent, line) } } return left } lp.parseMaybeUnary = function(sawUnary) { let start = this.storeCurrentPos(), expr if (this.options.ecmaVersion >= 8 && this.inAsync && this.toks.isContextual("await")) { expr = this.parseAwait() sawUnary = true } else if (this.tok.type.prefix) { let node = this.startNode(), update = this.tok.type === tt.incDec if (!update) sawUnary = true node.operator = this.tok.value node.prefix = true this.next() node.argument = this.parseMaybeUnary(true) if (update) node.argument = this.checkLVal(node.argument) expr = this.finishNode(node, update ? "UpdateExpression" : "UnaryExpression") } else if (this.tok.type === tt.ellipsis) { let node = this.startNode() this.next() node.argument = this.parseMaybeUnary(sawUnary) expr = this.finishNode(node, "SpreadElement") } else { expr = this.parseExprSubscripts() while (this.tok.type.postfix && !this.canInsertSemicolon()) { let node = this.startNodeAt(start) node.operator = this.tok.value node.prefix = false node.argument = this.checkLVal(expr) this.next() expr = this.finishNode(node, "UpdateExpression") } } if (!sawUnary && this.eat(tt.starstar)) { let node = this.startNodeAt(start) node.operator = "**" node.left = expr node.right = this.parseMaybeUnary(false) return this.finishNode(node, "BinaryExpression") } return expr } lp.parseExprSubscripts = function() { let start = this.storeCurrentPos() return this.parseSubscripts(this.parseExprAtom(), start, false, this.curIndent, this.curLineStart) } lp.parseSubscripts = function(base, start, noCalls, startIndent, line) { for (;;) { if (this.curLineStart != line && this.curIndent <= startIndent && this.tokenStartsLine()) { if (this.tok.type == tt.dot && this.curIndent == startIndent) --startIndent else return base } let maybeAsyncArrow = base.type === "Identifier" && base.name === "async" && !this.canInsertSemicolon() if (this.eat(tt.dot)) { let node = this.startNodeAt(start) node.object = base if (this.curLineStart != line && this.curIndent <= startIndent && this.tokenStartsLine()) node.property = this.dummyIdent() else node.property = this.parsePropertyAccessor() || this.dummyIdent() node.computed = false base = this.finishNode(node, "MemberExpression") } else if (this.tok.type == tt.bracketL) { this.pushCx() this.next() let node = this.startNodeAt(start) node.object = base node.property = this.parseExpression() node.computed = true this.popCx() this.expect(tt.bracketR) base = this.finishNode(node, "MemberExpression") } else if (!noCalls && this.tok.type == tt.parenL) { let exprList = this.parseExprList(tt.parenR) if (maybeAsyncArrow && this.eat(tt.arrow)) return this.parseArrowExpression(this.startNodeAt(start), exprList, true) let node = this.startNodeAt(start) node.callee = base node.arguments = exprList base = this.finishNode(node, "CallExpression") } else if (this.tok.type == tt.backQuote) { let node = this.startNodeAt(start) node.tag = base node.quasi = this.parseTemplate() base = this.finishNode(node, "TaggedTemplateExpression") } else { return base } } } lp.parseExprAtom = function() { let node switch (this.tok.type) { case tt._this: case tt._super: let type = this.tok.type === tt._this ? "ThisExpression" : "Super" node = this.startNode() this.next() return this.finishNode(node, type) case tt.name: let start = this.storeCurrentPos() let id = this.parseIdent() let isAsync = false if (id.name === "async" && !this.canInsertSemicolon()) { if (this.eat(tt._function)) return this.parseFunction(this.startNodeAt(start), false, true) if (this.tok.type === tt.name) { id = this.parseIdent() isAsync = true } } return this.eat(tt.arrow) ? this.parseArrowExpression(this.startNodeAt(start), [id], isAsync) : id case tt.regexp: node = this.startNode() let val = this.tok.value node.regex = {pattern: val.pattern, flags: val.flags} node.value = val.value node.raw = this.input.slice(this.tok.start, this.tok.end) this.next() return this.finishNode(node, "Literal") case tt.num: case tt.string: node = this.startNode() node.value = this.tok.value node.raw = this.input.slice(this.tok.start, this.tok.end) this.next() return this.finishNode(node, "Literal") case tt._null: case tt._true: case tt._false: node = this.startNode() node.value = this.tok.type === tt._null ? null : this.tok.type === tt._true node.raw = this.tok.type.keyword this.next() return this.finishNode(node, "Literal") case tt.parenL: let parenStart = this.storeCurrentPos() this.next() let inner = this.parseExpression() this.expect(tt.parenR) if (this.eat(tt.arrow)) { // (a,)=>a // SequenceExpression makes dummy in the last hole. Drop the dummy. let params = inner.expressions || [inner] if (params.length && isDummy(params[params.length - 1])) params.pop() return this.parseArrowExpression(this.startNodeAt(parenStart), params) } if (this.options.preserveParens) { let par = this.startNodeAt(parenStart) par.expression = inner inner = this.finishNode(par, "ParenthesizedExpression") } return inner case tt.bracketL: node = this.startNode() node.elements = this.parseExprList(tt.bracketR, true) return this.finishNode(node, "ArrayExpression") case tt.braceL: return this.parseObj() case tt._class: return this.parseClass(false) case tt._function: node = this.startNode() this.next() return this.parseFunction(node, false) case tt._new: return this.parseNew() case tt.backQuote: return this.parseTemplate() default: return this.dummyIdent() } } lp.parseNew = function() { let node = this.startNode(), startIndent = this.curIndent, line = this.curLineStart let meta = this.parseIdent(true) if (this.options.ecmaVersion >= 6 && this.eat(tt.dot)) { node.meta = meta node.property = this.parseIdent(true) return this.finishNode(node, "MetaProperty") } let start = this.storeCurrentPos() node.callee = this.parseSubscripts(this.parseExprAtom(), start, true, startIndent, line) if (this.tok.type == tt.parenL) { node.arguments = this.parseExprList(tt.parenR) } else { node.arguments = [] } return this.finishNode(node, "NewExpression") } lp.parseTemplateElement = function() { let elem = this.startNode() elem.value = { raw: this.input.slice(this.tok.start, this.tok.end).replace(/\r\n?/g, '\n'), cooked: this.tok.value } this.next() elem.tail = this.tok.type === tt.backQuote return this.finishNode(elem, "TemplateElement") } lp.parseTemplate = function() { let node = this.startNode() this.next() node.expressions = [] let curElt = this.parseTemplateElement() node.quasis = [curElt] while (!curElt.tail) { this.next() node.expressions.push(this.parseExpression()) if (this.expect(tt.braceR)) { curElt = this.parseTemplateElement() } else { curElt = this.startNode() curElt.value = {cooked: '', raw: ''} curElt.tail = true this.finishNode(curElt, "TemplateElement") } node.quasis.push(curElt) } this.expect(tt.backQuote) return this.finishNode(node, "TemplateLiteral") } lp.parseObj = function() { let node = this.startNode() node.properties = [] this.pushCx() let indent = this.curIndent + 1, line = this.curLineStart this.eat(tt.braceL) if (this.curIndent + 1 < indent) { indent = this.curIndent; line = this.curLineStart } while (!this.closes(tt.braceR, indent, line)) { let prop = this.startNode(), isGenerator, isAsync, start if (this.options.ecmaVersion >= 6) { start = this.storeCurrentPos() prop.method = false prop.shorthand = false isGenerator = this.eat(tt.star) } this.parsePropertyName(prop) if (!prop.computed && prop.key.type === "Identifier" && prop.key.name === "async" && this.tok.type !== tt.parenL && this.tok.type !== tt.colon && !this.canInsertSemicolon()) { this.parsePropertyName(prop) isAsync = true } else { isAsync = false } if (isDummy(prop.key)) { if (isDummy(this.parseMaybeAssign())) this.next(); this.eat(tt.comma); continue } if (this.eat(tt.colon)) { prop.kind = "init" prop.value = this.parseMaybeAssign() } else if (this.options.ecmaVersion >= 6 && (this.tok.type === tt.parenL || this.tok.type === tt.braceL)) { prop.kind = "init" prop.method = true prop.value = this.parseMethod(isGenerator, isAsync) } else if (this.options.ecmaVersion >= 5 && prop.key.type === "Identifier" && !prop.computed && (prop.key.name === "get" || prop.key.name === "set") && (this.tok.type != tt.comma && this.tok.type != tt.braceR)) { prop.kind = prop.key.name this.parsePropertyName(prop) prop.value = this.parseMethod(false) } else { prop.kind = "init" if (this.options.ecmaVersion >= 6) { if (this.eat(tt.eq)) { let assign = this.startNodeAt(start) assign.operator = "=" assign.left = prop.key assign.right = this.parseMaybeAssign() prop.value = this.finishNode(assign, "AssignmentExpression") } else { prop.value = prop.key } } else { prop.value = this.dummyIdent() } prop.shorthand = true } node.properties.push(this.finishNode(prop, "Property")) this.eat(tt.comma) } this.popCx() if (!this.eat(tt.braceR)) { // If there is no closing brace, make the node span to the start // of the next token (this is useful for Tern) this.last.end = this.tok.start if (this.options.locations) this.last.loc.end = this.tok.loc.start } return this.finishNode(node, "ObjectExpression") } lp.parsePropertyName = function(prop) { if (this.options.ecmaVersion >= 6) { if (this.eat(tt.bracketL)) { prop.computed = true prop.key = this.parseExpression() this.expect(tt.bracketR) return } else { prop.computed = false } } let key = (this.tok.type === tt.num || this.tok.type === tt.string) ? this.parseExprAtom() : this.parseIdent() prop.key = key || this.dummyIdent() } lp.parsePropertyAccessor = function() { if (this.tok.type === tt.name || this.tok.type.keyword) return this.parseIdent() } lp.parseIdent = function() { let name = this.tok.type === tt.name ? this.tok.value : this.tok.type.keyword if (!name) return this.dummyIdent() let node = this.startNode() this.next() node.name = name return this.finishNode(node, "Identifier") } lp.initFunction = function(node) { node.id = null node.params = [] if (this.options.ecmaVersion >= 6) { node.generator = false node.expression = false } if (this.options.ecmaVersion >= 8) node.async = false } // Convert existing expression atom to assignable pattern // if possible. lp.toAssignable = function(node, binding) { if (!node || node.type == "Identifier" || (node.type == "MemberExpression" && !binding)) { // Okay } else if (node.type == "ParenthesizedExpression") { node.expression = this.toAssignable(node.expression, binding) } else if (this.options.ecmaVersion < 6) { return this.dummyIdent() } else if (node.type == "ObjectExpression") { node.type = "ObjectPattern" let props = node.properties for (let i = 0; i < props.length; i++) props[i].value = this.toAssignable(props[i].value, binding) } else if (node.type == "ArrayExpression") { node.type = "ArrayPattern" this.toAssignableList(node.elements, binding) } else if (node.type == "SpreadElement") { node.type = "RestElement" node.argument = this.toAssignable(node.argument, binding) } else if (node.type == "AssignmentExpression") { node.type = "AssignmentPattern" delete node.operator } else { return this.dummyIdent() } return node } lp.toAssignableList = function(exprList, binding) { for (let i = 0; i < exprList.length; i++) exprList[i] = this.toAssignable(exprList[i], binding) return exprList } lp.parseFunctionParams = function(params) { params = this.parseExprList(tt.parenR) return this.toAssignableList(params, true) } lp.parseMethod = function(isGenerator, isAsync) { let node = this.startNode(), oldInAsync = this.inAsync this.initFunction(node) if (this.options.ecmaVersion >= 6) node.generator = !!isGenerator if (this.options.ecmaVersion >= 8) node.async = !!isAsync this.inAsync = node.async node.params = this.parseFunctionParams() node.expression = this.options.ecmaVersion >= 6 && this.tok.type !== tt.braceL node.body = node.expression ? this.parseMaybeAssign() : this.parseBlock() this.inAsync = oldInAsync return this.finishNode(node, "FunctionExpression") } lp.parseArrowExpression = function(node, params, isAsync) { let oldInAsync = this.inAsync this.initFunction(node) if (this.options.ecmaVersion >= 8) node.async = !!isAsync this.inAsync = node.async node.params = this.toAssignableList(params, true) node.expression = this.tok.type !== tt.braceL node.body = node.expression ? this.parseMaybeAssign() : this.parseBlock() this.inAsync = oldInAsync return this.finishNode(node, "ArrowFunctionExpression") } lp.parseExprList = function(close, allowEmpty) { this.pushCx() let indent = this.curIndent, line = this.curLineStart, elts = [] this.next() // Opening bracket while (!this.closes(close, indent + 1, line)) { if (this.eat(tt.comma)) { elts.push(allowEmpty ? null : this.dummyIdent()) continue } let elt = this.parseMaybeAssign() if (isDummy(elt)) { if (this.closes(close, indent, line)) break this.next() } else { elts.push(elt) } this.eat(tt.comma) } this.popCx() if (!this.eat(close)) { // If there is no closing brace, make the node span to the start // of the next token (this is useful for Tern) this.last.end = this.tok.start if (this.options.locations) this.last.loc.end = this.tok.loc.start } return elts } lp.parseAwait = function() { let node = this.startNode() this.next() node.argument = this.parseMaybeUnary() return this.finishNode(node, "AwaitExpression") }