• 跳轉至… +
    browser.coffee cake.coffee coffee-script.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
  • sourcemap.litcoffee

  • ¶

    原始碼映射允許 JavaScript 執行時間將執行中的 JavaScript 與對應的原始碼相匹配。這可能是壓縮的 JavaScript,但就我們而言,我們關注的是將漂亮列印的 JavaScript 映射回 CoffeeScript。

    為了產生映射,我們必須追蹤語法樹中每個節點的原始位置(行號、欄號),並能夠產生 映射檔 — 這是此資訊的 JSON 序列化之緊湊的 VLQ 編碼表示 — 以與產生的 JavaScript 一起寫出。

    LineMap

  • ¶

    LineMap 物件追蹤單行輸出 JavaScript 程式碼的原始行和欄位位置資訊。SourceMaps 是依據 LineMaps 實作的。

    class LineMap
      constructor: (@line) ->
        @columns = []
    
      add: (column, [sourceLine, sourceColumn], options={}) ->
        return if @columns[column] and options.noReplace
        @columns[column] = {line: @line, column, sourceLine, sourceColumn}
    
      sourceLocation: (column) ->
        column-- until (mapping = @columns[column]) or (column <= 0)
        mapping and [mapping.sourceLine, mapping.sourceColumn]
  • ¶

    SourceMap

  • ¶

    將單一產生的 JavaScript 檔案中的位置映射回原始 CoffeeScript 原始檔中的位置。

    這對於原始碼映射在磁碟上如何表示是故意採取不可知的態度。一旦編譯器準備好產生「v3」樣式的原始碼映射,我們就能夠瀏覽行和欄位緩衝區的陣列來產生它。

    class SourceMap
      constructor: ->
        @lines = []
  • ¶

    將對應新增至這個 SourceMap。sourceLocation 和 generatedLocation 都是 [line, column] 陣列。如果 options.noReplace 為 true,則如果已經有指定 line 和 column 的對應,這將不會有任何作用。

      add: (sourceLocation, generatedLocation, options = {}) ->
        [line, column] = generatedLocation
        lineMap = (@lines[line] or= new LineMap(line))
        lineMap.add column, sourceLocation, options
  • ¶

    查詢已產生程式碼中給定 line 和 column 的原始位置。

      sourceLocation: ([line, column]) ->
        line-- until (lineMap = @lines[line]) or (line <= 0)
        lineMap and lineMap.sourceLocation column
  • ¶

    V3 SourceMap 產生

  • ¶

    建立 V3 source map,將產生的 JSON 回傳為字串。options.sourceRoot 可用於指定寫入至 source map 的 sourceRoot。此外,options.sourceFiles 和 options.generatedFile 可傳遞以分別設定「sources」和「file」。

      generate: (options = {}, code = null) ->
        writingline       = 0
        lastColumn        = 0
        lastSourceLine    = 0
        lastSourceColumn  = 0
        needComma         = no
        buffer            = ""
    
        for lineMap, lineNumber in @lines when lineMap
          for mapping in lineMap.columns when mapping
            while writingline < mapping.line
              lastColumn = 0
              needComma = no
              buffer += ";"
              writingline++
  • ¶

    如果我們已經在這行寫入一個區段,則寫入逗號。

            if needComma
              buffer += ","
              needComma = no
  • ¶

    寫入下一個區段。區段可以是 1、4 或 5 個值。如果只有一個,則它是一個產生的欄位,與原始程式碼中的任何內容都不相符。

    產生的原始碼中的起始欄位,相對於目前行中任何先前記錄的欄位

            buffer += @encodeVlq mapping.column - lastColumn
            lastColumn = mapping.column
  • ¶

    來源清單中的索引

            buffer += @encodeVlq 0
  • ¶

    原始來源中的起始行,相對於前一個來源行。

            buffer += @encodeVlq mapping.sourceLine - lastSourceLine
            lastSourceLine = mapping.sourceLine
  • ¶

    原始來源中的起始欄位,相對於前一個欄位。

            buffer += @encodeVlq mapping.sourceColumn - lastSourceColumn
            lastSourceColumn = mapping.sourceColumn
            needComma = yes
  • ¶

    產生「v3」source map 的標準 JSON 物件格式。

        v3 =
          version:    3
          file:       options.generatedFile or ''
          sourceRoot: options.sourceRoot or ''
          sources:    options.sourceFiles or ['']
          names:      []
          mappings:   buffer
    
        v3.sourcesContent = [code] if options.inlineMap
    
        v3
  • ¶

    Base64 VLQ 編碼

  • ¶

    請注意,SourceMap VLQ 編碼是「反向」的。MIDI 風格的 VLQ 編碼會將原始值中的最高有效位元 (MSB) 放入 VLQ 編碼值中的 MSB(請參閱 Wikipedia)。SourceMap VLQ 則相反,將原始值中最低有效位元編碼到 VLQ 編碼值的開頭位元組中。

      VLQ_SHIFT            = 5
      VLQ_CONTINUATION_BIT = 1 << VLQ_SHIFT             # 0010 0000
      VLQ_VALUE_MASK       = VLQ_CONTINUATION_BIT - 1   # 0001 1111
    
      encodeVlq: (value) ->
        answer = ''
  • ¶

    最低有效位元代表符號。

        signBit = if value < 0 then 1 else 0
  • ¶

    接下來的位元是實際值。

        valueToEncode = (Math.abs(value) << 1) + signBit
  • ¶

    即使 valueToEncode 為 0,也要確保編碼至少一個字元。

        while valueToEncode or not answer
          nextChunk = valueToEncode & VLQ_VALUE_MASK
          valueToEncode = valueToEncode >> VLQ_SHIFT
          nextChunk |= VLQ_CONTINUATION_BIT if valueToEncode
          answer += @encodeBase64 nextChunk
    
        answer
  • ¶

    一般 Base64 編碼

  • ¶
      BASE64_CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
    
      encodeBase64: (value) ->
        BASE64_CHARS[value] or throw new Error "Cannot Base64 encode value: #{value}"
  • ¶

    我們用於原始碼對應的 API 只有 SourceMap 類別。

    module.exports = SourceMap