• 跳转到 … +
    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 代码。

    为了生成映射,我们必须跟踪语法树中每个节点的原始位置(行号、列号),并能够生成一个 映射文件 - 这是一个紧凑的、VLQ 编码的 JSON 序列化信息的表示形式 - 以及生成的 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 为真,则如果已存在指定 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 源代码映射生成

  • ¶

    构建一个 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 个值。如果只有一个,则它是一个生成的列,它与源代码中的任何内容都不匹配。

    生成的源代码中的起始列,相对于当前行中任何先前记录的列

            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 对象格式。

        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(参见 维基百科)。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