• 跳至… +
    browser.coffee cake.coffee coffeescript.coffee command.coffee grammar.coffee helpers.coffee index.coffee lexer.coffee nodes.coffee optparse.coffee register.coffee repl.coffee rewriter.coffee scope.litcoffee sourcemap.litcoffee
  • nodes.coffee

  • §

    nodes.coffee 包含語法樹的所有節點類別。大部分節點都是 語法 中動作的結果,但有些節點是由其他節點作為程式碼產生方法所建立。若要將語法樹轉換成 JavaScript 程式碼字串,請在根目錄上呼叫 compile()。

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

    匯入我們計畫使用的輔助程式。

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

    剖析器需要的函式。

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

    不需要自訂的節點的常數函式。

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

    CodeFragment

  • §

    下面定義的各種節點都編譯成 CodeFragment 物件的集合。CodeFragment 是產生程式碼的區塊,以及程式碼在原始檔案中的位置。CodeFragment 可以組裝成可運作的程式碼,只要依序串接所有 CodeFragment 的 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'
  • §

    用於決定在編譯節點之前是否將此節點包覆在閉包中,或直接編譯的共用邏輯。如果此節點是陳述句,且不是純陳述句,而且我們不在區塊的最上層(這將是不必要的),而且我們尚未被要求傳回結果(因為陳述句知道如何傳回結果),則我們需要包覆。

      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,則傳回 [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 規範,以改善與其他工具的互通性。警告:不要在子類別中覆寫此方法。只在需要時覆寫組件 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()
  • §

    必須在 astProperties 之前呼叫 @makeReturn,因為後者可能會呼叫子節點的 .ast(),而這些節點需要 makeReturn 執行的傳回邏輯。

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

    每個抽象語法樹節點物件都有四類別的屬性

    • 類型,儲存在 type 欄位中,字串類似 NumberLiteral。
    • 位置資料,儲存在 loc、start、end 和 range 欄位中。
    • 此節點的特定屬性,例如 parsedValue。
    • 本身為子節點的屬性,例如 body。這些欄位全部交錯在 Babel 規格中;type、start 和 parsedValue 都是 AST 節點物件中的頂層欄位。我們有不同的方法來傳回每個類別,並在此處合併在一起。
        Object.assign {}, {type: @astType(o)}, @astProperties(o), @astLocationData()
  • §

    預設情況下,節點類別沒有特定屬性。

      astProperties: -> {}
  • §

    預設情況下,節點類別的 AST type 是其類別名稱。

      astType: -> @constructor.name
  • §

    AST 位置資料是我們 Jison 位置資料的重新排列版本,已轉換成 Babel 規格使用的結構。

      astLocationData: ->
        jisonLocationDataToAstLocationData @locationData
  • §

    判斷 AST 節點是否需要 ExpressionStatement 包裹。通常與我們的 isStatement() 邏輯相符,但允許覆寫。

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

    將每個子項傳遞給函式,在函式傳回 false 時中斷。

      eachChild: (func) ->
        return this unless @children
        for attr in @children when @[attr]
          for child in flatten [@[attr]]
            return this if func(child) is false
        this
    
      traverseChildren: (crossScope, func) ->
        @eachChild (child) ->
          recur = func(child)
          child.traverseChildren(crossScope, func) unless recur is no
  • §

    replaceInContext 將遍歷子項,尋找 match 傳回 true 的節點。找到後,符合條件的節點將由呼叫 replacement 的結果取代。

      replaceInContext: (match, replacement) ->
        return false unless @children
        for attr in @children when children = @[attr]
          if Array.isArray children
            for child, i in children
              if match child
                children[i..i] = replacement child, @
                return true
              else
                return true if child.replaceInContext match, replacement
          else if match children
            @[attr] = replacement children, @
            return true
          else
            return true if children.replaceInContext match, replacement
    
      invert: ->
        new Op '!', this
    
      unwrapAll: ->
        node = this
        continue until node is node = node.unwrap()
        node
  • §

    常見節點屬性和方法的預設實作。如有需要,節點將以自訂邏輯覆寫這些屬性和方法。

  • §

    children 是在樹狀結構中遞迴的屬性。children 清單就是 AST 的結構。parent 指標和 children 指標是您用來遍歷樹狀結構的方式。

      children: []
  • §

    isStatement 與「所有內容都是表達式」有關。少數內容無法成為表達式,例如 break。isStatement 傳回 true 的內容無法用作表達式。由於陳述式出現在表達式位置,因此 nodes.coffee 會產生一些錯誤訊息。

      isStatement: NO
  • §

    追蹤已編譯成片段的註解,以避免重複輸出。

      compiledComments: []
  • §

    includeCommentFragments 讓 compileCommentFragments 知道這個節點是否特別了解如何在輸出中處理註解。

      includeCommentFragments: NO
  • §

    jumps 告訴您表達式或表達式的內部部分是否具有跳出正常控制流程的流程控制結構(例如 break、continue 或 return),且無法用作值。(請注意,throw 不被視為流程控制結構。)這很重要,因為表達式中間的流程控制毫無意義;我們必須禁止它。

      jumps: NO
  • §

    如果 node.shouldCache() 為 false,則可以安全地多次使用 node。否則,您需要將 node 的值儲存在變數中,並多次輸出該變數。有點像這樣:不需要快取 5。但是,returnFive() 可能會因為多次評估而產生副作用,因此我們需要快取它。這個參數命名為 shouldCache,而不是 mustCache,因為在某些情況下我們可能不需要快取,但我們希望快取,例如一個很長的表達式,它可能是冪等的,但我們希望快取以簡潔。

      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 始終會產生縮排兩個空格的輸出;因此我們只需要搜尋一個至少以兩個空格開頭的 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 是靜態值的基底類別,這些值可以直接傳遞到 JavaScript 中而無需轉換,例如:字串、數字、true、false、null…

    exports.Literal = class Literal extends Base
      constructor: (@value) ->
        super()
    
      shouldCache: NO
    
      assigns: (name) ->
        name is @value
    
      compileNode: (o) ->
        [@makeCode @value]
    
      astProperties: ->
        return
          value: @value
    
      toString: ->
  • §

    這僅供除錯使用。

        " #{if @isStatement() then super() else @constructor.name}: #{@value}"
    
    exports.NumberLiteral = class NumberLiteral extends Literal
      constructor: (@value, {@parsedValue} = {}) ->
        super()
        unless @parsedValue?
          if isNumber @value
            @parsedValue = @value
            @value = "#{@value}"
          else
            @parsedValue = parseNumber @value
    
      isBigInt: ->
        /n$/.test @value
    
      astType: ->
        if @isBigInt()
          'BigIntLiteral'
        else
          'NumericLiteral'
    
      astProperties: ->
        return
          value:
            if @isBigInt()
              @parsedValue.toString()
            else
              @parsedValue
          extra:
            rawValue:
              if @isBigInt()
                @parsedValue.toString()
              else
                @parsedValue
            raw: @value
    
    exports.InfinityLiteral = class InfinityLiteral extends NumberLiteral
      constructor: (@value, {@originalValue = 'Infinity'} = {}) ->
        super()
    
      compileNode: ->
        [@makeCode '2e308']
    
      astNode: (o) ->
        unless @originalValue is 'Infinity'
          return new NumberLiteral(@value).withLocationDataFrom(@).ast o
        super o
    
      astType: -> 'Identifier'
    
      astProperties: ->
        return
          name: 'Infinity'
          declaration: no
    
    exports.NaNLiteral = class NaNLiteral extends NumberLiteral
      constructor: ->
        super 'NaN'
    
      compileNode: (o) ->
        code = [@makeCode '0/0']
        if o.level >= LEVEL_OP then @wrapInParentheses code else code
    
      astType: -> 'Identifier'
    
      astProperties: ->
        return
          name: 'NaN'
          declaration: no
    
    exports.StringLiteral = class StringLiteral extends Literal
      constructor: (@originalValue, {@quote, @initialChunk, @finalChunk, @indent, @double, @heregex} = {}) ->
        super ''
        @quote = null if @quote is '///'
        @fromSourceString = @quote?
        @quote ?= '"'
        heredoc = @isFromHeredoc()
    
        val = @originalValue
        if @heregex
          val = val.replace HEREGEX_OMIT, '$1$2'
          val = replaceUnicodeCodePointEscapes val, flags: @heregex.flags
        else
          val = val.replace STRING_OMIT, '$1'
          val =
            unless @fromSourceString
              val
            else if heredoc
              indentRegex = /// \n#{@indent} ///g if @indent
    
              val = val.replace indentRegex, '\n' if indentRegex
              val = val.replace LEADING_BLANK_LINE,  '' if @initialChunk
              val = val.replace TRAILING_BLANK_LINE, '' if @finalChunk
              val
            else
              val.replace SIMPLE_STRING_OMIT, (match, offset) =>
                if (@initialChunk and offset is 0) or
                   (@finalChunk and offset + match.length is val.length)
                  ''
                else
                  ' '
        @delimiter = @quote.charAt 0
        @value = makeDelimitedLiteral val, {
          @delimiter
          @double
        }
    
        @unquotedValueForTemplateLiteral = makeDelimitedLiteral val, {
          delimiter: '`'
          @double
          escapeNewlines: no
          includeDelimiters: no
          convertTrailingNullEscapes: yes
        }
    
        @unquotedValueForJSX = makeDelimitedLiteral val, {
          @double
          escapeNewlines: no
          includeDelimiters: no
          escapeDelimiter: no
        }
    
      compileNode: (o) ->
        return StringWithInterpolations.fromStringLiteral(@).compileNode o if @shouldGenerateTemplateLiteral()
        return [@makeCode @unquotedValueForJSX] if @jsx
        super o
  • §

    StringLiteral 可以表示整個文字字串或內插字串中的文字片段。當解析成前者但需要當成後者處理時(例如標記範本文字的字串部分),這將傳回一個 StringLiteral 的副本,其位置資料中已移除引號(就像解析成內插字串的一部分時一樣)。

      withoutQuotesInLocationData: ->
        endsWithNewline = @originalValue[-1..] is '\n'
        locationData = Object.assign {}, @locationData
        locationData.first_column          += @quote.length
        if endsWithNewline
          locationData.last_line -= 1
          locationData.last_column =
            if locationData.last_line is locationData.first_line
              locationData.first_column + @originalValue.length - '\n'.length
            else
              @originalValue[...-1].length - '\n'.length - @originalValue[...-1].lastIndexOf('\n')
        else
          locationData.last_column         -= @quote.length
        locationData.last_column_exclusive -= @quote.length
        locationData.range = [
          locationData.range[0] + @quote.length
          locationData.range[1] - @quote.length
        ]
        copy = new StringLiteral @originalValue, {@quote, @initialChunk, @finalChunk, @indent, @double, @heregex}
        copy.locationData = locationData
        copy
    
      isFromHeredoc: ->
        @quote.length is 3
    
      shouldGenerateTemplateLiteral: ->
        @isFromHeredoc()
    
      astNode: (o) ->
        return StringWithInterpolations.fromStringLiteral(@).ast o if @shouldGenerateTemplateLiteral()
        super o
    
      astProperties: ->
        return
          value: @originalValue
          extra:
            raw: "#{@delimiter}#{@originalValue}#{@delimiter}"
    
    exports.RegexLiteral = class RegexLiteral extends Literal
      constructor: (value, {@delimiter = '/', @heregexCommentTokens = []} = {}) ->
        super ''
        heregex = @delimiter is '///'
        endDelimiterIndex = value.lastIndexOf '/'
        @flags = value[endDelimiterIndex + 1..]
        val = @originalValue = value[1...endDelimiterIndex]
        val = val.replace HEREGEX_OMIT, '$1$2' if heregex
        val = replaceUnicodeCodePointEscapes val, {@flags}
        @value = "#{makeDelimitedLiteral val, delimiter: '/'}#{@flags}"
    
      REGEX_REGEX: /// ^ / (.*) / \w* $ ///
    
      astType: -> 'RegExpLiteral'
    
      astProperties: (o) ->
        [, pattern] = @REGEX_REGEX.exec @value
        return {
          value: undefined
          pattern, @flags, @delimiter
          originalPattern: @originalValue
          extra:
            raw: @value
            originalRaw: "#{@delimiter}#{@originalValue}#{@delimiter}#{@flags}"
            rawValue: undefined
          comments:
            for heregexCommentToken in @heregexCommentTokens
              if heregexCommentToken.here
                new HereComment(heregexCommentToken).ast o
              else
                new LineComment(heregexCommentToken).ast o
        }
    
    exports.PassthroughLiteral = class PassthroughLiteral extends Literal
      constructor: (@originalValue, {@here, @generated} = {}) ->
        super ''
        @value = @originalValue.replace /\\+(`|$)/g, (string) ->
  • §

    string 永遠是類似於 ‘`‘、‘\`‘、‘\\`‘ 等的值。將其簡化為後半部分,我們將 ‘`‘ 轉換為 ‘',‘\\\‘ 轉換為 ‘`‘ 等。

          string[-Math.ceil(string.length / 2)..]
    
      astNode: (o) ->
        return null if @generated
        super o
    
      astProperties: ->
        return {
          value: @originalValue
          here: !!@here
        }
    
    exports.IdentifierLiteral = class IdentifierLiteral extends Literal
      isAssignable: YES
    
      eachName: (iterator) ->
        iterator @
    
      astType: ->
        if @jsx
          'JSXIdentifier'
        else
          'Identifier'
    
      astProperties: ->
        return
          name: @value
          declaration: !!@isDeclaration
    
    exports.PropertyName = class PropertyName extends Literal
      isAssignable: YES
    
      astType: ->
        if @jsx
          'JSXIdentifier'
        else
          'Identifier'
    
      astProperties: ->
        return
          name: @value
          declaration: no
    
    exports.ComputedPropertyName = class ComputedPropertyName extends PropertyName
      compileNode: (o) ->
        [@makeCode('['), @value.compileToFragments(o, LEVEL_LIST)..., @makeCode(']')]
    
      astNode: (o) ->
        @value.ast o
    
    exports.StatementLiteral = class StatementLiteral extends Literal
      isStatement: YES
    
      makeReturn: THIS
    
      jumps: (o) ->
        return this if @value is 'break' and not (o?.loop or o?.block)
        return this if @value is 'continue' and not o?.loop
    
      compileNode: (o) ->
        [@makeCode "#{@tab}#{@value};"]
    
      astType: ->
        switch @value
          when 'continue' then 'ContinueStatement'
          when 'break'    then 'BreakStatement'
          when 'debugger' then 'DebuggerStatement'
    
    exports.ThisLiteral = class ThisLiteral extends Literal
      constructor: (value) ->
        super 'this'
        @shorthand = value is '@'
    
      compileNode: (o) ->
        code = if o.scope.method?.bound then o.scope.method.context else @value
        [@makeCode code]
    
      astType: -> 'ThisExpression'
    
      astProperties: ->
        return
          shorthand: @shorthand
    
    exports.UndefinedLiteral = class UndefinedLiteral extends Literal
      constructor: ->
        super 'undefined'
    
      compileNode: (o) ->
        [@makeCode if o.level >= LEVEL_ACCESS then '(void 0)' else 'void 0']
    
      astType: -> 'Identifier'
    
      astProperties: ->
        return
          name: @value
          declaration: no
    
    exports.NullLiteral = class NullLiteral extends Literal
      constructor: ->
        super 'null'
    
    exports.BooleanLiteral = class BooleanLiteral extends Literal
      constructor: (value, {@originalValue} = {}) ->
        super value
        @originalValue ?= @value
    
      astProperties: ->
        value: if @value is 'true' then yes else no
        name: @originalValue
    
    exports.DefaultLiteral = class DefaultLiteral extends Literal
      astType: -> 'Identifier'
    
      astProperties: ->
        return
          name: 'default'
          declaration: no
  • §

    傳回

  • §

    return 是個 pureStatement,將其包裝在封閉中沒有意義。

    exports.Return = class Return extends Base
      constructor: (@expression, {@belongsToFuncDirectiveReturn} = {}) ->
        super()
    
      children: ['expression']
    
      isStatement:     YES
      makeReturn:      THIS
      jumps:           THIS
    
      compileToFragments: (o, level) ->
        expr = @expression?.makeReturn()
        if expr and expr not instanceof Return then expr.compileToFragments o, level else super o, level
    
      compileNode: (o) ->
        answer = []
  • §

    待辦事項:如果我們在此呼叫 expression.compile() 兩次,有時會得到不同的結果!

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

    不要將 return 從 await return/yield 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。如果屬性鏈中有散佈運算子 ?. 穿插,事情會變得更有趣。然後,我們必須小心,在建立散佈鏈時不要意外地兩次評估任何內容。

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

    快取的片段能正確編譯順序,並重複使用範圍內的變數。範例:a(x = 5).b(-> x = 6) 應與 a(x = 5); b(-> x = 6) 以相同的順序編譯(請參閱問題 #4437,https://github.com/jashkenas/coffeescript/issues/4437)

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

    將散佈展開為 If:a?.b -> a.b if a?

      unfoldSoak: (o) ->
        @unfoldedSoak ?= do =>
          ifn = @base.unfoldSoak o
          if ifn
            ifn.body.properties.push @properties...
            return ifn
          for prop, i in @properties when prop.soak
            prop.soak = off
            fst = new Value @base, @properties[...i]
            snd = new Value @base, @properties[i..]
            if fst.shouldCache()
              ref = new IdentifierLiteral o.scope.freeVariable 'ref'
              fst = new Parens new Assign ref, fst
              snd.base = ref
            return new If new Existence(fst), snd, soak: on
          no
    
      eachName: (iterator, {checkAssignability = yes} = {}) ->
        if @hasProperties()
          iterator @
        else if not checkAssignability or @base.isAssignable()
          @base.eachName iterator
        else
          @error 'tried to assign to unassignable value'
  • §

    對於 AST 產生,我們需要一個 object,如果它有屬性,就是這個 Value 減去其最後一個屬性。

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

    取得除了最後一個以外的所有屬性;對於只有一個屬性的 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 僅有一個屬性,因此位置資料就是父 Value 的基底。

            @base.locationData
          else
  • §

    這個新的 Value 有多個屬性,因此位置資料從父 Value 的基底延伸至此新節點中包含的最後一個屬性 (亦即父節點的倒數第二個屬性)。

            mergeLocationData @base.locationData, initialProperties[initialProperties.length - 1].locationData
        object
    
      containsSoak: ->
        return no unless @hasProperties()
    
        for property in @properties when property.soak
          return yes
    
        return yes if @base instanceof Call and @base.soak
    
        no
    
      astNode: (o) ->
  • §

    如果 Value 沒有任何屬性,則 AST 節點就是此節點的 base。

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

    否則,呼叫 Base::ast,它會依序呼叫下列 astType 和 astProperties 方法。

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

    如果這個 Value 有屬性,則最後一個屬性 (例如 a.b.c 中的 c) 會變成 property,而前一個屬性 (例如 a.b) 會變成指派給 object 屬性的子 Value 節點。

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

    不要在位置資料中包含 JSX 標籤的前導 <

        mergeAstLocationData(
          jisonLocationDataToAstLocationData(@base.tagNameLocationData),
          jisonLocationDataToAstLocationData(@properties[@properties.length - 1].locationData)
        )
    
    exports.MetaProperty = class MetaProperty extends Base
      constructor: (@meta, @property) ->
        super()
    
      children: ['meta', 'property']
    
      checkValid: (o) ->
        if @meta.value is 'new'
          if @property instanceof Access and @property.name.value is 'target'
            unless o.scope.parent?
              @error "new.target can only occur inside functions"
          else
            @error "the only valid meta property for new is new.target"
        else if @meta.value is 'import'
          unless @property instanceof Access and @property.name.value is 'meta'
            @error "the only valid meta property for import is import.meta"
    
      compileNode: (o) ->
        @checkValid o
        fragments = []
        fragments.push @meta.compileToFragments(o, LEVEL_ACCESS)...
        fragments.push @property.compileToFragments(o)...
        fragments
    
      astProperties: (o) ->
        @checkValid o
    
        return
          meta: @meta.ast o, LEVEL_ACCESS
          property: @property.ast o
  • §

    HereComment

  • §

    以 ### 分隔的註解 (變成 /* */)。

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

    取消縮排多行註解。它們會在稍後重新縮排。

        if multiline
          indent = null
          for line in @content.split '\n'
            leadingWhitespace = /^\s*/.exec(line)[0]
            if not indent or leadingWhitespace.length < indent.length
              indent = leadingWhitespace
          @content = @content.replace /// \n #{indent} ///g, '\n' if indent
    
        hasLeadingMarks = /\n\s*[#|\*]/.test @content
        @content = @content.replace /^([ \t]*)#(?=\s)/gm, ' *' if hasLeadingMarks
    
        @content = "/*#{@content}#{if hasLeadingMarks then ' ' else ''}*/"
        fragment = @makeCode @content
        fragment.newLine = @newLine
        fragment.unshift = @unshift
        fragment.multiline = multiline
  • §

    不要依賴 fragment.type,因為在編譯器縮小時可能會中斷。

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

    LineComment

  • §

    從 # 延伸至行尾的註解 (變成 //)。

    exports.LineComment = class LineComment extends Base
      constructor: ({ @content, @newLine, @unshift, @locationData, @precededByBlankLine }) ->
        super()
    
      compileNode: (o) ->
        fragment = @makeCode(if /^\s*$/.test @content then '' else "#{if @precededByBlankLine then "\n#{o.indent}" else ''}//#{@content}")
        fragment.newLine = @newLine
        fragment.unshift = @unshift
        fragment.trail = not @newLine and not @unshift
  • §

    不要依賴 fragment.type,因為在編譯器縮小時可能會中斷。

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

    JSX

    exports.JSXIdentifier = class JSXIdentifier extends IdentifierLiteral
      astType: -> 'JSXIdentifier'
    
    exports.JSXTag = class JSXTag extends JSXIdentifier
      constructor: (value, {
        @tagNameLocationData
        @closingTagOpeningBracketLocationData
        @closingTagSlashLocationData
        @closingTagNameLocationData
        @closingTagClosingBracketLocationData
      }) ->
        super value
    
      astProperties: ->
        return
          name: @value
    
    exports.JSXExpressionContainer = class JSXExpressionContainer extends Base
      constructor: (@expression, {locationData} = {}) ->
        super()
        @expression.jsxAttribute = yes
        @locationData = locationData ? @expression.locationData
    
      children: ['expression']
    
      compileNode: (o) ->
        @expression.compileNode(o)
    
      astProperties: (o) ->
        return
          expression: astAsBlockIfNeeded @expression, o
    
    exports.JSXEmptyExpression = class JSXEmptyExpression extends Base
    
    exports.JSXText = class JSXText extends Base
      constructor: (stringLiteral) ->
        super()
        @value = stringLiteral.unquotedValueForJSX
        @locationData = stringLiteral.locationData
    
      astProperties: ->
        return {
          @value
          extra:
            raw: @value
        }
    
    exports.JSXAttribute = class JSXAttribute extends Base
      constructor: ({@name, value}) ->
        super()
        @value =
          if value?
            value = value.base
            if value instanceof StringLiteral and not value.shouldGenerateTemplateLiteral()
              value
            else
              new JSXExpressionContainer value
          else
            null
        @value?.comments = value.comments
    
      children: ['name', 'value']
    
      compileNode: (o) ->
        compiledName = @name.compileToFragments o, LEVEL_LIST
        return compiledName unless @value?
        val = @value.compileToFragments o, LEVEL_LIST
        compiledName.concat @makeCode('='), val
    
      astProperties: (o) ->
        name = @name
        if ':' in name.value
          name = new JSXNamespacedName name
        return
          name: name.ast o
          value: @value?.ast(o) ? null
    
    exports.JSXAttributes = class JSXAttributes extends Base
      constructor: (arr) ->
        super()
        @attributes = []
        for object in arr.objects
          @checkValidAttribute object
          {base} = object
          if base instanceof IdentifierLiteral
  • §

    沒有值的屬性,例如 disabled

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

    物件擴充屬性,例如 {…props}

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

    包含具有值的屬性的物件,例如 a=”b” c={d}

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

    捕捉無效屬性:<div {a:”b”, props} {props} “value” />

      checkValidAttribute: (object) ->
        {base: attribute} = object
        properties = attribute?.properties or []
        if not (attribute instanceof Obj or attribute instanceof IdentifierLiteral) or (attribute instanceof Obj and not attribute.generated and (properties.length > 1 or not (properties[0] instanceof Splat)))
          object.error """
            Unexpected token. Allowed JSX attributes are: id="val", src={source}, {props...} or attribute.
          """
    
      compileNode: (o) ->
        fragments = []
        for attribute in @attributes
          fragments.push @makeCode ' '
          fragments.push attribute.compileToFragments(o, LEVEL_TOP)...
        fragments
    
      astNode: (o) ->
        attribute.ast(o) for attribute in @attributes
    
    exports.JSXNamespacedName = class JSXNamespacedName extends Base
      constructor: (tag) ->
        super()
        [namespace, name] = tag.value.split ':'
        @namespace = new JSXIdentifier(namespace).withLocationDataFrom locationData: extractSameLineLocationDataFirst(namespace.length) tag.locationData
        @name      = new JSXIdentifier(name     ).withLocationDataFrom locationData: extractSameLineLocationDataLast(name.length      ) tag.locationData
        @locationData = tag.locationData
    
      children: ['namespace', 'name']
    
      astProperties: (o) ->
        return
          namespace: @namespace.ast o
          name: @name.ast o
  • §

    JSX 元素的節點

    exports.JSXElement = class JSXElement extends Base
      constructor: ({@tagName, @attributes, @content}) ->
        super()
    
      children: ['tagName', 'attributes', 'content']
    
      compileNode: (o) ->
        @content?.base.jsx = yes
        fragments = [@makeCode('<')]
        fragments.push (tag = @tagName.compileToFragments(o, LEVEL_ACCESS))...
        fragments.push @attributes.compileToFragments(o)...
        if @content
          fragments.push @makeCode('>')
          fragments.push @content.compileNode(o, LEVEL_LIST)...
          fragments.push [@makeCode('</'), tag..., @makeCode('>')]...
        else
          fragments.push @makeCode(' />')
        fragments
    
      isFragment: ->
        [email protected]
    
      astNode: (o) ->
  • §

    包含元素屬性的 Arr 會擷取跨越開啟元素 < … > 的位置資料

        @openingElementLocationData = jisonLocationDataToAstLocationData @attributes.locationData
    
        tagName = @tagName.base
        tagName.locationData = tagName.tagNameLocationData
        if @content?
          @closingElementLocationData = mergeAstLocationData(
            jisonLocationDataToAstLocationData tagName.closingTagOpeningBracketLocationData
            jisonLocationDataToAstLocationData tagName.closingTagClosingBracketLocationData
          )
    
        super o
    
      astType: ->
        if @isFragment()
          'JSXFragment'
        else
          'JSXElement'
    
      elementAstProperties: (o) ->
        tagNameAst = =>
          tag = @tagName.unwrap()
          if tag?.value and ':' in tag.value
            tag = new JSXNamespacedName tag
          tag.ast o
    
        openingElement = Object.assign {
          type: 'JSXOpeningElement'
          name: tagNameAst()
          selfClosing: not @closingElementLocationData?
          attributes: @attributes.ast o
        }, @openingElementLocationData
    
        closingElement = null
        if @closingElementLocationData?
          closingElement = Object.assign {
            type: 'JSXClosingElement'
            name: Object.assign(
              tagNameAst(),
              jisonLocationDataToAstLocationData @tagName.base.closingTagNameLocationData
            )
          }, @closingElementLocationData
          if closingElement.name.type in ['JSXMemberExpression', 'JSXNamespacedName']
            rangeDiff = closingElement.range[0] - openingElement.range[0] + '/'.length
            columnDiff = closingElement.loc.start.column - openingElement.loc.start.column + '/'.length
            shiftAstLocationData = (node) =>
              node.range = [
                node.range[0] + rangeDiff
                node.range[1] + rangeDiff
              ]
              node.start += rangeDiff
              node.end += rangeDiff
              node.loc.start =
                line: @closingElementLocationData.loc.start.line
                column: node.loc.start.column + columnDiff
              node.loc.end =
                line: @closingElementLocationData.loc.start.line
                column: node.loc.end.column + columnDiff
            if closingElement.name.type is 'JSXMemberExpression'
              currentExpr = closingElement.name
              while currentExpr.type is 'JSXMemberExpression'
                shiftAstLocationData currentExpr unless currentExpr is closingElement.name
                shiftAstLocationData currentExpr.property
                currentExpr = currentExpr.object
              shiftAstLocationData currentExpr
            else # JSXNamespacedName
              shiftAstLocationData closingElement.name.namespace
              shiftAstLocationData closingElement.name.name
    
        {openingElement, closingElement}
    
      fragmentAstProperties: (o) ->
        openingFragment = Object.assign {
          type: 'JSXOpeningFragment'
        }, @openingElementLocationData
    
        closingFragment = Object.assign {
          type: 'JSXClosingFragment'
        }, @closingElementLocationData
    
        {openingFragment, closingFragment}
    
      contentAst: (o) ->
        return [] unless @content and not @content.base.isEmpty?()
    
        content = @content.unwrapAll()
        children =
          if content instanceof StringLiteral
            [new JSXText content]
          else # StringWithInterpolations
            for element in @content.unwrapAll().extractElements o, includeInterpolationWrappers: yes, isJsx: yes
              if element instanceof StringLiteral
                new JSXText element
              else # Interpolation
                {expression} = element
                unless expression?
                  emptyExpression = new JSXEmptyExpression()
                  emptyExpression.locationData = emptyExpressionLocationData {
                    interpolationNode: element
                    openingBrace: '{'
                    closingBrace: '}'
                  }
    
                  new JSXExpressionContainer emptyExpression, locationData: element.locationData
                else
                  unwrapped = expression.unwrapAll()
                  if unwrapped instanceof JSXElement and
  • §

    區分 <a><b /></a> 和 <a>{<b />}</a>

                      unwrapped.locationData.range[0] is element.locationData.range[0]
                    unwrapped
                  else
                    new JSXExpressionContainer unwrapped, locationData: element.locationData
    
        child.ast(o) for child in children when not (child instanceof JSXText and child.value.length is 0)
    
      astProperties: (o) ->
        Object.assign(
          if @isFragment()
            @fragmentAstProperties o
          else
            @elementAstProperties o
        ,
          children: @contentAst o
        )
    
      astLocationData: ->
        if @closingElementLocationData?
          mergeAstLocationData @openingElementLocationData, @closingElementLocationData
        else
          @openingElementLocationData
  • §

    呼叫

  • §

    函式呼叫的節點。

    exports.Call = class Call extends Base
      constructor: (@variable, @args = [], @soak, @token) ->
        super()
    
        @implicit = @args.implicit
        @isNew = no
        if @variable instanceof Value and @variable.isNotCallable()
          @variable.error "literal is not a function"
    
        if @variable.base instanceof JSXTag
          return new JSXElement(
            tagName: @variable
            attributes: new JSXAttributes @args[0].base
            content: @args[1]
          )
  • §

    @variable 永遠不會因為這個節點作為 RegexWithInterpolations 的一部分而建立而輸出為結果,因此,對於那個情況,將任何註解移至透過語法傳遞到 RegexWithInterpolations 的 args 屬性。

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

    設定位置時,我們有時需要更新開始位置,以考量我們左邊新發現的 new 營運子。這會擴充左邊的範圍,但不會擴充右邊的範圍。

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

    標記此呼叫為建立新執行個體。

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

    浸泡式鏈結呼叫會展開為 if/else 三元結構。

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

    編譯純粹的函式呼叫。

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

    如果變數是 Accessor 片段會被快取,並在 Value::compileNode 中稍後使用,以確保編譯的正確順序,以及在範圍內重複使用變數。範例:a(x = 5).b(-> x = 6) 應編譯為與 a(x = 5); b(-> x = 6) 相同的順序(請參閱問題 #4437,https://github.com/jashkenas/coffeescript/issues/4437)

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

    Super

  • §

    負責將 super() 呼叫轉換為對同名原型函式的呼叫。當設定 expressions 時,呼叫將以不改變 SuperCall 表達式回傳值的方式編譯。

    exports.SuperCall = class SuperCall extends Call
      children: Call::children.concat ['expressions']
    
      isStatement: (o) ->
        @expressions?.length and o.level is LEVEL_TOP
    
      compileNode: (o) ->
        return super o unless @expressions?.length
    
        superCall   = new Literal fragmentsToText super o
        replacement = new Block @expressions.slice()
    
        if o.level > LEVEL_TOP
  • §

    如果我們可能在表達式中,我們需要快取並回傳結果

          [superCall, ref] = superCall.cache o, null, YES
          replacement.push ref
    
        replacement.unshift superCall
        replacement.compileToFragments o, if o.level is LEVEL_TOP then o.level else LEVEL_LIST
    
    exports.Super = class Super extends Base
      constructor: (@accessor, @superLiteral) ->
        super()
    
      children: ['accessor']
    
      compileNode: (o) ->
        @checkInInstanceMethod o
    
        method = o.scope.namedMethod()
        unless method.ctor? or @accessor?
          {name, variable} = method
          if name.shouldCache() or (name instanceof Index and name.index.isAssignable())
            nref = new IdentifierLiteral o.scope.parent.freeVariable 'name'
            name.index = new Assign nref, name.index
          @accessor = if nref? then new Index nref else name
    
        if @accessor?.name?.comments
  • §

    super() 呼叫會編譯為例如 super.method(),這表示 method 屬性名稱在此處第一次編譯,並在類別的 method: 屬性編譯時再次編譯。由於此編譯首先發生,附加至 method: 的註解會在 super.method() 附近錯誤輸出,而我們希望它們在第二次傳遞時在 method: 輸出時輸出。因此在此編譯傳遞期間將它們擱置一旁,並將它們放回物件中,以便它們在稍後的編譯中存在。

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

    擴充

  • §

    節點用於擴充物件的原型,並使用祖先物件。取自 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
  • §

    存取

  • §

    使用 . 存取值中的屬性,或使用 :: 簡寫方式存取物件的原型。

    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 識別碼節點包含在 MemberExpression 節點的 property 中。

        @name.ast o
  • §

    索引

  • §

    使用 [ ... ] 編制索引,存取陣列或物件。

    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 識別碼節點包含在 MemberExpression 節點的 property 中。由於 MemberExpression 的 property 是 Index,因此 MemberExpression 的 computed 為 true。

        @index.ast o
  • §

    範圍

  • §

    範圍文字。範圍可以用於擷取陣列的部分 (切片),指定理解的範圍,或作為值,在執行階段擴充為對應的整數陣列。

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

    切片

  • §

    陣列切片文字。與 JavaScript 的 Array#slice 不同,第二個參數指定切片的結尾索引,就像第一個參數是開頭的索引一樣。

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

    陣列

  • §

    陣列文字。

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

    類別

  • §

    CoffeeScript 類別定義。使用其名稱、一個選擇性的超類別和一個主體來初始化一個 類別。

    exports.Class = class Class extends Base
      children: ['variable', 'parent', 'body']
    
      constructor: (@variable, @parent, @body) ->
        super()
        unless @body?
          @body = new Block
          @hasGeneratedBody = yes
    
      compileNode: (o) ->
        @name          = @determineName()
        executableBody = @walkBody o
  • §

    特殊處理,允許 class expr.A extends A 宣告

        parentName    = @parent.base.value if @parent instanceof Value and not @parent.hasProperties()
        @hasNameClash = @name? and @name is parentName
    
        node = @
    
        if executableBody or @hasNameClash
          node = new ExecutableClassBody node, executableBody
        else if not @name? and o.level is LEVEL_TOP
  • §

    匿名類別僅在表達式中有效

          node = new Parens node
    
        if @boundMethods.length and @parent
          @variable ?= new IdentifierLiteral o.scope.freeVariable '_class'
          [@variable, @variableRef] = @variable.cache o unless @variableRef?
    
        if @variable
          node = new Assign @variable, node, null, { @moduleDeclaration }
    
        @compileNode = @compileClassDeclaration
        try
          return node.compileToFragments o
        finally
          delete @compileNode
    
      compileClassDeclaration: (o) ->
        @ctor ?= @makeDefaultConstructor() if @externalCtor or @boundMethods.length
        @ctor?.noReturn = true
    
        @proxyBoundMethods() if @boundMethods.length
    
        o.indent += TAB
    
        result = []
        result.push @makeCode "class "
        result.push @makeCode @name if @name
        @compileCommentFragments o, @variable, result if @variable?.comments?
        result.push @makeCode ' ' if @name
        result.push @makeCode('extends '), @parent.compileToFragments(o)..., @makeCode ' ' if @parent
    
        result.push @makeCode '{'
        unless @body.isEmpty()
          @body.spaced = true
          result.push @makeCode '\n'
          result.push @body.compileToFragments(o, LEVEL_TOP)...
          result.push @makeCode "\n#{@tab}"
        result.push @makeCode '}'
    
        result
  • §

    找出此類別的適當名稱

      determineName: ->
        return null unless @variable
        [..., tail] = @variable.properties
        node = if tail
          tail instanceof Access and tail.name
        else
          @variable.base
        unless node instanceof IdentifierLiteral or node instanceof PropertyName
          return null
        name = node.value
        unless tail
          message = isUnassignable name
          @variable.error message if message
        if name in JS_FORBIDDEN then "_#{name}" else name
    
      walkBody: (o) ->
        @ctor          = null
        @boundMethods  = []
        executableBody = null
    
        initializer     = []
        { expressions } = @body
    
        i = 0
        for expression in expressions.slice()
          if expression instanceof Value and expression.isObject true
            { properties } = expression.base
            exprs     = []
            end       = 0
            start     = 0
            pushSlice = -> exprs.push new Value new Obj properties[start...end], true if end > start
    
            while assign = properties[end]
              if initializerExpression = @addInitializerExpression assign, o
                pushSlice()
                exprs.push initializerExpression
                initializer.push initializerExpression
                start = end + 1
              end++
            pushSlice()
    
            expressions[i..i] = exprs
            i += exprs.length
          else
            if initializerExpression = @addInitializerExpression expression, o
              initializer.push initializerExpression
              expressions[i] = initializerExpression
            i += 1
    
        for method in initializer when method instanceof Code
          if method.ctor
            method.error 'Cannot define more than one constructor in a class' if @ctor
            @ctor = method
          else if method.isStatic and method.bound
            method.context = @name
          else if method.bound
            @boundMethods.push method
    
        return unless o.compiling
        if initializer.length isnt expressions.length
          @body.expressions = (expression.hoist() for expression in initializer)
          new Block expressions
  • §

    將表達式新增到類別初始化程式

    這是用於判斷類別主體中的表達式應出現在初始化程式或可執行主體中的關鍵方法。如果指定的 node 在類別主體中有效,則方法會傳回一個(新的、修改的或相同的)節點以包含在類別初始化程式中,否則不會傳回任何內容,而節點將出現在可執行主體中。

    在撰寫本文時,只有方法(實例和靜態)在 ES 類別初始化程式中有效。隨著新的 ES 類別功能(例如類別欄位)達到第 4 階段,此方法需要更新以支援它們。我們還允許在初始化程式中使用 PassthroughLiteral(反引號表達式)作為未實作 ES 功能的跳脫口(例如透過 get 和 set 關鍵字定義的 getter 和 setter,而不是 Object.defineProperty 方法)。

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

    匯入和匯出

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

    待辦事項:在 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
  • §

    指定

  • §

    指定用於將區域變數指定給值,或設定物件的屬性,包括物件文字中的屬性。

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

    如果此指定識別碼附加一個或多個此處註解,請將它們作為宣告列的一部分輸出(除非其他此處註解已分段放置在那裡),以與 Flow 打字相容。如果此指定是給類別,例如 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://mdn.club.tw/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#Assignment_without_declaration,如果我們在不宣告的情況下解構,解構指定必須包含在括號中。如果 'o.level' 的優先順序低於 LEVEL_LIST (3)(即 LEVEL_COND (4)、LEVEL_OP (5) 或 LEVEL_ACCESS (6)),或者如果我們正在解構物件,例如 {a,b} = obj,則指定會包含在括號中。

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

    物件 rest 屬性不可指定:{{a}...}

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

    當將陣列或物件文字指定給值時,遞迴模式比對的簡要實作。窺探其屬性以指定內部名稱。

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

    {} = a 和 [] = a(空樣式)的特殊情況。編譯為僅 a。

        if olen is 0
          code = value.compileToFragments o
          return if o.level >= LEVEL_OP then @wrapInParentheses code else code
        [obj] = objects
    
        @disallowLoneExpansion()
        {splats, expans, splatsAndExpans} = @getAndCheckSplatsAndExpansions()
    
        isSplat = splats?.length > 0
        isExpans = expans?.length > 0
    
        vvar     = value.compileToFragments o, LEVEL_LIST
        vvarText = fragmentsToText vvar
        assigns  = []
        pushAssign = (variable, val) =>
          assigns.push new Assign(variable, val, null, param: @param, subpattern: yes).compileToFragments o, LEVEL_LIST
    
        if isSplat
          splatVar = objects[splats[0]].name.unwrap()
          if splatVar instanceof Arr or splatVar instanceof Obj
            splatVarRef = new IdentifierLiteral o.scope.freeVariable 'ref'
            objects[splats[0]].name = splatVarRef
            splatVarAssign = -> pushAssign new Value(splatVar), splatVarRef
  • §

    在這個時候,有許多事情要解構。因此,例如 {a, b} = fn() 中的 fn() 必須快取。如果 vvar 不是簡單變數,請將其轉換為簡單變數。

        if value.unwrap() not instanceof IdentifierLiteral or @variable.assigns(vvarText)
          ref = o.scope.freeVariable 'ref'
          assigns.push [@makeCode(ref + ' = '), vvar...]
          vvar = [@makeCode ref]
          vvarText = ref
    
        slicer = (type) -> (vvar, start, end = no) ->
          vvar = new IdentifierLiteral vvar unless vvar instanceof Value
          args = [vvar, new NumberLiteral(start)]
          args.push new NumberLiteral end if end
          slice = new Value (new IdentifierLiteral utility type, o), [new Access new PropertyName 'call']
          new Value new Call slice, args
  • §

    輸出 [].slice 程式碼的輔助程式。

        compSlice = slicer "slice"
  • §

    輸出 [].splice 程式碼的輔助程式。

        compSplice = slicer "splice"
  • §

    檢查 objects 陣列是否包含任何 Assign 執行個體,例如 {a:1}。

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

    檢查 objects 陣列是否包含任何不可指派的物件。

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

    當物件指派 ({a:1})、不可指派物件或僅單一節點時,objects 會很複雜。

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

    「複雜」的 objects 會在迴圈中處理。範例:[a, b, {c, r…}, d]、[a, …, {b, r…}, c, d]

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

    可以略過「省略」。

            continue if obj instanceof Elision
  • §

    如果 obj 是 {a: 1}

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

    obj 是 [a…], {a…} 或 a

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

    「簡單」的 objects 可以拆分並編譯成陣列,[a, b, c] = arr、[a, b, c…] = arr

        assignObjects = (objs, vvar, vvarTxt) =>
          vvar = new Value new Arr(objs, yes)
          vval = if vvarTxt instanceof Value then vvarTxt else new Value new Literal(vvarTxt)
          pushAssign vvar, vval
    
        processObjects = (objs, vvar, vvarTxt) ->
          if complexObjects objs
            loopObjects objs, vvar, vvarTxt
          else
            assignObjects objs, vvar, vvarTxt
  • §

    如果 objects 中有 Splat 或 Expansion,我們可以將陣列拆分成兩個簡單的子陣列。Splat [a, b, c…, d, e] 可以拆分成 [a, b, c…] 和 [d, e]。Expansion [a, b, …, c, d] 可以拆分成 [a, b] 和 [c, d]。範例:a) Splat CS: [a, b, c…, d, e] = arr JS: [a, b, …c] = arr、[d, e] = splice.call(c, -2) b) Expansion CS: [a, b, …, d, e] = arr JS: [a, b] = arr、[d, e] = slice.call(arr, -2)

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

    切片或拼接 objects。

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

    objects 中沒有 Splat 或 Expansion。

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

    由於某些原因,不允許 [...] = a。(是否等於 [] = a?)

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

    如果有多個 Splat 或 Expansion,則顯示錯誤。範例:[a, b, c…, d, e, f…], [a, b, …, c, d, …], [a, b, …, c, d, e…]

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

    計算所有 Splats:[a, b, c…, d, e]

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

    計算所有 Expansions:[a, b, …, c, d]

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

    結合 splats 和 expansions。

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

    程式碼

  • §

    函式定義。這是唯一建立新 Scope 的節點。當為了遍歷函式主體的內容時,程式碼沒有子節點 – 它們在內部 Scope 中。

    exports.Code = class Code extends Base
      constructor: (params, body, @funcGlyph, @paramStart) ->
        super()
    
        @params      = params or []
        @body        = body or new Block
        @bound       = @funcGlyph?.glyph is '=>'
        @isGenerator = no
        @isAsync     = no
        @isMethod    = no
    
        @body.traverseChildren no, (node) =>
          if (node instanceof Op and node.isYield()) or node instanceof YieldReturn
            @isGenerator = yes
          if (node instanceof Op and node.isAwait()) or node instanceof AwaitReturn
            @isAsync = yes
          if node instanceof For and node.isAwait()
            @isAsync = yes
    
        @propagateLhs()
    
      children: ['params', 'body']
    
      isStatement: -> @isMethod
    
      jumps: NO
    
      makeScope: (parentScope) -> new Scope parentScope, @body, this
  • §

    編譯會建立一個新的 Scope,除非明確要求與外部 Scope 共用。透過將此類參數設定為函式定義中的最後一個參數,來處理參數清單中的 splat 參數,這是 ES2015 規格的要求。如果 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
  • §

    剖析參數,將它們新增到要放入函數定義的參數清單中;並處理散列或展開,包括將表達式新增到函數主體中,以宣告所有會在散列/展開參數之後的參數變數。如果我們遇到一個參數,需要在函數主體中宣告,例如它已使用 this 解構,也宣告並指派函數主體中所有後續參數,以便任何非冪等參數都能以正確順序評估。

        for param, i in @params
  • §

    是否已對這個參數使用 ...?散列/展開參數無法有預設值,所以我們不必擔心這個。

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

    ES 以奇怪的方式處理散列陣列;在函數主體中以舊式方式處理它們。待辦事項:這是否應該在函數參數清單中處理,如果是,如何處理?

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

    剖析所有其他參數;如果尚未遇到散列參數,將這些其他參數新增到要輸出在函數定義中的清單中。

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

    如果這個參數出現在散列或展開之前,它將會在函數定義參數清單中。

            unless haveSplatParam
  • §

    如果此參數有預設值,且尚未由上述的 shouldCache() 區塊設定,請將其定義為函式主體中的陳述式。此參數位於展開參數之後,因此我們無法在參數清單中定義其預設值。

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

    將此參數的參照新增至函式範圍。

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

    此參數已解構。

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

    此參數的編譯僅用於取得其名稱以新增至範圍名稱追蹤;由於此處的編譯輸出並未保留供最終輸出,因此請勿在此編譯中包含註解,以免在實際編譯此參數時輸出這些註解。

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

    如果此參數有預設值,由於它不再位於函式參數清單中,因此我們需要將其預設值(如有必要)指定為主體中的表達式。

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

    將此參數新增至範圍,因為它先前已被略過,因此尚未新增。

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

    如果展開或擴充參數之後有參數,則需要在函式主體中指定這些參數。

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

    短路 traverseChildren 方法,以防止它跨越範圍邊界,除非 crossScope 為 true。

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

    此參數是否使用 ...?(每個函數只允許一個此類參數。)

          if param.splat or param instanceof Expansion
            if seenSplatParam
              param.error 'only one splat or expansion parameter is allowed per function definition'
            else if param instanceof Expansion and @params.length is 1
              param.error 'an expansion parameter cannot be the only parameter in a function definition'
            seenSplatParam = yes
    
      expandCtorSuper: (thisAssignments) ->
        return false unless @ctor
    
        seenSuper = @eachSuperCall @body, (superCall) =>
          superCall.expressions = thisAssignments
    
        haveThisParam = thisAssignments.length and thisAssignments.length isnt @thisAssignments?.length
        if @ctor is 'derived' and not seenSuper and haveThisParam
          param = thisAssignments[0].variable
          @flagThisParamInDerivedClassConstructorWithoutCallingSuper param
    
        seenSuper
  • §

    在給定的內容節點中尋找所有 super 呼叫;如果呼叫 iterator,則傳回 true。

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

    建構函數中的 super(唯一沒有存取器的 super)不能給予帶有對 this 參照的引數,因為這會在呼叫 super 之前參照 this。

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

    super 在繫結(箭頭)函數中具有相同的目標,因此也要檢查它們

          child not instanceof SuperCall and (child not instanceof Code or child.bound)
    
        seenSuper
    
      propagateLhs: ->
        for param in @params
          {name} = param
          if name instanceof Arr or name instanceof Obj
            name.propagateLhs yes
          else if param instanceof Expansion
            param.lhs = yes
    
      astAddParamsToScope: (o) ->
        @eachParamName (name) ->
          o.scope.add name, 'param'
    
      astNode: (o) ->
        @updateOptions o
        @checkForAsyncOrGeneratorConstructor()
        @checkForDuplicateParams()
        @disallowSuperInParamDefaults forAst: yes
        @disallowLoneExpansionAndMultipleSplats()
        seenSuper = @checkSuperCallsInConstructorBody()
        if @ctor is 'derived' and not seenSuper
          @eachParamName (name, node) =>
            if node.this
              @flagThisParamInDerivedClassConstructorWithoutCallingSuper node
        @astAddParamsToScope o
        @body.makeReturn null, yes unless @body.isEmpty() or @noReturn
    
        super o
    
      astType: ->
        if @isMethod
          'ClassMethod'
        else if @bound
          'ArrowFunctionExpression'
        else
          'FunctionExpression'
    
      paramForAst: (param) ->
        return param if param instanceof Expansion
        {name, value, splat} = param
        if splat
          new Splat name, lhs: yes, postfix: splat.postfix
          .withLocationDataFrom param
        else if value?
          new Assign name, value, null, param: yes
          .withLocationDataFrom locationData: mergeLocationData name.locationData, value.locationData
        else
          name
    
      methodAstProperties: (o) ->
        getIsComputed = =>
          return yes if @name instanceof Index
          return yes if @name instanceof ComputedPropertyName
          return yes if @name.name instanceof ComputedPropertyName
          no
    
        return
          static: !!@isStatic
          key: @name.ast o
          computed: getIsComputed()
          kind:
            if @ctor
              'constructor'
            else
              'method'
          operator: @operatorToken?.value ? '='
          staticClassName: @isStatic.staticClassName?.ast(o) ? null
          bound: !!@bound
    
      astProperties: (o) ->
        return Object.assign
          params: @paramForAst(param).ast(o) for param in @params
          body: @body.ast (Object.assign {}, o, checkForDirectives: yes), LEVEL_TOP
          generator: !!@isGenerator
          async: !!@isAsync
  • §

    我們從不產生命名函式,因此將 id 指定為 null,這與匿名函式表達式/箭頭函式的 Babel AST 相符

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

    參數

  • §

    函式定義中的參數。除了典型的 JavaScript 參數之外,這些參數還可以附加到函式的內容中,也可以是一個展開運算子,將一組參數收集到一個陣列中。

    exports.Param = class Param extends Base
      constructor: (@name, @value, @splat) ->
        super()
    
        message = isUnassignable @name.unwrapAll().value
        @name.error message if message
        if @name instanceof Obj and @name.generated
          token = @name.objects[0].operatorToken
          token.error "unexpected #{token.value}"
    
      children: ['name', 'value']
    
      compileToFragments: (o) ->
        @name.compileToFragments o, LEVEL_LIST
    
      compileToFragmentsWithoutComments: (o) ->
        @name.compileToFragmentsWithoutComments o, LEVEL_LIST
    
      asReference: (o) ->
        return @reference if @reference
        node = @name
        if node.this
          name = node.properties[0].name.value
          name = "_#{name}" if name in JS_FORBIDDEN
          node = new IdentifierLiteral o.scope.freeVariable name
        else if node.shouldCache()
          node = new IdentifierLiteral o.scope.freeVariable 'arg'
        node = new Value node
        node.updateLocationDataIfMissing @locationData
        @reference = node
    
      shouldCache: ->
        @name.shouldCache()
  • §

    反覆運算 Param 的名稱或名稱。從某種意義上說,解構參數表示多個 JS 參數。此方法允許反覆運算所有參數。iterator 函式將會以 iterator(name, node) 的形式呼叫,其中 name 是參數的名稱,而 node 是對應於該名稱的 AST 節點。

      eachName: (iterator, name = @name) ->
        checkAssignabilityOfLiteral = (literal) ->
          message = isUnassignable literal.value
          if message
            literal.error message
          unless literal.isAssignable()
            literal.error "'#{literal.value}' can't be assigned"
    
        atParam = (obj, originalObj = null) => iterator "@#{obj.properties[0].name.value}", obj, @, originalObj
        if name instanceof Call
          name.error "Function invocation can't be assigned"
  • §
    • 簡單字面值 foo
        if name instanceof Literal
          checkAssignabilityOfLiteral name
          return iterator name.value, name, @
  • §
    • at-params @foo
        return atParam name if name instanceof Value
        for obj in name.objects ? []
  • §

    儲存原始 obj。

          nObj = obj
  • §
    • 具有預設值的解構參數
          if obj instanceof Assign and not obj.context?
            obj = obj.variable
  • §
    • 解構參數內的指定 {foo:bar}
          if obj instanceof Assign
  • §

    … 可能具有預設值

            if obj.value instanceof Assign
              obj = obj.value.variable
            else
              obj = obj.value
            @eachName iterator, obj.unwrap()
  • §
    • 解構參數內的展開運算子 [xs...]
          else if obj instanceof Splat
            node = obj.name.unwrap()
            iterator node.value, node, @
          else if obj instanceof Value
  • §
    • 解構參數內的解構參數 [{a}]
            if obj.isArray() or obj.isObject()
              @eachName iterator, obj.base
  • §
    • 解構參數內的 at-params {@foo}
            else if obj.this
              atParam obj, nObj
  • §
    • 簡單的解構參數 {foo}
            else
              checkAssignabilityOfLiteral obj.base
              iterator obj.base.value, obj.base, @
          else if obj instanceof Elision
            obj
          else if obj not instanceof Expansion
            obj.error "illegal parameter #{obj.compile()}"
        return
  • §

    透過將給定的 AST 節點替換為一個名稱的新節點,來重新命名參數。這需要確保物件解構的來源不會改變。

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

    如果變數尚未保留,則不需要為解構變數指定新變數。範例:({@foo}) -> 應編譯為 ({foo}) { this.foo = foo} foo = 1; ({@foo}) -> 應編譯為 foo = 1; ({foo:foo1}) { this.foo = foo1 }

            if node.this and key.value is newNode.value
              new Value newNode
            else
              new Assign new Value(key), newNode, 'object'
          else
            newNode
    
        @replaceInContext isNode, replacement
  • §

    展開

  • §

    展開,作為函數參數、呼叫參數或解構賦值的一部分。

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

    擴充

  • §

    用於略過陣列解構(模式比對)或參數清單中的值。

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

    省略

  • §

    陣列省略元素(例如 [,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
  • §

    輸入

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

    compileOrTest 僅在我們有一個沒有展開運算子的陣列文字時

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

    拋出

  • §

    拋出例外狀況的簡單節點。

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

    Throw 已經是一種回傳,某種程度上來說…

      makeReturn: THIS
    
      compileNode: (o) ->
        fragments = @expression.compileToFragments o, LEVEL_LIST
        unshiftAfterComments fragments, @makeCode 'throw '
        fragments.unshift @makeCode @tab
        fragments.push @makeCode ';'
        fragments
    
      astType: -> 'ThrowStatement'
    
      astProperties: (o) ->
        return
          argument: @expression.ast o, LEVEL_LIST
  • §

    存在

  • §

    檢查一個變數是否存在 – 不是 null 也不是 undefined。這類似於 Ruby 中的 .nil?,而且避免必須查閱 JavaScript 真值表。選擇性地僅檢查一個變數是否不是 undefined。

    exports.Existence = class Existence extends Base
      constructor: (@expression, onlyNotUndefined = no) ->
        super()
        @comparisonTarget = if onlyNotUndefined then 'undefined' else 'null'
        salvagedComments = []
        @expression.traverseChildren yes, (child) ->
          if child.comments
            for comment in child.comments
              salvagedComments.push comment unless comment in salvagedComments
            delete child.comments
        attachCommentsToNode salvagedComments, @
        moveComments @expression, @
    
      children: ['expression']
    
      invert: NEGATE
    
      compileNode: (o) ->
        @expression.front = @front
        code = @expression.compile o, LEVEL_OP
        if @expression.unwrap() instanceof IdentifierLiteral and not o.scope.check code
          [cmp, cnj] = if @negated then ['===', '||'] else ['!==', '&&']
          code = "typeof #{code} #{cmp} \"undefined\"" + if @comparisonTarget isnt 'undefined' then " #{cnj} #{code} #{cmp} #{@comparisonTarget}" else ''
        else
  • §

    當與 null 比較時,我們明確地想要使用寬鬆相等(==),以便存在檢查大致對應於真值檢查。不要 將此變更為 null 的 ===,因為這將會破壞大量現有程式碼。然而,當僅與 undefined 比較時,我們想要使用 ===,因為此使用案例是為了與 ES2015+ 預設值相容,而預設值僅在變數為 undefined(但不是 null)時才會被指定。

          cmp = if @comparisonTarget is 'null'
            if @negated then '==' else '!='
          else # `undefined`
            if @negated then '===' else '!=='
          code = "#{code} #{cmp} #{@comparisonTarget}"
        [@makeCode(if o.level <= LEVEL_COND then code else "(#{code})")]
    
      astType: -> 'UnaryExpression'
    
      astProperties: (o) ->
        return
          argument: @expression.ast o
          operator: '?'
          prefix: no
  • §

    括號

  • §

    來源中明確指定的額外一組括號。我們曾試圖透過偵測和移除多餘的括號來清理結果,但不再這麼做 – 你可以隨意輸入任意數量。

    括號是強制任何陳述成為表達式的絕佳方式。

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

    如果這些括號包住一個 IdentifierLiteral 後面接著區塊註解,請輸出括號(或換句話說,不要最佳化移除這些多餘的括號)。這是因為 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()
  • §

    對於

  • §

    CoffeeScript 取代 for 迴圈的是陣列和物件理解,它們在此編譯成 for 迴圈。它們也作為一個表達式,能夠傳回每個過濾反覆運算的結果。

    與 Python 陣列理解不同,它們可以是多行的,而且你可以傳遞迴圈的目前索引作為第二個參數。與 Ruby 區塊不同,你可以在單一傳遞中對應和過濾。

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

    切換

  • §

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

    縮排為兩個空格,以利美化列印。

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

    有時在編譯節點時,我們希望在片段陣列的開頭插入一個片段;但如果開頭有一個或多個註解片段,我們希望在這些片段之後但在任何非註解片段之前插入此片段。

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

    如果浸泡,展開節點的子節點,然後將節點塞入已建立的 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
  • §

    取得兩個節點的位置資料,並傳回一個新的 locationData 物件,其中包含兩個節點的位置資料。因此新的 first_line 值將是兩個節點的 first_line 值中較早的一個,新的 last_column 是兩個節點的 last_column 值中較晚的一個,依此類推。

    如果您只想使用第二個節點的開始或結束位置資料來延伸第一個節點的位置資料,請傳遞 justLeading 或 justEnding 選項。因此,例如,如果 first 的範圍是 [4, 5],而 second 的範圍是 [1, 10],您將取得

    mergeLocationData(first, second).range                   # [1, 10]
    mergeLocationData(first, second, justLeading: yes).range # [1, 5]
    mergeLocationData(first, second, justEnding:  yes).range # [4, 10]
    
    exports.mergeLocationData = mergeLocationData = (locationDataA, locationDataB, {justLeading, justEnding} = {}) ->
      return Object.assign(
        if justEnding
          first_line:   locationDataA.first_line
          first_column: locationDataA.first_column
        else
          if isLocationDataStartGreater locationDataA, locationDataB
            first_line:   locationDataB.first_line
            first_column: locationDataB.first_column
          else
            first_line:   locationDataA.first_line
            first_column: locationDataA.first_column
      ,
        if justLeading
          last_line:             locationDataA.last_line
          last_column:           locationDataA.last_column
          last_line_exclusive:   locationDataA.last_line_exclusive
          last_column_exclusive: locationDataA.last_column_exclusive
        else
          if isLocationDataEndGreater locationDataA, locationDataB
            last_line:             locationDataA.last_line
            last_column:           locationDataA.last_column
            last_line_exclusive:   locationDataA.last_line_exclusive
            last_column_exclusive: locationDataA.last_column_exclusive
          else
            last_line:             locationDataB.last_line
            last_column:           locationDataB.last_column
            last_line_exclusive:   locationDataB.last_line_exclusive
            last_column_exclusive: locationDataB.last_column_exclusive
      ,
        range: [
          if justEnding
            locationDataA.range[0]
          else
            lesser locationDataA.range[0], locationDataB.range[0]
        ,
          if justLeading
            locationDataA.range[1]
          else
            greater locationDataA.range[1], locationDataB.range[1]
        ]
      )
  • §

    取兩個 AST 節點,或兩個 AST 節點的位置資料物件,並傳回一個新的位置資料物件,其中包含兩個節點的位置資料。因此,新的 start 值將會是兩個節點的 start 值中較早的一個,新的 end 值將會是兩個節點的 end 值中較晚的一個,依此類推。

    如果您只想使用第二個節點的開始或結束位置資料來延伸第一個節點的位置資料,請傳遞 justLeading 或 justEnding 選項。因此,例如,如果 first 的範圍是 [4, 5],而 second 的範圍是 [1, 10],您將取得

    mergeAstLocationData(first, second).range                   # [1, 10]
    mergeAstLocationData(first, second, justLeading: yes).range # [1, 5]
    mergeAstLocationData(first, second, justEnding:  yes).range # [4, 10]
    
    exports.mergeAstLocationData = mergeAstLocationData = (nodeA, nodeB, {justLeading, justEnding} = {}) ->
      return
        loc:
          start:
            if justEnding
              nodeA.loc.start
            else
              if isAstLocGreater nodeA.loc.start, nodeB.loc.start
                nodeB.loc.start
              else
                nodeA.loc.start
          end:
            if justLeading
              nodeA.loc.end
            else
              if isAstLocGreater nodeA.loc.end, nodeB.loc.end
                nodeA.loc.end
              else
                nodeB.loc.end
        range: [
          if justEnding
            nodeA.range[0]
          else
            lesser nodeA.range[0], nodeB.range[0]
        ,
          if justLeading
            nodeA.range[1]
          else
            greater nodeA.range[1], nodeB.range[1]
        ]
        start:
          if justEnding
            nodeA.start
          else
            lesser nodeA.start, nodeB.start
        end:
          if justLeading
            nodeA.end
          else
            greater nodeA.end, nodeB.end
  • §

    將 Jison 風格的節點類別位置資料轉換為 Babel 風格的位置資料

    exports.jisonLocationDataToAstLocationData = jisonLocationDataToAstLocationData = ({first_line, first_column, last_line_exclusive, last_column_exclusive, range}) ->
      return
        loc:
          start:
            line:   first_line + 1
            column: first_column
          end:
            line:   last_line_exclusive + 1
            column: last_column_exclusive
        range: [
          range[0]
          range[1]
        ]
        start: range[0]
        end:   range[1]
  • §

    產生一個零寬度的位置資料,對應到另一個節點位置的結尾。

    zeroWidthLocationDataFromEndLocation = ({range: [, endRange], last_line_exclusive, last_column_exclusive}) -> {
      first_line: last_line_exclusive
      first_column: last_column_exclusive
      last_line: last_line_exclusive
      last_column: last_column_exclusive
      last_line_exclusive
      last_column_exclusive
      range: [endRange, endRange]
    }
    
    extractSameLineLocationDataFirst = (numChars) -> ({range: [startRange], first_line, first_column}) -> {
      first_line
      first_column
      last_line: first_line
      last_column: first_column + numChars - 1
      last_line_exclusive: first_line
      last_column_exclusive: first_column + numChars
      range: [startRange, startRange + numChars]
    }
    
    extractSameLineLocationDataLast = (numChars) -> ({range: [, endRange], last_line, last_column, last_line_exclusive, last_column_exclusive}) -> {
      first_line: last_line
      first_column: last_column - (numChars - 1)
      last_line: last_line
      last_column: last_column
      last_line_exclusive
      last_column_exclusive
      range: [endRange - numChars, endRange]
    }
  • §

    我們目前沒有對應到內插/JSX 表達式大括號之間的空白的記號,因此透過從內插的位置資料中移除大括號來拼湊位置資料。技術上來說,如果結尾大括號前面有換行,這裡的 last_line/last_column 計算可能會不正確,但 last_line/last_column 根本不會用於 AST 產生。

    emptyExpressionLocationData = ({interpolationNode: element, openingBrace, closingBrace}) ->
      first_line:            element.locationData.first_line
      first_column:          element.locationData.first_column + openingBrace.length
      last_line:             element.locationData.last_line
      last_column:           element.locationData.last_column - closingBrace.length
      last_line_exclusive:   element.locationData.last_line
      last_column_exclusive: element.locationData.last_column
      range: [
        element.locationData.range[0] + openingBrace.length
        element.locationData.range[1] - closingBrace.length
      ]