• ジャンプ先 … +
    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 は、構文木のすべてのノードクラスを含んでいます。ほとんどのノードはgrammarのアクションの結果として作成されますが、いくつかはコード生成の方法として他のノードによって作成されます。構文木を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
  • §

    CodeFragment

  • §

    以下で定義されているさまざまなノードはすべて、CodeFragmentオブジェクトのコレクションにコンパイルされます。CodeFragmentsは生成されたコードのブロックであり、コードの由来であるソースファイル内の場所です。CodeFragmentsは、すべてのCodeFragmentsの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 ''}"
  • §

    CodeFragmentの配列を文字列に変換します。

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

    Base

  • §

    Baseは、構文木内のすべてのノードの抽象基底クラスです。各サブクラスは、そのノードのコード生成を実行する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'
  • §

    このノードをコンパイルする前にクロージャでラップするかどうか、または直接コンパイルするかどうかを決定するための共通のロジック。このノードがステートメントであり、pureStatementではなく、ブロックの最上位レベルにない場合(不要)、結果の返却を既に要求されていない場合(ステートメントは結果の返却方法を知っているため)にラップする必要があります。

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

    このノードまたはそのいずれかの子ノードに、特定の種類のノードが含まれていますか?子ノードを再帰的にトラバースし、predを検証する最初のノードを返します。それ以外の場合は、未定義を返します。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'
  • §

    ノードのプレーンなJavaScriptオブジェクト表現であり、JSONとしてシリアル化できます。これは、Node APIのastオプションが返すものです。他のツールとの相互運用性を向上させるために、Babel AST specにできるだけ近づけるように努力しています。警告:子クラスではこのメソッドをオーバーライドしないでください。必要に応じてコンポーネント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からの戻りロジックが必要となるため、astPropertiesの前に@makeReturnを呼び出す必要があります。

        @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: -> {}
  • §

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

      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から発生するエラーメッセージがいくつかあります。

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

    HoistTarget

  • §

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

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

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

    また、同じレベルに隣接する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
  • §

    **ブロック**本体内のすべての式をコンパイルします。結果を返す必要があり、それが式である場合は、それを返します。ステートメントの場合は、ステートメントにそれを実行するように要求します。

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

    最初の後続コメントは、`;// コメント`のようにコードの行の最後に続くか、コードの行の後に新しい行を開始しますか?

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

    既にブロックである場合を除き、指定されたノードを**ブロック**としてラップします。

      @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`は*純粋なステートメント*です。クロージャでラップしても意味がありません。

    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にコンパイルします。プロパティのチェーンに`?.`という*ソーク*演算子が散在している場合、状況はさらに複雑になります。その場合、ソークチェーンを作成するときに、誤って何かを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)`と同じ順序でコンパイルする必要があります(issue #4437、https://github.com/jashkenas/coffeescript/issues/4437参照)

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

    ソークを`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つを除くすべてプロパティを取得します。プロパティが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()
  • §

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

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

    無効な属性をキャッチします:`

    `

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

    ``と`{} `を区別します。

                      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)`と同じ順序でコンパイルする必要があります(issue #4437、https://github.com/jashkenas/coffeescript/issues/4437参照)

        varAccess = @variable?.properties?[0] instanceof Access
        argCode = (arg for arg in (@args || []) when arg instanceof Code)
        if argCode.length > 0 and varAccess and not @variable.base.cached
          [cache] = @variable.base.cache o, LEVEL_ACCESS, -> no
          @variable.base.cached = cache
    
        for arg, argIndex in @args
          if argIndex then compiledArgs.push @makeCode ", "
          compiledArgs.push (arg.compileToFragments o, LEVEL_LIST)...
    
        fragments = []
        if @isNew
          fragments.push @makeCode 'new '
        fragments.push @variable.compileToFragments(o, LEVEL_ACCESS)...
        fragments.push @makeCode('('), compiledArgs..., @makeCode(')')
        fragments
    
      checkForNewSuper: ->
        if @isNew
          @variable.error "Unsupported reference to 'super'" if @variable instanceof Super
    
      containsSoak: ->
        return yes if @soak
        return yes if @variable?.containsSoak?()
        no
    
      astNode: (o) ->
        if @soak and @variable instanceof Super and o.scope.namedMethod()?.ctor
          @variable.error "Unsupported reference to 'super'"
        @checkForNewSuper()
        super o
    
      astType: ->
        if @isNew
          'NewExpression'
        else if @containsSoak()
          'OptionalCallExpression'
        else
          'CallExpression'
    
      astProperties: (o) ->
        return
          callee: @variable.ast o, LEVEL_ACCESS
          arguments: arg.ast(o, LEVEL_LIST) for arg in @args
          optional: !!@soak
          implicit: !!@implicit
  • §

    スーパー

  • §

    `super()`呼び出しを、同じ名前のプロトタイプの関数に対する呼び出しに変換します。`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

  • §

    補間を含む正規表現は、実際には Call(正確には RegExp() 呼び出し)の変種であり、内部に StringWithInterpolations を含んでいます。

    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

  • §

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

    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番目のパラメータはスライスの終了インデックスを指定し、1番目のパラメータは開始インデックスを指定します。

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

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

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

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

      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 クラス定義です。名前、オプションのスーパークラス、および本体を使用して **Class** を初期化します。

    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 クラス機能が Stage 4 に達すると、このメソッドを更新してそれらをサポートする必要があります。さらに、実装されていない ES 機能(例:get および set キーワードで定義されたゲッターとセッターではなく、Object.defineProperty メソッドで定義されたゲッターとセッター)のエスケープハッチとして、イニシャライザで 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'
  • §

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

            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つ以上の herecomments が付いている場合、Flow 型付けとの互換性のために、宣言行の一部として出力します(他の herecomments が既にそこにステージングされている場合を除く)。この代入がクラス用である場合(例: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
  • §

    「複雑な」オブジェクトはループ処理されます。例: [a, b, {c, r…}, d], [a, …, {b, r…}, c, d]

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

    省略はスキップできます。

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

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

    オブジェクトに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
  • §

    オブジェクトのスライスまたはスプライス。

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

    オブジェクトに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
  • §

    FuncGlyph

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

    コード

  • §

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

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

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

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

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

          id: null
          hasIndentedBody: @body.locationData.first_line > @funcGlyph?.locationData.first_line
        ,
          if @isMethod then @methodAstProperties o else {}
    
      astLocationData: ->
        functionLocationData = super()
        return functionLocationData unless @isMethod
    
        astLocationData = mergeAstLocationData @name.astLocationData(), functionLocationData
        if @isStatic.staticClassName?
          astLocationData = mergeAstLocationData @isStatic.staticClassName.astLocationData(), astLocationData
        astLocationData
  • §

    Param

  • §

    関数定義内のパラメータ。典型的な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, @
  • §
    • @パラメータ@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
  • §
    • 非構造化パラメータ内の@パラメータ{@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ノードを新しいノードに置き換えることで、paramの名前を変更します。これにより、オブジェクトの非構造化のソースが変更されないようにする必要があります。

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

    Op

  • §

    単純な算術演算と論理演算。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()
  • §

    チェーンでは、チェーンされた式がラップされているため、生のobjリテラルを括弧でラップする必要はありません。

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

    単項Opをコンパイルします。

      compileUnary: (o) ->
        parts = []
        op = @operator
        parts.push [@makeCode op]
        if op is '!' and @first instanceof Existence
          @first.negated = not @first.negated
          return @first.compileToFragments o
        if o.level >= LEVEL_ACCESS
          return (new Parens this).compileToFragments o
        plusMinus = op in ['+', '-']
        parts.push [@makeCode(' ')] if op in ['typeof', 'delete'] or
                          plusMinus and @first instanceof Op and @first.operator is op
        if plusMinus and @first instanceof Op
          @first = new Parens @first
        parts.push @first.compileToFragments o, LEVEL_OP
        parts.reverse() if @flip
        @joinFragmentArrays parts, ''
    
      compileContinuation: (o) ->
        parts = []
        op = @operator
        @checkContinuation o unless @isAwait()
        if 'expression' in Object.keys(@first) and not (@first instanceof Throw)
          parts.push @first.expression.compileToFragments o, LEVEL_OP if @first.expression?
        else
          parts.push [@makeCode "("] if o.level >= LEVEL_PAREN
          parts.push [@makeCode op]
          parts.push [@makeCode " "] if @first.base?.value isnt ''
          parts.push @first.compileToFragments o, LEVEL_OP
          parts.push [@makeCode ")"] if o.level >= LEVEL_PAREN
        @joinFragmentArrays parts, ''
    
      checkContinuation: (o) ->
        unless o.scope.parent?
          @error "#{@operator} can only occur inside functions"
        if o.scope.method?.bound and o.scope.method.isGenerator
          @error 'yield cannot occur inside bound (fat arrow) functions'
    
      compileFloorDivision: (o) ->
        floor = new Value new IdentifierLiteral('Math'), [new Access new PropertyName 'floor']
        second = if @second.shouldCache() then new Parens @second else @second
        div = new Op '/', @first, second
        new Call(floor, [div]).compileToFragments o
    
      compileModulo: (o) ->
        mod = new Value new Literal utility 'modulo', o
        new Call(mod, [@first, @second]).compileToFragments o
    
      toString: (idt) ->
        super idt, @constructor.name + ' ' + @operator
    
      checkDeleteOperand: (o) ->
        if @operator is 'delete' and o.scope.check(@first.unwrapAll().value)
          @error 'delete operand may not be argument or var'
    
      astNode: (o) ->
        @checkContinuation o if @isYield()
        @checkDeleteOperand o
        super o
    
      astType: ->
        return 'AwaitExpression' if @isAwait()
        return 'YieldExpression' if @isYield()
        return 'ChainedComparison' if @isChain()
        switch @operator
          when '||', '&&', '?' then 'LogicalExpression'
          when '++', '--'      then 'UpdateExpression'
          else
            if @isUnary()      then 'UnaryExpression'
            else                    'BinaryExpression'
    
      operatorAst: ->
        "#{if @invertOperator then "#{@invertOperator} " else ''}#{@originalOperator}"
    
      chainAstProperties: (o) ->
        operators = [@operatorAst()]
        operands = [@second]
        currentOp = @first
        loop
          operators.unshift currentOp.operatorAst()
          operands.unshift currentOp.second
          currentOp = currentOp.first
          unless currentOp.isChainable()
            operands.unshift currentOp
            break
        return {
          operators
          operands: (operand.ast(o, LEVEL_OP) for operand in operands)
        }
    
      astProperties: (o) ->
        return @chainAstProperties(o) if @isChain()
    
        firstAst = @first.ast o, LEVEL_OP
        secondAst = @second?.ast o, LEVEL_OP
        operatorAst = @operatorAst()
        switch
          when @isUnary()
            argument =
              if @isYield() and @first.unwrap().value is ''
                null
              else
                firstAst
            return {argument} if @isAwait()
            return {
              argument
              delegate: @operator is 'yield*'
            } if @isYield()
            return {
              argument
              operator: operatorAst
              prefix: !@flip
            }
          else
            return
              left: firstAst
              right: secondAst
              operator: operatorAst
  • §

    In

    exports.In = class In extends Base
      constructor: (@object, @array) ->
        super()
    
      children: ['object', 'array']
    
      invert: NEGATE
    
      compileNode: (o) ->
        if @array instanceof Value and @array.isArray() and @array.base.objects.length
          for obj in @array.base.objects when obj instanceof Splat
            hasSplat = yes
            break
  • §

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

    Existence

  • §

    変数の存在をチェックします。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と比較する際には、緩い等号(==)を明示的に使用したいと考えています。これにより、存在チェックはおおよそ真偽値のチェックに対応します。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
  • §

    Parens

  • §

    ソースで明示的に指定された余分な括弧。かつては冗長な括弧を検出して削除することで結果をきれいにしようとしましたが、もうそうしません。好きなだけ入れることができます。

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

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

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

        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ループの代替は、配列とオブジェクトの内包表記であり、ここでは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`値の遅い方の値になります。

    `justLeading`または`justEnding`オプションを渡すと、最初のノードの位置データを2番目のノードの開始または終了位置データで拡張する場合があります。たとえば、`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`値の遅い方の値になります。

    `justLeading`または`justEnding`オプションを渡すと、最初のノードの位置データを2番目のノードの開始または終了位置データで拡張する場合があります。たとえば、`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
      ]