You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
421 lines
14 KiB
JavaScript
421 lines
14 KiB
JavaScript
import {LooseParser} from "./state"
|
|
import {isDummy} from "./parseutil"
|
|
import {getLineInfo, tokTypes as tt} from ".."
|
|
|
|
const lp = LooseParser.prototype
|
|
|
|
lp.parseTopLevel = function() {
|
|
let node = this.startNodeAt(this.options.locations ? [0, getLineInfo(this.input, 0)] : 0)
|
|
node.body = []
|
|
while (this.tok.type !== tt.eof) node.body.push(this.parseStatement())
|
|
this.last = this.tok
|
|
if (this.options.ecmaVersion >= 6) {
|
|
node.sourceType = this.options.sourceType
|
|
}
|
|
return this.finishNode(node, "Program")
|
|
}
|
|
|
|
lp.parseStatement = function() {
|
|
let starttype = this.tok.type, node = this.startNode()
|
|
|
|
switch (starttype) {
|
|
case tt._break: case tt._continue:
|
|
this.next()
|
|
let isBreak = starttype === tt._break
|
|
if (this.semicolon() || this.canInsertSemicolon()) {
|
|
node.label = null
|
|
} else {
|
|
node.label = this.tok.type === tt.name ? this.parseIdent() : null
|
|
this.semicolon()
|
|
}
|
|
return this.finishNode(node, isBreak ? "BreakStatement" : "ContinueStatement")
|
|
|
|
case tt._debugger:
|
|
this.next()
|
|
this.semicolon()
|
|
return this.finishNode(node, "DebuggerStatement")
|
|
|
|
case tt._do:
|
|
this.next()
|
|
node.body = this.parseStatement()
|
|
node.test = this.eat(tt._while) ? this.parseParenExpression() : this.dummyIdent()
|
|
this.semicolon()
|
|
return this.finishNode(node, "DoWhileStatement")
|
|
|
|
case tt._for:
|
|
this.next()
|
|
this.pushCx()
|
|
this.expect(tt.parenL)
|
|
if (this.tok.type === tt.semi) return this.parseFor(node, null)
|
|
if (this.tok.type === tt._var || this.tok.type === tt._let || this.tok.type === tt._const) {
|
|
let init = this.parseVar(true)
|
|
if (init.declarations.length === 1 && (this.tok.type === tt._in || this.isContextual("of"))) {
|
|
return this.parseForIn(node, init)
|
|
}
|
|
return this.parseFor(node, init)
|
|
}
|
|
let init = this.parseExpression(true)
|
|
if (this.tok.type === tt._in || this.isContextual("of"))
|
|
return this.parseForIn(node, this.toAssignable(init))
|
|
return this.parseFor(node, init)
|
|
|
|
case tt._function:
|
|
this.next()
|
|
return this.parseFunction(node, true)
|
|
|
|
case tt._if:
|
|
this.next()
|
|
node.test = this.parseParenExpression()
|
|
node.consequent = this.parseStatement()
|
|
node.alternate = this.eat(tt._else) ? this.parseStatement() : null
|
|
return this.finishNode(node, "IfStatement")
|
|
|
|
case tt._return:
|
|
this.next()
|
|
if (this.eat(tt.semi) || this.canInsertSemicolon()) node.argument = null
|
|
else { node.argument = this.parseExpression(); this.semicolon() }
|
|
return this.finishNode(node, "ReturnStatement")
|
|
|
|
case tt._switch:
|
|
let blockIndent = this.curIndent, line = this.curLineStart
|
|
this.next()
|
|
node.discriminant = this.parseParenExpression()
|
|
node.cases = []
|
|
this.pushCx()
|
|
this.expect(tt.braceL)
|
|
|
|
let cur
|
|
while (!this.closes(tt.braceR, blockIndent, line, true)) {
|
|
if (this.tok.type === tt._case || this.tok.type === tt._default) {
|
|
let isCase = this.tok.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 cur.test = null
|
|
this.expect(tt.colon)
|
|
} else {
|
|
if (!cur) {
|
|
node.cases.push(cur = this.startNode())
|
|
cur.consequent = []
|
|
cur.test = null
|
|
}
|
|
cur.consequent.push(this.parseStatement())
|
|
}
|
|
}
|
|
if (cur) this.finishNode(cur, "SwitchCase")
|
|
this.popCx()
|
|
this.eat(tt.braceR)
|
|
return this.finishNode(node, "SwitchStatement")
|
|
|
|
case tt._throw:
|
|
this.next()
|
|
node.argument = this.parseExpression()
|
|
this.semicolon()
|
|
return this.finishNode(node, "ThrowStatement")
|
|
|
|
case tt._try:
|
|
this.next()
|
|
node.block = this.parseBlock()
|
|
node.handler = null
|
|
if (this.tok.type === tt._catch) {
|
|
let clause = this.startNode()
|
|
this.next()
|
|
this.expect(tt.parenL)
|
|
clause.param = this.toAssignable(this.parseExprAtom(), 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) return node.block
|
|
return this.finishNode(node, "TryStatement")
|
|
|
|
case tt._var:
|
|
case tt._let:
|
|
case tt._const:
|
|
return this.parseVar()
|
|
|
|
case tt._while:
|
|
this.next()
|
|
node.test = this.parseParenExpression()
|
|
node.body = this.parseStatement()
|
|
return this.finishNode(node, "WhileStatement")
|
|
|
|
case tt._with:
|
|
this.next()
|
|
node.object = this.parseParenExpression()
|
|
node.body = this.parseStatement()
|
|
return this.finishNode(node, "WithStatement")
|
|
|
|
case tt.braceL:
|
|
return this.parseBlock()
|
|
|
|
case tt.semi:
|
|
this.next()
|
|
return this.finishNode(node, "EmptyStatement")
|
|
|
|
case tt._class:
|
|
return this.parseClass(true)
|
|
|
|
case tt._import:
|
|
return this.parseImport()
|
|
|
|
case tt._export:
|
|
return this.parseExport()
|
|
|
|
default:
|
|
let expr = this.parseExpression()
|
|
if (isDummy(expr)) {
|
|
this.next()
|
|
if (this.tok.type === tt.eof) return this.finishNode(node, "EmptyStatement")
|
|
return this.parseStatement()
|
|
} else if (starttype === tt.name && expr.type === "Identifier" && this.eat(tt.colon)) {
|
|
node.body = this.parseStatement()
|
|
node.label = expr
|
|
return this.finishNode(node, "LabeledStatement")
|
|
} else {
|
|
node.expression = expr
|
|
this.semicolon()
|
|
return this.finishNode(node, "ExpressionStatement")
|
|
}
|
|
}
|
|
}
|
|
|
|
lp.parseBlock = function() {
|
|
let node = this.startNode()
|
|
this.pushCx()
|
|
this.expect(tt.braceL)
|
|
let blockIndent = this.curIndent, line = this.curLineStart
|
|
node.body = []
|
|
while (!this.closes(tt.braceR, blockIndent, line, true))
|
|
node.body.push(this.parseStatement())
|
|
this.popCx()
|
|
this.eat(tt.braceR)
|
|
return this.finishNode(node, "BlockStatement")
|
|
}
|
|
|
|
lp.parseFor = function(node, init) {
|
|
node.init = init
|
|
node.test = node.update = null
|
|
if (this.eat(tt.semi) && this.tok.type !== tt.semi) node.test = this.parseExpression()
|
|
if (this.eat(tt.semi) && this.tok.type !== tt.parenR) node.update = this.parseExpression()
|
|
this.popCx()
|
|
this.expect(tt.parenR)
|
|
node.body = this.parseStatement()
|
|
return this.finishNode(node, "ForStatement")
|
|
}
|
|
|
|
lp.parseForIn = function(node, init) {
|
|
let type = this.tok.type === tt._in ? "ForInStatement" : "ForOfStatement"
|
|
this.next()
|
|
node.left = init
|
|
node.right = this.parseExpression()
|
|
this.popCx()
|
|
this.expect(tt.parenR)
|
|
node.body = this.parseStatement()
|
|
return this.finishNode(node, type)
|
|
}
|
|
|
|
lp.parseVar = function(noIn) {
|
|
let node = this.startNode()
|
|
node.kind = this.tok.type.keyword
|
|
this.next()
|
|
node.declarations = []
|
|
do {
|
|
let decl = this.startNode()
|
|
decl.id = this.options.ecmaVersion >= 6 ? this.toAssignable(this.parseExprAtom(), true) : this.parseIdent()
|
|
decl.init = this.eat(tt.eq) ? this.parseMaybeAssign(noIn) : null
|
|
node.declarations.push(this.finishNode(decl, "VariableDeclarator"))
|
|
} while (this.eat(tt.comma))
|
|
if (!node.declarations.length) {
|
|
let decl = this.startNode()
|
|
decl.id = this.dummyIdent()
|
|
node.declarations.push(this.finishNode(decl, "VariableDeclarator"))
|
|
}
|
|
if (!noIn) this.semicolon()
|
|
return this.finishNode(node, "VariableDeclaration")
|
|
}
|
|
|
|
lp.parseClass = function(isStatement) {
|
|
let node = this.startNode()
|
|
this.next()
|
|
if (this.tok.type === tt.name) node.id = this.parseIdent()
|
|
else if (isStatement) node.id = this.dummyIdent()
|
|
else node.id = null
|
|
node.superClass = this.eat(tt._extends) ? this.parseExpression() : null
|
|
node.body = this.startNode()
|
|
node.body.body = []
|
|
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)) {
|
|
if (this.semicolon()) continue
|
|
let method = this.startNode(), isGenerator
|
|
if (this.options.ecmaVersion >= 6) {
|
|
method.static = false
|
|
isGenerator = this.eat(tt.star)
|
|
}
|
|
this.parsePropertyName(method)
|
|
if (isDummy(method.key)) { if (isDummy(this.parseMaybeAssign())) this.next(); this.eat(tt.comma); continue }
|
|
if (method.key.type === "Identifier" && !method.computed && method.key.name === "static" &&
|
|
(this.tok.type != tt.parenL && this.tok.type != tt.braceL)) {
|
|
method.static = true
|
|
isGenerator = this.eat(tt.star)
|
|
this.parsePropertyName(method)
|
|
} else {
|
|
method.static = false
|
|
}
|
|
if (this.options.ecmaVersion >= 5 && method.key.type === "Identifier" &&
|
|
!method.computed && (method.key.name === "get" || method.key.name === "set") &&
|
|
this.tok.type !== tt.parenL && this.tok.type !== tt.braceL) {
|
|
method.kind = method.key.name
|
|
this.parsePropertyName(method)
|
|
method.value = this.parseMethod(false)
|
|
} else {
|
|
if (!method.computed && !method.static && !isGenerator && (
|
|
method.key.type === "Identifier" && method.key.name === "constructor" ||
|
|
method.key.type === "Literal" && method.key.value === "constructor")) {
|
|
method.kind = "constructor"
|
|
} else {
|
|
method.kind = "method"
|
|
}
|
|
method.value = this.parseMethod(isGenerator)
|
|
}
|
|
node.body.body.push(this.finishNode(method, "MethodDefinition"))
|
|
}
|
|
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
|
|
}
|
|
this.semicolon()
|
|
this.finishNode(node.body, "ClassBody")
|
|
return this.finishNode(node, isStatement ? "ClassDeclaration" : "ClassExpression")
|
|
}
|
|
|
|
lp.parseFunction = function(node, isStatement) {
|
|
this.initFunction(node)
|
|
if (this.options.ecmaVersion >= 6) {
|
|
node.generator = this.eat(tt.star)
|
|
}
|
|
if (this.tok.type === tt.name) node.id = this.parseIdent()
|
|
else if (isStatement) node.id = this.dummyIdent()
|
|
node.params = this.parseFunctionParams()
|
|
node.body = this.parseBlock()
|
|
return this.finishNode(node, isStatement ? "FunctionDeclaration" : "FunctionExpression")
|
|
}
|
|
|
|
lp.parseExport = function() {
|
|
let node = this.startNode()
|
|
this.next()
|
|
if (this.eat(tt.star)) {
|
|
node.source = this.eatContextual("from") ? this.parseExprAtom() : null
|
|
return this.finishNode(node, "ExportAllDeclaration")
|
|
}
|
|
if (this.eat(tt._default)) {
|
|
let expr = this.parseMaybeAssign()
|
|
if (expr.id) {
|
|
switch (expr.type) {
|
|
case "FunctionExpression": expr.type = "FunctionDeclaration"; break
|
|
case "ClassExpression": expr.type = "ClassDeclaration"; break
|
|
}
|
|
}
|
|
node.declaration = expr
|
|
this.semicolon()
|
|
return this.finishNode(node, "ExportDefaultDeclaration")
|
|
}
|
|
if (this.tok.type.keyword) {
|
|
node.declaration = this.parseStatement()
|
|
node.specifiers = []
|
|
node.source = null
|
|
} else {
|
|
node.declaration = null
|
|
node.specifiers = this.parseExportSpecifierList()
|
|
node.source = this.eatContextual("from") ? this.parseExprAtom() : null
|
|
this.semicolon()
|
|
}
|
|
return this.finishNode(node, "ExportNamedDeclaration")
|
|
}
|
|
|
|
lp.parseImport = function() {
|
|
let node = this.startNode()
|
|
this.next()
|
|
if (this.tok.type === tt.string) {
|
|
node.specifiers = []
|
|
node.source = this.parseExprAtom()
|
|
node.kind = ''
|
|
} else {
|
|
let elt
|
|
if (this.tok.type === tt.name && this.tok.value !== "from") {
|
|
elt = this.startNode()
|
|
elt.local = this.parseIdent()
|
|
this.finishNode(elt, "ImportDefaultSpecifier")
|
|
this.eat(tt.comma)
|
|
}
|
|
node.specifiers = this.parseImportSpecifierList()
|
|
node.source = this.eatContextual("from") && this.tok.type == tt.string ? this.parseExprAtom() : this.dummyString()
|
|
if (elt) node.specifiers.unshift(elt)
|
|
}
|
|
this.semicolon()
|
|
return this.finishNode(node, "ImportDeclaration")
|
|
}
|
|
|
|
lp.parseImportSpecifierList = function() {
|
|
let elts = []
|
|
if (this.tok.type === tt.star) {
|
|
let elt = this.startNode()
|
|
this.next()
|
|
if (this.eatContextual("as")) elt.local = this.parseIdent()
|
|
elts.push(this.finishNode(elt, "ImportNamespaceSpecifier"))
|
|
} else {
|
|
let indent = this.curIndent, line = this.curLineStart, continuedLine = this.nextLineStart
|
|
this.pushCx()
|
|
this.eat(tt.braceL)
|
|
if (this.curLineStart > continuedLine) continuedLine = this.curLineStart
|
|
while (!this.closes(tt.braceR, indent + (this.curLineStart <= continuedLine ? 1 : 0), line)) {
|
|
let elt = this.startNode()
|
|
if (this.eat(tt.star)) {
|
|
elt.local = this.eatContextual("as") ? this.parseIdent() : this.dummyIdent()
|
|
this.finishNode(elt, "ImportNamespaceSpecifier")
|
|
} else {
|
|
if (this.isContextual("from")) break
|
|
elt.imported = this.parseIdent()
|
|
if (isDummy(elt.imported)) break
|
|
elt.local = this.eatContextual("as") ? this.parseIdent() : elt.imported
|
|
this.finishNode(elt, "ImportSpecifier")
|
|
}
|
|
elts.push(elt)
|
|
this.eat(tt.comma)
|
|
}
|
|
this.eat(tt.braceR)
|
|
this.popCx()
|
|
}
|
|
return elts
|
|
}
|
|
|
|
lp.parseExportSpecifierList = function() {
|
|
let elts = []
|
|
let indent = this.curIndent, line = this.curLineStart, continuedLine = this.nextLineStart
|
|
this.pushCx()
|
|
this.eat(tt.braceL)
|
|
if (this.curLineStart > continuedLine) continuedLine = this.curLineStart
|
|
while (!this.closes(tt.braceR, indent + (this.curLineStart <= continuedLine ? 1 : 0), line)) {
|
|
if (this.isContextual("from")) break
|
|
let elt = this.startNode()
|
|
elt.local = this.parseIdent()
|
|
if (isDummy(elt.local)) break
|
|
elt.exported = this.eatContextual("as") ? this.parseIdent() : elt.local
|
|
this.finishNode(elt, "ExportSpecifier")
|
|
elts.push(elt)
|
|
this.eat(tt.comma)
|
|
}
|
|
this.eat(tt.braceR)
|
|
this.popCx()
|
|
return elts
|
|
}
|