mirror of
https://github.com/jashkenas/coffeescript.git
synced 2026-05-03 03:00:14 -04:00
Big refactor of SourceMap class. Literate CoffeeScript. Purdy.
This commit is contained in:
@@ -85,7 +85,7 @@
|
||||
|
||||
|
||||
<a class="source" href="sourcemap.html">
|
||||
sourcemap.coffee
|
||||
sourcemap.litcoffee
|
||||
</a>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -85,7 +85,7 @@
|
||||
|
||||
|
||||
<a class="source" href="sourcemap.html">
|
||||
sourcemap.coffee
|
||||
sourcemap.litcoffee
|
||||
</a>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -85,7 +85,7 @@
|
||||
|
||||
|
||||
<a class="source" href="sourcemap.html">
|
||||
sourcemap.coffee
|
||||
sourcemap.litcoffee
|
||||
</a>
|
||||
|
||||
</div>
|
||||
@@ -124,7 +124,7 @@ child_process = require <span class="string">'child_process'</span>
|
||||
{Lexer} = require <span class="string">'./lexer'</span>
|
||||
{parser} = require <span class="string">'./parser'</span>
|
||||
helpers = require <span class="string">'./helpers'</span>
|
||||
sourcemap = require <span class="string">'./sourcemap'</span></pre></div></div>
|
||||
SourceMap = require <span class="string">'./sourcemap'</span></pre></div></div>
|
||||
|
||||
</li>
|
||||
|
||||
@@ -171,7 +171,7 @@ sourcemap = require <span class="string">'./sourcemap'</span></pre></div></d
|
||||
|
||||
</p>
|
||||
<p>If <code>options.sourceMap</code> is specified, then <code>options.filename</code> must also be specified. All
|
||||
options that can be passed to <code>generateV3SourceMap()</code> may also be passed here.
|
||||
options that can be passed to <code>SourceMap#generate</code> may also be passed here.
|
||||
|
||||
</p>
|
||||
<p>This returns a javascript string, unless <code>options.sourceMap</code> is passed,
|
||||
@@ -186,7 +186,7 @@ lookups.
|
||||
{merge} = exports.helpers
|
||||
|
||||
<span class="keyword">if</span> options.sourceMap
|
||||
sourceMap = <span class="keyword">new</span> sourcemap.SourceMap()
|
||||
map = <span class="keyword">new</span> SourceMap
|
||||
|
||||
fragments = (parser.parse lexer.tokenize(code, options)).compileToFragments options
|
||||
|
||||
@@ -210,9 +210,9 @@ lookups.
|
||||
|
||||
</div>
|
||||
|
||||
<div class="content"><div class='highlight'><pre> <span class="keyword">if</span> sourceMap
|
||||
<div class="content"><div class='highlight'><pre> <span class="keyword">if</span> options.sourceMap
|
||||
<span class="keyword">if</span> fragment.locationData
|
||||
sourceMap.addMapping(
|
||||
map.add(
|
||||
[fragment.locationData.first_line, fragment.locationData.first_column],
|
||||
[currentLine, currentColumn],
|
||||
{noReplace: <span class="literal">true</span>})
|
||||
@@ -242,9 +242,8 @@ lookups.
|
||||
|
||||
<span class="keyword">if</span> options.sourceMap
|
||||
answer = {js}
|
||||
<span class="keyword">if</span> sourceMap
|
||||
answer.sourceMap = sourceMap
|
||||
answer.v3SourceMap = sourcemap.generateV3SourceMap(sourceMap, options, code)
|
||||
answer.sourceMap = map
|
||||
answer.v3SourceMap = map.generate(options, code)
|
||||
answer
|
||||
<span class="keyword">else</span>
|
||||
js</pre></div></div>
|
||||
@@ -710,7 +709,7 @@ exception is thrown in the module body.)
|
||||
|
||||
<span class="function"><span class="title">getSourceMapping</span></span> = (filename, line, column) ->
|
||||
sourceMap = mainModule._sourceMaps[filename]
|
||||
answer = sourceMap.getSourcePosition [line - <span class="number">1</span>, column - <span class="number">1</span>] <span class="keyword">if</span> sourceMap
|
||||
answer = sourceMap.sourceLocation [line - <span class="number">1</span>, column - <span class="number">1</span>] <span class="keyword">if</span> sourceMap
|
||||
<span class="keyword">if</span> answer <span class="keyword">then</span> [answer[<span class="number">0</span>] + <span class="number">1</span>, answer[<span class="number">1</span>] + <span class="number">1</span>] <span class="keyword">else</span> <span class="literal">null</span>
|
||||
|
||||
frames = <span class="keyword">for</span> frame <span class="keyword">in</span> stack
|
||||
|
||||
@@ -85,7 +85,7 @@
|
||||
|
||||
|
||||
<a class="source" href="sourcemap.html">
|
||||
sourcemap.coffee
|
||||
sourcemap.litcoffee
|
||||
</a>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -51,9 +51,17 @@ b, strong {
|
||||
font-family: "aller-bold";
|
||||
}
|
||||
|
||||
p, ul, ol {
|
||||
p {
|
||||
margin: 15px 0 0px;
|
||||
}
|
||||
.annotation ul, .annotation ol {
|
||||
margin: 25px 0;
|
||||
}
|
||||
.annotation ul li, .annotation ol li {
|
||||
font-size: 14px;
|
||||
line-height: 18px;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
color: #112233;
|
||||
|
||||
@@ -85,7 +85,7 @@
|
||||
|
||||
|
||||
<a class="source" href="sourcemap.html">
|
||||
sourcemap.coffee
|
||||
sourcemap.litcoffee
|
||||
</a>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -85,7 +85,7 @@
|
||||
|
||||
|
||||
<a class="source" href="sourcemap.html">
|
||||
sourcemap.coffee
|
||||
sourcemap.litcoffee
|
||||
</a>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -85,7 +85,7 @@
|
||||
|
||||
|
||||
<a class="source" href="sourcemap.html">
|
||||
sourcemap.coffee
|
||||
sourcemap.litcoffee
|
||||
</a>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -85,7 +85,7 @@
|
||||
|
||||
|
||||
<a class="source" href="sourcemap.html">
|
||||
sourcemap.coffee
|
||||
sourcemap.litcoffee
|
||||
</a>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -85,7 +85,7 @@
|
||||
|
||||
|
||||
<a class="source" href="sourcemap.html">
|
||||
sourcemap.coffee
|
||||
sourcemap.litcoffee
|
||||
</a>
|
||||
|
||||
</div>
|
||||
@@ -881,7 +881,7 @@ declarations of all inner variables pushed up to the top.
|
||||
<span class="keyword">if</span> assigns
|
||||
fragments.push <span class="property">@makeCode</span> <span class="string">",\n<span class="subst">#{@tab + TAB}</span>"</span> <span class="keyword">if</span> declars
|
||||
fragments.push <span class="property">@makeCode</span> (scope.assignedVariables().join <span class="string">",\n<span class="subst">#{@tab + TAB}</span>"</span>)
|
||||
fragments.push <span class="property">@makeCode</span> <span class="string">';\n\n'</span>
|
||||
fragments.push <span class="property">@makeCode</span> <span class="string">";\n<span class="subst">#{<span class="keyword">if</span> @spaced <span class="keyword">then</span> '\n' <span class="keyword">else</span> ''}</span>"</span>
|
||||
fragments.concat post</pre></div></div>
|
||||
|
||||
</li>
|
||||
|
||||
@@ -85,7 +85,7 @@
|
||||
|
||||
|
||||
<a class="source" href="sourcemap.html">
|
||||
sourcemap.coffee
|
||||
sourcemap.litcoffee
|
||||
</a>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -85,7 +85,7 @@
|
||||
|
||||
|
||||
<a class="source" href="sourcemap.html">
|
||||
sourcemap.coffee
|
||||
sourcemap.litcoffee
|
||||
</a>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -85,7 +85,7 @@
|
||||
|
||||
|
||||
<a class="source" href="sourcemap.html">
|
||||
sourcemap.coffee
|
||||
sourcemap.litcoffee
|
||||
</a>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -85,7 +85,7 @@
|
||||
|
||||
|
||||
<a class="source" href="sourcemap.html">
|
||||
sourcemap.coffee
|
||||
sourcemap.litcoffee
|
||||
</a>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<title>sourcemap.coffee</title>
|
||||
<title>sourcemap.litcoffee</title>
|
||||
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
|
||||
<meta name="viewport" content="width=device-width, target-densitydpi=160dpi, initial-scale=1.0; maximum-scale=1.0; user-scalable=0;">
|
||||
<link rel="stylesheet" media="all" href="docco.css" />
|
||||
@@ -85,7 +85,7 @@
|
||||
|
||||
|
||||
<a class="source" href="sourcemap.html">
|
||||
sourcemap.coffee
|
||||
sourcemap.litcoffee
|
||||
</a>
|
||||
|
||||
</div>
|
||||
@@ -96,7 +96,7 @@
|
||||
|
||||
<li id="title">
|
||||
<div class="annotation">
|
||||
<h1>sourcemap.coffee</h1>
|
||||
<h1>sourcemap.litcoffee</h1>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
@@ -105,18 +105,21 @@
|
||||
<li id="section-1">
|
||||
<div class="annotation">
|
||||
|
||||
<div class="pilwrap for-h3">
|
||||
<div class="pilwrap ">
|
||||
<a class="pilcrow" href="#section-1">¶</a>
|
||||
</div>
|
||||
<h3>LineMapping</h3>
|
||||
<p>Hold data about mappings for one line of generated source code.
|
||||
<p>Source maps allow JavaScript runtimes to match running JavaScript back to
|
||||
the original CoffeeScript source code that corresponds to it. In order to
|
||||
produce maps, we must keep track of positions (line number, column number)
|
||||
for every construct in the syntax tree, and be able to generate a map file
|
||||
-- which is a compact, VLQ-encoded representation of the JSON serialization
|
||||
of this information -- to write out alongside the generated JavaScript.
|
||||
|
||||
</p>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="content"><div class='highlight'><pre><span class="class"><span class="keyword">class</span> <span class="title">LineMapping</span></span>
|
||||
constructor: (<span class="property">@generatedLine</span>) -></pre></div></div>
|
||||
<div class="content"><div class='highlight'><pre>{merge} = require <span class="string">'./helpers'</span></pre></div></div>
|
||||
|
||||
</li>
|
||||
|
||||
@@ -124,16 +127,13 @@
|
||||
<li id="section-2">
|
||||
<div class="annotation">
|
||||
|
||||
<div class="pilwrap ">
|
||||
<div class="pilwrap for-h2">
|
||||
<a class="pilcrow" href="#section-2">¶</a>
|
||||
</div>
|
||||
<p>columnMap keeps track of which columns we've already mapped.
|
||||
</p>
|
||||
<h2>LineMap</h2>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="content"><div class='highlight'><pre> <span class="property">@columnMap</span> = {}</pre></div></div>
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
@@ -143,15 +143,24 @@
|
||||
<div class="pilwrap ">
|
||||
<a class="pilcrow" href="#section-3">¶</a>
|
||||
</div>
|
||||
<p>columnMappings is an array of all column mappings, sorted by generated-column.
|
||||
<p>Keeps track of information about column positions within a single line of
|
||||
output JavaScript code. <strong>SourceMap</strong>s are implemented in terms of <strong>LineMap</strong>s.
|
||||
|
||||
</p>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="content"><div class='highlight'><pre> <span class="property">@columnMappings</span> = []
|
||||
<div class="content"><div class='highlight'><pre><span class="class"><span class="keyword">class</span> <span class="title">LineMap</span></span>
|
||||
constructor: (<span class="property">@line</span>) ->
|
||||
<span class="property">@columns</span> = []
|
||||
|
||||
addMapping: (generatedColumn, [sourceLine, sourceColumn], options={}) ->
|
||||
<span class="keyword">if</span> <span class="property">@columnMap</span>[generatedColumn] <span class="keyword">and</span> options.noReplace</pre></div></div>
|
||||
add: (column, [sourceLine, sourceColumn], options={}) ->
|
||||
<span class="keyword">return</span> <span class="keyword">if</span> <span class="property">@columns</span>[column] <span class="keyword">and</span> options.noReplace
|
||||
<span class="property">@columns</span>[column] = {line: <span class="property">@line</span>, column, sourceLine, sourceColumn}
|
||||
|
||||
sourceLocation: (column) ->
|
||||
column-- <span class="keyword">until</span> (mapping = <span class="property">@columns</span>[column]) <span class="keyword">or</span> (column <= <span class="number">0</span>)
|
||||
mapping <span class="keyword">and</span> [mapping.sourceLine, mapping.sourceColumn]</pre></div></div>
|
||||
|
||||
</li>
|
||||
|
||||
@@ -159,60 +168,37 @@
|
||||
<li id="section-4">
|
||||
<div class="annotation">
|
||||
|
||||
<div class="pilwrap ">
|
||||
<div class="pilwrap for-h2">
|
||||
<a class="pilcrow" href="#section-4">¶</a>
|
||||
</div>
|
||||
<p>We already have a mapping for this column.
|
||||
</p>
|
||||
<h2>SourceMap</h2>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="content"><div class='highlight'><pre> <span class="keyword">return</span>
|
||||
|
||||
<span class="property">@columnMap</span>[generatedColumn] = {
|
||||
generatedLine: <span class="property">@generatedLine</span>
|
||||
generatedColumn
|
||||
sourceLine
|
||||
sourceColumn
|
||||
}
|
||||
|
||||
<span class="property">@columnMappings</span>.push <span class="property">@columnMap</span>[generatedColumn]
|
||||
<span class="property">@columnMappings</span>.sort (a,b) -> a.generatedColumn - b.generatedColumn
|
||||
|
||||
getSourcePosition: (generatedColumn) ->
|
||||
answer = <span class="literal">null</span>
|
||||
lastColumnMapping = <span class="literal">null</span>
|
||||
<span class="keyword">for</span> columnMapping <span class="keyword">in</span> <span class="property">@columnMappings</span>
|
||||
<span class="keyword">if</span> columnMapping.generatedColumn > generatedColumn
|
||||
<span class="keyword">break</span>
|
||||
<span class="keyword">else</span>
|
||||
lastColumnMapping = columnMapping
|
||||
<span class="keyword">if</span> lastColumnMapping
|
||||
answer = [lastColumnMapping.sourceLine, lastColumnMapping.sourceColumn]</pre></div></div>
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
<li id="section-5">
|
||||
<div class="annotation">
|
||||
|
||||
<div class="pilwrap for-h3">
|
||||
<div class="pilwrap ">
|
||||
<a class="pilcrow" href="#section-5">¶</a>
|
||||
</div>
|
||||
<h3>SourceMap</h3>
|
||||
<p>Maps locations in a generated source file back to locations in the original source file.
|
||||
<p>Maps locations in for a single generated JavaScript file back to locations in
|
||||
the original CoffeeScript source file.
|
||||
|
||||
</p>
|
||||
<p>This is intentionally agnostic towards how a source map might be represented on disk. A
|
||||
SourceMap can be converted to a "v3" style sourcemap with <code>#generateV3SourceMap()</code>, for example
|
||||
but the SourceMap class itself knows nothing about v3 source maps.
|
||||
<p>This is intentionally agnostic towards how a source map might be represented on
|
||||
disk. Once the compiler is ready to produce a "v3"-style source map, we can walk
|
||||
through the arrays of line and column buffer to produce it.
|
||||
|
||||
</p>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="content"><div class='highlight'><pre><span class="class"><span class="keyword">class</span> <span class="title">exports</span>.<span class="title">SourceMap</span></span>
|
||||
constructor: () -></pre></div></div>
|
||||
<div class="content"><div class='highlight'><pre><span class="class"><span class="keyword">class</span> <span class="title">SourceMap</span></span>
|
||||
constructor: ->
|
||||
<span class="property">@lines</span> = []</pre></div></div>
|
||||
|
||||
</li>
|
||||
|
||||
@@ -223,12 +209,19 @@ but the SourceMap class itself knows nothing about v3 source maps.
|
||||
<div class="pilwrap ">
|
||||
<a class="pilcrow" href="#section-6">¶</a>
|
||||
</div>
|
||||
<p><code>generatedLines</code> is an array of LineMappings, one per generated line.
|
||||
<p>Adds a mapping to this SourceMap. <code>sourceLocation</code> and <code>generatedLocation</code>
|
||||
are both <code>[line, column]</code> arrays. If <code>options.noReplace</code> is true, then if there
|
||||
is already a mapping for the specified <code>line</code> and <code>column</code>, this will have no
|
||||
effect.
|
||||
|
||||
</p>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="content"><div class='highlight'><pre> <span class="property">@generatedLines</span> = []</pre></div></div>
|
||||
<div class="content"><div class='highlight'><pre> add: (sourceLocation, generatedLocation, options = {}) ->
|
||||
[line, column] = generatedLocation
|
||||
lineMap = (<span class="property">@lines</span>[line] <span class="keyword">or</span>= <span class="keyword">new</span> LineMap(line))
|
||||
lineMap.add column, sourceLocation, options</pre></div></div>
|
||||
|
||||
</li>
|
||||
|
||||
@@ -239,26 +232,16 @@ but the SourceMap class itself knows nothing about v3 source maps.
|
||||
<div class="pilwrap ">
|
||||
<a class="pilcrow" href="#section-7">¶</a>
|
||||
</div>
|
||||
<p>Adds a mapping to this SourceMap.
|
||||
<p>Look up the original position of a given <code>line</code> and <code>column</code> in the generated
|
||||
code.
|
||||
|
||||
</p>
|
||||
<p><code>sourceLocation</code> and <code>generatedLocation</code> are both [line, column] arrays.
|
||||
|
||||
</p>
|
||||
<p>If <code>options.noReplace</code> is true, then if there is already a mapping for
|
||||
the specified <code>generatedLine</code> and <code>generatedColumn</code>, this will have no effect.
|
||||
</p>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="content"><div class='highlight'><pre> addMapping: (sourceLocation, generatedLocation, options={}) ->
|
||||
[generatedLine, generatedColumn] = generatedLocation
|
||||
|
||||
lineMapping = <span class="property">@generatedLines</span>[generatedLine]
|
||||
<span class="keyword">if</span> <span class="keyword">not</span> lineMapping
|
||||
lineMapping = <span class="property">@generatedLines</span>[generatedLine] = <span class="keyword">new</span> LineMapping(generatedLine)
|
||||
|
||||
lineMapping.addMapping generatedColumn, sourceLocation, options</pre></div></div>
|
||||
<div class="content"><div class='highlight'><pre> sourceLocation: ([line, column]) ->
|
||||
line-- <span class="keyword">until</span> (lineMap = <span class="property">@lines</span>[line]) <span class="keyword">or</span> (line <= <span class="number">0</span>)
|
||||
lineMap <span class="keyword">and</span> lineMap.sourceLocation column</pre></div></div>
|
||||
|
||||
</li>
|
||||
|
||||
@@ -269,15 +252,19 @@ the specified <code>generatedLine</code> and <code>generatedColumn</code>, this
|
||||
<div class="pilwrap ">
|
||||
<a class="pilcrow" href="#section-8">¶</a>
|
||||
</div>
|
||||
<p>Returns [sourceLine, sourceColumn], or null if no mapping could be found.
|
||||
<p><code>func</code> will be called once for every recorded mapping, in the order in
|
||||
which they occur in the generated source. <code>fn</code> will be passed an object
|
||||
with four properties: sourceLine, sourceColumn, line, and
|
||||
column.
|
||||
|
||||
</p>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="content"><div class='highlight'><pre> getSourcePosition: ([generatedLine, generatedColumn]) ->
|
||||
answer = <span class="literal">null</span>
|
||||
lineMapping = <span class="property">@generatedLines</span>[generatedLine]
|
||||
<span class="keyword">if</span> <span class="keyword">not</span> lineMapping</pre></div></div>
|
||||
<div class="content"><div class='highlight'><pre> each: (iterator) ->
|
||||
<span class="keyword">for</span> lineMap, lineNumber <span class="keyword">in</span> <span class="property">@lines</span> <span class="keyword">when</span> lineMap
|
||||
<span class="keyword">for</span> mapping <span class="keyword">in</span> lineMap.columns <span class="keyword">when</span> mapping
|
||||
iterator mapping</pre></div></div>
|
||||
|
||||
</li>
|
||||
|
||||
@@ -285,19 +272,13 @@ the specified <code>generatedLine</code> and <code>generatedColumn</code>, this
|
||||
<li id="section-9">
|
||||
<div class="annotation">
|
||||
|
||||
<div class="pilwrap ">
|
||||
<div class="pilwrap for-h2">
|
||||
<a class="pilcrow" href="#section-9">¶</a>
|
||||
</div>
|
||||
<p>TODO: Search backwards for the line?
|
||||
</p>
|
||||
<h2>V3 SourceMap Generation</h2>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="content"><div class='highlight'><pre> <span class="keyword">else</span>
|
||||
answer = lineMapping.getSourcePosition generatedColumn
|
||||
|
||||
answer</pre></div></div>
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
@@ -307,19 +288,29 @@ the specified <code>generatedLine</code> and <code>generatedColumn</code>, this
|
||||
<div class="pilwrap ">
|
||||
<a class="pilcrow" href="#section-10">¶</a>
|
||||
</div>
|
||||
<p><code>fn</code> will be called once for every recorded mapping, in the order in
|
||||
which they occur in the generated source. <code>fn</code> will be passed an object
|
||||
with four properties: sourceLine, sourceColumn, generatedLine, and
|
||||
generatedColumn.
|
||||
<p>Builds up a V3 source map, returning the generated JSON as a string.
|
||||
<code>options.sourceRoot</code> may be used to specify the sourceRoot written to the source
|
||||
map. Also, <code>options.sourceFiles</code> and <code>options.generatedFile</code> may be passed to
|
||||
set "sources" and "file", respectively.
|
||||
|
||||
</p>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="content"><div class='highlight'><pre> forEachMapping: (fn) ->
|
||||
<span class="keyword">for</span> lineMapping, generatedLineNumber <span class="keyword">in</span> <span class="property">@generatedLines</span>
|
||||
<span class="keyword">if</span> lineMapping
|
||||
<span class="keyword">for</span> columnMapping <span class="keyword">in</span> lineMapping.columnMappings
|
||||
fn(columnMapping)</pre></div></div>
|
||||
<div class="content"><div class='highlight'><pre> generate: (options = {}, code = <span class="literal">null</span>) ->
|
||||
writingline = <span class="number">0</span>
|
||||
lastColumn = <span class="number">0</span>
|
||||
lastSourceLine = <span class="number">0</span>
|
||||
lastSourceColumn = <span class="number">0</span>
|
||||
needComma = <span class="literal">no</span>
|
||||
buffer = <span class="string">""</span>
|
||||
|
||||
<span class="property">@each</span> (mapping) =>
|
||||
<span class="keyword">while</span> writingline < mapping.line
|
||||
lastColumn = <span class="number">0</span>
|
||||
needComma = <span class="literal">no</span>
|
||||
buffer += <span class="string">";"</span>
|
||||
writingline++</pre></div></div>
|
||||
|
||||
</li>
|
||||
|
||||
@@ -327,41 +318,18 @@ generatedColumn.
|
||||
<li id="section-11">
|
||||
<div class="annotation">
|
||||
|
||||
<div class="pilwrap for-h3">
|
||||
<div class="pilwrap ">
|
||||
<a class="pilcrow" href="#section-11">¶</a>
|
||||
</div>
|
||||
<h3>generateV3SourceMap</h3>
|
||||
<p>Builds a V3 source map from a SourceMap object.
|
||||
Returns the generated JSON as a string.
|
||||
|
||||
</p>
|
||||
<p><code>options.sourceRoot</code> may be used to specify the sourceRoot written to the source map. Also,
|
||||
<code>options.sourceFiles</code> and <code>options.generatedFile</code> may be passed to set "sources" and "file",
|
||||
respectively. Note that <code>sourceFiles</code> must be an array.
|
||||
<p>Write a comma if we've already written a segment on this line.
|
||||
|
||||
</p>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="content"><div class='highlight'><pre>exports.<span class="function"><span class="title">generateV3SourceMap</span></span> = (sourceMap, options={}, code) ->
|
||||
sourceRoot = options.sourceRoot <span class="keyword">or</span> <span class="string">""</span>
|
||||
sourceFiles = options.sourceFiles <span class="keyword">or</span> [<span class="string">""</span>]
|
||||
generatedFile = options.generatedFile <span class="keyword">or</span> <span class="string">""</span>
|
||||
|
||||
writingGeneratedLine = <span class="number">0</span>
|
||||
lastGeneratedColumnWritten = <span class="number">0</span>
|
||||
lastSourceLineWritten = <span class="number">0</span>
|
||||
lastSourceColumnWritten = <span class="number">0</span>
|
||||
needComma = <span class="literal">no</span>
|
||||
|
||||
mappings = <span class="string">""</span>
|
||||
|
||||
sourceMap.forEachMapping (mapping) ->
|
||||
<span class="keyword">while</span> writingGeneratedLine < mapping.generatedLine
|
||||
lastGeneratedColumnWritten = <span class="number">0</span>
|
||||
needComma = <span class="literal">no</span>
|
||||
mappings += <span class="string">";"</span>
|
||||
writingGeneratedLine++</pre></div></div>
|
||||
<div class="content"><div class='highlight'><pre> <span class="keyword">if</span> needComma
|
||||
buffer += <span class="string">","</span>
|
||||
needComma = <span class="literal">no</span></pre></div></div>
|
||||
|
||||
</li>
|
||||
|
||||
@@ -372,14 +340,19 @@ respectively. Note that <code>sourceFiles</code> must be an array.
|
||||
<div class="pilwrap ">
|
||||
<a class="pilcrow" href="#section-12">¶</a>
|
||||
</div>
|
||||
<p>Write a comma if we've already written a segment on this line.
|
||||
<p>Write the next segment. Segments can be 1, 4, or 5 values. If just one, then it
|
||||
is a generated column which doesn't match anything in the source code.
|
||||
|
||||
</p>
|
||||
<p>The starting column in the generated source, relative to any previous recorded
|
||||
column for the current line:
|
||||
|
||||
</p>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="content"><div class='highlight'><pre> <span class="keyword">if</span> needComma
|
||||
mappings += <span class="string">","</span>
|
||||
needComma = <span class="literal">no</span></pre></div></div>
|
||||
<div class="content"><div class='highlight'><pre> buffer += <span class="property">@encodeVlq</span> mapping.column - lastColumn
|
||||
lastColumn = mapping.column</pre></div></div>
|
||||
|
||||
</li>
|
||||
|
||||
@@ -390,26 +363,13 @@ respectively. Note that <code>sourceFiles</code> must be an array.
|
||||
<div class="pilwrap ">
|
||||
<a class="pilcrow" href="#section-13">¶</a>
|
||||
</div>
|
||||
<p>Write the next segment.
|
||||
Segments can be 1, 4, or 5 values. If just one, then it is a generated column which
|
||||
doesn't match anything in the source code.
|
||||
<p>The index into the list of sources:
|
||||
|
||||
</p>
|
||||
<p>Fields are all zero-based, and relative to the previous occurence unless otherwise noted:
|
||||
<em> starting-column in generated source, relative to previous occurence for the current line.
|
||||
</em> index into the "sources" list
|
||||
<em> starting line in the original source
|
||||
</em> starting column in the original source
|
||||
* index into the "names" list associated with this segment.
|
||||
|
||||
</p>
|
||||
<p>Add the generated start-column
|
||||
</p>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="content"><div class='highlight'><pre> mappings += exports.vlqEncodeValue(mapping.generatedColumn - lastGeneratedColumnWritten)
|
||||
lastGeneratedColumnWritten = mapping.generatedColumn</pre></div></div>
|
||||
<div class="content"><div class='highlight'><pre> buffer += <span class="property">@encodeVlq</span> <span class="number">0</span></pre></div></div>
|
||||
|
||||
</li>
|
||||
|
||||
@@ -420,12 +380,16 @@ doesn't match anything in the source code.
|
||||
<div class="pilwrap ">
|
||||
<a class="pilcrow" href="#section-14">¶</a>
|
||||
</div>
|
||||
<p>Add the index into the sources list
|
||||
<p>The starting line in the original source, relative to the previous source line.
|
||||
|
||||
</p>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="content"><div class='highlight'><pre> mappings += exports.vlqEncodeValue(<span class="number">0</span>)</pre></div></div>
|
||||
<div class="content"><div class='highlight'><pre> buffer += <span class="property">@encodeVlq</span> mapping.sourceLine - lastSourceLine
|
||||
<span class="keyword">if</span> lastSourceLine <span class="keyword">isnt</span> mapping.sourceLine
|
||||
lastSourceLine = mapping.sourceLine
|
||||
lastSourceColumn = <span class="number">0</span></pre></div></div>
|
||||
|
||||
</li>
|
||||
|
||||
@@ -436,13 +400,15 @@ doesn't match anything in the source code.
|
||||
<div class="pilwrap ">
|
||||
<a class="pilcrow" href="#section-15">¶</a>
|
||||
</div>
|
||||
<p>Add the source start-line
|
||||
<p>The starting column in the original source, relative to the previous column.
|
||||
|
||||
</p>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="content"><div class='highlight'><pre> mappings += exports.vlqEncodeValue(mapping.sourceLine - lastSourceLineWritten)
|
||||
lastSourceLineWritten = mapping.sourceLine</pre></div></div>
|
||||
<div class="content"><div class='highlight'><pre> buffer += <span class="property">@encodeVlq</span> mapping.sourceColumn - lastSourceColumn
|
||||
lastSourceColumn = mapping.sourceColumn
|
||||
needComma = <span class="literal">yes</span></pre></div></div>
|
||||
|
||||
</li>
|
||||
|
||||
@@ -453,13 +419,23 @@ doesn't match anything in the source code.
|
||||
<div class="pilwrap ">
|
||||
<a class="pilcrow" href="#section-16">¶</a>
|
||||
</div>
|
||||
<p>Add the source start-column
|
||||
<p>Produce the canonical JSON object format for a "v3" source map.
|
||||
|
||||
</p>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="content"><div class='highlight'><pre> mappings += exports.vlqEncodeValue(mapping.sourceColumn - lastSourceColumnWritten)
|
||||
lastSourceColumnWritten = mapping.sourceColumn</pre></div></div>
|
||||
<div class="content"><div class='highlight'><pre> v3 =
|
||||
version: <span class="number">3</span>
|
||||
file: options.generatedFile <span class="keyword">or</span> <span class="string">''</span>
|
||||
sourceRoot: options.sourceRoot <span class="keyword">or</span> <span class="string">''</span>
|
||||
sources: options.sourceFiles <span class="keyword">or</span> [<span class="string">''</span>]
|
||||
names: []
|
||||
mappings: buffer
|
||||
|
||||
v3.sourcesContent = [code] <span class="keyword">if</span> options.inline
|
||||
|
||||
<span class="keyword">return</span> JSON.stringify v3, <span class="literal">null</span>, <span class="number">2</span></pre></div></div>
|
||||
|
||||
</li>
|
||||
|
||||
@@ -467,29 +443,13 @@ doesn't match anything in the source code.
|
||||
<li id="section-17">
|
||||
<div class="annotation">
|
||||
|
||||
<div class="pilwrap ">
|
||||
<div class="pilwrap for-h2">
|
||||
<a class="pilcrow" href="#section-17">¶</a>
|
||||
</div>
|
||||
<p>TODO: Do we care about symbol names for CoffeeScript? Probably not.
|
||||
|
||||
</p>
|
||||
<h2>Base64 VLQ Encoding</h2>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="content"><div class='highlight'><pre> needComma = <span class="literal">yes</span>
|
||||
|
||||
answer = {
|
||||
version: <span class="number">3</span>
|
||||
file: generatedFile
|
||||
sourceRoot
|
||||
sources: sourceFiles
|
||||
names: []
|
||||
mappings
|
||||
}
|
||||
answer.sourcesContent = [code] <span class="keyword">if</span> options.inline
|
||||
|
||||
<span class="keyword">return</span> JSON.stringify answer, <span class="literal">null</span>, <span class="number">2</span></pre></div></div>
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
@@ -499,13 +459,22 @@ doesn't match anything in the source code.
|
||||
<div class="pilwrap ">
|
||||
<a class="pilcrow" href="#section-18">¶</a>
|
||||
</div>
|
||||
<p>Load a SourceMap from a JSON string. Returns the SourceMap object.
|
||||
<p>Note that SourceMap VLQ encoding is "backwards". MIDI-style VLQ encoding puts
|
||||
the most-significant-bit (MSB) from the original value into the MSB of the VLQ
|
||||
encoded value (see <a href="http://en.wikipedia.org/wiki/File:Uintvar_coding.svg">Wikipedia</a>).
|
||||
SourceMap VLQ does things the other way around, with the least significat four
|
||||
bits of the original value encoded into the first byte of the VLQ encoded value.
|
||||
|
||||
</p>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="content"><div class='highlight'><pre>exports.<span class="function"><span class="title">loadV3SourceMap</span></span> = (sourceMap) ->
|
||||
todo()</pre></div></div>
|
||||
<div class="content"><div class='highlight'><pre> VLQ_SHIFT = <span class="number">5</span>
|
||||
VLQ_CONTINUATION_BIT = <span class="number">1</span> << VLQ_SHIFT <span class="comment"># 0010 0000</span>
|
||||
VLQ_VALUE_MASK = VLQ_CONTINUATION_BIT - <span class="number">1</span> <span class="comment"># 0001 1111</span>
|
||||
|
||||
encodeVlq: (value) ->
|
||||
answer = <span class="string">''</span></pre></div></div>
|
||||
|
||||
</li>
|
||||
|
||||
@@ -513,28 +482,15 @@ doesn't match anything in the source code.
|
||||
<li id="section-19">
|
||||
<div class="annotation">
|
||||
|
||||
<div class="pilwrap for-h3">
|
||||
<div class="pilwrap ">
|
||||
<a class="pilcrow" href="#section-19">¶</a>
|
||||
</div>
|
||||
<h3>Base64 encoding helpers</h3>
|
||||
<p>Least significant bit represents the sign.
|
||||
</p>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="content"><div class='highlight'><pre>BASE64_CHARS = <span class="string">'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'</span>
|
||||
MAX_BASE64_VALUE = BASE64_CHARS.length - <span class="number">1</span>
|
||||
|
||||
<span class="function"><span class="title">encodeBase64Char</span></span> = (value) ->
|
||||
<span class="keyword">if</span> value > MAX_BASE64_VALUE
|
||||
<span class="keyword">throw</span> <span class="keyword">new</span> Error <span class="string">"Cannot encode value <span class="subst">#{value}</span> > <span class="subst">#{MAX_BASE64_VALUE}</span>"</span>
|
||||
<span class="keyword">else</span> <span class="keyword">if</span> value < <span class="number">0</span>
|
||||
<span class="keyword">throw</span> <span class="keyword">new</span> Error <span class="string">"Cannot encode value <span class="subst">#{value}</span> < 0"</span>
|
||||
BASE64_CHARS[value]
|
||||
|
||||
<span class="function"><span class="title">decodeBase64Char</span></span> = (char) ->
|
||||
value = BASE64_CHARS.indexOf char
|
||||
<span class="keyword">if</span> value == -<span class="number">1</span>
|
||||
<span class="keyword">throw</span> <span class="keyword">new</span> Error <span class="string">"Invalid Base 64 character: <span class="subst">#{char}</span>"</span>
|
||||
value</pre></div></div>
|
||||
<div class="content"><div class='highlight'><pre> signBit = <span class="keyword">if</span> value < <span class="number">0</span> <span class="keyword">then</span> <span class="number">1</span> <span class="keyword">else</span> <span class="number">0</span></pre></div></div>
|
||||
|
||||
</li>
|
||||
|
||||
@@ -542,23 +498,15 @@ MAX_BASE64_VALUE = BASE64_CHARS.length - <span class="number">1</span>
|
||||
<li id="section-20">
|
||||
<div class="annotation">
|
||||
|
||||
<div class="pilwrap for-h3">
|
||||
<div class="pilwrap ">
|
||||
<a class="pilcrow" href="#section-20">¶</a>
|
||||
</div>
|
||||
<h3>Base 64 VLQ encoding/decoding helpers</h3>
|
||||
<p>Note that SourceMap VLQ encoding is "backwards". MIDI style VLQ encoding puts the
|
||||
most-significant-bit (MSB) from the original value into the MSB of the VLQ encoded value
|
||||
(see <a href="http://en.wikipedia.org/wiki/File:Uintvar_coding.svg">http://en.wikipedia.org/wiki/File:Uintvar_coding.svg</a>). SourceMap VLQ does things
|
||||
the other way around, with the least significat four bits of the original value encoded
|
||||
into the first byte of the VLQ encoded value.
|
||||
|
||||
<p>The next bits are the actual value.
|
||||
</p>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="content"><div class='highlight'><pre>VLQ_SHIFT = <span class="number">5</span>
|
||||
VLQ_CONTINUATION_BIT = <span class="number">1</span> << VLQ_SHIFT <span class="comment"># 0010 0000</span>
|
||||
VLQ_VALUE_MASK = VLQ_CONTINUATION_BIT - <span class="number">1</span> <span class="comment"># 0001 1111</span></pre></div></div>
|
||||
<div class="content"><div class='highlight'><pre> valueToEncode = (Math.abs(value) << <span class="number">1</span>) + signBit</pre></div></div>
|
||||
|
||||
</li>
|
||||
|
||||
@@ -569,12 +517,18 @@ VLQ_VALUE_MASK = VLQ_CONTINUATION_BIT - <span class="number">1</span> <spa
|
||||
<div class="pilwrap ">
|
||||
<a class="pilcrow" href="#section-21">¶</a>
|
||||
</div>
|
||||
<p>Encode a value as Base 64 VLQ.
|
||||
<p>Make sure we encode at least one character, even if valueToEncode is 0.
|
||||
</p>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="content"><div class='highlight'><pre>exports.<span class="function"><span class="title">vlqEncodeValue</span></span> = (value) -></pre></div></div>
|
||||
<div class="content"><div class='highlight'><pre> <span class="keyword">while</span> valueToEncode <span class="keyword">or</span> <span class="keyword">not</span> answer
|
||||
nextChunk = valueToEncode & VLQ_VALUE_MASK
|
||||
valueToEncode = valueToEncode >> VLQ_SHIFT
|
||||
nextChunk |= VLQ_CONTINUATION_BIT <span class="keyword">if</span> valueToEncode
|
||||
answer += <span class="property">@encodeBase64</span> nextChunk
|
||||
|
||||
<span class="keyword">return</span> answer</pre></div></div>
|
||||
|
||||
</li>
|
||||
|
||||
@@ -582,16 +536,13 @@ VLQ_VALUE_MASK = VLQ_CONTINUATION_BIT - <span class="number">1</span> <spa
|
||||
<li id="section-22">
|
||||
<div class="annotation">
|
||||
|
||||
<div class="pilwrap ">
|
||||
<div class="pilwrap for-h2">
|
||||
<a class="pilcrow" href="#section-22">¶</a>
|
||||
</div>
|
||||
<p>Least significant bit represents the sign.
|
||||
</p>
|
||||
<h2>Regular Base64 Encoding</h2>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="content"><div class='highlight'><pre> signBit = <span class="keyword">if</span> value < <span class="number">0</span> <span class="keyword">then</span> <span class="number">1</span> <span class="keyword">else</span> <span class="number">0</span></pre></div></div>
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
@@ -601,14 +552,13 @@ VLQ_VALUE_MASK = VLQ_CONTINUATION_BIT - <span class="number">1</span> <spa
|
||||
<div class="pilwrap ">
|
||||
<a class="pilcrow" href="#section-23">¶</a>
|
||||
</div>
|
||||
<p>Next bits are the actual value
|
||||
</p>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<div class="content"><div class='highlight'><pre> valueToEncode = (Math.abs(value) << <span class="number">1</span>) + signBit
|
||||
<div class="content"><div class='highlight'><pre> BASE64_CHARS = <span class="string">'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'</span>
|
||||
|
||||
answer = <span class="string">""</span></pre></div></div>
|
||||
encodeBase64: (value) ->
|
||||
BASE64_CHARS[value] <span class="keyword">or</span> <span class="keyword">throw</span> <span class="keyword">new</span> Error <span class="string">"Cannot Base64 encode value: <span class="subst">#{value}</span>"</span></pre></div></div>
|
||||
|
||||
</li>
|
||||
|
||||
@@ -619,111 +569,13 @@ VLQ_VALUE_MASK = VLQ_CONTINUATION_BIT - <span class="number">1</span> <spa
|
||||
<div class="pilwrap ">
|
||||
<a class="pilcrow" href="#section-24">¶</a>
|
||||
</div>
|
||||
<p>Make sure we encode at least one character, even if valueToEncode is 0.
|
||||
<p>Our API for source maps is just the <code>SourceMap</code> class.
|
||||
|
||||
</p>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="content"><div class='highlight'><pre> <span class="keyword">while</span> valueToEncode || !answer
|
||||
nextVlqChunk = valueToEncode & VLQ_VALUE_MASK
|
||||
valueToEncode = valueToEncode >> VLQ_SHIFT
|
||||
|
||||
<span class="keyword">if</span> valueToEncode
|
||||
nextVlqChunk |= VLQ_CONTINUATION_BIT
|
||||
|
||||
answer += encodeBase64Char(nextVlqChunk)
|
||||
|
||||
<span class="keyword">return</span> answer</pre></div></div>
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
<li id="section-25">
|
||||
<div class="annotation">
|
||||
|
||||
<div class="pilwrap ">
|
||||
<a class="pilcrow" href="#section-25">¶</a>
|
||||
</div>
|
||||
<p>Decode a Base 64 VLQ value.
|
||||
|
||||
</p>
|
||||
<p>Returns <code>[value, consumed]</code> where <code>value</code> is the decoded value, and <code>consumed</code> is the number
|
||||
of characters consumed from <code>str</code>.
|
||||
</p>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="content"><div class='highlight'><pre>exports.<span class="function"><span class="title">vlqDecodeValue</span></span> = (str, offset=<span class="number">0</span>) ->
|
||||
position = offset
|
||||
done = <span class="literal">false</span>
|
||||
|
||||
value = <span class="number">0</span>
|
||||
continuationShift = <span class="number">0</span>
|
||||
|
||||
<span class="keyword">while</span> !done
|
||||
nextVlqChunk = decodeBase64Char(str[position])
|
||||
position += <span class="number">1</span>
|
||||
|
||||
nextChunkValue = nextVlqChunk & VLQ_VALUE_MASK
|
||||
value += (nextChunkValue << continuationShift)
|
||||
|
||||
<span class="keyword">if</span> !(nextVlqChunk & VLQ_CONTINUATION_BIT)</pre></div></div>
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
<li id="section-26">
|
||||
<div class="annotation">
|
||||
|
||||
<div class="pilwrap ">
|
||||
<a class="pilcrow" href="#section-26">¶</a>
|
||||
</div>
|
||||
<p>We'll be done after this character.
|
||||
</p>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="content"><div class='highlight'><pre> done = <span class="literal">true</span></pre></div></div>
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
<li id="section-27">
|
||||
<div class="annotation">
|
||||
|
||||
<div class="pilwrap ">
|
||||
<a class="pilcrow" href="#section-27">¶</a>
|
||||
</div>
|
||||
<p>Bits are encoded least-significant first (opposite of MIDI VLQ). Increase the
|
||||
continuationShift, so the next byte will end up where it should in the value.
|
||||
</p>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="content"><div class='highlight'><pre> continuationShift += VLQ_SHIFT
|
||||
|
||||
consumed = position - offset</pre></div></div>
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
<li id="section-28">
|
||||
<div class="annotation">
|
||||
|
||||
<div class="pilwrap ">
|
||||
<a class="pilcrow" href="#section-28">¶</a>
|
||||
</div>
|
||||
<p>Least significant bit represents the sign.
|
||||
</p>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="content"><div class='highlight'><pre> signBit = value & <span class="number">1</span>
|
||||
value = value >> <span class="number">1</span>
|
||||
|
||||
<span class="keyword">if</span> signBit <span class="keyword">then</span> value = -value
|
||||
|
||||
<span class="keyword">return</span> [value, consumed]</pre></div></div>
|
||||
<div class="content"><div class='highlight'><pre>module.exports = SourceMap</pre></div></div>
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Generated by CoffeeScript 1.6.2
|
||||
(function() {
|
||||
var Lexer, child_process, compile, ext, fork, formatSourcePosition, fs, helpers, lexer, loadFile, parser, patchStackTrace, patched, path, sourcemap, vm, _i, _len, _ref,
|
||||
var Lexer, SourceMap, child_process, compile, ext, fork, formatSourcePosition, fs, helpers, lexer, loadFile, parser, patchStackTrace, patched, path, vm, _i, _len, _ref,
|
||||
__hasProp = {}.hasOwnProperty;
|
||||
|
||||
fs = require('fs');
|
||||
@@ -17,20 +17,20 @@
|
||||
|
||||
helpers = require('./helpers');
|
||||
|
||||
sourcemap = require('./sourcemap');
|
||||
SourceMap = require('./sourcemap');
|
||||
|
||||
exports.VERSION = '1.6.2';
|
||||
|
||||
exports.helpers = helpers;
|
||||
|
||||
exports.compile = compile = function(code, options) {
|
||||
var answer, currentColumn, currentLine, fragment, fragments, header, js, merge, newLines, sourceMap, _i, _len;
|
||||
var answer, currentColumn, currentLine, fragment, fragments, header, js, map, merge, newLines, _i, _len;
|
||||
if (options == null) {
|
||||
options = {};
|
||||
}
|
||||
merge = exports.helpers.merge;
|
||||
if (options.sourceMap) {
|
||||
sourceMap = new sourcemap.SourceMap();
|
||||
map = new SourceMap;
|
||||
}
|
||||
fragments = (parser.parse(lexer.tokenize(code, options))).compileToFragments(options);
|
||||
currentLine = 0;
|
||||
@@ -41,9 +41,9 @@
|
||||
js = "";
|
||||
for (_i = 0, _len = fragments.length; _i < _len; _i++) {
|
||||
fragment = fragments[_i];
|
||||
if (sourceMap) {
|
||||
if (options.sourceMap) {
|
||||
if (fragment.locationData) {
|
||||
sourceMap.addMapping([fragment.locationData.first_line, fragment.locationData.first_column], [currentLine, currentColumn], {
|
||||
map.add([fragment.locationData.first_line, fragment.locationData.first_column], [currentLine, currentColumn], {
|
||||
noReplace: true
|
||||
});
|
||||
}
|
||||
@@ -61,10 +61,8 @@
|
||||
answer = {
|
||||
js: js
|
||||
};
|
||||
if (sourceMap) {
|
||||
answer.sourceMap = sourceMap;
|
||||
answer.v3SourceMap = sourcemap.generateV3SourceMap(sourceMap, options, code);
|
||||
}
|
||||
answer.sourceMap = map;
|
||||
answer.v3SourceMap = map.generate(options, code);
|
||||
return answer;
|
||||
} else {
|
||||
return js;
|
||||
@@ -255,7 +253,7 @@
|
||||
var answer, sourceMap;
|
||||
sourceMap = mainModule._sourceMaps[filename];
|
||||
if (sourceMap) {
|
||||
answer = sourceMap.getSourcePosition([line - 1, column - 1]);
|
||||
answer = sourceMap.sourceLocation([line - 1, column - 1]);
|
||||
}
|
||||
if (answer) {
|
||||
return [answer[0] + 1, answer[1] + 1];
|
||||
|
||||
@@ -1,238 +1,180 @@
|
||||
// Generated by CoffeeScript 1.6.2
|
||||
(function() {
|
||||
var BASE64_CHARS, LineMapping, MAX_BASE64_VALUE, VLQ_CONTINUATION_BIT, VLQ_SHIFT, VLQ_VALUE_MASK, decodeBase64Char, encodeBase64Char;
|
||||
var LineMap, SourceMap, merge;
|
||||
|
||||
LineMapping = (function() {
|
||||
function LineMapping(generatedLine) {
|
||||
this.generatedLine = generatedLine;
|
||||
this.columnMap = {};
|
||||
this.columnMappings = [];
|
||||
merge = require('./helpers').merge;
|
||||
|
||||
LineMap = (function() {
|
||||
function LineMap(line) {
|
||||
this.line = line;
|
||||
this.columns = [];
|
||||
}
|
||||
|
||||
LineMapping.prototype.addMapping = function(generatedColumn, _arg, options) {
|
||||
LineMap.prototype.add = function(column, _arg, options) {
|
||||
var sourceColumn, sourceLine;
|
||||
sourceLine = _arg[0], sourceColumn = _arg[1];
|
||||
if (options == null) {
|
||||
options = {};
|
||||
}
|
||||
if (this.columnMap[generatedColumn] && options.noReplace) {
|
||||
if (this.columns[column] && options.noReplace) {
|
||||
return;
|
||||
}
|
||||
this.columnMap[generatedColumn] = {
|
||||
generatedLine: this.generatedLine,
|
||||
generatedColumn: generatedColumn,
|
||||
return this.columns[column] = {
|
||||
line: this.line,
|
||||
column: column,
|
||||
sourceLine: sourceLine,
|
||||
sourceColumn: sourceColumn
|
||||
};
|
||||
this.columnMappings.push(this.columnMap[generatedColumn]);
|
||||
return this.columnMappings.sort(function(a, b) {
|
||||
return a.generatedColumn - b.generatedColumn;
|
||||
});
|
||||
};
|
||||
|
||||
LineMapping.prototype.getSourcePosition = function(generatedColumn) {
|
||||
var answer, columnMapping, lastColumnMapping, _i, _len, _ref;
|
||||
answer = null;
|
||||
lastColumnMapping = null;
|
||||
_ref = this.columnMappings;
|
||||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||||
columnMapping = _ref[_i];
|
||||
if (columnMapping.generatedColumn > generatedColumn) {
|
||||
break;
|
||||
} else {
|
||||
lastColumnMapping = columnMapping;
|
||||
}
|
||||
}
|
||||
if (lastColumnMapping) {
|
||||
return answer = [lastColumnMapping.sourceLine, lastColumnMapping.sourceColumn];
|
||||
LineMap.prototype.sourceLocation = function(column) {
|
||||
var mapping;
|
||||
while (!((mapping = this.columns[column]) || (column <= 0))) {
|
||||
column--;
|
||||
}
|
||||
return mapping && [mapping.sourceLine, mapping.sourceColumn];
|
||||
};
|
||||
|
||||
return LineMapping;
|
||||
return LineMap;
|
||||
|
||||
})();
|
||||
|
||||
exports.SourceMap = (function() {
|
||||
SourceMap = (function() {
|
||||
var BASE64_CHARS, VLQ_CONTINUATION_BIT, VLQ_SHIFT, VLQ_VALUE_MASK;
|
||||
|
||||
function SourceMap() {
|
||||
this.generatedLines = [];
|
||||
this.lines = [];
|
||||
}
|
||||
|
||||
SourceMap.prototype.addMapping = function(sourceLocation, generatedLocation, options) {
|
||||
var generatedColumn, generatedLine, lineMapping;
|
||||
SourceMap.prototype.add = function(sourceLocation, generatedLocation, options) {
|
||||
var column, line, lineMap, _base;
|
||||
if (options == null) {
|
||||
options = {};
|
||||
}
|
||||
generatedLine = generatedLocation[0], generatedColumn = generatedLocation[1];
|
||||
lineMapping = this.generatedLines[generatedLine];
|
||||
if (!lineMapping) {
|
||||
lineMapping = this.generatedLines[generatedLine] = new LineMapping(generatedLine);
|
||||
}
|
||||
return lineMapping.addMapping(generatedColumn, sourceLocation, options);
|
||||
line = generatedLocation[0], column = generatedLocation[1];
|
||||
lineMap = ((_base = this.lines)[line] || (_base[line] = new LineMap(line)));
|
||||
return lineMap.add(column, sourceLocation, options);
|
||||
};
|
||||
|
||||
SourceMap.prototype.getSourcePosition = function(_arg) {
|
||||
var answer, generatedColumn, generatedLine, lineMapping;
|
||||
generatedLine = _arg[0], generatedColumn = _arg[1];
|
||||
answer = null;
|
||||
lineMapping = this.generatedLines[generatedLine];
|
||||
if (!lineMapping) {
|
||||
SourceMap.prototype.sourceLocation = function(_arg) {
|
||||
var column, line, lineMap;
|
||||
line = _arg[0], column = _arg[1];
|
||||
while (!((lineMap = this.lines[line]) || (line <= 0))) {
|
||||
line--;
|
||||
}
|
||||
return lineMap && lineMap.sourceLocation(column);
|
||||
};
|
||||
|
||||
} else {
|
||||
answer = lineMapping.getSourcePosition(generatedColumn);
|
||||
SourceMap.prototype.each = function(iterator) {
|
||||
var lineMap, lineNumber, mapping, _i, _len, _ref, _results;
|
||||
_ref = this.lines;
|
||||
_results = [];
|
||||
for (lineNumber = _i = 0, _len = _ref.length; _i < _len; lineNumber = ++_i) {
|
||||
lineMap = _ref[lineNumber];
|
||||
if (lineMap) {
|
||||
_results.push((function() {
|
||||
var _j, _len1, _ref1, _results1;
|
||||
_ref1 = lineMap.columns;
|
||||
_results1 = [];
|
||||
for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {
|
||||
mapping = _ref1[_j];
|
||||
if (mapping) {
|
||||
_results1.push(iterator(mapping));
|
||||
}
|
||||
}
|
||||
return _results1;
|
||||
})());
|
||||
}
|
||||
}
|
||||
return _results;
|
||||
};
|
||||
|
||||
SourceMap.prototype.generate = function(options, code) {
|
||||
var buffer, lastColumn, lastSourceColumn, lastSourceLine, needComma, v3, writingline,
|
||||
_this = this;
|
||||
if (options == null) {
|
||||
options = {};
|
||||
}
|
||||
if (code == null) {
|
||||
code = null;
|
||||
}
|
||||
writingline = 0;
|
||||
lastColumn = 0;
|
||||
lastSourceLine = 0;
|
||||
lastSourceColumn = 0;
|
||||
needComma = false;
|
||||
buffer = "";
|
||||
this.each(function(mapping) {
|
||||
while (writingline < mapping.line) {
|
||||
lastColumn = 0;
|
||||
needComma = false;
|
||||
buffer += ";";
|
||||
writingline++;
|
||||
}
|
||||
if (needComma) {
|
||||
buffer += ",";
|
||||
needComma = false;
|
||||
}
|
||||
buffer += _this.encodeVlq(mapping.column - lastColumn);
|
||||
lastColumn = mapping.column;
|
||||
buffer += _this.encodeVlq(0);
|
||||
buffer += _this.encodeVlq(mapping.sourceLine - lastSourceLine);
|
||||
if (lastSourceLine !== mapping.sourceLine) {
|
||||
lastSourceLine = mapping.sourceLine;
|
||||
lastSourceColumn = 0;
|
||||
}
|
||||
buffer += _this.encodeVlq(mapping.sourceColumn - lastSourceColumn);
|
||||
lastSourceColumn = mapping.sourceColumn;
|
||||
return needComma = true;
|
||||
});
|
||||
v3 = {
|
||||
version: 3,
|
||||
file: options.generatedFile || '',
|
||||
sourceRoot: options.sourceRoot || '',
|
||||
sources: options.sourceFiles || [''],
|
||||
names: [],
|
||||
mappings: buffer
|
||||
};
|
||||
if (options.inline) {
|
||||
v3.sourcesContent = [code];
|
||||
}
|
||||
return JSON.stringify(v3, null, 2);
|
||||
};
|
||||
|
||||
VLQ_SHIFT = 5;
|
||||
|
||||
VLQ_CONTINUATION_BIT = 1 << VLQ_SHIFT;
|
||||
|
||||
VLQ_VALUE_MASK = VLQ_CONTINUATION_BIT - 1;
|
||||
|
||||
SourceMap.prototype.encodeVlq = function(value) {
|
||||
var answer, nextChunk, signBit, valueToEncode;
|
||||
answer = '';
|
||||
signBit = value < 0 ? 1 : 0;
|
||||
valueToEncode = (Math.abs(value) << 1) + signBit;
|
||||
while (valueToEncode || !answer) {
|
||||
nextChunk = valueToEncode & VLQ_VALUE_MASK;
|
||||
valueToEncode = valueToEncode >> VLQ_SHIFT;
|
||||
if (valueToEncode) {
|
||||
nextChunk |= VLQ_CONTINUATION_BIT;
|
||||
}
|
||||
answer += this.encodeBase64(nextChunk);
|
||||
}
|
||||
return answer;
|
||||
};
|
||||
|
||||
SourceMap.prototype.forEachMapping = function(fn) {
|
||||
var columnMapping, generatedLineNumber, lineMapping, _i, _len, _ref, _results;
|
||||
_ref = this.generatedLines;
|
||||
_results = [];
|
||||
for (generatedLineNumber = _i = 0, _len = _ref.length; _i < _len; generatedLineNumber = ++_i) {
|
||||
lineMapping = _ref[generatedLineNumber];
|
||||
if (lineMapping) {
|
||||
_results.push((function() {
|
||||
var _j, _len1, _ref1, _results1;
|
||||
_ref1 = lineMapping.columnMappings;
|
||||
_results1 = [];
|
||||
for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {
|
||||
columnMapping = _ref1[_j];
|
||||
_results1.push(fn(columnMapping));
|
||||
}
|
||||
return _results1;
|
||||
})());
|
||||
} else {
|
||||
_results.push(void 0);
|
||||
}
|
||||
}
|
||||
return _results;
|
||||
BASE64_CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
|
||||
|
||||
SourceMap.prototype.encodeBase64 = function(value) {
|
||||
return BASE64_CHARS[value] || (function() {
|
||||
throw new Error("Cannot Base64 encode value: " + value);
|
||||
})();
|
||||
};
|
||||
|
||||
return SourceMap;
|
||||
|
||||
})();
|
||||
|
||||
exports.generateV3SourceMap = function(sourceMap, options, code) {
|
||||
var answer, generatedFile, lastGeneratedColumnWritten, lastSourceColumnWritten, lastSourceLineWritten, mappings, needComma, sourceFiles, sourceRoot, writingGeneratedLine;
|
||||
if (options == null) {
|
||||
options = {};
|
||||
}
|
||||
sourceRoot = options.sourceRoot || "";
|
||||
sourceFiles = options.sourceFiles || [""];
|
||||
generatedFile = options.generatedFile || "";
|
||||
writingGeneratedLine = 0;
|
||||
lastGeneratedColumnWritten = 0;
|
||||
lastSourceLineWritten = 0;
|
||||
lastSourceColumnWritten = 0;
|
||||
needComma = false;
|
||||
mappings = "";
|
||||
sourceMap.forEachMapping(function(mapping) {
|
||||
while (writingGeneratedLine < mapping.generatedLine) {
|
||||
lastGeneratedColumnWritten = 0;
|
||||
needComma = false;
|
||||
mappings += ";";
|
||||
writingGeneratedLine++;
|
||||
}
|
||||
if (needComma) {
|
||||
mappings += ",";
|
||||
needComma = false;
|
||||
}
|
||||
mappings += exports.vlqEncodeValue(mapping.generatedColumn - lastGeneratedColumnWritten);
|
||||
lastGeneratedColumnWritten = mapping.generatedColumn;
|
||||
mappings += exports.vlqEncodeValue(0);
|
||||
mappings += exports.vlqEncodeValue(mapping.sourceLine - lastSourceLineWritten);
|
||||
lastSourceLineWritten = mapping.sourceLine;
|
||||
mappings += exports.vlqEncodeValue(mapping.sourceColumn - lastSourceColumnWritten);
|
||||
lastSourceColumnWritten = mapping.sourceColumn;
|
||||
return needComma = true;
|
||||
});
|
||||
answer = {
|
||||
version: 3,
|
||||
file: generatedFile,
|
||||
sourceRoot: sourceRoot,
|
||||
sources: sourceFiles,
|
||||
names: [],
|
||||
mappings: mappings
|
||||
};
|
||||
if (options.inline) {
|
||||
answer.sourcesContent = [code];
|
||||
}
|
||||
return JSON.stringify(answer, null, 2);
|
||||
};
|
||||
|
||||
exports.loadV3SourceMap = function(sourceMap) {
|
||||
return todo();
|
||||
};
|
||||
|
||||
BASE64_CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
|
||||
|
||||
MAX_BASE64_VALUE = BASE64_CHARS.length - 1;
|
||||
|
||||
encodeBase64Char = function(value) {
|
||||
if (value > MAX_BASE64_VALUE) {
|
||||
throw new Error("Cannot encode value " + value + " > " + MAX_BASE64_VALUE);
|
||||
} else if (value < 0) {
|
||||
throw new Error("Cannot encode value " + value + " < 0");
|
||||
}
|
||||
return BASE64_CHARS[value];
|
||||
};
|
||||
|
||||
decodeBase64Char = function(char) {
|
||||
var value;
|
||||
value = BASE64_CHARS.indexOf(char);
|
||||
if (value === -1) {
|
||||
throw new Error("Invalid Base 64 character: " + char);
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
VLQ_SHIFT = 5;
|
||||
|
||||
VLQ_CONTINUATION_BIT = 1 << VLQ_SHIFT;
|
||||
|
||||
VLQ_VALUE_MASK = VLQ_CONTINUATION_BIT - 1;
|
||||
|
||||
exports.vlqEncodeValue = function(value) {
|
||||
var answer, nextVlqChunk, signBit, valueToEncode;
|
||||
signBit = value < 0 ? 1 : 0;
|
||||
valueToEncode = (Math.abs(value) << 1) + signBit;
|
||||
answer = "";
|
||||
while (valueToEncode || !answer) {
|
||||
nextVlqChunk = valueToEncode & VLQ_VALUE_MASK;
|
||||
valueToEncode = valueToEncode >> VLQ_SHIFT;
|
||||
if (valueToEncode) {
|
||||
nextVlqChunk |= VLQ_CONTINUATION_BIT;
|
||||
}
|
||||
answer += encodeBase64Char(nextVlqChunk);
|
||||
}
|
||||
return answer;
|
||||
};
|
||||
|
||||
exports.vlqDecodeValue = function(str, offset) {
|
||||
var consumed, continuationShift, done, nextChunkValue, nextVlqChunk, position, signBit, value;
|
||||
if (offset == null) {
|
||||
offset = 0;
|
||||
}
|
||||
position = offset;
|
||||
done = false;
|
||||
value = 0;
|
||||
continuationShift = 0;
|
||||
while (!done) {
|
||||
nextVlqChunk = decodeBase64Char(str[position]);
|
||||
position += 1;
|
||||
nextChunkValue = nextVlqChunk & VLQ_VALUE_MASK;
|
||||
value += nextChunkValue << continuationShift;
|
||||
if (!(nextVlqChunk & VLQ_CONTINUATION_BIT)) {
|
||||
done = true;
|
||||
}
|
||||
continuationShift += VLQ_SHIFT;
|
||||
}
|
||||
consumed = position - offset;
|
||||
signBit = value & 1;
|
||||
value = value >> 1;
|
||||
if (signBit) {
|
||||
value = -value;
|
||||
}
|
||||
return [value, consumed];
|
||||
};
|
||||
module.exports = SourceMap;
|
||||
|
||||
}).call(this);
|
||||
|
||||
@@ -10,7 +10,7 @@ child_process = require 'child_process'
|
||||
{Lexer} = require './lexer'
|
||||
{parser} = require './parser'
|
||||
helpers = require './helpers'
|
||||
sourcemap = require './sourcemap'
|
||||
SourceMap = require './sourcemap'
|
||||
|
||||
# The current CoffeeScript version number.
|
||||
exports.VERSION = '1.6.2'
|
||||
@@ -21,7 +21,7 @@ exports.helpers = helpers
|
||||
# Compile CoffeeScript code to JavaScript, using the Coffee/Jison compiler.
|
||||
#
|
||||
# If `options.sourceMap` is specified, then `options.filename` must also be specified. All
|
||||
# options that can be passed to `generateV3SourceMap()` may also be passed here.
|
||||
# options that can be passed to `SourceMap#generate` may also be passed here.
|
||||
#
|
||||
# This returns a javascript string, unless `options.sourceMap` is passed,
|
||||
# in which case this returns a `{js, v3SourceMap, sourceMap}
|
||||
@@ -31,7 +31,7 @@ exports.compile = compile = (code, options = {}) ->
|
||||
{merge} = exports.helpers
|
||||
|
||||
if options.sourceMap
|
||||
sourceMap = new sourcemap.SourceMap()
|
||||
map = new SourceMap
|
||||
|
||||
fragments = (parser.parse lexer.tokenize(code, options)).compileToFragments options
|
||||
|
||||
@@ -41,9 +41,9 @@ exports.compile = compile = (code, options = {}) ->
|
||||
js = ""
|
||||
for fragment in fragments
|
||||
# Update the sourcemap with data from each fragment
|
||||
if sourceMap
|
||||
if options.sourceMap
|
||||
if fragment.locationData
|
||||
sourceMap.addMapping(
|
||||
map.add(
|
||||
[fragment.locationData.first_line, fragment.locationData.first_column],
|
||||
[currentLine, currentColumn],
|
||||
{noReplace: true})
|
||||
@@ -60,9 +60,8 @@ exports.compile = compile = (code, options = {}) ->
|
||||
|
||||
if options.sourceMap
|
||||
answer = {js}
|
||||
if sourceMap
|
||||
answer.sourceMap = sourceMap
|
||||
answer.v3SourceMap = sourcemap.generateV3SourceMap(sourceMap, options, code)
|
||||
answer.sourceMap = map
|
||||
answer.v3SourceMap = map.generate(options, code)
|
||||
answer
|
||||
else
|
||||
js
|
||||
@@ -222,7 +221,7 @@ patchStackTrace = ->
|
||||
|
||||
getSourceMapping = (filename, line, column) ->
|
||||
sourceMap = mainModule._sourceMaps[filename]
|
||||
answer = sourceMap.getSourcePosition [line - 1, column - 1] if sourceMap
|
||||
answer = sourceMap.sourceLocation [line - 1, column - 1] if sourceMap
|
||||
if answer then [answer[0] + 1, answer[1] + 1] else null
|
||||
|
||||
frames = for frame in stack
|
||||
|
||||
@@ -1,256 +0,0 @@
|
||||
#### LineMapping
|
||||
|
||||
# Hold data about mappings for one line of generated source code.
|
||||
|
||||
class LineMapping
|
||||
constructor: (@generatedLine) ->
|
||||
# columnMap keeps track of which columns we've already mapped.
|
||||
@columnMap = {}
|
||||
|
||||
# columnMappings is an array of all column mappings, sorted by generated-column.
|
||||
@columnMappings = []
|
||||
|
||||
addMapping: (generatedColumn, [sourceLine, sourceColumn], options={}) ->
|
||||
if @columnMap[generatedColumn] and options.noReplace
|
||||
# We already have a mapping for this column.
|
||||
return
|
||||
|
||||
@columnMap[generatedColumn] = {
|
||||
generatedLine: @generatedLine
|
||||
generatedColumn
|
||||
sourceLine
|
||||
sourceColumn
|
||||
}
|
||||
|
||||
@columnMappings.push @columnMap[generatedColumn]
|
||||
@columnMappings.sort (a,b) -> a.generatedColumn - b.generatedColumn
|
||||
|
||||
getSourcePosition: (generatedColumn) ->
|
||||
answer = null
|
||||
lastColumnMapping = null
|
||||
for columnMapping in @columnMappings
|
||||
if columnMapping.generatedColumn > generatedColumn
|
||||
break
|
||||
else
|
||||
lastColumnMapping = columnMapping
|
||||
if lastColumnMapping
|
||||
answer = [lastColumnMapping.sourceLine, lastColumnMapping.sourceColumn]
|
||||
|
||||
#### SourceMap
|
||||
|
||||
# Maps locations in a generated source file back to locations in the original source file.
|
||||
#
|
||||
# This is intentionally agnostic towards how a source map might be represented on disk. A
|
||||
# SourceMap can be converted to a "v3" style sourcemap with `#generateV3SourceMap()`, for example
|
||||
# but the SourceMap class itself knows nothing about v3 source maps.
|
||||
|
||||
class exports.SourceMap
|
||||
constructor: () ->
|
||||
# `generatedLines` is an array of LineMappings, one per generated line.
|
||||
@generatedLines = []
|
||||
|
||||
# Adds a mapping to this SourceMap.
|
||||
#
|
||||
# `sourceLocation` and `generatedLocation` are both [line, column] arrays.
|
||||
#
|
||||
# If `options.noReplace` is true, then if there is already a mapping for
|
||||
# the specified `generatedLine` and `generatedColumn`, this will have no effect.
|
||||
addMapping: (sourceLocation, generatedLocation, options={}) ->
|
||||
[generatedLine, generatedColumn] = generatedLocation
|
||||
|
||||
lineMapping = @generatedLines[generatedLine]
|
||||
if not lineMapping
|
||||
lineMapping = @generatedLines[generatedLine] = new LineMapping(generatedLine)
|
||||
|
||||
lineMapping.addMapping generatedColumn, sourceLocation, options
|
||||
|
||||
# Returns [sourceLine, sourceColumn], or null if no mapping could be found.
|
||||
getSourcePosition: ([generatedLine, generatedColumn]) ->
|
||||
answer = null
|
||||
lineMapping = @generatedLines[generatedLine]
|
||||
if not lineMapping
|
||||
# TODO: Search backwards for the line?
|
||||
else
|
||||
answer = lineMapping.getSourcePosition generatedColumn
|
||||
|
||||
answer
|
||||
|
||||
|
||||
# `fn` will be called once for every recorded mapping, in the order in
|
||||
# which they occur in the generated source. `fn` will be passed an object
|
||||
# with four properties: sourceLine, sourceColumn, generatedLine, and
|
||||
# generatedColumn.
|
||||
forEachMapping: (fn) ->
|
||||
for lineMapping, generatedLineNumber in @generatedLines
|
||||
if lineMapping
|
||||
for columnMapping in lineMapping.columnMappings
|
||||
fn(columnMapping)
|
||||
|
||||
|
||||
#### generateV3SourceMap
|
||||
|
||||
# Builds a V3 source map from a SourceMap object.
|
||||
# Returns the generated JSON as a string.
|
||||
#
|
||||
# `options.sourceRoot` may be used to specify the sourceRoot written to the source map. Also,
|
||||
# `options.sourceFiles` and `options.generatedFile` may be passed to set "sources" and "file",
|
||||
# respectively. Note that `sourceFiles` must be an array.
|
||||
|
||||
exports.generateV3SourceMap = (sourceMap, options={}, code) ->
|
||||
sourceRoot = options.sourceRoot or ""
|
||||
sourceFiles = options.sourceFiles or [""]
|
||||
generatedFile = options.generatedFile or ""
|
||||
|
||||
writingGeneratedLine = 0
|
||||
lastGeneratedColumnWritten = 0
|
||||
lastSourceLineWritten = 0
|
||||
lastSourceColumnWritten = 0
|
||||
needComma = no
|
||||
|
||||
mappings = ""
|
||||
|
||||
sourceMap.forEachMapping (mapping) ->
|
||||
while writingGeneratedLine < mapping.generatedLine
|
||||
lastGeneratedColumnWritten = 0
|
||||
needComma = no
|
||||
mappings += ";"
|
||||
writingGeneratedLine++
|
||||
|
||||
# Write a comma if we've already written a segment on this line.
|
||||
if needComma
|
||||
mappings += ","
|
||||
needComma = no
|
||||
|
||||
# Write the next segment.
|
||||
# Segments can be 1, 4, or 5 values. If just one, then it is a generated column which
|
||||
# doesn't match anything in the source code.
|
||||
#
|
||||
# Fields are all zero-based, and relative to the previous occurence unless otherwise noted:
|
||||
# * starting-column in generated source, relative to previous occurence for the current line.
|
||||
# * index into the "sources" list
|
||||
# * starting line in the original source
|
||||
# * starting column in the original source
|
||||
# * index into the "names" list associated with this segment.
|
||||
|
||||
# Add the generated start-column
|
||||
mappings += exports.vlqEncodeValue(mapping.generatedColumn - lastGeneratedColumnWritten)
|
||||
lastGeneratedColumnWritten = mapping.generatedColumn
|
||||
|
||||
# Add the index into the sources list
|
||||
mappings += exports.vlqEncodeValue(0)
|
||||
|
||||
# Add the source start-line
|
||||
mappings += exports.vlqEncodeValue(mapping.sourceLine - lastSourceLineWritten)
|
||||
lastSourceLineWritten = mapping.sourceLine
|
||||
|
||||
# Add the source start-column
|
||||
mappings += exports.vlqEncodeValue(mapping.sourceColumn - lastSourceColumnWritten)
|
||||
lastSourceColumnWritten = mapping.sourceColumn
|
||||
|
||||
# TODO: Do we care about symbol names for CoffeeScript? Probably not.
|
||||
|
||||
needComma = yes
|
||||
|
||||
answer = {
|
||||
version: 3
|
||||
file: generatedFile
|
||||
sourceRoot
|
||||
sources: sourceFiles
|
||||
names: []
|
||||
mappings
|
||||
}
|
||||
answer.sourcesContent = [code] if options.inline
|
||||
|
||||
return JSON.stringify answer, null, 2
|
||||
|
||||
# Load a SourceMap from a JSON string. Returns the SourceMap object.
|
||||
exports.loadV3SourceMap = (sourceMap) ->
|
||||
todo()
|
||||
|
||||
#### Base64 encoding helpers
|
||||
|
||||
BASE64_CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
|
||||
MAX_BASE64_VALUE = BASE64_CHARS.length - 1
|
||||
|
||||
encodeBase64Char = (value) ->
|
||||
if value > MAX_BASE64_VALUE
|
||||
throw new Error "Cannot encode value #{value} > #{MAX_BASE64_VALUE}"
|
||||
else if value < 0
|
||||
throw new Error "Cannot encode value #{value} < 0"
|
||||
BASE64_CHARS[value]
|
||||
|
||||
decodeBase64Char = (char) ->
|
||||
value = BASE64_CHARS.indexOf char
|
||||
if value == -1
|
||||
throw new Error "Invalid Base 64 character: #{char}"
|
||||
value
|
||||
|
||||
#### Base 64 VLQ encoding/decoding helpers
|
||||
|
||||
# Note that SourceMap VLQ encoding is "backwards". MIDI style VLQ encoding puts the
|
||||
# most-significant-bit (MSB) from the original value into the MSB of the VLQ encoded value
|
||||
# (see http://en.wikipedia.org/wiki/File:Uintvar_coding.svg). SourceMap VLQ does things
|
||||
# the other way around, with the least significat four bits of the original value encoded
|
||||
# into the first byte of the VLQ encoded value.
|
||||
|
||||
VLQ_SHIFT = 5
|
||||
VLQ_CONTINUATION_BIT = 1 << VLQ_SHIFT # 0010 0000
|
||||
VLQ_VALUE_MASK = VLQ_CONTINUATION_BIT - 1 # 0001 1111
|
||||
|
||||
# Encode a value as Base 64 VLQ.
|
||||
exports.vlqEncodeValue = (value) ->
|
||||
# Least significant bit represents the sign.
|
||||
signBit = if value < 0 then 1 else 0
|
||||
|
||||
# Next bits are the actual value
|
||||
valueToEncode = (Math.abs(value) << 1) + signBit
|
||||
|
||||
answer = ""
|
||||
# Make sure we encode at least one character, even if valueToEncode is 0.
|
||||
while valueToEncode || !answer
|
||||
nextVlqChunk = valueToEncode & VLQ_VALUE_MASK
|
||||
valueToEncode = valueToEncode >> VLQ_SHIFT
|
||||
|
||||
if valueToEncode
|
||||
nextVlqChunk |= VLQ_CONTINUATION_BIT
|
||||
|
||||
answer += encodeBase64Char(nextVlqChunk)
|
||||
|
||||
return answer
|
||||
|
||||
# Decode a Base 64 VLQ value.
|
||||
#
|
||||
# Returns `[value, consumed]` where `value` is the decoded value, and `consumed` is the number
|
||||
# of characters consumed from `str`.
|
||||
exports.vlqDecodeValue = (str, offset=0) ->
|
||||
position = offset
|
||||
done = false
|
||||
|
||||
value = 0
|
||||
continuationShift = 0
|
||||
|
||||
while !done
|
||||
nextVlqChunk = decodeBase64Char(str[position])
|
||||
position += 1
|
||||
|
||||
nextChunkValue = nextVlqChunk & VLQ_VALUE_MASK
|
||||
value += (nextChunkValue << continuationShift)
|
||||
|
||||
if !(nextVlqChunk & VLQ_CONTINUATION_BIT)
|
||||
# We'll be done after this character.
|
||||
done = true
|
||||
|
||||
# Bits are encoded least-significant first (opposite of MIDI VLQ). Increase the
|
||||
# continuationShift, so the next byte will end up where it should in the value.
|
||||
continuationShift += VLQ_SHIFT
|
||||
|
||||
consumed = position - offset
|
||||
|
||||
# Least significant bit represents the sign.
|
||||
signBit = value & 1
|
||||
value = value >> 1
|
||||
|
||||
if signBit then value = -value
|
||||
|
||||
return [value, consumed]
|
||||
|
||||
188
src/sourcemap.litcoffee
Normal file
188
src/sourcemap.litcoffee
Normal file
@@ -0,0 +1,188 @@
|
||||
Source maps allow JavaScript runtimes to match running JavaScript back to
|
||||
the original CoffeeScript source code that corresponds to it. In order to
|
||||
produce maps, we must keep track of positions (line number, column number)
|
||||
for every construct in the syntax tree, and be able to generate a map file
|
||||
-- which is a compact, VLQ-encoded representation of the JSON serialization
|
||||
of this information -- to write out alongside the generated JavaScript.
|
||||
|
||||
{merge} = require './helpers'
|
||||
|
||||
|
||||
LineMap
|
||||
-------
|
||||
|
||||
Keeps track of information about column positions within a single line of
|
||||
output JavaScript code. **SourceMap**s are implemented in terms of **LineMap**s.
|
||||
|
||||
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
|
||||
---------
|
||||
|
||||
Maps locations in for a single generated JavaScript file back to locations in
|
||||
the original CoffeeScript source file.
|
||||
|
||||
This is intentionally agnostic towards how a source map might be represented on
|
||||
disk. Once the compiler is ready to produce a "v3"-style source map, we can walk
|
||||
through the arrays of line and column buffer to produce it.
|
||||
|
||||
class SourceMap
|
||||
constructor: ->
|
||||
@lines = []
|
||||
|
||||
Adds a mapping to this SourceMap. `sourceLocation` and `generatedLocation`
|
||||
are both `[line, column]` arrays. If `options.noReplace` is true, then if there
|
||||
is already a mapping for the specified `line` and `column`, this will have no
|
||||
effect.
|
||||
|
||||
add: (sourceLocation, generatedLocation, options = {}) ->
|
||||
[line, column] = generatedLocation
|
||||
lineMap = (@lines[line] or= new LineMap(line))
|
||||
lineMap.add column, sourceLocation, options
|
||||
|
||||
Look up the original position of a given `line` and `column` in the generated
|
||||
code.
|
||||
|
||||
sourceLocation: ([line, column]) ->
|
||||
line-- until (lineMap = @lines[line]) or (line <= 0)
|
||||
lineMap and lineMap.sourceLocation column
|
||||
|
||||
`func` will be called once for every recorded mapping, in the order in
|
||||
which they occur in the generated source. `fn` will be passed an object
|
||||
with four properties: sourceLine, sourceColumn, line, and
|
||||
column.
|
||||
|
||||
each: (iterator) ->
|
||||
for lineMap, lineNumber in @lines when lineMap
|
||||
for mapping in lineMap.columns when mapping
|
||||
iterator mapping
|
||||
|
||||
|
||||
V3 SourceMap Generation
|
||||
-----------------------
|
||||
|
||||
Builds up a V3 source map, returning the generated JSON as a string.
|
||||
`options.sourceRoot` may be used to specify the sourceRoot written to the source
|
||||
map. Also, `options.sourceFiles` and `options.generatedFile` may be passed to
|
||||
set "sources" and "file", respectively.
|
||||
|
||||
generate: (options = {}, code = null) ->
|
||||
writingline = 0
|
||||
lastColumn = 0
|
||||
lastSourceLine = 0
|
||||
lastSourceColumn = 0
|
||||
needComma = no
|
||||
buffer = ""
|
||||
|
||||
@each (mapping) =>
|
||||
while writingline < mapping.line
|
||||
lastColumn = 0
|
||||
needComma = no
|
||||
buffer += ";"
|
||||
writingline++
|
||||
|
||||
Write a comma if we've already written a segment on this line.
|
||||
|
||||
if needComma
|
||||
buffer += ","
|
||||
needComma = no
|
||||
|
||||
Write the next segment. Segments can be 1, 4, or 5 values. If just one, then it
|
||||
is a generated column which doesn't match anything in the source code.
|
||||
|
||||
The starting column in the generated source, relative to any previous recorded
|
||||
column for the current line:
|
||||
|
||||
buffer += @encodeVlq mapping.column - lastColumn
|
||||
lastColumn = mapping.column
|
||||
|
||||
The index into the list of sources:
|
||||
|
||||
buffer += @encodeVlq 0
|
||||
|
||||
The starting line in the original source, relative to the previous source line.
|
||||
|
||||
buffer += @encodeVlq mapping.sourceLine - lastSourceLine
|
||||
if lastSourceLine isnt mapping.sourceLine
|
||||
lastSourceLine = mapping.sourceLine
|
||||
lastSourceColumn = 0
|
||||
|
||||
The starting column in the original source, relative to the previous column.
|
||||
|
||||
buffer += @encodeVlq mapping.sourceColumn - lastSourceColumn
|
||||
lastSourceColumn = mapping.sourceColumn
|
||||
needComma = yes
|
||||
|
||||
Produce the canonical JSON object format for a "v3" source map.
|
||||
|
||||
v3 =
|
||||
version: 3
|
||||
file: options.generatedFile or ''
|
||||
sourceRoot: options.sourceRoot or ''
|
||||
sources: options.sourceFiles or ['']
|
||||
names: []
|
||||
mappings: buffer
|
||||
|
||||
v3.sourcesContent = [code] if options.inline
|
||||
|
||||
return JSON.stringify v3, null, 2
|
||||
|
||||
|
||||
Base64 VLQ Encoding
|
||||
-------------------
|
||||
|
||||
Note that SourceMap VLQ encoding is "backwards". MIDI-style VLQ encoding puts
|
||||
the most-significant-bit (MSB) from the original value into the MSB of the VLQ
|
||||
encoded value (see [Wikipedia](http://en.wikipedia.org/wiki/File:Uintvar_coding.svg)).
|
||||
SourceMap VLQ does things the other way around, with the least significat four
|
||||
bits of the original value encoded into the first byte of the VLQ encoded value.
|
||||
|
||||
VLQ_SHIFT = 5
|
||||
VLQ_CONTINUATION_BIT = 1 << VLQ_SHIFT # 0010 0000
|
||||
VLQ_VALUE_MASK = VLQ_CONTINUATION_BIT - 1 # 0001 1111
|
||||
|
||||
encodeVlq: (value) ->
|
||||
answer = ''
|
||||
|
||||
# Least significant bit represents the sign.
|
||||
signBit = if value < 0 then 1 else 0
|
||||
|
||||
# The next bits are the actual value.
|
||||
valueToEncode = (Math.abs(value) << 1) + signBit
|
||||
|
||||
# Make sure we encode at least one character, even if valueToEncode is 0.
|
||||
while valueToEncode or not answer
|
||||
nextChunk = valueToEncode & VLQ_VALUE_MASK
|
||||
valueToEncode = valueToEncode >> VLQ_SHIFT
|
||||
nextChunk |= VLQ_CONTINUATION_BIT if valueToEncode
|
||||
answer += @encodeBase64 nextChunk
|
||||
|
||||
return answer
|
||||
|
||||
|
||||
Regular Base64 Encoding
|
||||
-----------------------
|
||||
|
||||
BASE64_CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
|
||||
|
||||
encodeBase64: (value) ->
|
||||
BASE64_CHARS[value] or throw new Error "Cannot Base64 encode value: #{value}"
|
||||
|
||||
|
||||
Our API for source maps is just the `SourceMap` class.
|
||||
|
||||
module.exports = SourceMap
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
return if global.testingBrowser
|
||||
|
||||
sourcemap = require '../src/sourcemap'
|
||||
SourceMap = require '../src/sourcemap'
|
||||
|
||||
vlqEncodedValues = [
|
||||
[1, "C"],
|
||||
@@ -12,39 +12,30 @@ vlqEncodedValues = [
|
||||
[948, "o7B"]
|
||||
]
|
||||
|
||||
test "vlqEncodeValue tests", ->
|
||||
test "encodeVlq tests", ->
|
||||
for pair in vlqEncodedValues
|
||||
eq (sourcemap.vlqEncodeValue pair[0]), pair[1]
|
||||
|
||||
test "vlqDecodeValue tests", ->
|
||||
for pair in vlqEncodedValues
|
||||
arrayEq (sourcemap.vlqDecodeValue pair[1]), [pair[0], pair[1].length]
|
||||
|
||||
test "vlqDecodeValue with offset", ->
|
||||
for pair in vlqEncodedValues
|
||||
# Try with an offset, and some cruft at the end.
|
||||
arrayEq (sourcemap.vlqDecodeValue ("abc" + pair[1] + "efg"), 3), [pair[0], pair[1].length]
|
||||
eq ((new SourceMap).encodeVlq pair[0]), pair[1]
|
||||
|
||||
eqJson = (a, b) ->
|
||||
eq (JSON.stringify JSON.parse a), (JSON.stringify JSON.parse b)
|
||||
|
||||
test "SourceMap tests", ->
|
||||
map = new sourcemap.SourceMap()
|
||||
map.addMapping [0, 0], [0, 0]
|
||||
map.addMapping [1, 5], [2, 4]
|
||||
map.addMapping [1, 6], [2, 7]
|
||||
map.addMapping [1, 9], [2, 8]
|
||||
map.addMapping [3, 0], [3, 4]
|
||||
map = new SourceMap
|
||||
map.add [0, 0], [0, 0]
|
||||
map.add [1, 5], [2, 4]
|
||||
map.add [1, 6], [2, 7]
|
||||
map.add [1, 9], [2, 8]
|
||||
map.add [3, 0], [3, 4]
|
||||
|
||||
testWithFilenames = sourcemap.generateV3SourceMap map, {
|
||||
testWithFilenames = map.generate {
|
||||
sourceRoot: "",
|
||||
sourceFiles: ["source.coffee"],
|
||||
generatedFile: "source.js"}
|
||||
eqJson testWithFilenames, '{"version":3,"file":"source.js","sourceRoot":"","sources":["source.coffee"],"names":[],"mappings":"AAAA;;IACK,GAAC,CAAG;IAET"}'
|
||||
eqJson (sourcemap.generateV3SourceMap map), '{"version":3,"file":"","sourceRoot":"","sources":[""],"names":[],"mappings":"AAAA;;IACK,GAAC,CAAG;IAET"}'
|
||||
eqJson testWithFilenames, '{"version":3,"file":"source.js","sourceRoot":"","sources":["source.coffee"],"names":[],"mappings":"AAAA;;IACK,GAAC,CAAG;IAEA"}'
|
||||
eqJson map.generate(), '{"version":3,"file":"","sourceRoot":"","sources":[""],"names":[],"mappings":"AAAA;;IACK,GAAC,CAAG;IAEA"}'
|
||||
|
||||
# Look up a generated column - should get back the original source position.
|
||||
arrayEq map.getSourcePosition([2,8]), [1,9]
|
||||
arrayEq map.sourceLocation([2,8]), [1,9]
|
||||
|
||||
# Look up a point futher along on the same line - should get back the same source position.
|
||||
arrayEq map.getSourcePosition([2,10]), [1,9]
|
||||
arrayEq map.sourceLocation([2,10]), [1,9]
|
||||
|
||||
Reference in New Issue
Block a user