nodes.coffee

- Jump To … +
  browser.coffee cake.coffee coffeescript.coffee command.coffee grammar.coffee helpers.coffee index.coffee lexer.coffee nodes.coffee optparse.coffee register.coffee repl.coffee rewriter.coffee scope.litcoffee sourcemap.litcoffee

- # nodes.coffee

- §

  `nodes.coffee` contains all of the node classes for the syntax tree. Most nodes are created as the result of actions in the grammar, but some are created by other nodes as a method of code generation. To convert the syntax tree into a string of JavaScript code, call `compile()` on the root.

      Error.stackTraceLimit = Infinity

      {Scope} = require './scope'
      {isUnassignable, JS_FORBIDDEN} = require './lexer'

- §

  Import the helpers we plan to use.

      {compact, flatten, extend, merge, del, starts, ends, some,
      addDataToNode, attachCommentsToNode, locationDataToString,
      throwSyntaxError, replaceUnicodeCodePointEscapes,
      isFunction, isPlainObject, isNumber, parseNumber} = require './helpers'

- §

  Functions required by parser.

      exports.extend = extend
      exports.addDataToNode = addDataToNode

- §

  Constant functions for nodes that don’t need customization.

      YES     = -> yes
      NO      = -> no
      THIS    = -> this
      NEGATE  = -> @negated = not @negated; this

- §

  ### CodeFragment

- §

  The various nodes defined below all compile to a collection of CodeFragment objects. A CodeFragments is a block of generated code, and the location in the source file where the code came from. CodeFragments can be assembled together into working code just by catting together all the CodeFragments’ `code` snippets, in order.

      exports.CodeFragment = class CodeFragment
        constructor: (parent, code) ->
          @code = "#{code}"
          @type = parent?.constructor?.name or 'unknown'
          @locationData = parent?.locationData
          @comments = parent?.comments

        toString: ->

- §

  This is only intended for debugging.

          "#{@code}#{if @locationData then ": " + locationDataToString(@locationData) else ''}"

- §

  Convert an array of CodeFragments into a string.

      fragmentsToText = (fragments) ->
        (fragment.code for fragment in fragments).join('')

- §

  ### Base

- §

  The Base is the abstract base class for all nodes in the syntax tree. Each subclass implements the `compileNode` method, which performs the code generation for that node. To compile a node to JavaScript, call `compile` on it, which wraps `compileNode` in some generic extra smarts, to know when the generated code needs to be wrapped up in a closure. An options hash is passed and cloned throughout, containing information about the environment from higher in the tree (such as if a returned value is being requested by the surrounding function), information about the current scope, and indentation level.

      exports.Base = class Base

        compile: (o, lvl) ->
          fragmentsToText @compileToFragments o, lvl

- §

  Occasionally a node is compiled multiple times, for example to get the name of a variable to add to scope tracking. When we know that a “premature” compilation won’t result in comments being output, set those comments aside so that they’re preserved for a later `compile` call that will result in the comments being included in the output.

        compileWithoutComments: (o, lvl, method = 'compile') ->
          if @comments
            @ignoreTheseCommentsTemporarily = @comments
            delete @comments
          unwrapped = @unwrapAll()
          if unwrapped.comments
            unwrapped.ignoreTheseCommentsTemporarily = unwrapped.comments
            delete unwrapped.comments

          fragments = @[method] o, lvl

          if @ignoreTheseCommentsTemporarily
            @comments = @ignoreTheseCommentsTemporarily
            delete @ignoreTheseCommentsTemporarily
          if unwrapped.ignoreTheseCommentsTemporarily
            unwrapped.comments = unwrapped.ignoreTheseCommentsTemporarily
            delete unwrapped.ignoreTheseCommentsTemporarily

          fragments

        compileNodeWithoutComments: (o, lvl) ->
          @compileWithoutComments o, lvl, 'compileNode'

- §

  Common logic for determining whether to wrap this node in a closure before compiling it, or to compile directly. We need to wrap if this node is a statement, and it’s not a pureStatement, and we’re not at the top level of a block (which would be unnecessary), and we haven’t already been asked to return the result (because statements know how to return results).

        compileToFragments: (o, lvl) ->
          o        = extend {}, o
          o.level  = lvl if lvl
          node     = @unfoldSoak(o) or this
          node.tab = o.indent

          fragments = if o.level is LEVEL_TOP or not node.isStatement(o)
            node.compileNode o
          else
            node.compileClosure o
          @compileCommentFragments o, node, fragments
          fragments

        compileToFragmentsWithoutComments: (o, lvl) ->
          @compileWithoutComments o, lvl, 'compileToFragments'

- §

  Statements converted into expressions via closure-wrapping share a scope object with their parent closure, to preserve the expected lexical scope.

        compileClosure: (o) ->
          @checkForPureStatementInExpression()
          o.sharedScope = yes
          func = new Code [], Block.wrap [this]
          args = []
          if @contains ((node) -> node instanceof SuperCall)
            func.bound = yes
          else if (argumentsNode = @contains isLiteralArguments) or @contains isLiteralThis
            args = [new ThisLiteral]
            if argumentsNode
              meth = 'apply'
              args.push new IdentifierLiteral 'arguments'
            else
              meth = 'call'
            func = new Value func, [new Access new PropertyName meth]
          parts = (new Call func, args).compileNode o

          switch
            when func.isGenerator or func.base?.isGenerator
              parts.unshift @makeCode "(yield* "
              parts.push    @makeCode ")"
            when func.isAsync or func.base?.isAsync
              parts.unshift @makeCode "(await "
              parts.push    @makeCode ")"
          parts

        compileCommentFragments: (o, node, fragments) ->
          return fragments unless node.comments

- §

  This is where comments, that are attached to nodes as a `comments` property, become `CodeFragment`s. “Inline block comments,” e.g. `/* */`-delimited comments that are interspersed within code on a line, are added to the current `fragments` stream. All other fragments are attached as properties to the nearest preceding or following fragment, to remain stowaways until they get properly output in `compileComments` later on.

          unshiftCommentFragment = (commentFragment) ->
            if commentFragment.unshift

- §

  Find the first non-comment fragment and insert `commentFragment` before it.

              unshiftAfterComments fragments, commentFragment
            else
              if fragments.length isnt 0
                precedingFragment = fragments[fragments.length - 1]
                if commentFragment.newLine and precedingFragment.code isnt '' and
                   not /\n\s*$/.test precedingFragment.code
                  commentFragment.code = "\n#{commentFragment.code}"
              fragments.push commentFragment

          for comment in node.comments when comment not in @compiledComments
            @compiledComments.push comment # Don’t output this comment twice.

- §

  For block/here comments, denoted by `###`, that are inline comments like `1 + ### comment ### 2`, create fragments and insert them into the fragments array. Otherwise attach comment fragments to their closest fragment for now, so they can be inserted into the output later after all the newlines have been added.

            if comment.here # Block comment, delimited by `###`.
              commentFragment = new HereComment(comment).compileNode o
            else # Line comment, delimited by `#`.
              commentFragment = new LineComment(comment).compileNode o
            if (commentFragment.isHereComment and not commentFragment.newLine) or
               node.includeCommentFragments()

- §

  Inline block comments, like `1 + /* comment */ 2`, or a node whose `compileToFragments` method has logic for outputting comments.

              unshiftCommentFragment commentFragment
            else
              fragments.push @makeCode '' if fragments.length is 0
              if commentFragment.unshift
                fragments[0].precedingComments ?= []
                fragments[0].precedingComments.push commentFragment
              else
                fragments[fragments.length - 1].followingComments ?= []
                fragments[fragments.length - 1].followingComments.push commentFragment
          fragments

- §

  If the code generation wishes to use the result of a complex expression in multiple places, ensure that the expression is only ever evaluated once, by assigning it to a temporary variable. Pass a level to precompile.

  If `level` is passed, then returns `[val, ref]`, where `val` is the compiled value, and `ref` is the compiled reference. If `level` is not passed, this returns `[val, ref]` where the two values are raw nodes which have not been compiled.

        cache: (o, level, shouldCache) ->
          complex = if shouldCache? then shouldCache this else @shouldCache()
          if complex
            ref = new IdentifierLiteral o.scope.freeVariable 'ref'
            sub = new Assign ref, this
            if level then [sub.compileToFragments(o, level), [@makeCode(ref.value)]] else [sub, ref]
          else
            ref = if level then @compileToFragments o, level else this
            [ref, ref]

- §

  Occasionally it may be useful to make an expression behave as if it was ‘hoisted’, whereby the result of the expression is available before its location in the source, but the expression’s variable scope corresponds to the source position. This is used extensively to deal with executable class bodies in classes.

  Calling this method mutates the node, proxying the `compileNode` and `compileToFragments` methods to store their result for later replacing the `target` node, which is returned by the call.

        hoist: ->
          @hoisted = yes
          target   = new HoistTarget @

          compileNode        = @compileNode
          compileToFragments = @compileToFragments

          @compileNode = (o) ->
            target.update compileNode, o

          @compileToFragments = (o) ->
            target.update compileToFragments, o

          target

        cacheToCodeFragments: (cacheValues) ->
          [fragmentsToText(cacheValues[0]), fragmentsToText(cacheValues[1])]

- §

  Construct a node that returns the current node’s result. Note that this is overridden for smarter behavior for many statement nodes (e.g. `If`, `For`).

        makeReturn: (results, mark) ->
          if mark

- §

  Mark this node as implicitly returned, so that it can be part of the node metadata returned in the AST.

            @canBeReturned = yes
            return
          node = @unwrapAll()
          if results
            new Call new Literal("#{results}.push"), [node]
          else
            new Return node

- §

  Does this node, or any of its children, contain a node of a certain kind? Recursively traverses down the children nodes and returns the first one that verifies `pred`. Otherwise return undefined. `contains` does not cross scope boundaries.

        contains: (pred) ->
          node = undefined
          @traverseChildren no, (n) ->
            if pred n
              node = n
              return no
          node

- §

  Pull out the last node of a node list.

        lastNode: (list) ->
          if list.length is 0 then null else list[list.length - 1]

- §

  Debugging representation of the node, for inspecting the parse tree. This is what `coffee --nodes` prints out.

        toString: (idt = '', name = @constructor.name) ->
          tree = '\n' + idt + name
          tree += '?' if @soak
          @eachChild (node) -> tree += node.toString idt + TAB
          tree

        checkForPureStatementInExpression: ->
          if jumpNode = @jumps()
            jumpNode.error 'cannot use a pure statement in an expression'

- §

  Plain JavaScript object representation of the node, that can be serialized as JSON. This is what the `ast` option in the Node API returns. We try to follow the Babel AST spec as closely as possible, for improved interoperability with other tools. WARNING: DO NOT OVERRIDE THIS METHOD IN CHILD CLASSES. Only override the component `ast*` methods as needed.

        ast: (o, level) ->

- §

  Merge `level` into `o` and perform other universal checks.

          o = @astInitialize o, level

- §

  Create serializable representation of this node.

          astNode = @astNode o

- §

  Mark AST nodes that correspond to expressions that (implicitly) return. We can’t do this as part of `astNode` because we need to assemble child nodes first before marking the parent being returned.

          if @astNode? and @canBeReturned
            Object.assign astNode, {returns: yes}
          astNode

        astInitialize: (o, level) ->
          o = Object.assign {}, o
          o.level = level if level?
          if o.level > LEVEL_TOP
            @checkForPureStatementInExpression()

- §

  `@makeReturn` must be called before `astProperties`, because the latter may call `.ast()` for child nodes and those nodes would need the return logic from `makeReturn` already executed by then.

          @makeReturn null, yes if @isStatement(o) and o.level isnt LEVEL_TOP and o.scope?
          o

        astNode: (o) ->

- §

  Every abstract syntax tree node object has four categories of properties:

  - type, stored in the `type` field and a string like `NumberLiteral`.
  - location data, stored in the `loc`, `start`, `end` and `range` fields.
  - properties specific to this node, like `parsedValue`.
  - properties that are themselves child nodes, like `body`. These fields are all intermixed in the Babel spec; `type` and `start` and `parsedValue` are all top level fields in the AST node object. We have separate methods for returning each category, that we merge together here.

          Object.assign {}, {type: @astType(o)}, @astProperties(o), @astLocationData()

- §

  By default, a node class has no specific properties.

        astProperties: -> {}

- §

  By default, a node class’s AST `type` is its class name.

        astType: -> @constructor.name

- §

  The AST location data is a rearranged version of our Jison location data, mutated into the structure that the Babel spec uses.

        astLocationData: ->
          jisonLocationDataToAstLocationData @locationData

- §

  Determines whether an AST node needs an `ExpressionStatement` wrapper. Typically matches our `isStatement()` logic but this allows overriding.

        isStatementAst: (o) ->
          @isStatement o

- §

  Passes each child to a function, breaking when the function returns `false`.

        eachChild: (func) ->
          return this unless @children
          for attr in @children when @[attr]
            for child in flatten [@[attr]]
              return this if func(child) is false
          this

        traverseChildren: (crossScope, func) ->
          @eachChild (child) ->
            recur = func(child)
            child.traverseChildren(crossScope, func) unless recur is no

- §

  `replaceInContext` will traverse children looking for a node for which `match` returns true. Once found, the matching node will be replaced by the result of calling `replacement`.

        replaceInContext: (match, replacement) ->
          return false unless @children
          for attr in @children when children = @[attr]
            if Array.isArray children
              for child, i in children
                if match child
                  children[i..i] = replacement child, @
                  return true
                else
                  return true if child.replaceInContext match, replacement
            else if match children
              @[attr] = replacement children, @
              return true
            else
              return true if children.replaceInContext match, replacement

        invert: ->
          new Op '!', this

        unwrapAll: ->
          node = this
          continue until node is node = node.unwrap()
          node

- §

  Default implementations of the common node properties and methods. Nodes will override these with custom logic, if needed.

- §

  `children` are the properties to recurse into when tree walking. The `children` list is the structure of the AST. The `parent` pointer, and the pointer to the `children` are how you can traverse the tree.

        children: []

- §

  `isStatement` has to do with “everything is an expression”. A few things can’t be expressions, such as `break`. Things that `isStatement` returns `true` for are things that can’t be used as expressions. There are some error messages that come from `nodes.coffee` due to statements ending up in expression position.

        isStatement: NO

- §

  Track comments that have been compiled into fragments, to avoid outputting them twice.

        compiledComments: []

- §

  `includeCommentFragments` lets `compileCommentFragments` know whether this node has special awareness of how to handle comments within its output.

        includeCommentFragments: NO

- §

  `jumps` tells you if an expression, or an internal part of an expression, has a flow control construct (like `break`, `continue`, or `return`) that jumps out of the normal flow of control and can’t be used as a value. (Note that `throw` is not considered a flow control construct.) This is important because flow control in the middle of an expression makes no sense; we have to disallow it.

        jumps: NO

- §

  If `node.shouldCache() is false`, it is safe to use `node` more than once. Otherwise you need to store the value of `node` in a variable and output that variable several times instead. Kind of like this: `5` need not be cached. `returnFive()`, however, could have side effects as a result of evaluating it more than once, and therefore we need to cache it. The parameter is named `shouldCache` rather than `mustCache` because there are also cases where we might not need to cache but where we want to, for example a long expression that may well be idempotent but we want to cache for brevity.

        shouldCache: YES

        isChainable: NO
        isAssignable: NO
        isNumber: NO

        unwrap: THIS
        unfoldSoak: NO

- §

  Is this node used to assign a certain variable?

        assigns: NO

- §

  For this node and all descendents, set the location data to `locationData` if the location data is not already set.

        updateLocationDataIfMissing: (locationData, force) ->
          @forceUpdateLocation = yes if force
          return this if @locationData and not @forceUpdateLocation
          delete @forceUpdateLocation
          @locationData = locationData

          @eachChild (child) ->
            child.updateLocationDataIfMissing locationData

- §

  Add location data from another node

        withLocationDataFrom: ({locationData}) ->
          @updateLocationDataIfMissing locationData

- §

  Add location data and comments from another node

        withLocationDataAndCommentsFrom: (node) ->
          @withLocationDataFrom node
          {comments} = node
          @comments = comments if comments?.length
          this

- §

  Throw a SyntaxError associated with this node’s location.

        error: (message) ->
          throwSyntaxError message, @locationData

        makeCode: (code) ->
          new CodeFragment this, code

        wrapInParentheses: (fragments) ->
          [@makeCode('('), fragments..., @makeCode(')')]

        wrapInBraces: (fragments) ->
          [@makeCode('{'), fragments..., @makeCode('}')]

- §

  `fragmentsList` is an array of arrays of fragments. Each array in fragmentsList will be concatenated together, with `joinStr` added in between each, to produce a final flat array of fragments.

        joinFragmentArrays: (fragmentsList, joinStr) ->
          answer = []
          for fragments, i in fragmentsList
            if i then answer.push @makeCode joinStr
            answer = answer.concat fragments
          answer

- §

  ### HoistTarget

- §

  A HoistTargetNode represents the output location in the node tree for a hoisted node. See Base#hoist.

      exports.HoistTarget = class HoistTarget extends Base

- §

  Expands hoisted fragments in the given array

        @expand = (fragments) ->
          for fragment, i in fragments by -1 when fragment.fragments
            fragments[i..i] = @expand fragment.fragments
          fragments

        constructor: (@source) ->
          super()

- §

  Holds presentational options to apply when the source node is compiled.

          @options = {}

- §

  Placeholder fragments to be replaced by the source node’s compilation.

          @targetFragments = { fragments: [] }

        isStatement: (o) ->
          @source.isStatement o

- §

  Update the target fragments with the result of compiling the source. Calls the given compile function with the node and options (overriden with the target presentational options).

        update: (compile, o) ->
          @targetFragments.fragments = compile.call @source, merge o, @options

- §

  Copies the target indent and level, and returns the placeholder fragments

        compileToFragments: (o, level) ->
          @options.indent = o.indent
          @options.level  = level ? o.level
          [ @targetFragments ]

        compileNode: (o) ->
          @compileToFragments o

        compileClosure: (o) ->
          @compileToFragments o

- §

  ### Root

- §

  The root node of the node tree

      exports.Root = class Root extends Base
        constructor: (@body) ->
          super()

          @isAsync = (new Code [], @body).isAsync

        children: ['body']

- §

  Wrap everything in a safety closure, unless requested not to. It would be better not to generate them in the first place, but for now, clean up obvious double-parentheses.

        compileNode: (o) ->
          o.indent    = if o.bare then '' else TAB
          o.level     = LEVEL_TOP
          o.compiling = yes
          @initializeScope o
          fragments = @body.compileRoot o
          return fragments if o.bare
          functionKeyword = "#{if @isAsync then 'async ' else ''}function"
          [].concat @makeCode("(#{functionKeyword}() {\n"), fragments, @makeCode("\n}).call(this);\n")

        initializeScope: (o) ->
          o.scope = new Scope null, @body, null, o.referencedVars ? []

- §

  Mark given local variables in the root scope as parameters so they don’t end up being declared on the root block.

          o.scope.parameter name for name in o.locals or []

        commentsAst: ->
          @allComments ?=
            for commentToken in (@allCommentTokens ? []) when not commentToken.heregex
              if commentToken.here
                new HereComment commentToken
              else
                new LineComment commentToken
          comment.ast() for comment in @allComments

        astNode: (o) ->
          o.level = LEVEL_TOP
          @initializeScope o
          super o

        astType: -> 'File'

        astProperties: (o) ->
          @body.isRootBlock = yes
          return
            program: Object.assign @body.ast(o), @astLocationData()
            comments: @commentsAst()

- §

  ### Block

- §

  The block is the list of expressions that forms the body of an indented block of code – the implementation of a function, a clause in an `if`, `switch`, or `try`, and so on…

      exports.Block = class Block extends Base
        constructor: (nodes) ->
          super()

          @expressions = compact flatten nodes or []

        children: ['expressions']

- §

  Tack an expression on to the end of this expression list.

        push: (node) ->
          @expressions.push node
          this

- §

  Remove and return the last expression of this expression list.

        pop: ->
          @expressions.pop()

- §

  Add an expression at the beginning of this expression list.

        unshift: (node) ->
          @expressions.unshift node
          this

- §

  If this Block consists of just a single node, unwrap it by pulling it back out.

        unwrap: ->
          if @expressions.length is 1 then @expressions[0] else this

- §

  Is this an empty block of code?

        isEmpty: ->
          not @expressions.length

        isStatement: (o) ->
          for exp in @expressions when exp.isStatement o
            return yes
          no

        jumps: (o) ->
          for exp in @expressions
            return jumpNode if jumpNode = exp.jumps o

- §

  A Block node does not return its entire body, rather it ensures that the final expression is returned.

        makeReturn: (results, mark) ->
          len = @expressions.length
          [..., lastExp] = @expressions
          lastExp = lastExp?.unwrap() or no

- §

  We also need to check that we’re not returning a JSX tag if there’s an adjacent one at the same level; JSX doesn’t allow that.

          if lastExp and lastExp instanceof Parens and lastExp.body.expressions.length > 1
            {body:{expressions}} = lastExp
            [..., penult, last] = expressions
            penult = penult.unwrap()
            last = last.unwrap()
            if penult instanceof JSXElement and last instanceof JSXElement
              expressions[expressions.length - 1].error 'Adjacent JSX elements must be wrapped in an enclosing tag'
          if mark
            @expressions[len - 1]?.makeReturn results, mark
            return
          while len--
            expr = @expressions[len]
            @expressions[len] = expr.makeReturn results
            @expressions.splice(len, 1) if expr instanceof Return and not expr.expression
            break
          this

        compile: (o, lvl) ->
          return new Root(this).withLocationDataFrom(this).compile o, lvl unless o.scope

          super o, lvl

- §

  Compile all expressions within the Block body. If we need to return the result, and it’s an expression, simply return it. If it’s a statement, ask the statement to do so.

        compileNode: (o) ->
          @tab  = o.indent
          top   = o.level is LEVEL_TOP
          compiledNodes = []

          for node, index in @expressions
            if node.hoisted

- §

  This is a hoisted expression. We want to compile this and ignore the result.

              node.compileToFragments o
              continue
            node = (node.unfoldSoak(o) or node)
            if node instanceof Block

- §

  This is a nested block. We don’t do anything special here like enclose it in a new scope; we just compile the statements in this block along with our own.

              compiledNodes.push node.compileNode o
            else if top
              node.front = yes
              fragments = node.compileToFragments o
              unless node.isStatement o
                fragments = indentInitial fragments, @
                [..., lastFragment] = fragments
                unless lastFragment.code is '' or lastFragment.isComment
                  fragments.push @makeCode ';'
              compiledNodes.push fragments
            else
              compiledNodes.push node.compileToFragments o, LEVEL_LIST
          if top
            if @spaced
              return [].concat @joinFragmentArrays(compiledNodes, '\n\n'), @makeCode('\n')
            else
              return @joinFragmentArrays(compiledNodes, '\n')
          if compiledNodes.length
            answer = @joinFragmentArrays(compiledNodes, ', ')
          else
            answer = [@makeCode 'void 0']
          if compiledNodes.length > 1 and o.level >= LEVEL_LIST then @wrapInParentheses answer else answer

        compileRoot: (o) ->
          @spaced = yes
          fragments = @compileWithDeclarations o
          HoistTarget.expand fragments
          @compileComments fragments

- §

  Compile the expressions body for the contents of a function, with declarations of all inner variables pushed up to the top.

        compileWithDeclarations: (o) ->
          fragments = []
          post = []
          for exp, i in @expressions
            exp = exp.unwrap()
            break unless exp instanceof Literal
          o = merge(o, level: LEVEL_TOP)
          if i
            rest = @expressions.splice i, 9e9
            [spaced,    @spaced] = [@spaced, no]
            [fragments, @spaced] = [@compileNode(o), spaced]
            @expressions = rest
          post = @compileNode o
          {scope} = o
          if scope.expressions is this
            declars = o.scope.hasDeclarations()
            assigns = scope.hasAssignments
            if declars or assigns
              fragments.push @makeCode '\n' if i
              fragments.push @makeCode "#{@tab}var "
              if declars
                declaredVariables = scope.declaredVariables()
                for declaredVariable, declaredVariablesIndex in declaredVariables
                  fragments.push @makeCode declaredVariable
                  if Object::hasOwnProperty.call o.scope.comments, declaredVariable
                    fragments.push o.scope.comments[declaredVariable]...
                  if declaredVariablesIndex isnt declaredVariables.length - 1
                    fragments.push @makeCode ', '
              if assigns
                fragments.push @makeCode ",\n#{@tab + TAB}" if declars
                fragments.push @makeCode scope.assignedVariables().join(",\n#{@tab + TAB}")
              fragments.push @makeCode ";\n#{if @spaced then '\n' else ''}"
            else if fragments.length and post.length
              fragments.push @makeCode "\n"
          fragments.concat post

        compileComments: (fragments) ->
          for fragment, fragmentIndex in fragments

- §

  Insert comments into the output at the next or previous newline. If there are no newlines at which to place comments, create them.

            if fragment.precedingComments

- §

  Determine the indentation level of the fragment that we are about to insert comments before, and use that indentation level for our inserted comments. At this point, the fragments’ `code` property is the generated output JavaScript, and CoffeeScript always generates output indented by two spaces; so all we need to do is search for a `code` property that begins with at least two spaces.

              fragmentIndent = ''
              for pastFragment in fragments[0...(fragmentIndex + 1)] by -1
                indent = /^ {2,}/m.exec pastFragment.code
                if indent
                  fragmentIndent = indent[0]
                  break
                else if '\n' in pastFragment.code
                  break
              code = "\n#{fragmentIndent}" + (
                  for commentFragment in fragment.precedingComments
                    if commentFragment.isHereComment and commentFragment.multiline
                      multident commentFragment.code, fragmentIndent, no
                    else
                      commentFragment.code
                ).join("\n#{fragmentIndent}").replace /^(\s*)$/gm, ''
              for pastFragment, pastFragmentIndex in fragments[0...(fragmentIndex + 1)] by -1
                newLineIndex = pastFragment.code.lastIndexOf '\n'
                if newLineIndex is -1

- §

  Keep searching previous fragments until we can’t go back any further, either because there are no fragments left or we’ve discovered that we’re in a code block that is interpolated inside a string.

                  if pastFragmentIndex is 0
                    pastFragment.code = '\n' + pastFragment.code
                    newLineIndex = 0
                  else if pastFragment.isStringWithInterpolations and pastFragment.code is '{'
                    code = code[1..] + '\n' # Move newline to end.
                    newLineIndex = 1
                  else
                    continue
                delete fragment.precedingComments
                pastFragment.code = pastFragment.code[0...newLineIndex] +
                  code + pastFragment.code[newLineIndex..]
                break

- §

  Yes, this is awfully similar to the previous `if` block, but if you look closely you’ll find lots of tiny differences that make this confusing if it were abstracted into a function that both blocks share.

            if fragment.followingComments

- §

  Does the first trailing comment follow at the end of a line of code, like `; // Comment`, or does it start a new line after a line of code?

              trail = fragment.followingComments[0].trail
              fragmentIndent = ''

- §

  Find the indent of the next line of code, if we have any non-trailing comments to output. We need to first find the next newline, as these comments will be output after that; and then the indent of the line that follows the next newline.

              unless trail and fragment.followingComments.length is 1
                onNextLine = no
                for upcomingFragment in fragments[fragmentIndex...]
                  unless onNextLine
                    if '\n' in upcomingFragment.code
                      onNextLine = yes
                    else
                      continue
                  else
                    indent = /^ {2,}/m.exec upcomingFragment.code
                    if indent
                      fragmentIndent = indent[0]
                      break
                    else if '\n' in upcomingFragment.code
                      break

- §

  Is this comment following the indent inserted by bare mode? If so, there’s no need to indent this further.

              code = if fragmentIndex is 1 and /^\s+$/.test fragments[0].code
                ''
              else if trail
                ' '
              else
                "\n#{fragmentIndent}"

- §

  Assemble properly indented comments.

              code += (
                  for commentFragment in fragment.followingComments
                    if commentFragment.isHereComment and commentFragment.multiline
                      multident commentFragment.code, fragmentIndent, no
                    else
                      commentFragment.code
                ).join("\n#{fragmentIndent}").replace /^(\s*)$/gm, ''
              for upcomingFragment, upcomingFragmentIndex in fragments[fragmentIndex...]
                newLineIndex = upcomingFragment.code.indexOf '\n'
                if newLineIndex is -1

- §

  Keep searching upcoming fragments until we can’t go any further, either because there are no fragments left or we’ve discovered that we’re in a code block that is interpolated inside a string.

                  if upcomingFragmentIndex is fragments.length - 1
                    upcomingFragment.code = upcomingFragment.code + '\n'
                    newLineIndex = upcomingFragment.code.length
                  else if upcomingFragment.isStringWithInterpolations and upcomingFragment.code is '}'
                    code = "#{code}\n"
                    newLineIndex = 0
                  else
                    continue
                delete fragment.followingComments

- §

  Avoid inserting extra blank lines.

                code = code.replace /^\n/, '' if upcomingFragment.code is '\n'
                upcomingFragment.code = upcomingFragment.code[0...newLineIndex] +
                  code + upcomingFragment.code[newLineIndex..]
                break

          fragments

- §

  Wrap up the given nodes as a Block, unless it already happens to be one.

        @wrap: (nodes) ->
          return nodes[0] if nodes.length is 1 and nodes[0] instanceof Block
          new Block nodes

        astNode: (o) ->
          if (o.level? and o.level isnt LEVEL_TOP) and @expressions.length
            return (new Sequence(@expressions).withLocationDataFrom @).ast o

          super o

        astType: ->
          if @isRootBlock
            'Program'
          else if @isClassBody
            'ClassBody'
          else
            'BlockStatement'

        astProperties: (o) ->
          checkForDirectives = del o, 'checkForDirectives'

          sniffDirectives @expressions, notFinalExpression: checkForDirectives if @isRootBlock or checkForDirectives
          directives = []
          body = []
          for expression in @expressions
            expressionAst = expression.ast o

- §

  Ignore generated PassthroughLiteral

            if not expressionAst?
              continue
            else if expression instanceof Directive
              directives.push expressionAst

- §

  If an expression is a statement, it can be added to the body as is.

            else if expression.isStatementAst o
              body.push expressionAst

- §

  Otherwise, we need to wrap it in an `ExpressionStatement` AST node.

            else
              body.push Object.assign
                  type: 'ExpressionStatement'
                  expression: expressionAst
                ,
                  expression.astLocationData()

          return {

- §

  For now, we’re not including `sourceType` on the `Program` AST node. Its value could be either `'script'` or `'module'`, and there’s no way for CoffeeScript to always know which it should be. The presence of an `import` or `export` statement in source code would imply that it should be a `module`, but a project may consist of mostly such files and also an outlier file that lacks `import` or `export` but is still imported into the project and therefore expects to be treated as a `module`. Determining the value of `sourceType` is essentially the same challenge posed by determining the parse goal of a JavaScript file, also `module` or `script`, and so if Node figures out a way to do so for `.js` files then CoffeeScript can copy Node’s algorithm.

- §

  sourceType: ‘module’

            body, directives
          }

        astLocationData: ->
          return if @isRootBlock and not @locationData?
          super()

- §

  A directive e.g. ‘use strict’. Currently only used during AST generation.

      exports.Directive = class Directive extends Base
        constructor: (@value) ->
          super()

        astProperties: (o) ->
          return
            value: Object.assign {},
              @value.ast o
              type: 'DirectiveLiteral'

- §

  ### Literal

- §

  `Literal` is a base class for static values that can be passed through directly into JavaScript without translation, such as: strings, numbers, `true`, `false`, `null`…

      exports.Literal = class Literal extends Base
        constructor: (@value) ->
          super()

        shouldCache: NO

        assigns: (name) ->
          name is @value

        compileNode: (o) ->
          [@makeCode @value]

        astProperties: ->
          return
            value: @value

        toString: ->

- §

  This is only intended for debugging.

          " #{if @isStatement() then super() else @constructor.name}: #{@value}"

      exports.NumberLiteral = class NumberLiteral extends Literal
        constructor: (@value, {@parsedValue} = {}) ->
          super()
          unless @parsedValue?
            if isNumber @value
              @parsedValue = @value
              @value = "#{@value}"
            else
              @parsedValue = parseNumber @value

        isBigInt: ->
          /n$/.test @value

        astType: ->
          if @isBigInt()
            'BigIntLiteral'
          else
            'NumericLiteral'

        astProperties: ->
          return
            value:
              if @isBigInt()
                @parsedValue.toString()
              else
                @parsedValue
            extra:
              rawValue:
                if @isBigInt()
                  @parsedValue.toString()
                else
                  @parsedValue
              raw: @value

      exports.InfinityLiteral = class InfinityLiteral extends NumberLiteral
        constructor: (@value, {@originalValue = 'Infinity'} = {}) ->
          super()

        compileNode: ->
          [@makeCode '2e308']

        astNode: (o) ->
          unless @originalValue is 'Infinity'
            return new NumberLiteral(@value).withLocationDataFrom(@).ast o
          super o

        astType: -> 'Identifier'

        astProperties: ->
          return
            name: 'Infinity'
            declaration: no

      exports.NaNLiteral = class NaNLiteral extends NumberLiteral
        constructor: ->
          super 'NaN'

        compileNode: (o) ->
          code = [@makeCode '0/0']
          if o.level >= LEVEL_OP then @wrapInParentheses code else code

        astType: -> 'Identifier'

        astProperties: ->
          return
            name: 'NaN'
            declaration: no

      exports.StringLiteral = class StringLiteral extends Literal
        constructor: (@originalValue, {@quote, @initialChunk, @finalChunk, @indent, @double, @heregex} = {}) ->
          super ''
          @quote = null if @quote is '///'
          @fromSourceString = @quote?
          @quote ?= '"'
          heredoc = @isFromHeredoc()

          val = @originalValue
          if @heregex
            val = val.replace HEREGEX_OMIT, '$1$2'
            val = replaceUnicodeCodePointEscapes val, flags: @heregex.flags
          else
            val = val.replace STRING_OMIT, '$1'
            val =
              unless @fromSourceString
                val
              else if heredoc
                indentRegex = /// \n#{@indent} ///g if @indent

                val = val.replace indentRegex, '\n' if indentRegex
                val = val.replace LEADING_BLANK_LINE,  '' if @initialChunk
                val = val.replace TRAILING_BLANK_LINE, '' if @finalChunk
                val
              else
                val.replace SIMPLE_STRING_OMIT, (match, offset) =>
                  if (@initialChunk and offset is 0) or
                     (@finalChunk and offset + match.length is val.length)
                    ''
                  else
                    ' '
          @delimiter = @quote.charAt 0
          @value = makeDelimitedLiteral val, {
            @delimiter
            @double
          }

          @unquotedValueForTemplateLiteral = makeDelimitedLiteral val, {
            delimiter: '`'
            @double
            escapeNewlines: no
            includeDelimiters: no
            convertTrailingNullEscapes: yes
          }

          @unquotedValueForJSX = makeDelimitedLiteral val, {
            @double
            escapeNewlines: no
            includeDelimiters: no
            escapeDelimiter: no
          }

        compileNode: (o) ->
          return StringWithInterpolations.fromStringLiteral(@).compileNode o if @shouldGenerateTemplateLiteral()
          return [@makeCode @unquotedValueForJSX] if @jsx
          super o

- §

  `StringLiteral`s can represent either entire literal strings or pieces of text inside of e.g. an interpolated string. When parsed as the former but needing to be treated as the latter (e.g. the string part of a tagged template literal), this will return a copy of the `StringLiteral` with the quotes trimmed from its location data (like it would have if parsed as part of an interpolated string).

        withoutQuotesInLocationData: ->
          endsWithNewline = @originalValue[-1..] is '\n'
          locationData = Object.assign {}, @locationData
          locationData.first_column          += @quote.length
          if endsWithNewline
            locationData.last_line -= 1
            locationData.last_column =
              if locationData.last_line is locationData.first_line
                locationData.first_column + @originalValue.length - '\n'.length
              else
                @originalValue[...-1].length - '\n'.length - @originalValue[...-1].lastIndexOf('\n')
          else
            locationData.last_column         -= @quote.length
          locationData.last_column_exclusive -= @quote.length
          locationData.range = [
            locationData.range[0] + @quote.length
            locationData.range[1] - @quote.length
          ]
          copy = new StringLiteral @originalValue, {@quote, @initialChunk, @finalChunk, @indent, @double, @heregex}
          copy.locationData = locationData
          copy

        isFromHeredoc: ->
          @quote.length is 3

        shouldGenerateTemplateLiteral: ->
          @isFromHeredoc()

        astNode: (o) ->
          return StringWithInterpolations.fromStringLiteral(@).ast o if @shouldGenerateTemplateLiteral()
          super o

        astProperties: ->
          return
            value: @originalValue
            extra:
              raw: "#{@delimiter}#{@originalValue}#{@delimiter}"

      exports.RegexLiteral = class RegexLiteral extends Literal
        constructor: (value, {@delimiter = '/', @heregexCommentTokens = []} = {}) ->
          super ''
          heregex = @delimiter is '///'
          endDelimiterIndex = value.lastIndexOf '/'
          @flags = value[endDelimiterIndex + 1..]
          val = @originalValue = value[1...endDelimiterIndex]
          val = val.replace HEREGEX_OMIT, '$1$2' if heregex
          val = replaceUnicodeCodePointEscapes val, {@flags}
          @value = "#{makeDelimitedLiteral val, delimiter: '/'}#{@flags}"

        REGEX_REGEX: /// ^ / (.*) / \w* $ ///

        astType: -> 'RegExpLiteral'

        astProperties: (o) ->
          [, pattern] = @REGEX_REGEX.exec @value
          return {
            value: undefined
            pattern, @flags, @delimiter
            originalPattern: @originalValue
            extra:
              raw: @value
              originalRaw: "#{@delimiter}#{@originalValue}#{@delimiter}#{@flags}"
              rawValue: undefined
            comments:
              for heregexCommentToken in @heregexCommentTokens
                if heregexCommentToken.here
                  new HereComment(heregexCommentToken).ast o
                else
                  new LineComment(heregexCommentToken).ast o
          }

      exports.PassthroughLiteral = class PassthroughLiteral extends Literal
        constructor: (@originalValue, {@here, @generated} = {}) ->
          super ''
          @value = @originalValue.replace /\\+(`|$)/g, (string) ->

- §

  `string` is always a value like ‘`‘, ‘\`‘, ‘\\`‘, etc. By reducing it to its latter half, we turn ‘`‘ to ‘`', '\\\`‘ to ‘`‘, etc.

            string[-Math.ceil(string.length / 2)..]

        astNode: (o) ->
          return null if @generated
          super o

        astProperties: ->
          return {
            value: @originalValue
            here: !!@here
          }

      exports.IdentifierLiteral = class IdentifierLiteral extends Literal
        isAssignable: YES

        eachName: (iterator) ->
          iterator @

        astType: ->
          if @jsx
            'JSXIdentifier'
          else
            'Identifier'

        astProperties: ->
          return
            name: @value
            declaration: !!@isDeclaration

      exports.PropertyName = class PropertyName extends Literal
        isAssignable: YES

        astType: ->
          if @jsx
            'JSXIdentifier'
          else
            'Identifier'

        astProperties: ->
          return
            name: @value
            declaration: no

      exports.ComputedPropertyName = class ComputedPropertyName extends PropertyName
        compileNode: (o) ->
          [@makeCode('['), @value.compileToFragments(o, LEVEL_LIST)..., @makeCode(']')]

        astNode: (o) ->
          @value.ast o

      exports.StatementLiteral = class StatementLiteral extends Literal
        isStatement: YES

        makeReturn: THIS

        jumps: (o) ->
          return this if @value is 'break' and not (o?.loop or o?.block)
          return this if @value is 'continue' and not o?.loop

        compileNode: (o) ->
          [@makeCode "#{@tab}#{@value};"]

        astType: ->
          switch @value
            when 'continue' then 'ContinueStatement'
            when 'break'    then 'BreakStatement'
            when 'debugger' then 'DebuggerStatement'

      exports.ThisLiteral = class ThisLiteral extends Literal
        constructor: (value) ->
          super 'this'
          @shorthand = value is '@'

        compileNode: (o) ->
          code = if o.scope.method?.bound then o.scope.method.context else @value
          [@makeCode code]

        astType: -> 'ThisExpression'

        astProperties: ->
          return
            shorthand: @shorthand

      exports.UndefinedLiteral = class UndefinedLiteral extends Literal
        constructor: ->
          super 'undefined'

        compileNode: (o) ->
          [@makeCode if o.level >= LEVEL_ACCESS then '(void 0)' else 'void 0']

        astType: -> 'Identifier'

        astProperties: ->
          return
            name: @value
            declaration: no

      exports.NullLiteral = class NullLiteral extends Literal
        constructor: ->
          super 'null'

      exports.BooleanLiteral = class BooleanLiteral extends Literal
        constructor: (value, {@originalValue} = {}) ->
          super value
          @originalValue ?= @value

        astProperties: ->
          value: if @value is 'true' then yes else no
          name: @originalValue

      exports.DefaultLiteral = class DefaultLiteral extends Literal
        astType: -> 'Identifier'

        astProperties: ->
          return
            name: 'default'
            declaration: no

- §

  ### Return

- §

  A `return` is a pureStatement—wrapping it in a closure wouldn’t make sense.

      exports.Return = class Return extends Base
        constructor: (@expression, {@belongsToFuncDirectiveReturn} = {}) ->
          super()

        children: ['expression']

        isStatement:     YES
        makeReturn:      THIS
        jumps:           THIS

        compileToFragments: (o, level) ->
          expr = @expression?.makeReturn()
          if expr and expr not instanceof Return then expr.compileToFragments o, level else super o, level

        compileNode: (o) ->
          answer = []

- §

  TODO: If we call `expression.compile()` here twice, we’ll sometimes get back different results!

          if @expression
            answer = @expression.compileToFragments o, LEVEL_PAREN
            unshiftAfterComments answer, @makeCode "#{@tab}return "

- §

  Since the `return` got indented by `@tab`, preceding comments that are multiline need to be indented.

            for fragment in answer
              if fragment.isHereComment and '\n' in fragment.code
                fragment.code = multident fragment.code, @tab
              else if fragment.isLineComment
                fragment.code = "#{@tab}#{fragment.code}"
              else
                break
          else
            answer.push @makeCode "#{@tab}return"
          answer.push @makeCode ';'
          answer

        checkForPureStatementInExpression: ->

- §

  don’t flag `return` from `await return`/`yield return` as invalid.

          return if @belongsToFuncDirectiveReturn
          super()

        astType: -> 'ReturnStatement'

        astProperties: (o) ->
          argument: @expression?.ast(o, LEVEL_PAREN) ? null

- §

  Parent class for `YieldReturn`/`AwaitReturn`.

      exports.FuncDirectiveReturn = class FuncDirectiveReturn extends Return
        constructor: (expression, {@returnKeyword}) ->
          super expression

        compileNode: (o) ->
          @checkScope o
          super o

        checkScope: (o) ->
          unless o.scope.parent?
            @error "#{@keyword} can only occur inside functions"

        isStatementAst: NO

        astNode: (o) ->
          @checkScope o

          new Op @keyword,
            new Return @expression, belongsToFuncDirectiveReturn: yes
            .withLocationDataFrom(
              if @expression?
                locationData: mergeLocationData @returnKeyword.locationData, @expression.locationData
              else
                @returnKeyword
            )
          .withLocationDataFrom @
          .ast o

- §

  `yield return` works exactly like `return`, except that it turns the function into a generator.

      exports.YieldReturn = class YieldReturn extends FuncDirectiveReturn
        keyword: 'yield'

      exports.AwaitReturn = class AwaitReturn extends FuncDirectiveReturn
        keyword: 'await'

- §

  ### Value

- §

  A value, variable or literal or parenthesized, indexed or dotted into, or vanilla.

      exports.Value = class Value extends Base
        constructor: (base, props, tag, isDefaultValue = no) ->
          super()
          return base if not props and base instanceof Value
          @base           = base
          @properties     = props or []
          @tag            = tag
          @[tag]          = yes if tag
          @isDefaultValue = isDefaultValue

- §

  If this is a `@foo =` assignment, if there are comments on `@` move them to be on `foo`.

          if @base?.comments and @base instanceof ThisLiteral and @properties[0]?.name?
            moveComments @base, @properties[0].name

        children: ['base', 'properties']

- §

  Add a property (or properties ) `Access` to the list.

        add: (props) ->
          @properties = @properties.concat props
          @forceUpdateLocation = yes
          this

        hasProperties: ->
          @properties.length isnt 0

        bareLiteral: (type) ->
          not @properties.length and @base instanceof type

- §

  Some boolean checks for the benefit of other nodes.

        isArray        : -> @bareLiteral(Arr)
        isRange        : -> @bareLiteral(Range)
        shouldCache    : -> @hasProperties() or @base.shouldCache()
        isAssignable   : (opts) -> @hasProperties() or @base.isAssignable opts
        isNumber       : -> @bareLiteral(NumberLiteral)
        isString       : -> @bareLiteral(StringLiteral)
        isRegex        : -> @bareLiteral(RegexLiteral)
        isUndefined    : -> @bareLiteral(UndefinedLiteral)
        isNull         : -> @bareLiteral(NullLiteral)
        isBoolean      : -> @bareLiteral(BooleanLiteral)
        isAtomic       : ->
          for node in @properties.concat @base
            return no if node.soak or node instanceof Call or node instanceof Op and node.operator is 'do'
          yes

        isNotCallable  : -> @isNumber() or @isString() or @isRegex() or
                            @isArray() or @isRange() or @isSplice() or @isObject() or
                            @isUndefined() or @isNull() or @isBoolean()

        isStatement : (o)    -> not @properties.length and @base.isStatement o
        isJSXTag    : -> @base instanceof JSXTag
        assigns     : (name) -> not @properties.length and @base.assigns name
        jumps       : (o)    -> not @properties.length and @base.jumps o

        isObject: (onlyGenerated) ->
          return no if @properties.length
          (@base instanceof Obj) and (not onlyGenerated or @base.generated)

        isElision: ->
          return no unless @base instanceof Arr
          @base.hasElision()

        isSplice: ->
          [..., lastProperty] = @properties
          lastProperty instanceof Slice

        looksStatic: (className) ->
          return no unless ((thisLiteral = @base) instanceof ThisLiteral or (name = @base).value is className) and
            @properties.length is 1 and @properties[0].name?.value isnt 'prototype'
          return
            staticClassName: thisLiteral ? name

- §

  The value can be unwrapped as its inner node, if there are no attached properties.

        unwrap: ->
          if @properties.length then this else @base

- §

  A reference has base part (`this` value) and name part. We cache them separately for compiling complex expressions. `a()[b()] ?= c` -> `(_base = a())[_name = b()] ? _base[_name] = c`

        cacheReference: (o) ->
          [..., name] = @properties
          if @properties.length < 2 and not @base.shouldCache() and not name?.shouldCache()
            return [this, this]  # `a` `a.b`
          base = new Value @base, @properties[...-1]
          if base.shouldCache()  # `a().b`
            bref = new IdentifierLiteral o.scope.freeVariable 'base'
            base = new Value new Parens new Assign bref, base
          return [base, bref] unless name  # `a()`
          if name.shouldCache()  # `a[b()]`
            nref = new IdentifierLiteral o.scope.freeVariable 'name'
            name = new Index new Assign nref, name.index
            nref = new Index nref
          [base.add(name), new Value(bref or base.base, [nref or name])]

- §

  We compile a value to JavaScript by compiling and joining each property. Things get much more interesting if the chain of properties has soak operators `?.` interspersed. Then we have to take care not to accidentally evaluate anything twice when building the soak chain.

        compileNode: (o) ->
          @base.front = @front
          props = @properties
          if props.length and @base.cached?

- §

  Cached fragments enable correct order of the compilation, and reuse of variables in the scope. Example: `a(x = 5).b(-> x = 6)` should compile in the same order as `a(x = 5); b(-> x = 6)` (see issue #4437, https://github.com/jashkenas/coffeescript/issues/4437)

            fragments = @base.cached
          else
            fragments = @base.compileToFragments o, (if props.length then LEVEL_ACCESS else null)
          if props.length and SIMPLENUM.test fragmentsToText fragments
            fragments.push @makeCode '.'
          for prop in props
            fragments.push (prop.compileToFragments o)...

          fragments

- §

  Unfold a soak into an `If`: `a?.b` -> `a.b if a?`

        unfoldSoak: (o) ->
          @unfoldedSoak ?= do =>
            ifn = @base.unfoldSoak o
            if ifn
              ifn.body.properties.push @properties...
              return ifn
            for prop, i in @properties when prop.soak
              prop.soak = off
              fst = new Value @base, @properties[...i]
              snd = new Value @base, @properties[i..]
              if fst.shouldCache()
                ref = new IdentifierLiteral o.scope.freeVariable 'ref'
                fst = new Parens new Assign ref, fst
                snd.base = ref
              return new If new Existence(fst), snd, soak: on
            no

        eachName: (iterator, {checkAssignability = yes} = {}) ->
          if @hasProperties()
            iterator @
          else if not checkAssignability or @base.isAssignable()
            @base.eachName iterator
          else
            @error 'tried to assign to unassignable value'

- §

  For AST generation, we need an `object` that’s this `Value` minus its last property, if it has properties.

        object: ->
          return @ unless @hasProperties()

- §

  Get all properties except the last one; for a `Value` with only one property, `initialProperties` is an empty array.

          initialProperties = @properties[0...@properties.length - 1]

- §

  Create the `object` that becomes the new “base” for the split-off final property.

          object = new Value @base, initialProperties, @tag, @isDefaultValue

- §

  Add location data to our new node, so that it has correct location data for source maps or later conversion into AST location data.

          object.locationData =
            if initialProperties.length is 0

- §

  This new `Value` has only one property, so the location data is just that of the parent `Value`’s base.

              @base.locationData
            else

- §

  This new `Value` has multiple properties, so the location data spans from the parent `Value`’s base to the last property that’s included in this new node (a.k.a. the second-to-last property of the parent).

              mergeLocationData @base.locationData, initialProperties[initialProperties.length - 1].locationData
          object

        containsSoak: ->
          return no unless @hasProperties()

          for property in @properties when property.soak
            return yes

          return yes if @base instanceof Call and @base.soak

          no

        astNode: (o) ->

- §

  If the `Value` has no properties, the AST node is just whatever this node’s `base` is.

          return @base.ast o unless @hasProperties()

- §

  Otherwise, call `Base::ast` which in turn calls the `astType` and `astProperties` methods below.

          super o

        astType: ->
          if @isJSXTag()
            'JSXMemberExpression'
          else if @containsSoak()
            'OptionalMemberExpression'
          else
            'MemberExpression'

- §

  If this `Value` has properties, the last property (e.g. `c` in `a.b.c`) becomes the `property`, and the preceding properties (e.g. `a.b`) become a child `Value` node assigned to the `object` property.

        astProperties: (o) ->
          [..., property] = @properties
          property.name.jsx = yes if @isJSXTag()
          computed = property instanceof Index or property.name?.unwrap() not instanceof PropertyName
          return {
            object: @object().ast o, LEVEL_ACCESS
            property: property.ast o, (LEVEL_PAREN if computed)
            computed
            optional: !!property.soak
            shorthand: !!property.shorthand
          }

        astLocationData: ->
          return super() unless @isJSXTag()

- §

  don’t include leading < of JSX tag in location data

          mergeAstLocationData(
            jisonLocationDataToAstLocationData(@base.tagNameLocationData),
            jisonLocationDataToAstLocationData(@properties[@properties.length - 1].locationData)
          )

      exports.MetaProperty = class MetaProperty extends Base
        constructor: (@meta, @property) ->
          super()

        children: ['meta', 'property']

        checkValid: (o) ->
          if @meta.value is 'new'
            if @property instanceof Access and @property.name.value is 'target'
              unless o.scope.parent?
                @error "new.target can only occur inside functions"
            else
              @error "the only valid meta property for new is new.target"
          else if @meta.value is 'import'
            unless @property instanceof Access and @property.name.value is 'meta'
              @error "the only valid meta property for import is import.meta"

        compileNode: (o) ->
          @checkValid o
          fragments = []
          fragments.push @meta.compileToFragments(o, LEVEL_ACCESS)...
          fragments.push @property.compileToFragments(o)...
          fragments

        astProperties: (o) ->
          @checkValid o

          return
            meta: @meta.ast o, LEVEL_ACCESS
            property: @property.ast o

- §

  ### HereComment

- §

  Comment delimited by `###` (becoming `/* */`).

      exports.HereComment = class HereComment extends Base
        constructor: ({ @content, @newLine, @unshift, @locationData }) ->
          super()

        compileNode: (o) ->
          multiline = '\n' in @content

- §

  Unindent multiline comments. They will be reindented later.

          if multiline
            indent = null
            for line in @content.split '\n'
              leadingWhitespace = /^\s*/.exec(line)[0]
              if not indent or leadingWhitespace.length < indent.length
                indent = leadingWhitespace
            @content = @content.replace /// \n #{indent} ///g, '\n' if indent

          hasLeadingMarks = /\n\s*[#|\*]/.test @content
          @content = @content.replace /^([ \t]*)#(?=\s)/gm, ' *' if hasLeadingMarks

          @content = "/*#{@content}#{if hasLeadingMarks then ' ' else ''}*/"
          fragment = @makeCode @content
          fragment.newLine = @newLine
          fragment.unshift = @unshift
          fragment.multiline = multiline

- §

  Don’t rely on `fragment.type`, which can break when the compiler is minified.

          fragment.isComment = fragment.isHereComment = yes
          fragment

        astType: -> 'CommentBlock'

        astProperties: ->
          return
            value: @content

- §

  ### LineComment

- §

  Comment running from `#` to the end of a line (becoming `//`).

      exports.LineComment = class LineComment extends Base
        constructor: ({ @content, @newLine, @unshift, @locationData, @precededByBlankLine }) ->
          super()

        compileNode: (o) ->
          fragment = @makeCode(if /^\s*$/.test @content then '' else "#{if @precededByBlankLine then "\n#{o.indent}" else ''}//#{@content}")
          fragment.newLine = @newLine
          fragment.unshift = @unshift
          fragment.trail = not @newLine and not @unshift

- §

  Don’t rely on `fragment.type`, which can break when the compiler is minified.

          fragment.isComment = fragment.isLineComment = yes
          fragment

        astType: -> 'CommentLine'

        astProperties: ->
          return
            value: @content

- §

  ### JSX

      exports.JSXIdentifier = class JSXIdentifier extends IdentifierLiteral
        astType: -> 'JSXIdentifier'

      exports.JSXTag = class JSXTag extends JSXIdentifier
        constructor: (value, {
          @tagNameLocationData
          @closingTagOpeningBracketLocationData
          @closingTagSlashLocationData
          @closingTagNameLocationData
          @closingTagClosingBracketLocationData
        }) ->
          super value

        astProperties: ->
          return
            name: @value

      exports.JSXExpressionContainer = class JSXExpressionContainer extends Base
        constructor: (@expression, {locationData} = {}) ->
          super()
          @expression.jsxAttribute = yes
          @locationData = locationData ? @expression.locationData

        children: ['expression']

        compileNode: (o) ->
          @expression.compileNode(o)

        astProperties: (o) ->
          return
            expression: astAsBlockIfNeeded @expression, o

      exports.JSXEmptyExpression = class JSXEmptyExpression extends Base

      exports.JSXText = class JSXText extends Base
        constructor: (stringLiteral) ->
          super()
          @value = stringLiteral.unquotedValueForJSX
          @locationData = stringLiteral.locationData

        astProperties: ->
          return {
            @value
            extra:
              raw: @value
          }

      exports.JSXAttribute = class JSXAttribute extends Base
        constructor: ({@name, value}) ->
          super()
          @value =
            if value?
              value = value.base
              if value instanceof StringLiteral and not value.shouldGenerateTemplateLiteral()
                value
              else
                new JSXExpressionContainer value
            else
              null
          @value?.comments = value.comments

        children: ['name', 'value']

        compileNode: (o) ->
          compiledName = @name.compileToFragments o, LEVEL_LIST
          return compiledName unless @value?
          val = @value.compileToFragments o, LEVEL_LIST
          compiledName.concat @makeCode('='), val

        astProperties: (o) ->
          name = @name
          if ':' in name.value
            name = new JSXNamespacedName name
          return
            name: name.ast o
            value: @value?.ast(o) ? null

      exports.JSXAttributes = class JSXAttributes extends Base
        constructor: (arr) ->
          super()
          @attributes = []
          for object in arr.objects
            @checkValidAttribute object
            {base} = object
            if base instanceof IdentifierLiteral

- §

  attribute with no value eg disabled

              attribute = new JSXAttribute name: new JSXIdentifier(base.value).withLocationDataAndCommentsFrom base
              attribute.locationData = base.locationData
              @attributes.push attribute
            else if not base.generated

- §

  object spread attribute eg {…props}

              attribute = base.properties[0]
              attribute.jsx = yes
              attribute.locationData = base.locationData
              @attributes.push attribute
            else

- §

  Obj containing attributes with values eg a=”b” c={d}

              for property in base.properties
                {variable, value} = property
                attribute = new JSXAttribute {
                  name: new JSXIdentifier(variable.base.value).withLocationDataAndCommentsFrom variable.base
                  value
                }
                attribute.locationData = property.locationData
                @attributes.push attribute
          @locationData = arr.locationData

        children: ['attributes']

- §

  Catch invalid attributes: <div {a:”b”, props} {props} “value” />

        checkValidAttribute: (object) ->
          {base: attribute} = object
          properties = attribute?.properties or []
          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)))
            object.error """
              Unexpected token. Allowed JSX attributes are: id="val", src={source}, {props...} or attribute.
            """

        compileNode: (o) ->
          fragments = []
          for attribute in @attributes
            fragments.push @makeCode ' '
            fragments.push attribute.compileToFragments(o, LEVEL_TOP)...
          fragments

        astNode: (o) ->
          attribute.ast(o) for attribute in @attributes

      exports.JSXNamespacedName = class JSXNamespacedName extends Base
        constructor: (tag) ->
          super()
          [namespace, name] = tag.value.split ':'
          @namespace = new JSXIdentifier(namespace).withLocationDataFrom locationData: extractSameLineLocationDataFirst(namespace.length) tag.locationData
          @name      = new JSXIdentifier(name     ).withLocationDataFrom locationData: extractSameLineLocationDataLast(name.length      ) tag.locationData
          @locationData = tag.locationData

        children: ['namespace', 'name']

        astProperties: (o) ->
          return
            namespace: @namespace.ast o
            name: @name.ast o

- §

  Node for a JSX element

      exports.JSXElement = class JSXElement extends Base
        constructor: ({@tagName, @attributes, @content}) ->
          super()

        children: ['tagName', 'attributes', 'content']

        compileNode: (o) ->
          @content?.base.jsx = yes
          fragments = [@makeCode('<')]
          fragments.push (tag = @tagName.compileToFragments(o, LEVEL_ACCESS))...
          fragments.push @attributes.compileToFragments(o)...
          if @content
            fragments.push @makeCode('>')
            fragments.push @content.compileNode(o, LEVEL_LIST)...
            fragments.push [@makeCode('</'), tag..., @makeCode('>')]...
          else
            fragments.push @makeCode(' />')
          fragments

        isFragment: ->
          !@tagName.base.value.length

        astNode: (o) ->

- §

  The location data spanning the opening element < … > is captured by the generated Arr which contains the element’s attributes

          @openingElementLocationData = jisonLocationDataToAstLocationData @attributes.locationData

          tagName = @tagName.base
          tagName.locationData = tagName.tagNameLocationData
          if @content?
            @closingElementLocationData = mergeAstLocationData(
              jisonLocationDataToAstLocationData tagName.closingTagOpeningBracketLocationData
              jisonLocationDataToAstLocationData tagName.closingTagClosingBracketLocationData
            )

          super o

        astType: ->
          if @isFragment()
            'JSXFragment'
          else
            'JSXElement'

        elementAstProperties: (o) ->
          tagNameAst = =>
            tag = @tagName.unwrap()
            if tag?.value and ':' in tag.value
              tag = new JSXNamespacedName tag
            tag.ast o

          openingElement = Object.assign {
            type: 'JSXOpeningElement'
            name: tagNameAst()
            selfClosing: not @closingElementLocationData?
            attributes: @attributes.ast o
          }, @openingElementLocationData

          closingElement = null
          if @closingElementLocationData?
            closingElement = Object.assign {
              type: 'JSXClosingElement'
              name: Object.assign(
                tagNameAst(),
                jisonLocationDataToAstLocationData @tagName.base.closingTagNameLocationData
              )
            }, @closingElementLocationData
            if closingElement.name.type in ['JSXMemberExpression', 'JSXNamespacedName']
              rangeDiff = closingElement.range[0] - openingElement.range[0] + '/'.length
              columnDiff = closingElement.loc.start.column - openingElement.loc.start.column + '/'.length
              shiftAstLocationData = (node) =>
                node.range = [
                  node.range[0] + rangeDiff
                  node.range[1] + rangeDiff
                ]
                node.start += rangeDiff
                node.end += rangeDiff
                node.loc.start =
                  line: @closingElementLocationData.loc.start.line
                  column: node.loc.start.column + columnDiff
                node.loc.end =
                  line: @closingElementLocationData.loc.start.line
                  column: node.loc.end.column + columnDiff
              if closingElement.name.type is 'JSXMemberExpression'
                currentExpr = closingElement.name
                while currentExpr.type is 'JSXMemberExpression'
                  shiftAstLocationData currentExpr unless currentExpr is closingElement.name
                  shiftAstLocationData currentExpr.property
                  currentExpr = currentExpr.object
                shiftAstLocationData currentExpr
              else # JSXNamespacedName
                shiftAstLocationData closingElement.name.namespace
                shiftAstLocationData closingElement.name.name

          {openingElement, closingElement}

        fragmentAstProperties: (o) ->
          openingFragment = Object.assign {
            type: 'JSXOpeningFragment'
          }, @openingElementLocationData

          closingFragment = Object.assign {
            type: 'JSXClosingFragment'
          }, @closingElementLocationData

          {openingFragment, closingFragment}

        contentAst: (o) ->
          return [] unless @content and not @content.base.isEmpty?()

          content = @content.unwrapAll()
          children =
            if content instanceof StringLiteral
              [new JSXText content]
            else # StringWithInterpolations
              for element in @content.unwrapAll().extractElements o, includeInterpolationWrappers: yes, isJsx: yes
                if element instanceof StringLiteral
                  new JSXText element
                else # Interpolation
                  {expression} = element
                  unless expression?
                    emptyExpression = new JSXEmptyExpression()
                    emptyExpression.locationData = emptyExpressionLocationData {
                      interpolationNode: element
                      openingBrace: '{'
                      closingBrace: '}'
                    }

                    new JSXExpressionContainer emptyExpression, locationData: element.locationData
                  else
                    unwrapped = expression.unwrapAll()
                    if unwrapped instanceof JSXElement and

- §

  distinguish `<a><b /></a>` from `<a>{<b />}</a>`

                        unwrapped.locationData.range[0] is element.locationData.range[0]
                      unwrapped
                    else
                      new JSXExpressionContainer unwrapped, locationData: element.locationData

          child.ast(o) for child in children when not (child instanceof JSXText and child.value.length is 0)

        astProperties: (o) ->
          Object.assign(
            if @isFragment()
              @fragmentAstProperties o
            else
              @elementAstProperties o
          ,
            children: @contentAst o
          )

        astLocationData: ->
          if @closingElementLocationData?
            mergeAstLocationData @openingElementLocationData, @closingElementLocationData
          else
            @openingElementLocationData

- §

  ### Call

- §

  Node for a function invocation.

      exports.Call = class Call extends Base
        constructor: (@variable, @args = [], @soak, @token) ->
          super()

          @implicit = @args.implicit
          @isNew = no
          if @variable instanceof Value and @variable.isNotCallable()
            @variable.error "literal is not a function"

          if @variable.base instanceof JSXTag
            return new JSXElement(
              tagName: @variable
              attributes: new JSXAttributes @args[0].base
              content: @args[1]
            )

- §

  `@variable` never gets output as a result of this node getting created as part of `RegexWithInterpolations`, so for that case move any comments to the `args` property that gets passed into `RegexWithInterpolations` via the grammar.

          if @variable.base?.value is 'RegExp' and @args.length isnt 0
            moveComments @variable, @args[0]

        children: ['variable', 'args']

- §

  When setting the location, we sometimes need to update the start location to account for a newly-discovered `new` operator to the left of us. This expands the range on the left, but not the right.

        updateLocationDataIfMissing: (locationData) ->
          if @locationData and @needsUpdatedStartLocation
            @locationData = Object.assign {},
              @locationData,
              first_line: locationData.first_line
              first_column: locationData.first_column
              range: [
                locationData.range[0]
                @locationData.range[1]
              ]
            base = @variable?.base or @variable
            if base.needsUpdatedStartLocation
              @variable.locationData = Object.assign {},
                @variable.locationData,
                first_line: locationData.first_line
                first_column: locationData.first_column
                range: [
                  locationData.range[0]
                  @variable.locationData.range[1]
                ]
              base.updateLocationDataIfMissing locationData
            delete @needsUpdatedStartLocation
          super locationData

- §

  Tag this invocation as creating a new instance.

        newInstance: ->
          base = @variable?.base or @variable
          if base instanceof Call and not base.isNew
            base.newInstance()
          else
            @isNew = true
          @needsUpdatedStartLocation = true
          this

- §

  Soaked chained invocations unfold into if/else ternary structures.

        unfoldSoak: (o) ->
          if @soak
            if @variable instanceof Super
              left = new Literal @variable.compile o
              rite = new Value left
              @variable.error "Unsupported reference to 'super'" unless @variable.accessor?
            else
              return ifn if ifn = unfoldSoak o, this, 'variable'
              [left, rite] = new Value(@variable).cacheReference o
            rite = new Call rite, @args
            rite.isNew = @isNew
            left = new Literal "typeof #{ left.compile o } === \"function\""
            return new If left, new Value(rite), soak: yes
          call = this
          list = []
          loop
            if call.variable instanceof Call
              list.push call
              call = call.variable
              continue
            break unless call.variable instanceof Value
            list.push call
            break unless (call = call.variable.base) instanceof Call
          for call in list.reverse()
            if ifn
              if call.variable instanceof Call
                call.variable = ifn
              else
                call.variable.base = ifn
            ifn = unfoldSoak o, call, 'variable'
          ifn

- §

  Compile a vanilla function call.

        compileNode: (o) ->
          @checkForNewSuper()
          @variable?.front = @front
          compiledArgs = []

- §

  If variable is `Accessor` fragments are cached and used later in `Value::compileNode` to ensure correct order of the compilation, and reuse of variables in the scope. Example: `a(x = 5).b(-> x = 6)` should compile in the same order as `a(x = 5); b(-> x = 6)` (see issue #4437, https://github.com/jashkenas/coffeescript/issues/4437)

          varAccess = @variable?.properties?[0] instanceof Access
          argCode = (arg for arg in (@args || []) when arg instanceof Code)
          if argCode.length > 0 and varAccess and not @variable.base.cached
            [cache] = @variable.base.cache o, LEVEL_ACCESS, -> no
            @variable.base.cached = cache

          for arg, argIndex in @args
            if argIndex then compiledArgs.push @makeCode ", "
            compiledArgs.push (arg.compileToFragments o, LEVEL_LIST)...

          fragments = []
          if @isNew
            fragments.push @makeCode 'new '
          fragments.push @variable.compileToFragments(o, LEVEL_ACCESS)...
          fragments.push @makeCode('('), compiledArgs..., @makeCode(')')
          fragments

        checkForNewSuper: ->
          if @isNew
            @variable.error "Unsupported reference to 'super'" if @variable instanceof Super

        containsSoak: ->
          return yes if @soak
          return yes if @variable?.containsSoak?()
          no

        astNode: (o) ->
          if @soak and @variable instanceof Super and o.scope.namedMethod()?.ctor
            @variable.error "Unsupported reference to 'super'"
          @checkForNewSuper()
          super o

        astType: ->
          if @isNew
            'NewExpression'
          else if @containsSoak()
            'OptionalCallExpression'
          else
            'CallExpression'

        astProperties: (o) ->
          return
            callee: @variable.ast o, LEVEL_ACCESS
            arguments: arg.ast(o, LEVEL_LIST) for arg in @args
            optional: !!@soak
            implicit: !!@implicit

- §

  ### Super

- §

  Takes care of converting `super()` calls into calls against the prototype’s function of the same name. When `expressions` are set the call will be compiled in such a way that the expressions are evaluated without altering the return value of the `SuperCall` expression.

      exports.SuperCall = class SuperCall extends Call
        children: Call::children.concat ['expressions']

        isStatement: (o) ->
          @expressions?.length and o.level is LEVEL_TOP

        compileNode: (o) ->
          return super o unless @expressions?.length

          superCall   = new Literal fragmentsToText super o
          replacement = new Block @expressions.slice()

          if o.level > LEVEL_TOP

- §

  If we might be in an expression we need to cache and return the result

            [superCall, ref] = superCall.cache o, null, YES
            replacement.push ref

          replacement.unshift superCall
          replacement.compileToFragments o, if o.level is LEVEL_TOP then o.level else LEVEL_LIST

      exports.Super = class Super extends Base
        constructor: (@accessor, @superLiteral) ->
          super()

        children: ['accessor']

        compileNode: (o) ->
          @checkInInstanceMethod o

          method = o.scope.namedMethod()
          unless method.ctor? or @accessor?
            {name, variable} = method
            if name.shouldCache() or (name instanceof Index and name.index.isAssignable())
              nref = new IdentifierLiteral o.scope.parent.freeVariable 'name'
              name.index = new Assign nref, name.index
            @accessor = if nref? then new Index nref else name

          if @accessor?.name?.comments

- §

  A `super()` call gets compiled to e.g. `super.method()`, which means the `method` property name gets compiled for the first time here, and again when the `method:` property of the class gets compiled. Since this compilation happens first, comments attached to `method:` would get incorrectly output near `super.method()`, when we want them to get output on the second pass when `method:` is output. So set them aside during this compilation pass, and put them back on the object so that they’re there for the later compilation.

            salvagedComments = @accessor.name.comments
            delete @accessor.name.comments
          fragments = (new Value (new Literal 'super'), if @accessor then [ @accessor ] else [])
          .compileToFragments o
          attachCommentsToNode salvagedComments, @accessor.name if salvagedComments
          fragments

        checkInInstanceMethod: (o) ->
          method = o.scope.namedMethod()
          @error 'cannot use super outside of an instance method' unless method?.isMethod

        astNode: (o) ->
          @checkInInstanceMethod o

          if @accessor?
            return (
              new Value(
                new Super().withLocationDataFrom (@superLiteral ? @)
                [@accessor]
              ).withLocationDataFrom @
            ).ast o

          super o

- §

  ### RegexWithInterpolations

- §

  Regexes with interpolations are in fact just a variation of a `Call` (a `RegExp()` call to be precise) with a `StringWithInterpolations` inside.

      exports.RegexWithInterpolations = class RegexWithInterpolations extends Base
        constructor: (@call, {@heregexCommentTokens = []} = {}) ->
          super()

        children: ['call']

        compileNode: (o) ->
          @call.compileNode o

        astType: -> 'InterpolatedRegExpLiteral'

        astProperties: (o) ->
          interpolatedPattern: @call.args[0].ast o
          flags: @call.args[1]?.unwrap().originalValue ? ''
          comments:
            for heregexCommentToken in @heregexCommentTokens
              if heregexCommentToken.here
                new HereComment(heregexCommentToken).ast o
              else
                new LineComment(heregexCommentToken).ast o

- §

  ### TaggedTemplateCall

      exports.TaggedTemplateCall = class TaggedTemplateCall extends Call
        constructor: (variable, arg, soak) ->
          arg = StringWithInterpolations.fromStringLiteral arg if arg instanceof StringLiteral
          super variable, [ arg ], soak

        compileNode: (o) ->
          @variable.compileToFragments(o, LEVEL_ACCESS).concat @args[0].compileToFragments(o, LEVEL_LIST)

        astType: -> 'TaggedTemplateExpression'

        astProperties: (o) ->
          return
            tag: @variable.ast o, LEVEL_ACCESS
            quasi: @args[0].ast o, LEVEL_LIST

- §

  ### Extends

- §

  Node to extend an object’s prototype with an ancestor object. After `goog.inherits` from the Closure Library.

      exports.Extends = class Extends extends Base
        constructor: (@child, @parent) ->
          super()

        children: ['child', 'parent']

- §

  Hooks one constructor into another’s prototype chain.

        compileToFragments: (o) ->
          new Call(new Value(new Literal utility 'extend', o), [@child, @parent]).compileToFragments o

- §

  ### Access

- §

  A `.` access into a property of a value, or the `::` shorthand for an access into the object’s prototype.

      exports.Access = class Access extends Base
        constructor: (@name, {@soak, @shorthand} = {}) ->
          super()

        children: ['name']

        compileToFragments: (o) ->
          name = @name.compileToFragments o
          node = @name.unwrap()
          if node instanceof PropertyName
            [@makeCode('.'), name...]
          else
            [@makeCode('['), name..., @makeCode(']')]

        shouldCache: NO

        astNode: (o) ->

- §

  Babel doesn’t have an AST node for `Access`, but rather just includes this Access node’s child `name` Identifier node as the `property` of the `MemberExpression` node.

          @name.ast o

- §

  ### Index

- §

  A `[ ... ]` indexed access into an array or object.

      exports.Index = class Index extends Base
        constructor: (@index) ->
          super()

        children: ['index']

        compileToFragments: (o) ->
          [].concat @makeCode("["), @index.compileToFragments(o, LEVEL_PAREN), @makeCode("]")

        shouldCache: ->
          @index.shouldCache()

        astNode: (o) ->

- §

  Babel doesn’t have an AST node for `Index`, but rather just includes this Index node’s child `index` Identifier node as the `property` of the `MemberExpression` node. The fact that the `MemberExpression`’s `property` is an Index means that `computed` is `true` for the `MemberExpression`.

          @index.ast o

- §

  ### Range

- §

  A range literal. Ranges can be used to extract portions (slices) of arrays, to specify a range for comprehensions, or as a value, to be expanded into the corresponding array of integers at runtime.

      exports.Range = class Range extends Base

        children: ['from', 'to']

        constructor: (@from, @to, tag) ->
          super()

          @exclusive = tag is 'exclusive'
          @equals = if @exclusive then '' else '='

- §

  Compiles the range’s source variables – where it starts and where it ends. But only if they need to be cached to avoid double evaluation.

        compileVariables: (o) ->
          o = merge o, top: true
          shouldCache = del o, 'shouldCache'
          [@fromC, @fromVar] = @cacheToCodeFragments @from.cache o, LEVEL_LIST, shouldCache
          [@toC, @toVar]     = @cacheToCodeFragments @to.cache o, LEVEL_LIST, shouldCache
          [@step, @stepVar]  = @cacheToCodeFragments step.cache o, LEVEL_LIST, shouldCache if step = del o, 'step'
          @fromNum = if @from.isNumber() then parseNumber @fromVar else null
          @toNum   = if @to.isNumber()   then parseNumber @toVar   else null
          @stepNum = if step?.isNumber() then parseNumber @stepVar else null

- §

  When compiled normally, the range returns the contents of the for loop needed to iterate over the values in the range. Used by comprehensions.

        compileNode: (o) ->
          @compileVariables o unless @fromVar
          return @compileArray(o) unless o.index

- §

  Set up endpoints.

          known    = @fromNum? and @toNum?
          idx      = del o, 'index'
          idxName  = del o, 'name'
          namedIndex = idxName and idxName isnt idx
          varPart  =
            if known and not namedIndex
              "var #{idx} = #{@fromC}"
            else
              "#{idx} = #{@fromC}"
          varPart += ", #{@toC}" if @toC isnt @toVar
          varPart += ", #{@step}" if @step isnt @stepVar
          [lt, gt] = ["#{idx} <#{@equals}", "#{idx} >#{@equals}"]

- §

  Generate the condition.

          [from, to] = [@fromNum, @toNum]

- §

  Always check if the `step` isn’t zero to avoid the infinite loop.

          stepNotZero = "#{ @stepNum ? @stepVar } !== 0"
          stepCond = "#{ @stepNum ? @stepVar } > 0"
          lowerBound = "#{lt} #{ if known then to else @toVar }"
          upperBound = "#{gt} #{ if known then to else @toVar }"
          condPart =
            if @step?
              if @stepNum? and @stepNum isnt 0
                if @stepNum > 0 then "#{lowerBound}" else "#{upperBound}"
              else
                "#{stepNotZero} && (#{stepCond} ? #{lowerBound} : #{upperBound})"
            else
              if known
                "#{ if from <= to then lt else gt } #{to}"
              else
                "(#{@fromVar} <= #{@toVar} ? #{lowerBound} : #{upperBound})"

          cond = if @stepVar then "#{@stepVar} > 0" else "#{@fromVar} <= #{@toVar}"

- §

  Generate the step.

          stepPart = if @stepVar
            "#{idx} += #{@stepVar}"
          else if known
            if namedIndex
              if from <= to then "++#{idx}" else "--#{idx}"
            else
              if from <= to then "#{idx}++" else "#{idx}--"
          else
            if namedIndex
              "#{cond} ? ++#{idx} : --#{idx}"
            else
              "#{cond} ? #{idx}++ : #{idx}--"

          varPart  = "#{idxName} = #{varPart}" if namedIndex
          stepPart = "#{idxName} = #{stepPart}" if namedIndex

- §

  The final loop body.

          [@makeCode "#{varPart}; #{condPart}; #{stepPart}"]

- §

  When used as a value, expand the range into the equivalent array.

        compileArray: (o) ->
          known = @fromNum? and @toNum?
          if known and Math.abs(@fromNum - @toNum) <= 20
            range = [@fromNum..@toNum]
            range.pop() if @exclusive
            return [@makeCode "[#{ range.join(', ') }]"]
          idt    = @tab + TAB
          i      = o.scope.freeVariable 'i', single: true, reserve: no
          result = o.scope.freeVariable 'results', reserve: no
          pre    = "\n#{idt}var #{result} = [];"
          if known
            o.index = i
            body    = fragmentsToText @compileNode o
          else
            vars    = "#{i} = #{@fromC}" + if @toC isnt @toVar then ", #{@toC}" else ''
            cond    = "#{@fromVar} <= #{@toVar}"
            body    = "var #{vars}; #{cond} ? #{i} <#{@equals} #{@toVar} : #{i} >#{@equals} #{@toVar}; #{cond} ? #{i}++ : #{i}--"
          post   = "{ #{result}.push(#{i}); }\n#{idt}return #{result};\n#{o.indent}"
          hasArgs = (node) -> node?.contains isLiteralArguments
          args   = ', arguments' if hasArgs(@from) or hasArgs(@to)
          [@makeCode "(function() {#{pre}\n#{idt}for (#{body})#{post}}).apply(this#{args ? ''})"]

        astProperties: (o) ->
          return {
            from: @from?.ast(o) ? null
            to: @to?.ast(o) ? null
            @exclusive
          }

- §

  ### Slice

- §

  An array slice literal. Unlike JavaScript’s `Array#slice`, the second parameter specifies the index of the end of the slice, just as the first parameter is the index of the beginning.

      exports.Slice = class Slice extends Base

        children: ['range']

        constructor: (@range) ->
          super()

- §

  We have to be careful when trying to slice through the end of the array, `9e9` is used because not all implementations respect `undefined` or `1/0`. `9e9` should be safe because `9e9` > `2**32`, the max array length.

        compileNode: (o) ->
          {to, from} = @range

- §

  Handle an expression in the property access, e.g. `a[!b in c..]`.

          if from?.shouldCache()
            from = new Value new Parens from
          if to?.shouldCache()
            to = new Value new Parens to
          fromCompiled = from?.compileToFragments(o, LEVEL_PAREN) or [@makeCode '0']
          if to
            compiled     = to.compileToFragments o, LEVEL_PAREN
            compiledText = fragmentsToText compiled
            if not (not @range.exclusive and +compiledText is -1)
              toStr = ', ' + if @range.exclusive
                compiledText
              else if to.isNumber()
                "#{+compiledText + 1}"
              else
                compiled = to.compileToFragments o, LEVEL_ACCESS
                "+#{fragmentsToText compiled} + 1 || 9e9"
          [@makeCode ".slice(#{ fragmentsToText fromCompiled }#{ toStr or '' })"]

        astNode: (o) ->
          @range.ast o

- §

  ### Obj

- §

  An object literal, nothing fancy.

      exports.Obj = class Obj extends Base
        constructor: (props, @generated = no) ->
          super()

          @objects = @properties = props or []

        children: ['properties']

        isAssignable: (opts) ->
          for prop in @properties

- §

  Check for reserved words.

            message = isUnassignable prop.unwrapAll().value
            prop.error message if message

            prop = prop.value if prop instanceof Assign and
              prop.context is 'object' and
              prop.value?.base not instanceof Arr
            return no unless prop.isAssignable opts
          yes

        shouldCache: ->
          not @isAssignable()

- §

  Check if object contains splat.

        hasSplat: ->
          return yes for prop in @properties when prop instanceof Splat
          no

- §

  Move rest property to the end of the list. `{a, rest..., b} = obj` -> `{a, b, rest...} = obj` `foo = ({a, rest..., b}) ->` -> `foo = {a, b, rest...}) ->`

        reorderProperties: ->
          props = @properties
          splatProps = @getAndCheckSplatProps()
          splatProp = props.splice splatProps[0], 1
          @objects = @properties = [].concat props, splatProp

        compileNode: (o) ->
          @reorderProperties() if @hasSplat() and @lhs
          props = @properties
          if @generated
            for node in props when node instanceof Value
              node.error 'cannot have an implicit value in an implicit object'

          idt      = o.indent += TAB
          lastNode = @lastNode @properties

- §

  If this object is the left-hand side of an assignment, all its children are too.

          @propagateLhs()

          isCompact = yes
          for prop in @properties
            if prop instanceof Assign and prop.context is 'object'
              isCompact = no

          answer = []
          answer.push @makeCode if isCompact then '' else '\n'
          for prop, i in props
            join = if i is props.length - 1
              ''
            else if isCompact
              ', '
            else if prop is lastNode
              '\n'
            else
              ',\n'
            indent = if isCompact then '' else idt

            key = if prop instanceof Assign and prop.context is 'object'
              prop.variable
            else if prop instanceof Assign
              prop.operatorToken.error "unexpected #{prop.operatorToken.value}" unless @lhs
              prop.variable
            else
              prop
            if key instanceof Value and key.hasProperties()
              key.error 'invalid object key' if prop.context is 'object' or not key.this
              key  = key.properties[0].name
              prop = new Assign key, prop, 'object'
            if key is prop
              if prop.shouldCache()
                [key, value] = prop.base.cache o
                key  = new PropertyName key.value if key instanceof IdentifierLiteral
                prop = new Assign key, value, 'object'
              else if key instanceof Value and key.base instanceof ComputedPropertyName

- §

  `{ [foo()] }` output as `{ [ref = foo()]: ref }`.

                if prop.base.value.shouldCache()
                  [key, value] = prop.base.value.cache o
                  key  = new ComputedPropertyName key.value if key instanceof IdentifierLiteral
                  prop = new Assign key, value, 'object'
                else

- §

  `{ [expression] }` output as `{ [expression]: expression }`.

                  prop = new Assign key, prop.base.value, 'object'
              else if not prop.bareLiteral?(IdentifierLiteral) and prop not instanceof Splat
                prop = new Assign prop, prop, 'object'
            if indent then answer.push @makeCode indent
            answer.push prop.compileToFragments(o, LEVEL_TOP)...
            if join then answer.push @makeCode join
          answer.push @makeCode if isCompact then '' else "\n#{@tab}"
          answer = @wrapInBraces answer
          if @front then @wrapInParentheses answer else answer

        getAndCheckSplatProps: ->
          return unless @hasSplat() and @lhs
          props = @properties
          splatProps = (i for prop, i in props when prop instanceof Splat)
          props[splatProps[1]].error "multiple spread elements are disallowed" if splatProps?.length > 1
          splatProps

        assigns: (name) ->
          for prop in @properties when prop.assigns name then return yes
          no

        eachName: (iterator) ->
          for prop in @properties
            prop = prop.value if prop instanceof Assign and prop.context is 'object'
            prop = prop.unwrapAll()
            prop.eachName iterator if prop.eachName?

- §

  Convert “bare” properties to `ObjectProperty`s (or `Splat`s).

        expandProperty: (property) ->
          {variable, context, operatorToken} = property
          key = if property instanceof Assign and context is 'object'
            variable
          else if property instanceof Assign
            operatorToken.error "unexpected #{operatorToken.value}" unless @lhs
            variable
          else
            property
          if key instanceof Value and key.hasProperties()
            key.error 'invalid object key' unless context isnt 'object' and key.this
            if property instanceof Assign
              return new ObjectProperty fromAssign: property
            else
              return new ObjectProperty key: property
          return new ObjectProperty(fromAssign: property) unless key is property
          return property if property instanceof Splat

          new ObjectProperty key: property

        expandProperties: ->
          @expandProperty(property) for property in @properties

        propagateLhs: (setLhs) ->
          @lhs = yes if setLhs
          return unless @lhs

          for property in @properties
            if property instanceof Assign and property.context is 'object'
              {value} = property
              unwrappedValue = value.unwrapAll()
              if unwrappedValue instanceof Arr or unwrappedValue instanceof Obj
                unwrappedValue.propagateLhs yes
              else if unwrappedValue instanceof Assign
                unwrappedValue.nestedLhs = yes
            else if property instanceof Assign

- §

  Shorthand property with default, e.g. `{a = 1} = b`.

              property.nestedLhs = yes
            else if property instanceof Splat
              property.propagateLhs yes

        astNode: (o) ->
          @getAndCheckSplatProps()
          super o

        astType: ->
          if @lhs
            'ObjectPattern'
          else
            'ObjectExpression'

        astProperties: (o) ->
          return
            implicit: !!@generated
            properties:
              property.ast(o) for property in @expandProperties()

      exports.ObjectProperty = class ObjectProperty extends Base
        constructor: ({key, fromAssign}) ->
          super()
          if fromAssign
            {variable: @key, value, context} = fromAssign
            if context is 'object'

- §

  All non-shorthand properties (i.e. includes `:`).

              @value = value
            else

- §

  Left-hand-side shorthand with default e.g. `{a = 1} = b`.

              @value = fromAssign
              @shorthand = yes
            @locationData = fromAssign.locationData
          else

- §

  Shorthand without default e.g. `{a}` or `{@a}` or `{[a]}`.

            @key = key
            @shorthand = yes
            @locationData = key.locationData

        astProperties: (o) ->
          isComputedPropertyName = (@key instanceof Value and @key.base instanceof ComputedPropertyName) or @key.unwrap() instanceof StringWithInterpolations
          keyAst = @key.ast o, LEVEL_LIST

          return
            key:
              if keyAst?.declaration
                Object.assign {}, keyAst, declaration: no
              else
                keyAst
            value: @value?.ast(o, LEVEL_LIST) ? keyAst
            shorthand: !!@shorthand
            computed: !!isComputedPropertyName
            method: no

- §

  ### Arr

- §

  An array literal.

      exports.Arr = class Arr extends Base
        constructor: (objs, @lhs = no) ->
          super()
          @objects = objs or []
          @propagateLhs()

        children: ['objects']

        hasElision: ->
          return yes for obj in @objects when obj instanceof Elision
          no

        isAssignable: (opts) ->
          {allowExpansion, allowNontrailingSplat, allowEmptyArray = no} = opts ? {}
          return allowEmptyArray unless @objects.length

          for obj, i in @objects
            return no if not allowNontrailingSplat and obj instanceof Splat and i + 1 isnt @objects.length
            return no unless (allowExpansion and obj instanceof Expansion) or (obj.isAssignable(opts) and (not obj.isAtomic or obj.isAtomic()))
          yes

        shouldCache: ->
          not @isAssignable()

        compileNode: (o) ->
          return [@makeCode '[]'] unless @objects.length
          o.indent += TAB
          fragmentIsElision = ([ fragment ]) ->
            fragment.type is 'Elision' and fragment.code.trim() is ','

- §

  Detect if `Elision`s at the beginning of the array are processed (e.g. [, , , a]).

          passedElision = no

          answer = []
          for obj, objIndex in @objects
            unwrappedObj = obj.unwrapAll()

- §

  Let `compileCommentFragments` know to intersperse block comments into the fragments created when compiling this array.

            if unwrappedObj.comments and
               unwrappedObj.comments.filter((comment) -> not comment.here).length is 0
              unwrappedObj.includeCommentFragments = YES

          compiledObjs = (obj.compileToFragments o, LEVEL_LIST for obj in @objects)
          olen = compiledObjs.length

- §

  If `compiledObjs` includes newlines, we will output this as a multiline array (i.e. with a newline and indentation after the `[`). If an element contains line comments, that should also trigger multiline output since by definition line comments will introduce newlines into our output. The exception is if only the first element has line comments; in that case, output as the compact form if we otherwise would have, so that the first element’s line comments get output before or after the array.

          includesLineCommentsOnNonFirstElement = no
          for fragments, index in compiledObjs
            for fragment in fragments
              if fragment.isHereComment
                fragment.code = fragment.code.trim()
              else if index isnt 0 and includesLineCommentsOnNonFirstElement is no and hasLineComments fragment
                includesLineCommentsOnNonFirstElement = yes

- §

  Add ‘, ‘ if all `Elisions` from the beginning of the array are processed (e.g. [, , , a]) and element isn’t `Elision` or last element is `Elision` (e.g. [a,,b,,])

            if index isnt 0 and passedElision and (not fragmentIsElision(fragments) or index is olen - 1)
              answer.push @makeCode ', '
            passedElision = passedElision or not fragmentIsElision fragments
            answer.push fragments...
          if includesLineCommentsOnNonFirstElement or '\n' in fragmentsToText(answer)
            for fragment, fragmentIndex in answer
              if fragment.isHereComment
                fragment.code = "#{multident(fragment.code, o.indent, no)}\n#{o.indent}"
              else if fragment.code is ', ' and not fragment?.isElision and fragment.type not in ['StringLiteral', 'StringWithInterpolations']
                fragment.code = ",\n#{o.indent}"
            answer.unshift @makeCode "[\n#{o.indent}"
            answer.push @makeCode "\n#{@tab}]"
          else
            for fragment in answer when fragment.isHereComment
              fragment.code = "#{fragment.code} "
            answer.unshift @makeCode '['
            answer.push @makeCode ']'
          answer

        assigns: (name) ->
          for obj in @objects when obj.assigns name then return yes
          no

        eachName: (iterator) ->
          for obj in @objects
            obj = obj.unwrapAll()
            obj.eachName iterator

- §

  If this array is the left-hand side of an assignment, all its children are too.

        propagateLhs: (setLhs) ->
          @lhs = yes if setLhs
          return unless @lhs
          for object in @objects
            object.lhs = yes if object instanceof Splat or object instanceof Expansion
            unwrappedObject = object.unwrapAll()
            if unwrappedObject instanceof Arr or unwrappedObject instanceof Obj
              unwrappedObject.propagateLhs yes
            else if unwrappedObject instanceof Assign
              unwrappedObject.nestedLhs = yes

        astType: ->
          if @lhs
            'ArrayPattern'
          else
            'ArrayExpression'

        astProperties: (o) ->
          return
            elements:
              object.ast(o, LEVEL_LIST) for object in @objects

- §

  ### Class

- §

  The CoffeeScript class definition. Initialize a Class with its name, an optional superclass, and a body.

      exports.Class = class Class extends Base
        children: ['variable', 'parent', 'body']

        constructor: (@variable, @parent, @body) ->
          super()
          unless @body?
            @body = new Block
            @hasGeneratedBody = yes

        compileNode: (o) ->
          @name          = @determineName()
          executableBody = @walkBody o

- §

  Special handling to allow `class expr.A extends A` declarations

          parentName    = @parent.base.value if @parent instanceof Value and not @parent.hasProperties()
          @hasNameClash = @name? and @name is parentName

          node = @

          if executableBody or @hasNameClash
            node = new ExecutableClassBody node, executableBody
          else if not @name? and o.level is LEVEL_TOP

- §

  Anonymous classes are only valid in expressions

            node = new Parens node

          if @boundMethods.length and @parent
            @variable ?= new IdentifierLiteral o.scope.freeVariable '_class'
            [@variable, @variableRef] = @variable.cache o unless @variableRef?

          if @variable
            node = new Assign @variable, node, null, { @moduleDeclaration }

          @compileNode = @compileClassDeclaration
          try
            return node.compileToFragments o
          finally
            delete @compileNode

        compileClassDeclaration: (o) ->
          @ctor ?= @makeDefaultConstructor() if @externalCtor or @boundMethods.length
          @ctor?.noReturn = true

          @proxyBoundMethods() if @boundMethods.length

          o.indent += TAB

          result = []
          result.push @makeCode "class "
          result.push @makeCode @name if @name
          @compileCommentFragments o, @variable, result if @variable?.comments?
          result.push @makeCode ' ' if @name
          result.push @makeCode('extends '), @parent.compileToFragments(o)..., @makeCode ' ' if @parent

          result.push @makeCode '{'
          unless @body.isEmpty()
            @body.spaced = true
            result.push @makeCode '\n'
            result.push @body.compileToFragments(o, LEVEL_TOP)...
            result.push @makeCode "\n#{@tab}"
          result.push @makeCode '}'

          result

- §

  Figure out the appropriate name for this class

        determineName: ->
          return null unless @variable
          [..., tail] = @variable.properties
          node = if tail
            tail instanceof Access and tail.name
          else
            @variable.base
          unless node instanceof IdentifierLiteral or node instanceof PropertyName
            return null
          name = node.value
          unless tail
            message = isUnassignable name
            @variable.error message if message
          if name in JS_FORBIDDEN then "_#{name}" else name

        walkBody: (o) ->
          @ctor          = null
          @boundMethods  = []
          executableBody = null

          initializer     = []
          { expressions } = @body

          i = 0
          for expression in expressions.slice()
            if expression instanceof Value and expression.isObject true
              { properties } = expression.base
              exprs     = []
              end       = 0
              start     = 0
              pushSlice = -> exprs.push new Value new Obj properties[start...end], true if end > start

              while assign = properties[end]
                if initializerExpression = @addInitializerExpression assign, o
                  pushSlice()
                  exprs.push initializerExpression
                  initializer.push initializerExpression
                  start = end + 1
                end++
              pushSlice()

              expressions[i..i] = exprs
              i += exprs.length
            else
              if initializerExpression = @addInitializerExpression expression, o
                initializer.push initializerExpression
                expressions[i] = initializerExpression
              i += 1

          for method in initializer when method instanceof Code
            if method.ctor
              method.error 'Cannot define more than one constructor in a class' if @ctor
              @ctor = method
            else if method.isStatic and method.bound
              method.context = @name
            else if method.bound
              @boundMethods.push method

          return unless o.compiling
          if initializer.length isnt expressions.length
            @body.expressions = (expression.hoist() for expression in initializer)
            new Block expressions

- §

  Add an expression to the class initializer

  This is the key method for determining whether an expression in a class body should appear in the initializer or the executable body. If the given `node` is valid in a class body the method will return a (new, modified, or identical) node for inclusion in the class initializer, otherwise nothing will be returned and the node will appear in the executable body.

  At time of writing, only methods (instance and static) are valid in ES class initializers. As new ES class features (such as class fields) reach Stage 4, this method will need to be updated to support them. We additionally allow `PassthroughLiteral`s (backticked expressions) in the initializer as an escape hatch for ES features that are not implemented (e.g. getters and setters defined via the `get` and `set` keywords as opposed to the `Object.defineProperty` method).

        addInitializerExpression: (node, o) ->
          if node.unwrapAll() instanceof PassthroughLiteral
            node
          else if @validInitializerMethod node
            @addInitializerMethod node
          else if not o.compiling and @validClassProperty node
            @addClassProperty node
          else if not o.compiling and @validClassPrototypeProperty node
            @addClassPrototypeProperty node
          else
            null

- §

  Checks if the given node is a valid ES class initializer method.

        validInitializerMethod: (node) ->
          return no unless node instanceof Assign and node.value instanceof Code
          return yes if node.context is 'object' and not node.variable.hasProperties()
          return node.variable.looksStatic(@name) and (@name or not node.value.bound)

- §

  Returns a configured class initializer method

        addInitializerMethod: (assign) ->
          { variable, value: method, operatorToken } = assign
          method.isMethod = yes
          method.isStatic = variable.looksStatic @name

          if method.isStatic
            method.name = variable.properties[0]
          else
            methodName  = variable.base
            method.name = new (if methodName.shouldCache() then Index else Access) methodName
            method.name.updateLocationDataIfMissing methodName.locationData
            isConstructor =
              if methodName instanceof StringLiteral
                methodName.originalValue is 'constructor'
              else
                methodName.value is 'constructor'
            method.ctor = (if @parent then 'derived' else 'base') if isConstructor
            method.error 'Cannot define a constructor as a bound (fat arrow) function' if method.bound and method.ctor

          method.operatorToken = operatorToken
          method

        validClassProperty: (node) ->
          return no unless node instanceof Assign
          return node.variable.looksStatic @name

        addClassProperty: (assign) ->
          {variable, value, operatorToken} = assign
          {staticClassName} = variable.looksStatic @name
          new ClassProperty({
            name: variable.properties[0]
            isStatic: yes
            staticClassName
            value
            operatorToken
          }).withLocationDataFrom assign

        validClassPrototypeProperty: (node) ->
          return no unless node instanceof Assign
          node.context is 'object' and not node.variable.hasProperties()

        addClassPrototypeProperty: (assign) ->
          {variable, value} = assign
          new ClassPrototypeProperty({
            name: variable.base
            value
          }).withLocationDataFrom assign

        makeDefaultConstructor: ->
          ctor = @addInitializerMethod new Assign (new Value new PropertyName 'constructor'), new Code
          @body.unshift ctor

          if @parent
            ctor.body.push new SuperCall new Super, [new Splat new IdentifierLiteral 'arguments']

          if @externalCtor
            applyCtor = new Value @externalCtor, [ new Access new PropertyName 'apply' ]
            applyArgs = [ new ThisLiteral, new IdentifierLiteral 'arguments' ]
            ctor.body.push new Call applyCtor, applyArgs
            ctor.body.makeReturn()

          ctor

        proxyBoundMethods: ->
          @ctor.thisAssignments = for method in @boundMethods
            method.classVariable = @variableRef if @parent

            name = new Value(new ThisLiteral, [ method.name ])
            new Assign name, new Call(new Value(name, [new Access new PropertyName 'bind']), [new ThisLiteral])

          null

        declareName: (o) ->
          return unless (name = @variable?.unwrap()) instanceof IdentifierLiteral
          alreadyDeclared = o.scope.find name.value
          name.isDeclaration = not alreadyDeclared

        isStatementAst: -> yes

        astNode: (o) ->
          if jumpNode = @body.jumps()
            jumpNode.error 'Class bodies cannot contain pure statements'
          if argumentsNode = @body.contains isLiteralArguments
            argumentsNode.error "Class bodies shouldn't reference arguments"
          @declareName o
          @name = @determineName()
          @body.isClassBody = yes
          @body.locationData = zeroWidthLocationDataFromEndLocation @locationData if @hasGeneratedBody
          @walkBody o
          sniffDirectives @body.expressions
          @ctor?.noReturn = yes

          super o

        astType: (o) ->
          if o.level is LEVEL_TOP
            'ClassDeclaration'
          else
            'ClassExpression'

        astProperties: (o) ->
          return
            id: @variable?.ast(o) ? null
            superClass: @parent?.ast(o, LEVEL_PAREN) ? null
            body: @body.ast o, LEVEL_TOP

      exports.ExecutableClassBody = class ExecutableClassBody extends Base
        children: [ 'class', 'body' ]

        defaultClassVariableName: '_Class'

        constructor: (@class, @body = new Block) ->
          super()

        compileNode: (o) ->
          if jumpNode = @body.jumps()
            jumpNode.error 'Class bodies cannot contain pure statements'
          if argumentsNode = @body.contains isLiteralArguments
            argumentsNode.error "Class bodies shouldn't reference arguments"

          params  = []
          args    = [new ThisLiteral]
          wrapper = new Code params, @body
          klass   = new Parens new Call (new Value wrapper, [new Access new PropertyName 'call']), args

          @body.spaced = true

          o.classScope = wrapper.makeScope o.scope

          @name      = @class.name ? o.classScope.freeVariable @defaultClassVariableName
          ident      = new IdentifierLiteral @name
          directives = @walkBody()
          @setContext()

          if @class.hasNameClash
            parent = new IdentifierLiteral o.classScope.freeVariable 'superClass'
            wrapper.params.push new Param parent
            args.push @class.parent
            @class.parent = parent

          if @externalCtor
            externalCtor = new IdentifierLiteral o.classScope.freeVariable 'ctor', reserve: no
            @class.externalCtor = externalCtor
            @externalCtor.variable.base = externalCtor

          if @name isnt @class.name
            @body.expressions.unshift new Assign (new IdentifierLiteral @name), @class
          else
            @body.expressions.unshift @class
          @body.expressions.unshift directives...
          @body.push ident

          klass.compileToFragments o

- §

  Traverse the class’s children and:

  - Hoist valid ES properties into `@properties`
  - Hoist static assignments into `@properties`
  - Convert invalid ES properties into class or prototype assignments

        walkBody: ->
          directives  = []

          index = 0
          while expr = @body.expressions[index]
            break unless expr instanceof Value and expr.isString()
            if expr.hoisted
              index++
            else
              directives.push @body.expressions.splice(index, 1)...

          @traverseChildren false, (child) =>
            return false if child instanceof Class or child instanceof HoistTarget

            cont = true
            if child instanceof Block
              for node, i in child.expressions
                if node instanceof Value and node.isObject(true)
                  cont = false
                  child.expressions[i] = @addProperties node.base.properties
                else if node instanceof Assign and node.variable.looksStatic @name
                  node.value.isStatic = yes
              child.expressions = flatten child.expressions
            cont

          directives

        setContext: ->
          @body.traverseChildren false, (node) =>
            if node instanceof ThisLiteral
              node.value   = @name
            else if node instanceof Code and node.bound and (node.isStatic or not node.name)
              node.context = @name

- §

  Make class/prototype assignments for invalid ES properties

        addProperties: (assigns) ->
          result = for assign in assigns
            variable = assign.variable
            base     = variable?.base
            value    = assign.value
            delete assign.context

            if base.value is 'constructor'
              if value instanceof Code
                base.error 'constructors must be defined at the top level of a class body'

- §

  The class scope is not available yet, so return the assignment to update later

              assign = @externalCtor = new Assign new Value, value
            else if not assign.variable.this
              name =
                if base instanceof ComputedPropertyName
                  new Index base.value
                else
                  new (if base.shouldCache() then Index else Access) base
              prototype = new Access new PropertyName 'prototype'
              variable  = new Value new ThisLiteral(), [ prototype, name ]

              assign.variable = variable
            else if assign.value instanceof Code
              assign.value.isStatic = true

            assign
          compact result

      exports.ClassProperty = class ClassProperty extends Base
        constructor: ({@name, @isStatic, @staticClassName, @value, @operatorToken}) ->
          super()

        children: ['name', 'value', 'staticClassName']

        isStatement: YES

        astProperties: (o) ->
          return
            key: @name.ast o, LEVEL_LIST
            value: @value.ast o, LEVEL_LIST
            static: !!@isStatic
            computed: @name instanceof Index or @name instanceof ComputedPropertyName
            operator: @operatorToken?.value ? '='
            staticClassName: @staticClassName?.ast(o) ? null

      exports.ClassPrototypeProperty = class ClassPrototypeProperty extends Base
        constructor: ({@name, @value}) ->
          super()

        children: ['name', 'value']

        isStatement: YES

        astProperties: (o) ->
          return
            key: @name.ast o, LEVEL_LIST
            value: @value.ast o, LEVEL_LIST
            computed: @name instanceof ComputedPropertyName or @name instanceof StringWithInterpolations

- §

  ### Import and Export

      exports.ModuleDeclaration = class ModuleDeclaration extends Base
        constructor: (@clause, @source, @assertions) ->
          super()
          @checkSource()

        children: ['clause', 'source', 'assertions']

        isStatement: YES
        jumps:       THIS
        makeReturn:  THIS

        checkSource: ->
          if @source? and @source instanceof StringWithInterpolations
            @source.error 'the name of the module to be imported from must be an uninterpolated string'

        checkScope: (o, moduleDeclarationType) ->

- §

  TODO: would be appropriate to flag this error during AST generation (as well as when compiling to JS). But `o.indent` isn’t tracked during AST generation, and there doesn’t seem to be a current alternative way to track whether we’re at the “program top-level”.

          if o.indent.length isnt 0
            @error "#{moduleDeclarationType} statements must be at top-level scope"

        astAssertions: (o) ->
          if @assertions?.properties?
            @assertions.properties.map (assertion) =>
              { start, end, loc, left, right } = assertion.ast(o)
              { type: 'ImportAttribute', start, end, loc, key: left, value: right }
          else
            []

      exports.ImportDeclaration = class ImportDeclaration extends ModuleDeclaration
        compileNode: (o) ->
          @checkScope o, 'import'
          o.importedSymbols = []

          code = []
          code.push @makeCode "#{@tab}import "
          code.push @clause.compileNode(o)... if @clause?

          if @source?.value?
            code.push @makeCode ' from ' unless @clause is null
            code.push @makeCode @source.value
            if @assertions?
              code.push @makeCode ' assert '
              code.push @assertions.compileToFragments(o)...

          code.push @makeCode ';'
          code

        astNode: (o) ->
          o.importedSymbols = []
          super o

        astProperties: (o) ->
          ret =
            specifiers: @clause?.ast(o) ? []
            source: @source.ast o
            assertions: @astAssertions(o)
          ret.importKind = 'value' if @clause
          ret

      exports.ImportClause = class ImportClause extends Base
        constructor: (@defaultBinding, @namedImports) ->
          super()

        children: ['defaultBinding', 'namedImports']

        compileNode: (o) ->
          code = []

          if @defaultBinding?
            code.push @defaultBinding.compileNode(o)...
            code.push @makeCode ', ' if @namedImports?

          if @namedImports?
            code.push @namedImports.compileNode(o)...

          code

        astNode: (o) ->

- §

  The AST for `ImportClause` is the non-nested list of import specifiers that will be the `specifiers` property of an `ImportDeclaration` AST

          compact flatten [
            @defaultBinding?.ast o
            @namedImports?.ast o
          ]

      exports.ExportDeclaration = class ExportDeclaration extends ModuleDeclaration
        compileNode: (o) ->
          @checkScope o, 'export'
          @checkForAnonymousClassExport()

          code = []
          code.push @makeCode "#{@tab}export "
          code.push @makeCode 'default ' if @ instanceof ExportDefaultDeclaration

          if @ not instanceof ExportDefaultDeclaration and
             (@clause instanceof Assign or @clause instanceof Class)
            code.push @makeCode 'var '
            @clause.moduleDeclaration = 'export'

          if @clause.body? and @clause.body instanceof Block
            code = code.concat @clause.compileToFragments o, LEVEL_TOP
          else
            code = code.concat @clause.compileNode o

          if @source?.value?
            code.push @makeCode " from #{@source.value}"
            if @assertions?
              code.push @makeCode ' assert '
              code.push @assertions.compileToFragments(o)...

          code.push @makeCode ';'
          code

- §

  Prevent exporting an anonymous class; all exported members must be named

        checkForAnonymousClassExport: ->
          if @ not instanceof ExportDefaultDeclaration and @clause instanceof Class and not @clause.variable
            @clause.error 'anonymous classes cannot be exported'

        astNode: (o) ->
          @checkForAnonymousClassExport()
          super o

      exports.ExportNamedDeclaration = class ExportNamedDeclaration extends ExportDeclaration
        astProperties: (o) ->
          ret =
            source: @source?.ast(o) ? null
            assertions: @astAssertions(o)
            exportKind: 'value'
          clauseAst = @clause.ast o
          if @clause instanceof ExportSpecifierList
            ret.specifiers = clauseAst
            ret.declaration = null
          else
            ret.specifiers = []
            ret.declaration = clauseAst
          ret

      exports.ExportDefaultDeclaration = class ExportDefaultDeclaration extends ExportDeclaration
        astProperties: (o) ->
          return
            declaration: @clause.ast o
            assertions: @astAssertions(o)

      exports.ExportAllDeclaration = class ExportAllDeclaration extends ExportDeclaration
        astProperties: (o) ->
          return
            source: @source.ast o
            assertions: @astAssertions(o)
            exportKind: 'value'

      exports.ModuleSpecifierList = class ModuleSpecifierList extends Base
        constructor: (@specifiers) ->
          super()

        children: ['specifiers']

        compileNode: (o) ->
          code = []
          o.indent += TAB
          compiledList = (specifier.compileToFragments o, LEVEL_LIST for specifier in @specifiers)

          if @specifiers.length isnt 0
            code.push @makeCode "{\n#{o.indent}"
            for fragments, index in compiledList
              code.push @makeCode(",\n#{o.indent}") if index
              code.push fragments...
            code.push @makeCode "\n}"
          else
            code.push @makeCode '{}'
          code

        astNode: (o) ->
          specifier.ast(o) for specifier in @specifiers

      exports.ImportSpecifierList = class ImportSpecifierList extends ModuleSpecifierList

      exports.ExportSpecifierList = class ExportSpecifierList extends ModuleSpecifierList

      exports.ModuleSpecifier = class ModuleSpecifier extends Base
        constructor: (@original, @alias, @moduleDeclarationType) ->
          super()

          if @original.comments or @alias?.comments
            @comments = []
            @comments.push @original.comments... if @original.comments
            @comments.push @alias.comments...    if @alias?.comments

- §

  The name of the variable entering the local scope

          @identifier = if @alias? then @alias.value else @original.value

        children: ['original', 'alias']

        compileNode: (o) ->
          @addIdentifierToScope o
          code = []
          code.push @makeCode @original.value
          code.push @makeCode " as #{@alias.value}" if @alias?
          code

        addIdentifierToScope: (o) ->
          o.scope.find @identifier, @moduleDeclarationType

        astNode: (o) ->
          @addIdentifierToScope o
          super o

      exports.ImportSpecifier = class ImportSpecifier extends ModuleSpecifier
        constructor: (imported, local) ->
          super imported, local, 'import'

        addIdentifierToScope: (o) ->

- §

  Per the spec, symbols can’t be imported multiple times (e.g. `import { foo, foo } from 'lib'` is invalid)

          if @identifier in o.importedSymbols or o.scope.check(@identifier)
            @error "'#{@identifier}' has already been declared"
          else
            o.importedSymbols.push @identifier
          super o

        astProperties: (o) ->
          originalAst = @original.ast o
          return
            imported: originalAst
            local: @alias?.ast(o) ? originalAst
            importKind: null

      exports.ImportDefaultSpecifier = class ImportDefaultSpecifier extends ImportSpecifier
        astProperties: (o) ->
          return
            local: @original.ast o

      exports.ImportNamespaceSpecifier = class ImportNamespaceSpecifier extends ImportSpecifier
        astProperties: (o) ->
          return
            local: @alias.ast o

      exports.ExportSpecifier = class ExportSpecifier extends ModuleSpecifier
        constructor: (local, exported) ->
          super local, exported, 'export'

        astProperties: (o) ->
          originalAst = @original.ast o
          return
            local: originalAst
            exported: @alias?.ast(o) ? originalAst

      exports.DynamicImport = class DynamicImport extends Base
        compileNode: ->
          [@makeCode 'import']

        astType: -> 'Import'

      exports.DynamicImportCall = class DynamicImportCall extends Call
        compileNode: (o) ->
          @checkArguments()
          super o

        checkArguments: ->
          unless 1 <= @args.length <= 2
            @error 'import() accepts either one or two arguments'

        astNode: (o) ->
          @checkArguments()
          super o

- §

  ### Assign

- §

  The Assign is used to assign a local variable to value, or to set the property of an object – including within object literals.

      exports.Assign = class Assign extends Base
        constructor: (@variable, @value, @context, options = {}) ->
          super()
          {@param, @subpattern, @operatorToken, @moduleDeclaration, @originalContext = @context} = options
          @propagateLhs()

        children: ['variable', 'value']

        isAssignable: YES

        isStatement: (o) ->
          o?.level is LEVEL_TOP and @context? and (@moduleDeclaration or "?" in @context)

        checkNameAssignability: (o, varBase) ->
          if o.scope.type(varBase.value) is 'import'
            varBase.error "'#{varBase.value}' is read-only"

        assigns: (name) ->
          @[if @context is 'object' then 'value' else 'variable'].assigns name

        unfoldSoak: (o) ->
          unfoldSoak o, this, 'variable'

        addScopeVariables: (o, {

- §

  During AST generation, we need to allow assignment to these constructs that are considered “unassignable” during compile-to-JS, while still flagging things like `[null] = b`.

          allowAssignmentToExpansion = no,
          allowAssignmentToNontrailingSplat = no,
          allowAssignmentToEmptyArray = no,
          allowAssignmentToComplexSplat = no
        } = {}) ->
          return unless not @context or @context is '**='

          varBase = @variable.unwrapAll()
          if not varBase.isAssignable {
            allowExpansion: allowAssignmentToExpansion
            allowNontrailingSplat: allowAssignmentToNontrailingSplat
            allowEmptyArray: allowAssignmentToEmptyArray
            allowComplexSplat: allowAssignmentToComplexSplat
          }
            @variable.error "'#{@variable.compile o}' can't be assigned"

          varBase.eachName (name) =>
            return if name.hasProperties?()

            message = isUnassignable name.value
            name.error message if message

- §

  `moduleDeclaration` can be `'import'` or `'export'`.

            @checkNameAssignability o, name
            if @moduleDeclaration
              o.scope.add name.value, @moduleDeclaration
              name.isDeclaration = yes
            else if @param
              o.scope.add name.value,
                if @param is 'alwaysDeclare'
                  'var'
                else
                  'param'
            else
              alreadyDeclared = o.scope.find name.value
              name.isDeclaration ?= not alreadyDeclared

- §

  If this assignment identifier has one or more herecomments attached, output them as part of the declarations line (unless other herecomments are already staged there) for compatibility with Flow typing. Don’t do this if this assignment is for a class, e.g. `ClassName = class ClassName {`, as Flow requires the comment to be between the class name and the `{`.

              if name.comments and not o.scope.comments[name.value] and
                 @value not instanceof Class and
                 name.comments.every((comment) -> comment.here and not comment.multiline)
                commentsNode = new IdentifierLiteral name.value
                commentsNode.comments = name.comments
                commentFragments = []
                @compileCommentFragments o, commentsNode, commentFragments
                o.scope.comments[name.value] = commentFragments

- §

  Compile an assignment, delegating to `compileDestructuring` or `compileSplice` if appropriate. Keep track of the name of the base object we’ve been assigned to, for correct internal references. If the variable has not been seen yet within the current scope, declare it.

        compileNode: (o) ->
          isValue = @variable instanceof Value
          if isValue

- §

  If `@variable` is an array or an object, we’re destructuring; if it’s also `isAssignable()`, the destructuring syntax is supported in ES and we can output it as is; otherwise we `@compileDestructuring` and convert this ES-unsupported destructuring into acceptable output.

            if @variable.isArray() or @variable.isObject()
              unless @variable.isAssignable()
                if @variable.isObject() and @variable.base.hasSplat()
                  return @compileObjectDestruct o
                else
                  return @compileDestructuring o

            return @compileSplice       o if @variable.isSplice()
            return @compileConditional  o if @isConditional()
            return @compileSpecialMath  o if @context in ['//=', '%%=']

          @addScopeVariables o
          if @value instanceof Code
            if @value.isStatic
              @value.name = @variable.properties[0]
            else if @variable.properties?.length >= 2
              [properties..., prototype, name] = @variable.properties
              @value.name = name if prototype.name?.value is 'prototype'

          val = @value.compileToFragments o, LEVEL_LIST
          compiledName = @variable.compileToFragments o, LEVEL_LIST

          if @context is 'object'
            if @variable.shouldCache()
              compiledName.unshift @makeCode '['
              compiledName.push @makeCode ']'
            return compiledName.concat @makeCode(': '), val

          answer = compiledName.concat @makeCode(" #{ @context or '=' } "), val

- §

  Per https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#Assignment_without_declaration, if we’re destructuring without declaring, the destructuring assignment must be wrapped in parentheses. The assignment is wrapped in parentheses if ‘o.level’ has lower precedence than LEVEL_LIST (3) (i.e. LEVEL_COND (4), LEVEL_OP (5) or LEVEL_ACCESS (6)), or if we’re destructuring object, e.g. {a,b} = obj.

          if o.level > LEVEL_LIST or isValue and @variable.base instanceof Obj and not @nestedLhs and not (@param is yes)
            @wrapInParentheses answer
          else
            answer

- §

  Object rest property is not assignable: `{{a}...}`

        compileObjectDestruct: (o) ->
          @variable.base.reorderProperties()
          {properties: props} = @variable.base
          [..., splat] = props
          splatProp = splat.name
          assigns = []
          refVal = new Value new IdentifierLiteral o.scope.freeVariable 'ref'
          props.splice -1, 1, new Splat refVal
          assigns.push new Assign(new Value(new Obj props), @value).compileToFragments o, LEVEL_LIST
          assigns.push new Assign(new Value(splatProp), refVal).compileToFragments o, LEVEL_LIST
          @joinFragmentArrays assigns, ', '

- §

  Brief implementation of recursive pattern matching, when assigning array or object literals to a value. Peeks at their properties to assign inner names.

        compileDestructuring: (o) ->
          top       = o.level is LEVEL_TOP
          {value}   = this
          {objects} = @variable.base
          olen      = objects.length

- §

  Special-case for `{} = a` and `[] = a` (empty patterns). Compile to simply `a`.

          if olen is 0
            code = value.compileToFragments o
            return if o.level >= LEVEL_OP then @wrapInParentheses code else code
          [obj] = objects

          @disallowLoneExpansion()
          {splats, expans, splatsAndExpans} = @getAndCheckSplatsAndExpansions()

          isSplat = splats?.length > 0
          isExpans = expans?.length > 0

          vvar     = value.compileToFragments o, LEVEL_LIST
          vvarText = fragmentsToText vvar
          assigns  = []
          pushAssign = (variable, val) =>
            assigns.push new Assign(variable, val, null, param: @param, subpattern: yes).compileToFragments o, LEVEL_LIST

          if isSplat
            splatVar = objects[splats[0]].name.unwrap()
            if splatVar instanceof Arr or splatVar instanceof Obj
              splatVarRef = new IdentifierLiteral o.scope.freeVariable 'ref'
              objects[splats[0]].name = splatVarRef
              splatVarAssign = -> pushAssign new Value(splatVar), splatVarRef

- §

  At this point, there are several things to destructure. So the `fn()` in `{a, b} = fn()` must be cached, for example. Make vvar into a simple variable if it isn’t already.

          if value.unwrap() not instanceof IdentifierLiteral or @variable.assigns(vvarText)
            ref = o.scope.freeVariable 'ref'
            assigns.push [@makeCode(ref + ' = '), vvar...]
            vvar = [@makeCode ref]
            vvarText = ref

          slicer = (type) -> (vvar, start, end = no) ->
            vvar = new IdentifierLiteral vvar unless vvar instanceof Value
            args = [vvar, new NumberLiteral(start)]
            args.push new NumberLiteral end if end
            slice = new Value (new IdentifierLiteral utility type, o), [new Access new PropertyName 'call']
            new Value new Call slice, args

- §

  Helper which outputs `[].slice` code.

          compSlice = slicer "slice"

- §

  Helper which outputs `[].splice` code.

          compSplice = slicer "splice"

- §

  Check if `objects` array contains any instance of `Assign`, e.g. {a:1}.

          hasObjAssigns = (objs) ->
            (i for obj, i in objs when obj instanceof Assign and obj.context is 'object')

- §

  Check if `objects` array contains any unassignable object.

          objIsUnassignable = (objs) ->
            return yes for obj in objs when not obj.isAssignable()
            no

- §

  `objects` are complex when there is object assign ({a:1}), unassignable object, or just a single node.

          complexObjects = (objs) ->
            hasObjAssigns(objs).length or objIsUnassignable(objs) or olen is 1

- §

  “Complex” `objects` are processed in a loop. Examples: [a, b, {c, r…}, d], [a, …, {b, r…}, c, d]

          loopObjects = (objs, vvar, vvarTxt) =>
            for obj, i in objs

- §

  `Elision` can be skipped.

              continue if obj instanceof Elision

- §

  If `obj` is {a: 1}

              if obj instanceof Assign and obj.context is 'object'
                {variable: {base: idx}, value: vvar} = obj
                {variable: vvar} = vvar if vvar instanceof Assign
                idx =
                  if vvar.this
                    vvar.properties[0].name
                  else
                    new PropertyName vvar.unwrap().value
                acc = idx.unwrap() instanceof PropertyName
                vval = new Value value, [new (if acc then Access else Index) idx]
              else

- §

  `obj` is [a…], {a…} or a

                vvar = switch
                  when obj instanceof Splat then new Value obj.name
                  else obj
                vval = switch
                  when obj instanceof Splat then compSlice(vvarTxt, i)
                  else new Value new Literal(vvarTxt), [new Index new NumberLiteral i]
              message = isUnassignable vvar.unwrap().value
              vvar.error message if message
              pushAssign vvar, vval

- §

  “Simple” `objects` can be split and compiled to arrays, [a, b, c] = arr, [a, b, c…] = arr

          assignObjects = (objs, vvar, vvarTxt) =>
            vvar = new Value new Arr(objs, yes)
            vval = if vvarTxt instanceof Value then vvarTxt else new Value new Literal(vvarTxt)
            pushAssign vvar, vval

          processObjects = (objs, vvar, vvarTxt) ->
            if complexObjects objs
              loopObjects objs, vvar, vvarTxt
            else
              assignObjects objs, vvar, vvarTxt

- §

  In case there is `Splat` or `Expansion` in `objects`, we can split array in two simple subarrays. `Splat` [a, b, c…, d, e] can be split into [a, b, c…] and [d, e]. `Expansion` [a, b, …, c, d] can be split into [a, b] and [c, d]. Examples: a) `Splat` CS: [a, b, c…, d, e] = arr JS: [a, b, …c] = arr, [d, e] = splice.call(c, -2) b) `Expansion` CS: [a, b, …, d, e] = arr JS: [a, b] = arr, [d, e] = slice.call(arr, -2)

          if splatsAndExpans.length
            expIdx = splatsAndExpans[0]
            leftObjs = objects.slice 0, expIdx + (if isSplat then 1 else 0)
            rightObjs = objects.slice expIdx + 1
            processObjects leftObjs, vvar, vvarText if leftObjs.length isnt 0
            if rightObjs.length isnt 0

- §

  Slice or splice `objects`.

              refExp = switch
                when isSplat then compSplice new Value(objects[expIdx].name), rightObjs.length * -1
                when isExpans then compSlice vvarText, rightObjs.length * -1
              if complexObjects rightObjs
                restVar = refExp
                refExp = o.scope.freeVariable 'ref'
                assigns.push [@makeCode(refExp + ' = '), restVar.compileToFragments(o, LEVEL_LIST)...]
              processObjects rightObjs, vvar, refExp
          else

- §

  There is no `Splat` or `Expansion` in `objects`.

            processObjects objects, vvar, vvarText
          splatVarAssign?()
          assigns.push vvar unless top or @subpattern
          fragments = @joinFragmentArrays assigns, ', '
          if o.level < LEVEL_LIST then fragments else @wrapInParentheses fragments

- §

  Disallow `[...] = a` for some reason. (Could be equivalent to `[] = a`?)

        disallowLoneExpansion: ->
          return unless @variable.base instanceof Arr
          {objects} = @variable.base
          return unless objects?.length is 1
          [loneObject] = objects
          if loneObject instanceof Expansion
            loneObject.error 'Destructuring assignment has no target'

- §

  Show error if there is more than one `Splat`, or `Expansion`. Examples: [a, b, c…, d, e, f…], [a, b, …, c, d, …], [a, b, …, c, d, e…]

        getAndCheckSplatsAndExpansions: ->
          return {splats: [], expans: [], splatsAndExpans: []} unless @variable.base instanceof Arr
          {objects} = @variable.base

- §

  Count all `Splats`: [a, b, c…, d, e]

          splats = (i for obj, i in objects when obj instanceof Splat)

- §

  Count all `Expansions`: [a, b, …, c, d]

          expans = (i for obj, i in objects when obj instanceof Expansion)

- §

  Combine splats and expansions.

          splatsAndExpans = [splats..., expans...]
          if splatsAndExpans.length > 1

- §

  Sort ‘splatsAndExpans’ so we can show error at first disallowed token.

            objects[splatsAndExpans.sort()[1]].error "multiple splats/expansions are disallowed in an assignment"
          {splats, expans, splatsAndExpans}

- §

  When compiling a conditional assignment, take care to ensure that the operands are only evaluated once, even though we have to reference them more than once.

        compileConditional: (o) ->
          [left, right] = @variable.cacheReference o

- §

  Disallow conditional assignment of undefined variables.

          if not left.properties.length and left.base instanceof Literal and
                 left.base not instanceof ThisLiteral and not o.scope.check left.base.value
            @throwUnassignableConditionalError left.base.value
          if "?" in @context
            o.isExistentialEquals = true
            new If(new Existence(left), right, type: 'if').addElse(new Assign(right, @value, '=')).compileToFragments o
          else
            fragments = new Op(@context[...-1], left, new Assign(right, @value, '=')).compileToFragments o
            if o.level <= LEVEL_LIST then fragments else @wrapInParentheses fragments

- §

  Convert special math assignment operators like `a //= b` to the equivalent extended form `a = a ** b` and then compiles that.

        compileSpecialMath: (o) ->
          [left, right] = @variable.cacheReference o
          new Assign(left, new Op(@context[...-1], right, @value)).compileToFragments o

- §

  Compile the assignment from an array splice literal, using JavaScript’s `Array#splice` method.

        compileSplice: (o) ->
          {range: {from, to, exclusive}} = @variable.properties.pop()
          unwrappedVar = @variable.unwrapAll()
          if unwrappedVar.comments
            moveComments unwrappedVar, @
            delete @variable.comments
          name = @variable.compile o
          if from
            [fromDecl, fromRef] = @cacheToCodeFragments from.cache o, LEVEL_OP
          else
            fromDecl = fromRef = '0'
          if to
            if from?.isNumber() and to.isNumber()
              to = to.compile(o) - fromRef
              to += 1 unless exclusive
            else
              to = to.compile(o, LEVEL_ACCESS) + ' - ' + fromRef
              to += ' + 1' unless exclusive
          else
            to = "9e9"
          [valDef, valRef] = @value.cache o, LEVEL_LIST
          answer = [].concat @makeCode("#{utility 'splice', o}.apply(#{name}, [#{fromDecl}, #{to}].concat("), valDef, @makeCode(")), "), valRef
          if o.level > LEVEL_TOP then @wrapInParentheses answer else answer

        eachName: (iterator) ->
          @variable.unwrapAll().eachName iterator

        isDefaultAssignment: -> @param or @nestedLhs

        propagateLhs: ->
          return unless @variable?.isArray?() or @variable?.isObject?()

- §

  This is the left-hand side of an assignment; let `Arr` and `Obj` know that, so that those nodes know that they’re assignable as destructured variables.

          @variable.base.propagateLhs yes

        throwUnassignableConditionalError: (name) ->
          @variable.error "the variable \"#{name}\" can't be assigned with #{@context} because it has not been declared before"

        isConditional: ->
          @context in ['||=', '&&=', '?=']

        isStatementAst: NO

        astNode: (o) ->
          @disallowLoneExpansion()
          @getAndCheckSplatsAndExpansions()
          if @isConditional()
            variable = @variable.unwrap()
            if variable instanceof IdentifierLiteral and not o.scope.check variable.value
              @throwUnassignableConditionalError variable.value
          @addScopeVariables o, allowAssignmentToExpansion: yes, allowAssignmentToNontrailingSplat: yes, allowAssignmentToEmptyArray: yes, allowAssignmentToComplexSplat: yes
          super o

        astType: ->
          if @isDefaultAssignment()
            'AssignmentPattern'
          else
            'AssignmentExpression'

        astProperties: (o) ->
          ret =
            right: @value.ast o, LEVEL_LIST
            left: @variable.ast o, LEVEL_LIST

          unless @isDefaultAssignment()
            ret.operator = @originalContext ? '='

          ret

- §

  ### FuncGlyph

      exports.FuncGlyph = class FuncGlyph extends Base
        constructor: (@glyph) ->
          super()

- §

  ### Code

- §

  A function definition. This is the only node that creates a new Scope. When for the purposes of walking the contents of a function body, the Code has no children – they’re within the inner scope.

      exports.Code = class Code extends Base
        constructor: (params, body, @funcGlyph, @paramStart) ->
          super()

          @params      = params or []
          @body        = body or new Block
          @bound       = @funcGlyph?.glyph is '=>'
          @isGenerator = no
          @isAsync     = no
          @isMethod    = no

          @body.traverseChildren no, (node) =>
            if (node instanceof Op and node.isYield()) or node instanceof YieldReturn
              @isGenerator = yes
            if (node instanceof Op and node.isAwait()) or node instanceof AwaitReturn
              @isAsync = yes
            if node instanceof For and node.isAwait()
              @isAsync = yes

          @propagateLhs()

        children: ['params', 'body']

        isStatement: -> @isMethod

        jumps: NO

        makeScope: (parentScope) -> new Scope parentScope, @body, this

- §

  Compilation creates a new scope unless explicitly asked to share with the outer scope. Handles splat parameters in the parameter list by setting such parameters to be the final parameter in the function definition, as required per the ES2015 spec. If the CoffeeScript function definition had parameters after the splat, they are declared via expressions in the function body.

        compileNode: (o) ->
          @checkForAsyncOrGeneratorConstructor()

          if @bound
            @context = o.scope.method.context if o.scope.method?.bound
            @context = 'this' unless @context

          @updateOptions o
          params           = []
          exprs            = []
          thisAssignments  = @thisAssignments?.slice() ? []
          paramsAfterSplat = []
          haveSplatParam   = no
          haveBodyParam    = no

          @checkForDuplicateParams()
          @disallowLoneExpansionAndMultipleSplats()

- §

  Separate `this` assignments.

          @eachParamName (name, node, param, obj) ->
            if node.this
              name   = node.properties[0].name.value
              name   = "_#{name}" if name in JS_FORBIDDEN
              target = new IdentifierLiteral o.scope.freeVariable name, reserve: no

- §

  `Param` is object destructuring with a default value: ({@prop = 1}) -> In a case when the variable name is already reserved, we have to assign a new variable name to the destructured variable: ({prop:prop1 = 1}) ->

              replacement =
                  if param.name instanceof Obj and obj instanceof Assign and
                      obj.operatorToken.value is '='
                    new Assign (new IdentifierLiteral name), target, 'object' #, operatorToken: new Literal ':'
                  else
                    target
              param.renameParam node, replacement
              thisAssignments.push new Assign node, target

- §

  Parse the parameters, adding them to the list of parameters to put in the function definition; and dealing with splats or expansions, including adding expressions to the function body to declare all parameter variables that would have been after the splat/expansion parameter. If we encounter a parameter that needs to be declared in the function body for any reason, for example it’s destructured with `this`, also declare and assign all subsequent parameters in the function body so that any non-idempotent parameters are evaluated in the correct order.

          for param, i in @params

- §

  Was `...` used with this parameter? Splat/expansion parameters cannot have default values, so we need not worry about that.

            if param.splat or param instanceof Expansion
              haveSplatParam = yes
              if param.splat
                if param.name instanceof Arr or param.name instanceof Obj

- §

  Splat arrays are treated oddly by ES; deal with them the legacy way in the function body. TODO: Should this be handled in the function parameter list, and if so, how?

                  splatParamName = o.scope.freeVariable 'arg'
                  params.push ref = new Value new IdentifierLiteral splatParamName
                  exprs.push new Assign new Value(param.name), ref
                else
                  params.push ref = param.asReference o
                  splatParamName = fragmentsToText ref.compileNodeWithoutComments o
                if param.shouldCache()
                  exprs.push new Assign new Value(param.name), ref
              else # `param` is an Expansion
                splatParamName = o.scope.freeVariable 'args'
                params.push new Value new IdentifierLiteral splatParamName

              o.scope.parameter splatParamName

- §

  Parse all other parameters; if a splat paramater has not yet been encountered, add these other parameters to the list to be output in the function definition.

            else
              if param.shouldCache() or haveBodyParam
                param.assignedInBody = yes
                haveBodyParam = yes

- §

  This parameter cannot be declared or assigned in the parameter list. So put a reference in the parameter list and add a statement to the function body assigning it, e.g. `(arg) => { var a = arg.a; }`, with a default value if it has one.

                if param.value?
                  condition = new Op '===', param, new UndefinedLiteral
                  ifTrue = new Assign new Value(param.name), param.value
                  exprs.push new If condition, ifTrue
                else
                  exprs.push new Assign new Value(param.name), param.asReference(o), null, param: 'alwaysDeclare'

- §

  If this parameter comes before the splat or expansion, it will go in the function definition parameter list.

              unless haveSplatParam

- §

  If this parameter has a default value, and it hasn’t already been set by the `shouldCache()` block above, define it as a statement in the function body. This parameter comes after the splat parameter, so we can’t define its default value in the parameter list.

                if param.shouldCache()
                  ref = param.asReference o
                else
                  if param.value? and not param.assignedInBody
                    ref = new Assign new Value(param.name), param.value, null, param: yes
                  else
                    ref = param

- §

  Add this parameter’s reference(s) to the function scope.

                if param.name instanceof Arr or param.name instanceof Obj

- §

  This parameter is destructured.

                  param.name.lhs = yes
                  unless param.shouldCache()
                    param.name.eachName (prop) ->
                      o.scope.parameter prop.value
                else

- §

  This compilation of the parameter is only to get its name to add to the scope name tracking; since the compilation output here isn’t kept for eventual output, don’t include comments in this compilation, so that they get output the “real” time this param is compiled.

                  paramToAddToScope = if param.value? then param else ref
                  o.scope.parameter fragmentsToText paramToAddToScope.compileToFragmentsWithoutComments o
                params.push ref
              else
                paramsAfterSplat.push param

- §

  If this parameter had a default value, since it’s no longer in the function parameter list we need to assign its default value (if necessary) as an expression in the body.

                if param.value? and not param.shouldCache()
                  condition = new Op '===', param, new UndefinedLiteral
                  ifTrue = new Assign new Value(param.name), param.value
                  exprs.push new If condition, ifTrue

- §

  Add this parameter to the scope, since it wouldn’t have been added yet since it was skipped earlier.

                o.scope.add param.name.value, 'var', yes if param.name?.value?

- §

  If there were parameters after the splat or expansion parameter, those parameters need to be assigned in the body of the function.

          if paramsAfterSplat.length isnt 0

- §

  Create a destructured assignment, e.g. `[a, b, c] = [args..., b, c]`

            exprs.unshift new Assign new Value(
                new Arr [new Splat(new IdentifierLiteral(splatParamName)), (param.asReference o for param in paramsAfterSplat)...]
              ), new Value new IdentifierLiteral splatParamName

- §

  Add new expressions to the function body

          wasEmpty = @body.isEmpty()
          @disallowSuperInParamDefaults()
          @checkSuperCallsInConstructorBody()
          @body.expressions.unshift thisAssignments... unless @expandCtorSuper thisAssignments
          @body.expressions.unshift exprs...
          if @isMethod and @bound and not @isStatic and @classVariable
            boundMethodCheck = new Value new Literal utility 'boundMethodCheck', o
            @body.expressions.unshift new Call(boundMethodCheck, [new Value(new ThisLiteral), @classVariable])
          @body.makeReturn() unless wasEmpty or @noReturn

- §

  JavaScript doesn’t allow bound (`=>`) functions to also be generators. This is usually caught via `Op::compileContinuation`, but double-check:

          if @bound and @isGenerator
            yieldNode = @body.contains (node) -> node instanceof Op and node.operator is 'yield'
            (yieldNode or @).error 'yield cannot occur inside bound (fat arrow) functions'

- §

  Assemble the output

          modifiers = []
          modifiers.push 'static' if @isMethod and @isStatic
          modifiers.push 'async'  if @isAsync
          unless @isMethod or @bound
            modifiers.push "function#{if @isGenerator then '*' else ''}"
          else if @isGenerator
            modifiers.push '*'

          signature = [@makeCode '(']

- §

  Block comments between a function name and `(` get output between `function` and `(`.

          if @paramStart?.comments?
            @compileCommentFragments o, @paramStart, signature
          for param, i in params
            signature.push @makeCode ', ' if i isnt 0
            signature.push @makeCode '...' if haveSplatParam and i is params.length - 1

- §

  Compile this parameter, but if any generated variables get created (e.g. `ref`), shift those into the parent scope since we can’t put a `var` line inside a function parameter list.

            scopeVariablesCount = o.scope.variables.length
            signature.push param.compileToFragments(o, LEVEL_PAREN)...
            if scopeVariablesCount isnt o.scope.variables.length
              generatedVariables = o.scope.variables.splice scopeVariablesCount
              o.scope.parent.variables.push generatedVariables...
          signature.push @makeCode ')'

- §

  Block comments between `)` and `->`/`=>` get output between `)` and `{`.

          if @funcGlyph?.comments?
            comment.unshift = no for comment in @funcGlyph.comments
            @compileCommentFragments o, @funcGlyph, signature

          body = @body.compileWithDeclarations o unless @body.isEmpty()

- §

  We need to compile the body before method names to ensure `super` references are handled.

          if @isMethod
            [methodScope, o.scope] = [o.scope, o.scope.parent]
            name = @name.compileToFragments o
            name.shift() if name[0].code is '.'
            o.scope = methodScope

          answer = @joinFragmentArrays (@makeCode m for m in modifiers), ' '
          answer.push @makeCode ' ' if modifiers.length and name
          answer.push name... if name
          answer.push signature...
          answer.push @makeCode ' =>' if @bound and not @isMethod
          answer.push @makeCode ' {'
          answer.push @makeCode('\n'), body..., @makeCode("\n#{@tab}") if body?.length
          answer.push @makeCode '}'

          return indentInitial answer, @ if @isMethod
          if @front or (o.level >= LEVEL_ACCESS) then @wrapInParentheses answer else answer

        updateOptions: (o) ->
          o.scope         = del(o, 'classScope') or @makeScope o.scope
          o.scope.shared  = del(o, 'sharedScope')
          o.indent        += TAB
          delete o.bare
          delete o.isExistentialEquals

        checkForDuplicateParams: ->
          paramNames = []
          @eachParamName (name, node, param) ->
            node.error "multiple parameters named '#{name}'" if name in paramNames
            paramNames.push name

        eachParamName: (iterator) ->
          param.eachName iterator for param in @params

- §

  Short-circuit `traverseChildren` method to prevent it from crossing scope boundaries unless `crossScope` is `true`.

        traverseChildren: (crossScope, func) ->
          super(crossScope, func) if crossScope

- §

  Short-circuit `replaceInContext` method to prevent it from crossing context boundaries. Bound functions have the same context.

        replaceInContext: (child, replacement) ->
          if @bound
            super child, replacement
          else
            false

        disallowSuperInParamDefaults: ({forAst} = {}) ->
          return false unless @ctor

          @eachSuperCall Block.wrap(@params), (superCall) ->
            superCall.error "'super' is not allowed in constructor parameter defaults"
          , checkForThisBeforeSuper: not forAst

        checkSuperCallsInConstructorBody: ->
          return false unless @ctor

          seenSuper = @eachSuperCall @body, (superCall) =>
            superCall.error "'super' is only allowed in derived class constructors" if @ctor is 'base'

          seenSuper

        flagThisParamInDerivedClassConstructorWithoutCallingSuper: (param) ->
          param.error "Can't use @params in derived class constructors without calling super"

        checkForAsyncOrGeneratorConstructor: ->
          if @ctor
            @name.error 'Class constructor may not be async'       if @isAsync
            @name.error 'Class constructor may not be a generator' if @isGenerator

        disallowLoneExpansionAndMultipleSplats: ->
          seenSplatParam = no
          for param in @params

- §

  Was `...` used with this parameter? (Only one such parameter is allowed per function.)

            if param.splat or param instanceof Expansion
              if seenSplatParam
                param.error 'only one splat or expansion parameter is allowed per function definition'
              else if param instanceof Expansion and @params.length is 1
                param.error 'an expansion parameter cannot be the only parameter in a function definition'
              seenSplatParam = yes

        expandCtorSuper: (thisAssignments) ->
          return false unless @ctor

          seenSuper = @eachSuperCall @body, (superCall) =>
            superCall.expressions = thisAssignments

          haveThisParam = thisAssignments.length and thisAssignments.length isnt @thisAssignments?.length
          if @ctor is 'derived' and not seenSuper and haveThisParam
            param = thisAssignments[0].variable
            @flagThisParamInDerivedClassConstructorWithoutCallingSuper param

          seenSuper

- §

  Find all super calls in the given context node; returns `true` if `iterator` is called.

        eachSuperCall: (context, iterator, {checkForThisBeforeSuper = yes} = {}) ->
          seenSuper = no

          context.traverseChildren yes, (child) =>
            if child instanceof SuperCall

- §

  `super` in a constructor (the only `super` without an accessor) cannot be given an argument with a reference to `this`, as that would be referencing `this` before calling `super`.

              unless child.variable.accessor
                childArgs = child.args.filter (arg) ->
                  arg not instanceof Class and (arg not instanceof Code or arg.bound)
                Block.wrap(childArgs).traverseChildren yes, (node) =>
                  node.error "Can't call super with @params in derived class constructors" if node.this
              seenSuper = yes
              iterator child
            else if checkForThisBeforeSuper and child instanceof ThisLiteral and @ctor is 'derived' and not seenSuper
              child.error "Can't reference 'this' before calling super in derived class constructors"

- §

  `super` has the same target in bound (arrow) functions, so check them too

            child not instanceof SuperCall and (child not instanceof Code or child.bound)

          seenSuper

        propagateLhs: ->
          for param in @params
            {name} = param
            if name instanceof Arr or name instanceof Obj
              name.propagateLhs yes
            else if param instanceof Expansion
              param.lhs = yes

        astAddParamsToScope: (o) ->
          @eachParamName (name) ->
            o.scope.add name, 'param'

        astNode: (o) ->
          @updateOptions o
          @checkForAsyncOrGeneratorConstructor()
          @checkForDuplicateParams()
          @disallowSuperInParamDefaults forAst: yes
          @disallowLoneExpansionAndMultipleSplats()
          seenSuper = @checkSuperCallsInConstructorBody()
          if @ctor is 'derived' and not seenSuper
            @eachParamName (name, node) =>
              if node.this
                @flagThisParamInDerivedClassConstructorWithoutCallingSuper node
          @astAddParamsToScope o
          @body.makeReturn null, yes unless @body.isEmpty() or @noReturn

          super o

        astType: ->
          if @isMethod
            'ClassMethod'
          else if @bound
            'ArrowFunctionExpression'
          else
            'FunctionExpression'

        paramForAst: (param) ->
          return param if param instanceof Expansion
          {name, value, splat} = param
          if splat
            new Splat name, lhs: yes, postfix: splat.postfix
            .withLocationDataFrom param
          else if value?
            new Assign name, value, null, param: yes
            .withLocationDataFrom locationData: mergeLocationData name.locationData, value.locationData
          else
            name

        methodAstProperties: (o) ->
          getIsComputed = =>
            return yes if @name instanceof Index
            return yes if @name instanceof ComputedPropertyName
            return yes if @name.name instanceof ComputedPropertyName
            no

          return
            static: !!@isStatic
            key: @name.ast o
            computed: getIsComputed()
            kind:
              if @ctor
                'constructor'
              else
                'method'
            operator: @operatorToken?.value ? '='
            staticClassName: @isStatic.staticClassName?.ast(o) ? null
            bound: !!@bound

        astProperties: (o) ->
          return Object.assign
            params: @paramForAst(param).ast(o) for param in @params
            body: @body.ast (Object.assign {}, o, checkForDirectives: yes), LEVEL_TOP
            generator: !!@isGenerator
            async: !!@isAsync

- §

  We never generate named functions, so specify `id` as `null`, which matches the Babel AST for anonymous function expressions/arrow functions

            id: null
            hasIndentedBody: @body.locationData.first_line > @funcGlyph?.locationData.first_line
          ,
            if @isMethod then @methodAstProperties o else {}

        astLocationData: ->
          functionLocationData = super()
          return functionLocationData unless @isMethod

          astLocationData = mergeAstLocationData @name.astLocationData(), functionLocationData
          if @isStatic.staticClassName?
            astLocationData = mergeAstLocationData @isStatic.staticClassName.astLocationData(), astLocationData
          astLocationData

- §

  ### Param

- §

  A parameter in a function definition. Beyond a typical JavaScript parameter, these parameters can also attach themselves to the context of the function, as well as be a splat, gathering up a group of parameters into an array.

      exports.Param = class Param extends Base
        constructor: (@name, @value, @splat) ->
          super()

          message = isUnassignable @name.unwrapAll().value
          @name.error message if message
          if @name instanceof Obj and @name.generated
            token = @name.objects[0].operatorToken
            token.error "unexpected #{token.value}"

        children: ['name', 'value']

        compileToFragments: (o) ->
          @name.compileToFragments o, LEVEL_LIST

        compileToFragmentsWithoutComments: (o) ->
          @name.compileToFragmentsWithoutComments o, LEVEL_LIST

        asReference: (o) ->
          return @reference if @reference
          node = @name
          if node.this
            name = node.properties[0].name.value
            name = "_#{name}" if name in JS_FORBIDDEN
            node = new IdentifierLiteral o.scope.freeVariable name
          else if node.shouldCache()
            node = new IdentifierLiteral o.scope.freeVariable 'arg'
          node = new Value node
          node.updateLocationDataIfMissing @locationData
          @reference = node

        shouldCache: ->
          @name.shouldCache()

- §

  Iterates the name or names of a `Param`. In a sense, a destructured parameter represents multiple JS parameters. This method allows to iterate them all. The `iterator` function will be called as `iterator(name, node)` where `name` is the name of the parameter and `node` is the AST node corresponding to that name.

        eachName: (iterator, name = @name) ->
          checkAssignabilityOfLiteral = (literal) ->
            message = isUnassignable literal.value
            if message
              literal.error message
            unless literal.isAssignable()
              literal.error "'#{literal.value}' can't be assigned"

          atParam = (obj, originalObj = null) => iterator "@#{obj.properties[0].name.value}", obj, @, originalObj
          if name instanceof Call
            name.error "Function invocation can't be assigned"

- §
  - simple literals `foo`

          if name instanceof Literal
            checkAssignabilityOfLiteral name
            return iterator name.value, name, @

- §
  - at-params `@foo`

          return atParam name if name instanceof Value
          for obj in name.objects ? []

- §

  Save original obj.

            nObj = obj

- §
  - destructured parameter with default value

            if obj instanceof Assign and not obj.context?
              obj = obj.variable

- §
  - assignments within destructured parameters `{foo:bar}`

            if obj instanceof Assign

- §

  … possibly with a default value

              if obj.value instanceof Assign
                obj = obj.value.variable
              else
                obj = obj.value
              @eachName iterator, obj.unwrap()

- §
  - splats within destructured parameters `[xs...]`

            else if obj instanceof Splat
              node = obj.name.unwrap()
              iterator node.value, node, @
            else if obj instanceof Value

- §
  - destructured parameters within destructured parameters `[{a}]`

              if obj.isArray() or obj.isObject()
                @eachName iterator, obj.base

- §
  - at-params within destructured parameters `{@foo}`

              else if obj.this
                atParam obj, nObj

- §
  - simple destructured parameters {foo}

              else
                checkAssignabilityOfLiteral obj.base
                iterator obj.base.value, obj.base, @
            else if obj instanceof Elision
              obj
            else if obj not instanceof Expansion
              obj.error "illegal parameter #{obj.compile()}"
          return

- §

  Rename a param by replacing the given AST node for a name with a new node. This needs to ensure that the the source for object destructuring does not change.

        renameParam: (node, newNode) ->
          isNode      = (candidate) -> candidate is node
          replacement = (node, parent) =>
            if parent instanceof Obj
              key = node
              key = node.properties[0].name if node.this

- §

  No need to assign a new variable for the destructured variable if the variable isn’t reserved. Examples: `({@foo}) ->` should compile to `({foo}) { this.foo = foo}` `foo = 1; ({@foo}) ->` should compile to `foo = 1; ({foo:foo1}) { this.foo = foo1 }`

              if node.this and key.value is newNode.value
                new Value newNode
              else
                new Assign new Value(key), newNode, 'object'
            else
              newNode

          @replaceInContext isNode, replacement

- §

  ### Splat

- §

  A splat, either as a parameter to a function, an argument to a call, or as part of a destructuring assignment.

      exports.Splat = class Splat extends Base
        constructor: (name, {@lhs, @postfix = true} = {}) ->
          super()
          @name = if name.compile then name else new Literal name

        children: ['name']

        shouldCache: -> no

        isAssignable: ({allowComplexSplat = no} = {})->
          return allowComplexSplat if @name instanceof Obj or @name instanceof Parens
          @name.isAssignable() and (not @name.isAtomic or @name.isAtomic())

        assigns: (name) ->
          @name.assigns name

        compileNode: (o) ->
          compiledSplat = [@makeCode('...'), @name.compileToFragments(o, LEVEL_OP)...]
          return compiledSplat unless @jsx
          return [@makeCode('{'), compiledSplat..., @makeCode('}')]

        unwrap: -> @name

        propagateLhs: (setLhs) ->
          @lhs = yes if setLhs
          return unless @lhs
          @name.propagateLhs? yes

        astType: ->
          if @jsx
            'JSXSpreadAttribute'
          else if @lhs
            'RestElement'
          else
            'SpreadElement'

        astProperties: (o) -> {
          argument: @name.ast o, LEVEL_OP
          @postfix
        }

- §

  ### Expansion

- §

  Used to skip values inside an array destructuring (pattern matching) or parameter list.

      exports.Expansion = class Expansion extends Base

        shouldCache: NO

        compileNode: (o) ->
          @throwLhsError()

        asReference: (o) ->
          this

        eachName: (iterator) ->

        throwLhsError: ->
          @error 'Expansion must be used inside a destructuring assignment or parameter list'

        astNode: (o) ->
          unless @lhs
            @throwLhsError()

          super o

        astType: -> 'RestElement'

        astProperties: ->
          return
            argument: null

- §

  ### Elision

- §

  Array elision element (for example, [,a, , , b, , c, ,]).

      exports.Elision = class Elision extends Base

        isAssignable: YES

        shouldCache: NO

        compileToFragments: (o, level) ->
          fragment = super o, level
          fragment.isElision = yes
          fragment

        compileNode: (o) ->
          [@makeCode ', ']

        asReference: (o) ->
          this

        eachName: (iterator) ->

        astNode: ->
          null

- §

  ### While

- §

  A while loop, the only sort of low-level loop exposed by CoffeeScript. From it, all other loops can be manufactured. Useful in cases where you need more flexibility or more speed than a comprehension can provide.

      exports.While = class While extends Base
        constructor: (@condition, {invert: @inverted, @guard, @isLoop} = {}) ->
          super()

        children: ['condition', 'guard', 'body']

        isStatement: YES

        makeReturn: (results, mark) ->
          return super(results, mark) if results
          @returns = not @jumps()
          if mark
            @body.makeReturn(results, mark) if @returns
            return
          this

        addBody: (@body) ->
          this

        jumps: ->
          {expressions} = @body
          return no unless expressions.length
          for node in expressions
            return jumpNode if jumpNode = node.jumps loop: yes
          no

- §

  The main difference from a JavaScript while is that the CoffeeScript while can be used as a part of a larger expression – while loops may return an array containing the computed result of each iteration.

        compileNode: (o) ->
          o.indent += TAB
          set      = ''
          {body}   = this
          if body.isEmpty()
            body = @makeCode ''
          else
            if @returns
              body.makeReturn rvar = o.scope.freeVariable 'results'
              set  = "#{@tab}#{rvar} = [];\n"
            if @guard
              if body.expressions.length > 1
                body.expressions.unshift new If (new Parens @guard).invert(), new StatementLiteral "continue"
              else
                body = Block.wrap [new If @guard, body] if @guard
            body = [].concat @makeCode("\n"), (body.compileToFragments o, LEVEL_TOP), @makeCode("\n#{@tab}")
          answer = [].concat @makeCode(set + @tab + "while ("), @processedCondition().compileToFragments(o, LEVEL_PAREN),
            @makeCode(") {"), body, @makeCode("}")
          if @returns
            answer.push @makeCode "\n#{@tab}return #{rvar};"
          answer

        processedCondition: ->
          @processedConditionCache ?= if @inverted then @condition.invert() else @condition

        astType: -> 'WhileStatement'

        astProperties: (o) ->
          return
            test: @condition.ast o, LEVEL_PAREN
            body: @body.ast o, LEVEL_TOP
            guard: @guard?.ast(o) ? null
            inverted: !!@inverted
            postfix: !!@postfix
            loop: !!@isLoop

- §

  ### Op

- §

  Simple Arithmetic and logical operations. Performs some conversion from CoffeeScript operations into their JavaScript equivalents.

      exports.Op = class Op extends Base
        constructor: (op, first, second, flip, {@invertOperator, @originalOperator = op} = {}) ->
          super()

          if op is 'new'
            if ((firstCall = unwrapped = first.unwrap()) instanceof Call or (firstCall = unwrapped.base) instanceof Call) and not firstCall.do and not firstCall.isNew
              return new Value firstCall.newInstance(), if firstCall is unwrapped then [] else unwrapped.properties
            first = new Parens first unless first instanceof Parens or first.unwrap() instanceof IdentifierLiteral or first.hasProperties?()
            call = new Call first, []
            call.locationData = @locationData
            call.isNew = yes
            return call

          @operator = CONVERSIONS[op] or op
          @first    = first
          @second   = second
          @flip     = !!flip

          if @operator in ['--', '++']
            message = isUnassignable @first.unwrapAll().value
            @first.error message if message

          return this

- §

  The map of conversions from CoffeeScript to JavaScript symbols.

        CONVERSIONS =
          '==':        '==='
          '!=':        '!=='
          'of':        'in'
          'yieldfrom': 'yield*'

- §

  The map of invertible operators.

        INVERSIONS =
          '!==': '==='
          '===': '!=='

        children: ['first', 'second']

        isNumber: ->
          @isUnary() and @operator in ['+', '-'] and
            @first instanceof Value and @first.isNumber()

        isAwait: ->
          @operator is 'await'

        isYield: ->
          @operator in ['yield', 'yield*']

        isUnary: ->
          not @second

        shouldCache: ->
          not @isNumber()

- §

  Am I capable of Python-style comparison chaining?

        isChainable: ->
          @operator in ['<', '>', '>=', '<=', '===', '!==']

        isChain: ->
          @isChainable() and @first.isChainable()

        invert: ->
          if @isInOperator()
            @invertOperator = '!'
            return @
          if @isChain()
            allInvertable = yes
            curr = this
            while curr and curr.operator
              allInvertable and= (curr.operator of INVERSIONS)
              curr = curr.first
            return new Parens(this).invert() unless allInvertable
            curr = this
            while curr and curr.operator
              curr.invert = !curr.invert
              curr.operator = INVERSIONS[curr.operator]
              curr = curr.first
            this
          else if op = INVERSIONS[@operator]
            @operator = op
            if @first.unwrap() instanceof Op
              @first.invert()
            this
          else if @second
            new Parens(this).invert()
          else if @operator is '!' and (fst = @first.unwrap()) instanceof Op and
                                        fst.operator in ['!', 'in', 'instanceof']
            fst
          else
            new Op '!', this

        unfoldSoak: (o) ->
          @operator in ['++', '--', 'delete'] and unfoldSoak o, this, 'first'

        generateDo: (exp) ->
          passedParams = []
          func = if exp instanceof Assign and (ref = exp.value.unwrap()) instanceof Code
            ref
          else
            exp
          for param in func.params or []
            if param.value
              passedParams.push param.value
              delete param.value
            else
              passedParams.push param
          call = new Call exp, passedParams
          call.do = yes
          call

        isInOperator: ->
          @originalOperator is 'in'

        compileNode: (o) ->
          if @isInOperator()
            inNode = new In @first, @second
            return (if @invertOperator then inNode.invert() else inNode).compileNode o
          if @invertOperator
            @invertOperator = null
            return @invert().compileNode(o)
          return Op::generateDo(@first).compileNode o if @operator is 'do'
          isChain = @isChain()

- §

  In chains, there’s no need to wrap bare obj literals in parens, as the chained expression is wrapped.

          @first.front = @front unless isChain
          @checkDeleteOperand o
          return @compileContinuation o if @isYield() or @isAwait()
          return @compileUnary        o if @isUnary()
          return @compileChain        o if isChain
          switch @operator
            when '?'  then @compileExistence o, @second.isDefaultValue
            when '//' then @compileFloorDivision o
            when '%%' then @compileModulo o
            else
              lhs = @first.compileToFragments o, LEVEL_OP
              rhs = @second.compileToFragments o, LEVEL_OP
              answer = [].concat lhs, @makeCode(" #{@operator} "), rhs
              if o.level <= LEVEL_OP then answer else @wrapInParentheses answer

- §

  Mimic Python’s chained comparisons when multiple comparison operators are used sequentially. For example:

      bin/coffee -e 'console.log 50 < 65 > 10'
      true

        compileChain: (o) ->
          [@first.second, shared] = @first.second.cache o
          fst = @first.compileToFragments o, LEVEL_OP
          fragments = fst.concat @makeCode(" #{if @invert then '&&' else '||'} "),
            (shared.compileToFragments o), @makeCode(" #{@operator} "), (@second.compileToFragments o, LEVEL_OP)
          @wrapInParentheses fragments

- §

  Keep reference to the left expression, unless this an existential assignment

        compileExistence: (o, checkOnlyUndefined) ->
          if @first.shouldCache()
            ref = new IdentifierLiteral o.scope.freeVariable 'ref'
            fst = new Parens new Assign ref, @first
          else
            fst = @first
            ref = fst
          new If(new Existence(fst, checkOnlyUndefined), ref, type: 'if').addElse(@second).compileToFragments o

- §

  Compile a unary Op.

        compileUnary: (o) ->
          parts = []
          op = @operator
          parts.push [@makeCode op]
          if op is '!' and @first instanceof Existence
            @first.negated = not @first.negated
            return @first.compileToFragments o
          if o.level >= LEVEL_ACCESS
            return (new Parens this).compileToFragments o
          plusMinus = op in ['+', '-']
          parts.push [@makeCode(' ')] if op in ['typeof', 'delete'] or
                            plusMinus and @first instanceof Op and @first.operator is op
          if plusMinus and @first instanceof Op
            @first = new Parens @first
          parts.push @first.compileToFragments o, LEVEL_OP
          parts.reverse() if @flip
          @joinFragmentArrays parts, ''

        compileContinuation: (o) ->
          parts = []
          op = @operator
          @checkContinuation o unless @isAwait()
          if 'expression' in Object.keys(@first) and not (@first instanceof Throw)
            parts.push @first.expression.compileToFragments o, LEVEL_OP if @first.expression?
          else
            parts.push [@makeCode "("] if o.level >= LEVEL_PAREN
            parts.push [@makeCode op]
            parts.push [@makeCode " "] if @first.base?.value isnt ''
            parts.push @first.compileToFragments o, LEVEL_OP
            parts.push [@makeCode ")"] if o.level >= LEVEL_PAREN
          @joinFragmentArrays parts, ''

        checkContinuation: (o) ->
          unless o.scope.parent?
            @error "#{@operator} can only occur inside functions"
          if o.scope.method?.bound and o.scope.method.isGenerator
            @error 'yield cannot occur inside bound (fat arrow) functions'

        compileFloorDivision: (o) ->
          floor = new Value new IdentifierLiteral('Math'), [new Access new PropertyName 'floor']
          second = if @second.shouldCache() then new Parens @second else @second
          div = new Op '/', @first, second
          new Call(floor, [div]).compileToFragments o

        compileModulo: (o) ->
          mod = new Value new Literal utility 'modulo', o
          new Call(mod, [@first, @second]).compileToFragments o

        toString: (idt) ->
          super idt, @constructor.name + ' ' + @operator

        checkDeleteOperand: (o) ->
          if @operator is 'delete' and o.scope.check(@first.unwrapAll().value)
            @error 'delete operand may not be argument or var'

        astNode: (o) ->
          @checkContinuation o if @isYield()
          @checkDeleteOperand o
          super o

        astType: ->
          return 'AwaitExpression' if @isAwait()
          return 'YieldExpression' if @isYield()
          return 'ChainedComparison' if @isChain()
          switch @operator
            when '||', '&&', '?' then 'LogicalExpression'
            when '++', '--'      then 'UpdateExpression'
            else
              if @isUnary()      then 'UnaryExpression'
              else                    'BinaryExpression'

        operatorAst: ->
          "#{if @invertOperator then "#{@invertOperator} " else ''}#{@originalOperator}"

        chainAstProperties: (o) ->
          operators = [@operatorAst()]
          operands = [@second]
          currentOp = @first
          loop
            operators.unshift currentOp.operatorAst()
            operands.unshift currentOp.second
            currentOp = currentOp.first
            unless currentOp.isChainable()
              operands.unshift currentOp
              break
          return {
            operators
            operands: (operand.ast(o, LEVEL_OP) for operand in operands)
          }

        astProperties: (o) ->
          return @chainAstProperties(o) if @isChain()

          firstAst = @first.ast o, LEVEL_OP
          secondAst = @second?.ast o, LEVEL_OP
          operatorAst = @operatorAst()
          switch
            when @isUnary()
              argument =
                if @isYield() and @first.unwrap().value is ''
                  null
                else
                  firstAst
              return {argument} if @isAwait()
              return {
                argument
                delegate: @operator is 'yield*'
              } if @isYield()
              return {
                argument
                operator: operatorAst
                prefix: !@flip
              }
            else
              return
                left: firstAst
                right: secondAst
                operator: operatorAst

- §

  ### In

      exports.In = class In extends Base
        constructor: (@object, @array) ->
          super()

        children: ['object', 'array']

        invert: NEGATE

        compileNode: (o) ->
          if @array instanceof Value and @array.isArray() and @array.base.objects.length
            for obj in @array.base.objects when obj instanceof Splat
              hasSplat = yes
              break

- §

  `compileOrTest` only if we have an array literal with no splats

            return @compileOrTest o unless hasSplat
          @compileLoopTest o

        compileOrTest: (o) ->
          [sub, ref] = @object.cache o, LEVEL_OP
          [cmp, cnj] = if @negated then [' !== ', ' && '] else [' === ', ' || ']
          tests = []
          for item, i in @array.base.objects
            if i then tests.push @makeCode cnj
            tests = tests.concat (if i then ref else sub), @makeCode(cmp), item.compileToFragments(o, LEVEL_ACCESS)
          if o.level < LEVEL_OP then tests else @wrapInParentheses tests

        compileLoopTest: (o) ->
          [sub, ref] = @object.cache o, LEVEL_LIST
          fragments = [].concat @makeCode(utility('indexOf', o) + ".call("), @array.compileToFragments(o, LEVEL_LIST),
            @makeCode(", "), ref, @makeCode(") " + if @negated then '< 0' else '>= 0')
          return fragments if fragmentsToText(sub) is fragmentsToText(ref)
          fragments = sub.concat @makeCode(', '), fragments
          if o.level < LEVEL_LIST then fragments else @wrapInParentheses fragments

        toString: (idt) ->
          super idt, @constructor.name + if @negated then '!' else ''

- §

  ### Try

- §

  A classic try/catch/finally block.

      exports.Try = class Try extends Base
        constructor: (@attempt, @catch, @ensure, @finallyTag) ->
          super()

        children: ['attempt', 'catch', 'ensure']

        isStatement: YES

        jumps: (o) -> @attempt.jumps(o) or @catch?.jumps(o)

        makeReturn: (results, mark) ->
          if mark
            @attempt?.makeReturn results, mark
            @catch?.makeReturn results, mark
            return
          @attempt = @attempt.makeReturn results if @attempt
          @catch   = @catch  .makeReturn results if @catch
          this

- §

  Compilation is more or less as you would expect – the finally clause is optional, the catch is not.

        compileNode: (o) ->
          originalIndent = o.indent
          o.indent  += TAB
          tryPart   = @attempt.compileToFragments o, LEVEL_TOP

          catchPart = if @catch
            @catch.compileToFragments merge(o, indent: originalIndent), LEVEL_TOP
          else unless @ensure or @catch
            generatedErrorVariableName = o.scope.freeVariable 'error', reserve: no
            [@makeCode(" catch (#{generatedErrorVariableName}) {}")]
          else
            []

          ensurePart = if @ensure then ([].concat @makeCode(" finally {\n"), @ensure.compileToFragments(o, LEVEL_TOP),
            @makeCode("\n#{@tab}}")) else []

          [].concat @makeCode("#{@tab}try {\n"),
            tryPart,
            @makeCode("\n#{@tab}}"), catchPart, ensurePart

        astType: -> 'TryStatement'

        astProperties: (o) ->
          return
            block: @attempt.ast o, LEVEL_TOP
            handler: @catch?.ast(o) ? null
            finalizer:
              if @ensure?
                Object.assign @ensure.ast(o, LEVEL_TOP),

- §

  Include `finally` keyword in location data.

                  mergeAstLocationData(
                    jisonLocationDataToAstLocationData(@finallyTag.locationData),
                    @ensure.astLocationData()
                  )
              else
                null

      exports.Catch = class Catch extends Base
        constructor: (@recovery, @errorVariable) ->
          super()
          @errorVariable?.unwrap().propagateLhs? yes

        children: ['recovery', 'errorVariable']

        isStatement: YES

        jumps: (o) -> @recovery.jumps o

        makeReturn: (results, mark) ->
          ret = @recovery.makeReturn results, mark
          return if mark
          @recovery = ret
          this

        compileNode: (o) ->
          o.indent  += TAB
          generatedErrorVariableName = o.scope.freeVariable 'error', reserve: no
          placeholder = new IdentifierLiteral generatedErrorVariableName
          @checkUnassignable()
          if @errorVariable
            @recovery.unshift new Assign @errorVariable, placeholder
          [].concat @makeCode(" catch ("), placeholder.compileToFragments(o), @makeCode(") {\n"),
            @recovery.compileToFragments(o, LEVEL_TOP), @makeCode("\n#{@tab}}")

        checkUnassignable: ->
          if @errorVariable
            message = isUnassignable @errorVariable.unwrapAll().value
            @errorVariable.error message if message

        astNode: (o) ->
          @checkUnassignable()
          @errorVariable?.eachName (name) ->
            alreadyDeclared = o.scope.find name.value
            name.isDeclaration = not alreadyDeclared

          super o

        astType: -> 'CatchClause'

        astProperties: (o) ->
          return
            param: @errorVariable?.ast(o) ? null
            body: @recovery.ast o, LEVEL_TOP

- §

  ### Throw

- §

  Simple node to throw an exception.

      exports.Throw = class Throw extends Base
        constructor: (@expression) ->
          super()

        children: ['expression']

        isStatement: YES
        jumps:       NO

- §

  A Throw is already a return, of sorts…

        makeReturn: THIS

        compileNode: (o) ->
          fragments = @expression.compileToFragments o, LEVEL_LIST
          unshiftAfterComments fragments, @makeCode 'throw '
          fragments.unshift @makeCode @tab
          fragments.push @makeCode ';'
          fragments

        astType: -> 'ThrowStatement'

        astProperties: (o) ->
          return
            argument: @expression.ast o, LEVEL_LIST

- §

  ### Existence

- §

  Checks a variable for existence – not `null` and not `undefined`. This is similar to `.nil?` in Ruby, and avoids having to consult a JavaScript truth table. Optionally only check if a variable is not `undefined`.

      exports.Existence = class Existence extends Base
        constructor: (@expression, onlyNotUndefined = no) ->
          super()
          @comparisonTarget = if onlyNotUndefined then 'undefined' else 'null'
          salvagedComments = []
          @expression.traverseChildren yes, (child) ->
            if child.comments
              for comment in child.comments
                salvagedComments.push comment unless comment in salvagedComments
              delete child.comments
          attachCommentsToNode salvagedComments, @
          moveComments @expression, @

        children: ['expression']

        invert: NEGATE

        compileNode: (o) ->
          @expression.front = @front
          code = @expression.compile o, LEVEL_OP
          if @expression.unwrap() instanceof IdentifierLiteral and not o.scope.check code
            [cmp, cnj] = if @negated then ['===', '||'] else ['!==', '&&']
            code = "typeof #{code} #{cmp} \"undefined\"" + if @comparisonTarget isnt 'undefined' then " #{cnj} #{code} #{cmp} #{@comparisonTarget}" else ''
          else

- §

  We explicity want to use loose equality (`==`) when comparing against `null`, so that an existence check roughly corresponds to a check for truthiness. Do not change this to `===` for `null`, as this will break mountains of existing code. When comparing only against `undefined`, however, we want to use `===` because this use case is for parity with ES2015+ default values, which only get assigned when the variable is `undefined` (but not `null`).

            cmp = if @comparisonTarget is 'null'
              if @negated then '==' else '!='
            else # `undefined`
              if @negated then '===' else '!=='
            code = "#{code} #{cmp} #{@comparisonTarget}"
          [@makeCode(if o.level <= LEVEL_COND then code else "(#{code})")]

        astType: -> 'UnaryExpression'

        astProperties: (o) ->
          return
            argument: @expression.ast o
            operator: '?'
            prefix: no

- §

  ### Parens

- §

  An extra set of parentheses, specified explicitly in the source. At one time we tried to clean up the results by detecting and removing redundant parentheses, but no longer – you can put in as many as you please.

  Parentheses are a good way to force any statement to become an expression.

      exports.Parens = class Parens extends Base
        constructor: (@body) ->
          super()

        children: ['body']

        unwrap: -> @body

        shouldCache: -> @body.shouldCache()

        compileNode: (o) ->
          expr = @body.unwrap()

- §

  If these parentheses are wrapping an `IdentifierLiteral` followed by a block comment, output the parentheses (or put another way, don’t optimize away these redundant parentheses). This is because Flow requires parentheses in certain circumstances to distinguish identifiers followed by comment-based type annotations from JavaScript labels.

          shouldWrapComment = expr.comments?.some(
            (comment) -> comment.here and not comment.unshift and not comment.newLine)
          if expr instanceof Value and expr.isAtomic() and not @jsxAttribute and not shouldWrapComment
            expr.front = @front
            return expr.compileToFragments o
          fragments = expr.compileToFragments o, LEVEL_PAREN
          bare = o.level < LEVEL_OP and not shouldWrapComment and (
              expr instanceof Op and not expr.isInOperator() or expr.unwrap() instanceof Call or
              (expr instanceof For and expr.returns)
            ) and (o.level < LEVEL_COND or fragments.length <= 3)
          return @wrapInBraces fragments if @jsxAttribute
          if bare then fragments else @wrapInParentheses fragments

        astNode: (o) -> @body.unwrap().ast o, LEVEL_PAREN

- §

  ### StringWithInterpolations

      exports.StringWithInterpolations = class StringWithInterpolations extends Base
        constructor: (@body, {@quote, @startQuote, @jsxAttribute} = {}) ->
          super()

        @fromStringLiteral: (stringLiteral) ->
          updatedString = stringLiteral.withoutQuotesInLocationData()
          updatedStringValue = new Value(updatedString).withLocationDataFrom updatedString
          new StringWithInterpolations Block.wrap([updatedStringValue]), quote: stringLiteral.quote, jsxAttribute: stringLiteral.jsxAttribute
          .withLocationDataFrom stringLiteral

        children: ['body']

- §

  `unwrap` returns `this` to stop ancestor nodes reaching in to grab @body, and using @body.compileNode. `StringWithInterpolations.compileNode` is the custom logic to output interpolated strings as code.

        unwrap: -> this

        shouldCache: -> @body.shouldCache()

        extractElements: (o, {includeInterpolationWrappers, isJsx} = {}) ->

- §

  Assumes that `expr` is `Block`

          expr = @body.unwrap()

          elements = []
          salvagedComments = []
          expr.traverseChildren no, (node) =>
            if node instanceof StringLiteral
              if node.comments
                salvagedComments.push node.comments...
                delete node.comments
              elements.push node
              return yes
            else if node instanceof Interpolation
              if salvagedComments.length isnt 0
                for comment in salvagedComments
                  comment.unshift = yes
                  comment.newLine = yes
                attachCommentsToNode salvagedComments, node
              if (unwrapped = node.expression?.unwrapAll()) instanceof PassthroughLiteral and unwrapped.generated and not (isJsx and o.compiling)
                if o.compiling
                  commentPlaceholder = new StringLiteral('').withLocationDataFrom node
                  commentPlaceholder.comments = unwrapped.comments
                  (commentPlaceholder.comments ?= []).push node.comments... if node.comments
                  elements.push new Value commentPlaceholder
                else
                  empty = new Interpolation().withLocationDataFrom node
                  empty.comments = node.comments
                  elements.push empty
              else if node.expression or includeInterpolationWrappers
                (node.expression?.comments ?= []).push node.comments... if node.comments
                elements.push if includeInterpolationWrappers then node else node.expression
              return no
            else if node.comments

- §

  This node is getting discarded, but salvage its comments.

              if elements.length isnt 0 and elements[elements.length - 1] not instanceof StringLiteral
                for comment in node.comments
                  comment.unshift = no
                  comment.newLine = yes
                attachCommentsToNode node.comments, elements[elements.length - 1]
              else
                salvagedComments.push node.comments...
              delete node.comments
            return yes

          elements

        compileNode: (o) ->
          @comments ?= @startQuote?.comments

          if @jsxAttribute
            wrapped = new Parens new StringWithInterpolations @body
            wrapped.jsxAttribute = yes
            return wrapped.compileNode o

          elements = @extractElements o, isJsx: @jsx

          fragments = []
          fragments.push @makeCode '`' unless @jsx
          for element in elements
            if element instanceof StringLiteral
              unquotedElementValue = if @jsx then element.unquotedValueForJSX else element.unquotedValueForTemplateLiteral
              fragments.push @makeCode unquotedElementValue
            else
              fragments.push @makeCode '$' unless @jsx
              code = element.compileToFragments(o, LEVEL_PAREN)
              if not @isNestedTag(element) or
                 code.some((fragment) -> fragment.comments?.some((comment) -> comment.here is no))
                code = @wrapInBraces code

- §

  Flag the `{` and `}` fragments as having been generated by this `StringWithInterpolations` node, so that `compileComments` knows to treat them as bounds. But the braces are unnecessary if all of the enclosed comments are `/* */` comments. Don’t trust `fragment.type`, which can report minified variable names when this compiler is minified.

                code[0].isStringWithInterpolations = yes
                code[code.length - 1].isStringWithInterpolations = yes
              fragments.push code...
          fragments.push @makeCode '`' unless @jsx
          fragments

        isNestedTag: (element) ->
          call = element.unwrapAll?()
          @jsx and call instanceof JSXElement

        astType: -> 'TemplateLiteral'

        astProperties: (o) ->
          elements = @extractElements o, includeInterpolationWrappers: yes
          [..., last] = elements

          quasis = []
          expressions = []

          for element, index in elements
            if element instanceof StringLiteral
              quasis.push new TemplateElement(
                element.originalValue
                tail: element is last
              ).withLocationDataFrom(element).ast o
            else # Interpolation
              {expression} = element
              node =
                unless expression?
                  emptyInterpolation = new EmptyInterpolation()
                  emptyInterpolation.locationData = emptyExpressionLocationData {
                    interpolationNode: element
                    openingBrace: '#{'
                    closingBrace: '}'
                  }
                  emptyInterpolation
                else
                  expression.unwrapAll()
              expressions.push astAsBlockIfNeeded node, o

          {expressions, quasis, @quote}

      exports.TemplateElement = class TemplateElement extends Base
        constructor: (@value, {@tail} = {}) ->
          super()

        astProperties: ->
          return
            value:
              raw: @value
            tail: !!@tail

      exports.Interpolation = class Interpolation extends Base
        constructor: (@expression) ->
          super()

        children: ['expression']

- §

  Represents the contents of an empty interpolation (e.g. `#{}`). Only used during AST generation.

      exports.EmptyInterpolation = class EmptyInterpolation extends Base
        constructor: ->
          super()

- §

  ### For

- §

  CoffeeScript’s replacement for the for loop is our array and object comprehensions, that compile into for loops here. They also act as an expression, able to return the result of each filtered iteration.

  Unlike Python array comprehensions, they can be multi-line, and you can pass the current index of the loop as a second parameter. Unlike Ruby blocks, you can map and filter in a single pass.

      exports.For = class For extends While
        constructor: (body, source) ->
          super()
          @addBody body
          @addSource source

        children: ['body', 'source', 'guard', 'step']

        isAwait: -> @await ? no

        addBody: (body) ->
          @body = Block.wrap [body]
          {expressions} = @body
          if expressions.length
            @body.locationData ?= mergeLocationData expressions[0].locationData, expressions[expressions.length - 1].locationData
          this

        addSource: (source) ->
          {@source  = no} = source
          attribs   = ["name", "index", "guard", "step", "own", "ownTag", "await", "awaitTag", "object", "from"]
          @[attr]   = source[attr] ? @[attr] for attr in attribs
          return this unless @source
          @index.error 'cannot use index with for-from' if @from and @index
          @ownTag.error "cannot use own with for-#{if @from then 'from' else 'in'}" if @own and not @object
          [@name, @index] = [@index, @name] if @object
          @index.error 'index cannot be a pattern matching expression' if @index?.isArray?() or @index?.isObject?()
          @awaitTag.error 'await must be used with for-from' if @await and not @from
          @range   = @source instanceof Value and @source.base instanceof Range and not @source.properties.length and not @from
          @pattern = @name instanceof Value
          @name.unwrap().propagateLhs?(yes) if @pattern
          @index.error 'indexes do not apply to range loops' if @range and @index
          @name.error 'cannot pattern match over range loops' if @range and @pattern
          @returns = no

- §

  Move up any comments in the “`for` line”, i.e. the line of code with `for`, from any child nodes of that line up to the `for` node itself so that these comments get output, and get output above the `for` loop.

          for attribute in ['source', 'guard', 'step', 'name', 'index'] when @[attribute]
            @[attribute].traverseChildren yes, (node) =>
              if node.comments

- §

  These comments are buried pretty deeply, so if they happen to be trailing comments the line they trail will be unrecognizable when we’re done compiling this `for` loop; so just shift them up to output above the `for` line.

                comment.newLine = comment.unshift = yes for comment in node.comments
                moveComments node, @[attribute]
            moveComments @[attribute], @
          this

- §

  Welcome to the hairiest method in all of CoffeeScript. Handles the inner loop, filtering, stepping, and result saving for array, object, and range comprehensions. Some of the generated code can be shared in common, and some cannot.

        compileNode: (o) ->
          body        = Block.wrap [@body]
          [..., last] = body.expressions
          @returns    = no if last?.jumps() instanceof Return
          source      = if @range then @source.base else @source
          scope       = o.scope
          name        = @name  and (@name.compile o, LEVEL_LIST) if not @pattern
          index       = @index and (@index.compile o, LEVEL_LIST)
          scope.find(name)  if name and not @pattern
          scope.find(index) if index and @index not instanceof Value
          rvar        = scope.freeVariable 'results' if @returns
          if @from
            ivar = scope.freeVariable 'x', single: true if @pattern
          else
            ivar = (@object and index) or scope.freeVariable 'i', single: true
          kvar        = ((@range or @from) and name) or index or ivar
          kvarAssign  = if kvar isnt ivar then "#{kvar} = " else ""
          if @step and not @range
            [step, stepVar] = @cacheToCodeFragments @step.cache o, LEVEL_LIST, shouldCacheOrIsAssignable
            stepNum   = parseNumber stepVar if @step.isNumber()
          name        = ivar if @pattern
          varPart     = ''
          guardPart   = ''
          defPart     = ''
          idt1        = @tab + TAB
          if @range
            forPartFragments = source.compileToFragments merge o,
              {index: ivar, name, @step, shouldCache: shouldCacheOrIsAssignable}
          else
            svar    = @source.compile o, LEVEL_LIST
            if (name or @own) and @source.unwrap() not instanceof IdentifierLiteral
              defPart    += "#{@tab}#{ref = scope.freeVariable 'ref'} = #{svar};\n"
              svar       = ref
            if name and not @pattern and not @from
              namePart   = "#{name} = #{svar}[#{kvar}]"
            if not @object and not @from
              defPart += "#{@tab}#{step};\n" if step isnt stepVar
              down = stepNum < 0
              lvar = scope.freeVariable 'len' unless @step and stepNum? and down
              declare = "#{kvarAssign}#{ivar} = 0, #{lvar} = #{svar}.length"
              declareDown = "#{kvarAssign}#{ivar} = #{svar}.length - 1"
              compare = "#{ivar} < #{lvar}"
              compareDown = "#{ivar} >= 0"
              if @step
                if stepNum?
                  if down
                    compare = compareDown
                    declare = declareDown
                else
                  compare = "#{stepVar} > 0 ? #{compare} : #{compareDown}"
                  declare = "(#{stepVar} > 0 ? (#{declare}) : #{declareDown})"
                increment = "#{ivar} += #{stepVar}"
              else
                increment = "#{if kvar isnt ivar then "++#{ivar}" else "#{ivar}++"}"
              forPartFragments = [@makeCode("#{declare}; #{compare}; #{kvarAssign}#{increment}")]
          if @returns
            resultPart   = "#{@tab}#{rvar} = [];\n"
            returnResult = "\n#{@tab}return #{rvar};"
            body.makeReturn rvar
          if @guard
            if body.expressions.length > 1
              body.expressions.unshift new If (new Parens @guard).invert(), new StatementLiteral "continue"
            else
              body = Block.wrap [new If @guard, body] if @guard
          if @pattern
            body.expressions.unshift new Assign @name, if @from then new IdentifierLiteral kvar else new Literal "#{svar}[#{kvar}]"

          varPart = "\n#{idt1}#{namePart};" if namePart
          if @object
            forPartFragments = [@makeCode("#{kvar} in #{svar}")]
            guardPart = "\n#{idt1}if (!#{utility 'hasProp', o}.call(#{svar}, #{kvar})) continue;" if @own
          else if @from
            if @await
              forPartFragments = new Op 'await', new Parens new Literal "#{kvar} of #{svar}"
              forPartFragments = forPartFragments.compileToFragments o, LEVEL_TOP
            else
              forPartFragments = [@makeCode("#{kvar} of #{svar}")]
          bodyFragments = body.compileToFragments merge(o, indent: idt1), LEVEL_TOP
          if bodyFragments and bodyFragments.length > 0
            bodyFragments = [].concat @makeCode('\n'), bodyFragments, @makeCode('\n')

          fragments = [@makeCode(defPart)]
          fragments.push @makeCode(resultPart) if resultPart
          forCode = if @await then 'for ' else 'for ('
          forClose = if @await then '' else ')'
          fragments = fragments.concat @makeCode(@tab), @makeCode( forCode),
            forPartFragments, @makeCode("#{forClose} {#{guardPart}#{varPart}"), bodyFragments,
            @makeCode(@tab), @makeCode('}')
          fragments.push @makeCode(returnResult) if returnResult
          fragments

        astNode: (o) ->
          addToScope = (name) ->
            alreadyDeclared = o.scope.find name.value
            name.isDeclaration = not alreadyDeclared
          @name?.eachName addToScope, checkAssignability: no
          @index?.eachName addToScope, checkAssignability: no
          super o

        astType: -> 'For'

        astProperties: (o) ->
          return
            source: @source?.ast o
            body: @body.ast o, LEVEL_TOP
            guard: @guard?.ast(o) ? null
            name: @name?.ast(o) ? null
            index: @index?.ast(o) ? null
            step: @step?.ast(o) ? null
            postfix: !!@postfix
            own: !!@own
            await: !!@await
            style: switch
              when @from   then 'from'
              when @object then 'of'
              when @name   then 'in'
              else              'range'

- §

  ### Switch

- §

  A JavaScript switch statement. Converts into a returnable expression on-demand.

      exports.Switch = class Switch extends Base
        constructor: (@subject, @cases, @otherwise) ->
          super()

        children: ['subject', 'cases', 'otherwise']

        isStatement: YES

        jumps: (o = {block: yes}) ->
          for {block} in @cases
            return jumpNode if jumpNode = block.jumps o
          @otherwise?.jumps o

        makeReturn: (results, mark) ->
          block.makeReturn(results, mark) for {block} in @cases
          @otherwise or= new Block [new Literal 'void 0'] if results
          @otherwise?.makeReturn results, mark
          this

        compileNode: (o) ->
          idt1 = o.indent + TAB
          idt2 = o.indent = idt1 + TAB
          fragments = [].concat @makeCode(@tab + "switch ("),
            (if @subject then @subject.compileToFragments(o, LEVEL_PAREN) else @makeCode "false"),
            @makeCode(") {\n")
          for {conditions, block}, i in @cases
            for cond in flatten [conditions]
              cond  = cond.invert() unless @subject
              fragments = fragments.concat @makeCode(idt1 + "case "), cond.compileToFragments(o, LEVEL_PAREN), @makeCode(":\n")
            fragments = fragments.concat body, @makeCode('\n') if (body = block.compileToFragments o, LEVEL_TOP).length > 0
            break if i is @cases.length - 1 and not @otherwise
            expr = @lastNode block.expressions
            continue if expr instanceof Return or expr instanceof Throw or (expr instanceof Literal and expr.jumps() and expr.value isnt 'debugger')
            fragments.push cond.makeCode(idt2 + 'break;\n')
          if @otherwise and @otherwise.expressions.length
            fragments.push @makeCode(idt1 + "default:\n"), (@otherwise.compileToFragments o, LEVEL_TOP)..., @makeCode("\n")
          fragments.push @makeCode @tab + '}'
          fragments

        astType: -> 'SwitchStatement'

        casesAst: (o) ->
          cases = []

          for kase, caseIndex in @cases
            {conditions: tests, block: consequent} = kase
            tests = flatten [tests]
            lastTestIndex = tests.length - 1
            for test, testIndex in tests
              testConsequent =
                if testIndex is lastTestIndex
                  consequent
                else
                  null

              caseLocationData = test.locationData
              caseLocationData = mergeLocationData caseLocationData, testConsequent.expressions[testConsequent.expressions.length - 1].locationData if testConsequent?.expressions.length
              caseLocationData = mergeLocationData caseLocationData, kase.locationData, justLeading: yes if testIndex is 0
              caseLocationData = mergeLocationData caseLocationData, kase.locationData, justEnding:  yes if testIndex is lastTestIndex

              cases.push new SwitchCase(test, testConsequent, trailing: testIndex is lastTestIndex).withLocationDataFrom locationData: caseLocationData

          if @otherwise?.expressions.length
            cases.push new SwitchCase(null, @otherwise).withLocationDataFrom @otherwise

          kase.ast(o) for kase in cases

        astProperties: (o) ->
          return
            discriminant: @subject?.ast(o, LEVEL_PAREN) ? null
            cases: @casesAst o

      class SwitchCase extends Base
        constructor: (@test, @block, {@trailing} = {}) ->
          super()

        children: ['test', 'block']

        astProperties: (o) ->
          return
            test: @test?.ast(o, LEVEL_PAREN) ? null
            consequent: @block?.ast(o, LEVEL_TOP).body ? []
            trailing: !!@trailing

      exports.SwitchWhen = class SwitchWhen extends Base
        constructor: (@conditions, @block) ->
          super()

        children: ['conditions', 'block']

- §

  ### If

- §

  If/else statements. Acts as an expression by pushing down requested returns to the last line of each clause.

  Single-expression Ifs are compiled into conditional operators if possible, because ternaries are already proper expressions, and don’t need conversion.

      exports.If = class If extends Base
        constructor: (@condition, @body, options = {}) ->
          super()
          @elseBody  = null
          @isChain   = false
          {@soak, @postfix, @type} = options
          moveComments @condition, @ if @condition.comments

        children: ['condition', 'body', 'elseBody']

        bodyNode:     -> @body?.unwrap()
        elseBodyNode: -> @elseBody?.unwrap()

- §

  Rewrite a chain of Ifs to add a default case as the final else.

        addElse: (elseBody) ->
          if @isChain
            @elseBodyNode().addElse elseBody
            @locationData = mergeLocationData @locationData, @elseBodyNode().locationData
          else
            @isChain  = elseBody instanceof If
            @elseBody = @ensureBlock elseBody
            @elseBody.updateLocationDataIfMissing elseBody.locationData
            @locationData = mergeLocationData @locationData, @elseBody.locationData if @locationData? and @elseBody.locationData?
          this

- §

  The If only compiles into a statement if either of its bodies needs to be a statement. Otherwise a conditional operator is safe.

        isStatement: (o) ->
          o?.level is LEVEL_TOP or
            @bodyNode().isStatement(o) or @elseBodyNode()?.isStatement(o)

        jumps: (o) -> @body.jumps(o) or @elseBody?.jumps(o)

        compileNode: (o) ->
          if @isStatement o then @compileStatement o else @compileExpression o

        makeReturn: (results, mark) ->
          if mark
            @body?.makeReturn results, mark
            @elseBody?.makeReturn results, mark
            return
          @elseBody  or= new Block [new Literal 'void 0'] if results
          @body     and= new Block [@body.makeReturn results]
          @elseBody and= new Block [@elseBody.makeReturn results]
          this

        ensureBlock: (node) ->
          if node instanceof Block then node else new Block [node]

- §

  Compile the `If` as a regular if-else statement. Flattened chains force inner else bodies into statement form.

        compileStatement: (o) ->
          child    = del o, 'chainChild'
          exeq     = del o, 'isExistentialEquals'

          if exeq
            return new If(@processedCondition().invert(), @elseBodyNode(), type: 'if').compileToFragments o

          indent   = o.indent + TAB
          cond     = @processedCondition().compileToFragments o, LEVEL_PAREN
          body     = @ensureBlock(@body).compileToFragments merge o, {indent}
          ifPart   = [].concat @makeCode("if ("), cond, @makeCode(") {\n"), body, @makeCode("\n#{@tab}}")
          ifPart.unshift @makeCode @tab unless child
          return ifPart unless @elseBody
          answer = ifPart.concat @makeCode(' else ')
          if @isChain
            o.chainChild = yes
            answer = answer.concat @elseBody.unwrap().compileToFragments o, LEVEL_TOP
          else
            answer = answer.concat @makeCode("{\n"), @elseBody.compileToFragments(merge(o, {indent}), LEVEL_TOP), @makeCode("\n#{@tab}}")
          answer

- §

  Compile the `If` as a conditional operator.

        compileExpression: (o) ->
          cond = @processedCondition().compileToFragments o, LEVEL_COND
          body = @bodyNode().compileToFragments o, LEVEL_LIST
          alt  = if @elseBodyNode() then @elseBodyNode().compileToFragments(o, LEVEL_LIST) else [@makeCode('void 0')]
          fragments = cond.concat @makeCode(" ? "), body, @makeCode(" : "), alt
          if o.level >= LEVEL_COND then @wrapInParentheses fragments else fragments

        unfoldSoak: ->
          @soak and this

        processedCondition: ->
          @processedConditionCache ?= if @type is 'unless' then @condition.invert() else @condition

        isStatementAst: (o) ->
          o.level is LEVEL_TOP

        astType: (o) ->
          if @isStatementAst o
            'IfStatement'
          else
            'ConditionalExpression'

        astProperties: (o) ->
          isStatement = @isStatementAst o

          return
            test: @condition.ast o, if isStatement then LEVEL_PAREN else LEVEL_COND
            consequent:
              if isStatement
                @body.ast o, LEVEL_TOP
              else
                @bodyNode().ast o, LEVEL_TOP
            alternate:
              if @isChain
                @elseBody.unwrap().ast o, if isStatement then LEVEL_TOP else LEVEL_COND
              else if not isStatement and @elseBody?.expressions?.length is 1
                @elseBody.expressions[0].ast o, LEVEL_TOP
              else
                @elseBody?.ast(o, LEVEL_TOP) ? null
            postfix: !!@postfix
            inverted: @type is 'unless'

- §

  A sequence expression e.g. `(a; b)`. Currently only used during AST generation.

      exports.Sequence = class Sequence extends Base
        children: ['expressions']

        constructor: (@expressions) ->
          super()

        astNode: (o) ->
          return @expressions[0].ast(o) if @expressions.length is 1
          super o

        astType: -> 'SequenceExpression'

        astProperties: (o) ->
          return
            expressions:
              expression.ast(o) for expression in @expressions

- §

  ## Constants

- §

      UTILITIES =
        modulo: -> 'function(a, b) { return (+a % (b = +b) + b) % b; }'

        boundMethodCheck: -> "
          function(instance, Constructor) {
            if (!(instance instanceof Constructor)) {
              throw new Error('Bound instance method accessed before binding');
            }
          }
        "

- §

  Shortcuts to speed up the lookup time for native functions.

        hasProp: -> '{}.hasOwnProperty'
        indexOf: -> '[].indexOf'
        slice  : -> '[].slice'
        splice : -> '[].splice'

- §

  Levels indicate a node’s position in the AST. Useful for knowing if parens are necessary or superfluous.

      LEVEL_TOP    = 1  # ...;
      LEVEL_PAREN  = 2  # (...)
      LEVEL_LIST   = 3  # [...]
      LEVEL_COND   = 4  # ... ? x : y
      LEVEL_OP     = 5  # !...
      LEVEL_ACCESS = 6  # ...[0]

- §

  Tabs are two spaces for pretty printing.

      TAB = '  '

      SIMPLENUM = /^[+-]?\d+$/
      SIMPLE_STRING_OMIT = /\s*\n\s*/g
      LEADING_BLANK_LINE  = /^[^\n\S]*\n/
      TRAILING_BLANK_LINE = /\n[^\n\S]*$/
      STRING_OMIT    = ///
          ((?:\\\\)+)      # Consume (and preserve) an even number of backslashes.
        | \\[^\S\n]*\n\s*  # Remove escaped newlines.
      ///g
      HEREGEX_OMIT = ///
          ((?:\\\\)+)     # Consume (and preserve) an even number of backslashes.
        | \\(\s)          # Preserve escaped whitespace.
        | \s+(?:#.*)?     # Remove whitespace and comments.
      ///g

- §

  ## Helper Functions

- §
- §

  Helper for ensuring that utility functions are assigned at the top level.

      utility = (name, o) ->
        {root} = o.scope
        if name of root.utilities
          root.utilities[name]
        else
          ref = root.freeVariable name
          root.assign ref, UTILITIES[name] o
          root.utilities[name] = ref

      multident = (code, tab, includingFirstLine = yes) ->
        endsWithNewLine = code[code.length - 1] is '\n'
        code = (if includingFirstLine then tab else '') + code.replace /\n/g, "$&#{tab}"
        code = code.replace /\s+$/, ''
        code = code + '\n' if endsWithNewLine
        code

- §

  Wherever in CoffeeScript 1 we might’ve inserted a `makeCode "#{@tab}"` to indent a line of code, now we must account for the possibility of comments preceding that line of code. If there are such comments, indent each line of such comments, and then indent the first following line of code.

      indentInitial = (fragments, node) ->
        for fragment, fragmentIndex in fragments
          if fragment.isHereComment
            fragment.code = multident fragment.code, node.tab
          else
            fragments.splice fragmentIndex, 0, node.makeCode "#{node.tab}"
            break
        fragments

      hasLineComments = (node) ->
        return no unless node.comments
        for comment in node.comments
          return yes if comment.here is no
        return no

- §

  Move the `comments` property from one object to another, deleting it from the first object.

      moveComments = (from, to) ->
        return unless from?.comments
        attachCommentsToNode from.comments, to
        delete from.comments

- §

  Sometimes when compiling a node, we want to insert a fragment at the start of an array of fragments; but if the start has one or more comment fragments, we want to insert this fragment after those but before any non-comments.

      unshiftAfterComments = (fragments, fragmentToInsert) ->
        inserted = no
        for fragment, fragmentIndex in fragments when not fragment.isComment
          fragments.splice fragmentIndex, 0, fragmentToInsert
          inserted = yes
          break
        fragments.push fragmentToInsert unless inserted
        fragments

      isLiteralArguments = (node) ->
        node instanceof IdentifierLiteral and node.value is 'arguments'

      isLiteralThis = (node) ->
        node instanceof ThisLiteral or (node instanceof Code and node.bound)

      shouldCacheOrIsAssignable = (node) -> node.shouldCache() or node.isAssignable?()

- §

  Unfold a node’s child if soak, then tuck the node under created `If`

      unfoldSoak = (o, parent, name) ->
        return unless ifn = parent[name].unfoldSoak o
        parent[name] = ifn.body
        ifn.body = new Value parent
        ifn

- §

  Constructs a string or regex by escaping certain characters.

      makeDelimitedLiteral = (body, {delimiter: delimiterOption, escapeNewlines, double, includeDelimiters = yes, escapeDelimiter = yes, convertTrailingNullEscapes} = {}) ->
        body = '(?:)' if body is '' and delimiterOption is '/'
        escapeTemplateLiteralCurlies = delimiterOption is '`'
        regex = ///
            (\\\\)                               # Escaped backslash.
          | (\\0(?=\d))                          # Null character mistaken as octal escape.
          #{
            if convertTrailingNullEscapes
              /// | (\\0) $ ///.source           # Trailing null character that could be mistaken as octal escape.
            else
              ''
          }
          #{
            if escapeDelimiter
              /// | \\?(#{delimiterOption}) ///.source # (Possibly escaped) delimiter.
            else
              ''
          }
          #{
            if escapeTemplateLiteralCurlies
              /// | \\?(\$\{) ///.source         # `${` inside template literals must be escaped.
            else
              ''
          }
          | \\?(?:
              #{if escapeNewlines then '(\n)|' else ''}
                (\r)
              | (\u2028)
              | (\u2029)
            )                                    # (Possibly escaped) newlines.
          | (\\.)                                # Other escapes.
        ///g
        body = body.replace regex, (match, backslash, nul, ...args) ->
          trailingNullEscape =
            args.shift() if convertTrailingNullEscapes
          delimiter =
            args.shift() if escapeDelimiter
          templateLiteralCurly =
            args.shift() if escapeTemplateLiteralCurlies
          lf =
            args.shift() if escapeNewlines
          [cr, ls, ps, other] = args
          switch

- §

  Ignore escaped backslashes.

            when backslash then (if double then backslash + backslash else backslash)
            when nul                  then '\\x00'
            when trailingNullEscape   then "\\x00"
            when delimiter            then "\\#{delimiter}"
            when templateLiteralCurly then "\\${"
            when lf                   then '\\n'
            when cr                   then '\\r'
            when ls                   then '\\u2028'
            when ps                   then '\\u2029'
            when other                then (if double then "\\#{other}" else other)
        printedDelimiter = if includeDelimiters then delimiterOption else ''
        "#{printedDelimiter}#{body}#{printedDelimiter}"

      sniffDirectives = (expressions, {notFinalExpression} = {}) ->
        index = 0
        lastIndex = expressions.length - 1
        while index <= lastIndex
          break if index is lastIndex and notFinalExpression
          expression = expressions[index]
          if (unwrapped = expression?.unwrap?()) instanceof PassthroughLiteral and unwrapped.generated
            index++
            continue
          break unless expression instanceof Value and expression.isString() and not expression.unwrap().shouldGenerateTemplateLiteral()
          expressions[index] =
            new Directive expression
            .withLocationDataFrom expression
          index++

      astAsBlockIfNeeded = (node, o) ->
        unwrapped = node.unwrap()
        if unwrapped instanceof Block and unwrapped.expressions.length > 1
          unwrapped.makeReturn null, yes
          unwrapped.ast o, LEVEL_TOP
        else
          node.ast o, LEVEL_PAREN

- §

  Helpers for `mergeLocationData` and `mergeAstLocationData` below.

      lesser  = (a, b) -> if a < b then a else b
      greater = (a, b) -> if a > b then a else b

      isAstLocGreater = (a, b) ->
        return yes if a.line > b.line
        return no unless a.line is b.line
        a.column > b.column

      isLocationDataStartGreater = (a, b) ->
        return yes if a.first_line > b.first_line
        return no unless a.first_line is b.first_line
        a.first_column > b.first_column

      isLocationDataEndGreater = (a, b) ->
        return yes if a.last_line > b.last_line
        return no unless a.last_line is b.last_line
        a.last_column > b.last_column

- §

  Take two nodes’ location data and return a new `locationData` object that encompasses the location data of both nodes. So the new `first_line` value will be the earlier of the two nodes’ `first_line` values, the new `last_column` the later of the two nodes’ `last_column` values, etc.

  If you only want to extend the first node’s location data with the start or end location data of the second node, pass the `justLeading` or `justEnding` options. So e.g. if `first`’s range is [4, 5] and `second`’s range is [1, 10], you’d get:

      mergeLocationData(first, second).range                   # [1, 10]
      mergeLocationData(first, second, justLeading: yes).range # [1, 5]
      mergeLocationData(first, second, justEnding:  yes).range # [4, 10]

      exports.mergeLocationData = mergeLocationData = (locationDataA, locationDataB, {justLeading, justEnding} = {}) ->
        return Object.assign(
          if justEnding
            first_line:   locationDataA.first_line
            first_column: locationDataA.first_column
          else
            if isLocationDataStartGreater locationDataA, locationDataB
              first_line:   locationDataB.first_line
              first_column: locationDataB.first_column
            else
              first_line:   locationDataA.first_line
              first_column: locationDataA.first_column
        ,
          if justLeading
            last_line:             locationDataA.last_line
            last_column:           locationDataA.last_column
            last_line_exclusive:   locationDataA.last_line_exclusive
            last_column_exclusive: locationDataA.last_column_exclusive
          else
            if isLocationDataEndGreater locationDataA, locationDataB
              last_line:             locationDataA.last_line
              last_column:           locationDataA.last_column
              last_line_exclusive:   locationDataA.last_line_exclusive
              last_column_exclusive: locationDataA.last_column_exclusive
            else
              last_line:             locationDataB.last_line
              last_column:           locationDataB.last_column
              last_line_exclusive:   locationDataB.last_line_exclusive
              last_column_exclusive: locationDataB.last_column_exclusive
        ,
          range: [
            if justEnding
              locationDataA.range[0]
            else
              lesser locationDataA.range[0], locationDataB.range[0]
          ,
            if justLeading
              locationDataA.range[1]
            else
              greater locationDataA.range[1], locationDataB.range[1]
          ]
        )

- §

  Take two AST nodes, or two AST nodes’ location data objects, and return a new location data object that encompasses the location data of both nodes. So the new `start` value will be the earlier of the two nodes’ `start` values, the new `end` value will be the later of the two nodes’ `end` values, etc.

  If you only want to extend the first node’s location data with the start or end location data of the second node, pass the `justLeading` or `justEnding` options. So e.g. if `first`’s range is [4, 5] and `second`’s range is [1, 10], you’d get:

      mergeAstLocationData(first, second).range                   # [1, 10]
      mergeAstLocationData(first, second, justLeading: yes).range # [1, 5]
      mergeAstLocationData(first, second, justEnding:  yes).range # [4, 10]

      exports.mergeAstLocationData = mergeAstLocationData = (nodeA, nodeB, {justLeading, justEnding} = {}) ->
        return
          loc:
            start:
              if justEnding
                nodeA.loc.start
              else
                if isAstLocGreater nodeA.loc.start, nodeB.loc.start
                  nodeB.loc.start
                else
                  nodeA.loc.start
            end:
              if justLeading
                nodeA.loc.end
              else
                if isAstLocGreater nodeA.loc.end, nodeB.loc.end
                  nodeA.loc.end
                else
                  nodeB.loc.end
          range: [
            if justEnding
              nodeA.range[0]
            else
              lesser nodeA.range[0], nodeB.range[0]
          ,
            if justLeading
              nodeA.range[1]
            else
              greater nodeA.range[1], nodeB.range[1]
          ]
          start:
            if justEnding
              nodeA.start
            else
              lesser nodeA.start, nodeB.start
          end:
            if justLeading
              nodeA.end
            else
              greater nodeA.end, nodeB.end

- §

  Convert Jison-style node class location data to Babel-style location data

      exports.jisonLocationDataToAstLocationData = jisonLocationDataToAstLocationData = ({first_line, first_column, last_line_exclusive, last_column_exclusive, range}) ->
        return
          loc:
            start:
              line:   first_line + 1
              column: first_column
            end:
              line:   last_line_exclusive + 1
              column: last_column_exclusive
          range: [
            range[0]
            range[1]
          ]
          start: range[0]
          end:   range[1]

- §

  Generate a zero-width location data that corresponds to the end of another node’s location.

      zeroWidthLocationDataFromEndLocation = ({range: [, endRange], last_line_exclusive, last_column_exclusive}) -> {
        first_line: last_line_exclusive
        first_column: last_column_exclusive
        last_line: last_line_exclusive
        last_column: last_column_exclusive
        last_line_exclusive
        last_column_exclusive
        range: [endRange, endRange]
      }

      extractSameLineLocationDataFirst = (numChars) -> ({range: [startRange], first_line, first_column}) -> {
        first_line
        first_column
        last_line: first_line
        last_column: first_column + numChars - 1
        last_line_exclusive: first_line
        last_column_exclusive: first_column + numChars
        range: [startRange, startRange + numChars]
      }

      extractSameLineLocationDataLast = (numChars) -> ({range: [, endRange], last_line, last_column, last_line_exclusive, last_column_exclusive}) -> {
        first_line: last_line
        first_column: last_column - (numChars - 1)
        last_line: last_line
        last_column: last_column
        last_line_exclusive
        last_column_exclusive
        range: [endRange - numChars, endRange]
      }

- §

  We don’t currently have a token corresponding to the empty space between interpolation/JSX expression braces, so piece together the location data by trimming the braces from the Interpolation’s location data. Technically the last_line/last_column calculation here could be incorrect if the ending brace is preceded by a newline, but last_line/last_column aren’t used for AST generation anyway.

      emptyExpressionLocationData = ({interpolationNode: element, openingBrace, closingBrace}) ->
        first_line:            element.locationData.first_line
        first_column:          element.locationData.first_column + openingBrace.length
        last_line:             element.locationData.last_line
        last_column:           element.locationData.last_column - closingBrace.length
        last_line_exclusive:   element.locationData.last_line
        last_column_exclusive: element.locationData.last_column
        range: [
          element.locationData.range[0] + openingBrace.length
          element.locationData.range[1] - closingBrace.length
        ]