1 import {types as tt} from "./tokentype.js"
2 import {Parser} from "./state.js"
3 import {hasOwn} from "./util.js"
4 import {BIND_NONE, BIND_OUTSIDE, BIND_LEXICAL} from "./scopeflags.js"
6 const pp = Parser.prototype
8 // Convert existing expression atom to assignable pattern
11 pp.toAssignable = function(node, isBinding, refDestructuringErrors) {
12 if (this.options.ecmaVersion >= 6 && node) {
15 if (this.inAsync && node.name === "await")
16 this.raise(node.start, "Cannot use 'await' as identifier inside an async function")
21 case "AssignmentPattern":
25 case "ObjectExpression":
26 node.type = "ObjectPattern"
27 if (refDestructuringErrors) this.checkPatternErrors(refDestructuringErrors, true)
28 for (let prop of node.properties) {
29 this.toAssignable(prop, isBinding)
31 // AssignmentRestProperty[Yield, Await] :
32 // `...` DestructuringAssignmentTarget[Yield, Await]
34 // It is a Syntax Error if |DestructuringAssignmentTarget| is an |ArrayLiteral| or an |ObjectLiteral|.
36 prop.type === "RestElement" &&
37 (prop.argument.type === "ArrayPattern" || prop.argument.type === "ObjectPattern")
39 this.raise(prop.argument.start, "Unexpected token")
45 // AssignmentProperty has type === "Property"
46 if (node.kind !== "init") this.raise(node.key.start, "Object pattern can't contain getter or setter")
47 this.toAssignable(node.value, isBinding)
50 case "ArrayExpression":
51 node.type = "ArrayPattern"
52 if (refDestructuringErrors) this.checkPatternErrors(refDestructuringErrors, true)
53 this.toAssignableList(node.elements, isBinding)
57 node.type = "RestElement"
58 this.toAssignable(node.argument, isBinding)
59 if (node.argument.type === "AssignmentPattern")
60 this.raise(node.argument.start, "Rest elements cannot have a default value")
63 case "AssignmentExpression":
64 if (node.operator !== "=") this.raise(node.left.end, "Only '=' operator can be used for specifying default value.")
65 node.type = "AssignmentPattern"
67 this.toAssignable(node.left, isBinding)
70 case "ParenthesizedExpression":
71 this.toAssignable(node.expression, isBinding, refDestructuringErrors)
74 case "ChainExpression":
75 this.raiseRecoverable(node.start, "Optional chaining cannot appear in left-hand side")
78 case "MemberExpression":
82 this.raise(node.start, "Assigning to rvalue")
84 case "BinaryExpression": // Nick
85 case "Literal": // Nick
88 temp.type === "BinaryExpression" &&
89 temp.operator === "-" &&
90 (temp.right.type === "Identifier" || temp.right.type === "Literal")
93 if (temp.type !== "Identifier" && temp.type !== "Literal")
94 this.raise(node.start, "Assigning to rvalue")
97 } else if (refDestructuringErrors) this.checkPatternErrors(refDestructuringErrors, true)
101 // Convert list of expression atoms to binding list.
103 pp.toAssignableList = function(exprList, isBinding) {
104 let end = exprList.length
105 for (let i = 0; i < end; i++) {
106 let elt = exprList[i]
107 if (elt) this.toAssignable(elt, isBinding)
110 let last = exprList[end - 1]
111 if (this.options.ecmaVersion === 6 && isBinding && last && last.type === "RestElement" && last.argument.type !== "Identifier")
112 this.unexpected(last.argument.start)
117 // Parses spread element.
119 pp.parseSpread = function(refDestructuringErrors) {
120 let node = this.startNode()
122 node.argument = this.parseMaybeAssign(false, refDestructuringErrors)
123 return this.finishNode(node, "SpreadElement")
126 pp.parseRestBinding = function() {
127 let node = this.startNode()
130 // RestElement inside of a function parameter must be an identifier
131 if (this.options.ecmaVersion === 6 && this.type !== tt.name)
134 node.argument = this.parseBindingAtom()
136 return this.finishNode(node, "RestElement")
139 // Parses lvalue (assignable) atom.
141 pp.parseBindingAtom = function() {
142 if (this.options.ecmaVersion >= 6) {
145 let node = this.startNode()
147 node.elements = this.parseBindingList(tt.bracketR, true, true)
148 return this.finishNode(node, "ArrayPattern")
151 return this.parseObj(true)
154 return this.parseIdent()
157 pp.parseBindingList = function(close, allowEmpty, allowTrailingComma) {
158 let elts = [], first = true
159 while (!this.eat(close)) {
160 if (first) first = false
161 else this.expect(tt.comma)
162 if (allowEmpty && this.type === tt.comma) {
164 } else if (allowTrailingComma && this.afterTrailingComma(close)) {
166 } else if (this.type === tt.ellipsis) {
167 let rest = this.parseRestBinding()
168 this.parseBindingListItem(rest)
170 if (this.type === tt.comma) this.raise(this.start, "Comma is not permitted after the rest element")
174 let elem = this.parseMaybeDefault(this.start, this.startLoc)
175 this.parseBindingListItem(elem)
182 pp.parseBindingListItem = function(param) {
186 // Parses assignment pattern around given atom if possible.
188 pp.parseMaybeDefault = function(startPos, startLoc, left) {
189 left = left || this.parseBindingAtom()
190 if (this.options.ecmaVersion < 6 || !this.eat(tt.eq)) return left
191 let node = this.startNodeAt(startPos, startLoc)
193 node.right = this.parseMaybeAssign()
194 return this.finishNode(node, "AssignmentPattern")
197 // The following three functions all verify that a node is an lvalue —
198 // something that can be bound, or assigned to. In order to do so, they perform
199 // a variety of checks:
201 // - Check that none of the bound/assigned-to identifiers are reserved words.
202 // - Record name declarations for bindings in the appropriate scope.
203 // - Check duplicate argument names, if checkClashes is set.
205 // If a complex binding pattern is encountered (e.g., object and array
206 // destructuring), the entire pattern is recursively checked.
208 // There are three versions of checkLVal*() appropriate for different
211 // - checkLValSimple() shall be used if the syntactic construct supports
212 // nothing other than identifiers and member expressions. Parenthesized
213 // expressions are also correctly handled. This is generally appropriate for
214 // constructs for which the spec says
216 // > It is a Syntax Error if AssignmentTargetType of [the production] is not
219 // It is also appropriate for checking if an identifier is valid and not
220 // defined elsewhere, like import declarations or function/class identifiers.
222 // Examples where this is used include:
224 // import a from '…';
225 // where a is the node to be checked.
227 // - checkLValPattern() shall be used if the syntactic construct supports
228 // anything checkLValSimple() supports, as well as object and array
229 // destructuring patterns. This is generally appropriate for constructs for
230 // which the spec says
232 // > It is a Syntax Error if [the production] is neither an ObjectLiteral nor
233 // > an ArrayLiteral and AssignmentTargetType of [the production] is not
236 // Examples where this is used include:
239 // try { … } catch (a) { … }
240 // where a is the node to be checked.
242 // - checkLValInnerPattern() shall be used if the syntactic construct supports
243 // anything checkLValPattern() supports, as well as default assignment
244 // patterns, rest elements, and other constructs that may appear within an
245 // object or array destructuring pattern.
247 // As a special case, function parameters also use checkLValInnerPattern(),
248 // as they also support defaults and rest constructs.
250 // These functions deliberately support both assignment and binding constructs,
251 // as the logic for both is exceedingly similar. If the node is the target of
252 // an assignment, then bindingType should be set to BIND_NONE. Otherwise, it
253 // should be set to the appropriate BIND_* constant, like BIND_VAR or
256 // If the function is called with a non-BIND_NONE bindingType, then
257 // additionally a checkClashes object may be specified to allow checking for
258 // duplicate argument names. checkClashes is ignored if the provided construct
259 // is an assignment (i.e., bindingType is BIND_NONE).
261 pp.checkLValSimple = function(expr, bindingType = BIND_NONE, checkClashes) {
262 const isBind = bindingType !== BIND_NONE
266 if (this.strict && this.reservedWordsStrictBind.test(expr.name))
267 this.raiseRecoverable(expr.start, (isBind ? "Binding " : "Assigning to ") + expr.name + " in strict mode")
269 if (bindingType === BIND_LEXICAL && expr.name === "let")
270 this.raiseRecoverable(expr.start, "let is disallowed as a lexically bound name")
272 if (hasOwn(checkClashes, expr.name))
273 this.raiseRecoverable(expr.start, "Argument name clash")
274 checkClashes[expr.name] = true
276 if (bindingType !== BIND_OUTSIDE) this.declareName(expr.name, bindingType, expr.start)
280 case "ChainExpression":
281 this.raiseRecoverable(expr.start, "Optional chaining cannot appear in left-hand side")
284 case "MemberExpression":
285 if (isBind) this.raiseRecoverable(expr.start, "Binding member expression")
288 case "ParenthesizedExpression":
289 if (isBind) this.raiseRecoverable(expr.start, "Binding parenthesized expression")
290 return this.checkLValSimple(expr.expression, bindingType, checkClashes)
293 this.raise(expr.start, (isBind ? "Binding" : "Assigning to") + " rvalue")
295 case "BinaryExpression": // Nick
296 case "Literal": // Nick
299 temp.type === "BinaryExpression" &&
300 temp.operator === "-" &&
301 (temp.right.type === "Identifier" || temp.right.type === "Literal")
304 if (temp.type !== "Identifier" && temp.type !== "Literal")
305 this.raise(expr.start, (isBind ? "Binding" : "Assigning to") + " rvalue")
310 pp.checkLValPattern = function(expr, bindingType = BIND_NONE, checkClashes) {
312 case "ObjectPattern":
313 for (let prop of expr.properties) {
314 this.checkLValInnerPattern(prop, bindingType, checkClashes)
319 for (let elem of expr.elements) {
320 if (elem) this.checkLValInnerPattern(elem, bindingType, checkClashes)
325 this.checkLValSimple(expr, bindingType, checkClashes)
329 pp.checkLValInnerPattern = function(expr, bindingType = BIND_NONE, checkClashes) {
332 // AssignmentProperty has type === "Property"
333 this.checkLValInnerPattern(expr.value, bindingType, checkClashes)
336 case "AssignmentPattern":
337 this.checkLValPattern(expr.left, bindingType, checkClashes)
341 this.checkLValPattern(expr.argument, bindingType, checkClashes)
345 this.checkLValPattern(expr, bindingType, checkClashes)