• ジャンプ先 … +
    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 には、構文ツリーのすべてのノードクラスが含まれています。ほとんどのノードは文法のアクションの結果として作成されますが、コード生成の方法として他のノードによって作成されるものもあります。構文ツリーを JavaScript コードの文字列に変換するには、ルートで compile() を呼び出します。

    Error.stackTraceLimit = Infinity
    
    {Scope} = require './scope'
    {isUnassignable, JS_FORBIDDEN} = require './lexer'
  • §

    使用予定のヘルパーをインポートします。

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

    パーサーに必要な関数。

    exports.extend = extend
    exports.addDataToNode = addDataToNode
  • §

    カスタマイズを必要としないノードの定数関数。

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

    コードフラグメント

  • §

    以下に定義されているさまざまなノードはすべて、コードフラグメントオブジェクトのコレクションにコンパイルされます。コードフラグメントは生成されたコードのブロックと、コードのソースファイル内の場所です。コードフラグメントは、すべてのコードフラグメントのcodeスニペットを順番に連結するだけで、動作するコードに組み立てられます。

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

    これはデバッグのみを目的としています。

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

    コードフラグメントの配列を文字列に変換します。

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

    基底

  • §

    基底は、構文ツリー内のすべてのノードの抽象基底クラスです。各サブクラスは、そのノードのコード生成を実行するcompileNodeメソッドを実装します。ノードを JavaScript にコンパイルするには、compile を呼び出します。これは、生成されたコードをクロージャでラップする必要がある場合を認識するために、compileNode をいくつかの一般的な追加機能でラップします。オプションハッシュが渡され、ツリーの上位からの環境に関する情報(周囲の関数によって戻り値が要求されているかどうかなど)、現在のスコープに関する情報、インデントレベルなどが含まれています。

    exports.Base = class Base
    
      compile: (o, lvl) ->
        fragmentsToText @compileToFragments o, lvl
  • §

    スコープ追跡に追加する変数の名前を取得するなど、ノードが複数回コンパイルされる場合があります。「時期尚早な」コンパイルによってコメントが出力されないことがわかっている場合は、出力にコメントが含まれる後続のcompile呼び出しのために保存されるように、それらのコメントを脇に置いておきます。

      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'
  • §

    このノードをコンパイルする前にクロージャでラップする必要があるか、直接コンパイルする必要があるかを判断するための共通ロジック。このノードが*ステートメント*であり、*純粋なステートメント*ではなく、ブロックの最上位ではなく(不要になります)、結果を返すようにまだ求められていない場合(ステートメントは結果を返す方法を知っているため)、ラップする必要があります。

      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'
  • §

    クロージャラッピングによって式に変換されたステートメントは、期待されるレキシカルスコープを保持するために、親クロージャとスコープオブジェクトを共有します。

      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
  • §

    ここでは、commentsプロパティとしてノードにアタッチされているコメントがCodeFragmentになります。コード行内に散在する `/* */` 区切りのコメントなどの「インラインブロックコメント」は、現在の `fragments` ストリームに追加されます。他のすべてのフラグメントは、直前または直後のフラグメントのプロパティとしてアタッチされ、`compileComments` で適切に出力されるまで密航者のままになります。

        unshiftCommentFragment = (commentFragment) ->
          if commentFragment.unshift
  • §

    最初のコメント以外のフラグメントを見つけて、その前にcommentFragmentを挿入します。

            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.
  • §

    1 + ### comment ### 2 のようなインラインコメントである `###` で示されるブロック/ここコメントの場合、フラグメントを作成してフラグメント配列に挿入します。それ以外の場合は、コメントフラグメントを今のところ最も近いフラグメントにアタッチして、すべての改行が追加された後に後で出力に挿入できるようにします。

          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()
  • §

    1 + /* comment */ 2 のようなインラインブロックコメント、または出力にコメントを出力するためのロジックを持つ `compileToFragments` メソッドを持つノード。

            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
  • §

    コード生成で複雑な式の結果を複数箇所で使用する場合、一時変数に代入することで、式が一度だけ評価されるようにします。レベルを渡してプリコンパイルします。

    level が渡された場合、[val, ref] を返します。ここで、val はコンパイルされた値、ref はコンパイルされた参照です。level が渡されない場合、これは2つの値がコンパイルされていない生のノードである `[val, ref]` を返します。

      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]
  • §

    式の結果がソース内のその場所の前に利用可能になるように、式を「巻き上げられた」かのように動作させることが便利な場合があります。ただし、式の変数スコープはソースの位置に対応します。これは、クラスの実行可能なクラス本体を処理するために広く使用されています。

    このメソッドを呼び出すと、ノードが変更され、`compileNode` および `compileToFragments` メソッドがプロキシされて、後で呼び出しによって返される `target` ノードを置き換えるための結果が格納されます。

      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])]
  • §

    現在のノードの結果を返すノードを構築します。これは、多くのステートメントノード(例:`If`、`For`)のよりスマートな動作のためにオーバーライドされることに注意してください。

      makeReturn: (results, mark) ->
        if mark
  • §

    このノードを暗黙的に返されるものとしてマークして、AST で返されるノードメタデータの一部にすることができます。

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

    このノード、またはその子に、特定の種類のノードが含まれていますか?*children* ノードを再帰的にトラバースし、`pred` を検証する最初のノードを返します。それ以外の場合は undefined を返します。`contains` はスコープ境界を越えません。

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

    ノードリストの最後のノードを取り出します。

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

    解析ツリーを検査するための、ノードのデバッグ表現。これは `coffee --nodes` が出力するものです。

      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'
  • §

    JSON としてシリアル化できる、ノードのプレーンな JavaScript オブジェクト表現。これは、Node API の `ast` オプションが返すものです。他のツールとの相互運用性を向上させるために、Babel AST 仕様 にできるだけ厳密に従うようにしています。**警告:子クラスでこのメソッドをオーバーライドしないでください。** 必要に応じて、コンポーネントの `ast*` メソッドのみをオーバーライドしてください。

      ast: (o, level) ->
  • §

    level を o にマージし、他の普遍的なチェックを実行します。

        o = @astInitialize o, level
  • §

    このノードのシリアル化可能な表現を作成します。

        astNode = @astNode o
  • §

    (暗黙的に)返される式に対応する AST ノードをマークします。親ノードが返されることをマークする前に、最初に子ノードをアセンブルする必要があるため、これは `astNode` の一部として実行できません。

        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()
  • §

    後者が子ノードに対して `.ast()` を呼び出す可能性があり、それらのノードは `makeReturn` からの戻りロジックがその時までに既に実行されている必要があるため、`@makeReturn` は `astProperties` の前に呼び出す必要があります。

        @makeReturn null, yes if @isStatement(o) and o.level isnt LEVEL_TOP and o.scope?
        o
    
      astNode: (o) ->
  • §

    すべての抽象構文ツリーノードオブジェクトには、4つのカテゴリのプロパティがあります

    • タイプ。`type` フィールドに格納され、`NumberLiteral` のような文字列です。
    • 場所データ。`loc`、`start`、`end`、`range` フィールドに格納されます。
    • このノードに固有のプロパティ。`parsedValue` などです。
    • それ自体が子ノードであるプロパティ。`body` などです。これらのフィールドはすべて Babel の仕様で混在しています。`type` と `start` と `parsedValue` はすべて、AST ノードオブジェクトのトップレベルフィールドです。ここでは、各カテゴリを返すための個別のメソッドがあり、それらをここでマージします。
        Object.assign {}, {type: @astType(o)}, @astProperties(o), @astLocationData()
  • §

    デフォルトでは、ノードクラスには特定のプロパティがありません。

      astProperties: -> {}
  • §

    デフォルトでは、ノードクラスの AST `type` はそのクラス名です。

      astType: -> @constructor.name
  • §

    AST の場所データは、Jison の場所データを再配置したもので、Babel の仕様が使用する構造に変換されています。

      astLocationData: ->
        jisonLocationDataToAstLocationData @locationData
  • §

    AST ノードに `ExpressionStatement` ラッパーが必要かどうかを判断します。通常は `isStatement()` ロジックと一致しますが、これはオーバーライドできます。

      isStatementAst: (o) ->
        @isStatement o
  • §

    各子を関数に渡し、関数が `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` は、`match` が true を返すノードを探して子をトラバースします。見つかったら、一致するノードは `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
  • §

    一般的なノードプロパティとメソッドのデフォルト実装。ノードは、必要に応じて、カスタムロジックでこれらをオーバーライドします。

  • §

    `children` は、ツリートラバーサル時に再帰するプロパティです。`children` リスト *が* AST の構造です。`parent` ポインタと `children` へのポインタは、ツリーをトラバースする方法です。

      children: []
  • §

    `isStatement` は「すべてが式である」ことに関係しています。`break` など、式にできないものがいくつかあります。`isStatement` が `true` を返すものは、式として使用できないものです。`nodes.coffee` には、ステートメントが式の位置に ended up することによるエラーメッセージがいくつかあります。

      isStatement: NO
  • §

    フラグメントにコンパイルされたコメントを追跡して、2回出力されないようにします。

      compiledComments: []
  • §

    `includeCommentFragments` は、`compileCommentFragments` に、このノードがその出力内のコメントを処理する方法を特別に認識しているかどうかを知らせます。

      includeCommentFragments: NO
  • §

    `jumps` は、式、または式の内部部分に、通常の制御フローからジャンプし、値として使用できない `break`、`continue`、`return` などのフロー制御構造体があるかどうかを示します。(`throw` はフロー制御構造体とは見なされません。)これは、式の中央にあるフロー制御は意味がないため重要です。許可しない必要があります。

      jumps: NO
  • §

    `node.shouldCache() is false` の場合、`node` を複数回安全に使用できます。それ以外の場合は、`node` の値を変数に格納し、代わりにその変数を複数回出力する必要があります。このように:`5` はキャッシュする必要はありません。ただし、`returnFive()` は、複数回評価した結果として副作用が発生する可能性があるため、キャッシュする必要があります。パラメータの名前が `mustCache` ではなく `shouldCache` であるのは、キャッシュする必要がない場合でもキャッシュしたい場合があるためです。たとえば、冪等である可能性が高い長い式ですが、簡潔にするためにキャッシュしたい場合があります。

      shouldCache: YES
    
      isChainable: NO
      isAssignable: NO
      isNumber: NO
    
      unwrap: THIS
      unfoldSoak: NO
  • §

    このノードは、特定の変数を割り当てるために使用されていますか?

      assigns: NO
  • §

    このノードとそのすべての子孫について、ロケーションデータがまだ設定されていない場合、locationData に設定します。

      updateLocationDataIfMissing: (locationData, force) ->
        @forceUpdateLocation = yes if force
        return this if @locationData and not @forceUpdateLocation
        delete @forceUpdateLocation
        @locationData = locationData
    
        @eachChild (child) ->
          child.updateLocationDataIfMissing locationData
  • §

    別のノードからロケーションデータを追加します。

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

    別のノードからロケーションデータとコメントを追加します。

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

    このノードのロケーションに関連付けられた SyntaxError をスローします。

      error: (message) ->
        throwSyntaxError message, @locationData
    
      makeCode: (code) ->
        new CodeFragment this, code
    
      wrapInParentheses: (fragments) ->
        [@makeCode('('), fragments..., @makeCode(')')]
    
      wrapInBraces: (fragments) ->
        [@makeCode('{'), fragments..., @makeCode('}')]
  • §

    fragmentsList はフラグメントの配列の配列です。 fragmentsList 内の各配列は連結され、間に joinStr が追加されて、フラグメントの最終的なフラット配列が生成されます。

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

    ホイストターゲット

  • §

    HoistTargetNode は、ホイストされたノードのノードツリー内の出力位置を表します。 Base#hoist を参照してください。

    exports.HoistTarget = class HoistTarget extends Base
  • §

    指定された配列内のホイストされたフラグメントを展開します。

      @expand = (fragments) ->
        for fragment, i in fragments by -1 when fragment.fragments
          fragments[i..i] = @expand fragment.fragments
        fragments
    
      constructor: (@source) ->
        super()
  • §

    ソースノードがコンパイルされるときに適用される表示オプションを保持します。

        @options = {}
  • §

    ソースノードのコンパイルによって置き換えられるプレースホルダーフラグメント。

        @targetFragments = { fragments: [] }
    
      isStatement: (o) ->
        @source.isStatement o
  • §

    ターゲットフラグメントをソースのコンパイル結果で更新します。 指定されたコンパイル関数をノードとオプション(ターゲットの表示オプションでオーバーライド)を使用して呼び出します。

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

    ターゲットのインデントとレベルをコピーし、プレースホルダーフラグメントを返します。

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

    ルート

  • §

    ノードツリーのルートノード

    exports.Root = class Root extends Base
      constructor: (@body) ->
        super()
    
        @isAsync = (new Code [], @body).isAsync
    
      children: ['body']
  • §

    要求されない限り、すべてを安全クロージャでラップします。 最初に生成しない方が良いですが、今のところ、明らかな二重括弧をクリーンアップします。

      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 ? []
  • §

    ルートスコープ内の指定されたローカル変数をパラメータとしてマークして、ルートブロックで宣言されないようにします。

        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()
  • §

    ブロック

  • §

    ブロックは、インデントされたコードブロックの本体を形成する式リストです。関数の実装、if、switch、または try の句などです。

    exports.Block = class Block extends Base
      constructor: (nodes) ->
        super()
    
        @expressions = compact flatten nodes or []
    
      children: ['expressions']
  • §

    この式リストの最後に式を追加します。

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

    この式リストの最後の式を削除して返します。

      pop: ->
        @expressions.pop()
  • §

    この式リストの先頭に式を追加します。

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

    このブロックが単一のノードで構成されている場合は、それを引き出してラップ解除します。

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

    これは空のコードブロックですか?

      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
  • §

    Block ノードは本体全体を返すのではなく、最後の式が返されることを保証します。

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

    また、同じレベルに隣接する JSX タグがある場合、JSX タグを返していないことを確認する必要があります。 JSX はそれを許可しません。

        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
  • §

    Block 本体内のすべての式をコンパイルします。 結果を返す必要があり、それが式の場合は、単にそれを返します。 ステートメントの場合は、ステートメントにそうするように指示します。

      compileNode: (o) ->
        @tab  = o.indent
        top   = o.level is LEVEL_TOP
        compiledNodes = []
    
        for node, index in @expressions
          if node.hoisted
  • §

    これはホイストされた式です。 これをコンパイルし、結果を無視します。

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

    これはネストされたブロックです。 ここでは、新しいスコープで囲むなど、特別なことはしません。 このブロックのステートメントを独自のステートメントと一緒にコンパイルするだけです。

            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
  • §

    すべての内部変数の宣言が先頭にプッシュされた状態で、関数の中身の式本体をコンパイルします。

      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
  • §

    次のまたは前の改行に出力にコメントを挿入します。 コメントを配置する改行がない場合は、作成します。

          if fragment.precedingComments
  • §

    コメントを挿入しようとしているフラグメントのインデントレベルを決定し、挿入されたコメントにそのインデントレベルを使用します。 この時点で、フラグメントの code プロパティは生成された出力 JavaScript であり、CoffeeScript は常に2つのスペースでインデントされた出力を生成します。 したがって、2つ以上のスペースで始まる code プロパティを検索するだけで済みます。

            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
  • §

    フラグメントがなくなるか、文字列内に補間されたコードブロックにいることが判明するまで、前のフラグメントを検索し続けます。

                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
  • §

    はい、これは前の if ブロックと非常に似ていますが、よく見ると、両方のブロックが共有する関数に抽象化された場合に混乱を招く小さな違いがたくさんあります。

          if fragment.followingComments
  • §

    最初の末尾のコメントは、; // Comment のようにコード行の最後に続くのですか、それともコード行の後に新しい行を開始するのですか?

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

    出力する末尾以外のコメントがある場合、次のコード行のインデントを見つけます。 これらのコメントはその後に出力されるため、最初に次の改行を見つける必要があります。 次に、次の改行に続く行のインデント。

            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
  • §

    このコメントは、ベアモードによって挿入されたインデントに従っていますか? もしそうなら、これをさらにインデントする必要はありません。

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

    適切にインデントされたコメントを組み立てます。

            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
  • §

    フラグメントがなくなるか、文字列内に補間されたコードブロックにいることが判明するまで、今後のフラグメントを検索し続けます。

                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
  • §

    余分な空白行を挿入しないようにします。

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

    指定されたノードを Block としてラップします。ただし、すでに Block である場合は除きます。

      @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
  • §

    生成された PassthroughLiteral を無視します。

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

    式がステートメントの場合、そのまま本文に追加できます。

          else if expression.isStatementAst o
            body.push expressionAst
  • §

    それ以外の場合は、ExpressionStatement AST ノードにラップする必要があります。

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

    今のところ、Program AST ノードに sourceType を含めていません。 その値は 'script' または 'module' のいずれかになり得ますが、CoffeeScript が常にどちらであるべきかを知る方法はありません。 ソースコードに import または export ステートメントが存在すると、それが module であることが暗示されますが、プロジェクトはそのようなファイルで主に構成され、import または export がない外れ値ファイルも含まれる場合があります。それでもプロジェクトにインポートされ、したがって module として扱われることを期待しています。 sourceType の値を決定することは、JavaScript ファイルの解析目標(これも module または script)を決定することによって提起される課題と本質的に同じであるため、Node が .js ファイルに対してその方法を見つけ出した場合、CoffeeScript は Node のアルゴリズムをコピーできます。

  • §

    sourceType: 'module'

          body, directives
        }
    
      astLocationData: ->
        return if @isRootBlock and not @locationData?
        super()
  • §

    ディレクティブ、例えば「use strict」。 現在、AST 生成中にのみ使用されます。

    exports.Directive = class Directive extends Base
      constructor: (@value) ->
        super()
    
      astProperties: (o) ->
        return
          value: Object.assign {},
            @value.ast o
            type: 'DirectiveLiteral'
  • §

    リテラル

  • §

    Literal は、文字列、数値、true、false、null など、変換せずに JavaScript に直接渡すことができる静的値の基底クラスです。

    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: ->
  • §

    これはデバッグのみを目的としています。

        " #{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 は、リテラル文字列全体または補間された文字列などの内部のテキストの一部を表すことができます。 前者として解析されたが、後者として扱われる必要がある場合(たとえば、タグ付きテンプレートリテラルの文字列部分)、これはロケーションデータから引用符がトリミングされた StringLiteral のコピーを返します(補間された文字列の一部として解析された場合と同様)。

      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 は常に '`'、'\`'、'\\`' などの値です。 後半に減らすことで、'`' を '`' に、'\\\`' を '`' に変換します。

          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 は *pureStatement* です。クロージャでラップしても意味がありません。

    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:ここで expression.compile() を2回呼び出すと、異なる結果が返されることがあります!

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

    return は @tab によってインデントされているため、複数行の先行コメントはインデントする必要があります。

          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: ->
  • §

    await return/yield return からの return を無効としてフラグ付けしないでください。

        return if @belongsToFuncDirectiveReturn
        super()
    
      astType: -> 'ReturnStatement'
    
      astProperties: (o) ->
        argument: @expression?.ast(o, LEVEL_PAREN) ? null
  • §

    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 は、関数をジェネレーターに変換することを除いて、return とまったく同じように機能します。

    exports.YieldReturn = class YieldReturn extends FuncDirectiveReturn
      keyword: 'yield'
    
    exports.AwaitReturn = class AwaitReturn extends FuncDirectiveReturn
      keyword: 'await'
  • §

    値

  • §

    値、変数、リテラル、または括弧で囲まれた、インデックス付けまたはドットで囲まれた、またはバニラ。

    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
  • §

    これが @foo = 割り当ての場合、@ にコメントがある場合は、foo に移動します。

        if @base?.comments and @base instanceof ThisLiteral and @properties[0]?.name?
          moveComments @base, @properties[0].name
    
      children: ['base', 'properties']
  • §

    プロパティ(または *プロパティ*)Access をリストに追加します。

      add: (props) ->
        @properties = @properties.concat props
        @forceUpdateLocation = yes
        this
    
      hasProperties: ->
        @properties.length isnt 0
    
      bareLiteral: (type) ->
        not @properties.length and @base instanceof type
  • §

    他のノードの利益のためのいくつかのブール値チェック。

      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
  • §

    アタッチされたプロパティがない場合、値は内部ノードとしてラップ解除できます。

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

    参照にはベース部分(this 値)と名前部分があります。 複雑な式をコンパイルするために、それらを個別にキャッシュします。 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])]
  • §

    各プロパティをコンパイルして結合することで、値を JavaScript にコンパイルします。 プロパティのチェーンに *soak* 演算子 ?. が散在していると、事態ははるかに面白くなります。 その後、ソークチェーンを構築するときに誤って2回評価しないように注意する必要があります。

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

    キャッシュされたフラグメントは、コンパイルの正しい順序と、スコープ内の変数の再利用を可能にします。 例:a(x = 5).b(-> x = 6) は a(x = 5); b(-> x = 6) と同じ順序でコンパイルする必要があります(問題#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
  • §

    ソークを 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'
  • §

    AST 生成の場合、プロパティがある場合、この Value から最後のプロパティを引いた object が必要です。

      object: ->
        return @ unless @hasProperties()
  • §

    最後以外のすべてのプロパティを取得します。 プロパティが1つだけの Value の場合、initialProperties は空の配列です。

        initialProperties = @properties[0.[email protected] - 1]
  • §

    分割された最後のプロパティの新しい「ベース」となる object を作成します。

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

    新しいノードにロケーションデータを追加して、ソースマップまたは後の AST ロケーションデータへの変換のための正しいロケーションデータを持つようにします。

        object.locationData =
          if initialProperties.length is 0
  • §

    この新しい Value にはプロパティが1つしかないため、ロケーションデータは親 Value のベースのロケーションデータと同じです。

            @base.locationData
          else
  • §

    この新しい Value には複数のプロパティがあるため、ロケーションデータは親 Value のベースからこの新しいノードに含まれる最後のプロパティ(つまり、親の最後から2番目のプロパティ)まで及んでいます。

            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) ->
  • §

    Value にプロパティがない場合、AST ノードはこのノードの base が何であるかです。

        return @base.ast o unless @hasProperties()
  • §

    それ以外の場合は、Base::ast を呼び出します。これは、以下の astType メソッドと astProperties メソッドを呼び出します。

        super o
    
      astType: ->
        if @isJSXTag()
          'JSXMemberExpression'
        else if @containsSoak()
          'OptionalMemberExpression'
        else
          'MemberExpression'
  • §

    この Value にプロパティがある場合、*最後の* プロパティ(例:a.b.c の c)は property になり、先行するプロパティ(例:a.b)は object プロパティに割り当てられた子 Value ノードになります。

      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()
  • §

    JSX タグの先頭の < をロケーションデータに含めないでください。

        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

  • §

    ### で区切られたコメント(/* */ になります)。

    exports.HereComment = class HereComment extends Base
      constructor: ({ @content, @newLine, @unshift, @locationData }) ->
        super()
    
      compileNode: (o) ->
        multiline = '\n' in @content
  • §

    複数行のコメントのインデントを解除します。 後で再びインデントされます。

        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
  • §

    コンパイラが縮小されると壊れる可能性のある fragment.type に依存しないでください。

        fragment.isComment = fragment.isHereComment = yes
        fragment
    
      astType: -> 'CommentBlock'
    
      astProperties: ->
        return
          value: @content
  • §

    LineComment

  • §

    # から行末まで実行されるコメント(// になります)。

    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
  • §

    コンパイラが縮小されると壊れる可能性のある fragment.type に依存しないでください。

        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
  • §

    値のない属性、例:disabled

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

    オブジェクトスプレッド属性、例:{…props}

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

    値を持つ属性を含むオブジェクト、例: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']
  • §

    無効な属性をキャッチします:<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
  • §

    JSX 要素のノード

    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: ->
        [email protected]
    
      astNode: (o) ->
  • §

    開始要素 < … > にまたがるロケーションデータは、要素の属性を含む生成された Arr によってキャプチャされます。

        @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
  • §

    <a><b /></a> と <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
  • §

    呼び出し

  • §

    関数呼び出しのノード。

    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 は、このノードが RegexWithInterpolations の一部として作成される結果として出力されることはありません。そのため、この場合は、文法を介して RegexWithInterpolations に渡される args プロパティにコメントを移動してください。

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

    場所を設定する場合、新しく発見された左側の new 演算子を考慮して、開始位置を更新する必要がある場合があります。これは左側の範囲を拡張しますが、右側の範囲は拡張しません。

      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
  • §

    この呼び出しを新しいインスタンスを作成するものとしてタグ付けします。

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

    連鎖された呼び出しは、if/else 三項構造に展開されます。

      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
  • §

    通常の関数呼び出しをコンパイルします。

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

    変数が Accessor の場合、フラグメントはキャッシュされ、後で Value::compileNode で使用されて、コンパイルの正しい順序とスコープ内の変数の再利用が保証されます。例:a(x = 5).b(-> x = 6) は a(x = 5); b(-> x = 6) と同じ順序でコンパイルする必要があります(問題 #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(親クラス)

  • §

    super() 呼び出しを、プロトタイプの同名の関数に対する呼び出しに変換します。expressions が設定されている場合、呼び出しは、SuperCall 式の戻り値を変更せずに式が評価されるようにコンパイルされます。

    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
  • §

    式の中にある可能性がある場合は、結果をキャッシュして返す必要があります

          [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
  • §

    super() 呼び出しは、たとえば super.method() にコンパイルされます。つまり、method プロパティ名はここで初めてコンパイルされ、クラスの method: プロパティがコンパイルされるときに再びコンパイルされます。このコンパイルは最初に行われるため、method: に添付されたコメントは、super.method() の近くに誤って出力されます。method: が出力される2回目のパスで出力されるようにしたいので、このコンパイルパス中にそれらを脇に置いて、後のコンパイルのためにオブジェクトに戻します。

          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(補間を含む正規表現)

  • §

    補間を含む正規表現は、実際には、内部に StringWithInterpolations を持つ Call(正確には RegExp() 呼び出し)のバリエーションです。

    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(拡張)

  • §

    オブジェクトのプロトタイプを祖先オブジェクトで拡張するためのノード。Closure Library の goog.inherits に倣っています。

    exports.Extends = class Extends extends Base
      constructor: (@child, @parent) ->
        super()
    
      children: ['child', 'parent']
  • §

    あるコンストラクタを別のコンストラクタのプロトタイプチェーンにフックします。

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

    Access(アクセス)

  • §

    値のプロパティへの . アクセス、またはオブジェクトのプロトタイプへのアクセスのための :: shorthand です。

    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 には Access 用の AST ノードがありませんが、この Access ノードの子である name Identifier ノードを MemberExpression ノードの property として含んでいます。

        @name.ast o
  • §

    Index(インデックス)

  • §

    配列またはオブジェクトへの [ ... ] インデックス付きアクセスです。

    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 には Index 用の AST ノードがありませんが、この Index ノードの子である index Identifier ノードを MemberExpression ノードの property として含んでいます。MemberExpression の property が Index であるということは、MemberExpression の computed が true であることを意味します。

        @index.ast o
  • §

    Range(範囲)

  • §

    範囲リテラル。範囲は、配列の一部(スライス)を抽出したり、内包表記の範囲を指定したり、値として使用して実行時に対応する整数の配列に展開したりするために使用できます。

    exports.Range = class Range extends Base
    
      children: ['from', 'to']
    
      constructor: (@from, @to, tag) ->
        super()
    
        @exclusive = tag is 'exclusive'
        @equals = if @exclusive then '' else '='
  • §

    範囲のソース変数(開始位置と終了位置)をコンパイルします。ただし、二重評価を避けるためにキャッシュする必要がある場合のみです。

      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
  • §

    通常コンパイルされる場合、範囲は、範囲内の値を反復処理するために必要な *for ループ* の内容を返します。内包表記で使用されます。

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

    エンドポイントを設定します。

        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}"]
  • §

    条件を生成します。

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

    無限ループを避けるために、常に step がゼロでないことを確認します。

        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}"
  • §

    ステップを生成します。

        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
  • §

    最終的なループ本体。

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

    値として使用される場合、範囲を等価な配列に展開します。

      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(スライス)

  • §

    配列スライスリテラル。JavaScript の Array#slice とは異なり、2 番目のパラメータは、最初のパラメータが始まりのインデックスであるのと同様に、スライスの終わりのインデックスを指定します。

    exports.Slice = class Slice extends Base
    
      children: ['range']
    
      constructor: (@range) ->
        super()
  • §

    配列の終わりまでスライスしようとすると注意が必要です。すべての実装が undefined または 1/0 を尊重するわけではないため、9e9 が使用されます。9e9 > 2**32(最大配列長)であるため、9e9 は安全です。

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

    プロパティアクセスで式を処理します。たとえば、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(オブジェクト)

  • §

    オブジェクトリテラル。特別なことはありません。

    exports.Obj = class Obj extends Base
      constructor: (props, @generated = no) ->
        super()
    
        @objects = @properties = props or []
    
      children: ['properties']
    
      isAssignable: (opts) ->
        for prop in @properties
  • §

    予約語をチェックします。

          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()
  • §

    オブジェクトに splat が含まれているかどうかを確認します。

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

    rest プロパティをリストの最後に移動します。{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
  • §

    このオブジェクトが代入の左辺である場合、すべての子も左辺です。

        @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()] } は { [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] } は { [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?
  • §

    「ベア」プロパティを ObjectProperty(または Splat)に変換します。

      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
  • §

    デフォルト値を持つ省略形プロパティ。例:{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'
  • §

    省略形ではないすべてのプロパティ(つまり、: を含む)。

            @value = value
          else
  • §

    デフォルト値を持つ左辺の省略形。例:{a = 1} = b。

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

    デフォルト値のない省略形。例:{a} または {@a} または {[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(配列)

  • §

    配列リテラル。

    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 ','
  • §

    配列の先頭にある Elision が処理されているかどうかを検出します(例:[, , , a])。

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

    この配列のコンパイル時に作成されたフラグメントにブロックコメントを挿入するように compileCommentFragments に指示します。

          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
  • §

    compiledObjs に改行が含まれている場合、これは複数行の配列として出力されます(つまり、[ の後に改行とインデントが続きます)。要素に行コメントが含まれている場合、行コメントは定義上出力に改行を導入するため、複数行の出力もトリガーする必要があります。最初の要素にのみ行コメントがある場合を除きます。その場合は、そうでない場合はコンパクトな形式として出力します。そのため、最初の要素の行コメントは配列の前または後に出力されます。

        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
  • §

    配列の先頭からすべての Elision が処理され(例:[, , , a])、要素が Elision ではなく、最後の要素が Elision(例:[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
  • §

    この配列が代入の左辺である場合、すべての子も左辺です。

      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(クラス)

  • §

    CoffeeScript クラス定義。名前、オプションの親クラス、および本体を使用して **クラス** を初期化します。

    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
  • §

    class expr.A extends A 宣言を許可するための特別な処理

        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
  • §

    匿名クラスは式でのみ有効です

          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
  • §

    このクラスに適切な名前を決定します

      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
  • §

    クラスイニシャライザに式を追加します

    これは、クラス本体内の式がイニシャライザに表示されるべきか、実行可能本体に表示されるべきかを判断するための重要なメソッドです。指定された node がクラス本体で有効な場合、メソッドはクラスイニシャライザに含めるための(新しい、変更された、または同一の)ノードを返します。そうでない場合は何も返されず、ノードは実行可能本体に表示されます。

    この記事の執筆時点では、ES クラスイニシャライザではメソッド(インスタンスと静的)のみが有効です。新しい ES クラス機能(クラスフィールドなど)がステージ 4 に達すると、このメソッドを更新してそれらをサポートする必要があります。さらに、実装されていない ES 機能(たとえば、Object.defineProperty メソッドではなく get および set キーワードを介して定義されたゲッターとセッター)の回避策として、イニシャライザで PassthroughLiteral(バックティックで囲まれた式)を許可します。

      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
  • §

    指定されたノードが有効な ES クラスイニシャライザメソッドであるかどうかを確認します。

      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)
  • §

    構成済みのクラスイニシャライザメソッドを返します

      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
  • §

    クラスの子をトラバースして

    • 有効な ES プロパティを @properties にホイストします
    • 静的代入を @properties にホイストします
    • 無効な ES プロパティをクラスまたはプロトタイプ代入に変換します
      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
  • §

    無効な ES プロパティのクラス/プロトタイプ代入を作成します

      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'
  • §

    クラ lass スコープはまだ利用できないため、後で更新するために代入を返します

            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:AST 生成中(および JS へのコンパイル時)にこのエラーをフラグ付けすることが適切です。しかし、o.indent は AST 生成中は追跡されず、「プログラムのトップレベル」にいるかどうかを追跡する代替手段は現在ないようです。

        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) ->
  • §

    ImportClause の AST は、ImportDeclaration AST の specifiers プロパティとなるインポート指定子のネストされていないリストです

        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
  • §

    匿名クラスのエクスポートを防ぎます。エクスポートされるすべてのメンバーに名前を付ける必要があります

      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
  • §

    ローカルスコープに入る変数の名前

        @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) ->
  • §

    仕様によると、シンボルを複数回インポートすることはできません(例:import { foo, foo } from 'lib' は無効です)

        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(代入)

  • §

    **Assign** は、ローカル変数を値に代入したり、オブジェクトリテラル内を含むオブジェクトのプロパティを設定したりするために使用されます。

    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, {
  • §

    AST 生成中は、JS へのコンパイル中に「代入不可」と見なされるこれらのコンストラクトへの代入を許可する必要がありますが、[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 は 'import' または '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
  • §

    この代入識別子に 1 つ以上の herecomment が添付されている場合、Flow タイピングとの互換性のために、それらを宣言行の一部として出力します(他の herecomment が既にそこにステージングされていない限り)。この代入がクラス用である場合、たとえば ClassName = class ClassName { の場合は、これを行わないでください。Flow では、コメントがクラス名と { の間にある必要があります。

            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
  • §

    代入をコンパイルし、必要に応じて compileDestructuring または compileSplice に委任します。正しい内部参照のために、代入された基本オブジェクトの名前を追跡します。変数が現在のスコープ内でまだ認識されていない場合は、宣言します。

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

    @variable が配列またはオブジェクトの場合、デストラクチャリングを行っています。また、isAssignable() である場合、デストラクチャリング構文は ES でサポートされており、そのまま出力できます。そうでない場合は、@compileDestructuring を使用して、この ES でサポートされていないデストラクチャリングを許容できる出力に変換します。

          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
  • §

    https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#Assignment_without_declaration によると、宣言なしでデストラクチャリングを行う場合、デストラクチャリング代入をかっこで囲む必要があります。代入は、'o.level' の優先順位が LEVEL_LIST(3)よりも低い場合(つまり、LEVEL_COND(4)、LEVEL_OP(5)、または LEVEL_ACCESS(6))、またはオブジェクトをデストラクチャリングしている場合(例:{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
  • §

    オブジェクトの rest プロパティは代入できません:{{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, ', '
  • §

    配列またはオブジェクトリテラルを値に代入する場合の再帰パターンマッチングの簡単な実装。内部名を代入するためにプロパティを覗き見ます。

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

    {} = a および [] = a(空のパターン)の特別なケース。単純に 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
  • §

    この時点で、デストラクチャリングするものがいくつかあります。そのため、たとえば {a, b} = fn() の fn() はキャッシュする必要があります。vvar がまだ単純な変数でない場合は、単純な変数にします。

        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
  • §

    [].slice コードを出力するヘルパー。

        compSlice = slicer "slice"
  • §

    [].splice コードを出力するヘルパー。

        compSplice = slicer "splice"
  • §

    objects 配列に Assign のインスタンス(例:{a:1})が含まれているかどうかを確認します。

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

    objects 配列に代入できないオブジェクトが含まれているかどうかを確認します。

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

    オブジェクトの代入({a:1})、代入できないオブジェクト、または単一のノードがある場合、objects は複雑です。

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

    「複雑な」objects はループで処理されます。例:[a, b, {c, r…}, d], [a, …, {b, r…}, c, d]

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

    Elision はスキップできます。

            continue if obj instanceof Elision
  • §

    obj が {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 は [a…]、{a…}、または 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
  • §

    「単純な」objects は分割して配列にコンパイルできます。[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
  • §

    objects 内に Splat または Expansion が存在する場合、配列を2つの単純な部分配列に分割できます。 Splat [a, b, c…, d, e] は [a, b, c…] と [d, e] に分割できます。 Expansion [a, b, …, c, d] は [a, b] と [c, d] に分割できます。 例: 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
  • §

    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
  • §

    objects 内に Splat または Expansion は存在しません。

          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
  • §

    何らかの理由で [...] = a を許可しません。([] = 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'
  • §

    複数の Splat または Expansion が存在する場合、エラーを表示します。 例: [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
  • §

    すべての Splat をカウントします: [a, b, c…, d, e]

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

    すべての Expansion をカウントします: [a, b, …, c, d]

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

    splat と expansion を組み合わせます。

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

    ‘splatsAndExpans’ をソートして、最初に許可されていないトークンでエラーを表示できるようにします。

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

    条件付き代入をコンパイルする際には、オペランドが複数回参照される場合でも、一度だけ評価されるように注意してください。

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

    未定義変数の条件付き代入を許可しません。

        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
  • §

    a //= b のような特殊な数学的代入演算子を、同等の拡張形式 a = a ** b に変換してからコンパイルします。

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

    JavaScript の Array#splice メソッドを使用して、配列スプライスリテラルからの代入をコンパイルします。

      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?()
  • §

    これは代入の左辺です。Arr と Obj にそれを知らせ、それらのノードが分割代入された変数として代入可能であることを認識させます。

        @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
  • §

    関数グリフ

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

    コード

  • §

    関数定義。これは新しいスコープを作成する唯一のノードです。関数本体の内容をウォークスルーする目的では、コードには *子* がありません。それらは内部スコープ内にあります。

    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
  • §

    コンパイルは、外側のスコープと明示的に共有するように要求されない限り、新しいスコープを作成します。ES2015仕様で要求されているように、パラメータリスト内のsplatパラメータを関数定義の最後のパラメータになるように設定することで処理します。CoffeeScript関数定義にsplatの後にパラメータがある場合、それらは関数本体内で式を介して宣言されます。

      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()
  • §

    this 代入を分離します。

        @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 はデフォルト値を持つオブジェクトの分割代入です: ({@prop = 1}) -> 変数名が既に予約されている場合、分割代入された変数に新しい変数名を割り当てる必要があります: ({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
  • §

    パラメータを解析し、関数定義に配置するパラメータのリストに追加します。splatまたはexpansionを処理し、splat/expansionパラメータの後にあるすべてのパラメータ変数を宣言する式を関数本体に追加します。何らかの理由で関数本体で宣言する必要があるパラメータが発生した場合、たとえば、this で分割代入されている場合は、後続のすべてのパラメータも関数本体で宣言して代入し、冪等でないパラメータが正しい順序で評価されるようにします。

        for param, i in @params
  • §

    このパラメータで ... が使用されましたか? Splat/expansion パラメータにはデフォルト値を設定できないため、それについて心配する必要はありません。

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

    Splat配列はESによって奇妙に扱われます。関数本体で従来の方法で処理します。TODO: これは関数パラメータリストで処理する必要がありますか?もしそうなら、どのように処理する必要がありますか?

                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
  • §

    他のすべてのパラメータを解析します。splatパラメータがまだ見つかっていない場合は、これらの他のパラメータを関数定義で出力されるリストに追加します。

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

    このパラメータはパラメータリストで宣言または代入できません。そのため、パラメータリストに参照を配置し、それを代入するステートメントを関数本体に追加します。たとえば、デフォルト値がある場合は (arg) => { var a = arg.a; } となります。

              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'
  • §

    このパラメータがsplatまたはexpansionの前にある場合、関数定義パラメータリストに配置されます。

            unless haveSplatParam
  • §

    このパラメータにデフォルト値があり、上記の shouldCache() ブロックによってまだ設定されていない場合は、関数本体でステートメントとして定義します。このパラメータはsplatパラメータの後にあるため、パラメータリストにデフォルト値を定義することはできません。

              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
  • §

    このパラメータの参照を関数スコープに追加します。

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

    このパラメータは分割代入されます。

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

    このパラメータのコンパイルは、スコープ名トラッキングに追加する名前を取得するためだけに行われます。ここでコンパイルされた出力は最終的な出力のために保持されないため、このコンパイルにコメントを含めないでください。そうすることで、このパラメータがコンパイルされる「実際」の時間にコメントが出力されます。

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

    このパラメータにデフォルト値があった場合、関数パラメータリストにないため、デフォルト値(必要な場合)を本体の式として代入する必要があります。

              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
  • §

    このパラメータは、以前にスキップされたためまだ追加されていないため、スコープに追加します。

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

    splatまたはexpansionパラメータの後にパラメータがある場合、それらのパラメータは関数の本体で代入する必要があります。

        if paramsAfterSplat.length isnt 0
  • §

    分割代入を作成します。たとえば、[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
  • §

    関数本体に新しい式を追加します。

        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は、バインドされた(=>)関数がジェネレータになることを許可していません。これは通常、Op::compileContinuation によってキャッチされますが、再確認してください。

        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'
  • §

    出力を組み立てます。

        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 '(']
  • §

    関数名と ( の間のブロックコメントは、function と ( の間に出力されます。

        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
  • §

    このパラメータをコンパイルしますが、生成された変数(たとえば、ref)が作成された場合は、関数パラメータリスト内に var 行を配置できないため、それらを親スコープに移動します。

          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 ')'
  • §

    ) と ->/=> の間のブロックコメントは、) と { の間に出力されます。

        if @funcGlyph?.comments?
          comment.unshift = no for comment in @funcGlyph.comments
          @compileCommentFragments o, @funcGlyph, signature
    
        body = @body.compileWithDeclarations o unless @body.isEmpty()
  • §

    super 参照が処理されるように、メソッド名の前に本体をコンパイルする必要があります。

        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
  • §

    crossScope が true でない限り、スコープ境界を越えないように traverseChildren メソッドをショートサーキットします。

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

    コンテキスト境界を越えないように replaceInContext メソッドをショートサーキットします。バインドされた関数は同じコンテキストを持ちます。

      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
  • §

    このパラメータで ... が使用されましたか?(関数ごとに許可されるのは、そのようなパラメータは1つだけです。)

          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
  • §

    指定されたコンテキストノード内のすべての super 呼び出しを見つけます。iterator が呼び出された場合は true を返します。

      eachSuperCall: (context, iterator, {checkForThisBeforeSuper = yes} = {}) ->
        seenSuper = no
    
        context.traverseChildren yes, (child) =>
          if child instanceof SuperCall
  • §

    コンストラクタ内の super(アクセサのない唯一の super)には、this への参照を持つ引数を指定できません。これは、super を呼び出す前に this を参照することになるためです。

            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 は、バインドされた(アロー)関数で同じターゲットを持つため、それらもチェックします。

          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
  • §

    名前付き関数を生成することはないため、id を null として指定します。これは、匿名関数式/アロー関数の Babel AST と一致します。

          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
  • §

    パラメータ

  • §

    関数定義のパラメータ。典型的な JavaScript パラメータに加えて、これらのパラメータは関数のコンテキストに自身をアタッチすることも、splat になることもでき、パラメータのグループを配列にまとめます。

    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()
  • §

    Param の名前を反復処理します。ある意味で、分割代入されたパラメータは複数の JS パラメータを表します。このメソッドを使用すると、それらすべてを反復処理できます。iterator 関数は iterator(name, node) として呼び出されます。ここで、name はパラメータの名前、node はその名前に対応する AST ノードです。

      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"
  • §
    • 単純なリテラル 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 ? []
  • §

    元のobjを保存します。

          nObj = obj
  • §
    • デフォルト値を持つ分割代入されたパラメータ
          if obj instanceof Assign and not obj.context?
            obj = obj.variable
  • §
    • 分割代入されたパラメータ内の代入 {foo:bar}
          if obj instanceof Assign
  • §

    …おそらくデフォルト値付き

            if obj.value instanceof Assign
              obj = obj.value.variable
            else
              obj = obj.value
            @eachName iterator, obj.unwrap()
  • §
    • 分割代入されたパラメータ内の splat [xs...]
          else if obj instanceof Splat
            node = obj.name.unwrap()
            iterator node.value, node, @
          else if obj instanceof Value
  • §
    • 分割代入されたパラメータ内の分割代入されたパラメータ [{a}]
            if obj.isArray() or obj.isObject()
              @eachName iterator, obj.base
  • §
    • 分割代入されたパラメータ内の at-params {@foo}
            else if obj.this
              atParam obj, nObj
  • §
    • 単純な分割代入されたパラメータ {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
  • §

    名前の AST ノードを新しいノードに置き換えることによって、パラメータの名前を変更します。これは、オブジェクトの分割代入のソースが変更されないようにする必要があります。

      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
  • §

    変数が予約されていない場合、分割代入された変数に新しい変数を割り当てる必要はありません。例: ({@foo}) -> は ({foo}) { this.foo = foo} にコンパイルされる必要があります foo = 1; ({@foo}) -> は 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

  • §

    関数へのパラメータ、呼び出しへの引数、または分割代入の一部としての splat。

    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

  • §

    配列の分割代入(パターンマッチング)またはパラメータリスト内の値をスキップするために使用されます。

    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

  • §

    配列省略要素(たとえば、[,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

  • §

    while ループ。CoffeeScript によって公開される唯一の種類の低レベルループです。それから、他のすべてのループを作成できます。包括的な理解が提供できるよりも柔軟性または速度が必要な場合に役立ちます。

    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
  • §

    JavaScript の *while* との主な違いは、CoffeeScript の *while* はより大きな式の一部として使用できることです。while ループは、各反復の計算結果を含む配列を返す場合があります。

      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
  • §

    演算子

  • §

    単純な算術演算と論理演算。CoffeeScript 演算から JavaScript の同等の演算への変換を実行します。

    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
  • §

    CoffeeScript から JavaScript シンボルへの変換のマップ。

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

    可逆演算子のマップ。

      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()
  • §

    Pythonスタイルの比較連鎖は可能ですか?

      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()
  • §

    チェーンでは、チェーン式がラップされているため、裸のオブジェクトリテラルを括弧で囲む必要はありません。

        @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
  • §

    複数の比較演算子が連続して使用される場合、Python の連鎖比較を模倣します。例えば

    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
  • §

    これが存在代入でない限り、左側の式への参照を保持します。

      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
  • §

    単項 **演算子** をコンパイルします。

      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
  • §

    splat がない配列リテラルがある場合にのみ compileOrTest します。

          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

  • §

    古典的な *try/catch/finally* ブロック。

    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
  • §

    コンパイルは、ほぼ予想どおりです。*finally* 句はオプションで、*catch* は必須です。

      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),
  • §

    位置データに finally キーワードを含めます。

                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

  • §

    例外をスローするための単純なノード。

    exports.Throw = class Throw extends Base
      constructor: (@expression) ->
        super()
    
      children: ['expression']
    
      isStatement: YES
      jumps:       NO
  • §

    **Throw** はすでに一種の return です…

      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
  • §

    存在

  • §

    変数が存在するかどうかをチェックします - null でも undefined でもないことを確認します。これは Ruby の .nil? と似ており、JavaScript の真理値表を参照する必要がありません。オプションで、変数が 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
  • §

    null と比較する場合は、明示的に緩やかな等価性 (==) を使用したいと考えています。そのため、存在チェックはほぼ truthiness のチェックに対応します。既存のコードが大量に壊れてしまうため、null に対してこれを === に変更しないでください。ただし、undefined とのみ比較する場合は、=== を使用したいと考えています。なぜなら、このユースケースは ES2015+ のデフォルト値との互換性のためであり、デフォルト値は変数が undefined(ただし 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
  • §

    括弧

  • §

    ソースで明示的に指定された、余分な括弧のセット。かつては冗長な括弧を検出して削除することで結果をクリーンアップしようとしましたが、もはやそうではありません - 好きなだけ括弧を入れることができます。

    括弧は、任意のステートメントを強制的に式にするための良い方法です。

    exports.Parens = class Parens extends Base
      constructor: (@body) ->
        super()
    
      children: ['body']
    
      unwrap: -> @body
    
      shouldCache: -> @body.shouldCache()
    
      compileNode: (o) ->
        expr = @body.unwrap()
  • §

    これらの括弧が IdentifierLiteral の後にブロックコメントが続くものを囲んでいる場合は、括弧を出力します(言い換えれば、これらの冗長な括弧を最適化して削除しないでください)。これは、コメントベースの型注釈が続く識別子を JavaScript のラベルと区別するために、Flow が特定の状況で括弧を必要とするためです。

        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 は this を返して、祖先ノードが @body を取得し、@body.compileNode を使用することを阻止します。StringWithInterpolations.compileNode は、補間された文字列をコードとして出力するための *唯一の* カスタムロジックです。

      unwrap: -> this
    
      shouldCache: -> @body.shouldCache()
    
      extractElements: (o, {includeInterpolationWrappers, isJsx} = {}) ->
  • §

    expr が 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
  • §

    このノードは破棄されますが、そのコメントはサルベージされます。

            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
  • §

    { および } フラグメントに、この StringWithInterpolations ノードによって生成されたことをフラグ付けします。そのため、compileComments はそれらを境界として扱うことを認識します。ただし、囲まれたコメントがすべて /* */ コメントの場合は、中括弧は不要です。このコンパイラが縮小されている場合、縮小された変数名を報告する可能性がある fragment.type を信用しないでください。

              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']
  • §

    空の補間の内容を表します(例:#{})。AST 生成中にのみ使用されます。

    exports.EmptyInterpolation = class EmptyInterpolation extends Base
      constructor: ->
        super()
  • §

    For(繰り返し)

  • §

    CoffeeScript の *for* ループの代替は、配列とオブジェクトの comprehensions(内包表記)であり、ここで *for* ループにコンパイルされます。これらは式としても機能し、各フィルタリングされた反復の結果を返すことができます。

    Python の配列内包表記とは異なり、複数行にすることができ、ループの現在のインデックスを2番目のパラメーターとして渡すことができます。Ruby のブロックとは異なり、1回のパスでマップとフィルターを実行できます。

    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
  • §

    「for 行」、つまり for を含むコード行にある子ノードのコメントを、for ノード自体に移動します。そのため、これらのコメントが出力され、for ループの上に 出力されます。

        for attribute in ['source', 'guard', 'step', 'name', 'index'] when @[attribute]
          @[attribute].traverseChildren yes, (node) =>
            if node.comments
  • §

    これらのコメントはかなり深く埋め込まれているため、末尾のコメントである場合、この for ループのコンパイルが完了したときには、それらが続く行は認識できなくなります。そのため、for 行の上に 出力するために、それらを上にシフトするだけです。

              comment.newLine = comment.unshift = yes for comment in node.comments
              moveComments node, @[attribute]
          moveComments @[attribute], @
        this
  • §

    CoffeeScript の中で最も厄介なメソッドへようこそ。配列、オブジェクト、および範囲の内包表記の内部ループ、フィルタリング、ステッピング、および結果の保存を処理します。生成されたコードの一部は共通して共有できますが、一部は共有できません。

      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 not @from 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(分岐)

  • §

    JavaScript の *switch* ステートメント。オンデマンドで返却可能な式に変換します。

    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* ステートメント。要求された戻り値を各句の最後の行にプッシュダウンすることにより、式として機能します。

    単一式の **If** は、可能であれば条件演算子にコンパイルされます。なぜなら、三項演算子はすでに適切な式であり、変換する必要がないためです。

    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()
  • §

    **If** のチェーンを書き直し、デフォルトケースを最後の *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
  • §

    **If** は、いずれかの本体がステートメントである必要がある場合にのみ、ステートメントにコンパイルされます。それ以外の場合は、条件演算子は安全です。

      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]
  • §

    If を通常の *if-else* ステートメントとしてコンパイルします。フラット化されたチェーンは、内部の *else* 本体 をステートメント形式に強制します。

      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
  • §

    If を条件演算子としてコンパイルします。

      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; b)。現在、AST 生成中にのみ使用されます。

    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
  • §

    定数

  • §
    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');
          }
        }
      "
  • §

    ネイティブ関数のルックアップ時間を短縮するためのショートカット。

      hasProp: -> '{}.hasOwnProperty'
      indexOf: -> '[].indexOf'
      slice  : -> '[].slice'
      splice : -> '[].splice'
  • §

    レベルは、AST 内のノードの位置を示します。括弧が必要か不要かを知るのに役立ちます。

    LEVEL_TOP    = 1  # ...;
    LEVEL_PAREN  = 2  # (...)
    LEVEL_LIST   = 3  # [...]
    LEVEL_COND   = 4  # ... ? x : y
    LEVEL_OP     = 5  # !...
    LEVEL_ACCESS = 6  # ...[0]
  • §

    タブは、プリティプリントのための2つのスペースです。

    TAB = '  '
    
    SIMPLENUM = /^[+-]?\d+(?:_\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
  • §

    ヘルパー関数

  • §
  • §

    ユーティリティ関数がトップレベルで割り当てられていることを保証するためのヘルパー。

    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
  • §

    CoffeeScript 1 でコード行をインデントするために makeCode "#{@tab}" を挿入した可能性のある場所では、コード行の前にコメントが存在する可能性を考慮する必要があります。そのようなコメントがある場合は、そのようなコメントの各行をインデントしてから、*その後* 最初のコード行をインデントします。

    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
  • §

    comments プロパティをあるオブジェクトから別のオブジェクトに移動し、最初のオブジェクトから削除します。

    moveComments = (from, to) ->
      return unless from?.comments
      attachCommentsToNode from.comments, to
      delete from.comments
  • §

    ノードをコンパイルするときに、フラグメントの配列の先頭にフラグメントを挿入したい場合があります。ただし、先頭に1つ以上のコメントフラグメントがある場合、コメントの後、非コメントの前にこのフラグメントを挿入します。

    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?()
  • §

    soak の場合、ノードの子を展開し、作成された If の下にノードを配置します。

    unfoldSoak = (o, parent, name) ->
      return unless ifn = parent[name].unfoldSoak o
      parent[name] = ifn.body
      ifn.body = new Value parent
      ifn
  • §

    特定の文字をエスケープすることにより、文字列または正規表現を構築します。

    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
  • §

    エスケープされたバックスラッシュを無視します。

          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
  • §

    以下の mergeLocationData および mergeAstLocationData のためのヘルパー。

    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
  • §

    2つのノードのロケーションデータを取得し、両方のノードのロケーションデータを網羅する新しい locationData オブジェクトを返します。そのため、新しい first_line 値は2つのノードの first_line 値のうち早い方になり、新しい last_column は2つのノードの last_column 値のうち遅い方になります。

    最初のノードのロケーションデータを2番目のノードの開始または終了ロケーションデータのみで拡張する場合は、justLeading または justEnding オプションを渡します。たとえば、first の範囲が [4, 5] で、second の範囲が [1, 10] の場合、次のようになります。

    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]
        ]
      )
  • §

    2つの AST ノード、または2つの AST ノードのロケーションデータオブジェクトを取得し、両方のノードのロケーションデータを網羅する新しいロケーションデータオブジェクトを返します。そのため、新しい start 値は2つのノードの start 値のうち早い方になり、新しい end 値は2つのノードの end 値のうち遅い方になります。

    最初のノードのロケーションデータを2番目のノードの開始または終了ロケーションデータのみで拡張する場合は、justLeading または justEnding オプションを渡します。たとえば、first の範囲が [4, 5] で、second の範囲が [1, 10] の場合、次のようになります。

    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
  • §

    Jison スタイルのノードクラスのロケーションデータを Babel スタイルのロケーションデータに変換します。

    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]
  • §

    別のノードのロケーションの終わりに対応する、幅ゼロのロケーションデータを生成します。

    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]
    }
  • §

    現在、補間/JSX 式の中括弧間の空白に対応するトークンがないため、補間のロケーションデータから中括弧をトリミングすることでロケーションデータをまとめます。技術的には、終了中括弧の前に改行がある場合、ここでの last_line/last_column の計算は正しくない可能性がありますが、last_line/last_column は AST 生成には使用されません。

    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
      ]