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

    为了生成映射,我们必须跟踪语法树中每个节点的原始位置(行号、列号),并能够生成一个 映射文件 — 这是一个紧凑的、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
  • §

    缓存

  • §

    一个静态源代码映射缓存 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 个值。如果只有一个,那么它是一个生成的列,它与源代码中的任何内容都不匹配。

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

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