Upgrade to https://github.com/acornjs/acorn.git commit 84eda6bf
[jst.git] / src / lval.js
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"
5
6 const pp = Parser.prototype
7
8 // Convert existing expression atom to assignable pattern
9 // if possible.
10
11 pp.toAssignable = function(node, isBinding, refDestructuringErrors) {
12   if (this.options.ecmaVersion >= 6 && node) {
13     switch (node.type) {
14     case "Identifier":
15       if (this.inAsync && node.name === "await")
16         this.raise(node.start, "Cannot use 'await' as identifier inside an async function")
17       break
18
19     case "ObjectPattern":
20     case "ArrayPattern":
21     case "AssignmentPattern":
22     case "RestElement":
23       break
24
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)
30         // Early error:
31         //   AssignmentRestProperty[Yield, Await] :
32         //     `...` DestructuringAssignmentTarget[Yield, Await]
33         //
34         //   It is a Syntax Error if |DestructuringAssignmentTarget| is an |ArrayLiteral| or an |ObjectLiteral|.
35         if (
36           prop.type === "RestElement" &&
37           (prop.argument.type === "ArrayPattern" || prop.argument.type === "ObjectPattern")
38         ) {
39           this.raise(prop.argument.start, "Unexpected token")
40         }
41       }
42       break
43
44     case "Property":
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)
48       break
49
50     case "ArrayExpression":
51       node.type = "ArrayPattern"
52       if (refDestructuringErrors) this.checkPatternErrors(refDestructuringErrors, true)
53       this.toAssignableList(node.elements, isBinding)
54       break
55
56     case "SpreadElement":
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")
61       break
62
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"
66       delete node.operator
67       this.toAssignable(node.left, isBinding)
68       break
69
70     case "ParenthesizedExpression":
71       this.toAssignable(node.expression, isBinding, refDestructuringErrors)
72       break
73
74     case "ChainExpression":
75       this.raiseRecoverable(node.start, "Optional chaining cannot appear in left-hand side")
76       break
77
78     case "MemberExpression":
79       if (!isBinding) break
80
81     default:
82       this.raise(node.start, "Assigning to rvalue")
83
84     case "BinaryExpression": // Nick
85     case "Literal": // Nick
86       let temp = node
87       while (
88         temp.type === "BinaryExpression" &&
89         temp.operator === "-" &&
90         (temp.right.type === "Identifier" || temp.right.type === "Literal")
91       )
92         temp = temp.left
93       if (temp.type !== "Identifier" && temp.type !== "Literal")
94         this.raise(node.start, "Assigning to rvalue")
95       break
96     }
97   } else if (refDestructuringErrors) this.checkPatternErrors(refDestructuringErrors, true)
98   return node
99 }
100
101 // Convert list of expression atoms to binding list.
102
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)
108   }
109   if (end) {
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)
113   }
114   return exprList
115 }
116
117 // Parses spread element.
118
119 pp.parseSpread = function(refDestructuringErrors) {
120   let node = this.startNode()
121   this.next()
122   node.argument = this.parseMaybeAssign(false, refDestructuringErrors)
123   return this.finishNode(node, "SpreadElement")
124 }
125
126 pp.parseRestBinding = function() {
127   let node = this.startNode()
128   this.next()
129
130   // RestElement inside of a function parameter must be an identifier
131   if (this.options.ecmaVersion === 6 && this.type !== tt.name)
132     this.unexpected()
133
134   node.argument = this.parseBindingAtom()
135
136   return this.finishNode(node, "RestElement")
137 }
138
139 // Parses lvalue (assignable) atom.
140
141 pp.parseBindingAtom = function() {
142   if (this.options.ecmaVersion >= 6) {
143     switch (this.type) {
144     case tt.bracketL:
145       let node = this.startNode()
146       this.next()
147       node.elements = this.parseBindingList(tt.bracketR, true, true)
148       return this.finishNode(node, "ArrayPattern")
149
150     case tt.braceL:
151       return this.parseObj(true)
152     }
153   }
154   return this.parseIdent()
155 }
156
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) {
163       elts.push(null)
164     } else if (allowTrailingComma && this.afterTrailingComma(close)) {
165       break
166     } else if (this.type === tt.ellipsis) {
167       let rest = this.parseRestBinding()
168       this.parseBindingListItem(rest)
169       elts.push(rest)
170       if (this.type === tt.comma) this.raise(this.start, "Comma is not permitted after the rest element")
171       this.expect(close)
172       break
173     } else {
174       let elem = this.parseMaybeDefault(this.start, this.startLoc)
175       this.parseBindingListItem(elem)
176       elts.push(elem)
177     }
178   }
179   return elts
180 }
181
182 pp.parseBindingListItem = function(param) {
183   return param
184 }
185
186 // Parses assignment pattern around given atom if possible.
187
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)
192   node.left = left
193   node.right = this.parseMaybeAssign()
194   return this.finishNode(node, "AssignmentPattern")
195 }
196
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:
200 //
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.
204 //
205 // If a complex binding pattern is encountered (e.g., object and array
206 // destructuring), the entire pattern is recursively checked.
207 //
208 // There are three versions of checkLVal*() appropriate for different
209 // circumstances:
210 //
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
215 //
216 //   > It is a Syntax Error if AssignmentTargetType of [the production] is not
217 //   > simple.
218 //
219 //   It is also appropriate for checking if an identifier is valid and not
220 //   defined elsewhere, like import declarations or function/class identifiers.
221 //
222 //   Examples where this is used include:
223 //     a += …;
224 //     import a from '…';
225 //   where a is the node to be checked.
226 //
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
231 //
232 //   > It is a Syntax Error if [the production] is neither an ObjectLiteral nor
233 //   > an ArrayLiteral and AssignmentTargetType of [the production] is not
234 //   > simple.
235 //
236 //   Examples where this is used include:
237 //     (a = …);
238 //     const a = …;
239 //     try { … } catch (a) { … }
240 //   where a is the node to be checked.
241 //
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.
246 //
247 //   As a special case, function parameters also use checkLValInnerPattern(),
248 //   as they also support defaults and rest constructs.
249 //
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
254 // BIND_LEXICAL.
255 //
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).
260
261 pp.checkLValSimple = function(expr, bindingType = BIND_NONE, checkClashes) {
262   const isBind = bindingType !== BIND_NONE
263
264   switch (expr.type) {
265   case "Identifier":
266     if (this.strict && this.reservedWordsStrictBind.test(expr.name))
267       this.raiseRecoverable(expr.start, (isBind ? "Binding " : "Assigning to ") + expr.name + " in strict mode")
268     if (isBind) {
269       if (bindingType === BIND_LEXICAL && expr.name === "let")
270         this.raiseRecoverable(expr.start, "let is disallowed as a lexically bound name")
271       if (checkClashes) {
272         if (hasOwn(checkClashes, expr.name))
273           this.raiseRecoverable(expr.start, "Argument name clash")
274         checkClashes[expr.name] = true
275       }
276       if (bindingType !== BIND_OUTSIDE) this.declareName(expr.name, bindingType, expr.start)
277     }
278     break
279
280   case "ChainExpression":
281     this.raiseRecoverable(expr.start, "Optional chaining cannot appear in left-hand side")
282     break
283
284   case "MemberExpression":
285     if (isBind) this.raiseRecoverable(expr.start, "Binding member expression")
286     break
287
288   case "ParenthesizedExpression":
289     if (isBind) this.raiseRecoverable(expr.start, "Binding parenthesized expression")
290     return this.checkLValSimple(expr.expression, bindingType, checkClashes)
291
292   default:
293     this.raise(expr.start, (isBind ? "Binding" : "Assigning to") + " rvalue")
294
295   case "BinaryExpression": // Nick
296   case "Literal": // Nick
297     let temp = expr
298     while (
299       temp.type === "BinaryExpression" &&
300       temp.operator === "-" &&
301         (temp.right.type === "Identifier" || temp.right.type === "Literal")
302     )
303       temp = temp.left
304     if (temp.type !== "Identifier" && temp.type !== "Literal")
305       this.raise(expr.start, (isBind ? "Binding" : "Assigning to") + " rvalue")
306     break
307   }
308 }
309
310 pp.checkLValPattern = function(expr, bindingType = BIND_NONE, checkClashes) {
311   switch (expr.type) {
312   case "ObjectPattern":
313     for (let prop of expr.properties) {
314       this.checkLValInnerPattern(prop, bindingType, checkClashes)
315     }
316     break
317
318   case "ArrayPattern":
319     for (let elem of expr.elements) {
320       if (elem) this.checkLValInnerPattern(elem, bindingType, checkClashes)
321     }
322     break
323
324   default:
325     this.checkLValSimple(expr, bindingType, checkClashes)
326   }
327 }
328
329 pp.checkLValInnerPattern = function(expr, bindingType = BIND_NONE, checkClashes) {
330   switch (expr.type) {
331   case "Property":
332     // AssignmentProperty has type === "Property"
333     this.checkLValInnerPattern(expr.value, bindingType, checkClashes)
334     break
335
336   case "AssignmentPattern":
337     this.checkLValPattern(expr.left, bindingType, checkClashes)
338     break
339
340   case "RestElement":
341     this.checkLValPattern(expr.argument, bindingType, checkClashes)
342     break
343
344   default:
345     this.checkLValPattern(expr, bindingType, checkClashes)
346   }
347 }