PageRenderTime 100ms CodeModel.GetById 27ms RepoModel.GetById 0ms app.codeStats 1ms

/src/nodes.coffee

http://github.com/jashkenas/coffee-script
CoffeeScript | 6111 lines | 3085 code | 559 blank | 2467 comment | 610 complexity | 75430d71fe3a3205f3c9b75d5441a518 MD5 | raw file
  1. # `nodes.coffee` contains all of the node classes for the syntax tree. Most
  2. # nodes are created as the result of actions in the [grammar](grammar.html),
  3. # but some are created by other nodes as a method of code generation. To convert
  4. # the syntax tree into a string of JavaScript code, call `compile()` on the root.
  5. Error.stackTraceLimit = Infinity
  6. {Scope} = require './scope'
  7. {isUnassignable, JS_FORBIDDEN} = require './lexer'
  8. # Import the helpers we plan to use.
  9. {compact, flatten, extend, merge, del, starts, ends, some,
  10. addDataToNode, attachCommentsToNode, locationDataToString,
  11. throwSyntaxError, replaceUnicodeCodePointEscapes,
  12. isFunction, isPlainObject, isNumber, parseNumber} = require './helpers'
  13. # Functions required by parser.
  14. exports.extend = extend
  15. exports.addDataToNode = addDataToNode
  16. # Constant functions for nodes that dont need customization.
  17. YES = -> yes
  18. NO = -> no
  19. THIS = -> this
  20. NEGATE = -> @negated = not @negated; this
  21. #### CodeFragment
  22. # The various nodes defined below all compile to a collection of **CodeFragment** objects.
  23. # A CodeFragments is a block of generated code, and the location in the source file where the code
  24. # came from. CodeFragments can be assembled together into working code just by catting together
  25. # all the CodeFragments' `code` snippets, in order.
  26. exports.CodeFragment = class CodeFragment
  27. constructor: (parent, code) ->
  28. @code = "#{code}"
  29. @type = parent?.constructor?.name or 'unknown'
  30. @locationData = parent?.locationData
  31. @comments = parent?.comments
  32. toString: ->
  33. # This is only intended for debugging.
  34. "#{@code}#{if @locationData then ": " + locationDataToString(@locationData) else ''}"
  35. # Convert an array of CodeFragments into a string.
  36. fragmentsToText = (fragments) ->
  37. (fragment.code for fragment in fragments).join('')
  38. #### Base
  39. # The **Base** is the abstract base class for all nodes in the syntax tree.
  40. # Each subclass implements the `compileNode` method, which performs the
  41. # code generation for that node. To compile a node to JavaScript,
  42. # call `compile` on it, which wraps `compileNode` in some generic extra smarts,
  43. # to know when the generated code needs to be wrapped up in a closure.
  44. # An options hash is passed and cloned throughout, containing information about
  45. # the environment from higher in the tree (such as if a returned value is
  46. # being requested by the surrounding function), information about the current
  47. # scope, and indentation level.
  48. exports.Base = class Base
  49. compile: (o, lvl) ->
  50. fragmentsToText @compileToFragments o, lvl
  51. # Occasionally a node is compiled multiple times, for example to get the name
  52. # of a variable to add to scope tracking. When we know that a premature
  53. # compilation wont result in comments being output, set those comments aside
  54. # so that theyre preserved for a later `compile` call that will result in
  55. # the comments being included in the output.
  56. compileWithoutComments: (o, lvl, method = 'compile') ->
  57. if @comments
  58. @ignoreTheseCommentsTemporarily = @comments
  59. delete @comments
  60. unwrapped = @unwrapAll()
  61. if unwrapped.comments
  62. unwrapped.ignoreTheseCommentsTemporarily = unwrapped.comments
  63. delete unwrapped.comments
  64. fragments = @[method] o, lvl
  65. if @ignoreTheseCommentsTemporarily
  66. @comments = @ignoreTheseCommentsTemporarily
  67. delete @ignoreTheseCommentsTemporarily
  68. if unwrapped.ignoreTheseCommentsTemporarily
  69. unwrapped.comments = unwrapped.ignoreTheseCommentsTemporarily
  70. delete unwrapped.ignoreTheseCommentsTemporarily
  71. fragments
  72. compileNodeWithoutComments: (o, lvl) ->
  73. @compileWithoutComments o, lvl, 'compileNode'
  74. # Common logic for determining whether to wrap this node in a closure before
  75. # compiling it, or to compile directly. We need to wrap if this node is a
  76. # *statement*, and it's not a *pureStatement*, and we're not at
  77. # the top level of a block (which would be unnecessary), and we haven't
  78. # already been asked to return the result (because statements know how to
  79. # return results).
  80. compileToFragments: (o, lvl) ->
  81. o = extend {}, o
  82. o.level = lvl if lvl
  83. node = @unfoldSoak(o) or this
  84. node.tab = o.indent
  85. fragments = if o.level is LEVEL_TOP or not node.isStatement(o)
  86. node.compileNode o
  87. else
  88. node.compileClosure o
  89. @compileCommentFragments o, node, fragments
  90. fragments
  91. compileToFragmentsWithoutComments: (o, lvl) ->
  92. @compileWithoutComments o, lvl, 'compileToFragments'
  93. # Statements converted into expressions via closure-wrapping share a scope
  94. # object with their parent closure, to preserve the expected lexical scope.
  95. compileClosure: (o) ->
  96. @checkForPureStatementInExpression()
  97. o.sharedScope = yes
  98. func = new Code [], Block.wrap [this]
  99. args = []
  100. if @contains ((node) -> node instanceof SuperCall)
  101. func.bound = yes
  102. else if (argumentsNode = @contains isLiteralArguments) or @contains isLiteralThis
  103. args = [new ThisLiteral]
  104. if argumentsNode
  105. meth = 'apply'
  106. args.push new IdentifierLiteral 'arguments'
  107. else
  108. meth = 'call'
  109. func = new Value func, [new Access new PropertyName meth]
  110. parts = (new Call func, args).compileNode o
  111. switch
  112. when func.isGenerator or func.base?.isGenerator
  113. parts.unshift @makeCode "(yield* "
  114. parts.push @makeCode ")"
  115. when func.isAsync or func.base?.isAsync
  116. parts.unshift @makeCode "(await "
  117. parts.push @makeCode ")"
  118. parts
  119. compileCommentFragments: (o, node, fragments) ->
  120. return fragments unless node.comments
  121. # This is where comments, that are attached to nodes as a `comments`
  122. # property, become `CodeFragment`s. Inline block comments, e.g.
  123. # `/* */`-delimited comments that are interspersed within code on a line,
  124. # are added to the current `fragments` stream. All other fragments are
  125. # attached as properties to the nearest preceding or following fragment,
  126. # to remain stowaways until they get properly output in `compileComments`
  127. # later on.
  128. unshiftCommentFragment = (commentFragment) ->
  129. if commentFragment.unshift
  130. # Find the first non-comment fragment and insert `commentFragment`
  131. # before it.
  132. unshiftAfterComments fragments, commentFragment
  133. else
  134. if fragments.length isnt 0
  135. precedingFragment = fragments[fragments.length - 1]
  136. if commentFragment.newLine and precedingFragment.code isnt '' and
  137. not /\n\s*$/.test precedingFragment.code
  138. commentFragment.code = "\n#{commentFragment.code}"
  139. fragments.push commentFragment
  140. for comment in node.comments when comment not in @compiledComments
  141. @compiledComments.push comment # Dont output this comment twice.
  142. # For block/here comments, denoted by `###`, that are inline comments
  143. # like `1 + ### comment ### 2`, create fragments and insert them into
  144. # the fragments array.
  145. # Otherwise attach comment fragments to their closest fragment for now,
  146. # so they can be inserted into the output later after all the newlines
  147. # have been added.
  148. if comment.here # Block comment, delimited by `###`.
  149. commentFragment = new HereComment(comment).compileNode o
  150. else # Line comment, delimited by `#`.
  151. commentFragment = new LineComment(comment).compileNode o
  152. if (commentFragment.isHereComment and not commentFragment.newLine) or
  153. node.includeCommentFragments()
  154. # Inline block comments, like `1 + /* comment */ 2`, or a node whose
  155. # `compileToFragments` method has logic for outputting comments.
  156. unshiftCommentFragment commentFragment
  157. else
  158. fragments.push @makeCode '' if fragments.length is 0
  159. if commentFragment.unshift
  160. fragments[0].precedingComments ?= []
  161. fragments[0].precedingComments.push commentFragment
  162. else
  163. fragments[fragments.length - 1].followingComments ?= []
  164. fragments[fragments.length - 1].followingComments.push commentFragment
  165. fragments
  166. # If the code generation wishes to use the result of a complex expression
  167. # in multiple places, ensure that the expression is only ever evaluated once,
  168. # by assigning it to a temporary variable. Pass a level to precompile.
  169. #
  170. # If `level` is passed, then returns `[val, ref]`, where `val` is the compiled value, and `ref`
  171. # is the compiled reference. If `level` is not passed, this returns `[val, ref]` where
  172. # the two values are raw nodes which have not been compiled.
  173. cache: (o, level, shouldCache) ->
  174. complex = if shouldCache? then shouldCache this else @shouldCache()
  175. if complex
  176. ref = new IdentifierLiteral o.scope.freeVariable 'ref'
  177. sub = new Assign ref, this
  178. if level then [sub.compileToFragments(o, level), [@makeCode(ref.value)]] else [sub, ref]
  179. else
  180. ref = if level then @compileToFragments o, level else this
  181. [ref, ref]
  182. # Occasionally it may be useful to make an expression behave as if it was 'hoisted', whereby the
  183. # result of the expression is available before its location in the source, but the expression's
  184. # variable scope corresponds to the source position. This is used extensively to deal with executable
  185. # class bodies in classes.
  186. #
  187. # Calling this method mutates the node, proxying the `compileNode` and `compileToFragments`
  188. # methods to store their result for later replacing the `target` node, which is returned by the
  189. # call.
  190. hoist: ->
  191. @hoisted = yes
  192. target = new HoistTarget @
  193. compileNode = @compileNode
  194. compileToFragments = @compileToFragments
  195. @compileNode = (o) ->
  196. target.update compileNode, o
  197. @compileToFragments = (o) ->
  198. target.update compileToFragments, o
  199. target
  200. cacheToCodeFragments: (cacheValues) ->
  201. [fragmentsToText(cacheValues[0]), fragmentsToText(cacheValues[1])]
  202. # Construct a node that returns the current nodes result.
  203. # Note that this is overridden for smarter behavior for
  204. # many statement nodes (e.g. `If`, `For`).
  205. makeReturn: (results, mark) ->
  206. if mark
  207. # Mark this node as implicitly returned, so that it can be part of the
  208. # node metadata returned in the AST.
  209. @canBeReturned = yes
  210. return
  211. node = @unwrapAll()
  212. if results
  213. new Call new Literal("#{results}.push"), [node]
  214. else
  215. new Return node
  216. # Does this node, or any of its children, contain a node of a certain kind?
  217. # Recursively traverses down the *children* nodes and returns the first one
  218. # that verifies `pred`. Otherwise return undefined. `contains` does not cross
  219. # scope boundaries.
  220. contains: (pred) ->
  221. node = undefined
  222. @traverseChildren no, (n) ->
  223. if pred n
  224. node = n
  225. return no
  226. node
  227. # Pull out the last node of a node list.
  228. lastNode: (list) ->
  229. if list.length is 0 then null else list[list.length - 1]
  230. # Debugging representation of the node, for inspecting the parse tree.
  231. # This is what `coffee --nodes` prints out.
  232. toString: (idt = '', name = @constructor.name) ->
  233. tree = '\n' + idt + name
  234. tree += '?' if @soak
  235. @eachChild (node) -> tree += node.toString idt + TAB
  236. tree
  237. checkForPureStatementInExpression: ->
  238. if jumpNode = @jumps()
  239. jumpNode.error 'cannot use a pure statement in an expression'
  240. # Plain JavaScript object representation of the node, that can be serialized
  241. # as JSON. This is what the `ast` option in the Node API returns.
  242. # We try to follow the [Babel AST spec](https://github.com/babel/babel/blob/master/packages/babel-parser/ast/spec.md)
  243. # as closely as possible, for improved interoperability with other tools.
  244. # **WARNING: DO NOT OVERRIDE THIS METHOD IN CHILD CLASSES.**
  245. # Only override the component `ast*` methods as needed.
  246. ast: (o, level) ->
  247. # Merge `level` into `o` and perform other universal checks.
  248. o = @astInitialize o, level
  249. # Create serializable representation of this node.
  250. astNode = @astNode o
  251. # Mark AST nodes that correspond to expressions that (implicitly) return.
  252. # We cant do this as part of `astNode` because we need to assemble child
  253. # nodes first before marking the parent being returned.
  254. if @astNode? and @canBeReturned
  255. Object.assign astNode, {returns: yes}
  256. astNode
  257. astInitialize: (o, level) ->
  258. o = Object.assign {}, o
  259. o.level = level if level?
  260. if o.level > LEVEL_TOP
  261. @checkForPureStatementInExpression()
  262. # `@makeReturn` must be called before `astProperties`, because the latter may call
  263. # `.ast()` for child nodes and those nodes would need the return logic from `makeReturn`
  264. # already executed by then.
  265. @makeReturn null, yes if @isStatement(o) and o.level isnt LEVEL_TOP and o.scope?
  266. o
  267. astNode: (o) ->
  268. # Every abstract syntax tree node object has four categories of properties:
  269. # - type, stored in the `type` field and a string like `NumberLiteral`.
  270. # - location data, stored in the `loc`, `start`, `end` and `range` fields.
  271. # - properties specific to this node, like `parsedValue`.
  272. # - properties that are themselves child nodes, like `body`.
  273. # These fields are all intermixed in the Babel spec; `type` and `start` and
  274. # `parsedValue` are all top level fields in the AST node object. We have
  275. # separate methods for returning each category, that we merge together here.
  276. Object.assign {}, {type: @astType(o)}, @astProperties(o), @astLocationData()
  277. # By default, a node class has no specific properties.
  278. astProperties: -> {}
  279. # By default, a node classs AST `type` is its class name.
  280. astType: -> @constructor.name
  281. # The AST location data is a rearranged version of our Jison location data,
  282. # mutated into the structure that the Babel spec uses.
  283. astLocationData: ->
  284. jisonLocationDataToAstLocationData @locationData
  285. # Determines whether an AST node needs an `ExpressionStatement` wrapper.
  286. # Typically matches our `isStatement()` logic but this allows overriding.
  287. isStatementAst: (o) ->
  288. @isStatement o
  289. # Passes each child to a function, breaking when the function returns `false`.
  290. eachChild: (func) ->
  291. return this unless @children
  292. for attr in @children when @[attr]
  293. for child in flatten [@[attr]]
  294. return this if func(child) is false
  295. this
  296. traverseChildren: (crossScope, func) ->
  297. @eachChild (child) ->
  298. recur = func(child)
  299. child.traverseChildren(crossScope, func) unless recur is no
  300. # `replaceInContext` will traverse children looking for a node for which `match` returns
  301. # true. Once found, the matching node will be replaced by the result of calling `replacement`.
  302. replaceInContext: (match, replacement) ->
  303. return false unless @children
  304. for attr in @children when children = @[attr]
  305. if Array.isArray children
  306. for child, i in children
  307. if match child
  308. children[i..i] = replacement child, @
  309. return true
  310. else
  311. return true if child.replaceInContext match, replacement
  312. else if match children
  313. @[attr] = replacement children, @
  314. return true
  315. else
  316. return true if children.replaceInContext match, replacement
  317. invert: ->
  318. new Op '!', this
  319. unwrapAll: ->
  320. node = this
  321. continue until node is node = node.unwrap()
  322. node
  323. # Default implementations of the common node properties and methods. Nodes
  324. # will override these with custom logic, if needed.
  325. # `children` are the properties to recurse into when tree walking. The
  326. # `children` list *is* the structure of the AST. The `parent` pointer, and
  327. # the pointer to the `children` are how you can traverse the tree.
  328. children: []
  329. # `isStatement` has to do with everything is an expression. A few things
  330. # cant be expressions, such as `break`. Things that `isStatement` returns
  331. # `true` for are things that cant be used as expressions. There are some
  332. # error messages that come from `nodes.coffee` due to statements ending up
  333. # in expression position.
  334. isStatement: NO
  335. # Track comments that have been compiled into fragments, to avoid outputting
  336. # them twice.
  337. compiledComments: []
  338. # `includeCommentFragments` lets `compileCommentFragments` know whether this node
  339. # has special awareness of how to handle comments within its output.
  340. includeCommentFragments: NO
  341. # `jumps` tells you if an expression, or an internal part of an expression
  342. # has a flow control construct (like `break`, or `continue`, or `return`,
  343. # or `throw`) that jumps out of the normal flow of control and cant be
  344. # used as a value. This is important because things like this make no sense;
  345. # we have to disallow them.
  346. jumps: NO
  347. # If `node.shouldCache() is false`, it is safe to use `node` more than once.
  348. # Otherwise you need to store the value of `node` in a variable and output
  349. # that variable several times instead. Kind of like this: `5` need not be
  350. # cached. `returnFive()`, however, could have side effects as a result of
  351. # evaluating it more than once, and therefore we need to cache it. The
  352. # parameter is named `shouldCache` rather than `mustCache` because there are
  353. # also cases where we might not need to cache but where we want to, for
  354. # example a long expression that may well be idempotent but we want to cache
  355. # for brevity.
  356. shouldCache: YES
  357. isChainable: NO
  358. isAssignable: NO
  359. isNumber: NO
  360. unwrap: THIS
  361. unfoldSoak: NO
  362. # Is this node used to assign a certain variable?
  363. assigns: NO
  364. # For this node and all descendents, set the location data to `locationData`
  365. # if the location data is not already set.
  366. updateLocationDataIfMissing: (locationData, force) ->
  367. @forceUpdateLocation = yes if force
  368. return this if @locationData and not @forceUpdateLocation
  369. delete @forceUpdateLocation
  370. @locationData = locationData
  371. @eachChild (child) ->
  372. child.updateLocationDataIfMissing locationData
  373. # Add location data from another node
  374. withLocationDataFrom: ({locationData}) ->
  375. @updateLocationDataIfMissing locationData
  376. # Add location data and comments from another node
  377. withLocationDataAndCommentsFrom: (node) ->
  378. @withLocationDataFrom node
  379. {comments} = node
  380. @comments = comments if comments?.length
  381. this
  382. # Throw a SyntaxError associated with this nodes location.
  383. error: (message) ->
  384. throwSyntaxError message, @locationData
  385. makeCode: (code) ->
  386. new CodeFragment this, code
  387. wrapInParentheses: (fragments) ->
  388. [@makeCode('('), fragments..., @makeCode(')')]
  389. wrapInBraces: (fragments) ->
  390. [@makeCode('{'), fragments..., @makeCode('}')]
  391. # `fragmentsList` is an array of arrays of fragments. Each array in fragmentsList will be
  392. # concatenated together, with `joinStr` added in between each, to produce a final flat array
  393. # of fragments.
  394. joinFragmentArrays: (fragmentsList, joinStr) ->
  395. answer = []
  396. for fragments, i in fragmentsList
  397. if i then answer.push @makeCode joinStr
  398. answer = answer.concat fragments
  399. answer
  400. #### HoistTarget
  401. # A **HoistTargetNode** represents the output location in the node tree for a hoisted node.
  402. # See Base#hoist.
  403. exports.HoistTarget = class HoistTarget extends Base
  404. # Expands hoisted fragments in the given array
  405. @expand = (fragments) ->
  406. for fragment, i in fragments by -1 when fragment.fragments
  407. fragments[i..i] = @expand fragment.fragments
  408. fragments
  409. constructor: (@source) ->
  410. super()
  411. # Holds presentational options to apply when the source node is compiled.
  412. @options = {}
  413. # Placeholder fragments to be replaced by the source nodes compilation.
  414. @targetFragments = { fragments: [] }
  415. isStatement: (o) ->
  416. @source.isStatement o
  417. # Update the target fragments with the result of compiling the source.
  418. # Calls the given compile function with the node and options (overriden with the target
  419. # presentational options).
  420. update: (compile, o) ->
  421. @targetFragments.fragments = compile.call @source, merge o, @options
  422. # Copies the target indent and level, and returns the placeholder fragments
  423. compileToFragments: (o, level) ->
  424. @options.indent = o.indent
  425. @options.level = level ? o.level
  426. [ @targetFragments ]
  427. compileNode: (o) ->
  428. @compileToFragments o
  429. compileClosure: (o) ->
  430. @compileToFragments o
  431. #### Root
  432. # The root node of the node tree
  433. exports.Root = class Root extends Base
  434. constructor: (@body) ->
  435. super()
  436. children: ['body']
  437. # Wrap everything in a safety closure, unless requested not to. It would be
  438. # better not to generate them in the first place, but for now, clean up
  439. # obvious double-parentheses.
  440. compileNode: (o) ->
  441. o.indent = if o.bare then '' else TAB
  442. o.level = LEVEL_TOP
  443. o.compiling = yes
  444. @initializeScope o
  445. fragments = @body.compileRoot o
  446. return fragments if o.bare
  447. [].concat @makeCode("(function() {\n"), fragments, @makeCode("\n}).call(this);\n")
  448. initializeScope: (o) ->
  449. o.scope = new Scope null, @body, null, o.referencedVars ? []
  450. # Mark given local variables in the root scope as parameters so they dont
  451. # end up being declared on the root block.
  452. o.scope.parameter name for name in o.locals or []
  453. commentsAst: ->
  454. @allComments ?=
  455. for commentToken in (@allCommentTokens ? []) when not commentToken.heregex
  456. if commentToken.here
  457. new HereComment commentToken
  458. else
  459. new LineComment commentToken
  460. comment.ast() for comment in @allComments
  461. astNode: (o) ->
  462. o.level = LEVEL_TOP
  463. @initializeScope o
  464. super o
  465. astType: -> 'File'
  466. astProperties: (o) ->
  467. @body.isRootBlock = yes
  468. return
  469. program: Object.assign @body.ast(o), @astLocationData()
  470. comments: @commentsAst()
  471. #### Block
  472. # The block is the list of expressions that forms the body of an
  473. # indented block of code -- the implementation of a function, a clause in an
  474. # `if`, `switch`, or `try`, and so on...
  475. exports.Block = class Block extends Base
  476. constructor: (nodes) ->
  477. super()
  478. @expressions = compact flatten nodes or []
  479. children: ['expressions']
  480. # Tack an expression on to the end of this expression list.
  481. push: (node) ->
  482. @expressions.push node
  483. this
  484. # Remove and return the last expression of this expression list.
  485. pop: ->
  486. @expressions.pop()
  487. # Add an expression at the beginning of this expression list.
  488. unshift: (node) ->
  489. @expressions.unshift node
  490. this
  491. # If this Block consists of just a single node, unwrap it by pulling
  492. # it back out.
  493. unwrap: ->
  494. if @expressions.length is 1 then @expressions[0] else this
  495. # Is this an empty block of code?
  496. isEmpty: ->
  497. not @expressions.length
  498. isStatement: (o) ->
  499. for exp in @expressions when exp.isStatement o
  500. return yes
  501. no
  502. jumps: (o) ->
  503. for exp in @expressions
  504. return jumpNode if jumpNode = exp.jumps o
  505. # A Block node does not return its entire body, rather it
  506. # ensures that the final expression is returned.
  507. makeReturn: (results, mark) ->
  508. len = @expressions.length
  509. [..., lastExp] = @expressions
  510. lastExp = lastExp?.unwrap() or no
  511. # We also need to check that were not returning a JSX tag if theres an
  512. # adjacent one at the same level; JSX doesnt allow that.
  513. if lastExp and lastExp instanceof Parens and lastExp.body.expressions.length > 1
  514. {body:{expressions}} = lastExp
  515. [..., penult, last] = expressions
  516. penult = penult.unwrap()
  517. last = last.unwrap()
  518. if penult instanceof JSXElement and last instanceof JSXElement
  519. expressions[expressions.length - 1].error 'Adjacent JSX elements must be wrapped in an enclosing tag'
  520. if mark
  521. @expressions[len - 1]?.makeReturn results, mark
  522. return
  523. while len--
  524. expr = @expressions[len]
  525. @expressions[len] = expr.makeReturn results
  526. @expressions.splice(len, 1) if expr instanceof Return and not expr.expression
  527. break
  528. this
  529. compile: (o, lvl) ->
  530. return new Root(this).withLocationDataFrom(this).compile o, lvl unless o.scope
  531. super o, lvl
  532. # Compile all expressions within the **Block** body. If we need to return
  533. # the result, and its an expression, simply return it. If its a statement,
  534. # ask the statement to do so.
  535. compileNode: (o) ->
  536. @tab = o.indent
  537. top = o.level is LEVEL_TOP
  538. compiledNodes = []
  539. for node, index in @expressions
  540. if node.hoisted
  541. # This is a hoisted expression.
  542. # We want to compile this and ignore the result.
  543. node.compileToFragments o
  544. continue
  545. node = (node.unfoldSoak(o) or node)
  546. if node instanceof Block
  547. # This is a nested block. We dont do anything special here like
  548. # enclose it in a new scope; we just compile the statements in this
  549. # block along with our own.
  550. compiledNodes.push node.compileNode o
  551. else if top
  552. node.front = yes
  553. fragments = node.compileToFragments o
  554. unless node.isStatement o
  555. fragments = indentInitial fragments, @
  556. [..., lastFragment] = fragments
  557. unless lastFragment.code is '' or lastFragment.isComment
  558. fragments.push @makeCode ';'
  559. compiledNodes.push fragments
  560. else
  561. compiledNodes.push node.compileToFragments o, LEVEL_LIST
  562. if top
  563. if @spaced
  564. return [].concat @joinFragmentArrays(compiledNodes, '\n\n'), @makeCode('\n')
  565. else
  566. return @joinFragmentArrays(compiledNodes, '\n')
  567. if compiledNodes.length
  568. answer = @joinFragmentArrays(compiledNodes, ', ')
  569. else
  570. answer = [@makeCode 'void 0']
  571. if compiledNodes.length > 1 and o.level >= LEVEL_LIST then @wrapInParentheses answer else answer
  572. compileRoot: (o) ->
  573. @spaced = yes
  574. fragments = @compileWithDeclarations o
  575. HoistTarget.expand fragments
  576. @compileComments fragments
  577. # Compile the expressions body for the contents of a function, with
  578. # declarations of all inner variables pushed up to the top.
  579. compileWithDeclarations: (o) ->
  580. fragments = []
  581. post = []
  582. for exp, i in @expressions
  583. exp = exp.unwrap()
  584. break unless exp instanceof Literal
  585. o = merge(o, level: LEVEL_TOP)
  586. if i
  587. rest = @expressions.splice i, 9e9
  588. [spaced, @spaced] = [@spaced, no]
  589. [fragments, @spaced] = [@compileNode(o), spaced]
  590. @expressions = rest
  591. post = @compileNode o
  592. {scope} = o
  593. if scope.expressions is this
  594. declars = o.scope.hasDeclarations()
  595. assigns = scope.hasAssignments
  596. if declars or assigns
  597. fragments.push @makeCode '\n' if i
  598. fragments.push @makeCode "#{@tab}var "
  599. if declars
  600. declaredVariables = scope.declaredVariables()
  601. for declaredVariable, declaredVariablesIndex in declaredVariables
  602. fragments.push @makeCode declaredVariable
  603. if Object::hasOwnProperty.call o.scope.comments, declaredVariable
  604. fragments.push o.scope.comments[declaredVariable]...
  605. if declaredVariablesIndex isnt declaredVariables.length - 1
  606. fragments.push @makeCode ', '
  607. if assigns
  608. fragments.push @makeCode ",\n#{@tab + TAB}" if declars
  609. fragments.push @makeCode scope.assignedVariables().join(",\n#{@tab + TAB}")
  610. fragments.push @makeCode ";\n#{if @spaced then '\n' else ''}"
  611. else if fragments.length and post.length
  612. fragments.push @makeCode "\n"
  613. fragments.concat post
  614. compileComments: (fragments) ->
  615. for fragment, fragmentIndex in fragments
  616. # Insert comments into the output at the next or previous newline.
  617. # If there are no newlines at which to place comments, create them.
  618. if fragment.precedingComments
  619. # Determine the indentation level of the fragment that we are about
  620. # to insert comments before, and use that indentation level for our
  621. # inserted comments. At this point, the fragments `code` property
  622. # is the generated output JavaScript, and CoffeeScript always
  623. # generates output indented by two spaces; so all we need to do is
  624. # search for a `code` property that begins with at least two spaces.
  625. fragmentIndent = ''
  626. for pastFragment in fragments[0...(fragmentIndex + 1)] by -1
  627. indent = /^ {2,}/m.exec pastFragment.code
  628. if indent
  629. fragmentIndent = indent[0]
  630. break
  631. else if '\n' in pastFragment.code
  632. break
  633. code = "\n#{fragmentIndent}" + (
  634. for commentFragment in fragment.precedingComments
  635. if commentFragment.isHereComment and commentFragment.multiline
  636. multident commentFragment.code, fragmentIndent, no
  637. else
  638. commentFragment.code
  639. ).join("\n#{fragmentIndent}").replace /^(\s*)$/gm, ''
  640. for pastFragment, pastFragmentIndex in fragments[0...(fragmentIndex + 1)] by -1
  641. newLineIndex = pastFragment.code.lastIndexOf '\n'
  642. if newLineIndex is -1
  643. # Keep searching previous fragments until we cant go back any
  644. # further, either because there are no fragments left or weve
  645. # discovered that were in a code block that is interpolated
  646. # inside a string.
  647. if pastFragmentIndex is 0
  648. pastFragment.code = '\n' + pastFragment.code
  649. newLineIndex = 0
  650. else if pastFragment.isStringWithInterpolations and pastFragment.code is '{'
  651. code = code[1..] + '\n' # Move newline to end.
  652. newLineIndex = 1
  653. else
  654. continue
  655. delete fragment.precedingComments
  656. pastFragment.code = pastFragment.code[0...newLineIndex] +
  657. code + pastFragment.code[newLineIndex..]
  658. break
  659. # Yes, this is awfully similar to the previous `if` block, but if you
  660. # look closely youll find lots of tiny differences that make this
  661. # confusing if it were abstracted into a function that both blocks share.
  662. if fragment.followingComments
  663. # Does the first trailing comment follow at the end of a line of code,
  664. # like `; // Comment`, or does it start a new line after a line of code?
  665. trail = fragment.followingComments[0].trail
  666. fragmentIndent = ''
  667. # Find the indent of the next line of code, if we have any non-trailing
  668. # comments to output. We need to first find the next newline, as these
  669. # comments will be output after that; and then the indent of the line
  670. # that follows the next newline.
  671. unless trail and fragment.followingComments.length is 1
  672. onNextLine = no
  673. for upcomingFragment in fragments[fragmentIndex...]
  674. unless onNextLine
  675. if '\n' in upcomingFragment.code
  676. onNextLine = yes
  677. else
  678. continue
  679. else
  680. indent = /^ {2,}/m.exec upcomingFragment.code
  681. if indent
  682. fragmentIndent = indent[0]
  683. break
  684. else if '\n' in upcomingFragment.code
  685. break
  686. # Is this comment following the indent inserted by bare mode?
  687. # If so, theres no need to indent this further.
  688. code = if fragmentIndex is 1 and /^\s+$/.test fragments[0].code
  689. ''
  690. else if trail
  691. ' '
  692. else
  693. "\n#{fragmentIndent}"
  694. # Assemble properly indented comments.
  695. code += (
  696. for commentFragment in fragment.followingComments
  697. if commentFragment.isHereComment and commentFragment.multiline
  698. multident commentFragment.code, fragmentIndent, no
  699. else
  700. commentFragment.code
  701. ).join("\n#{fragmentIndent}").replace /^(\s*)$/gm, ''
  702. for upcomingFragment, upcomingFragmentIndex in fragments[fragmentIndex...]
  703. newLineIndex = upcomingFragment.code.indexOf '\n'
  704. if newLineIndex is -1
  705. # Keep searching upcoming fragments until we cant go any
  706. # further, either because there are no fragments left or weve
  707. # discovered that were in a code block that is interpolated
  708. # inside a string.
  709. if upcomingFragmentIndex is fragments.length - 1
  710. upcomingFragment.code = upcomingFragment.code + '\n'
  711. newLineIndex = upcomingFragment.code.length
  712. else if upcomingFragment.isStringWithInterpolations and upcomingFragment.code is '}'
  713. code = "#{code}\n"
  714. newLineIndex = 0
  715. else
  716. continue
  717. delete fragment.followingComments
  718. # Avoid inserting extra blank lines.
  719. code = code.replace /^\n/, '' if upcomingFragment.code is '\n'
  720. upcomingFragment.code = upcomingFragment.code[0...newLineIndex] +
  721. code + upcomingFragment.code[newLineIndex..]
  722. break
  723. fragments
  724. # Wrap up the given nodes as a **Block**, unless it already happens
  725. # to be one.
  726. @wrap: (nodes) ->
  727. return nodes[0] if nodes.length is 1 and nodes[0] instanceof Block
  728. new Block nodes
  729. astNode: (o) ->
  730. if (o.level? and o.level isnt LEVEL_TOP) and @expressions.length
  731. return (new Sequence(@expressions).withLocationDataFrom @).ast o
  732. super o
  733. astType: ->
  734. if @isRootBlock
  735. 'Program'
  736. else if @isClassBody
  737. 'ClassBody'
  738. else
  739. 'BlockStatement'
  740. astProperties: (o) ->
  741. checkForDirectives = del o, 'checkForDirectives'
  742. sniffDirectives @expressions, notFinalExpression: checkForDirectives if @isRootBlock or checkForDirectives
  743. directives = []
  744. body = []
  745. for expression in @expressions
  746. expressionAst = expression.ast o
  747. # Ignore generated PassthroughLiteral
  748. if not expressionAst?
  749. continue
  750. else if expression instanceof Directive
  751. directives.push expressionAst
  752. # If an expression is a statement, it can be added to the body as is.
  753. else if expression.isStatementAst o
  754. body.push expressionAst
  755. # Otherwise, we need to wrap it in an `ExpressionStatement` AST node.
  756. else
  757. body.push Object.assign
  758. type: 'ExpressionStatement'
  759. expression: expressionAst
  760. ,
  761. expression.astLocationData()
  762. return {
  763. # For now, were not including `sourceType` on the `Program` AST node.
  764. # Its value could be either `'script'` or `'module'`, and theres no way
  765. # for CoffeeScript to always know which it should be. The presence of an
  766. # `import` or `export` statement in source code would imply that it should
  767. # be a `module`, but a project may consist of mostly such files and also
  768. # an outlier file that lacks `import` or `export` but is still imported
  769. # into the project and therefore expects to be treated as a `module`.
  770. # Determining the value of `sourceType` is essentially the same challenge
  771. # posed by determining the parse goal of a JavaScript file, also `module`
  772. # or `script`, and so if Node figures out a way to do so for `.js` files
  773. # then CoffeeScript can copy Nodes algorithm.
  774. # sourceType: 'module'
  775. body, directives
  776. }
  777. astLocationData: ->
  778. return if @isRootBlock and not @locationData?
  779. super()
  780. # A directive e.g. 'use strict'.
  781. # Currently only used during AST generation.
  782. exports.Directive = class Directive extends Base
  783. constructor: (@value) ->
  784. super()
  785. astProperties: (o) ->
  786. return
  787. value: Object.assign {},
  788. @value.ast o
  789. type: 'DirectiveLiteral'
  790. #### Literal
  791. # `Literal` is a base class for static values that can be passed through
  792. # directly into JavaScript without translation, such as: strings, numbers,
  793. # `true`, `false`, `null`...
  794. exports.Literal = class Literal extends Base
  795. constructor: (@value) ->
  796. super()
  797. shouldCache: NO
  798. assigns: (name) ->
  799. name is @value
  800. compileNode: (o) ->
  801. [@makeCode @value]
  802. astProperties: ->
  803. return
  804. value: @value
  805. toString: ->
  806. # This is only intended for debugging.
  807. " #{if @isStatement() then super() else @constructor.name}: #{@value}"
  808. exports.NumberLiteral = class NumberLiteral extends Literal
  809. constructor: (@value, {@parsedValue} = {}) ->
  810. super()
  811. unless @parsedValue?
  812. if isNumber @value
  813. @parsedValue = @value
  814. @value = "#{@value}"
  815. else
  816. @parsedValue = parseNumber @value
  817. isBigInt: ->
  818. /n$/.test @value
  819. astType: ->
  820. if @isBigInt()
  821. 'BigIntLiteral'
  822. else
  823. 'NumericLiteral'
  824. astProperties: ->
  825. return
  826. value:
  827. if @isBigInt()
  828. @parsedValue.toString()
  829. else
  830. @parsedValue
  831. extra:
  832. rawValue:
  833. if @isBigInt()
  834. @parsedValue.toString()
  835. else
  836. @parsedValue
  837. raw: @value
  838. exports.InfinityLiteral = class InfinityLiteral extends NumberLiteral
  839. constructor: (@value, {@originalValue = 'Infinity'} = {}) ->
  840. super()
  841. compileNode: ->
  842. [@makeCode '2e308']
  843. astNode: (o) ->
  844. unless @originalValue is 'Infinity'
  845. return new NumberLiteral(@value).withLocationDataFrom(@).ast o
  846. super o
  847. astType: -> 'Identifier'
  848. astProperties: ->
  849. return
  850. name: 'Infinity'
  851. declaration: no
  852. exports.NaNLiteral = class NaNLiteral extends NumberLiteral
  853. constructor: ->
  854. super 'NaN'
  855. compileNode: (o) ->
  856. code = [@makeCode '0/0']
  857. if o.level >= LEVEL_OP then @wrapInParentheses code else code
  858. astType: -> 'Identifier'
  859. astProperties: ->
  860. return
  861. name: 'NaN'
  862. declaration: no
  863. exports.StringLiteral = class StringLiteral extends Literal
  864. constructor: (@originalValue, {@quote, @initialChunk, @finalChunk, @indent, @double, @heregex} = {}) ->
  865. super ''
  866. @quote = null if @quote is '///'
  867. @fromSourceString = @quote?
  868. @quote ?= '"'
  869. heredoc = @isFromHeredoc()
  870. val = @originalValue
  871. if @heregex
  872. val = val.replace HEREGEX_OMIT, '$1$2'
  873. val = replaceUnicodeCodePointEscapes val, flags: @heregex.flags
  874. else
  875. val = val.replace STRING_OMIT, '$1'
  876. val =
  877. unless @fromSourceString
  878. val
  879. else if heredoc
  880. indentRegex = /// \n#{@indent} ///g if @indent
  881. val = val.replace indentRegex, '\n' if indentRegex
  882. val = val.replace LEADING_BLANK_LINE, '' if @initialChunk
  883. val = val.replace TRAILING_BLANK_LINE, '' if @finalChunk
  884. val
  885. else
  886. val.replace SIMPLE_STRING_OMIT, (match, offset) =>
  887. if (@initialChunk and offset is 0) or
  888. (@finalChunk and offset + match.length is val.length)
  889. ''
  890. else
  891. ' '
  892. @delimiter = @quote.charAt 0
  893. @value = makeDelimitedLiteral val, {
  894. @delimiter
  895. @double
  896. }
  897. @unquotedValueForTemplateLiteral = makeDelimitedLiteral val, {
  898. delimiter: '`'
  899. @double
  900. escapeNewlines: no
  901. includeDelimiters: no
  902. convertTrailingNullEscapes: yes
  903. }
  904. @unquotedValueForJSX = makeDelimitedLiteral val, {
  905. @double
  906. escapeNewlines: no
  907. includeDelimiters: no
  908. escapeDelimiter: no
  909. }
  910. compileNode: (o) ->
  911. return StringWithInterpolations.fromStringLiteral(@).compileNode o if @shouldGenerateTemplateLiteral()
  912. return [@makeCode @unquotedValueForJSX] if @jsx
  913. super o
  914. # `StringLiteral`s can represent either entire literal strings
  915. # or pieces of text inside of e.g. an interpolated string.
  916. # When parsed as the former but needing to be treated as the latter
  917. # (e.g. the string part of a tagged template literal), this will return
  918. # a copy of the `StringLiteral` with the quotes trimmed from its location
  919. # data (like it would have if parsed as part of an interpolated string).
  920. withoutQuotesInLocationData: ->
  921. endsWithNewline = @originalValue[-1..] is '\n'
  922. locationData = Object.assign {}, @locationData
  923. locationData.first_column += @quote.length
  924. if endsWithNewline
  925. locationData.last_line -= 1
  926. locationData.last_column =
  927. if locationData.last_line is locationData.first_line
  928. locationData.first_column + @originalValue.length - '\n'.length
  929. else
  930. @originalValue[...-1].length - '\n'.length - @originalValue[...-1].lastIndexOf('\n')
  931. else
  932. locationData.last_column -= @quote.length
  933. locationData.last_column_exclusive -= @quote.length
  934. locationData.range = [
  935. locationData.range[0] + @quote.length
  936. locationData.range[1] - @quote.length
  937. ]
  938. copy = new StringLiteral @originalValue, {@quote, @initialChunk, @finalChunk, @indent, @double, @heregex}
  939. copy.locationData = locationData
  940. copy
  941. isFromHeredoc: ->
  942. @quote.length is 3
  943. shouldGenerateTemplateLiteral: ->
  944. @isFromHeredoc()
  945. astNode: (o) ->
  946. return StringWithInterpolations.fromStringLiteral(@).ast o if @shouldGenerateTemplateLiteral()
  947. super o
  948. astProperties: ->
  949. return
  950. value: @originalValue
  951. extra:
  952. raw: "#{@delimiter}#{@originalValue}#{@delimiter}"
  953. exports.RegexLiteral = class RegexLiteral extends Literal
  954. constructor: (value, {@delimiter = '/', @heregexCommentTokens = []} = {}) ->
  955. super ''
  956. heregex = @delimiter is '///'
  957. endDelimiterIndex = value.lastIndexOf '/'
  958. @flags = value[endDelimiterIndex + 1..]
  959. val = @originalValue = value[1...endDelimiterIndex]
  960. val = val.replace HEREGEX_OMIT, '$1$2' if heregex
  961. val = replaceUnicodeCodePointEscapes val, {@flags}
  962. @value = "#{makeDelimitedLiteral val, delimiter: '/'}#{@flags}"
  963. REGEX_REGEX: /// ^ / (.*) / \w* $ ///
  964. astType: -> 'RegExpLiteral'
  965. astProperties: (o) ->
  966. [, pattern] = @REGEX_REGEX.exec @value
  967. return {
  968. value: undefined
  969. pattern, @flags, @delimiter
  970. originalPattern: @originalValue
  971. extra:
  972. raw: @value
  973. originalRaw: "#{@delimiter}#{@originalValue}#{@delimiter}#{@flags}"
  974. rawValue: undefined
  975. comments:
  976. for heregexCommentToken in @heregexCommentTokens
  977. if heregexCommentToken.here
  978. new HereComment(heregexCommentToken).ast o
  979. else
  980. new LineComment(heregexCommentToken).ast o
  981. }
  982. exports.PassthroughLiteral = class PassthroughLiteral extends Literal
  983. constructor: (@originalValue, {@here, @generated} = {}) ->
  984. super ''
  985. @value = @originalValue.replace /\\+(`|$)/g, (string) ->
  986. # `string` is always a value like '\`', '\\\`', '\\\\\`', etc.
  987. # By reducing it to its latter half, we turn '\`' to '`', '\\\`' to '\`', etc.
  988. string[-Math.ceil(string.length / 2)..]
  989. astNode: (o) ->
  990. return null if @generated
  991. super o
  992. astProperties: ->
  993. return {
  994. value: @originalValue
  995. here: !!@here
  996. }
  997. exports.IdentifierLiteral = class IdentifierLiteral extends Literal
  998. isAssignable: YES
  999. eachName: (iterator) ->
  1000. iterator @
  1001. astType: ->
  1002. if @jsx
  1003. 'JSXIdentifier'
  1004. else
  1005. 'Identifier'
  1006. astProperties: ->
  1007. return
  1008. name: @value
  1009. declaration: !!@isDeclaration
  1010. exports.PropertyName = class PropertyName extends Literal
  1011. isAssignable: YES
  1012. astType: ->
  1013. if @jsx
  1014. 'JSXIdentifier'
  1015. else
  1016. 'Identifier'
  1017. astProperties: ->
  1018. return
  1019. name: @value
  1020. declaration: no
  1021. exports.ComputedPropertyName = class ComputedPropertyName extends PropertyName
  1022. compileNode: (o) ->
  1023. [@makeCode('['), @value.compileToFragments(o, LEVEL_LIST)..., @makeCode(']')]
  1024. astNode: (o) ->
  1025. @value.ast o
  1026. exports.StatementLiteral = class StatementLiteral extends Literal
  1027. isStatement: YES
  1028. makeReturn: THIS
  1029. jumps: (o) ->
  1030. return this if @value is 'break' and not (o?.loop or o?.block)
  1031. return this if @value is 'continue' and not o?.loop
  1032. compileNode: (o) ->
  1033. [@makeCode "#{@tab}#{@value};"]
  1034. astType: ->
  1035. switch @value
  1036. when 'continue' then 'ContinueStatement'
  1037. when 'break' then 'BreakStatement'
  1038. when 'debugger' then 'DebuggerStatement'
  1039. exports.ThisLiteral = class ThisLiteral extends Literal
  1040. constructor: (value) ->
  1041. super 'this'
  1042. @shorthand = value is '@'
  1043. compileNode: (o) ->
  1044. code = if o.scope.method?.bound then o.scope.method.context else @value
  1045. [@makeCode code]
  1046. astType: -> 'ThisExpression'
  1047. astProperties: ->
  1048. return
  1049. shorthand: @shorthand
  1050. exports.UndefinedLiteral = class UndefinedLiteral extends Literal
  1051. constructor: ->
  1052. super 'undefined'
  1053. compileNode: (o) ->
  1054. [@makeCode if o.level >= LEVEL_ACCESS then '(void 0)' else 'void 0']
  1055. astType: -> 'Identifier'
  1056. astProperties: ->
  1057. return
  1058. name: @value
  1059. declaration: no
  1060. exports.NullLiteral = class NullLiteral extends Literal
  1061. constructor: ->
  1062. super 'null'
  1063. exports.BooleanLiteral = class BooleanLiteral extends Literal
  1064. constructor: (value, {@originalValue} = {}) ->
  1065. super value
  1066. @originalValue ?= @value
  1067. astProperties: ->
  1068. value: if @value is 'true' then yes else no
  1069. name: @originalValue
  1070. exports.DefaultLiteral = class DefaultLiteral extends Literal
  1071. astType: -> 'Identifier'
  1072. astProperties: ->
  1073. return
  1074. name: 'default'
  1075. declaration: no
  1076. #### Return
  1077. # A `return` is a *pureStatement*wrapping it in a closure wouldnt make sense.
  1078. exports.Return = class Return extends Base
  1079. constructor: (@expression, {@belongsToFuncDirectiveReturn} = {}) ->
  1080. super()
  1081. children: ['expression']
  1082. isStatement: YES
  1083. makeReturn: THIS
  1084. jumps: THIS
  1085. compileToFragments: (o, level) ->
  1086. expr = @expression?.makeReturn()
  1087. if expr and expr not instanceof Return then expr.compileToFragments o, level else super o, level
  1088. compileNode: (o) ->
  1089. answer = []
  1090. # TODO: If we call `expression.compile()` here twice, well sometimes
  1091. # get back different results!
  1092. if @expression
  1093. answer = @expression.compileToFragments o, LEVEL_PAREN
  1094. unshiftAfterComments answer, @makeCode "#{@tab}return "
  1095. # Since the `return` got indented by `@tab`, preceding comments that are
  1096. # multiline need to be indented.
  1097. for fragment in answer
  1098. if fragment.isHereComment and '\n' in fragment.code
  1099. fragment.code = multident fragment.code, @tab
  1100. else if fragment.isLineComment
  1101. fragment.code = "#{@tab}#{fragment.code}"
  1102. else
  1103. break
  1104. else
  1105. answer.push @makeCode "#{@tab}return"
  1106. answer.push @makeCode ';'
  1107. answer
  1108. checkForPureStatementInExpression: ->
  1109. # dont flag `return` from `await return`/`yield return` as invalid.
  1110. return if @belongsToFuncDirectiveReturn
  1111. super()
  1112. astType: -> 'ReturnStatement'
  1113. astProperties: (o) ->
  1114. argument: @expression?.ast(o, LEVEL_PAREN) ? null
  1115. # Parent class for `YieldReturn`/`AwaitReturn`.
  1116. exports.FuncDirectiveReturn = class FuncDirectiveReturn extends Return
  1117. constructor: (expression, {@returnKeyword}) ->
  1118. super expression
  1119. compileNode: (o) ->
  1120. @checkScope o
  1121. super o
  1122. checkScope: (o) ->
  1123. unless o.scope.parent?
  1124. @error "#{@keyword} can only occur inside functions"
  1125. isStatementAst: NO
  1126. astNode: (o) ->
  1127. @checkScope o
  1128. new Op @keyword,
  1129. new Return @expression, belongsToFuncDirectiveReturn: yes
  1130. .withLocationDataFrom(
  1131. if @expression?
  1132. locationData: mergeLocationData @returnKeyword.locationData, @expression.locationData
  1133. else
  1134. @returnKeyword
  1135. )
  1136. .withLocationDataFrom @
  1137. .ast o
  1138. # `yield return` works exactly like `return`, except that it turns the function
  1139. # into a generator.
  1140. exports.YieldReturn = class YieldReturn extends FuncDirectiveReturn
  1141. keyword: 'yield'
  1142. exports.AwaitReturn = class AwaitReturn extends FuncDirectiveReturn
  1143. keyword: 'await'
  1144. #### Value
  1145. # A value, variable or literal or parenthesized, indexed or dotted into,
  1146. # or vanilla.
  1147. exports.Value = class Value extends Base
  1148. constructor: (base, props, tag, isDefaultValue = no) ->
  1149. super()
  1150. return base if not props and base instanceof Value
  1151. @base = base
  1152. @properties = props or []
  1153. @tag = tag
  1154. @[tag] = yes if tag
  1155. @isDefaultValue = isDefaultValue
  1156. # If this is a `@foo =` assignment, if there are comments on `@` move them
  1157. # to be on `foo`.
  1158. if @base?.comments and @base instanceof ThisLiteral and @properties[0]?.name?
  1159. moveComments @base, @properties[0].name
  1160. children: ['base', 'properties']
  1161. # Add a property (or *properties* ) `Access` to the list.
  1162. add: (props) ->
  1163. @properties = @properties.concat props
  1164. @forceUpdateLocation = yes
  1165. this
  1166. hasProperties: ->
  1167. @properties.length isnt 0
  1168. bareLiteral: (type) ->
  1169. not @properties.length and @base instanceof type
  1170. # Some boolean checks for the benefit of other nodes.
  1171. isArray : -> @bareLiteral(Arr)
  1172. isRange : -> @bareLiteral(Range)
  1173. shouldCache : -> @hasProperties() or @base.shouldCache()
  1174. isAssignable : (opts) -> @hasProperties() or @base.isAssignable opts
  1175. isNumber : -> @bareLiteral(NumberLiteral)
  1176. isString : -> @bareLiteral(StringLiteral)
  1177. isRegex : -> @bareLiteral(RegexLiteral)
  1178. isUndefined : -> @bareLiteral(UndefinedLiteral)
  1179. isNull : -> @bareLiteral(NullLiteral)
  1180. isBoolean : -> @bareLiteral(BooleanLiteral)
  1181. isAtomic : ->
  1182. for node in @properties.concat @base
  1183. return no if node.soak or node instanceof Call or node instanceof Op and node.operator is 'do'
  1184. yes
  1185. isNotCallable : -> @isNumber() or @isString() or @isRegex() or
  1186. @isArray() or @isRange() or @isSplice() or @isObject() or
  1187. @isUndefined() or @isNull() or @isBoolean()
  1188. isStatement : (o) -> not @properties.length and @base.isStatement o
  1189. isJSXTag : -> @base instanceof JSXTag
  1190. assigns : (name) -> not @properties.length and @base.assigns name
  1191. jumps : (o) -> not @properties.length and @base.jumps o
  1192. isObject: (onlyGenerated) ->
  1193. return no if @properties.length
  1194. (@base instanceof Obj) and (not onlyGenerated or @base.generated)
  1195. isElision: ->
  1196. return no unless @base instanceof Arr
  1197. @base.hasElision()
  1198. isSplice: ->
  1199. [..., lastProperty] = @properties
  1200. lastProperty instanceof Slice
  1201. looksStatic: (className) ->
  1202. return no unless ((thisLiteral = @base) instanceof ThisLiteral or (name = @base).value is className) and
  1203. @properties.length is 1 and @properties[0].name?.value isnt 'prototype'
  1204. return
  1205. staticClassName: thisLiteral ? name
  1206. # The value can be unwrapped as its inner node, if there are no attached
  1207. # properties.
  1208. unwrap: ->
  1209. if @properties.length then this else @base
  1210. # A reference has base part (`this` value) and name part.
  1211. # We cache them separately for compiling complex expressions.
  1212. # `a()[b()] ?= c` -> `(_base = a())[_name = b()] ? _base[_name] = c`
  1213. cacheReference: (o) ->
  1214. [..., name] = @properties
  1215. if @properties.length < 2 and not @base.shouldCache() and not name?.shouldCache()
  1216. return [this, this] # `a` `a.b`
  1217. base = new Value @base, @properties[...-1]
  1218. if base.shouldCache() # `a().b`
  1219. bref = new IdentifierLiteral o.scope.freeVariable 'base'
  1220. base = new Value new Parens new Assign bref, base
  1221. return [base, bref] unless name # `a()`
  1222. if name.shouldCache() # `a[b()]`
  1223. nref = new IdentifierLiteral o.scope.freeVariable 'name'
  1224. name = new Index new Assign nref, name.index
  1225. nref = new Index nref
  1226. [base.add(name), new Value(bref or base.base, [nref or name])]
  1227. # We compile a value to JavaScript by compiling and joining each property.
  1228. # Things get much more interesting if the chain of properties has *soak*
  1229. # operators `?.` interspersed. Then we have to take care not to accidentally
  1230. # evaluate anything twice when building the soak chain.
  1231. compileNode: (o) ->
  1232. @base.front = @front
  1233. props = @properties
  1234. if props.length and @base.cached?
  1235. # Cached fragments enable correct order of the compilation,
  1236. # and reuse of variables in the scope.
  1237. # Example:
  1238. # `a(x = 5).b(-> x = 6)` should compile in the same order as
  1239. # `a(x = 5); b(-> x = 6)`
  1240. # (see issue #4437, https://github.com/jashkenas/coffeescript/issues/4437)
  1241. fragments = @base.cached
  1242. else
  1243. fragments = @base.compileToFragments o, (if props.length then LEVEL_ACCESS else null)
  1244. if props.length and SIMPLENUM.test fragmentsToText fragments
  1245. fragments.push @makeCode '.'
  1246. for prop in props
  1247. fragments.push (prop.compileToFragments o)...
  1248. fragments
  1249. # Unfold a soak into an `If`: `a?.b` -> `a.b if a?`
  1250. unfoldSoak: (o) ->
  1251. @unfoldedSoak ?= do =>
  1252. ifn = @base.unfoldSoak o
  1253. if ifn
  1254. ifn.body.properties.push @properties...
  1255. return ifn
  1256. for prop, i in @properties when prop.soak
  1257. prop.soak = off
  1258. fst = new Value @base, @properties[...i]
  1259. snd = new Value @base, @properties[i..]
  1260. if fst.shouldCache()
  1261. ref = new IdentifierLiteral o.scope.freeVariable 'ref'
  1262. fst = new Parens new Assign ref, fst
  1263. snd.base = ref
  1264. return new If new Existence(fst), snd, soak: on
  1265. no
  1266. eachName: (iterator, {checkAssignability = yes} = {}) ->
  1267. if @hasProperties()
  1268. iterator @
  1269. else if not checkAssignability or @base.isAssignable()
  1270. @base.eachName iterator
  1271. else
  1272. @error 'tried to assign to unassignable value'
  1273. # For AST generation, we need an `object` thats this `Value` minus its last
  1274. # property, if it has properties.
  1275. object: ->
  1276. return @ unless @hasProperties()
  1277. # Get all properties except the last one; for a `Value` with only one
  1278. # property, `initialProperties` is an empty array.
  1279. initialProperties = @properties[0...@properties.length - 1]
  1280. # Create the `object` that becomes the new base for the split-off final
  1281. # property.
  1282. object = new Value @base, initialProperties, @tag, @isDefaultValue
  1283. # Add location data to our new node, so that it has correct location data
  1284. # for source maps or later conversion into AST location data.
  1285. object.locationData =
  1286. if initialProperties.length is 0
  1287. # This new `Value` has only one property, so the location data is just
  1288. # that of the parent `Value`s base.
  1289. @base.locationData
  1290. else
  1291. # This new `Value` has multiple properties, so the location data spans
  1292. # from the parent `Value`s base to the last property thats included
  1293. # in this new node (a.k.a. the second-to-last property of the parent).
  1294. mergeLocationData @base.locationData, initialProperties[initialProperties.length - 1].locationData
  1295. object
  1296. containsSoak: ->
  1297. return no unless @hasProperties()
  1298. for property in @properties when property.soak
  1299. return yes
  1300. return yes if @base instanceof Call and @base.soak
  1301. no
  1302. astNode: (o) ->
  1303. # If the `Value` has no properties, the AST node is just whatever this
  1304. # nodes `base` is.
  1305. return @base.ast o unless @hasProperties()
  1306. # Otherwise, call `Base::ast` which in turn calls the `astType` and
  1307. # `astProperties` methods below.
  1308. super o
  1309. astType: ->
  1310. if @isJSXTag()
  1311. 'JSXMemberExpression'
  1312. else if @containsSoak()
  1313. 'OptionalMemberExpression'
  1314. else
  1315. 'MemberExpression'
  1316. # If this `Value` has properties, the *last* property (e.g. `c` in `a.b.c`)
  1317. # becomes the `property`, and the preceding properties (e.g. `a.b`) become
  1318. # a child `Value` node assigned to the `object` property.
  1319. astProperties: (o) ->
  1320. [..., property] = @properties
  1321. property.name.jsx = yes if @isJSXTag()
  1322. computed = property instanceof Index or property.name?.unwrap() not instanceof PropertyName
  1323. return {
  1324. object: @object().ast o, LEVEL_ACCESS
  1325. property: property.ast o, (LEVEL_PAREN if computed)
  1326. computed
  1327. optional: !!property.soak
  1328. shorthand: !!property.shorthand
  1329. }
  1330. astLocationData: ->
  1331. return super() unless @isJSXTag()
  1332. # don't include leading < of JSX tag in location data
  1333. mergeAstLocationData(
  1334. jisonLocationDataToAstLocationData(@base.tagNameLocationData),
  1335. jisonLocationDataToAstLocationData(@properties[@properties.length - 1].locationData)
  1336. )
  1337. exports.MetaProperty = class MetaProperty extends Base
  1338. constructor: (@meta, @property) ->
  1339. super()
  1340. children: ['meta', 'property']
  1341. checkValid: (o) ->
  1342. if @meta.value is 'new'
  1343. if @property instanceof Access and @property.name.value is 'target'
  1344. unless o.scope.parent?
  1345. @error "new.target can only occur inside functions"
  1346. else
  1347. @error "the only valid meta property for new is new.target"
  1348. compileNode: (o) ->
  1349. @checkValid o
  1350. fragments = []
  1351. fragments.push @meta.compileToFragments(o, LEVEL_ACCESS)...
  1352. fragments.push @property.compileToFragments(o)...
  1353. fragments
  1354. astProperties: (o) ->
  1355. @checkValid o
  1356. return
  1357. meta: @meta.ast o, LEVEL_ACCESS
  1358. property: @property.ast o
  1359. #### HereComment
  1360. # Comment delimited by `###` (becoming `/* */`).
  1361. exports.HereComment = class HereComment extends Base
  1362. constructor: ({ @content, @newLine, @unshift, @locationData }) ->
  1363. super()
  1364. compileNode: (o) ->
  1365. multiline = '\n' in @content
  1366. # Unindent multiline comments. They will be reindented later.
  1367. if multiline
  1368. indent = null
  1369. for line in @content.split '\n'
  1370. leadingWhitespace = /^\s*/.exec(line)[0]
  1371. if not indent or leadingWhitespace.length < indent.length
  1372. indent = leadingWhitespace
  1373. @content = @content.replace /// \n #{indent} ///g, '\n' if indent
  1374. hasLeadingMarks = /\n\s*[#|\*]/.test @content
  1375. @content = @content.replace /^([ \t]*)#(?=\s)/gm, ' *' if hasLeadingMarks
  1376. @content = "/*#{@content}#{if hasLeadingMarks then ' ' else ''}*/"
  1377. fragment = @makeCode @content
  1378. fragment.newLine = @newLine
  1379. fragment.unshift = @unshift
  1380. fragment.multiline = multiline
  1381. # Dont rely on `fragment.type`, which can break when the compiler is minified.
  1382. fragment.isComment = fragment.isHereComment = yes
  1383. fragment
  1384. astType: -> 'CommentBlock'
  1385. astProperties: ->
  1386. return
  1387. value: @content
  1388. #### LineComment
  1389. # Comment running from `#` to the end of a line (becoming `//`).
  1390. exports.LineComment = class LineComment extends Base
  1391. constructor: ({ @content, @newLine, @unshift, @locationData, @precededByBlankLine }) ->
  1392. super()
  1393. compileNode: (o) ->
  1394. fragment = @makeCode(if /^\s*$/.test @content then '' else "#{if @precededByBlankLine then "\n#{o.indent}" else ''}//#{@content}")
  1395. fragment.newLine = @newLine
  1396. fragment.unshift = @unshift
  1397. fragment.trail = not @newLine and not @unshift
  1398. # Dont rely on `fragment.type`, which can break when the compiler is minified.
  1399. fragment.isComment = fragment.isLineComment = yes
  1400. fragment
  1401. astType: -> 'CommentLine'
  1402. astProperties: ->
  1403. return
  1404. value: @content
  1405. #### JSX
  1406. exports.JSXIdentifier = class JSXIdentifier extends IdentifierLiteral
  1407. astType: -> 'JSXIdentifier'
  1408. exports.JSXTag = class JSXTag extends JSXIdentifier
  1409. constructor: (value, {
  1410. @tagNameLocationData
  1411. @closingTagOpeningBracketLocationData
  1412. @closingTagSlashLocationData
  1413. @closingTagNameLocationData
  1414. @closingTagClosingBracketLocationData
  1415. }) ->
  1416. super value
  1417. astProperties: ->
  1418. return
  1419. name: @value
  1420. exports.JSXExpressionContainer = class JSXExpressionContainer extends Base
  1421. constructor: (@expression, {locationData} = {}) ->
  1422. super()
  1423. @expression.jsxAttribute = yes
  1424. @locationData = locationData ? @expression.locationData
  1425. children: ['expression']
  1426. compileNode: (o) ->
  1427. @expression.compileNode(o)
  1428. astProperties: (o) ->
  1429. return
  1430. expression: astAsBlockIfNeeded @expression, o
  1431. exports.JSXEmptyExpression = class JSXEmptyExpression extends Base
  1432. exports.JSXText = class JSXText extends Base
  1433. constructor: (stringLiteral) ->
  1434. super()
  1435. @value = stringLiteral.unquotedValueForJSX
  1436. @locationData = stringLiteral.locationData
  1437. astProperties: ->
  1438. return {
  1439. @value
  1440. extra:
  1441. raw: @value
  1442. }
  1443. exports.JSXAttribute = class JSXAttribute extends Base
  1444. constructor: ({@name, value}) ->
  1445. super()
  1446. @value =
  1447. if value?
  1448. value = value.base
  1449. if value instanceof StringLiteral
  1450. value
  1451. else
  1452. new JSXExpressionContainer value
  1453. else
  1454. null
  1455. @value?.comments = value.comments
  1456. children: ['name', 'value']
  1457. compileNode: (o) ->
  1458. compiledName = @name.compileToFragments o, LEVEL_LIST
  1459. return compiledName unless @value?
  1460. val = @value.compileToFragments o, LEVEL_LIST
  1461. compiledName.concat @makeCode('='), val
  1462. astProperties: (o) ->
  1463. name = @name
  1464. if ':' in name.value
  1465. name = new JSXNamespacedName name
  1466. return
  1467. name: name.ast o
  1468. value: @value?.ast(o) ? null
  1469. exports.JSXAttributes = class JSXAttributes extends Base
  1470. constructor: (arr) ->
  1471. super()
  1472. @attributes = []
  1473. for object in arr.objects
  1474. @checkValidAttribute object
  1475. {base} = object
  1476. if base instanceof IdentifierLiteral
  1477. # attribute with no value eg disabled
  1478. attribute = new JSXAttribute name: new JSXIdentifier(base.value).withLocationDataAndCommentsFrom base
  1479. attribute.locationData = base.locationData
  1480. @attributes.push attribute
  1481. else if not base.generated
  1482. # object spread attribute eg {...props}
  1483. attribute = base.properties[0]
  1484. attribute.jsx = yes
  1485. attribute.locationData = base.locationData
  1486. @attributes.push attribute
  1487. else
  1488. # Obj containing attributes with values eg a="b" c={d}
  1489. for property in base.properties
  1490. {variable, value} = property
  1491. attribute = new JSXAttribute {
  1492. name: new JSXIdentifier(variable.base.value).withLocationDataAndCommentsFrom variable.base
  1493. value
  1494. }
  1495. attribute.locationData = property.locationData
  1496. @attributes.push attribute
  1497. @locationData = arr.locationData
  1498. children: ['attributes']
  1499. # Catch invalid attributes: <div {a:"b", props} {props} "value" />
  1500. checkValidAttribute: (object) ->
  1501. {base: attribute} = object
  1502. properties = attribute?.properties or []
  1503. if not (attribute instanceof Obj or attribute instanceof IdentifierLiteral) or (attribute instanceof Obj and not attribute.generated and (properties.length > 1 or not (properties[0] instanceof Splat)))
  1504. object.error """
  1505. Unexpected token. Allowed JSX attributes are: id="val", src={source}, {props...} or attribute.
  1506. """
  1507. compileNode: (o) ->
  1508. fragments = []
  1509. for attribute in @attributes
  1510. fragments.push @makeCode ' '
  1511. fragments.push attribute.compileToFragments(o, LEVEL_TOP)...
  1512. fragments
  1513. astNode: (o) ->
  1514. attribute.ast(o) for attribute in @attributes
  1515. exports.JSXNamespacedName = class JSXNamespacedName extends Base
  1516. constructor: (tag) ->
  1517. super()
  1518. [namespace, name] = tag.value.split ':'
  1519. @namespace = new JSXIdentifier(namespace).withLocationDataFrom locationData: extractSameLineLocationDataFirst(namespace.length) tag.locationData
  1520. @name = new JSXIdentifier(name ).withLocationDataFrom locationData: extractSameLineLocationDataLast(name.length ) tag.locationData
  1521. @locationData = tag.locationData
  1522. children: ['namespace', 'name']
  1523. astProperties: (o) ->
  1524. return
  1525. namespace: @namespace.ast o
  1526. name: @name.ast o
  1527. # Node for a JSX element
  1528. exports.JSXElement = class JSXElement extends Base
  1529. constructor: ({@tagName, @attributes, @content}) ->
  1530. super()
  1531. children: ['tagName', 'attributes', 'content']
  1532. compileNode: (o) ->
  1533. @content?.base.jsx = yes
  1534. fragments = [@makeCode('<')]
  1535. fragments.push (tag = @tagName.compileToFragments(o, LEVEL_ACCESS))...
  1536. fragments.push @attributes.compileToFragments(o)...
  1537. if @content
  1538. fragments.push @makeCode('>')
  1539. fragments.push @content.compileNode(o, LEVEL_LIST)...
  1540. fragments.push [@makeCode('</'), tag..., @makeCode('>')]...
  1541. else
  1542. fragments.push @makeCode(' />')
  1543. fragments
  1544. isFragment: ->
  1545. !@tagName.base.value.length
  1546. astNode: (o) ->
  1547. # The location data spanning the opening element < ... > is captured by
  1548. # the generated Arr which contains the element's attributes
  1549. @openingElementLocationData = jisonLocationDataToAstLocationData @attributes.locationData
  1550. tagName = @tagName.base
  1551. tagName.locationData = tagName.tagNameLocationData
  1552. if @content?
  1553. @closingElementLocationData = mergeAstLocationData(
  1554. jisonLocationDataToAstLocationData tagName.closingTagOpeningBracketLocationData
  1555. jisonLocationDataToAstLocationData tagName.closingTagClosingBracketLocationData
  1556. )
  1557. super o
  1558. astType: ->
  1559. if @isFragment()
  1560. 'JSXFragment'
  1561. else
  1562. 'JSXElement'
  1563. elementAstProperties: (o) ->
  1564. tagNameAst = =>
  1565. tag = @tagName.unwrap()
  1566. if tag?.value and ':' in tag.value
  1567. tag = new JSXNamespacedName tag
  1568. tag.ast o
  1569. openingElement = Object.assign {
  1570. type: 'JSXOpeningElement'
  1571. name: tagNameAst()
  1572. selfClosing: not @closingElementLocationData?
  1573. attributes: @attributes.ast o
  1574. }, @openingElementLocationData
  1575. closingElement = null
  1576. if @closingElementLocationData?
  1577. closingElement = Object.assign {
  1578. type: 'JSXClosingElement'
  1579. name: Object.assign(
  1580. tagNameAst(),
  1581. jisonLocationDataToAstLocationData @tagName.base.closingTagNameLocationData
  1582. )
  1583. }, @closingElementLocationData
  1584. if closingElement.name.type in ['JSXMemberExpression', 'JSXNamespacedName']
  1585. rangeDiff = closingElement.range[0] - openingElement.range[0] + '/'.length
  1586. columnDiff = closingElement.loc.start.column - openingElement.loc.start.column + '/'.length
  1587. shiftAstLocationData = (node) =>
  1588. node.range = [
  1589. node.range[0] + rangeDiff
  1590. node.range[1] + rangeDiff
  1591. ]
  1592. node.start += rangeDiff
  1593. node.end += rangeDiff
  1594. node.loc.start =
  1595. line: @closingElementLocationData.loc.start.line
  1596. column: node.loc.start.column + columnDiff
  1597. node.loc.end =
  1598. line: @closingElementLocationData.loc.start.line
  1599. column: node.loc.end.column + columnDiff
  1600. if closingElement.name.type is 'JSXMemberExpression'
  1601. currentExpr = closingElement.name
  1602. while currentExpr.type is 'JSXMemberExpression'
  1603. shiftAstLocationData currentExpr unless currentExpr is closingElement.name
  1604. shiftAstLocationData currentExpr.property
  1605. currentExpr = currentExpr.object
  1606. shiftAstLocationData currentExpr
  1607. else # JSXNamespacedName
  1608. shiftAstLocationData closingElement.name.namespace
  1609. shiftAstLocationData closingElement.name.name
  1610. {openingElement, closingElement}
  1611. fragmentAstProperties: (o) ->
  1612. openingFragment = Object.assign {
  1613. type: 'JSXOpeningFragment'
  1614. }, @openingElementLocationData
  1615. closingFragment = Object.assign {
  1616. type: 'JSXClosingFragment'
  1617. }, @closingElementLocationData
  1618. {openingFragment, closingFragment}
  1619. contentAst: (o) ->
  1620. return [] unless @content and not @content.base.isEmpty?()
  1621. content = @content.unwrapAll()
  1622. children =
  1623. if content instanceof StringLiteral
  1624. [new JSXText content]
  1625. else # StringWithInterpolations
  1626. for element in @content.unwrapAll().extractElements o, includeInterpolationWrappers: yes, isJsx: yes
  1627. if element instanceof StringLiteral
  1628. new JSXText element
  1629. else # Interpolation
  1630. {expression} = element
  1631. unless expression?
  1632. emptyExpression = new JSXEmptyExpression()
  1633. emptyExpression.locationData = emptyExpressionLocationData {
  1634. interpolationNode: element
  1635. openingBrace: '{'
  1636. closingBrace: '}'
  1637. }
  1638. new JSXExpressionContainer emptyExpression, locationData: element.locationData
  1639. else
  1640. unwrapped = expression.unwrapAll()
  1641. if unwrapped instanceof JSXElement and
  1642. # distinguish `<a><b /></a>` from `<a>{<b />}</a>`
  1643. unwrapped.locationData.range[0] is element.locationData.range[0]
  1644. unwrapped
  1645. else
  1646. new JSXExpressionContainer unwrapped, locationData: element.locationData
  1647. child.ast(o) for child in children when not (child instanceof JSXText and child.value.length is 0)
  1648. astProperties: (o) ->
  1649. Object.assign(
  1650. if @isFragment()
  1651. @fragmentAstProperties o
  1652. else
  1653. @elementAstProperties o
  1654. ,
  1655. children: @contentAst o
  1656. )
  1657. astLocationData: ->
  1658. if @closingElementLocationData?
  1659. mergeAstLocationData @openingElementLocationData, @closingElementLocationData
  1660. else
  1661. @openingElementLocationData
  1662. #### Call
  1663. # Node for a function invocation.
  1664. exports.Call = class Call extends Base
  1665. constructor: (@variable, @args = [], @soak, @token) ->
  1666. super()
  1667. @implicit = @args.implicit
  1668. @isNew = no
  1669. if @variable instanceof Value and @variable.isNotCallable()
  1670. @variable.error "literal is not a function"
  1671. if @variable.base instanceof JSXTag
  1672. return new JSXElement(
  1673. tagName: @variable
  1674. attributes: new JSXAttributes @args[0].base
  1675. content: @args[1]
  1676. )
  1677. # `@variable` never gets output as a result of this node getting created as
  1678. # part of `RegexWithInterpolations`, so for that case move any comments to
  1679. # the `args` property that gets passed into `RegexWithInterpolations` via
  1680. # the grammar.
  1681. if @variable.base?.value is 'RegExp' and @args.length isnt 0
  1682. moveComments @variable, @args[0]
  1683. children: ['variable', 'args']
  1684. # When setting the location, we sometimes need to update the start location to
  1685. # account for a newly-discovered `new` operator to the left of us. This
  1686. # expands the range on the left, but not the right.
  1687. updateLocationDataIfMissing: (locationData) ->
  1688. if @locationData and @needsUpdatedStartLocation
  1689. @locationData = Object.assign {},
  1690. @locationData,
  1691. first_line: locationData.first_line
  1692. first_column: locationData.first_column
  1693. range: [
  1694. locationData.range[0]
  1695. @locationData.range[1]
  1696. ]
  1697. base = @variable?.base or @variable
  1698. if base.needsUpdatedStartLocation
  1699. @variable.locationData = Object.assign {},
  1700. @variable.locationData,
  1701. first_line: locationData.first_line
  1702. first_column: locationData.first_column
  1703. range: [
  1704. locationData.range[0]
  1705. @variable.locationData.range[1]
  1706. ]
  1707. base.updateLocationDataIfMissing locationData
  1708. delete @needsUpdatedStartLocation
  1709. super locationData
  1710. # Tag this invocation as creating a new instance.
  1711. newInstance: ->
  1712. base = @variable?.base or @variable
  1713. if base instanceof Call and not base.isNew
  1714. base.newInstance()
  1715. else
  1716. @isNew = true
  1717. @needsUpdatedStartLocation = true
  1718. this
  1719. # Soaked chained invocations unfold into if/else ternary structures.
  1720. unfoldSoak: (o) ->
  1721. if @soak
  1722. if @variable instanceof Super
  1723. left = new Literal @variable.compile o
  1724. rite = new Value left
  1725. @variable.error "Unsupported reference to 'super'" unless @variable.accessor?
  1726. else
  1727. return ifn if ifn = unfoldSoak o, this, 'variable'
  1728. [left, rite] = new Value(@variable).cacheReference o
  1729. rite = new Call rite, @args
  1730. rite.isNew = @isNew
  1731. left = new Literal "typeof #{ left.compile o } === \"function\""
  1732. return new If left, new Value(rite), soak: yes
  1733. call = this
  1734. list = []
  1735. loop
  1736. if call.variable instanceof Call
  1737. list.push call
  1738. call = call.variable
  1739. continue
  1740. break unless call.variable instanceof Value
  1741. list.push call
  1742. break unless (call = call.variable.base) instanceof Call
  1743. for call in list.reverse()
  1744. if ifn
  1745. if call.variable instanceof Call
  1746. call.variable = ifn
  1747. else
  1748. call.variable.base = ifn
  1749. ifn = unfoldSoak o, call, 'variable'
  1750. ifn
  1751. # Compile a vanilla function call.
  1752. compileNode: (o) ->
  1753. @checkForNewSuper()
  1754. @variable?.front = @front
  1755. compiledArgs = []
  1756. # If variable is `Accessor` fragments are cached and used later
  1757. # in `Value::compileNode` to ensure correct order of the compilation,
  1758. # and reuse of variables in the scope.
  1759. # Example:
  1760. # `a(x = 5).b(-> x = 6)` should compile in the same order as
  1761. # `a(x = 5); b(-> x = 6)`
  1762. # (see issue #4437, https://github.com/jashkenas/coffeescript/issues/4437)
  1763. varAccess = @variable?.properties?[0] instanceof Access
  1764. argCode = (arg for arg in (@args || []) when arg instanceof Code)
  1765. if argCode.length > 0 and varAccess and not @variable.base.cached
  1766. [cache] = @variable.base.cache o, LEVEL_ACCESS, -> no
  1767. @variable.base.cached = cache
  1768. for arg, argIndex in @args
  1769. if argIndex then compiledArgs.push @makeCode ", "
  1770. compiledArgs.push (arg.compileToFragments o, LEVEL_LIST)...
  1771. fragments = []
  1772. if @isNew
  1773. fragments.push @makeCode 'new '
  1774. fragments.push @variable.compileToFragments(o, LEVEL_ACCESS)...
  1775. fragments.push @makeCode('('), compiledArgs..., @makeCode(')')
  1776. fragments
  1777. checkForNewSuper: ->
  1778. if @isNew
  1779. @variable.error "Unsupported reference to 'super'" if @variable instanceof Super
  1780. containsSoak: ->
  1781. return yes if @soak
  1782. return yes if @variable?.containsSoak?()
  1783. no
  1784. astNode: (o) ->
  1785. if @soak and @variable instanceof Super and o.scope.namedMethod()?.ctor
  1786. @variable.error "Unsupported reference to 'super'"
  1787. @checkForNewSuper()
  1788. super o
  1789. astType: ->
  1790. if @isNew
  1791. 'NewExpression'
  1792. else if @containsSoak()
  1793. 'OptionalCallExpression'
  1794. else
  1795. 'CallExpression'
  1796. astProperties: (o) ->
  1797. return
  1798. callee: @variable.ast o, LEVEL_ACCESS
  1799. arguments: arg.ast(o, LEVEL_LIST) for arg in @args
  1800. optional: !!@soak
  1801. implicit: !!@implicit
  1802. #### Super
  1803. # Takes care of converting `super()` calls into calls against the prototype's
  1804. # function of the same name.
  1805. # When `expressions` are set the call will be compiled in such a way that the
  1806. # expressions are evaluated without altering the return value of the `SuperCall`
  1807. # expression.
  1808. exports.SuperCall = class SuperCall extends Call
  1809. children: Call::children.concat ['expressions']
  1810. isStatement: (o) ->
  1811. @expressions?.length and o.level is LEVEL_TOP
  1812. compileNode: (o) ->
  1813. return super o unless @expressions?.length
  1814. superCall = new Literal fragmentsToText super o
  1815. replacement = new Block @expressions.slice()
  1816. if o.level > LEVEL_TOP
  1817. # If we might be in an expression we need to cache and return the result
  1818. [superCall, ref] = superCall.cache o, null, YES
  1819. replacement.push ref
  1820. replacement.unshift superCall
  1821. replacement.compileToFragments o, if o.level is LEVEL_TOP then o.level else LEVEL_LIST
  1822. exports.Super = class Super extends Base
  1823. constructor: (@accessor, @superLiteral) ->
  1824. super()
  1825. children: ['accessor']
  1826. compileNode: (o) ->
  1827. @checkInInstanceMethod o
  1828. method = o.scope.namedMethod()
  1829. unless method.ctor? or @accessor?
  1830. {name, variable} = method
  1831. if name.shouldCache() or (name instanceof Index and name.index.isAssignable())
  1832. nref = new IdentifierLiteral o.scope.parent.freeVariable 'name'
  1833. name.index = new Assign nref, name.index
  1834. @accessor = if nref? then new Index nref else name
  1835. if @accessor?.name?.comments
  1836. # A `super()` call gets compiled to e.g. `super.method()`, which means
  1837. # the `method` property name gets compiled for the first time here, and
  1838. # again when the `method:` property of the class gets compiled. Since
  1839. # this compilation happens first, comments attached to `method:` would
  1840. # get incorrectly output near `super.method()`, when we want them to
  1841. # get output on the second pass when `method:` is output. So set them
  1842. # aside during this compilation pass, and put them back on the object so
  1843. # that theyre there for the later compilation.
  1844. salvagedComments = @accessor.name.comments
  1845. delete @accessor.name.comments
  1846. fragments = (new Value (new Literal 'super'), if @accessor then [ @accessor ] else [])
  1847. .compileToFragments o
  1848. attachCommentsToNode salvagedComments, @accessor.name if salvagedComments
  1849. fragments
  1850. checkInInstanceMethod: (o) ->
  1851. method = o.scope.namedMethod()
  1852. @error 'cannot use super outside of an instance method' unless method?.isMethod
  1853. astNode: (o) ->
  1854. @checkInInstanceMethod o
  1855. if @accessor?
  1856. return (
  1857. new Value(
  1858. new Super().withLocationDataFrom (@superLiteral ? @)
  1859. [@accessor]
  1860. ).withLocationDataFrom @
  1861. ).ast o
  1862. super o
  1863. #### RegexWithInterpolations
  1864. # Regexes with interpolations are in fact just a variation of a `Call` (a
  1865. # `RegExp()` call to be precise) with a `StringWithInterpolations` inside.
  1866. exports.RegexWithInterpolations = class RegexWithInterpolations extends Base
  1867. constructor: (@call, {@heregexCommentTokens = []} = {}) ->
  1868. super()
  1869. children: ['call']
  1870. compileNode: (o) ->
  1871. @call.compileNode o
  1872. astType: -> 'InterpolatedRegExpLiteral'
  1873. astProperties: (o) ->
  1874. interpolatedPattern: @call.args[0].ast o
  1875. flags: @call.args[1]?.unwrap().originalValue ? ''
  1876. comments:
  1877. for heregexCommentToken in @heregexCommentTokens
  1878. if heregexCommentToken.here
  1879. new HereComment(heregexCommentToken).ast o
  1880. else
  1881. new LineComment(heregexCommentToken).ast o
  1882. #### TaggedTemplateCall
  1883. exports.TaggedTemplateCall = class TaggedTemplateCall extends Call
  1884. constructor: (variable, arg, soak) ->
  1885. arg = StringWithInterpolations.fromStringLiteral arg if arg instanceof StringLiteral
  1886. super variable, [ arg ], soak
  1887. compileNode: (o) ->
  1888. @variable.compileToFragments(o, LEVEL_ACCESS).concat @args[0].compileToFragments(o, LEVEL_LIST)
  1889. astType: -> 'TaggedTemplateExpression'
  1890. astProperties: (o) ->
  1891. return
  1892. tag: @variable.ast o, LEVEL_ACCESS
  1893. quasi: @args[0].ast o, LEVEL_LIST
  1894. #### Extends
  1895. # Node to extend an object's prototype with an ancestor object.
  1896. # After `goog.inherits` from the
  1897. # [Closure Library](https://github.com/google/closure-library/blob/master/closure/goog/base.js).
  1898. exports.Extends = class Extends extends Base
  1899. constructor: (@child, @parent) ->
  1900. super()
  1901. children: ['child', 'parent']
  1902. # Hooks one constructor into another's prototype chain.
  1903. compileToFragments: (o) ->
  1904. new Call(new Value(new Literal utility 'extend', o), [@child, @parent]).compileToFragments o
  1905. #### Access
  1906. # A `.` access into a property of a value, or the `::` shorthand for
  1907. # an access into the object's prototype.
  1908. exports.Access = class Access extends Base
  1909. constructor: (@name, {@soak, @shorthand} = {}) ->
  1910. super()
  1911. children: ['name']
  1912. compileToFragments: (o) ->
  1913. name = @name.compileToFragments o
  1914. node = @name.unwrap()
  1915. if node instanceof PropertyName
  1916. [@makeCode('.'), name...]
  1917. else
  1918. [@makeCode('['), name..., @makeCode(']')]
  1919. shouldCache: NO
  1920. astNode: (o) ->
  1921. # Babel doesnt have an AST node for `Access`, but rather just includes
  1922. # this Access nodes child `name` Identifier node as the `property` of
  1923. # the `MemberExpression` node.
  1924. @name.ast o
  1925. #### Index
  1926. # A `[ ... ]` indexed access into an array or object.
  1927. exports.Index = class Index extends Base
  1928. constructor: (@index) ->
  1929. super()
  1930. children: ['index']
  1931. compileToFragments: (o) ->
  1932. [].concat @makeCode("["), @index.compileToFragments(o, LEVEL_PAREN), @makeCode("]")
  1933. shouldCache: ->
  1934. @index.shouldCache()
  1935. astNode: (o) ->
  1936. # Babel doesnt have an AST node for `Index`, but rather just includes
  1937. # this Index nodes child `index` Identifier node as the `property` of
  1938. # the `MemberExpression` node. The fact that the `MemberExpression`s
  1939. # `property` is an Index means that `computed` is `true` for the
  1940. # `MemberExpression`.
  1941. @index.ast o
  1942. #### Range
  1943. # A range literal. Ranges can be used to extract portions (slices) of arrays,
  1944. # to specify a range for comprehensions, or as a value, to be expanded into the
  1945. # corresponding array of integers at runtime.
  1946. exports.Range = class Range extends Base
  1947. children: ['from', 'to']
  1948. constructor: (@from, @to, tag) ->
  1949. super()
  1950. @exclusive = tag is 'exclusive'
  1951. @equals = if @exclusive then '' else '='
  1952. # Compiles the range's source variables -- where it starts and where it ends.
  1953. # But only if they need to be cached to avoid double evaluation.
  1954. compileVariables: (o) ->
  1955. o = merge o, top: true
  1956. shouldCache = del o, 'shouldCache'
  1957. [@fromC, @fromVar] = @cacheToCodeFragments @from.cache o, LEVEL_LIST, shouldCache
  1958. [@toC, @toVar] = @cacheToCodeFragments @to.cache o, LEVEL_LIST, shouldCache
  1959. [@step, @stepVar] = @cacheToCodeFragments step.cache o, LEVEL_LIST, shouldCache if step = del o, 'step'
  1960. @fromNum = if @from.isNumber() then parseNumber @fromVar else null
  1961. @toNum = if @to.isNumber() then parseNumber @toVar else null
  1962. @stepNum = if step?.isNumber() then parseNumber @stepVar else null
  1963. # When compiled normally, the range returns the contents of the *for loop*
  1964. # needed to iterate over the values in the range. Used by comprehensions.
  1965. compileNode: (o) ->
  1966. @compileVariables o unless @fromVar
  1967. return @compileArray(o) unless o.index
  1968. # Set up endpoints.
  1969. known = @fromNum? and @toNum?
  1970. idx = del o, 'index'
  1971. idxName = del o, 'name'
  1972. namedIndex = idxName and idxName isnt idx
  1973. varPart =
  1974. if known and not namedIndex
  1975. "var #{idx} = #{@fromC}"
  1976. else
  1977. "#{idx} = #{@fromC}"
  1978. varPart += ", #{@toC}" if @toC isnt @toVar
  1979. varPart += ", #{@step}" if @step isnt @stepVar
  1980. [lt, gt] = ["#{idx} <#{@equals}", "#{idx} >#{@equals}"]
  1981. # Generate the condition.
  1982. [from, to] = [@fromNum, @toNum]
  1983. # Always check if the `step` isn't zero to avoid the infinite loop.
  1984. stepNotZero = "#{ @stepNum ? @stepVar } !== 0"
  1985. stepCond = "#{ @stepNum ? @stepVar } > 0"
  1986. lowerBound = "#{lt} #{ if known then to else @toVar }"
  1987. upperBound = "#{gt} #{ if known then to else @toVar }"
  1988. condPart =
  1989. if @step?
  1990. if @stepNum? and @stepNum isnt 0
  1991. if @stepNum > 0 then "#{lowerBound}" else "#{upperBound}"
  1992. else
  1993. "#{stepNotZero} && (#{stepCond} ? #{lowerBound} : #{upperBound})"
  1994. else
  1995. if known
  1996. "#{ if from <= to then lt else gt } #{to}"
  1997. else
  1998. "(#{@fromVar} <= #{@toVar} ? #{lowerBound} : #{upperBound})"
  1999. cond = if @stepVar then "#{@stepVar} > 0" else "#{@fromVar} <= #{@toVar}"
  2000. # Generate the step.
  2001. stepPart = if @stepVar
  2002. "#{idx} += #{@stepVar}"
  2003. else if known
  2004. if namedIndex
  2005. if from <= to then "++#{idx}" else "--#{idx}"
  2006. else
  2007. if from <= to then "#{idx}++" else "#{idx}--"
  2008. else
  2009. if namedIndex
  2010. "#{cond} ? ++#{idx} : --#{idx}"
  2011. else
  2012. "#{cond} ? #{idx}++ : #{idx}--"
  2013. varPart = "#{idxName} = #{varPart}" if namedIndex
  2014. stepPart = "#{idxName} = #{stepPart}" if namedIndex
  2015. # The final loop body.
  2016. [@makeCode "#{varPart}; #{condPart}; #{stepPart}"]
  2017. # When used as a value, expand the range into the equivalent array.
  2018. compileArray: (o) ->
  2019. known = @fromNum? and @toNum?
  2020. if known and Math.abs(@fromNum - @toNum) <= 20
  2021. range = [@fromNum..@toNum]
  2022. range.pop() if @exclusive
  2023. return [@makeCode "[#{ range.join(', ') }]"]
  2024. idt = @tab + TAB
  2025. i = o.scope.freeVariable 'i', single: true, reserve: no
  2026. result = o.scope.freeVariable 'results', reserve: no
  2027. pre = "\n#{idt}var #{result} = [];"
  2028. if known
  2029. o.index = i
  2030. body = fragmentsToText @compileNode o
  2031. else
  2032. vars = "#{i} = #{@fromC}" + if @toC isnt @toVar then ", #{@toC}" else ''
  2033. cond = "#{@fromVar} <= #{@toVar}"
  2034. body = "var #{vars}; #{cond} ? #{i} <#{@equals} #{@toVar} : #{i} >#{@equals} #{@toVar}; #{cond} ? #{i}++ : #{i}--"
  2035. post = "{ #{result}.push(#{i}); }\n#{idt}return #{result};\n#{o.indent}"
  2036. hasArgs = (node) -> node?.contains isLiteralArguments
  2037. args = ', arguments' if hasArgs(@from) or hasArgs(@to)
  2038. [@makeCode "(function() {#{pre}\n#{idt}for (#{body})#{post}}).apply(this#{args ? ''})"]
  2039. astProperties: (o) ->
  2040. return {
  2041. from: @from?.ast(o) ? null
  2042. to: @to?.ast(o) ? null
  2043. @exclusive
  2044. }
  2045. #### Slice
  2046. # An array slice literal. Unlike JavaScripts `Array#slice`, the second parameter
  2047. # specifies the index of the end of the slice, just as the first parameter
  2048. # is the index of the beginning.
  2049. exports.Slice = class Slice extends Base
  2050. children: ['range']
  2051. constructor: (@range) ->
  2052. super()
  2053. # We have to be careful when trying to slice through the end of the array,
  2054. # `9e9` is used because not all implementations respect `undefined` or `1/0`.
  2055. # `9e9` should be safe because `9e9` > `2**32`, the max array length.
  2056. compileNode: (o) ->
  2057. {to, from} = @range
  2058. # Handle an expression in the property access, e.g. `a[!b in c..]`.
  2059. if from?.shouldCache()
  2060. from = new Value new Parens from
  2061. if to?.shouldCache()
  2062. to = new Value new Parens to
  2063. fromCompiled = from?.compileToFragments(o, LEVEL_PAREN) or [@makeCode '0']
  2064. if to
  2065. compiled = to.compileToFragments o, LEVEL_PAREN
  2066. compiledText = fragmentsToText compiled
  2067. if not (not @range.exclusive and +compiledText is -1)
  2068. toStr = ', ' + if @range.exclusive
  2069. compiledText
  2070. else if to.isNumber()
  2071. "#{+compiledText + 1}"
  2072. else
  2073. compiled = to.compileToFragments o, LEVEL_ACCESS
  2074. "+#{fragmentsToText compiled} + 1 || 9e9"
  2075. [@makeCode ".slice(#{ fragmentsToText fromCompiled }#{ toStr or '' })"]
  2076. astNode: (o) ->
  2077. @range.ast o
  2078. #### Obj
  2079. # An object literal, nothing fancy.
  2080. exports.Obj = class Obj extends Base
  2081. constructor: (props, @generated = no) ->
  2082. super()
  2083. @objects = @properties = props or []
  2084. children: ['properties']
  2085. isAssignable: (opts) ->
  2086. for prop in @properties
  2087. # Check for reserved words.
  2088. message = isUnassignable prop.unwrapAll().value
  2089. prop.error message if message
  2090. prop = prop.value if prop instanceof Assign and
  2091. prop.context is 'object' and
  2092. prop.value?.base not instanceof Arr
  2093. return no unless prop.isAssignable opts
  2094. yes
  2095. shouldCache: ->
  2096. not @isAssignable()
  2097. # Check if object contains splat.
  2098. hasSplat: ->
  2099. return yes for prop in @properties when prop instanceof Splat
  2100. no
  2101. # Move rest property to the end of the list.
  2102. # `{a, rest..., b} = obj` -> `{a, b, rest...} = obj`
  2103. # `foo = ({a, rest..., b}) ->` -> `foo = {a, b, rest...}) ->`
  2104. reorderProperties: ->
  2105. props = @properties
  2106. splatProps = @getAndCheckSplatProps()
  2107. splatProp = props.splice splatProps[0], 1
  2108. @objects = @properties = [].concat props, splatProp
  2109. compileNode: (o) ->
  2110. @reorderProperties() if @hasSplat() and @lhs
  2111. props = @properties
  2112. if @generated
  2113. for node in props when node instanceof Value
  2114. node.error 'cannot have an implicit value in an implicit object'
  2115. idt = o.indent += TAB
  2116. lastNode = @lastNode @properties
  2117. # If this object is the left-hand side of an assignment, all its children
  2118. # are too.
  2119. @propagateLhs()
  2120. isCompact = yes
  2121. for prop in @properties
  2122. if prop instanceof Assign and prop.context is 'object'
  2123. isCompact = no
  2124. answer = []
  2125. answer.push @makeCode if isCompact then '' else '\n'
  2126. for prop, i in props
  2127. join = if i is props.length - 1
  2128. ''
  2129. else if isCompact
  2130. ', '
  2131. else if prop is lastNode
  2132. '\n'
  2133. else
  2134. ',\n'
  2135. indent = if isCompact then '' else idt
  2136. key = if prop instanceof Assign and prop.context is 'object'
  2137. prop.variable
  2138. else if prop instanceof Assign
  2139. prop.operatorToken.error "unexpected #{prop.operatorToken.value}" unless @lhs
  2140. prop.variable
  2141. else
  2142. prop
  2143. if key instanceof Value and key.hasProperties()
  2144. key.error 'invalid object key' if prop.context is 'object' or not key.this
  2145. key = key.properties[0].name
  2146. prop = new Assign key, prop, 'object'
  2147. if key is prop
  2148. if prop.shouldCache()
  2149. [key, value] = prop.base.cache o
  2150. key = new PropertyName key.value if key instanceof IdentifierLiteral
  2151. prop = new Assign key, value, 'object'
  2152. else if key instanceof Value and key.base instanceof ComputedPropertyName
  2153. # `{ [foo()] }` output as `{ [ref = foo()]: ref }`.
  2154. if prop.base.value.shouldCache()
  2155. [key, value] = prop.base.value.cache o
  2156. key = new ComputedPropertyName key.value if key instanceof IdentifierLiteral
  2157. prop = new Assign key, value, 'object'
  2158. else
  2159. # `{ [expression] }` output as `{ [expression]: expression }`.
  2160. prop = new Assign key, prop.base.value, 'object'
  2161. else if not prop.bareLiteral?(IdentifierLiteral) and prop not instanceof Splat
  2162. prop = new Assign prop, prop, 'object'
  2163. if indent then answer.push @makeCode indent
  2164. answer.push prop.compileToFragments(o, LEVEL_TOP)...
  2165. if join then answer.push @makeCode join
  2166. answer.push @makeCode if isCompact then '' else "\n#{@tab}"
  2167. answer = @wrapInBraces answer
  2168. if @front then @wrapInParentheses answer else answer
  2169. getAndCheckSplatProps: ->
  2170. return unless @hasSplat() and @lhs
  2171. props = @properties
  2172. splatProps = (i for prop, i in props when prop instanceof Splat)
  2173. props[splatProps[1]].error "multiple spread elements are disallowed" if splatProps?.length > 1
  2174. splatProps
  2175. assigns: (name) ->
  2176. for prop in @properties when prop.assigns name then return yes
  2177. no
  2178. eachName: (iterator) ->
  2179. for prop in @properties
  2180. prop = prop.value if prop instanceof Assign and prop.context is 'object'
  2181. prop = prop.unwrapAll()
  2182. prop.eachName iterator if prop.eachName?
  2183. # Convert bare properties to `ObjectProperty`s (or `Splat`s).
  2184. expandProperty: (property) ->
  2185. {variable, context, operatorToken} = property
  2186. key = if property instanceof Assign and context is 'object'
  2187. variable
  2188. else if property instanceof Assign
  2189. operatorToken.error "unexpected #{operatorToken.value}" unless @lhs
  2190. variable
  2191. else
  2192. property
  2193. if key instanceof Value and key.hasProperties()
  2194. key.error 'invalid object key' unless context isnt 'object' and key.this
  2195. if property instanceof Assign
  2196. return new ObjectProperty fromAssign: property
  2197. else
  2198. return new ObjectProperty key: property
  2199. return new ObjectProperty(fromAssign: property) unless key is property
  2200. return property if property instanceof Splat
  2201. new ObjectProperty key: property
  2202. expandProperties: ->
  2203. @expandProperty(property) for property in @properties
  2204. propagateLhs: (setLhs) ->
  2205. @lhs = yes if setLhs
  2206. return unless @lhs
  2207. for property in @properties
  2208. if property instanceof Assign and property.context is 'object'
  2209. {value} = property
  2210. unwrappedValue = value.unwrapAll()
  2211. if unwrappedValue instanceof Arr or unwrappedValue instanceof Obj
  2212. unwrappedValue.propagateLhs yes
  2213. else if unwrappedValue instanceof Assign
  2214. unwrappedValue.nestedLhs = yes
  2215. else if property instanceof Assign
  2216. # Shorthand property with default, e.g. `{a = 1} = b`.
  2217. property.nestedLhs = yes
  2218. else if property instanceof Splat
  2219. property.propagateLhs yes
  2220. astNode: (o) ->
  2221. @getAndCheckSplatProps()
  2222. super o
  2223. astType: ->
  2224. if @lhs
  2225. 'ObjectPattern'
  2226. else
  2227. 'ObjectExpression'
  2228. astProperties: (o) ->
  2229. return
  2230. implicit: !!@generated
  2231. properties:
  2232. property.ast(o) for property in @expandProperties()
  2233. exports.ObjectProperty = class ObjectProperty extends Base
  2234. constructor: ({key, fromAssign}) ->
  2235. super()
  2236. if fromAssign
  2237. {variable: @key, value, context} = fromAssign
  2238. if context is 'object'
  2239. # All non-shorthand properties (i.e. includes `:`).
  2240. @value = value
  2241. else
  2242. # Left-hand-side shorthand with default e.g. `{a = 1} = b`.
  2243. @value = fromAssign
  2244. @shorthand = yes
  2245. @locationData = fromAssign.locationData
  2246. else
  2247. # Shorthand without default e.g. `{a}` or `{@a}` or `{[a]}`.
  2248. @key = key
  2249. @shorthand = yes
  2250. @locationData = key.locationData
  2251. astProperties: (o) ->
  2252. isComputedPropertyName = (@key instanceof Value and @key.base instanceof ComputedPropertyName) or @key.unwrap() instanceof StringWithInterpolations
  2253. keyAst = @key.ast o, LEVEL_LIST
  2254. return
  2255. key:
  2256. if keyAst?.declaration
  2257. Object.assign {}, keyAst, declaration: no
  2258. else
  2259. keyAst
  2260. value: @value?.ast(o, LEVEL_LIST) ? keyAst
  2261. shorthand: !!@shorthand
  2262. computed: !!isComputedPropertyName
  2263. method: no
  2264. #### Arr
  2265. # An array literal.
  2266. exports.Arr = class Arr extends Base
  2267. constructor: (objs, @lhs = no) ->
  2268. super()
  2269. @objects = objs or []
  2270. @propagateLhs()
  2271. children: ['objects']
  2272. hasElision: ->
  2273. return yes for obj in @objects when obj instanceof Elision
  2274. no
  2275. isAssignable: (opts) ->
  2276. {allowExpansion, allowNontrailingSplat, allowEmptyArray = no} = opts ? {}
  2277. return allowEmptyArray unless @objects.length
  2278. for obj, i in @objects
  2279. return no if not allowNontrailingSplat and obj instanceof Splat and i + 1 isnt @objects.length
  2280. return no unless (allowExpansion and obj instanceof Expansion) or (obj.isAssignable(opts) and (not obj.isAtomic or obj.isAtomic()))
  2281. yes
  2282. shouldCache: ->
  2283. not @isAssignable()
  2284. compileNode: (o) ->
  2285. return [@makeCode '[]'] unless @objects.length
  2286. o.indent += TAB
  2287. fragmentIsElision = ([ fragment ]) ->
  2288. fragment.type is 'Elision' and fragment.code.trim() is ','
  2289. # Detect if `Elision`s at the beginning of the array are processed (e.g. [, , , a]).
  2290. passedElision = no
  2291. answer = []
  2292. for obj, objIndex in @objects
  2293. unwrappedObj = obj.unwrapAll()
  2294. # Let `compileCommentFragments` know to intersperse block comments
  2295. # into the fragments created when compiling this array.
  2296. if unwrappedObj.comments and
  2297. unwrappedObj.comments.filter((comment) -> not comment.here).length is 0
  2298. unwrappedObj.includeCommentFragments = YES
  2299. compiledObjs = (obj.compileToFragments o, LEVEL_LIST for obj in @objects)
  2300. olen = compiledObjs.length
  2301. # If `compiledObjs` includes newlines, we will output this as a multiline
  2302. # array (i.e. with a newline and indentation after the `[`). If an element
  2303. # contains line comments, that should also trigger multiline output since
  2304. # by definition line comments will introduce newlines into our output.
  2305. # The exception is if only the first element has line comments; in that
  2306. # case, output as the compact form if we otherwise would have, so that the
  2307. # first elements line comments get output before or after the array.
  2308. includesLineCommentsOnNonFirstElement = no
  2309. for fragments, index in compiledObjs
  2310. for fragment in fragments
  2311. if fragment.isHereComment
  2312. fragment.code = fragment.code.trim()
  2313. else if index isnt 0 and includesLineCommentsOnNonFirstElement is no and hasLineComments fragment
  2314. includesLineCommentsOnNonFirstElement = yes
  2315. # Add ', ' if all `Elisions` from the beginning of the array are processed (e.g. [, , , a]) and
  2316. # element isn't `Elision` or last element is `Elision` (e.g. [a,,b,,])
  2317. if index isnt 0 and passedElision and (not fragmentIsElision(fragments) or index is olen - 1)
  2318. answer.push @makeCode ', '
  2319. passedElision = passedElision or not fragmentIsElision fragments
  2320. answer.push fragments...
  2321. if includesLineCommentsOnNonFirstElement or '\n' in fragmentsToText(answer)
  2322. for fragment, fragmentIndex in answer
  2323. if fragment.isHereComment
  2324. fragment.code = "#{multident(fragment.code, o.indent, no)}\n#{o.indent}"
  2325. else if fragment.code is ', ' and not fragment?.isElision and fragment.type not in ['StringLiteral', 'StringWithInterpolations']
  2326. fragment.code = ",\n#{o.indent}"
  2327. answer.unshift @makeCode "[\n#{o.indent}"
  2328. answer.push @makeCode "\n#{@tab}]"
  2329. else
  2330. for fragment in answer when fragment.isHereComment
  2331. fragment.code = "#{fragment.code} "
  2332. answer.unshift @makeCode '['
  2333. answer.push @makeCode ']'
  2334. answer
  2335. assigns: (name) ->
  2336. for obj in @objects when obj.assigns name then return yes
  2337. no
  2338. eachName: (iterator) ->
  2339. for obj in @objects
  2340. obj = obj.unwrapAll()
  2341. obj.eachName iterator
  2342. # If this array is the left-hand side of an assignment, all its children
  2343. # are too.
  2344. propagateLhs: (setLhs) ->
  2345. @lhs = yes if setLhs
  2346. return unless @lhs
  2347. for object in @objects
  2348. object.lhs = yes if object instanceof Splat or object instanceof Expansion
  2349. unwrappedObject = object.unwrapAll()
  2350. if unwrappedObject instanceof Arr or unwrappedObject instanceof Obj
  2351. unwrappedObject.propagateLhs yes
  2352. else if unwrappedObject instanceof Assign
  2353. unwrappedObject.nestedLhs = yes
  2354. astType: ->
  2355. if @lhs
  2356. 'ArrayPattern'
  2357. else
  2358. 'ArrayExpression'
  2359. astProperties: (o) ->
  2360. return
  2361. elements:
  2362. object.ast(o, LEVEL_LIST) for object in @objects
  2363. #### Class
  2364. # The CoffeeScript class definition.
  2365. # Initialize a **Class** with its name, an optional superclass, and a body.
  2366. exports.Class = class Class extends Base
  2367. children: ['variable', 'parent', 'body']
  2368. constructor: (@variable, @parent, @body) ->
  2369. super()
  2370. unless @body?
  2371. @body = new Block
  2372. @hasGeneratedBody = yes
  2373. compileNode: (o) ->
  2374. @name = @determineName()
  2375. executableBody = @walkBody o
  2376. # Special handling to allow `class expr.A extends A` declarations
  2377. parentName = @parent.base.value if @parent instanceof Value and not @parent.hasProperties()
  2378. @hasNameClash = @name? and @name is parentName
  2379. node = @
  2380. if executableBody or @hasNameClash
  2381. node = new ExecutableClassBody node, executableBody
  2382. else if not @name? and o.level is LEVEL_TOP
  2383. # Anonymous classes are only valid in expressions
  2384. node = new Parens node
  2385. if @boundMethods.length and @parent
  2386. @variable ?= new IdentifierLiteral o.scope.freeVariable '_class'
  2387. [@variable, @variableRef] = @variable.cache o unless @variableRef?
  2388. if @variable
  2389. node = new Assign @variable, node, null, { @moduleDeclaration }
  2390. @compileNode = @compileClassDeclaration
  2391. try
  2392. return node.compileToFragments o
  2393. finally
  2394. delete @compileNode
  2395. compileClassDeclaration: (o) ->
  2396. @ctor ?= @makeDefaultConstructor() if @externalCtor or @boundMethods.length
  2397. @ctor?.noReturn = true
  2398. @proxyBoundMethods() if @boundMethods.length
  2399. o.indent += TAB
  2400. result = []
  2401. result.push @makeCode "class "
  2402. result.push @makeCode @name if @name
  2403. @compileCommentFragments o, @variable, result if @variable?.comments?
  2404. result.push @makeCode ' ' if @name
  2405. result.push @makeCode('extends '), @parent.compileToFragments(o)..., @makeCode ' ' if @parent
  2406. result.push @makeCode '{'
  2407. unless @body.isEmpty()
  2408. @body.spaced = true
  2409. result.push @makeCode '\n'
  2410. result.push @body.compileToFragments(o, LEVEL_TOP)...
  2411. result.push @makeCode "\n#{@tab}"
  2412. result.push @makeCode '}'
  2413. result
  2414. # Figure out the appropriate name for this class
  2415. determineName: ->
  2416. return null unless @variable
  2417. [..., tail] = @variable.properties
  2418. node = if tail
  2419. tail instanceof Access and tail.name
  2420. else
  2421. @variable.base
  2422. unless node instanceof IdentifierLiteral or node instanceof PropertyName
  2423. return null
  2424. name = node.value
  2425. unless tail
  2426. message = isUnassignable name
  2427. @variable.error message if message
  2428. if name in JS_FORBIDDEN then "_#{name}" else name
  2429. walkBody: (o) ->
  2430. @ctor = null
  2431. @boundMethods = []
  2432. executableBody = null
  2433. initializer = []
  2434. { expressions } = @body
  2435. i = 0
  2436. for expression in expressions.slice()
  2437. if expression instanceof Value and expression.isObject true
  2438. { properties } = expression.base
  2439. exprs = []
  2440. end = 0
  2441. start = 0
  2442. pushSlice = -> exprs.push new Value new Obj properties[start...end], true if end > start
  2443. while assign = properties[end]
  2444. if initializerExpression = @addInitializerExpression assign, o
  2445. pushSlice()
  2446. exprs.push initializerExpression
  2447. initializer.push initializerExpression
  2448. start = end + 1
  2449. end++
  2450. pushSlice()
  2451. expressions[i..i] = exprs
  2452. i += exprs.length
  2453. else
  2454. if initializerExpression = @addInitializerExpression expression, o
  2455. initializer.push initializerExpression
  2456. expressions[i] = initializerExpression
  2457. i += 1
  2458. for method in initializer when method instanceof Code
  2459. if method.ctor
  2460. method.error 'Cannot define more than one constructor in a class' if @ctor
  2461. @ctor = method
  2462. else if method.isStatic and method.bound
  2463. method.context = @name
  2464. else if method.bound
  2465. @boundMethods.push method
  2466. return unless o.compiling
  2467. if initializer.length isnt expressions.length
  2468. @body.expressions = (expression.hoist() for expression in initializer)
  2469. new Block expressions
  2470. # Add an expression to the class initializer
  2471. #
  2472. # This is the key method for determining whether an expression in a class
  2473. # body should appear in the initializer or the executable body. If the given
  2474. # `node` is valid in a class body the method will return a (new, modified,
  2475. # or identical) node for inclusion in the class initializer, otherwise
  2476. # nothing will be returned and the node will appear in the executable body.
  2477. #
  2478. # At time of writing, only methods (instance and static) are valid in ES
  2479. # class initializers. As new ES class features (such as class fields) reach
  2480. # Stage 4, this method will need to be updated to support them. We
  2481. # additionally allow `PassthroughLiteral`s (backticked expressions) in the
  2482. # initializer as an escape hatch for ES features that are not implemented
  2483. # (e.g. getters and setters defined via the `get` and `set` keywords as
  2484. # opposed to the `Object.defineProperty` method).
  2485. addInitializerExpression: (node, o) ->
  2486. if node.unwrapAll() instanceof PassthroughLiteral
  2487. node
  2488. else if @validInitializerMethod node
  2489. @addInitializerMethod node
  2490. else if not o.compiling and @validClassProperty node
  2491. @addClassProperty node
  2492. else if not o.compiling and @validClassPrototypeProperty node
  2493. @addClassPrototypeProperty node
  2494. else
  2495. null
  2496. # Checks if the given node is a valid ES class initializer method.
  2497. validInitializerMethod: (node) ->
  2498. return no unless node instanceof Assign and node.value instanceof Code
  2499. return yes if node.context is 'object' and not node.variable.hasProperties()
  2500. return node.variable.looksStatic(@name) and (@name or not node.value.bound)
  2501. # Returns a configured class initializer method
  2502. addInitializerMethod: (assign) ->
  2503. { variable, value: method, operatorToken } = assign
  2504. method.isMethod = yes
  2505. method.isStatic = variable.looksStatic @name
  2506. if method.isStatic
  2507. method.name = variable.properties[0]
  2508. else
  2509. methodName = variable.base
  2510. method.name = new (if methodName.shouldCache() then Index else Access) methodName
  2511. method.name.updateLocationDataIfMissing methodName.locationData
  2512. isConstructor =
  2513. if methodName instanceof StringLiteral
  2514. methodName.originalValue is 'constructor'
  2515. else
  2516. methodName.value is 'constructor'
  2517. method.ctor = (if @parent then 'derived' else 'base') if isConstructor
  2518. method.error 'Cannot define a constructor as a bound (fat arrow) function' if method.bound and method.ctor
  2519. method.operatorToken = operatorToken
  2520. method
  2521. validClassProperty: (node) ->
  2522. return no unless node instanceof Assign
  2523. return node.variable.looksStatic @name
  2524. addClassProperty: (assign) ->
  2525. {variable, value, operatorToken} = assign
  2526. {staticClassName} = variable.looksStatic @name
  2527. new ClassProperty({
  2528. name: variable.properties[0]
  2529. isStatic: yes
  2530. staticClassName
  2531. value
  2532. operatorToken
  2533. }).withLocationDataFrom assign
  2534. validClassPrototypeProperty: (node) ->
  2535. return no unless node instanceof Assign
  2536. node.context is 'object' and not node.variable.hasProperties()
  2537. addClassPrototypeProperty: (assign) ->
  2538. {variable, value} = assign
  2539. new ClassPrototypeProperty({
  2540. name: variable.base
  2541. value
  2542. }).withLocationDataFrom assign
  2543. makeDefaultConstructor: ->
  2544. ctor = @addInitializerMethod new Assign (new Value new PropertyName 'constructor'), new Code
  2545. @body.unshift ctor
  2546. if @parent
  2547. ctor.body.push new SuperCall new Super, [new Splat new IdentifierLiteral 'arguments']
  2548. if @externalCtor
  2549. applyCtor = new Value @externalCtor, [ new Access new PropertyName 'apply' ]
  2550. applyArgs = [ new ThisLiteral, new IdentifierLiteral 'arguments' ]
  2551. ctor.body.push new Call applyCtor, applyArgs
  2552. ctor.body.makeReturn()
  2553. ctor
  2554. proxyBoundMethods: ->
  2555. @ctor.thisAssignments = for method in @boundMethods
  2556. method.classVariable = @variableRef if @parent
  2557. name = new Value(new ThisLiteral, [ method.name ])
  2558. new Assign name, new Call(new Value(name, [new Access new PropertyName 'bind']), [new ThisLiteral])
  2559. null
  2560. declareName: (o) ->
  2561. return unless (name = @variable?.unwrap()) instanceof IdentifierLiteral
  2562. alreadyDeclared = o.scope.find name.value
  2563. name.isDeclaration = not alreadyDeclared
  2564. isStatementAst: -> yes
  2565. astNode: (o) ->
  2566. if jumpNode = @body.jumps()
  2567. jumpNode.error 'Class bodies cannot contain pure statements'
  2568. if argumentsNode = @body.contains isLiteralArguments
  2569. argumentsNode.error "Class bodies shouldn't reference arguments"
  2570. @declareName o
  2571. @name = @determineName()
  2572. @body.isClassBody = yes
  2573. @body.locationData = zeroWidthLocationDataFromEndLocation @locationData if @hasGeneratedBody
  2574. @walkBody o
  2575. sniffDirectives @body.expressions
  2576. @ctor?.noReturn = yes
  2577. super o
  2578. astType: (o) ->
  2579. if o.level is LEVEL_TOP
  2580. 'ClassDeclaration'
  2581. else
  2582. 'ClassExpression'
  2583. astProperties: (o) ->
  2584. return
  2585. id: @variable?.ast(o) ? null
  2586. superClass: @parent?.ast(o, LEVEL_PAREN) ? null
  2587. body: @body.ast o, LEVEL_TOP
  2588. exports.ExecutableClassBody = class ExecutableClassBody extends Base
  2589. children: [ 'class', 'body' ]
  2590. defaultClassVariableName: '_Class'
  2591. constructor: (@class, @body = new Block) ->
  2592. super()
  2593. compileNode: (o) ->
  2594. if jumpNode = @body.jumps()
  2595. jumpNode.error 'Class bodies cannot contain pure statements'
  2596. if argumentsNode = @body.contains isLiteralArguments
  2597. argumentsNode.error "Class bodies shouldn't reference arguments"
  2598. params = []
  2599. args = [new ThisLiteral]
  2600. wrapper = new Code params, @body
  2601. klass = new Parens new Call (new Value wrapper, [new Access new PropertyName 'call']), args
  2602. @body.spaced = true
  2603. o.classScope = wrapper.makeScope o.scope
  2604. @name = @class.name ? o.classScope.freeVariable @defaultClassVariableName
  2605. ident = new IdentifierLiteral @name
  2606. directives = @walkBody()
  2607. @setContext()
  2608. if @class.hasNameClash
  2609. parent = new IdentifierLiteral o.classScope.freeVariable 'superClass'
  2610. wrapper.params.push new Param parent
  2611. args.push @class.parent
  2612. @class.parent = parent
  2613. if @externalCtor
  2614. externalCtor = new IdentifierLiteral o.classScope.freeVariable 'ctor', reserve: no
  2615. @class.externalCtor = externalCtor
  2616. @externalCtor.variable.base = externalCtor
  2617. if @name isnt @class.name
  2618. @body.expressions.unshift new Assign (new IdentifierLiteral @name), @class
  2619. else
  2620. @body.expressions.unshift @class
  2621. @body.expressions.unshift directives...
  2622. @body.push ident
  2623. klass.compileToFragments o
  2624. # Traverse the class's children and:
  2625. # - Hoist valid ES properties into `@properties`
  2626. # - Hoist static assignments into `@properties`
  2627. # - Convert invalid ES properties into class or prototype assignments
  2628. walkBody: ->
  2629. directives = []
  2630. index = 0
  2631. while expr = @body.expressions[index]
  2632. break unless expr instanceof Value and expr.isString()
  2633. if expr.hoisted
  2634. index++
  2635. else
  2636. directives.push @body.expressions.splice(index, 1)...
  2637. @traverseChildren false, (child) =>
  2638. return false if child instanceof Class or child instanceof HoistTarget
  2639. cont = true
  2640. if child instanceof Block
  2641. for node, i in child.expressions
  2642. if node instanceof Value and node.isObject(true)
  2643. cont = false
  2644. child.expressions[i] = @addProperties node.base.properties
  2645. else if node instanceof Assign and node.variable.looksStatic @name
  2646. node.value.isStatic = yes
  2647. child.expressions = flatten child.expressions
  2648. cont
  2649. directives
  2650. setContext: ->
  2651. @body.traverseChildren false, (node) =>
  2652. if node instanceof ThisLiteral
  2653. node.value = @name
  2654. else if node instanceof Code and node.bound and (node.isStatic or not node.name)
  2655. node.context = @name
  2656. # Make class/prototype assignments for invalid ES properties
  2657. addProperties: (assigns) ->
  2658. result = for assign in assigns
  2659. variable = assign.variable
  2660. base = variable?.base
  2661. value = assign.value
  2662. delete assign.context
  2663. if base.value is 'constructor'
  2664. if value instanceof Code
  2665. base.error 'constructors must be defined at the top level of a class body'
  2666. # The class scope is not available yet, so return the assignment to update later
  2667. assign = @externalCtor = new Assign new Value, value
  2668. else if not assign.variable.this
  2669. name =
  2670. if base instanceof ComputedPropertyName
  2671. new Index base.value
  2672. else
  2673. new (if base.shouldCache() then Index else Access) base
  2674. prototype = new Access new PropertyName 'prototype'
  2675. variable = new Value new ThisLiteral(), [ prototype, name ]
  2676. assign.variable = variable
  2677. else if assign.value instanceof Code
  2678. assign.value.isStatic = true
  2679. assign
  2680. compact result
  2681. exports.ClassProperty = class ClassProperty extends Base
  2682. constructor: ({@name, @isStatic, @staticClassName, @value, @operatorToken}) ->
  2683. super()
  2684. children: ['name', 'value', 'staticClassName']
  2685. isStatement: YES
  2686. astProperties: (o) ->
  2687. return
  2688. key: @name.ast o, LEVEL_LIST
  2689. value: @value.ast o, LEVEL_LIST
  2690. static: !!@isStatic
  2691. computed: @name instanceof Index or @name instanceof ComputedPropertyName
  2692. operator: @operatorToken?.value ? '='
  2693. staticClassName: @staticClassName?.ast(o) ? null
  2694. exports.ClassPrototypeProperty = class ClassPrototypeProperty extends Base
  2695. constructor: ({@name, @value}) ->
  2696. super()
  2697. children: ['name', 'value']
  2698. isStatement: YES
  2699. astProperties: (o) ->
  2700. return
  2701. key: @name.ast o, LEVEL_LIST
  2702. value: @value.ast o, LEVEL_LIST
  2703. computed: @name instanceof ComputedPropertyName or @name instanceof StringWithInterpolations
  2704. #### Import and Export
  2705. exports.ModuleDeclaration = class ModuleDeclaration extends Base
  2706. constructor: (@clause, @source) ->
  2707. super()
  2708. @checkSource()
  2709. children: ['clause', 'source']
  2710. isStatement: YES
  2711. jumps: THIS
  2712. makeReturn: THIS
  2713. checkSource: ->
  2714. if @source? and @source instanceof StringWithInterpolations
  2715. @source.error 'the name of the module to be imported from must be an uninterpolated string'
  2716. checkScope: (o, moduleDeclarationType) ->
  2717. # TODO: would be appropriate to flag this error during AST generation (as
  2718. # well as when compiling to JS). But `o.indent` isnt tracked during AST
  2719. # generation, and there doesnt seem to be a current alternative way to track
  2720. # whether were at the program top-level.
  2721. if o.indent.length isnt 0
  2722. @error "#{moduleDeclarationType} statements must be at top-level scope"
  2723. exports.ImportDeclaration = class ImportDeclaration extends ModuleDeclaration
  2724. compileNode: (o) ->
  2725. @checkScope o, 'import'
  2726. o.importedSymbols = []
  2727. code = []
  2728. code.push @makeCode "#{@tab}import "
  2729. code.push @clause.compileNode(o)... if @clause?
  2730. if @source?.value?
  2731. code.push @makeCode ' from ' unless @clause is null
  2732. code.push @makeCode @source.value
  2733. code.push @makeCode ';'
  2734. code
  2735. astNode: (o) ->
  2736. o.importedSymbols = []
  2737. super o
  2738. astProperties: (o) ->
  2739. ret =
  2740. specifiers: @clause?.ast(o) ? []
  2741. source: @source.ast o
  2742. ret.importKind = 'value' if @clause
  2743. ret
  2744. exports.ImportClause = class ImportClause extends Base
  2745. constructor: (@defaultBinding, @namedImports) ->
  2746. super()
  2747. children: ['defaultBinding', 'namedImports']
  2748. compileNode: (o) ->
  2749. code = []
  2750. if @defaultBinding?
  2751. code.push @defaultBinding.compileNode(o)...
  2752. code.push @makeCode ', ' if @namedImports?
  2753. if @namedImports?
  2754. code.push @namedImports.compileNode(o)...
  2755. code
  2756. astNode: (o) ->
  2757. # The AST for `ImportClause` is the non-nested list of import specifiers
  2758. # that will be the `specifiers` property of an `ImportDeclaration` AST
  2759. compact flatten [
  2760. @defaultBinding?.ast o
  2761. @namedImports?.ast o
  2762. ]
  2763. exports.ExportDeclaration = class ExportDeclaration extends ModuleDeclaration
  2764. compileNode: (o) ->
  2765. @checkScope o, 'export'
  2766. @checkForAnonymousClassExport()
  2767. code = []
  2768. code.push @makeCode "#{@tab}export "
  2769. code.push @makeCode 'default ' if @ instanceof ExportDefaultDeclaration
  2770. if @ not instanceof ExportDefaultDeclaration and
  2771. (@clause instanceof Assign or @clause instanceof Class)
  2772. code.push @makeCode 'var '
  2773. @clause.moduleDeclaration = 'export'
  2774. if @clause.body? and @clause.body instanceof Block
  2775. code = code.concat @clause.compileToFragments o, LEVEL_TOP
  2776. else
  2777. code = code.concat @clause.compileNode o
  2778. code.push @makeCode " from #{@source.value}" if @source?.value?
  2779. code.push @makeCode ';'
  2780. code
  2781. # Prevent exporting an anonymous class; all exported members must be named
  2782. checkForAnonymousClassExport: ->
  2783. if @ not instanceof ExportDefaultDeclaration and @clause instanceof Class and not @clause.variable
  2784. @clause.error 'anonymous classes cannot be exported'
  2785. astNode: (o) ->
  2786. @checkForAnonymousClassExport()
  2787. super o
  2788. exports.ExportNamedDeclaration = class ExportNamedDeclaration extends ExportDeclaration
  2789. astProperties: (o) ->
  2790. ret =
  2791. source: @source?.ast(o) ? null
  2792. exportKind: 'value'
  2793. clauseAst = @clause.ast o
  2794. if @clause instanceof ExportSpecifierList
  2795. ret.specifiers = clauseAst
  2796. ret.declaration = null
  2797. else
  2798. ret.specifiers = []
  2799. ret.declaration = clauseAst
  2800. ret
  2801. exports.ExportDefaultDeclaration = class ExportDefaultDeclaration extends ExportDeclaration
  2802. astProperties: (o) ->
  2803. return
  2804. declaration: @clause.ast o
  2805. exports.ExportAllDeclaration = class ExportAllDeclaration extends ExportDeclaration
  2806. astProperties: (o) ->
  2807. return
  2808. source: @source.ast o
  2809. exportKind: 'value'
  2810. exports.ModuleSpecifierList = class ModuleSpecifierList extends Base
  2811. constructor: (@specifiers) ->
  2812. super()
  2813. children: ['specifiers']
  2814. compileNode: (o) ->
  2815. code = []
  2816. o.indent += TAB
  2817. compiledList = (specifier.compileToFragments o, LEVEL_LIST for specifier in @specifiers)
  2818. if @specifiers.length isnt 0
  2819. code.push @makeCode "{\n#{o.indent}"
  2820. for fragments, index in compiledList
  2821. code.push @makeCode(",\n#{o.indent}") if index
  2822. code.push fragments...
  2823. code.push @makeCode "\n}"
  2824. else
  2825. code.push @makeCode '{}'
  2826. code
  2827. astNode: (o) ->
  2828. specifier.ast(o) for specifier in @specifiers
  2829. exports.ImportSpecifierList = class ImportSpecifierList extends ModuleSpecifierList
  2830. exports.ExportSpecifierList = class ExportSpecifierList extends ModuleSpecifierList
  2831. exports.ModuleSpecifier = class ModuleSpecifier extends Base
  2832. constructor: (@original, @alias, @moduleDeclarationType) ->
  2833. super()
  2834. if @original.comments or @alias?.comments
  2835. @comments = []
  2836. @comments.push @original.comments... if @original.comments
  2837. @comments.push @alias.comments... if @alias?.comments
  2838. # The name of the variable entering the local scope
  2839. @identifier = if @alias? then @alias.value else @original.value
  2840. children: ['original', 'alias']
  2841. compileNode: (o) ->
  2842. @addIdentifierToScope o
  2843. code = []
  2844. code.push @makeCode @original.value
  2845. code.push @makeCode " as #{@alias.value}" if @alias?
  2846. code
  2847. addIdentifierToScope: (o) ->
  2848. o.scope.find @identifier, @moduleDeclarationType
  2849. astNode: (o) ->
  2850. @addIdentifierToScope o
  2851. super o
  2852. exports.ImportSpecifier = class ImportSpecifier extends ModuleSpecifier
  2853. constructor: (imported, local) ->
  2854. super imported, local, 'import'
  2855. addIdentifierToScope: (o) ->
  2856. # Per the spec, symbols cant be imported multiple times
  2857. # (e.g. `import { foo, foo } from 'lib'` is invalid)
  2858. if @identifier in o.importedSymbols or o.scope.check(@identifier)
  2859. @error "'#{@identifier}' has already been declared"
  2860. else
  2861. o.importedSymbols.push @identifier
  2862. super o
  2863. astProperties: (o) ->
  2864. originalAst = @original.ast o
  2865. return
  2866. imported: originalAst
  2867. local: @alias?.ast(o) ? originalAst
  2868. importKind: null
  2869. exports.ImportDefaultSpecifier = class ImportDefaultSpecifier extends ImportSpecifier
  2870. astProperties: (o) ->
  2871. return
  2872. local: @original.ast o
  2873. exports.ImportNamespaceSpecifier = class ImportNamespaceSpecifier extends ImportSpecifier
  2874. astProperties: (o) ->
  2875. return
  2876. local: @alias.ast o
  2877. exports.ExportSpecifier = class ExportSpecifier extends ModuleSpecifier
  2878. constructor: (local, exported) ->
  2879. super local, exported, 'export'
  2880. astProperties: (o) ->
  2881. originalAst = @original.ast o
  2882. return
  2883. local: originalAst
  2884. exported: @alias?.ast(o) ? originalAst
  2885. exports.DynamicImport = class DynamicImport extends Base
  2886. compileNode: ->
  2887. [@makeCode 'import']
  2888. astType: -> 'Import'
  2889. exports.DynamicImportCall = class DynamicImportCall extends Call
  2890. compileNode: (o) ->
  2891. @checkArguments()
  2892. super o
  2893. checkArguments: ->
  2894. unless @args.length is 1
  2895. @error 'import() requires exactly one argument'
  2896. astNode: (o) ->
  2897. @checkArguments()
  2898. super o
  2899. #### Assign
  2900. # The **Assign** is used to assign a local variable to value, or to set the
  2901. # property of an object -- including within object literals.
  2902. exports.Assign = class Assign extends Base
  2903. constructor: (@variable, @value, @context, options = {}) ->
  2904. super()
  2905. {@param, @subpattern, @operatorToken, @moduleDeclaration, @originalContext = @context} = options
  2906. @propagateLhs()
  2907. children: ['variable', 'value']
  2908. isAssignable: YES
  2909. isStatement: (o) ->
  2910. o?.level is LEVEL_TOP and @context? and (@moduleDeclaration or "?" in @context)
  2911. checkNameAssignability: (o, varBase) ->
  2912. if o.scope.type(varBase.value) is 'import'
  2913. varBase.error "'#{varBase.value}' is read-only"
  2914. assigns: (name) ->
  2915. @[if @context is 'object' then 'value' else 'variable'].assigns name
  2916. unfoldSoak: (o) ->
  2917. unfoldSoak o, this, 'variable'
  2918. addScopeVariables: (o, {
  2919. # During AST generation, we need to allow assignment to these constructs
  2920. # that are considered unassignable during compile-to-JS, while still
  2921. # flagging things like `[null] = b`.
  2922. allowAssignmentToExpansion = no,
  2923. allowAssignmentToNontrailingSplat = no,
  2924. allowAssignmentToEmptyArray = no,
  2925. allowAssignmentToComplexSplat = no
  2926. } = {}) ->
  2927. return unless not @context or @context is '**='
  2928. varBase = @variable.unwrapAll()
  2929. if not varBase.isAssignable {
  2930. allowExpansion: allowAssignmentToExpansion
  2931. allowNontrailingSplat: allowAssignmentToNontrailingSplat
  2932. allowEmptyArray: allowAssignmentToEmptyArray
  2933. allowComplexSplat: allowAssignmentToComplexSplat
  2934. }
  2935. @variable.error "'#{@variable.compile o}' can't be assigned"
  2936. varBase.eachName (name) =>
  2937. return if name.hasProperties?()
  2938. message = isUnassignable name.value
  2939. name.error message if message
  2940. # `moduleDeclaration` can be `'import'` or `'export'`.
  2941. @checkNameAssignability o, name
  2942. if @moduleDeclaration
  2943. o.scope.add name.value, @moduleDeclaration
  2944. name.isDeclaration = yes
  2945. else if @param
  2946. o.scope.add name.value,
  2947. if @param is 'alwaysDeclare'
  2948. 'var'
  2949. else
  2950. 'param'
  2951. else
  2952. alreadyDeclared = o.scope.find name.value
  2953. name.isDeclaration ?= not alreadyDeclared
  2954. # If this assignment identifier has one or more herecomments
  2955. # attached, output them as part of the declarations line (unless
  2956. # other herecomments are already staged there) for compatibility
  2957. # with Flow typing. Dont do this if this assignment is for a
  2958. # class, e.g. `ClassName = class ClassName {`, as Flow requires
  2959. # the comment to be between the class name and the `{`.
  2960. if name.comments and not o.scope.comments[name.value] and
  2961. @value not instanceof Class and
  2962. name.comments.every((comment) -> comment.here and not comment.multiline)
  2963. commentsNode = new IdentifierLiteral name.value
  2964. commentsNode.comments = name.comments
  2965. commentFragments = []
  2966. @compileCommentFragments o, commentsNode, commentFragments
  2967. o.scope.comments[name.value] = commentFragments
  2968. # Compile an assignment, delegating to `compileDestructuring` or
  2969. # `compileSplice` if appropriate. Keep track of the name of the base object
  2970. # we've been assigned to, for correct internal references. If the variable
  2971. # has not been seen yet within the current scope, declare it.
  2972. compileNode: (o) ->
  2973. isValue = @variable instanceof Value
  2974. if isValue
  2975. # If `@variable` is an array or an object, were destructuring;
  2976. # if its also `isAssignable()`, the destructuring syntax is supported
  2977. # in ES and we can output it as is; otherwise we `@compileDestructuring`
  2978. # and convert this ES-unsupported destructuring into acceptable output.
  2979. if @variable.isArray() or @variable.isObject()
  2980. unless @variable.isAssignable()
  2981. if @variable.isObject() and @variable.base.hasSplat()
  2982. return @compileObjectDestruct o
  2983. else
  2984. return @compileDestructuring o
  2985. return @compileSplice o if @variable.isSplice()
  2986. return @compileConditional o if @isConditional()
  2987. return @compileSpecialMath o if @context in ['//=', '%%=']
  2988. @addScopeVariables o
  2989. if @value instanceof Code
  2990. if @value.isStatic
  2991. @value.name = @variable.properties[0]
  2992. else if @variable.properties?.length >= 2
  2993. [properties..., prototype, name] = @variable.properties
  2994. @value.name = name if prototype.name?.value is 'prototype'
  2995. val = @value.compileToFragments o, LEVEL_LIST
  2996. compiledName = @variable.compileToFragments o, LEVEL_LIST
  2997. if @context is 'object'
  2998. if @variable.shouldCache()
  2999. compiledName.unshift @makeCode '['
  3000. compiledName.push @makeCode ']'
  3001. return compiledName.concat @makeCode(': '), val
  3002. answer = compiledName.concat @makeCode(" #{ @context or '=' } "), val
  3003. # Per https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#Assignment_without_declaration,
  3004. # if were destructuring without declaring, the destructuring assignment must be wrapped in parentheses.
  3005. # The assignment is wrapped in parentheses if 'o.level' has lower precedence than LEVEL_LIST (3)
  3006. # (i.e. LEVEL_COND (4), LEVEL_OP (5) or LEVEL_ACCESS (6)), or if we're destructuring object, e.g. {a,b} = obj.
  3007. if o.level > LEVEL_LIST or isValue and @variable.base instanceof Obj and not @nestedLhs and not (@param is yes)
  3008. @wrapInParentheses answer
  3009. else
  3010. answer
  3011. # Object rest property is not assignable: `{{a}...}`
  3012. compileObjectDestruct: (o) ->
  3013. @variable.base.reorderProperties()
  3014. {properties: props} = @variable.base
  3015. [..., splat] = props
  3016. splatProp = splat.name
  3017. assigns = []
  3018. refVal = new Value new IdentifierLiteral o.scope.freeVariable 'ref'
  3019. props.splice -1, 1, new Splat refVal
  3020. assigns.push new Assign(new Value(new Obj props), @value).compileToFragments o, LEVEL_LIST
  3021. assigns.push new Assign(new Value(splatProp), refVal).compileToFragments o, LEVEL_LIST
  3022. @joinFragmentArrays assigns, ', '
  3023. # Brief implementation of recursive pattern matching, when assigning array or
  3024. # object literals to a value. Peeks at their properties to assign inner names.
  3025. compileDestructuring: (o) ->
  3026. top = o.level is LEVEL_TOP
  3027. {value} = this
  3028. {objects} = @variable.base
  3029. olen = objects.length
  3030. # Special-case for `{} = a` and `[] = a` (empty patterns).
  3031. # Compile to simply `a`.
  3032. if olen is 0
  3033. code = value.compileToFragments o
  3034. return if o.level >= LEVEL_OP then @wrapInParentheses code else code
  3035. [obj] = objects
  3036. @disallowLoneExpansion()
  3037. {splats, expans, splatsAndExpans} = @getAndCheckSplatsAndExpansions()
  3038. isSplat = splats?.length > 0
  3039. isExpans = expans?.length > 0
  3040. vvar = value.compileToFragments o, LEVEL_LIST
  3041. vvarText = fragmentsToText vvar
  3042. assigns = []
  3043. pushAssign = (variable, val) =>
  3044. assigns.push new Assign(variable, val, null, param: @param, subpattern: yes).compileToFragments o, LEVEL_LIST
  3045. if isSplat
  3046. splatVar = objects[splats[0]].name.unwrap()
  3047. if splatVar instanceof Arr or splatVar instanceof Obj
  3048. splatVarRef = new IdentifierLiteral o.scope.freeVariable 'ref'
  3049. objects[splats[0]].name = splatVarRef
  3050. splatVarAssign = -> pushAssign new Value(splatVar), splatVarRef
  3051. # At this point, there are several things to destructure. So the `fn()` in
  3052. # `{a, b} = fn()` must be cached, for example. Make vvar into a simple
  3053. # variable if it isnt already.
  3054. if value.unwrap() not instanceof IdentifierLiteral or @variable.assigns(vvarText)
  3055. ref = o.scope.freeVariable 'ref'
  3056. assigns.push [@makeCode(ref + ' = '), vvar...]
  3057. vvar = [@makeCode ref]
  3058. vvarText = ref
  3059. slicer = (type) -> (vvar, start, end = no) ->
  3060. vvar = new IdentifierLiteral vvar unless vvar instanceof Value
  3061. args = [vvar, new NumberLiteral(start)]
  3062. args.push new NumberLiteral end if end
  3063. slice = new Value (new IdentifierLiteral utility type, o), [new Access new PropertyName 'call']
  3064. new Value new Call slice, args
  3065. # Helper which outputs `[].slice` code.
  3066. compSlice = slicer "slice"
  3067. # Helper which outputs `[].splice` code.
  3068. compSplice = slicer "splice"
  3069. # Check if `objects` array contains any instance of `Assign`, e.g. {a:1}.
  3070. hasObjAssigns = (objs) ->
  3071. (i for obj, i in objs when obj instanceof Assign and obj.context is 'object')
  3072. # Check if `objects` array contains any unassignable object.
  3073. objIsUnassignable = (objs) ->
  3074. return yes for obj in objs when not obj.isAssignable()
  3075. no
  3076. # `objects` are complex when there is object assign ({a:1}),
  3077. # unassignable object, or just a single node.
  3078. complexObjects = (objs) ->
  3079. hasObjAssigns(objs).length or objIsUnassignable(objs) or olen is 1
  3080. # "Complex" `objects` are processed in a loop.
  3081. # Examples: [a, b, {c, r...}, d], [a, ..., {b, r...}, c, d]
  3082. loopObjects = (objs, vvar, vvarTxt) =>
  3083. for obj, i in objs
  3084. # `Elision` can be skipped.
  3085. continue if obj instanceof Elision
  3086. # If `obj` is {a: 1}
  3087. if obj instanceof Assign and obj.context is 'object'
  3088. {variable: {base: idx}, value: vvar} = obj
  3089. {variable: vvar} = vvar if vvar instanceof Assign
  3090. idx =
  3091. if vvar.this
  3092. vvar.properties[0].name
  3093. else
  3094. new PropertyName vvar.unwrap().value
  3095. acc = idx.unwrap() instanceof PropertyName
  3096. vval = new Value value, [new (if acc then Access else Index) idx]
  3097. else
  3098. # `obj` is [a...], {a...} or a
  3099. vvar = switch
  3100. when obj instanceof Splat then new Value obj.name
  3101. else obj
  3102. vval = switch
  3103. when obj instanceof Splat then compSlice(vvarTxt, i)
  3104. else new Value new Literal(vvarTxt), [new Index new NumberLiteral i]
  3105. message = isUnassignable vvar.unwrap().value
  3106. vvar.error message if message
  3107. pushAssign vvar, vval
  3108. # "Simple" `objects` can be split and compiled to arrays, [a, b, c] = arr, [a, b, c...] = arr
  3109. assignObjects = (objs, vvar, vvarTxt) =>
  3110. vvar = new Value new Arr(objs, yes)
  3111. vval = if vvarTxt instanceof Value then vvarTxt else new Value new Literal(vvarTxt)
  3112. pushAssign vvar, vval
  3113. processObjects = (objs, vvar, vvarTxt) ->
  3114. if complexObjects objs
  3115. loopObjects objs, vvar, vvarTxt
  3116. else
  3117. assignObjects objs, vvar, vvarTxt
  3118. # In case there is `Splat` or `Expansion` in `objects`,
  3119. # we can split array in two simple subarrays.
  3120. # `Splat` [a, b, c..., d, e] can be split into [a, b, c...] and [d, e].
  3121. # `Expansion` [a, b, ..., c, d] can be split into [a, b] and [c, d].
  3122. # Examples:
  3123. # a) `Splat`
  3124. # CS: [a, b, c..., d, e] = arr
  3125. # JS: [a, b, ...c] = arr, [d, e] = splice.call(c, -2)
  3126. # b) `Expansion`
  3127. # CS: [a, b, ..., d, e] = arr
  3128. # JS: [a, b] = arr, [d, e] = slice.call(arr, -2)
  3129. if splatsAndExpans.length
  3130. expIdx = splatsAndExpans[0]
  3131. leftObjs = objects.slice 0, expIdx + (if isSplat then 1 else 0)
  3132. rightObjs = objects.slice expIdx + 1
  3133. processObjects leftObjs, vvar, vvarText if leftObjs.length isnt 0
  3134. if rightObjs.length isnt 0
  3135. # Slice or splice `objects`.
  3136. refExp = switch
  3137. when isSplat then compSplice new Value(objects[expIdx].name), rightObjs.length * -1
  3138. when isExpans then compSlice vvarText, rightObjs.length * -1
  3139. if complexObjects rightObjs
  3140. restVar = refExp
  3141. refExp = o.scope.freeVariable 'ref'
  3142. assigns.push [@makeCode(refExp + ' = '), restVar.compileToFragments(o, LEVEL_LIST)...]
  3143. processObjects rightObjs, vvar, refExp
  3144. else
  3145. # There is no `Splat` or `Expansion` in `objects`.
  3146. processObjects objects, vvar, vvarText
  3147. splatVarAssign?()
  3148. assigns.push vvar unless top or @subpattern
  3149. fragments = @joinFragmentArrays assigns, ', '
  3150. if o.level < LEVEL_LIST then fragments else @wrapInParentheses fragments
  3151. # Disallow `[...] = a` for some reason. (Could be equivalent to `[] = a`?)
  3152. disallowLoneExpansion: ->
  3153. return unless @variable.base instanceof Arr
  3154. {objects} = @variable.base
  3155. return unless objects?.length is 1
  3156. [loneObject] = objects
  3157. if loneObject instanceof Expansion
  3158. loneObject.error 'Destructuring assignment has no target'
  3159. # Show error if there is more than one `Splat`, or `Expansion`.
  3160. # Examples: [a, b, c..., d, e, f...], [a, b, ..., c, d, ...], [a, b, ..., c, d, e...]
  3161. getAndCheckSplatsAndExpansions: ->
  3162. return {splats: [], expans: [], splatsAndExpans: []} unless @variable.base instanceof Arr
  3163. {objects} = @variable.base
  3164. # Count all `Splats`: [a, b, c..., d, e]
  3165. splats = (i for obj, i in objects when obj instanceof Splat)
  3166. # Count all `Expansions`: [a, b, ..., c, d]
  3167. expans = (i for obj, i in objects when obj instanceof Expansion)
  3168. # Combine splats and expansions.
  3169. splatsAndExpans = [splats..., expans...]
  3170. if splatsAndExpans.length > 1
  3171. # Sort 'splatsAndExpans' so we can show error at first disallowed token.
  3172. objects[splatsAndExpans.sort()[1]].error "multiple splats/expansions are disallowed in an assignment"
  3173. {splats, expans, splatsAndExpans}
  3174. # When compiling a conditional assignment, take care to ensure that the
  3175. # operands are only evaluated once, even though we have to reference them
  3176. # more than once.
  3177. compileConditional: (o) ->
  3178. [left, right] = @variable.cacheReference o
  3179. # Disallow conditional assignment of undefined variables.
  3180. if not left.properties.length and left.base instanceof Literal and
  3181. left.base not instanceof ThisLiteral and not o.scope.check left.base.value
  3182. @throwUnassignableConditionalError left.base.value
  3183. if "?" in @context
  3184. o.isExistentialEquals = true
  3185. new If(new Existence(left), right, type: 'if').addElse(new Assign(right, @value, '=')).compileToFragments o
  3186. else
  3187. fragments = new Op(@context[...-1], left, new Assign(right, @value, '=')).compileToFragments o
  3188. if o.level <= LEVEL_LIST then fragments else @wrapInParentheses fragments
  3189. # Convert special math assignment operators like `a //= b` to the equivalent
  3190. # extended form `a = a ** b` and then compiles that.
  3191. compileSpecialMath: (o) ->
  3192. [left, right] = @variable.cacheReference o
  3193. new Assign(left, new Op(@context[...-1], right, @value)).compileToFragments o
  3194. # Compile the assignment from an array splice literal, using JavaScript's
  3195. # `Array#splice` method.
  3196. compileSplice: (o) ->
  3197. {range: {from, to, exclusive}} = @variable.properties.pop()
  3198. unwrappedVar = @variable.unwrapAll()
  3199. if unwrappedVar.comments
  3200. moveComments unwrappedVar, @
  3201. delete @variable.comments
  3202. name = @variable.compile o
  3203. if from
  3204. [fromDecl, fromRef] = @cacheToCodeFragments from.cache o, LEVEL_OP
  3205. else
  3206. fromDecl = fromRef = '0'
  3207. if to
  3208. if from?.isNumber() and to.isNumber()
  3209. to = to.compile(o) - fromRef
  3210. to += 1 unless exclusive
  3211. else
  3212. to = to.compile(o, LEVEL_ACCESS) + ' - ' + fromRef
  3213. to += ' + 1' unless exclusive
  3214. else
  3215. to = "9e9"
  3216. [valDef, valRef] = @value.cache o, LEVEL_LIST
  3217. answer = [].concat @makeCode("#{utility 'splice', o}.apply(#{name}, [#{fromDecl}, #{to}].concat("), valDef, @makeCode(")), "), valRef
  3218. if o.level > LEVEL_TOP then @wrapInParentheses answer else answer
  3219. eachName: (iterator) ->
  3220. @variable.unwrapAll().eachName iterator
  3221. isDefaultAssignment: -> @param or @nestedLhs
  3222. propagateLhs: ->
  3223. return unless @variable?.isArray?() or @variable?.isObject?()
  3224. # This is the left-hand side of an assignment; let `Arr` and `Obj`
  3225. # know that, so that those nodes know that theyre assignable as
  3226. # destructured variables.
  3227. @variable.base.propagateLhs yes
  3228. throwUnassignableConditionalError: (name) ->
  3229. @variable.error "the variable \"#{name}\" can't be assigned with #{@context} because it has not been declared before"
  3230. isConditional: ->
  3231. @context in ['||=', '&&=', '?=']
  3232. isStatementAst: NO
  3233. astNode: (o) ->
  3234. @disallowLoneExpansion()
  3235. @getAndCheckSplatsAndExpansions()
  3236. if @isConditional()
  3237. variable = @variable.unwrap()
  3238. if variable instanceof IdentifierLiteral and not o.scope.check variable.value
  3239. @throwUnassignableConditionalError variable.value
  3240. @addScopeVariables o, allowAssignmentToExpansion: yes, allowAssignmentToNontrailingSplat: yes, allowAssignmentToEmptyArray: yes, allowAssignmentToComplexSplat: yes
  3241. super o
  3242. astType: ->
  3243. if @isDefaultAssignment()
  3244. 'AssignmentPattern'
  3245. else
  3246. 'AssignmentExpression'
  3247. astProperties: (o) ->
  3248. ret =
  3249. right: @value.ast o, LEVEL_LIST
  3250. left: @variable.ast o, LEVEL_LIST
  3251. unless @isDefaultAssignment()
  3252. ret.operator = @originalContext ? '='
  3253. ret
  3254. #### FuncGlyph
  3255. exports.FuncGlyph = class FuncGlyph extends Base
  3256. constructor: (@glyph) ->
  3257. super()
  3258. #### Code
  3259. # A function definition. This is the only node that creates a new Scope.
  3260. # When for the purposes of walking the contents of a function body, the Code
  3261. # has no *children* -- they're within the inner scope.
  3262. exports.Code = class Code extends Base
  3263. constructor: (params, body, @funcGlyph, @paramStart) ->
  3264. super()
  3265. @params = params or []
  3266. @body = body or new Block
  3267. @bound = @funcGlyph?.glyph is '=>'
  3268. @isGenerator = no
  3269. @isAsync = no
  3270. @isMethod = no
  3271. @body.traverseChildren no, (node) =>
  3272. if (node instanceof Op and node.isYield()) or node instanceof YieldReturn
  3273. @isGenerator = yes
  3274. if (node instanceof Op and node.isAwait()) or node instanceof AwaitReturn
  3275. @isAsync = yes
  3276. if node instanceof For and node.isAwait()
  3277. @isAsync = yes
  3278. @propagateLhs()
  3279. children: ['params', 'body']
  3280. isStatement: -> @isMethod
  3281. jumps: NO
  3282. makeScope: (parentScope) -> new Scope parentScope, @body, this
  3283. # Compilation creates a new scope unless explicitly asked to share with the
  3284. # outer scope. Handles splat parameters in the parameter list by setting
  3285. # such parameters to be the final parameter in the function definition, as
  3286. # required per the ES2015 spec. If the CoffeeScript function definition had
  3287. # parameters after the splat, they are declared via expressions in the
  3288. # function body.
  3289. compileNode: (o) ->
  3290. @checkForAsyncOrGeneratorConstructor()
  3291. if @bound
  3292. @context = o.scope.method.context if o.scope.method?.bound
  3293. @context = 'this' unless @context
  3294. @updateOptions o
  3295. params = []
  3296. exprs = []
  3297. thisAssignments = @thisAssignments?.slice() ? []
  3298. paramsAfterSplat = []
  3299. haveSplatParam = no
  3300. haveBodyParam = no
  3301. @checkForDuplicateParams()
  3302. @disallowLoneExpansionAndMultipleSplats()
  3303. # Separate `this` assignments.
  3304. @eachParamName (name, node, param, obj) ->
  3305. if node.this
  3306. name = node.properties[0].name.value
  3307. name = "_#{name}" if name in JS_FORBIDDEN
  3308. target = new IdentifierLiteral o.scope.freeVariable name, reserve: no
  3309. # `Param` is object destructuring with a default value: ({@prop = 1}) ->
  3310. # In a case when the variable name is already reserved, we have to assign
  3311. # a new variable name to the destructured variable: ({prop:prop1 = 1}) ->
  3312. replacement =
  3313. if param.name instanceof Obj and obj instanceof Assign and
  3314. obj.operatorToken.value is '='
  3315. new Assign (new IdentifierLiteral name), target, 'object' #, operatorToken: new Literal ':'
  3316. else
  3317. target
  3318. param.renameParam node, replacement
  3319. thisAssignments.push new Assign node, target
  3320. # Parse the parameters, adding them to the list of parameters to put in the
  3321. # function definition; and dealing with splats or expansions, including
  3322. # adding expressions to the function body to declare all parameter
  3323. # variables that would have been after the splat/expansion parameter.
  3324. # If we encounter a parameter that needs to be declared in the function
  3325. # body for any reason, for example its destructured with `this`, also
  3326. # declare and assign all subsequent parameters in the function body so that
  3327. # any non-idempotent parameters are evaluated in the correct order.
  3328. for param, i in @params
  3329. # Was `...` used with this parameter? Splat/expansion parameters cannot
  3330. # have default values, so we need not worry about that.
  3331. if param.splat or param instanceof Expansion
  3332. haveSplatParam = yes
  3333. if param.splat
  3334. if param.name instanceof Arr or param.name instanceof Obj
  3335. # Splat arrays are treated oddly by ES; deal with them the legacy
  3336. # way in the function body. TODO: Should this be handled in the
  3337. # function parameter list, and if so, how?
  3338. splatParamName = o.scope.freeVariable 'arg'
  3339. params.push ref = new Value new IdentifierLiteral splatParamName
  3340. exprs.push new Assign new Value(param.name), ref
  3341. else
  3342. params.push ref = param.asReference o
  3343. splatParamName = fragmentsToText ref.compileNodeWithoutComments o
  3344. if param.shouldCache()
  3345. exprs.push new Assign new Value(param.name), ref
  3346. else # `param` is an Expansion
  3347. splatParamName = o.scope.freeVariable 'args'
  3348. params.push new Value new IdentifierLiteral splatParamName
  3349. o.scope.parameter splatParamName
  3350. # Parse all other parameters; if a splat paramater has not yet been
  3351. # encountered, add these other parameters to the list to be output in
  3352. # the function definition.
  3353. else
  3354. if param.shouldCache() or haveBodyParam
  3355. param.assignedInBody = yes
  3356. haveBodyParam = yes
  3357. # This parameter cannot be declared or assigned in the parameter
  3358. # list. So put a reference in the parameter list and add a statement
  3359. # to the function body assigning it, e.g.
  3360. # `(arg) => { var a = arg.a; }`, with a default value if it has one.
  3361. if param.value?
  3362. condition = new Op '===', param, new UndefinedLiteral
  3363. ifTrue = new Assign new Value(param.name), param.value
  3364. exprs.push new If condition, ifTrue
  3365. else
  3366. exprs.push new Assign new Value(param.name), param.asReference(o), null, param: 'alwaysDeclare'
  3367. # If this parameter comes before the splat or expansion, it will go
  3368. # in the function definition parameter list.
  3369. unless haveSplatParam
  3370. # If this parameter has a default value, and it hasnt already been
  3371. # set by the `shouldCache()` block above, define it as a statement in
  3372. # the function body. This parameter comes after the splat parameter,
  3373. # so we cant define its default value in the parameter list.
  3374. if param.shouldCache()
  3375. ref = param.asReference o
  3376. else
  3377. if param.value? and not param.assignedInBody
  3378. ref = new Assign new Value(param.name), param.value, null, param: yes
  3379. else
  3380. ref = param
  3381. # Add this parameters reference(s) to the function scope.
  3382. if param.name instanceof Arr or param.name instanceof Obj
  3383. # This parameter is destructured.
  3384. param.name.lhs = yes
  3385. unless param.shouldCache()
  3386. param.name.eachName (prop) ->
  3387. o.scope.parameter prop.value
  3388. else
  3389. # This compilation of the parameter is only to get its name to add
  3390. # to the scope name tracking; since the compilation output here
  3391. # isnt kept for eventual output, dont include comments in this
  3392. # compilation, so that they get output the real time this param
  3393. # is compiled.
  3394. paramToAddToScope = if param.value? then param else ref
  3395. o.scope.parameter fragmentsToText paramToAddToScope.compileToFragmentsWithoutComments o
  3396. params.push ref
  3397. else
  3398. paramsAfterSplat.push param
  3399. # If this parameter had a default value, since its no longer in the
  3400. # function parameter list we need to assign its default value
  3401. # (if necessary) as an expression in the body.
  3402. if param.value? and not param.shouldCache()
  3403. condition = new Op '===', param, new UndefinedLiteral
  3404. ifTrue = new Assign new Value(param.name), param.value
  3405. exprs.push new If condition, ifTrue
  3406. # Add this parameter to the scope, since it wouldnt have been added
  3407. # yet since it was skipped earlier.
  3408. o.scope.add param.name.value, 'var', yes if param.name?.value?
  3409. # If there were parameters after the splat or expansion parameter, those
  3410. # parameters need to be assigned in the body of the function.
  3411. if paramsAfterSplat.length isnt 0
  3412. # Create a destructured assignment, e.g. `[a, b, c] = [args..., b, c]`
  3413. exprs.unshift new Assign new Value(
  3414. new Arr [new Splat(new IdentifierLiteral(splatParamName)), (param.asReference o for param in paramsAfterSplat)...]
  3415. ), new Value new IdentifierLiteral splatParamName
  3416. # Add new expressions to the function body
  3417. wasEmpty = @body.isEmpty()
  3418. @disallowSuperInParamDefaults()
  3419. @checkSuperCallsInConstructorBody()
  3420. @body.expressions.unshift thisAssignments... unless @expandCtorSuper thisAssignments
  3421. @body.expressions.unshift exprs...
  3422. if @isMethod and @bound and not @isStatic and @classVariable
  3423. boundMethodCheck = new Value new Literal utility 'boundMethodCheck', o
  3424. @body.expressions.unshift new Call(boundMethodCheck, [new Value(new ThisLiteral), @classVariable])
  3425. @body.makeReturn() unless wasEmpty or @noReturn
  3426. # JavaScript doesnt allow bound (`=>`) functions to also be generators.
  3427. # This is usually caught via `Op::compileContinuation`, but double-check:
  3428. if @bound and @isGenerator
  3429. yieldNode = @body.contains (node) -> node instanceof Op and node.operator is 'yield'
  3430. (yieldNode or @).error 'yield cannot occur inside bound (fat arrow) functions'
  3431. # Assemble the output
  3432. modifiers = []
  3433. modifiers.push 'static' if @isMethod and @isStatic
  3434. modifiers.push 'async' if @isAsync
  3435. unless @isMethod or @bound
  3436. modifiers.push "function#{if @isGenerator then '*' else ''}"
  3437. else if @isGenerator
  3438. modifiers.push '*'
  3439. signature = [@makeCode '(']
  3440. # Block comments between a function name and `(` get output between
  3441. # `function` and `(`.
  3442. if @paramStart?.comments?
  3443. @compileCommentFragments o, @paramStart, signature
  3444. for param, i in params
  3445. signature.push @makeCode ', ' if i isnt 0
  3446. signature.push @makeCode '...' if haveSplatParam and i is params.length - 1
  3447. # Compile this parameter, but if any generated variables get created
  3448. # (e.g. `ref`), shift those into the parent scope since we cant put a
  3449. # `var` line inside a function parameter list.
  3450. scopeVariablesCount = o.scope.variables.length
  3451. signature.push param.compileToFragments(o, LEVEL_PAREN)...
  3452. if scopeVariablesCount isnt o.scope.variables.length
  3453. generatedVariables = o.scope.variables.splice scopeVariablesCount
  3454. o.scope.parent.variables.push generatedVariables...
  3455. signature.push @makeCode ')'
  3456. # Block comments between `)` and `->`/`=>` get output between `)` and `{`.
  3457. if @funcGlyph?.comments?
  3458. comment.unshift = no for comment in @funcGlyph.comments
  3459. @compileCommentFragments o, @funcGlyph, signature
  3460. body = @body.compileWithDeclarations o unless @body.isEmpty()
  3461. # We need to compile the body before method names to ensure `super`
  3462. # references are handled.
  3463. if @isMethod
  3464. [methodScope, o.scope] = [o.scope, o.scope.parent]
  3465. name = @name.compileToFragments o
  3466. name.shift() if name[0].code is '.'
  3467. o.scope = methodScope
  3468. answer = @joinFragmentArrays (@makeCode m for m in modifiers), ' '
  3469. answer.push @makeCode ' ' if modifiers.length and name
  3470. answer.push name... if name
  3471. answer.push signature...
  3472. answer.push @makeCode ' =>' if @bound and not @isMethod
  3473. answer.push @makeCode ' {'
  3474. answer.push @makeCode('\n'), body..., @makeCode("\n#{@tab}") if body?.length
  3475. answer.push @makeCode '}'
  3476. return indentInitial answer, @ if @isMethod
  3477. if @front or (o.level >= LEVEL_ACCESS) then @wrapInParentheses answer else answer
  3478. updateOptions: (o) ->
  3479. o.scope = del(o, 'classScope') or @makeScope o.scope
  3480. o.scope.shared = del(o, 'sharedScope')
  3481. o.indent += TAB
  3482. delete o.bare
  3483. delete o.isExistentialEquals
  3484. checkForDuplicateParams: ->
  3485. paramNames = []
  3486. @eachParamName (name, node, param) ->
  3487. node.error "multiple parameters named '#{name}'" if name in paramNames
  3488. paramNames.push name
  3489. eachParamName: (iterator) ->
  3490. param.eachName iterator for param in @params
  3491. # Short-circuit `traverseChildren` method to prevent it from crossing scope
  3492. # boundaries unless `crossScope` is `true`.
  3493. traverseChildren: (crossScope, func) ->
  3494. super(crossScope, func) if crossScope
  3495. # Short-circuit `replaceInContext` method to prevent it from crossing context boundaries. Bound
  3496. # functions have the same context.
  3497. replaceInContext: (child, replacement) ->
  3498. if @bound
  3499. super child, replacement
  3500. else
  3501. false
  3502. disallowSuperInParamDefaults: ({forAst} = {}) ->
  3503. return false unless @ctor
  3504. @eachSuperCall Block.wrap(@params), (superCall) ->
  3505. superCall.error "'super' is not allowed in constructor parameter defaults"
  3506. , checkForThisBeforeSuper: not forAst
  3507. checkSuperCallsInConstructorBody: ->
  3508. return false unless @ctor
  3509. seenSuper = @eachSuperCall @body, (superCall) =>
  3510. superCall.error "'super' is only allowed in derived class constructors" if @ctor is 'base'
  3511. seenSuper
  3512. flagThisParamInDerivedClassConstructorWithoutCallingSuper: (param) ->
  3513. param.error "Can't use @params in derived class constructors without calling super"
  3514. checkForAsyncOrGeneratorConstructor: ->
  3515. if @ctor
  3516. @name.error 'Class constructor may not be async' if @isAsync
  3517. @name.error 'Class constructor may not be a generator' if @isGenerator
  3518. disallowLoneExpansionAndMultipleSplats: ->
  3519. seenSplatParam = no
  3520. for param in @params
  3521. # Was `...` used with this parameter? (Only one such parameter is allowed
  3522. # per function.)
  3523. if param.splat or param instanceof Expansion
  3524. if seenSplatParam
  3525. param.error 'only one splat or expansion parameter is allowed per function definition'
  3526. else if param instanceof Expansion and @params.length is 1
  3527. param.error 'an expansion parameter cannot be the only parameter in a function definition'
  3528. seenSplatParam = yes
  3529. expandCtorSuper: (thisAssignments) ->
  3530. return false unless @ctor
  3531. seenSuper = @eachSuperCall @body, (superCall) =>
  3532. superCall.expressions = thisAssignments
  3533. haveThisParam = thisAssignments.length and thisAssignments.length isnt @thisAssignments?.length
  3534. if @ctor is 'derived' and not seenSuper and haveThisParam
  3535. param = thisAssignments[0].variable
  3536. @flagThisParamInDerivedClassConstructorWithoutCallingSuper param
  3537. seenSuper
  3538. # Find all super calls in the given context node;
  3539. # returns `true` if `iterator` is called.
  3540. eachSuperCall: (context, iterator, {checkForThisBeforeSuper = yes} = {}) ->
  3541. seenSuper = no
  3542. context.traverseChildren yes, (child) =>
  3543. if child instanceof SuperCall
  3544. # `super` in a constructor (the only `super` without an accessor)
  3545. # cannot be given an argument with a reference to `this`, as that would
  3546. # be referencing `this` before calling `super`.
  3547. unless child.variable.accessor
  3548. childArgs = child.args.filter (arg) ->
  3549. arg not instanceof Class and (arg not instanceof Code or arg.bound)
  3550. Block.wrap(childArgs).traverseChildren yes, (node) =>
  3551. node.error "Can't call super with @params in derived class constructors" if node.this
  3552. seenSuper = yes
  3553. iterator child
  3554. else if checkForThisBeforeSuper and child instanceof ThisLiteral and @ctor is 'derived' and not seenSuper
  3555. child.error "Can't reference 'this' before calling super in derived class constructors"
  3556. # `super` has the same target in bound (arrow) functions, so check them too
  3557. child not instanceof SuperCall and (child not instanceof Code or child.bound)
  3558. seenSuper
  3559. propagateLhs: ->
  3560. for param in @params
  3561. {name} = param
  3562. if name instanceof Arr or name instanceof Obj
  3563. name.propagateLhs yes
  3564. else if param instanceof Expansion
  3565. param.lhs = yes
  3566. astAddParamsToScope: (o) ->
  3567. @eachParamName (name) ->
  3568. o.scope.add name, 'param'
  3569. astNode: (o) ->
  3570. @updateOptions o
  3571. @checkForAsyncOrGeneratorConstructor()
  3572. @checkForDuplicateParams()
  3573. @disallowSuperInParamDefaults forAst: yes
  3574. @disallowLoneExpansionAndMultipleSplats()
  3575. seenSuper = @checkSuperCallsInConstructorBody()
  3576. if @ctor is 'derived' and not seenSuper
  3577. @eachParamName (name, node) =>
  3578. if node.this
  3579. @flagThisParamInDerivedClassConstructorWithoutCallingSuper node
  3580. @astAddParamsToScope o
  3581. @body.makeReturn null, yes unless @body.isEmpty() or @noReturn
  3582. super o
  3583. astType: ->
  3584. if @isMethod
  3585. 'ClassMethod'
  3586. else if @bound
  3587. 'ArrowFunctionExpression'
  3588. else
  3589. 'FunctionExpression'
  3590. paramForAst: (param) ->
  3591. return param if param instanceof Expansion
  3592. {name, value, splat} = param
  3593. if splat
  3594. new Splat name, lhs: yes, postfix: splat.postfix
  3595. .withLocationDataFrom param
  3596. else if value?
  3597. new Assign name, value, null, param: yes
  3598. .withLocationDataFrom locationData: mergeLocationData name.locationData, value.locationData
  3599. else
  3600. name
  3601. methodAstProperties: (o) ->
  3602. getIsComputed = =>
  3603. return yes if @name instanceof Index
  3604. return yes if @name instanceof ComputedPropertyName
  3605. return yes if @name.name instanceof ComputedPropertyName
  3606. no
  3607. return
  3608. static: !!@isStatic
  3609. key: @name.ast o
  3610. computed: getIsComputed()
  3611. kind:
  3612. if @ctor
  3613. 'constructor'
  3614. else
  3615. 'method'
  3616. operator: @operatorToken?.value ? '='
  3617. staticClassName: @isStatic.staticClassName?.ast(o) ? null
  3618. bound: !!@bound
  3619. astProperties: (o) ->
  3620. return Object.assign
  3621. params: @paramForAst(param).ast(o) for param in @params
  3622. body: @body.ast (Object.assign {}, o, checkForDirectives: yes), LEVEL_TOP
  3623. generator: !!@isGenerator
  3624. async: !!@isAsync
  3625. # We never generate named functions, so specify `id` as `null`, which
  3626. # matches the Babel AST for anonymous function expressions/arrow functions
  3627. id: null
  3628. hasIndentedBody: @body.locationData.first_line > @funcGlyph?.locationData.first_line
  3629. ,
  3630. if @isMethod then @methodAstProperties o else {}
  3631. astLocationData: ->
  3632. functionLocationData = super()
  3633. return functionLocationData unless @isMethod
  3634. astLocationData = mergeAstLocationData @name.astLocationData(), functionLocationData
  3635. if @isStatic.staticClassName?
  3636. astLocationData = mergeAstLocationData @isStatic.staticClassName.astLocationData(), astLocationData
  3637. astLocationData
  3638. #### Param
  3639. # A parameter in a function definition. Beyond a typical JavaScript parameter,
  3640. # these parameters can also attach themselves to the context of the function,
  3641. # as well as be a splat, gathering up a group of parameters into an array.
  3642. exports.Param = class Param extends Base
  3643. constructor: (@name, @value, @splat) ->
  3644. super()
  3645. message = isUnassignable @name.unwrapAll().value
  3646. @name.error message if message
  3647. if @name instanceof Obj and @name.generated
  3648. token = @name.objects[0].operatorToken
  3649. token.error "unexpected #{token.value}"
  3650. children: ['name', 'value']
  3651. compileToFragments: (o) ->
  3652. @name.compileToFragments o, LEVEL_LIST
  3653. compileToFragmentsWithoutComments: (o) ->
  3654. @name.compileToFragmentsWithoutComments o, LEVEL_LIST
  3655. asReference: (o) ->
  3656. return @reference if @reference
  3657. node = @name
  3658. if node.this
  3659. name = node.properties[0].name.value
  3660. name = "_#{name}" if name in JS_FORBIDDEN
  3661. node = new IdentifierLiteral o.scope.freeVariable name
  3662. else if node.shouldCache()
  3663. node = new IdentifierLiteral o.scope.freeVariable 'arg'
  3664. node = new Value node
  3665. node.updateLocationDataIfMissing @locationData
  3666. @reference = node
  3667. shouldCache: ->
  3668. @name.shouldCache()
  3669. # Iterates the name or names of a `Param`.
  3670. # In a sense, a destructured parameter represents multiple JS parameters. This
  3671. # method allows to iterate them all.
  3672. # The `iterator` function will be called as `iterator(name, node)` where
  3673. # `name` is the name of the parameter and `node` is the AST node corresponding
  3674. # to that name.
  3675. eachName: (iterator, name = @name) ->
  3676. checkAssignabilityOfLiteral = (literal) ->
  3677. message = isUnassignable literal.value
  3678. if message
  3679. literal.error message
  3680. unless literal.isAssignable()
  3681. literal.error "'#{literal.value}' can't be assigned"
  3682. atParam = (obj, originalObj = null) => iterator "@#{obj.properties[0].name.value}", obj, @, originalObj
  3683. if name instanceof Call
  3684. name.error "Function invocation can't be assigned"
  3685. # * simple literals `foo`
  3686. if name instanceof Literal
  3687. checkAssignabilityOfLiteral name
  3688. return iterator name.value, name, @
  3689. # * at-params `@foo`
  3690. return atParam name if name instanceof Value
  3691. for obj in name.objects ? []
  3692. # Save original obj.
  3693. nObj = obj
  3694. # * destructured parameter with default value
  3695. if obj instanceof Assign and not obj.context?
  3696. obj = obj.variable
  3697. # * assignments within destructured parameters `{foo:bar}`
  3698. if obj instanceof Assign
  3699. # ... possibly with a default value
  3700. if obj.value instanceof Assign
  3701. obj = obj.value.variable
  3702. else
  3703. obj = obj.value
  3704. @eachName iterator, obj.unwrap()
  3705. # * splats within destructured parameters `[xs...]`
  3706. else if obj instanceof Splat
  3707. node = obj.name.unwrap()
  3708. iterator node.value, node, @
  3709. else if obj instanceof Value
  3710. # * destructured parameters within destructured parameters `[{a}]`
  3711. if obj.isArray() or obj.isObject()
  3712. @eachName iterator, obj.base
  3713. # * at-params within destructured parameters `{@foo}`
  3714. else if obj.this
  3715. atParam obj, nObj
  3716. # * simple destructured parameters {foo}
  3717. else
  3718. checkAssignabilityOfLiteral obj.base
  3719. iterator obj.base.value, obj.base, @
  3720. else if obj instanceof Elision
  3721. obj
  3722. else if obj not instanceof Expansion
  3723. obj.error "illegal parameter #{obj.compile()}"
  3724. return
  3725. # Rename a param by replacing the given AST node for a name with a new node.
  3726. # This needs to ensure that the the source for object destructuring does not change.
  3727. renameParam: (node, newNode) ->
  3728. isNode = (candidate) -> candidate is node
  3729. replacement = (node, parent) =>
  3730. if parent instanceof Obj
  3731. key = node
  3732. key = node.properties[0].name if node.this
  3733. # No need to assign a new variable for the destructured variable if the variable isn't reserved.
  3734. # Examples:
  3735. # `({@foo}) ->` should compile to `({foo}) { this.foo = foo}`
  3736. # `foo = 1; ({@foo}) ->` should compile to `foo = 1; ({foo:foo1}) { this.foo = foo1 }`
  3737. if node.this and key.value is newNode.value
  3738. new Value newNode
  3739. else
  3740. new Assign new Value(key), newNode, 'object'
  3741. else
  3742. newNode
  3743. @replaceInContext isNode, replacement
  3744. #### Splat
  3745. # A splat, either as a parameter to a function, an argument to a call,
  3746. # or as part of a destructuring assignment.
  3747. exports.Splat = class Splat extends Base
  3748. constructor: (name, {@lhs, @postfix = true} = {}) ->
  3749. super()
  3750. @name = if name.compile then name else new Literal name
  3751. children: ['name']
  3752. shouldCache: -> no
  3753. isAssignable: ({allowComplexSplat = no} = {})->
  3754. return allowComplexSplat if @name instanceof Obj or @name instanceof Parens
  3755. @name.isAssignable() and (not @name.isAtomic or @name.isAtomic())
  3756. assigns: (name) ->
  3757. @name.assigns name
  3758. compileNode: (o) ->
  3759. compiledSplat = [@makeCode('...'), @name.compileToFragments(o, LEVEL_OP)...]
  3760. return compiledSplat unless @jsx
  3761. return [@makeCode('{'), compiledSplat..., @makeCode('}')]
  3762. unwrap: -> @name
  3763. propagateLhs: (setLhs) ->
  3764. @lhs = yes if setLhs
  3765. return unless @lhs
  3766. @name.propagateLhs? yes
  3767. astType: ->
  3768. if @jsx
  3769. 'JSXSpreadAttribute'
  3770. else if @lhs
  3771. 'RestElement'
  3772. else
  3773. 'SpreadElement'
  3774. astProperties: (o) -> {
  3775. argument: @name.ast o, LEVEL_OP
  3776. @postfix
  3777. }
  3778. #### Expansion
  3779. # Used to skip values inside an array destructuring (pattern matching) or
  3780. # parameter list.
  3781. exports.Expansion = class Expansion extends Base
  3782. shouldCache: NO
  3783. compileNode: (o) ->
  3784. @throwLhsError()
  3785. asReference: (o) ->
  3786. this
  3787. eachName: (iterator) ->
  3788. throwLhsError: ->
  3789. @error 'Expansion must be used inside a destructuring assignment or parameter list'
  3790. astNode: (o) ->
  3791. unless @lhs
  3792. @throwLhsError()
  3793. super o
  3794. astType: -> 'RestElement'
  3795. astProperties: ->
  3796. return
  3797. argument: null
  3798. #### Elision
  3799. # Array elision element (for example, [,a, , , b, , c, ,]).
  3800. exports.Elision = class Elision extends Base
  3801. isAssignable: YES
  3802. shouldCache: NO
  3803. compileToFragments: (o, level) ->
  3804. fragment = super o, level
  3805. fragment.isElision = yes
  3806. fragment
  3807. compileNode: (o) ->
  3808. [@makeCode ', ']
  3809. asReference: (o) ->
  3810. this
  3811. eachName: (iterator) ->
  3812. astNode: ->
  3813. null
  3814. #### While
  3815. # A while loop, the only sort of low-level loop exposed by CoffeeScript. From
  3816. # it, all other loops can be manufactured. Useful in cases where you need more
  3817. # flexibility or more speed than a comprehension can provide.
  3818. exports.While = class While extends Base
  3819. constructor: (@condition, {invert: @inverted, @guard, @isLoop} = {}) ->
  3820. super()
  3821. children: ['condition', 'guard', 'body']
  3822. isStatement: YES
  3823. makeReturn: (results, mark) ->
  3824. return super(results, mark) if results
  3825. @returns = not @jumps()
  3826. if mark
  3827. @body.makeReturn(results, mark) if @returns
  3828. return
  3829. this
  3830. addBody: (@body) ->
  3831. this
  3832. jumps: ->
  3833. {expressions} = @body
  3834. return no unless expressions.length
  3835. for node in expressions
  3836. return jumpNode if jumpNode = node.jumps loop: yes
  3837. no
  3838. # The main difference from a JavaScript *while* is that the CoffeeScript
  3839. # *while* can be used as a part of a larger expression -- while loops may
  3840. # return an array containing the computed result of each iteration.
  3841. compileNode: (o) ->
  3842. o.indent += TAB
  3843. set = ''
  3844. {body} = this
  3845. if body.isEmpty()
  3846. body = @makeCode ''
  3847. else
  3848. if @returns
  3849. body.makeReturn rvar = o.scope.freeVariable 'results'
  3850. set = "#{@tab}#{rvar} = [];\n"
  3851. if @guard
  3852. if body.expressions.length > 1
  3853. body.expressions.unshift new If (new Parens @guard).invert(), new StatementLiteral "continue"
  3854. else
  3855. body = Block.wrap [new If @guard, body] if @guard
  3856. body = [].concat @makeCode("\n"), (body.compileToFragments o, LEVEL_TOP), @makeCode("\n#{@tab}")
  3857. answer = [].concat @makeCode(set + @tab + "while ("), @processedCondition().compileToFragments(o, LEVEL_PAREN),
  3858. @makeCode(") {"), body, @makeCode("}")
  3859. if @returns
  3860. answer.push @makeCode "\n#{@tab}return #{rvar};"
  3861. answer
  3862. processedCondition: ->
  3863. @processedConditionCache ?= if @inverted then @condition.invert() else @condition
  3864. astType: -> 'WhileStatement'
  3865. astProperties: (o) ->
  3866. return
  3867. test: @condition.ast o, LEVEL_PAREN
  3868. body: @body.ast o, LEVEL_TOP
  3869. guard: @guard?.ast(o) ? null
  3870. inverted: !!@inverted
  3871. postfix: !!@postfix
  3872. loop: !!@isLoop
  3873. #### Op
  3874. # Simple Arithmetic and logical operations. Performs some conversion from
  3875. # CoffeeScript operations into their JavaScript equivalents.
  3876. exports.Op = class Op extends Base
  3877. constructor: (op, first, second, flip, {@invertOperator, @originalOperator = op} = {}) ->
  3878. super()
  3879. if op is 'new'
  3880. if ((firstCall = unwrapped = first.unwrap()) instanceof Call or (firstCall = unwrapped.base) instanceof Call) and not firstCall.do and not firstCall.isNew
  3881. return new Value firstCall.newInstance(), if firstCall is unwrapped then [] else unwrapped.properties
  3882. first = new Parens first unless first instanceof Parens or first.unwrap() instanceof IdentifierLiteral or first.hasProperties?()
  3883. call = new Call first, []
  3884. call.locationData = @locationData
  3885. call.isNew = yes
  3886. return call
  3887. @operator = CONVERSIONS[op] or op
  3888. @first = first
  3889. @second = second
  3890. @flip = !!flip
  3891. if @operator in ['--', '++']
  3892. message = isUnassignable @first.unwrapAll().value
  3893. @first.error message if message
  3894. return this
  3895. # The map of conversions from CoffeeScript to JavaScript symbols.
  3896. CONVERSIONS =
  3897. '==': '==='
  3898. '!=': '!=='
  3899. 'of': 'in'
  3900. 'yieldfrom': 'yield*'
  3901. # The map of invertible operators.
  3902. INVERSIONS =
  3903. '!==': '==='
  3904. '===': '!=='
  3905. children: ['first', 'second']
  3906. isNumber: ->
  3907. @isUnary() and @operator in ['+', '-'] and
  3908. @first instanceof Value and @first.isNumber()
  3909. isAwait: ->
  3910. @operator is 'await'
  3911. isYield: ->
  3912. @operator in ['yield', 'yield*']
  3913. isUnary: ->
  3914. not @second
  3915. shouldCache: ->
  3916. not @isNumber()
  3917. # Am I capable of
  3918. # [Python-style comparison chaining](https://docs.python.org/3/reference/expressions.html#not-in)?
  3919. isChainable: ->
  3920. @operator in ['<', '>', '>=', '<=', '===', '!==']
  3921. isChain: ->
  3922. @isChainable() and @first.isChainable()
  3923. invert: ->
  3924. if @isInOperator()
  3925. @invertOperator = '!'
  3926. return @
  3927. if @isChain()
  3928. allInvertable = yes
  3929. curr = this
  3930. while curr and curr.operator
  3931. allInvertable and= (curr.operator of INVERSIONS)
  3932. curr = curr.first
  3933. return new Parens(this).invert() unless allInvertable
  3934. curr = this
  3935. while curr and curr.operator
  3936. curr.invert = !curr.invert
  3937. curr.operator = INVERSIONS[curr.operator]
  3938. curr = curr.first
  3939. this
  3940. else if op = INVERSIONS[@operator]
  3941. @operator = op
  3942. if @first.unwrap() instanceof Op
  3943. @first.invert()
  3944. this
  3945. else if @second
  3946. new Parens(this).invert()
  3947. else if @operator is '!' and (fst = @first.unwrap()) instanceof Op and
  3948. fst.operator in ['!', 'in', 'instanceof']
  3949. fst
  3950. else
  3951. new Op '!', this
  3952. unfoldSoak: (o) ->
  3953. @operator in ['++', '--', 'delete'] and unfoldSoak o, this, 'first'
  3954. generateDo: (exp) ->
  3955. passedParams = []
  3956. func = if exp instanceof Assign and (ref = exp.value.unwrap()) instanceof Code
  3957. ref
  3958. else
  3959. exp
  3960. for param in func.params or []
  3961. if param.value
  3962. passedParams.push param.value
  3963. delete param.value
  3964. else
  3965. passedParams.push param
  3966. call = new Call exp, passedParams
  3967. call.do = yes
  3968. call
  3969. isInOperator: ->
  3970. @originalOperator is 'in'
  3971. compileNode: (o) ->
  3972. if @isInOperator()
  3973. inNode = new In @first, @second
  3974. return (if @invertOperator then inNode.invert() else inNode).compileNode o
  3975. if @invertOperator
  3976. @invertOperator = null
  3977. return @invert().compileNode(o)
  3978. return Op::generateDo(@first).compileNode o if @operator is 'do'
  3979. isChain = @isChain()
  3980. # In chains, there's no need to wrap bare obj literals in parens,
  3981. # as the chained expression is wrapped.
  3982. @first.front = @front unless isChain
  3983. @checkDeleteOperand o
  3984. return @compileContinuation o if @isYield() or @isAwait()
  3985. return @compileUnary o if @isUnary()
  3986. return @compileChain o if isChain
  3987. switch @operator
  3988. when '?' then @compileExistence o, @second.isDefaultValue
  3989. when '//' then @compileFloorDivision o
  3990. when '%%' then @compileModulo o
  3991. else
  3992. lhs = @first.compileToFragments o, LEVEL_OP
  3993. rhs = @second.compileToFragments o, LEVEL_OP
  3994. answer = [].concat lhs, @makeCode(" #{@operator} "), rhs
  3995. if o.level <= LEVEL_OP then answer else @wrapInParentheses answer
  3996. # Mimic Python's chained comparisons when multiple comparison operators are
  3997. # used sequentially. For example:
  3998. #
  3999. # bin/coffee -e 'console.log 50 < 65 > 10'
  4000. # true
  4001. compileChain: (o) ->
  4002. [@first.second, shared] = @first.second.cache o
  4003. fst = @first.compileToFragments o, LEVEL_OP
  4004. fragments = fst.concat @makeCode(" #{if @invert then '&&' else '||'} "),
  4005. (shared.compileToFragments o), @makeCode(" #{@operator} "), (@second.compileToFragments o, LEVEL_OP)
  4006. @wrapInParentheses fragments
  4007. # Keep reference to the left expression, unless this an existential assignment
  4008. compileExistence: (o, checkOnlyUndefined) ->
  4009. if @first.shouldCache()
  4010. ref = new IdentifierLiteral o.scope.freeVariable 'ref'
  4011. fst = new Parens new Assign ref, @first
  4012. else
  4013. fst = @first
  4014. ref = fst
  4015. new If(new Existence(fst, checkOnlyUndefined), ref, type: 'if').addElse(@second).compileToFragments o
  4016. # Compile a unary **Op**.
  4017. compileUnary: (o) ->
  4018. parts = []
  4019. op = @operator
  4020. parts.push [@makeCode op]
  4021. if op is '!' and @first instanceof Existence
  4022. @first.negated = not @first.negated
  4023. return @first.compileToFragments o
  4024. if o.level >= LEVEL_ACCESS
  4025. return (new Parens this).compileToFragments o
  4026. plusMinus = op in ['+', '-']
  4027. parts.push [@makeCode(' ')] if op in ['typeof', 'delete'] or
  4028. plusMinus and @first instanceof Op and @first.operator is op
  4029. if plusMinus and @first instanceof Op
  4030. @first = new Parens @first
  4031. parts.push @first.compileToFragments o, LEVEL_OP
  4032. parts.reverse() if @flip
  4033. @joinFragmentArrays parts, ''
  4034. compileContinuation: (o) ->
  4035. parts = []
  4036. op = @operator
  4037. @checkContinuation o
  4038. if 'expression' in Object.keys(@first) and not (@first instanceof Throw)
  4039. parts.push @first.expression.compileToFragments o, LEVEL_OP if @first.expression?
  4040. else
  4041. parts.push [@makeCode "("] if o.level >= LEVEL_PAREN
  4042. parts.push [@makeCode op]
  4043. parts.push [@makeCode " "] if @first.base?.value isnt ''
  4044. parts.push @first.compileToFragments o, LEVEL_OP
  4045. parts.push [@makeCode ")"] if o.level >= LEVEL_PAREN
  4046. @joinFragmentArrays parts, ''
  4047. checkContinuation: (o) ->
  4048. unless o.scope.parent?
  4049. @error "#{@operator} can only occur inside functions"
  4050. if o.scope.method?.bound and o.scope.method.isGenerator
  4051. @error 'yield cannot occur inside bound (fat arrow) functions'
  4052. compileFloorDivision: (o) ->
  4053. floor = new Value new IdentifierLiteral('Math'), [new Access new PropertyName 'floor']
  4054. second = if @second.shouldCache() then new Parens @second else @second
  4055. div = new Op '/', @first, second
  4056. new Call(floor, [div]).compileToFragments o
  4057. compileModulo: (o) ->
  4058. mod = new Value new Literal utility 'modulo', o
  4059. new Call(mod, [@first, @second]).compileToFragments o
  4060. toString: (idt) ->
  4061. super idt, @constructor.name + ' ' + @operator
  4062. checkDeleteOperand: (o) ->
  4063. if @operator is 'delete' and o.scope.check(@first.unwrapAll().value)
  4064. @error 'delete operand may not be argument or var'
  4065. astNode: (o) ->
  4066. @checkContinuation o if @isYield() or @isAwait()
  4067. @checkDeleteOperand o
  4068. super o
  4069. astType: ->
  4070. return 'AwaitExpression' if @isAwait()
  4071. return 'YieldExpression' if @isYield()
  4072. return 'ChainedComparison' if @isChain()
  4073. switch @operator
  4074. when '||', '&&', '?' then 'LogicalExpression'
  4075. when '++', '--' then 'UpdateExpression'
  4076. else
  4077. if @isUnary() then 'UnaryExpression'
  4078. else 'BinaryExpression'
  4079. operatorAst: ->
  4080. "#{if @invertOperator then "#{@invertOperator} " else ''}#{@originalOperator}"
  4081. chainAstProperties: (o) ->
  4082. operators = [@operatorAst()]
  4083. operands = [@second]
  4084. currentOp = @first
  4085. loop
  4086. operators.unshift currentOp.operatorAst()
  4087. operands.unshift currentOp.second
  4088. currentOp = currentOp.first
  4089. unless currentOp.isChainable()
  4090. operands.unshift currentOp
  4091. break
  4092. return {
  4093. operators
  4094. operands: (operand.ast(o, LEVEL_OP) for operand in operands)
  4095. }
  4096. astProperties: (o) ->
  4097. return @chainAstProperties(o) if @isChain()
  4098. firstAst = @first.ast o, LEVEL_OP
  4099. secondAst = @second?.ast o, LEVEL_OP
  4100. operatorAst = @operatorAst()
  4101. switch
  4102. when @isUnary()
  4103. argument =
  4104. if @isYield() and @first.unwrap().value is ''
  4105. null
  4106. else
  4107. firstAst
  4108. return {argument} if @isAwait()
  4109. return {
  4110. argument
  4111. delegate: @operator is 'yield*'
  4112. } if @isYield()
  4113. return {
  4114. argument
  4115. operator: operatorAst
  4116. prefix: !@flip
  4117. }
  4118. else
  4119. return
  4120. left: firstAst
  4121. right: secondAst
  4122. operator: operatorAst
  4123. #### In
  4124. exports.In = class In extends Base
  4125. constructor: (@object, @array) ->
  4126. super()
  4127. children: ['object', 'array']
  4128. invert: NEGATE
  4129. compileNode: (o) ->
  4130. if @array instanceof Value and @array.isArray() and @array.base.objects.length
  4131. for obj in @array.base.objects when obj instanceof Splat
  4132. hasSplat = yes
  4133. break
  4134. # `compileOrTest` only if we have an array literal with no splats
  4135. return @compileOrTest o unless hasSplat
  4136. @compileLoopTest o
  4137. compileOrTest: (o) ->
  4138. [sub, ref] = @object.cache o, LEVEL_OP
  4139. [cmp, cnj] = if @negated then [' !== ', ' && '] else [' === ', ' || ']
  4140. tests = []
  4141. for item, i in @array.base.objects
  4142. if i then tests.push @makeCode cnj
  4143. tests = tests.concat (if i then ref else sub), @makeCode(cmp), item.compileToFragments(o, LEVEL_ACCESS)
  4144. if o.level < LEVEL_OP then tests else @wrapInParentheses tests
  4145. compileLoopTest: (o) ->
  4146. [sub, ref] = @object.cache o, LEVEL_LIST
  4147. fragments = [].concat @makeCode(utility('indexOf', o) + ".call("), @array.compileToFragments(o, LEVEL_LIST),
  4148. @makeCode(", "), ref, @makeCode(") " + if @negated then '< 0' else '>= 0')
  4149. return fragments if fragmentsToText(sub) is fragmentsToText(ref)
  4150. fragments = sub.concat @makeCode(', '), fragments
  4151. if o.level < LEVEL_LIST then fragments else @wrapInParentheses fragments
  4152. toString: (idt) ->
  4153. super idt, @constructor.name + if @negated then '!' else ''
  4154. #### Try
  4155. # A classic *try/catch/finally* block.
  4156. exports.Try = class Try extends Base
  4157. constructor: (@attempt, @catch, @ensure, @finallyTag) ->
  4158. super()
  4159. children: ['attempt', 'catch', 'ensure']
  4160. isStatement: YES
  4161. jumps: (o) -> @attempt.jumps(o) or @catch?.jumps(o)
  4162. makeReturn: (results, mark) ->
  4163. if mark
  4164. @attempt?.makeReturn results, mark
  4165. @catch?.makeReturn results, mark
  4166. return
  4167. @attempt = @attempt.makeReturn results if @attempt
  4168. @catch = @catch .makeReturn results if @catch
  4169. this
  4170. # Compilation is more or less as you would expect -- the *finally* clause
  4171. # is optional, the *catch* is not.
  4172. compileNode: (o) ->
  4173. originalIndent = o.indent
  4174. o.indent += TAB
  4175. tryPart = @attempt.compileToFragments o, LEVEL_TOP
  4176. catchPart = if @catch
  4177. @catch.compileToFragments merge(o, indent: originalIndent), LEVEL_TOP
  4178. else unless @ensure or @catch
  4179. generatedErrorVariableName = o.scope.freeVariable 'error', reserve: no
  4180. [@makeCode(" catch (#{generatedErrorVariableName}) {}")]
  4181. else
  4182. []
  4183. ensurePart = if @ensure then ([].concat @makeCode(" finally {\n"), @ensure.compileToFragments(o, LEVEL_TOP),
  4184. @makeCode("\n#{@tab}}")) else []
  4185. [].concat @makeCode("#{@tab}try {\n"),
  4186. tryPart,
  4187. @makeCode("\n#{@tab}}"), catchPart, ensurePart
  4188. astType: -> 'TryStatement'
  4189. astProperties: (o) ->
  4190. return
  4191. block: @attempt.ast o, LEVEL_TOP
  4192. handler: @catch?.ast(o) ? null
  4193. finalizer:
  4194. if @ensure?
  4195. Object.assign @ensure.ast(o, LEVEL_TOP),
  4196. # Include `finally` keyword in location data.
  4197. mergeAstLocationData(
  4198. jisonLocationDataToAstLocationData(@finallyTag.locationData),
  4199. @ensure.astLocationData()
  4200. )
  4201. else
  4202. null
  4203. exports.Catch = class Catch extends Base
  4204. constructor: (@recovery, @errorVariable) ->
  4205. super()
  4206. @errorVariable?.unwrap().propagateLhs? yes
  4207. children: ['recovery', 'errorVariable']
  4208. isStatement: YES
  4209. jumps: (o) -> @recovery.jumps(o)
  4210. makeReturn: (results, mark) ->
  4211. ret = @recovery.makeReturn results, mark
  4212. return if mark
  4213. @recovery = ret
  4214. this
  4215. compileNode: (o) ->
  4216. o.indent += TAB
  4217. generatedErrorVariableName = o.scope.freeVariable 'error', reserve: no
  4218. placeholder = new IdentifierLiteral generatedErrorVariableName
  4219. @checkUnassignable()
  4220. if @errorVariable
  4221. @recovery.unshift new Assign @errorVariable, placeholder
  4222. [].concat @makeCode(" catch ("), placeholder.compileToFragments(o), @makeCode(") {\n"),
  4223. @recovery.compileToFragments(o, LEVEL_TOP), @makeCode("\n#{@tab}}")
  4224. checkUnassignable: ->
  4225. if @errorVariable
  4226. message = isUnassignable @errorVariable.unwrapAll().value
  4227. @errorVariable.error message if message
  4228. astNode: (o) ->
  4229. @checkUnassignable()
  4230. @errorVariable?.eachName (name) ->
  4231. alreadyDeclared = o.scope.find name.value
  4232. name.isDeclaration = not alreadyDeclared
  4233. super o
  4234. astType: -> 'CatchClause'
  4235. astProperties: (o) ->
  4236. return
  4237. param: @errorVariable?.ast(o) ? null
  4238. body: @recovery.ast o, LEVEL_TOP
  4239. #### Throw
  4240. # Simple node to throw an exception.
  4241. exports.Throw = class Throw extends Base
  4242. constructor: (@expression) ->
  4243. super()
  4244. children: ['expression']
  4245. isStatement: YES
  4246. jumps: NO
  4247. # A **Throw** is already a return, of sorts...
  4248. makeReturn: THIS
  4249. compileNode: (o) ->
  4250. fragments = @expression.compileToFragments o, LEVEL_LIST
  4251. unshiftAfterComments fragments, @makeCode 'throw '
  4252. fragments.unshift @makeCode @tab
  4253. fragments.push @makeCode ';'
  4254. fragments
  4255. astType: -> 'ThrowStatement'
  4256. astProperties: (o) ->
  4257. return
  4258. argument: @expression.ast o, LEVEL_LIST
  4259. #### Existence
  4260. # Checks a variable for existence -- not `null` and not `undefined`. This is
  4261. # similar to `.nil?` in Ruby, and avoids having to consult a JavaScript truth
  4262. # table. Optionally only check if a variable is not `undefined`.
  4263. exports.Existence = class Existence extends Base
  4264. constructor: (@expression, onlyNotUndefined = no) ->
  4265. super()
  4266. @comparisonTarget = if onlyNotUndefined then 'undefined' else 'null'
  4267. salvagedComments = []
  4268. @expression.traverseChildren yes, (child) ->
  4269. if child.comments
  4270. for comment in child.comments
  4271. salvagedComments.push comment unless comment in salvagedComments
  4272. delete child.comments
  4273. attachCommentsToNode salvagedComments, @
  4274. moveComments @expression, @
  4275. children: ['expression']
  4276. invert: NEGATE
  4277. compileNode: (o) ->
  4278. @expression.front = @front
  4279. code = @expression.compile o, LEVEL_OP
  4280. if @expression.unwrap() instanceof IdentifierLiteral and not o.scope.check code
  4281. [cmp, cnj] = if @negated then ['===', '||'] else ['!==', '&&']
  4282. code = "typeof #{code} #{cmp} \"undefined\"" + if @comparisonTarget isnt 'undefined' then " #{cnj} #{code} #{cmp} #{@comparisonTarget}" else ''
  4283. else
  4284. # We explicity want to use loose equality (`==`) when comparing against `null`,
  4285. # so that an existence check roughly corresponds to a check for truthiness.
  4286. # Do *not* change this to `===` for `null`, as this will break mountains of
  4287. # existing code. When comparing only against `undefined`, however, we want to
  4288. # use `===` because this use case is for parity with ES2015+ default values,
  4289. # which only get assigned when the variable is `undefined` (but not `null`).
  4290. cmp = if @comparisonTarget is 'null'
  4291. if @negated then '==' else '!='
  4292. else # `undefined`
  4293. if @negated then '===' else '!=='
  4294. code = "#{code} #{cmp} #{@comparisonTarget}"
  4295. [@makeCode(if o.level <= LEVEL_COND then code else "(#{code})")]
  4296. astType: -> 'UnaryExpression'
  4297. astProperties: (o) ->
  4298. return
  4299. argument: @expression.ast o
  4300. operator: '?'
  4301. prefix: no
  4302. #### Parens
  4303. # An extra set of parentheses, specified explicitly in the source. At one time
  4304. # we tried to clean up the results by detecting and removing redundant
  4305. # parentheses, but no longer -- you can put in as many as you please.
  4306. #
  4307. # Parentheses are a good way to force any statement to become an expression.
  4308. exports.Parens = class Parens extends Base
  4309. constructor: (@body) ->
  4310. super()
  4311. children: ['body']
  4312. unwrap: -> @body
  4313. shouldCache: -> @body.shouldCache()
  4314. compileNode: (o) ->
  4315. expr = @body.unwrap()
  4316. # If these parentheses are wrapping an `IdentifierLiteral` followed by a
  4317. # block comment, output the parentheses (or put another way, dont optimize
  4318. # away these redundant parentheses). This is because Flow requires
  4319. # parentheses in certain circumstances to distinguish identifiers followed
  4320. # by comment-based type annotations from JavaScript labels.
  4321. shouldWrapComment = expr.comments?.some(
  4322. (comment) -> comment.here and not comment.unshift and not comment.newLine)
  4323. if expr instanceof Value and expr.isAtomic() and not @jsxAttribute and not shouldWrapComment
  4324. expr.front = @front
  4325. return expr.compileToFragments o
  4326. fragments = expr.compileToFragments o, LEVEL_PAREN
  4327. bare = o.level < LEVEL_OP and not shouldWrapComment and (
  4328. expr instanceof Op and not expr.isInOperator() or expr.unwrap() instanceof Call or
  4329. (expr instanceof For and expr.returns)
  4330. ) and (o.level < LEVEL_COND or fragments.length <= 3)
  4331. return @wrapInBraces fragments if @jsxAttribute
  4332. if bare then fragments else @wrapInParentheses fragments
  4333. astNode: (o) -> @body.unwrap().ast o, LEVEL_PAREN
  4334. #### StringWithInterpolations
  4335. exports.StringWithInterpolations = class StringWithInterpolations extends Base
  4336. constructor: (@body, {@quote, @startQuote} = {}) ->
  4337. super()
  4338. @fromStringLiteral: (stringLiteral) ->
  4339. updatedString = stringLiteral.withoutQuotesInLocationData()
  4340. updatedStringValue = new Value(updatedString).withLocationDataFrom updatedString
  4341. new StringWithInterpolations Block.wrap([updatedStringValue]), quote: stringLiteral.quote
  4342. .withLocationDataFrom stringLiteral
  4343. children: ['body']
  4344. # `unwrap` returns `this` to stop ancestor nodes reaching in to grab @body,
  4345. # and using @body.compileNode. `StringWithInterpolations.compileNode` is
  4346. # _the_ custom logic to output interpolated strings as code.
  4347. unwrap: -> this
  4348. shouldCache: -> @body.shouldCache()
  4349. extractElements: (o, {includeInterpolationWrappers, isJsx} = {}) ->
  4350. # Assumes that `expr` is `Block`
  4351. expr = @body.unwrap()
  4352. elements = []
  4353. salvagedComments = []
  4354. expr.traverseChildren no, (node) =>
  4355. if node instanceof StringLiteral
  4356. if node.comments
  4357. salvagedComments.push node.comments...
  4358. delete node.comments
  4359. elements.push node
  4360. return yes
  4361. else if node instanceof Interpolation
  4362. if salvagedComments.length isnt 0
  4363. for comment in salvagedComments
  4364. comment.unshift = yes
  4365. comment.newLine = yes
  4366. attachCommentsToNode salvagedComments, node
  4367. if (unwrapped = node.expression?.unwrapAll()) instanceof PassthroughLiteral and unwrapped.generated and not (isJsx and o.compiling)
  4368. if o.compiling
  4369. commentPlaceholder = new StringLiteral('').withLocationDataFrom node
  4370. commentPlaceholder.comments = unwrapped.comments
  4371. (commentPlaceholder.comments ?= []).push node.comments... if node.comments
  4372. elements.push new Value commentPlaceholder
  4373. else
  4374. empty = new Interpolation().withLocationDataFrom node
  4375. empty.comments = node.comments
  4376. elements.push empty
  4377. else if node.expression or includeInterpolationWrappers
  4378. (node.expression?.comments ?= []).push node.comments... if node.comments
  4379. elements.push if includeInterpolationWrappers then node else node.expression
  4380. return no
  4381. else if node.comments
  4382. # This node is getting discarded, but salvage its comments.
  4383. if elements.length isnt 0 and elements[elements.length - 1] not instanceof StringLiteral
  4384. for comment in node.comments
  4385. comment.unshift = no
  4386. comment.newLine = yes
  4387. attachCommentsToNode node.comments, elements[elements.length - 1]
  4388. else
  4389. salvagedComments.push node.comments...
  4390. delete node.comments
  4391. return yes
  4392. elements
  4393. compileNode: (o) ->
  4394. @comments ?= @startQuote?.comments
  4395. if @jsxAttribute
  4396. wrapped = new Parens new StringWithInterpolations @body
  4397. wrapped.jsxAttribute = yes
  4398. return wrapped.compileNode o
  4399. elements = @extractElements o, isJsx: @jsx
  4400. fragments = []
  4401. fragments.push @makeCode '`' unless @jsx
  4402. for element in elements
  4403. if element instanceof StringLiteral
  4404. unquotedElementValue = if @jsx then element.unquotedValueForJSX else element.unquotedValueForTemplateLiteral
  4405. fragments.push @makeCode unquotedElementValue
  4406. else
  4407. fragments.push @makeCode '$' unless @jsx
  4408. code = element.compileToFragments(o, LEVEL_PAREN)
  4409. if not @isNestedTag(element) or
  4410. code.some((fragment) -> fragment.comments?.some((comment) -> comment.here is no))
  4411. code = @wrapInBraces code
  4412. # Flag the `{` and `}` fragments as having been generated by this
  4413. # `StringWithInterpolations` node, so that `compileComments` knows
  4414. # to treat them as bounds. But the braces are unnecessary if all of
  4415. # the enclosed comments are `/* */` comments. Dont trust
  4416. # `fragment.type`, which can report minified variable names when
  4417. # this compiler is minified.
  4418. code[0].isStringWithInterpolations = yes
  4419. code[code.length - 1].isStringWithInterpolations = yes
  4420. fragments.push code...
  4421. fragments.push @makeCode '`' unless @jsx
  4422. fragments
  4423. isNestedTag: (element) ->
  4424. call = element.unwrapAll?()
  4425. @jsx and call instanceof JSXElement
  4426. astType: -> 'TemplateLiteral'
  4427. astProperties: (o) ->
  4428. elements = @extractElements o, includeInterpolationWrappers: yes
  4429. [..., last] = elements
  4430. quasis = []
  4431. expressions = []
  4432. for element, index in elements
  4433. if element instanceof StringLiteral
  4434. quasis.push new TemplateElement(
  4435. element.originalValue
  4436. tail: element is last
  4437. ).withLocationDataFrom(element).ast o
  4438. else # Interpolation
  4439. {expression} = element
  4440. node =
  4441. unless expression?
  4442. emptyInterpolation = new EmptyInterpolation()
  4443. emptyInterpolation.locationData = emptyExpressionLocationData {
  4444. interpolationNode: element
  4445. openingBrace: '#{'
  4446. closingBrace: '}'
  4447. }
  4448. emptyInterpolation
  4449. else
  4450. expression.unwrapAll()
  4451. expressions.push astAsBlockIfNeeded node, o
  4452. {expressions, quasis, @quote}
  4453. exports.TemplateElement = class TemplateElement extends Base
  4454. constructor: (@value, {@tail} = {}) ->
  4455. super()
  4456. astProperties: ->
  4457. return
  4458. value:
  4459. raw: @value
  4460. tail: !!@tail
  4461. exports.Interpolation = class Interpolation extends Base
  4462. constructor: (@expression) ->
  4463. super()
  4464. children: ['expression']
  4465. # Represents the contents of an empty interpolation (e.g. `#{}`).
  4466. # Only used during AST generation.
  4467. exports.EmptyInterpolation = class EmptyInterpolation extends Base
  4468. constructor: ->
  4469. super()
  4470. #### For
  4471. # CoffeeScript's replacement for the *for* loop is our array and object
  4472. # comprehensions, that compile into *for* loops here. They also act as an
  4473. # expression, able to return the result of each filtered iteration.
  4474. #
  4475. # Unlike Python array comprehensions, they can be multi-line, and you can pass
  4476. # the current index of the loop as a second parameter. Unlike Ruby blocks,
  4477. # you can map and filter in a single pass.
  4478. exports.For = class For extends While
  4479. constructor: (body, source) ->
  4480. super()
  4481. @addBody body
  4482. @addSource source
  4483. children: ['body', 'source', 'guard', 'step']
  4484. isAwait: -> @await ? no
  4485. addBody: (body) ->
  4486. @body = Block.wrap [body]
  4487. {expressions} = @body
  4488. if expressions.length
  4489. @body.locationData ?= mergeLocationData expressions[0].locationData, expressions[expressions.length - 1].locationData
  4490. this
  4491. addSource: (source) ->
  4492. {@source = no} = source
  4493. attribs = ["name", "index", "guard", "step", "own", "ownTag", "await", "awaitTag", "object", "from"]
  4494. @[attr] = source[attr] ? @[attr] for attr in attribs
  4495. return this unless @source
  4496. @index.error 'cannot use index with for-from' if @from and @index
  4497. @ownTag.error "cannot use own with for-#{if @from then 'from' else 'in'}" if @own and not @object
  4498. [@name, @index] = [@index, @name] if @object
  4499. @index.error 'index cannot be a pattern matching expression' if @index?.isArray?() or @index?.isObject?()
  4500. @awaitTag.error 'await must be used with for-from' if @await and not @from
  4501. @range = @source instanceof Value and @source.base instanceof Range and not @source.properties.length and not @from
  4502. @pattern = @name instanceof Value
  4503. @name.unwrap().propagateLhs?(yes) if @pattern
  4504. @index.error 'indexes do not apply to range loops' if @range and @index
  4505. @name.error 'cannot pattern match over range loops' if @range and @pattern
  4506. @returns = no
  4507. # Move up any comments in the `for` line, i.e. the line of code with `for`,
  4508. # from any child nodes of that line up to the `for` node itself so that these
  4509. # comments get output, and get output above the `for` loop.
  4510. for attribute in ['source', 'guard', 'step', 'name', 'index'] when @[attribute]
  4511. @[attribute].traverseChildren yes, (node) =>
  4512. if node.comments
  4513. # These comments are buried pretty deeply, so if they happen to be
  4514. # trailing comments the line they trail will be unrecognizable when
  4515. # were done compiling this `for` loop; so just shift them up to
  4516. # output above the `for` line.
  4517. comment.newLine = comment.unshift = yes for comment in node.comments
  4518. moveComments node, @[attribute]
  4519. moveComments @[attribute], @
  4520. this
  4521. # Welcome to the hairiest method in all of CoffeeScript. Handles the inner
  4522. # loop, filtering, stepping, and result saving for array, object, and range
  4523. # comprehensions. Some of the generated code can be shared in common, and
  4524. # some cannot.
  4525. compileNode: (o) ->
  4526. body = Block.wrap [@body]
  4527. [..., last] = body.expressions
  4528. @returns = no if last?.jumps() instanceof Return
  4529. source = if @range then @source.base else @source
  4530. scope = o.scope
  4531. name = @name and (@name.compile o, LEVEL_LIST) if not @pattern
  4532. index = @index and (@index.compile o, LEVEL_LIST)
  4533. scope.find(name) if name and not @pattern
  4534. scope.find(index) if index and @index not instanceof Value
  4535. rvar = scope.freeVariable 'results' if @returns
  4536. if @from
  4537. ivar = scope.freeVariable 'x', single: true if @pattern
  4538. else
  4539. ivar = (@object and index) or scope.freeVariable 'i', single: true
  4540. kvar = ((@range or @from) and name) or index or ivar
  4541. kvarAssign = if kvar isnt ivar then "#{kvar} = " else ""
  4542. if @step and not @range
  4543. [step, stepVar] = @cacheToCodeFragments @step.cache o, LEVEL_LIST, shouldCacheOrIsAssignable
  4544. stepNum = parseNumber stepVar if @step.isNumber()
  4545. name = ivar if @pattern
  4546. varPart = ''
  4547. guardPart = ''
  4548. defPart = ''
  4549. idt1 = @tab + TAB
  4550. if @range
  4551. forPartFragments = source.compileToFragments merge o,
  4552. {index: ivar, name, @step, shouldCache: shouldCacheOrIsAssignable}
  4553. else
  4554. svar = @source.compile o, LEVEL_LIST
  4555. if (name or @own) and @source.unwrap() not instanceof IdentifierLiteral
  4556. defPart += "#{@tab}#{ref = scope.freeVariable 'ref'} = #{svar};\n"
  4557. svar = ref
  4558. if name and not @pattern and not @from
  4559. namePart = "#{name} = #{svar}[#{kvar}]"
  4560. if not @object and not @from
  4561. defPart += "#{@tab}#{step};\n" if step isnt stepVar
  4562. down = stepNum < 0
  4563. lvar = scope.freeVariable 'len' unless @step and stepNum? and down
  4564. declare = "#{kvarAssign}#{ivar} = 0, #{lvar} = #{svar}.length"
  4565. declareDown = "#{kvarAssign}#{ivar} = #{svar}.length - 1"
  4566. compare = "#{ivar} < #{lvar}"
  4567. compareDown = "#{ivar} >= 0"
  4568. if @step
  4569. if stepNum?
  4570. if down
  4571. compare = compareDown
  4572. declare = declareDown
  4573. else
  4574. compare = "#{stepVar} > 0 ? #{compare} : #{compareDown}"
  4575. declare = "(#{stepVar} > 0 ? (#{declare}) : #{declareDown})"
  4576. increment = "#{ivar} += #{stepVar}"
  4577. else
  4578. increment = "#{if kvar isnt ivar then "++#{ivar}" else "#{ivar}++"}"
  4579. forPartFragments = [@makeCode("#{declare}; #{compare}; #{kvarAssign}#{increment}")]
  4580. if @returns
  4581. resultPart = "#{@tab}#{rvar} = [];\n"
  4582. returnResult = "\n#{@tab}return #{rvar};"
  4583. body.makeReturn rvar
  4584. if @guard
  4585. if body.expressions.length > 1
  4586. body.expressions.unshift new If (new Parens @guard).invert(), new StatementLiteral "continue"
  4587. else
  4588. body = Block.wrap [new If @guard, body] if @guard
  4589. if @pattern
  4590. body.expressions.unshift new Assign @name, if @from then new IdentifierLiteral kvar else new Literal "#{svar}[#{kvar}]"
  4591. varPart = "\n#{idt1}#{namePart};" if namePart
  4592. if @object
  4593. forPartFragments = [@makeCode("#{kvar} in #{svar}")]
  4594. guardPart = "\n#{idt1}if (!#{utility 'hasProp', o}.call(#{svar}, #{kvar})) continue;" if @own
  4595. else if @from
  4596. if @await
  4597. forPartFragments = new Op 'await', new Parens new Literal "#{kvar} of #{svar}"
  4598. forPartFragments = forPartFragments.compileToFragments o, LEVEL_TOP
  4599. else
  4600. forPartFragments = [@makeCode("#{kvar} of #{svar}")]
  4601. bodyFragments = body.compileToFragments merge(o, indent: idt1), LEVEL_TOP
  4602. if bodyFragments and bodyFragments.length > 0
  4603. bodyFragments = [].concat @makeCode('\n'), bodyFragments, @makeCode('\n')
  4604. fragments = [@makeCode(defPart)]
  4605. fragments.push @makeCode(resultPart) if resultPart
  4606. forCode = if @await then 'for ' else 'for ('
  4607. forClose = if @await then '' else ')'
  4608. fragments = fragments.concat @makeCode(@tab), @makeCode( forCode),
  4609. forPartFragments, @makeCode("#{forClose} {#{guardPart}#{varPart}"), bodyFragments,
  4610. @makeCode(@tab), @makeCode('}')
  4611. fragments.push @makeCode(returnResult) if returnResult
  4612. fragments
  4613. astNode: (o) ->
  4614. addToScope = (name) ->
  4615. alreadyDeclared = o.scope.find name.value
  4616. name.isDeclaration = not alreadyDeclared
  4617. @name?.eachName addToScope, checkAssignability: no
  4618. @index?.eachName addToScope, checkAssignability: no
  4619. super o
  4620. astType: -> 'For'
  4621. astProperties: (o) ->
  4622. return
  4623. source: @source?.ast o
  4624. body: @body.ast o, LEVEL_TOP
  4625. guard: @guard?.ast(o) ? null
  4626. name: @name?.ast(o) ? null
  4627. index: @index?.ast(o) ? null
  4628. step: @step?.ast(o) ? null
  4629. postfix: !!@postfix
  4630. own: !!@own
  4631. await: !!@await
  4632. style: switch
  4633. when @from then 'from'
  4634. when @object then 'of'
  4635. when @name then 'in'
  4636. else 'range'
  4637. #### Switch
  4638. # A JavaScript *switch* statement. Converts into a returnable expression on-demand.
  4639. exports.Switch = class Switch extends Base
  4640. constructor: (@subject, @cases, @otherwise) ->
  4641. super()
  4642. children: ['subject', 'cases', 'otherwise']
  4643. isStatement: YES
  4644. jumps: (o = {block: yes}) ->
  4645. for {block} in @cases
  4646. return jumpNode if jumpNode = block.jumps o
  4647. @otherwise?.jumps o
  4648. makeReturn: (results, mark) ->
  4649. block.makeReturn(results, mark) for {block} in @cases
  4650. @otherwise or= new Block [new Literal 'void 0'] if results
  4651. @otherwise?.makeReturn results, mark
  4652. this
  4653. compileNode: (o) ->
  4654. idt1 = o.indent + TAB
  4655. idt2 = o.indent = idt1 + TAB
  4656. fragments = [].concat @makeCode(@tab + "switch ("),
  4657. (if @subject then @subject.compileToFragments(o, LEVEL_PAREN) else @makeCode "false"),
  4658. @makeCode(") {\n")
  4659. for {conditions, block}, i in @cases
  4660. for cond in flatten [conditions]
  4661. cond = cond.invert() unless @subject
  4662. fragments = fragments.concat @makeCode(idt1 + "case "), cond.compileToFragments(o, LEVEL_PAREN), @makeCode(":\n")
  4663. fragments = fragments.concat body, @makeCode('\n') if (body = block.compileToFragments o, LEVEL_TOP).length > 0
  4664. break if i is @cases.length - 1 and not @otherwise
  4665. expr = @lastNode block.expressions
  4666. continue if expr instanceof Return or expr instanceof Throw or (expr instanceof Literal and expr.jumps() and expr.value isnt 'debugger')
  4667. fragments.push cond.makeCode(idt2 + 'break;\n')
  4668. if @otherwise and @otherwise.expressions.length
  4669. fragments.push @makeCode(idt1 + "default:\n"), (@otherwise.compileToFragments o, LEVEL_TOP)..., @makeCode("\n")
  4670. fragments.push @makeCode @tab + '}'
  4671. fragments
  4672. astType: -> 'SwitchStatement'
  4673. casesAst: (o) ->
  4674. cases = []
  4675. for kase, caseIndex in @cases
  4676. {conditions: tests, block: consequent} = kase
  4677. tests = flatten [tests]
  4678. lastTestIndex = tests.length - 1
  4679. for test, testIndex in tests
  4680. testConsequent =
  4681. if testIndex is lastTestIndex
  4682. consequent
  4683. else
  4684. null
  4685. caseLocationData = test.locationData
  4686. caseLocationData = mergeLocationData caseLocationData, testConsequent.expressions[testConsequent.expressions.length - 1].locationData if testConsequent?.expressions.length
  4687. caseLocationData = mergeLocationData caseLocationData, kase.locationData, justLeading: yes if testIndex is 0
  4688. caseLocationData = mergeLocationData caseLocationData, kase.locationData, justEnding: yes if testIndex is lastTestIndex
  4689. cases.push new SwitchCase(test, testConsequent, trailing: testIndex is lastTestIndex).withLocationDataFrom locationData: caseLocationData
  4690. if @otherwise?.expressions.length
  4691. cases.push new SwitchCase(null, @otherwise).withLocationDataFrom @otherwise
  4692. kase.ast(o) for kase in cases
  4693. astProperties: (o) ->
  4694. return
  4695. discriminant: @subject?.ast(o, LEVEL_PAREN) ? null
  4696. cases: @casesAst o
  4697. class SwitchCase extends Base
  4698. constructor: (@test, @block, {@trailing} = {}) ->
  4699. super()
  4700. children: ['test', 'block']
  4701. astProperties: (o) ->
  4702. return
  4703. test: @test?.ast(o, LEVEL_PAREN) ? null
  4704. consequent: @block?.ast(o, LEVEL_TOP).body ? []
  4705. trailing: !!@trailing
  4706. exports.SwitchWhen = class SwitchWhen extends Base
  4707. constructor: (@conditions, @block) ->
  4708. super()
  4709. children: ['conditions', 'block']
  4710. #### If
  4711. # *If/else* statements. Acts as an expression by pushing down requested returns
  4712. # to the last line of each clause.
  4713. #
  4714. # Single-expression **Ifs** are compiled into conditional operators if possible,
  4715. # because ternaries are already proper expressions, and dont need conversion.
  4716. exports.If = class If extends Base
  4717. constructor: (@condition, @body, options = {}) ->
  4718. super()
  4719. @elseBody = null
  4720. @isChain = false
  4721. {@soak, @postfix, @type} = options
  4722. moveComments @condition, @ if @condition.comments
  4723. children: ['condition', 'body', 'elseBody']
  4724. bodyNode: -> @body?.unwrap()
  4725. elseBodyNode: -> @elseBody?.unwrap()
  4726. # Rewrite a chain of **Ifs** to add a default case as the final *else*.
  4727. addElse: (elseBody) ->
  4728. if @isChain
  4729. @elseBodyNode().addElse elseBody
  4730. @locationData = mergeLocationData @locationData, @elseBodyNode().locationData
  4731. else
  4732. @isChain = elseBody instanceof If
  4733. @elseBody = @ensureBlock elseBody
  4734. @elseBody.updateLocationDataIfMissing elseBody.locationData
  4735. @locationData = mergeLocationData @locationData, @elseBody.locationData if @locationData? and @elseBody.locationData?
  4736. this
  4737. # The **If** only compiles into a statement if either of its bodies needs
  4738. # to be a statement. Otherwise a conditional operator is safe.
  4739. isStatement: (o) ->
  4740. o?.level is LEVEL_TOP or
  4741. @bodyNode().isStatement(o) or @elseBodyNode()?.isStatement(o)
  4742. jumps: (o) -> @body.jumps(o) or @elseBody?.jumps(o)
  4743. compileNode: (o) ->
  4744. if @isStatement o then @compileStatement o else @compileExpression o
  4745. makeReturn: (results, mark) ->
  4746. if mark
  4747. @body?.makeReturn results, mark
  4748. @elseBody?.makeReturn results, mark
  4749. return
  4750. @elseBody or= new Block [new Literal 'void 0'] if results
  4751. @body and= new Block [@body.makeReturn results]
  4752. @elseBody and= new Block [@elseBody.makeReturn results]
  4753. this
  4754. ensureBlock: (node) ->
  4755. if node instanceof Block then node else new Block [node]
  4756. # Compile the `If` as a regular *if-else* statement. Flattened chains
  4757. # force inner *else* bodies into statement form.
  4758. compileStatement: (o) ->
  4759. child = del o, 'chainChild'
  4760. exeq = del o, 'isExistentialEquals'
  4761. if exeq
  4762. return new If(@processedCondition().invert(), @elseBodyNode(), type: 'if').compileToFragments o
  4763. indent = o.indent + TAB
  4764. cond = @processedCondition().compileToFragments o, LEVEL_PAREN
  4765. body = @ensureBlock(@body).compileToFragments merge o, {indent}
  4766. ifPart = [].concat @makeCode("if ("), cond, @makeCode(") {\n"), body, @makeCode("\n#{@tab}}")
  4767. ifPart.unshift @makeCode @tab unless child
  4768. return ifPart unless @elseBody
  4769. answer = ifPart.concat @makeCode(' else ')
  4770. if @isChain
  4771. o.chainChild = yes
  4772. answer = answer.concat @elseBody.unwrap().compileToFragments o, LEVEL_TOP
  4773. else
  4774. answer = answer.concat @makeCode("{\n"), @elseBody.compileToFragments(merge(o, {indent}), LEVEL_TOP), @makeCode("\n#{@tab}}")
  4775. answer
  4776. # Compile the `If` as a conditional operator.
  4777. compileExpression: (o) ->
  4778. cond = @processedCondition().compileToFragments o, LEVEL_COND
  4779. body = @bodyNode().compileToFragments o, LEVEL_LIST
  4780. alt = if @elseBodyNode() then @elseBodyNode().compileToFragments(o, LEVEL_LIST) else [@makeCode('void 0')]
  4781. fragments = cond.concat @makeCode(" ? "), body, @makeCode(" : "), alt
  4782. if o.level >= LEVEL_COND then @wrapInParentheses fragments else fragments
  4783. unfoldSoak: ->
  4784. @soak and this
  4785. processedCondition: ->
  4786. @processedConditionCache ?= if @type is 'unless' then @condition.invert() else @condition
  4787. isStatementAst: (o) ->
  4788. o.level is LEVEL_TOP
  4789. astType: (o) ->
  4790. if @isStatementAst o
  4791. 'IfStatement'
  4792. else
  4793. 'ConditionalExpression'
  4794. astProperties: (o) ->
  4795. isStatement = @isStatementAst o
  4796. return
  4797. test: @condition.ast o, if isStatement then LEVEL_PAREN else LEVEL_COND
  4798. consequent:
  4799. if isStatement
  4800. @body.ast o, LEVEL_TOP
  4801. else
  4802. @bodyNode().ast o, LEVEL_TOP
  4803. alternate:
  4804. if @isChain
  4805. @elseBody.unwrap().ast o, if isStatement then LEVEL_TOP else LEVEL_COND
  4806. else if not isStatement and @elseBody?.expressions?.length is 1
  4807. @elseBody.expressions[0].ast o, LEVEL_TOP
  4808. else
  4809. @elseBody?.ast(o, LEVEL_TOP) ? null
  4810. postfix: !!@postfix
  4811. inverted: @type is 'unless'
  4812. # A sequence expression e.g. `(a; b)`.
  4813. # Currently only used during AST generation.
  4814. exports.Sequence = class Sequence extends Base
  4815. children: ['expressions']
  4816. constructor: (@expressions) ->
  4817. super()
  4818. astNode: (o) ->
  4819. return @expressions[0].ast(o) if @expressions.length is 1
  4820. super o
  4821. astType: -> 'SequenceExpression'
  4822. astProperties: (o) ->
  4823. return
  4824. expressions:
  4825. expression.ast(o) for expression in @expressions
  4826. # Constants
  4827. # ---------
  4828. UTILITIES =
  4829. modulo: -> 'function(a, b) { return (+a % (b = +b) + b) % b; }'
  4830. boundMethodCheck: -> "
  4831. function(instance, Constructor) {
  4832. if (!(instance instanceof Constructor)) {
  4833. throw new Error('Bound instance method accessed before binding');
  4834. }
  4835. }
  4836. "
  4837. # Shortcuts to speed up the lookup time for native functions.
  4838. hasProp: -> '{}.hasOwnProperty'
  4839. indexOf: -> '[].indexOf'
  4840. slice : -> '[].slice'
  4841. splice : -> '[].splice'
  4842. # Levels indicate a node's position in the AST. Useful for knowing if
  4843. # parens are necessary or superfluous.
  4844. LEVEL_TOP = 1 # ...;
  4845. LEVEL_PAREN = 2 # (...)
  4846. LEVEL_LIST = 3 # [...]
  4847. LEVEL_COND = 4 # ... ? x : y
  4848. LEVEL_OP = 5 # !...
  4849. LEVEL_ACCESS = 6 # ...[0]
  4850. # Tabs are two spaces for pretty printing.
  4851. TAB = ' '
  4852. SIMPLENUM = /^[+-]?\d+$/
  4853. SIMPLE_STRING_OMIT = /\s*\n\s*/g
  4854. LEADING_BLANK_LINE = /^[^\n\S]*\n/
  4855. TRAILING_BLANK_LINE = /\n[^\n\S]*$/
  4856. STRING_OMIT = ///
  4857. ((?:\\\\)+) # Consume (and preserve) an even number of backslashes.
  4858. | \\[^\S\n]*\n\s* # Remove escaped newlines.
  4859. ///g
  4860. HEREGEX_OMIT = ///
  4861. ((?:\\\\)+) # Consume (and preserve) an even number of backslashes.
  4862. | \\(\s) # Preserve escaped whitespace.
  4863. | \s+(?:#.*)? # Remove whitespace and comments.
  4864. ///g
  4865. # Helper Functions
  4866. # ----------------
  4867. # Helper for ensuring that utility functions are assigned at the top level.
  4868. utility = (name, o) ->
  4869. {root} = o.scope
  4870. if name of root.utilities
  4871. root.utilities[name]
  4872. else
  4873. ref = root.freeVariable name
  4874. root.assign ref, UTILITIES[name] o
  4875. root.utilities[name] = ref
  4876. multident = (code, tab, includingFirstLine = yes) ->
  4877. endsWithNewLine = code[code.length - 1] is '\n'
  4878. code = (if includingFirstLine then tab else '') + code.replace /\n/g, "$&#{tab}"
  4879. code = code.replace /\s+$/, ''
  4880. code = code + '\n' if endsWithNewLine
  4881. code
  4882. # Wherever in CoffeeScript 1 we mightve inserted a `makeCode "#{@tab}"` to
  4883. # indent a line of code, now we must account for the possibility of comments
  4884. # preceding that line of code. If there are such comments, indent each line of
  4885. # such comments, and _then_ indent the first following line of code.
  4886. indentInitial = (fragments, node) ->
  4887. for fragment, fragmentIndex in fragments
  4888. if fragment.isHereComment
  4889. fragment.code = multident fragment.code, node.tab
  4890. else
  4891. fragments.splice fragmentIndex, 0, node.makeCode "#{node.tab}"
  4892. break
  4893. fragments
  4894. hasLineComments = (node) ->
  4895. return no unless node.comments
  4896. for comment in node.comments
  4897. return yes if comment.here is no
  4898. return no
  4899. # Move the `comments` property from one object to another, deleting it from
  4900. # the first object.
  4901. moveComments = (from, to) ->
  4902. return unless from?.comments
  4903. attachCommentsToNode from.comments, to
  4904. delete from.comments
  4905. # Sometimes when compiling a node, we want to insert a fragment at the start
  4906. # of an array of fragments; but if the start has one or more comment fragments,
  4907. # we want to insert this fragment after those but before any non-comments.
  4908. unshiftAfterComments = (fragments, fragmentToInsert) ->
  4909. inserted = no
  4910. for fragment, fragmentIndex in fragments when not fragment.isComment
  4911. fragments.splice fragmentIndex, 0, fragmentToInsert
  4912. inserted = yes
  4913. break
  4914. fragments.push fragmentToInsert unless inserted
  4915. fragments
  4916. isLiteralArguments = (node) ->
  4917. node instanceof IdentifierLiteral and node.value is 'arguments'
  4918. isLiteralThis = (node) ->
  4919. node instanceof ThisLiteral or (node instanceof Code and node.bound)
  4920. shouldCacheOrIsAssignable = (node) -> node.shouldCache() or node.isAssignable?()
  4921. # Unfold a node's child if soak, then tuck the node under created `If`
  4922. unfoldSoak = (o, parent, name) ->
  4923. return unless ifn = parent[name].unfoldSoak o
  4924. parent[name] = ifn.body
  4925. ifn.body = new Value parent
  4926. ifn
  4927. # Constructs a string or regex by escaping certain characters.
  4928. makeDelimitedLiteral = (body, {delimiter: delimiterOption, escapeNewlines, double, includeDelimiters = yes, escapeDelimiter = yes, convertTrailingNullEscapes} = {}) ->
  4929. body = '(?:)' if body is '' and delimiterOption is '/'
  4930. escapeTemplateLiteralCurlies = delimiterOption is '`'
  4931. regex = ///
  4932. (\\\\) # Escaped backslash.
  4933. | (\\0(?=\d)) # Null character mistaken as octal escape.
  4934. #{
  4935. if convertTrailingNullEscapes
  4936. /// | (\\0) $ ///.source # Trailing null character that could be mistaken as octal escape.
  4937. else
  4938. ''
  4939. }
  4940. #{
  4941. if escapeDelimiter
  4942. /// | \\?(#{delimiterOption}) ///.source # (Possibly escaped) delimiter.
  4943. else
  4944. ''
  4945. }
  4946. #{
  4947. if escapeTemplateLiteralCurlies
  4948. /// | \\?(\$\{) ///.source # `${` inside template literals must be escaped.
  4949. else
  4950. ''
  4951. }
  4952. | \\?(?:
  4953. #{if escapeNewlines then '(\n)|' else ''}
  4954. (\r)
  4955. | (\u2028)
  4956. | (\u2029)
  4957. ) # (Possibly escaped) newlines.
  4958. | (\\.) # Other escapes.
  4959. ///g
  4960. body = body.replace regex, (match, backslash, nul, ...args) ->
  4961. trailingNullEscape =
  4962. args.shift() if convertTrailingNullEscapes
  4963. delimiter =
  4964. args.shift() if escapeDelimiter
  4965. templateLiteralCurly =
  4966. args.shift() if escapeTemplateLiteralCurlies
  4967. lf =
  4968. args.shift() if escapeNewlines
  4969. [cr, ls, ps, other] = args
  4970. switch
  4971. # Ignore escaped backslashes.
  4972. when backslash then (if double then backslash + backslash else backslash)
  4973. when nul then '\\x00'
  4974. when trailingNullEscape then "\\x00"
  4975. when delimiter then "\\#{delimiter}"
  4976. when templateLiteralCurly then "\\${"
  4977. when lf then '\\n'
  4978. when cr then '\\r'
  4979. when ls then '\\u2028'
  4980. when ps then '\\u2029'
  4981. when other then (if double then "\\#{other}" else other)
  4982. printedDelimiter = if includeDelimiters then delimiterOption else ''
  4983. "#{printedDelimiter}#{body}#{printedDelimiter}"
  4984. sniffDirectives = (expressions, {notFinalExpression} = {}) ->
  4985. index = 0
  4986. lastIndex = expressions.length - 1
  4987. while index <= lastIndex
  4988. break if index is lastIndex and notFinalExpression
  4989. expression = expressions[index]
  4990. if (unwrapped = expression?.unwrap?()) instanceof PassthroughLiteral and unwrapped.generated
  4991. index++
  4992. continue
  4993. break unless expression instanceof Value and expression.isString() and not expression.unwrap().shouldGenerateTemplateLiteral()
  4994. expressions[index] =
  4995. new Directive expression
  4996. .withLocationDataFrom expression
  4997. index++
  4998. astAsBlockIfNeeded = (node, o) ->
  4999. unwrapped = node.unwrap()
  5000. if unwrapped instanceof Block and unwrapped.expressions.length > 1
  5001. unwrapped.makeReturn null, yes
  5002. unwrapped.ast o, LEVEL_TOP
  5003. else
  5004. node.ast o, LEVEL_PAREN
  5005. # Helpers for `mergeLocationData` and `mergeAstLocationData` below.
  5006. lesser = (a, b) -> if a < b then a else b
  5007. greater = (a, b) -> if a > b then a else b
  5008. isAstLocGreater = (a, b) ->
  5009. return yes if a.line > b.line
  5010. return no unless a.line is b.line
  5011. a.column > b.column
  5012. isLocationDataStartGreater = (a, b) ->
  5013. return yes if a.first_line > b.first_line
  5014. return no unless a.first_line is b.first_line
  5015. a.first_column > b.first_column
  5016. isLocationDataEndGreater = (a, b) ->
  5017. return yes if a.last_line > b.last_line
  5018. return no unless a.last_line is b.last_line
  5019. a.last_column > b.last_column
  5020. # Take two nodes location data and return a new `locationData` object that
  5021. # encompasses the location data of both nodes. So the new `first_line` value
  5022. # will be the earlier of the two nodes `first_line` values, the new
  5023. # `last_column` the later of the two nodes `last_column` values, etc.
  5024. #
  5025. # If you only want to extend the first nodes location data with the start or
  5026. # end location data of the second node, pass the `justLeading` or `justEnding`
  5027. # options. So e.g. if `first`s range is [4, 5] and `second`s range is [1, 10],
  5028. # youd get:
  5029. # ```
  5030. # mergeLocationData(first, second).range # [1, 10]
  5031. # mergeLocationData(first, second, justLeading: yes).range # [1, 5]
  5032. # mergeLocationData(first, second, justEnding: yes).range # [4, 10]
  5033. # ```
  5034. exports.mergeLocationData = mergeLocationData = (locationDataA, locationDataB, {justLeading, justEnding} = {}) ->
  5035. return Object.assign(
  5036. if justEnding
  5037. first_line: locationDataA.first_line
  5038. first_column: locationDataA.first_column
  5039. else
  5040. if isLocationDataStartGreater locationDataA, locationDataB
  5041. first_line: locationDataB.first_line
  5042. first_column: locationDataB.first_column
  5043. else
  5044. first_line: locationDataA.first_line
  5045. first_column: locationDataA.first_column
  5046. ,
  5047. if justLeading
  5048. last_line: locationDataA.last_line
  5049. last_column: locationDataA.last_column
  5050. last_line_exclusive: locationDataA.last_line_exclusive
  5051. last_column_exclusive: locationDataA.last_column_exclusive
  5052. else
  5053. if isLocationDataEndGreater locationDataA, locationDataB
  5054. last_line: locationDataA.last_line
  5055. last_column: locationDataA.last_column
  5056. last_line_exclusive: locationDataA.last_line_exclusive
  5057. last_column_exclusive: locationDataA.last_column_exclusive
  5058. else
  5059. last_line: locationDataB.last_line
  5060. last_column: locationDataB.last_column
  5061. last_line_exclusive: locationDataB.last_line_exclusive
  5062. last_column_exclusive: locationDataB.last_column_exclusive
  5063. ,
  5064. range: [
  5065. if justEnding
  5066. locationDataA.range[0]
  5067. else
  5068. lesser locationDataA.range[0], locationDataB.range[0]
  5069. ,
  5070. if justLeading
  5071. locationDataA.range[1]
  5072. else
  5073. greater locationDataA.range[1], locationDataB.range[1]
  5074. ]
  5075. )
  5076. # Take two AST nodes, or two AST nodes location data objects, and return a new
  5077. # location data object that encompasses the location data of both nodes. So the
  5078. # new `start` value will be the earlier of the two nodes `start` values, the
  5079. # new `end` value will be the later of the two nodes `end` values, etc.
  5080. #
  5081. # If you only want to extend the first nodes location data with the start or
  5082. # end location data of the second node, pass the `justLeading` or `justEnding`
  5083. # options. So e.g. if `first`s range is [4, 5] and `second`s range is [1, 10],
  5084. # youd get:
  5085. # ```
  5086. # mergeAstLocationData(first, second).range # [1, 10]
  5087. # mergeAstLocationData(first, second, justLeading: yes).range # [1, 5]
  5088. # mergeAstLocationData(first, second, justEnding: yes).range # [4, 10]
  5089. # ```
  5090. exports.mergeAstLocationData = mergeAstLocationData = (nodeA, nodeB, {justLeading, justEnding} = {}) ->
  5091. return
  5092. loc:
  5093. start:
  5094. if justEnding
  5095. nodeA.loc.start
  5096. else
  5097. if isAstLocGreater nodeA.loc.start, nodeB.loc.start
  5098. nodeB.loc.start
  5099. else
  5100. nodeA.loc.start
  5101. end:
  5102. if justLeading
  5103. nodeA.loc.end
  5104. else
  5105. if isAstLocGreater nodeA.loc.end, nodeB.loc.end
  5106. nodeA.loc.end
  5107. else
  5108. nodeB.loc.end
  5109. range: [
  5110. if justEnding
  5111. nodeA.range[0]
  5112. else
  5113. lesser nodeA.range[0], nodeB.range[0]
  5114. ,
  5115. if justLeading
  5116. nodeA.range[1]
  5117. else
  5118. greater nodeA.range[1], nodeB.range[1]
  5119. ]
  5120. start:
  5121. if justEnding
  5122. nodeA.start
  5123. else
  5124. lesser nodeA.start, nodeB.start
  5125. end:
  5126. if justLeading
  5127. nodeA.end
  5128. else
  5129. greater nodeA.end, nodeB.end
  5130. # Convert Jison-style node class location data to Babel-style location data
  5131. exports.jisonLocationDataToAstLocationData = jisonLocationDataToAstLocationData = ({first_line, first_column, last_line_exclusive, last_column_exclusive, range}) ->
  5132. return
  5133. loc:
  5134. start:
  5135. line: first_line + 1
  5136. column: first_column
  5137. end:
  5138. line: last_line_exclusive + 1
  5139. column: last_column_exclusive
  5140. range: [
  5141. range[0]
  5142. range[1]
  5143. ]
  5144. start: range[0]
  5145. end: range[1]
  5146. # Generate a zero-width location data that corresponds to the end of another nodes location.
  5147. zeroWidthLocationDataFromEndLocation = ({range: [, endRange], last_line_exclusive, last_column_exclusive}) -> {
  5148. first_line: last_line_exclusive
  5149. first_column: last_column_exclusive
  5150. last_line: last_line_exclusive
  5151. last_column: last_column_exclusive
  5152. last_line_exclusive
  5153. last_column_exclusive
  5154. range: [endRange, endRange]
  5155. }
  5156. extractSameLineLocationDataFirst = (numChars) -> ({range: [startRange], first_line, first_column}) -> {
  5157. first_line
  5158. first_column
  5159. last_line: first_line
  5160. last_column: first_column + numChars - 1
  5161. last_line_exclusive: first_line
  5162. last_column_exclusive: first_column + numChars
  5163. range: [startRange, startRange + numChars]
  5164. }
  5165. extractSameLineLocationDataLast = (numChars) -> ({range: [, endRange], last_line, last_column, last_line_exclusive, last_column_exclusive}) -> {
  5166. first_line: last_line
  5167. first_column: last_column - (numChars - 1)
  5168. last_line: last_line
  5169. last_column: last_column
  5170. last_line_exclusive
  5171. last_column_exclusive
  5172. range: [endRange - numChars, endRange]
  5173. }
  5174. # We dont currently have a token corresponding to the empty space
  5175. # between interpolation/JSX expression braces, so piece together the location
  5176. # data by trimming the braces from the Interpolations location data.
  5177. # Technically the last_line/last_column calculation here could be
  5178. # incorrect if the ending brace is preceded by a newline, but
  5179. # last_line/last_column arent used for AST generation anyway.
  5180. emptyExpressionLocationData = ({interpolationNode: element, openingBrace, closingBrace}) ->
  5181. first_line: element.locationData.first_line
  5182. first_column: element.locationData.first_column + openingBrace.length
  5183. last_line: element.locationData.last_line
  5184. last_column: element.locationData.last_column - closingBrace.length
  5185. last_line_exclusive: element.locationData.last_line
  5186. last_column_exclusive: element.locationData.last_column
  5187. range: [
  5188. element.locationData.range[0] + openingBrace.length
  5189. element.locationData.range[1] - closingBrace.length
  5190. ]