Big refactor of SourceMap class. Literate CoffeeScript. Purdy.

This commit is contained in:
Jeremy Ashkenas
2013-03-18 19:23:05 +08:00
parent 566a7dabb2
commit 6786bab2ba
21 changed files with 545 additions and 824 deletions

View File

@@ -85,7 +85,7 @@
<a class="source" href="sourcemap.html">
sourcemap.coffee
sourcemap.litcoffee
</a>
</div>

View File

@@ -85,7 +85,7 @@
<a class="source" href="sourcemap.html">
sourcemap.coffee
sourcemap.litcoffee
</a>
</div>

View File

@@ -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) -&gt;
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

View File

@@ -85,7 +85,7 @@
<a class="source" href="sourcemap.html">
sourcemap.coffee
sourcemap.litcoffee
</a>
</div>

View File

@@ -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;

View File

@@ -85,7 +85,7 @@
<a class="source" href="sourcemap.html">
sourcemap.coffee
sourcemap.litcoffee
</a>
</div>

View File

@@ -85,7 +85,7 @@
<a class="source" href="sourcemap.html">
sourcemap.coffee
sourcemap.litcoffee
</a>
</div>

View File

@@ -85,7 +85,7 @@
<a class="source" href="sourcemap.html">
sourcemap.coffee
sourcemap.litcoffee
</a>
</div>

View File

@@ -85,7 +85,7 @@
<a class="source" href="sourcemap.html">
sourcemap.coffee
sourcemap.litcoffee
</a>
</div>

View File

@@ -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>

View File

@@ -85,7 +85,7 @@
<a class="source" href="sourcemap.html">
sourcemap.coffee
sourcemap.litcoffee
</a>
</div>

View File

@@ -85,7 +85,7 @@
<a class="source" href="sourcemap.html">
sourcemap.coffee
sourcemap.litcoffee
</a>
</div>

View File

@@ -85,7 +85,7 @@
<a class="source" href="sourcemap.html">
sourcemap.coffee
sourcemap.litcoffee
</a>
</div>

View File

@@ -85,7 +85,7 @@
<a class="source" href="sourcemap.html">
sourcemap.coffee
sourcemap.litcoffee
</a>
</div>

View File

@@ -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">&#182;</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>) -&gt;</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">&#182;</a>
</div>
<p>columnMap keeps track of which columns we&#39;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">&#182;</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>) -&gt;
<span class="property">@columns</span> = []
addMapping: (generatedColumn, [sourceLine, sourceColumn], options={}) -&gt;
<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={}) -&gt;
<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) -&gt;
column-- <span class="keyword">until</span> (mapping = <span class="property">@columns</span>[column]) <span class="keyword">or</span> (column &lt;= <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">&#182;</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) -&gt; a.generatedColumn - b.generatedColumn
getSourcePosition: (generatedColumn) -&gt;
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 &gt; 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">&#182;</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 &quot;v3&quot; 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 &quot;v3&quot;-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: () -&gt;</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: -&gt;
<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">&#182;</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 = {}) -&gt;
[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">&#182;</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={}) -&gt;
[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]) -&gt;
line-- <span class="keyword">until</span> (lineMap = <span class="property">@lines</span>[line]) <span class="keyword">or</span> (line &lt;= <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">&#182;</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]) -&gt;
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) -&gt;
<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">&#182;</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">&#182;</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 &quot;sources&quot; and &quot;file&quot;, respectively.
</p>
</div>
<div class="content"><div class='highlight'><pre> forEachMapping: (fn) -&gt;
<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>) -&gt;
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) =&gt;
<span class="keyword">while</span> writingline &lt; 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">&#182;</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 &quot;sources&quot; and &quot;file&quot;,
respectively. Note that <code>sourceFiles</code> must be an array.
<p>Write a comma if we&#39;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) -&gt;
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) -&gt;
<span class="keyword">while</span> writingGeneratedLine &lt; 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">&#182;</a>
</div>
<p>Write a comma if we&#39;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&#39;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">&#182;</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&#39;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 &quot;sources&quot; list
<em> starting line in the original source
</em> starting column in the original source
* index into the &quot;names&quot; 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&#39;t match anything in the source code.
<div class="pilwrap ">
<a class="pilcrow" href="#section-14">&#182;</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&#39;t match anything in the source code.
<div class="pilwrap ">
<a class="pilcrow" href="#section-15">&#182;</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&#39;t match anything in the source code.
<div class="pilwrap ">
<a class="pilcrow" href="#section-16">&#182;</a>
</div>
<p>Add the source start-column
<p>Produce the canonical JSON object format for a &quot;v3&quot; 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&#39;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">&#182;</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&#39;t match anything in the source code.
<div class="pilwrap ">
<a class="pilcrow" href="#section-18">&#182;</a>
</div>
<p>Load a SourceMap from a JSON string. Returns the SourceMap object.
<p>Note that SourceMap VLQ encoding is &quot;backwards&quot;. 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) -&gt;
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> &lt;&lt; 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) -&gt;
answer = <span class="string">''</span></pre></div></div>
</li>
@@ -513,28 +482,15 @@ doesn&#39;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">&#182;</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) -&gt;
<span class="keyword">if</span> value &gt; 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> &gt; <span class="subst">#{MAX_BASE64_VALUE}</span>"</span>
<span class="keyword">else</span> <span class="keyword">if</span> value &lt; <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> &lt; 0"</span>
BASE64_CHARS[value]
<span class="function"><span class="title">decodeBase64Char</span></span> = (char) -&gt;
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 &lt; <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">&#182;</a>
</div>
<h3>Base 64 VLQ encoding/decoding helpers</h3>
<p>Note that SourceMap VLQ encoding is &quot;backwards&quot;. 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> &lt;&lt; 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) &lt;&lt; <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">&#182;</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) -&gt;</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 &amp; VLQ_VALUE_MASK
valueToEncode = valueToEncode &gt;&gt; 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">&#182;</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 &lt; <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">&#182;</a>
</div>
<p>Next bits are the actual value
</p>
</div>
<div class="content"><div class='highlight'><pre> valueToEncode = (Math.abs(value) &lt;&lt; <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) -&gt;
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">&#182;</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 &amp; VLQ_VALUE_MASK
valueToEncode = valueToEncode &gt;&gt; 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">&#182;</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>) -&gt;
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 &amp; VLQ_VALUE_MASK
value += (nextChunkValue &lt;&lt; continuationShift)
<span class="keyword">if</span> !(nextVlqChunk &amp; VLQ_CONTINUATION_BIT)</pre></div></div>
</li>
<li id="section-26">
<div class="annotation">
<div class="pilwrap ">
<a class="pilcrow" href="#section-26">&#182;</a>
</div>
<p>We&#39;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">&#182;</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">&#182;</a>
</div>
<p>Least significant bit represents the sign.
</p>
</div>
<div class="content"><div class='highlight'><pre> signBit = value &amp; <span class="number">1</span>
value = value &gt;&gt; <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>

View File

@@ -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];

View File

@@ -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);

View File

@@ -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

View File

@@ -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
View 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

View File

@@ -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]