• ジャンプ先 … +
    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
  • sourcemap.litcoffee

  • §

    ソースマップにより、JavaScriptランタイムは実行中のJavaScriptを、対応する元のソースコードにマッピングできます。これはミニファイされたJavaScriptにも適用されますが、ここでは、整形済みのJavaScriptをCoffeeScriptにマッピングすることに重点を置いています。

    マップを作成するには、構文木の各ノードの元の位置(行番号、列番号)を追跡し、マップファイル(この情報のJSONシリアライズのコンパクトなVLQエンコード表現)を生成して、生成されたJavaScriptと共に書き出す必要があります。

    LineMap

  • §

    LineMapオブジェクトは、出力されたJavaScriptコードの1行に関する元の行と列の位置に関する情報を追跡します。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はどちらも[行, 列]配列です。options.noReplaceがtrueの場合、指定された行と列のマッピングが既に存在する場合は、効果がありません。

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

    生成されたコード内の特定の行と列の元の位置を検索します。

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

    キャッシング

  • §

    静的なソースマップキャッシュfilename: map。これらはスタックトレースの変換に使用され、現在、ソースマップオプションを使用してコンパイルされたすべてのファイルに対してCoffeeScript.compileで設定されています。

      @sourceMaps: Object.create null
    
      @registerCompiled: (filename, source, sourcemap) =>
        if sourcemap?
          @sourceMaps[filename] = sourcemap
    
      @getSourceMap: (filename) =>
        @sourceMaps[filename]
  • §

    V3ソースマップの生成

  • §

    V3ソースマップを構築し、生成されたJSONを文字列として返します。options.sourceRootを使用して、ソースマップに書き込まれる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つの値を持つことができます。1つの値のみの場合は、ソースコードと一致しない生成された列です。

    生成されたソースの開始列(現在の行の以前に記録された列からの相対位置)

            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」ソースマップの標準的なJSONオブジェクト形式を生成します。

        sources = if options.sourceFiles
          options.sourceFiles
        else if options.filename
          [options.filename]
        else
          ['<anonymous>']
    
        v3 =
          version:    3
          file:       options.generatedFile or ''
          sourceRoot: options.sourceRoot or ''
          sources:    sources
          names:      []
          mappings:   buffer
    
        v3.sourcesContent = [code] if options.sourceMap or options.inlineMap
    
        v3
  • §

    Base64 VLQエンコーディング

  • §

    SourceMap VLQエンコーディングは「逆順」であることに注意してください。MIDIスタイルのVLQエンコーディングは、元の値の最上位ビット(MSB)をVLQエンコード値のMSBに配置します(Wikipediaを参照)。SourceMap VLQは、元の値の最下位4ビットを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であっても、少なくとも1文字をエンコードするようにします。

        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