-import {types as tt} from "./tokentype"
-import {Parser} from "./state"
-import {lineBreak, skipWhiteSpace} from "./whitespace"
-import {isIdentifierStart, isIdentifierChar, keywordRelationalOperator} from "./identifier"
-import {has} from "./util"
-import {DestructuringErrors} from "./parseutil"
-import {functionFlags, SCOPE_SIMPLE_CATCH, BIND_SIMPLE_CATCH, BIND_LEXICAL, BIND_VAR, BIND_FUNCTION} from "./scopeflags"
+import {types as tt} from "./tokentype.js"
+import {Parser} from "./state.js"
+import {lineBreak, skipWhiteSpace} from "./whitespace.js"
+import {isIdentifierStart, isIdentifierChar, keywordRelationalOperator} from "./identifier.js"
+import {hasOwn, loneSurrogate} from "./util.js"
+import {DestructuringErrors} from "./parseutil.js"
+import {functionFlags, SCOPE_SIMPLE_CATCH, BIND_SIMPLE_CATCH, BIND_LEXICAL, BIND_VAR, BIND_FUNCTION, SCOPE_CLASS_STATIC_BLOCK, SCOPE_SUPER} from "./scopeflags.js"
const pp = Parser.prototype
// to its body instead of creating a new node.
pp.parseTopLevel = function(node) {
- let exports = {}
+ let exports = Object.create(null)
if (!node.body) node.body = []
while (this.type !== tt.eof) {
let stmt = this.parseStatement(null, true, exports)
node.body.push(stmt)
}
+ if (this.inModule)
+ for (let name of Object.keys(this.undefinedExports))
+ this.raiseRecoverable(this.undefinedExports[name].start, `Export '${name}' is not defined`)
this.adaptDirectivePrologue(node.body)
this.next()
- if (this.options.ecmaVersion >= 6) {
- node.sourceType = this.options.sourceType
- }
+ node.sourceType = this.options.sourceType
return this.finishNode(node, "Program")
}
const loopLabel = {kind: "loop"}, switchLabel = {kind: "switch"}
-pp.isLet = function() {
+pp.isLet = function(context) {
if (this.options.ecmaVersion < 6 || !this.isContextual("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 '['
+ // For ambiguous cases, determine if a LexicalDeclaration (or only a
+ // Statement) is allowed here. If context is not empty then only a Statement
+ // is allowed. However, `let [` is an explicit negative lookahead for
+ // ExpressionStatement, so special-case it first.
+ if (nextCh === 91 || nextCh === 92 || nextCh > 0xd7ff && nextCh < 0xdc00) return true // '[', '/', astral
+ if (context) return false
+
+ if (nextCh === 123) return true // '{'
if (isIdentifierStart(nextCh, true)) {
let pos = next + 1
- while (isIdentifierChar(this.input.charCodeAt(pos), true)) ++pos
+ while (isIdentifierChar(nextCh = this.input.charCodeAt(pos), true)) ++pos
+ if (nextCh === 92 || nextCh > 0xd7ff && nextCh < 0xdc00) return true
let ident = this.input.slice(next, pos)
if (!keywordRelationalOperator.test(ident)) return true
}
skipWhiteSpace.lastIndex = this.pos
let skip = skipWhiteSpace.exec(this.input)
- let next = this.pos + skip[0].length
+ let next = this.pos + skip[0].length, after
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)))
+ (next + 8 === this.input.length ||
+ !(isIdentifierChar(after = this.input.charCodeAt(next + 8)) || after > 0xd7ff && after < 0xdc00))
}
// Parse a single statement.
pp.parseStatement = function(context, topLevel, exports) {
let starttype = this.type, node = this.startNode(), kind
- if (this.isLet()) {
+ if (this.isLet(context)) {
starttype = tt._var
kind = "let"
}
case tt._do: return this.parseDoStatement(node)
case tt._for: return this.parseForStatement(node)
case tt._function:
- if ((context && (this.strict || context !== "if")) && this.options.ecmaVersion >= 6) this.unexpected()
+ // Function as sole body of either an if statement or a labeled statement
+ // works, but not when it is part of a labeled statement that is the sole
+ // body of an if statement.
+ if ((context && (this.strict || context !== "if" && context !== "label")) && this.options.ecmaVersion >= 6) this.unexpected()
return this.parseFunctionStatement(node, false, !context)
case tt._class:
if (context) this.unexpected()
case tt.semi: return this.parseEmptyStatement(node)
case tt._export:
case tt._import:
+ if (this.options.ecmaVersion > 10 && starttype === tt._import) {
+ skipWhiteSpace.lastIndex = this.pos
+ let skip = skipWhiteSpace.exec(this.input)
+ let next = this.pos + skip[0].length, nextCh = this.input.charCodeAt(next)
+ if (nextCh === 40 || nextCh === 46) // '(' or '.'
+ return this.parseExpressionStatement(node, this.parseExpression())
+ }
+
if (!this.options.allowImportExportEverywhere) {
if (!topLevel)
this.raise(this.start, "'import' and 'export' may only appear at the top level")
pp.parseForStatement = function(node) {
this.next()
- let awaitAt = (this.options.ecmaVersion >= 9 && (this.inAsync || (!this.inFunction && this.options.allowAwaitOutsideFunction)) && this.eatContextual("await")) ? this.lastTokStart : -1
+ let awaitAt = (this.options.ecmaVersion >= 9 && this.canAwait && this.eatContextual("await")) ? this.lastTokStart : -1
this.labels.push(loopLabel)
this.enterScope(0)
this.expect(tt.parenL)
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)) {
+ if ((this.type === tt._in || (this.options.ecmaVersion >= 6 && this.isContextual("of"))) && init.declarations.length === 1) {
if (this.options.ecmaVersion >= 9) {
if (this.type === tt._in) {
if (awaitAt > -1) this.unexpected(awaitAt)
if (awaitAt > -1) this.unexpected(awaitAt)
return this.parseFor(node, init)
}
+ let startsWithLet = this.isContextual("let"), isForOf = false
let refDestructuringErrors = new DestructuringErrors
- let init = this.parseExpression(true, refDestructuringErrors)
- if (this.type === tt._in || (this.options.ecmaVersion >= 6 && this.isContextual("of"))) {
+ let init = this.parseExpression(awaitAt > -1 ? "await" : true, refDestructuringErrors)
+ if (this.type === tt._in || (isForOf = this.options.ecmaVersion >= 6 && this.isContextual("of"))) {
if (this.options.ecmaVersion >= 9) {
if (this.type === tt._in) {
if (awaitAt > -1) this.unexpected(awaitAt)
} else node.await = awaitAt > -1
}
+ if (startsWithLet && isForOf) this.raise(init.start, "The left-hand side of a for-of loop may not start with 'let'.")
this.toAssignable(init, false, refDestructuringErrors)
- this.checkLVal(init)
+ this.checkLValPattern(init)
return this.parseForIn(node, init)
} else {
this.checkExpressionErrors(refDestructuringErrors, true)
clause.param = this.parseBindingAtom()
let simple = clause.param.type === "Identifier"
this.enterScope(simple ? SCOPE_SIMPLE_CATCH : 0)
- this.checkLVal(clause.param, simple ? BIND_SIMPLE_CATCH : BIND_LEXICAL)
+ this.checkLValPattern(clause.param, simple ? BIND_SIMPLE_CATCH : BIND_LEXICAL)
this.expect(tt.parenR)
} else {
if (this.options.ecmaVersion < 10) this.unexpected()
} else break
}
this.labels.push({name: maybeName, kind, statementStart: this.start})
- node.body = this.parseStatement(context)
- if (node.body.type === "ClassDeclaration" ||
- node.body.type === "VariableDeclaration" && node.body.kind !== "var" ||
- node.body.type === "FunctionDeclaration" && (this.strict || node.body.generator || node.body.async))
- this.raiseRecoverable(node.body.start, "Invalid labeled declaration")
+ node.body = this.parseStatement(context ? context.indexOf("label") === -1 ? context + "label" : context : "label")
this.labels.pop()
node.label = expr
return this.finishNode(node, "LabeledStatement")
// strict"` declarations when `allowStrict` is true (used for
// function bodies).
-pp.parseBlock = function(createNewLexicalScope = true, node = this.startNode()) {
+pp.parseBlock = function(createNewLexicalScope = true, node = this.startNode(), exitStrict) {
node.body = []
this.expect(tt.braceL)
if (createNewLexicalScope) this.enterScope(0)
- while (!this.eat(tt.braceR)) {
+ while (this.type !== tt.braceR) {
let stmt = this.parseStatement(null)
node.body.push(stmt)
}
+ if (exitStrict) this.strict = false
+ this.next()
if (createNewLexicalScope) this.exitScope()
return this.finishNode(node, "BlockStatement")
}
this.expect(tt.semi)
node.update = this.type === tt.parenR ? null : this.parseExpression()
this.expect(tt.parenR)
- this.exitScope()
node.body = this.parseStatement("for")
+ this.exitScope()
this.labels.pop()
return this.finishNode(node, "ForStatement")
}
// same from parser's perspective.
pp.parseForIn = function(node, init) {
- let type = this.type === tt._in ? "ForInStatement" : "ForOfStatement"
+ const isForIn = this.type === tt._in
this.next()
- if (type === "ForInStatement") {
- if (init.type === "AssignmentPattern" ||
- (init.type === "VariableDeclaration" && init.declarations[0].init != null &&
- (this.strict || init.declarations[0].id.type !== "Identifier")))
- this.raise(init.start, "Invalid assignment in for-in loop head")
+
+ if (
+ init.type === "VariableDeclaration" &&
+ init.declarations[0].init != null &&
+ (
+ !isForIn ||
+ this.options.ecmaVersion < 8 ||
+ this.strict ||
+ init.kind !== "var" ||
+ init.declarations[0].id.type !== "Identifier"
+ )
+ ) {
+ this.raise(
+ init.start,
+ `${
+ isForIn ? "for-in" : "for-of"
+ } loop variable declaration may not have an initializer`
+ )
}
node.left = init
- node.right = type === "ForInStatement" ? this.parseExpression() : this.parseMaybeAssign()
+ node.right = isForIn ? this.parseExpression() : this.parseMaybeAssign()
this.expect(tt.parenR)
- this.exitScope()
node.body = this.parseStatement("for")
+ this.exitScope()
this.labels.pop()
- return this.finishNode(node, type)
+ return this.finishNode(node, isForIn ? "ForInStatement" : "ForOfStatement")
}
// Parse a list of variable declarations.
}
pp.parseVarId = function(decl, kind) {
- decl.id = this.parseBindingAtom(kind)
- this.checkLVal(decl.id, kind === "var" ? BIND_VAR : BIND_LEXICAL, false)
+ decl.id = this.parseBindingAtom()
+ this.checkLValPattern(decl.id, kind === "var" ? BIND_VAR : BIND_LEXICAL, false)
}
const FUNC_STATEMENT = 1, FUNC_HANGING_STATEMENT = 2, FUNC_NULLABLE_ID = 4
// Parse a function declaration or literal (depending on the
-// `isStatement` parameter).
+// `statement & FUNC_STATEMENT`).
-pp.parseFunction = function(node, statement, allowExpressionBody, isAsync) {
+// Remove `allowExpressionBody` for 7.0.0, as it is only called with false
+pp.parseFunction = function(node, statement, allowExpressionBody, isAsync, forInit) {
this.initFunction(node)
- if (this.options.ecmaVersion >= 9 || this.options.ecmaVersion >= 6 && !isAsync)
+ if (this.options.ecmaVersion >= 9 || this.options.ecmaVersion >= 6 && !isAsync) {
+ if (this.type === tt.star && (statement & FUNC_HANGING_STATEMENT))
+ this.unexpected()
node.generator = this.eat(tt.star)
+ }
if (this.options.ecmaVersion >= 8)
node.async = !!isAsync
if (statement & FUNC_STATEMENT) {
node.id = (statement & FUNC_NULLABLE_ID) && this.type !== tt.name ? null : this.parseIdent()
if (node.id && !(statement & FUNC_HANGING_STATEMENT))
- this.checkLVal(node.id, this.inModule && !this.inFunction ? BIND_LEXICAL : BIND_FUNCTION)
+ // If it is a regular function declaration in sloppy mode, then it is
+ // subject to Annex B semantics (BIND_FUNCTION). Otherwise, the binding
+ // mode depends on properties of the current scope (see
+ // treatFunctionsAsVar).
+ this.checkLValSimple(node.id, (this.strict || node.generator || node.async) ? this.treatFunctionsAsVar ? BIND_VAR : BIND_LEXICAL : BIND_FUNCTION)
}
- let oldYieldPos = this.yieldPos, oldAwaitPos = this.awaitPos
+ let oldYieldPos = this.yieldPos, oldAwaitPos = this.awaitPos, oldAwaitIdentPos = this.awaitIdentPos
this.yieldPos = 0
this.awaitPos = 0
+ this.awaitIdentPos = 0
this.enterScope(functionFlags(node.async, node.generator))
if (!(statement & FUNC_STATEMENT))
node.id = this.type === tt.name ? this.parseIdent() : null
this.parseFunctionParams(node)
- this.parseFunctionBody(node, allowExpressionBody)
+ this.parseFunctionBody(node, allowExpressionBody, false, forInit)
this.yieldPos = oldYieldPos
this.awaitPos = oldAwaitPos
+ this.awaitIdentPos = oldAwaitIdentPos
return this.finishNode(node, (statement & FUNC_STATEMENT) ? "FunctionDeclaration" : "FunctionExpression")
}
pp.parseClass = function(node, isStatement) {
this.next()
+ // ecma-262 14.6 Class Definitions
+ // A class definition is always strict mode code.
+ const oldStrict = this.strict
+ this.strict = true
+
this.parseClassId(node, isStatement)
this.parseClassSuper(node)
- let classBody = this.startNode()
+ const privateNameMap = this.enterClassBody()
+ const classBody = this.startNode()
let hadConstructor = false
classBody.body = []
this.expect(tt.braceL)
- while (!this.eat(tt.braceR)) {
+ while (this.type !== tt.braceR) {
const element = this.parseClassElement(node.superClass !== null)
if (element) {
classBody.body.push(element)
if (element.type === "MethodDefinition" && element.kind === "constructor") {
if (hadConstructor) this.raise(element.start, "Duplicate constructor in the same class")
hadConstructor = true
+ } else if (element.key && element.key.type === "PrivateIdentifier" && isPrivateNameConflicted(privateNameMap, element)) {
+ this.raiseRecoverable(element.key.start, `Identifier '#${element.key.name}' has already been declared`)
}
}
}
+ this.strict = oldStrict
+ this.next()
node.body = this.finishNode(classBody, "ClassBody")
+ this.exitClassBody()
return this.finishNode(node, isStatement ? "ClassDeclaration" : "ClassExpression")
}
pp.parseClassElement = function(constructorAllowsSuper) {
if (this.eat(tt.semi)) return null
- let method = this.startNode()
- const tryContextual = (k, noLineBreak = false) => {
- const start = this.start, startLoc = this.startLoc
- if (!this.eatContextual(k)) return false
- if (this.type !== tt.parenL && (!noLineBreak || !this.canInsertSemicolon())) return true
- if (method.key) this.unexpected()
- method.computed = false
- method.key = this.startNodeAt(start, startLoc)
- method.key.name = k
- this.finishNode(method.key, "Identifier")
- return false
- }
-
- method.kind = "method"
- method.static = tryContextual("static")
- let isGenerator = this.eat(tt.star)
+ const ecmaVersion = this.options.ecmaVersion
+ const node = this.startNode()
+ let keyName = ""
+ let isGenerator = false
let isAsync = false
- if (!isGenerator) {
- if (this.options.ecmaVersion >= 8 && tryContextual("async", true)) {
+ let kind = "method"
+ let isStatic = false
+
+ if (this.eatContextual("static")) {
+ // Parse static init block
+ if (ecmaVersion >= 13 && this.eat(tt.braceL)) {
+ this.parseClassStaticBlock(node)
+ return node
+ }
+ if (this.isClassElementNameStart() || this.type === tt.star) {
+ isStatic = true
+ } else {
+ keyName = "static"
+ }
+ }
+ node.static = isStatic
+ if (!keyName && ecmaVersion >= 8 && this.eatContextual("async")) {
+ if ((this.isClassElementNameStart() || this.type === tt.star) && !this.canInsertSemicolon()) {
isAsync = true
- isGenerator = this.options.ecmaVersion >= 9 && this.eat(tt.star)
- } else if (tryContextual("get")) {
- method.kind = "get"
- } else if (tryContextual("set")) {
- method.kind = "set"
+ } else {
+ keyName = "async"
+ }
+ }
+ if (!keyName && (ecmaVersion >= 9 || !isAsync) && this.eat(tt.star)) {
+ isGenerator = true
+ }
+ if (!keyName && !isAsync && !isGenerator) {
+ const lastValue = this.value
+ if (this.eatContextual("get") || this.eatContextual("set")) {
+ if (this.isClassElementNameStart()) {
+ kind = lastValue
+ } else {
+ keyName = lastValue
+ }
+ }
+ }
+
+ // Parse element name
+ if (keyName) {
+ // 'async', 'get', 'set', or 'static' were not a keyword contextually.
+ // The last token is any of those. Make it the element name.
+ node.computed = false
+ node.key = this.startNodeAt(this.lastTokStart, this.lastTokStartLoc)
+ node.key.name = keyName
+ this.finishNode(node.key, "Identifier")
+ } else {
+ this.parseClassElementName(node)
+ }
+
+ // Parse element value
+ if (ecmaVersion < 13 || this.type === tt.parenL || kind !== "method" || isGenerator || isAsync) {
+ const isConstructor = !node.static && checkKeyName(node, "constructor")
+ const allowsDirectSuper = isConstructor && constructorAllowsSuper
+ // Couldn't move this check into the 'parseClassMethod' method for backward compatibility.
+ if (isConstructor && kind !== "method") this.raise(node.key.start, "Constructor can't have get/set modifier")
+ node.kind = isConstructor ? "constructor" : kind
+ this.parseClassMethod(node, isGenerator, isAsync, allowsDirectSuper)
+ } else {
+ this.parseClassField(node)
+ }
+
+ return node
+}
+
+pp.isClassElementNameStart = function() {
+ return (
+ this.type === tt.name ||
+ this.type === tt.privateId ||
+ this.type === tt.num ||
+ this.type === tt.string ||
+ this.type === tt.bracketL ||
+ this.type.keyword
+ )
+}
+
+pp.parseClassElementName = function(element) {
+ if (this.type === tt.privateId) {
+ if (this.value === "constructor") {
+ this.raise(this.start, "Classes can't have an element named '#constructor'")
}
+ element.computed = false
+ element.key = this.parsePrivateIdent()
+ } else {
+ this.parsePropertyName(element)
}
- if (!method.key) this.parsePropertyName(method)
- let {key} = method
- let allowsDirectSuper = false
- if (!method.computed && !method.static && (key.type === "Identifier" && key.name === "constructor" ||
- key.type === "Literal" && key.value === "constructor")) {
- if (method.kind !== "method") this.raise(key.start, "Constructor can't have get/set modifier")
+}
+
+pp.parseClassMethod = function(method, isGenerator, isAsync, allowsDirectSuper) {
+ // Check key and flags
+ const key = method.key
+ if (method.kind === "constructor") {
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"
- allowsDirectSuper = constructorAllowsSuper
- } else if (method.static && key.type === "Identifier" && key.name === "prototype") {
+ } else if (method.static && checkKeyName(method, "prototype")) {
this.raise(key.start, "Classes may not have a static property named prototype")
}
- this.parseClassMethod(method, isGenerator, isAsync, allowsDirectSuper)
- if (method.kind === "get" && method.value.params.length !== 0)
- this.raiseRecoverable(method.value.start, "getter should have no params")
- if (method.kind === "set" && method.value.params.length !== 1)
- this.raiseRecoverable(method.value.start, "setter should have exactly one param")
- if (method.kind === "set" && method.value.params[0].type === "RestElement")
- this.raiseRecoverable(method.value.params[0].start, "Setter cannot use rest params")
- return method
-}
-pp.parseClassMethod = function(method, isGenerator, isAsync, allowsDirectSuper) {
- method.value = this.parseMethod(isGenerator, isAsync, allowsDirectSuper)
+ // Parse value
+ const value = method.value = this.parseMethod(isGenerator, isAsync, allowsDirectSuper)
+
+ // Check value
+ if (method.kind === "get" && value.params.length !== 0)
+ this.raiseRecoverable(value.start, "getter should have no params")
+ if (method.kind === "set" && value.params.length !== 1)
+ this.raiseRecoverable(value.start, "setter should have exactly one param")
+ if (method.kind === "set" && value.params[0].type === "RestElement")
+ this.raiseRecoverable(value.params[0].start, "Setter cannot use rest params")
+
return this.finishNode(method, "MethodDefinition")
}
+pp.parseClassField = function(field) {
+ if (checkKeyName(field, "constructor")) {
+ this.raise(field.key.start, "Classes can't have a field named 'constructor'")
+ } else if (field.static && checkKeyName(field, "prototype")) {
+ this.raise(field.key.start, "Classes can't have a static field named 'prototype'")
+ }
+
+ if (this.eat(tt.eq)) {
+ // To raise SyntaxError if 'arguments' exists in the initializer.
+ const scope = this.currentThisScope()
+ const inClassFieldInit = scope.inClassFieldInit
+ scope.inClassFieldInit = true
+ field.value = this.parseMaybeAssign()
+ scope.inClassFieldInit = inClassFieldInit
+ } else {
+ field.value = null
+ }
+ this.semicolon()
+
+ return this.finishNode(field, "PropertyDefinition")
+}
+
+pp.parseClassStaticBlock = function(node) {
+ node.body = []
+
+ let oldLabels = this.labels
+ this.labels = []
+ this.enterScope(SCOPE_CLASS_STATIC_BLOCK | SCOPE_SUPER)
+ while (this.type !== tt.braceR) {
+ let stmt = this.parseStatement(null)
+ node.body.push(stmt)
+ }
+ this.next()
+ this.exitScope()
+ this.labels = oldLabels
+
+ return this.finishNode(node, "StaticBlock")
+}
+
pp.parseClassId = function(node, isStatement) {
- node.id = this.type === tt.name ? this.parseIdent() : isStatement === true ? this.unexpected() : null
+ if (this.type === tt.name) {
+ node.id = this.parseIdent()
+ if (isStatement)
+ this.checkLValSimple(node.id, BIND_LEXICAL, false)
+ } else {
+ if (isStatement === true)
+ this.unexpected()
+ node.id = null
+ }
}
pp.parseClassSuper = function(node) {
- node.superClass = this.eat(tt._extends) ? this.parseExprSubscripts() : null
+ node.superClass = this.eat(tt._extends) ? this.parseExprSubscripts(false) : null
+}
+
+pp.enterClassBody = function() {
+ const element = {declared: Object.create(null), used: []}
+ this.privateNameStack.push(element)
+ return element.declared
+}
+
+pp.exitClassBody = function() {
+ const {declared, used} = this.privateNameStack.pop()
+ const len = this.privateNameStack.length
+ const parent = len === 0 ? null : this.privateNameStack[len - 1]
+ for (let i = 0; i < used.length; ++i) {
+ const id = used[i]
+ if (!hasOwn(declared, id.name)) {
+ if (parent) {
+ parent.used.push(id)
+ } else {
+ this.raiseRecoverable(id.start, `Private field '#${id.name}' must be declared in an enclosing class`)
+ }
+ }
+ }
+}
+
+function isPrivateNameConflicted(privateNameMap, element) {
+ const name = element.key.name
+ const curr = privateNameMap[name]
+
+ let next = "true"
+ if (element.type === "MethodDefinition" && (element.kind === "get" || element.kind === "set")) {
+ next = (element.static ? "s" : "i") + element.kind
+ }
+
+ // `class { get #a(){}; static set #a(_){} }` is also conflict.
+ if (
+ curr === "iget" && next === "iset" ||
+ curr === "iset" && next === "iget" ||
+ curr === "sget" && next === "sset" ||
+ curr === "sset" && next === "sget"
+ ) {
+ privateNameMap[name] = "true"
+ return false
+ } else if (!curr) {
+ privateNameMap[name] = next
+ return false
+ } else {
+ return true
+ }
+}
+
+function checkKeyName(node, name) {
+ const {computed, key} = node
+ return !computed && (
+ key.type === "Identifier" && key.name === name ||
+ key.type === "Literal" && key.value === name
+ )
}
// Parses module export declaration.
this.next()
// export * from '...'
if (this.eat(tt.star)) {
+ if (this.options.ecmaVersion >= 11) {
+ if (this.eatContextual("as")) {
+ node.exported = this.parseModuleExportName()
+ this.checkExport(exports, node.exported, this.lastTokStart)
+ } else {
+ node.exported = null
+ }
+ }
this.expectContextual("from")
if (this.type !== tt.string) this.unexpected()
node.source = this.parseExprAtom()
let fNode = this.startNode()
this.next()
if (isAsync) this.next()
- node.declaration = this.parseFunction(fNode, FUNC_STATEMENT | FUNC_NULLABLE_ID, false, isAsync, true)
+ node.declaration = this.parseFunction(fNode, FUNC_STATEMENT | FUNC_NULLABLE_ID, false, isAsync)
} else if (this.type === tt._class) {
let cNode = this.startNode()
node.declaration = this.parseClass(cNode, "nullableID")
if (node.declaration.type === "VariableDeclaration")
this.checkVariableExport(exports, node.declaration.declarations)
else
- this.checkExport(exports, node.declaration.id.name, node.declaration.id.start)
+ this.checkExport(exports, node.declaration.id, node.declaration.id.start)
node.specifiers = []
node.source = null
} else { // export { x, y as z } [from '...']
if (this.type !== tt.string) this.unexpected()
node.source = this.parseExprAtom()
} else {
- // check for keywords used as local names
for (let spec of node.specifiers) {
+ // check for keywords used as local names
this.checkUnreserved(spec.local)
+ // check if export is defined
+ this.checkLocalExport(spec.local)
+
+ if (spec.local.type === "Literal") {
+ this.raise(spec.local.start, "A string literal cannot be used as an exported binding without `from`.")
+ }
}
node.source = null
pp.checkExport = function(exports, name, pos) {
if (!exports) return
- if (has(exports, name))
+ if (typeof name !== "string")
+ name = name.type === "Identifier" ? name.name : name.value
+ if (hasOwn(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)
+ this.checkExport(exports, pat, pat.start)
else if (type === "ObjectPattern")
for (let prop of pat.properties)
this.checkPatternExport(exports, prop)
} 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)
+ node.local = this.parseModuleExportName()
+ node.exported = this.eatContextual("as") ? this.parseModuleExportName() : node.local
+ this.checkExport(
+ exports,
+ node.exported,
+ node.exported.start
+ )
nodes.push(this.finishNode(node, "ExportSpecifier"))
}
return nodes
// import defaultObj, { x, y as z } from '...'
let node = this.startNode()
node.local = this.parseIdent()
- this.checkLVal(node.local, BIND_LEXICAL)
+ this.checkLValSimple(node.local, BIND_LEXICAL)
nodes.push(this.finishNode(node, "ImportDefaultSpecifier"))
if (!this.eat(tt.comma)) return nodes
}
this.next()
this.expectContextual("as")
node.local = this.parseIdent()
- this.checkLVal(node.local, BIND_LEXICAL)
+ this.checkLValSimple(node.local, BIND_LEXICAL)
nodes.push(this.finishNode(node, "ImportNamespaceSpecifier"))
return nodes
}
} else first = false
let node = this.startNode()
- node.imported = this.parseIdent(true)
+ node.imported = this.parseModuleExportName()
if (this.eatContextual("as")) {
node.local = this.parseIdent()
} else {
this.checkUnreserved(node.imported)
node.local = node.imported
}
- this.checkLVal(node.local, BIND_LEXICAL)
+ this.checkLValSimple(node.local, BIND_LEXICAL)
nodes.push(this.finishNode(node, "ImportSpecifier"))
}
return nodes
}
+pp.parseModuleExportName = function() {
+ if (this.options.ecmaVersion >= 13 && this.type === tt.string) {
+ const stringLiteral = this.parseLiteral(this.value)
+ if (loneSurrogate.test(stringLiteral.value)) {
+ this.raise(stringLiteral.start, "An export name cannot include a lone surrogate.")
+ }
+ return stringLiteral
+ }
+ return this.parseIdent(true)
+}
+
// Set `ExpressionStatement#directive` property for directive prologues.
pp.adaptDirectivePrologue = function(statements) {
for (let i = 0; i < statements.length && this.isDirectiveCandidate(statements[i]); ++i) {