Upgrade handlebars to 1.3.0.

This was "npm update handlebars" followed by copying
runtime.js into the static directory and restoring the
copyright header.

(imported from commit 69d30cbfcb3b776cdfdcffa17a87704540eab76a)
This commit is contained in:
Steve Howell
2014-01-15 13:02:37 -05:00
committed by Tim Abbott
parent f015d27d31
commit 3bd0bfd4fd
257 changed files with 32326 additions and 40001 deletions

View File

@@ -1,4 +1,2 @@
.DS_Store
.tmp*~
*.local.*
.pinf-*
tmp/
node_modules/

View File

@@ -0,0 +1,7 @@
language: node_js
node_js:
- "0.4"
- "0.6"
- "0.8"
- "0.10"
- "0.11"

29
node_modules/handlebars/node_modules/uglify-js/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,29 @@
UglifyJS is released under the BSD license:
Copyright 2012-2013 (c) Mihai Bazon <mihai.bazon@gmail.com>
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
* Redistributions of source code must retain the above
copyright notice, this list of conditions and the following
disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials
provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “AS IS” AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.

View File

@@ -1,981 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
lang="en" xml:lang="en">
<head>
<title>UglifyJS &ndash; a JavaScript parser/compressor/beautifier</title>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
<meta name="generator" content="Org-mode"/>
<meta name="generated" content="2011-12-09 14:59:08 EET"/>
<meta name="author" content="Mihai Bazon"/>
<meta name="description" content="a JavaScript parser/compressor/beautifier in JavaScript"/>
<meta name="keywords" content="javascript, js, parser, compiler, compressor, mangle, minify, minifier"/>
<style type="text/css">
<!--/*--><![CDATA[/*><!--*/
html { font-family: Times, serif; font-size: 12pt; }
.title { text-align: center; }
.todo { color: red; }
.done { color: green; }
.tag { background-color: #add8e6; font-weight:normal }
.target { }
.timestamp { color: #bebebe; }
.timestamp-kwd { color: #5f9ea0; }
.right {margin-left:auto; margin-right:0px; text-align:right;}
.left {margin-left:0px; margin-right:auto; text-align:left;}
.center {margin-left:auto; margin-right:auto; text-align:center;}
p.verse { margin-left: 3% }
pre {
border: 1pt solid #AEBDCC;
background-color: #F3F5F7;
padding: 5pt;
font-family: courier, monospace;
font-size: 90%;
overflow:auto;
}
table { border-collapse: collapse; }
td, th { vertical-align: top; }
th.right { text-align:center; }
th.left { text-align:center; }
th.center { text-align:center; }
td.right { text-align:right; }
td.left { text-align:left; }
td.center { text-align:center; }
dt { font-weight: bold; }
div.figure { padding: 0.5em; }
div.figure p { text-align: center; }
div.inlinetask {
padding:10px;
border:2px solid gray;
margin:10px;
background: #ffffcc;
}
textarea { overflow-x: auto; }
.linenr { font-size:smaller }
.code-highlighted {background-color:#ffff00;}
.org-info-js_info-navigation { border-style:none; }
#org-info-js_console-label { font-size:10px; font-weight:bold;
white-space:nowrap; }
.org-info-js_search-highlight {background-color:#ffff00; color:#000000;
font-weight:bold; }
/*]]>*/-->
</style>
<link rel="stylesheet" type="text/css" href="docstyle.css" />
<script type="text/javascript">
<!--/*--><![CDATA[/*><!--*/
function CodeHighlightOn(elem, id)
{
var target = document.getElementById(id);
if(null != target) {
elem.cacheClassElem = elem.className;
elem.cacheClassTarget = target.className;
target.className = "code-highlighted";
elem.className = "code-highlighted";
}
}
function CodeHighlightOff(elem, id)
{
var target = document.getElementById(id);
if(elem.cacheClassElem)
elem.className = elem.cacheClassElem;
if(elem.cacheClassTarget)
target.className = elem.cacheClassTarget;
}
/*]]>*///-->
</script>
</head>
<body>
<div id="preamble">
</div>
<div id="content">
<h1 class="title">UglifyJS &ndash; a JavaScript parser/compressor/beautifier</h1>
<div id="table-of-contents">
<h2>Table of Contents</h2>
<div id="text-table-of-contents">
<ul>
<li><a href="#sec-1">1 UglifyJS &mdash; a JavaScript parser/compressor/beautifier </a>
<ul>
<li><a href="#sec-1-1">1.1 Unsafe transformations </a>
<ul>
<li><a href="#sec-1-1-1">1.1.1 Calls involving the global Array constructor </a></li>
<li><a href="#sec-1-1-2">1.1.2 <code>obj.toString()</code> ==&gt; <code>obj+“”</code> </a></li>
</ul>
</li>
<li><a href="#sec-1-2">1.2 Install (NPM) </a></li>
<li><a href="#sec-1-3">1.3 Install latest code from GitHub </a></li>
<li><a href="#sec-1-4">1.4 Usage </a>
<ul>
<li><a href="#sec-1-4-1">1.4.1 API </a></li>
<li><a href="#sec-1-4-2">1.4.2 Beautifier shortcoming &ndash; no more comments </a></li>
<li><a href="#sec-1-4-3">1.4.3 Use as a code pre-processor </a></li>
</ul>
</li>
<li><a href="#sec-1-5">1.5 Compression &ndash; how good is it? </a></li>
<li><a href="#sec-1-6">1.6 Bugs? </a></li>
<li><a href="#sec-1-7">1.7 Links </a></li>
<li><a href="#sec-1-8">1.8 License </a></li>
</ul>
</li>
</ul>
</div>
</div>
<div id="outline-container-1" class="outline-2">
<h2 id="sec-1"><span class="section-number-2">1</span> UglifyJS &mdash; a JavaScript parser/compressor/beautifier </h2>
<div class="outline-text-2" id="text-1">
<p>
This package implements a general-purpose JavaScript
parser/compressor/beautifier toolkit. It is developed on <a href="http://nodejs.org/">NodeJS</a>, but it
should work on any JavaScript platform supporting the CommonJS module system
(and if your platform of choice doesn't support CommonJS, you can easily
implement it, or discard the <code>exports.*</code> lines from UglifyJS sources).
</p>
<p>
The tokenizer/parser generates an abstract syntax tree from JS code. You
can then traverse the AST to learn more about the code, or do various
manipulations on it. This part is implemented in <a href="../lib/parse-js.js">parse-js.js</a> and it's a
port to JavaScript of the excellent <a href="http://marijn.haverbeke.nl/parse-js/">parse-js</a> Common Lisp library from <a href="http://marijn.haverbeke.nl/">Marijn Haverbeke</a>.
</p>
<p>
( See <a href="http://github.com/mishoo/cl-uglify-js">cl-uglify-js</a> if you're looking for the Common Lisp version of
UglifyJS. )
</p>
<p>
The second part of this package, implemented in <a href="../lib/process.js">process.js</a>, inspects and
manipulates the AST generated by the parser to provide the following:
</p>
<ul>
<li>ability to re-generate JavaScript code from the AST. Optionally
indented&mdash;you can use this if you want to “beautify” a program that has
been compressed, so that you can inspect the source. But you can also run
our code generator to print out an AST without any whitespace, so you
achieve compression as well.
</li>
<li>shorten variable names (usually to single characters). Our mangler will
analyze the code and generate proper variable names, depending on scope
and usage, and is smart enough to deal with globals defined elsewhere, or
with <code>eval()</code> calls or <code>with{}</code> statements. In short, if <code>eval()</code> or
<code>with{}</code> are used in some scope, then all variables in that scope and any
variables in the parent scopes will remain unmangled, and any references
to such variables remain unmangled as well.
</li>
<li>various small optimizations that may lead to faster code but certainly
lead to smaller code. Where possible, we do the following:
<ul>
<li>foo["bar"] ==&gt; foo.bar
</li>
<li>remove block brackets <code>{}</code>
</li>
<li>join consecutive var declarations:
var a = 10; var b = 20; ==&gt; var a=10,b=20;
</li>
<li>resolve simple constant expressions: 1 +2 * 3 ==&gt; 7. We only do the
replacement if the result occupies less bytes; for example 1/3 would
translate to 0.333333333333, so in this case we don't replace it.
</li>
<li>consecutive statements in blocks are merged into a sequence; in many
cases, this leaves blocks with a single statement, so then we can remove
the block brackets.
</li>
<li>various optimizations for IF statements:
<ul>
<li>if (foo) bar(); else baz(); ==&gt; foo?bar():baz();
</li>
<li>if (!foo) bar(); else baz(); ==&gt; foo?baz():bar();
</li>
<li>if (foo) bar(); ==&gt; foo&amp;&amp;bar();
</li>
<li>if (!foo) bar(); ==&gt; foo||bar();
</li>
<li>if (foo) return bar(); else return baz(); ==&gt; return foo?bar():baz();
</li>
<li>if (foo) return bar(); else something(); ==&gt; {if(foo)return bar();something()}
</li>
</ul>
</li>
<li>remove some unreachable code and warn about it (code that follows a
<code>return</code>, <code>throw</code>, <code>break</code> or <code>continue</code> statement, except
function/variable declarations).
</li>
<li>act a limited version of a pre-processor (c.f. the pre-processor of
C/C++) to allow you to safely replace selected global symbols with
specified values. When combined with the optimisations above this can
make UglifyJS operate slightly more like a compilation process, in
that when certain symbols are replaced by constant values, entire code
blocks may be optimised away as unreachable.
</li>
</ul>
</li>
</ul>
</div>
<div id="outline-container-1-1" class="outline-3">
<h3 id="sec-1-1"><span class="section-number-3">1.1</span> <span class="target">Unsafe transformations</span> </h3>
<div class="outline-text-3" id="text-1-1">
<p>
The following transformations can in theory break code, although they're
probably safe in most practical cases. To enable them you need to pass the
<code>--unsafe</code> flag.
</p>
</div>
<div id="outline-container-1-1-1" class="outline-4">
<h4 id="sec-1-1-1"><span class="section-number-4">1.1.1</span> Calls involving the global Array constructor </h4>
<div class="outline-text-4" id="text-1-1-1">
<p>
The following transformations occur:
</p>
<pre class="src src-js"><span class="org-keyword">new</span> <span class="org-type">Array</span>(1, 2, 3, 4) =&gt; [1,2,3,4]
Array(a, b, c) =&gt; [a,b,c]
<span class="org-keyword">new</span> <span class="org-type">Array</span>(5) =&gt; Array(5)
<span class="org-keyword">new</span> <span class="org-type">Array</span>(a) =&gt; Array(a)
</pre>
<p>
These are all safe if the Array name isn't redefined. JavaScript does allow
one to globally redefine Array (and pretty much everything, in fact) but I
personally don't see why would anyone do that.
</p>
<p>
UglifyJS does handle the case where Array is redefined locally, or even
globally but with a <code>function</code> or <code>var</code> declaration. Therefore, in the
following cases UglifyJS <b>doesn't touch</b> calls or instantiations of Array:
</p>
<pre class="src src-js"><span class="org-comment-delimiter">// </span><span class="org-comment">case 1. globally declared variable</span>
<span class="org-keyword">var</span> <span class="org-variable-name">Array</span>;
<span class="org-keyword">new</span> <span class="org-type">Array</span>(1, 2, 3);
Array(a, b);
<span class="org-comment-delimiter">// </span><span class="org-comment">or (can be declared later)</span>
<span class="org-keyword">new</span> <span class="org-type">Array</span>(1, 2, 3);
<span class="org-keyword">var</span> <span class="org-variable-name">Array</span>;
<span class="org-comment-delimiter">// </span><span class="org-comment">or (can be a function)</span>
<span class="org-keyword">new</span> <span class="org-type">Array</span>(1, 2, 3);
<span class="org-keyword">function</span> <span class="org-function-name">Array</span>() { ... }
<span class="org-comment-delimiter">// </span><span class="org-comment">case 2. declared in a function</span>
(<span class="org-keyword">function</span>(){
a = <span class="org-keyword">new</span> <span class="org-type">Array</span>(1, 2, 3);
b = Array(5, 6);
<span class="org-keyword">var</span> <span class="org-variable-name">Array</span>;
})();
<span class="org-comment-delimiter">// </span><span class="org-comment">or</span>
(<span class="org-keyword">function</span>(<span class="org-variable-name">Array</span>){
<span class="org-keyword">return</span> Array(5, 6, 7);
})();
<span class="org-comment-delimiter">// </span><span class="org-comment">or</span>
(<span class="org-keyword">function</span>(){
<span class="org-keyword">return</span> <span class="org-keyword">new</span> <span class="org-type">Array</span>(1, 2, 3, 4);
<span class="org-keyword">function</span> <span class="org-function-name">Array</span>() { ... }
})();
<span class="org-comment-delimiter">// </span><span class="org-comment">etc.</span>
</pre>
</div>
</div>
<div id="outline-container-1-1-2" class="outline-4">
<h4 id="sec-1-1-2"><span class="section-number-4">1.1.2</span> <code>obj.toString()</code> ==&gt; <code>obj+“”</code> </h4>
<div class="outline-text-4" id="text-1-1-2">
</div>
</div>
</div>
<div id="outline-container-1-2" class="outline-3">
<h3 id="sec-1-2"><span class="section-number-3">1.2</span> Install (NPM) </h3>
<div class="outline-text-3" id="text-1-2">
<p>
UglifyJS is now available through NPM &mdash; <code>npm install uglify-js</code> should do
the job.
</p>
</div>
</div>
<div id="outline-container-1-3" class="outline-3">
<h3 id="sec-1-3"><span class="section-number-3">1.3</span> Install latest code from GitHub </h3>
<div class="outline-text-3" id="text-1-3">
<pre class="src src-sh"><span class="org-comment-delimiter">## </span><span class="org-comment">clone the repository</span>
mkdir -p /where/you/wanna/put/it
<span class="org-builtin">cd</span> /where/you/wanna/put/it
git clone git://github.com/mishoo/UglifyJS.git
<span class="org-comment-delimiter">## </span><span class="org-comment">make the module available to Node</span>
mkdir -p ~/.node_libraries/
<span class="org-builtin">cd</span> ~/.node_libraries/
ln -s /where/you/wanna/put/it/UglifyJS/uglify-js.js
<span class="org-comment-delimiter">## </span><span class="org-comment">and if you want the CLI script too:</span>
mkdir -p ~/bin
<span class="org-builtin">cd</span> ~/bin
ln -s /where/you/wanna/put/it/UglifyJS/bin/uglifyjs
<span class="org-comment-delimiter"># </span><span class="org-comment">(then add ~/bin to your $PATH if it's not there already)</span>
</pre>
</div>
</div>
<div id="outline-container-1-4" class="outline-3">
<h3 id="sec-1-4"><span class="section-number-3">1.4</span> Usage </h3>
<div class="outline-text-3" id="text-1-4">
<p>
There is a command-line tool that exposes the functionality of this library
for your shell-scripting needs:
</p>
<pre class="src src-sh">uglifyjs [ options... ] [ filename ]
</pre>
<p>
<code>filename</code> should be the last argument and should name the file from which
to read the JavaScript code. If you don't specify it, it will read code
from STDIN.
</p>
<p>
Supported options:
</p>
<ul>
<li><code>-b</code> or <code>--beautify</code> &mdash; output indented code; when passed, additional
options control the beautifier:
<ul>
<li><code>-i N</code> or <code>--indent N</code> &mdash; indentation level (number of spaces)
</li>
<li><code>-q</code> or <code>--quote-keys</code> &mdash; quote keys in literal objects (by default,
only keys that cannot be identifier names will be quotes).
</li>
</ul>
</li>
<li><code>--ascii</code> &mdash; pass this argument to encode non-ASCII characters as
<code>\uXXXX</code> sequences. By default UglifyJS won't bother to do it and will
output Unicode characters instead. (the output is always encoded in UTF8,
but if you pass this option you'll only get ASCII).
</li>
<li><code>-nm</code> or <code>--no-mangle</code> &mdash; don't mangle names.
</li>
<li><code>-nmf</code> or <code>--no-mangle-functions</code> &ndash; in case you want to mangle variable
names, but not touch function names.
</li>
<li><code>-ns</code> or <code>--no-squeeze</code> &mdash; don't call <code>ast_squeeze()</code> (which does various
optimizations that result in smaller, less readable code).
</li>
<li><code>-mt</code> or <code>--mangle-toplevel</code> &mdash; mangle names in the toplevel scope too
(by default we don't do this).
</li>
<li><code>--no-seqs</code> &mdash; when <code>ast_squeeze()</code> is called (thus, unless you pass
<code>--no-squeeze</code>) it will reduce consecutive statements in blocks into a
sequence. For example, "a = 10; b = 20; foo();" will be written as
"a=10,b=20,foo();". In various occasions, this allows us to discard the
block brackets (since the block becomes a single statement). This is ON
by default because it seems safe and saves a few hundred bytes on some
libs that I tested it on, but pass <code>--no-seqs</code> to disable it.
</li>
<li><code>--no-dead-code</code> &mdash; by default, UglifyJS will remove code that is
obviously unreachable (code that follows a <code>return</code>, <code>throw</code>, <code>break</code> or
<code>continue</code> statement and is not a function/variable declaration). Pass
this option to disable this optimization.
</li>
<li><code>-nc</code> or <code>--no-copyright</code> &mdash; by default, <code>uglifyjs</code> will keep the initial
comment tokens in the generated code (assumed to be copyright information
etc.). If you pass this it will discard it.
</li>
<li><code>-o filename</code> or <code>--output filename</code> &mdash; put the result in <code>filename</code>. If
this isn't given, the result goes to standard output (or see next one).
</li>
<li><code>--overwrite</code> &mdash; if the code is read from a file (not from STDIN) and you
pass <code>--overwrite</code> then the output will be written in the same file.
</li>
<li><code>--ast</code> &mdash; pass this if you want to get the Abstract Syntax Tree instead
of JavaScript as output. Useful for debugging or learning more about the
internals.
</li>
<li><code>-v</code> or <code>--verbose</code> &mdash; output some notes on STDERR (for now just how long
each operation takes).
</li>
<li><code>-d SYMBOL[=VALUE]</code> or <code>--define SYMBOL[=VALUE]</code> &mdash; will replace
all instances of the specified symbol where used as an identifier
(except where symbol has properly declared by a var declaration or
use as function parameter or similar) with the specified value. This
argument may be specified multiple times to define multiple
symbols - if no value is specified the symbol will be replaced with
the value <code>true</code>, or you can specify a numeric value (such as
<code>1024</code>), a quoted string value (such as ="object"= or
='https://github.com'<code>), or the name of another symbol or keyword (such as =null</code> or <code>document</code>).
This allows you, for example, to assign meaningful names to key
constant values but discard the symbolic names in the uglified
version for brevity/efficiency, or when used wth care, allows
UglifyJS to operate as a form of <b>conditional compilation</b>
whereby defining appropriate values may, by dint of the constant
folding and dead code removal features above, remove entire
superfluous code blocks (e.g. completely remove instrumentation or
trace code for production use).
Where string values are being defined, the handling of quotes are
likely to be subject to the specifics of your command shell
environment, so you may need to experiment with quoting styles
depending on your platform, or you may find the option
<code>--define-from-module</code> more suitable for use.
</li>
<li><code>-define-from-module SOMEMODULE</code> &mdash; will load the named module (as
per the NodeJS <code>require()</code> function) and iterate all the exported
properties of the module defining them as symbol names to be defined
(as if by the <code>--define</code> option) per the name of each property
(i.e. without the module name prefix) and given the value of the
property. This is a much easier way to handle and document groups of
symbols to be defined rather than a large number of <code>--define</code>
options.
</li>
<li><code>--unsafe</code> &mdash; enable other additional optimizations that are known to be
unsafe in some contrived situations, but could still be generally useful.
For now only these:
<ul>
<li>foo.toString() ==&gt; foo+""
</li>
<li>new Array(x,&hellip;) ==&gt; [x,&hellip;]
</li>
<li>new Array(x) ==&gt; Array(x)
</li>
</ul>
</li>
<li><code>--max-line-len</code> (default 32K characters) &mdash; add a newline after around
32K characters. I've seen both FF and Chrome croak when all the code was
on a single line of around 670K. Pass &ndash;max-line-len 0 to disable this
safety feature.
</li>
<li><code>--reserved-names</code> &mdash; some libraries rely on certain names to be used, as
pointed out in issue #92 and #81, so this option allow you to exclude such
names from the mangler. For example, to keep names <code>require</code> and <code>$super</code>
intact you'd specify &ndash;reserved-names "require,$super".
</li>
<li><code>--inline-script</code> &ndash; when you want to include the output literally in an
HTML <code>&lt;script&gt;</code> tag you can use this option to prevent <code>&lt;/script</code> from
showing up in the output.
</li>
<li><code>--lift-vars</code> &ndash; when you pass this, UglifyJS will apply the following
transformations (see the notes in API, <code>ast_lift_variables</code>):
<ul>
<li>put all <code>var</code> declarations at the start of the scope
</li>
<li>make sure a variable is declared only once
</li>
<li>discard unused function arguments
</li>
<li>discard unused inner (named) functions
</li>
<li>finally, try to merge assignments into that one <code>var</code> declaration, if
possible.
</li>
</ul>
</li>
</ul>
</div>
<div id="outline-container-1-4-1" class="outline-4">
<h4 id="sec-1-4-1"><span class="section-number-4">1.4.1</span> API </h4>
<div class="outline-text-4" id="text-1-4-1">
<p>
To use the library from JavaScript, you'd do the following (example for
NodeJS):
</p>
<pre class="src src-js"><span class="org-keyword">var</span> <span class="org-variable-name">jsp</span> = require(<span class="org-string">"uglify-js"</span>).parser;
<span class="org-keyword">var</span> <span class="org-variable-name">pro</span> = require(<span class="org-string">"uglify-js"</span>).uglify;
<span class="org-keyword">var</span> <span class="org-variable-name">orig_code</span> = <span class="org-string">"... JS code here"</span>;
<span class="org-keyword">var</span> <span class="org-variable-name">ast</span> = jsp.parse(orig_code); <span class="org-comment-delimiter">// </span><span class="org-comment">parse code and get the initial AST</span>
ast = pro.ast_mangle(ast); <span class="org-comment-delimiter">// </span><span class="org-comment">get a new AST with mangled names</span>
ast = pro.ast_squeeze(ast); <span class="org-comment-delimiter">// </span><span class="org-comment">get an AST with compression optimizations</span>
<span class="org-keyword">var</span> <span class="org-variable-name">final_code</span> = pro.gen_code(ast); <span class="org-comment-delimiter">// </span><span class="org-comment">compressed code here</span>
</pre>
<p>
The above performs the full compression that is possible right now. As you
can see, there are a sequence of steps which you can apply. For example if
you want compressed output but for some reason you don't want to mangle
variable names, you would simply skip the line that calls
<code>pro.ast_mangle(ast)</code>.
</p>
<p>
Some of these functions take optional arguments. Here's a description:
</p>
<ul>
<li><code>jsp.parse(code, strict_semicolons)</code> &ndash; parses JS code and returns an AST.
<code>strict_semicolons</code> is optional and defaults to <code>false</code>. If you pass
<code>true</code> then the parser will throw an error when it expects a semicolon and
it doesn't find it. For most JS code you don't want that, but it's useful
if you want to strictly sanitize your code.
</li>
<li><code>pro.ast_lift_variables(ast)</code> &ndash; merge and move <code>var</code> declarations to the
scop of the scope; discard unused function arguments or variables; discard
unused (named) inner functions. It also tries to merge assignments
following the <code>var</code> declaration into it.
<p>
If your code is very hand-optimized concerning <code>var</code> declarations, this
lifting variable declarations might actually increase size. For me it
helps out. On jQuery it adds 865 bytes (243 after gzip). YMMV. Also
note that (since it's not enabled by default) this operation isn't yet
heavily tested (please report if you find issues!).
</p>
<p>
Note that although it might increase the image size (on jQuery it gains
865 bytes, 243 after gzip) it's technically more correct: in certain
situations, dead code removal might drop variable declarations, which
would not happen if the variables are lifted in advance.
</p>
<p>
Here's an example of what it does:
</p></li>
</ul>
<pre class="src src-js"><span class="org-keyword">function</span> <span class="org-function-name">f</span>(<span class="org-variable-name">a</span>, <span class="org-variable-name">b</span>, <span class="org-variable-name">c</span>, <span class="org-variable-name">d</span>, <span class="org-variable-name">e</span>) {
<span class="org-keyword">var</span> <span class="org-variable-name">q</span>;
<span class="org-keyword">var</span> <span class="org-variable-name">w</span>;
w = 10;
q = 20;
<span class="org-keyword">for</span> (<span class="org-keyword">var</span> <span class="org-variable-name">i</span> = 1; i &lt; 10; ++i) {
<span class="org-keyword">var</span> <span class="org-variable-name">boo</span> = foo(a);
}
<span class="org-keyword">for</span> (<span class="org-keyword">var</span> <span class="org-variable-name">i</span> = 0; i &lt; 1; ++i) {
<span class="org-keyword">var</span> <span class="org-variable-name">boo</span> = bar(c);
}
<span class="org-keyword">function</span> <span class="org-function-name">foo</span>(){ ... }
<span class="org-keyword">function</span> <span class="org-function-name">bar</span>(){ ... }
<span class="org-keyword">function</span> <span class="org-function-name">baz</span>(){ ... }
}
<span class="org-comment-delimiter">// </span><span class="org-comment">transforms into ==&gt;</span>
<span class="org-keyword">function</span> <span class="org-function-name">f</span>(<span class="org-variable-name">a</span>, <span class="org-variable-name">b</span>, <span class="org-variable-name">c</span>) {
<span class="org-keyword">var</span> <span class="org-variable-name">i</span>, <span class="org-variable-name">boo</span>, <span class="org-variable-name">w</span> = 10, <span class="org-variable-name">q</span> = 20;
<span class="org-keyword">for</span> (i = 1; i &lt; 10; ++i) {
boo = foo(a);
}
<span class="org-keyword">for</span> (i = 0; i &lt; 1; ++i) {
boo = bar(c);
}
<span class="org-keyword">function</span> <span class="org-function-name">foo</span>() { ... }
<span class="org-keyword">function</span> <span class="org-function-name">bar</span>() { ... }
}
</pre>
<ul>
<li><code>pro.ast_mangle(ast, options)</code> &ndash; generates a new AST containing mangled
(compressed) variable and function names. It supports the following
options:
<ul>
<li><code>toplevel</code> &ndash; mangle toplevel names (by default we don't touch them).
</li>
<li><code>except</code> &ndash; an array of names to exclude from compression.
</li>
<li><code>defines</code> &ndash; an object with properties named after symbols to
replace (see the <code>--define</code> option for the script) and the values
representing the AST replacement value.
</li>
</ul>
</li>
<li><code>pro.ast_squeeze(ast, options)</code> &ndash; employs further optimizations designed
to reduce the size of the code that <code>gen_code</code> would generate from the
AST. Returns a new AST. <code>options</code> can be a hash; the supported options
are:
<ul>
<li><code>make_seqs</code> (default true) which will cause consecutive statements in a
block to be merged using the "sequence" (comma) operator
</li>
<li><code>dead_code</code> (default true) which will remove unreachable code.
</li>
</ul>
</li>
<li><code>pro.gen_code(ast, options)</code> &ndash; generates JS code from the AST. By
default it's minified, but using the <code>options</code> argument you can get nicely
formatted output. <code>options</code> is, well, optional :-) and if you pass it it
must be an object and supports the following properties (below you can see
the default values):
<ul>
<li><code>beautify: false</code> &ndash; pass <code>true</code> if you want indented output
</li>
<li><code>indent_start: 0</code> (only applies when <code>beautify</code> is <code>true</code>) &ndash; initial
indentation in spaces
</li>
<li><code>indent_level: 4</code> (only applies when <code>beautify</code> is <code>true</code>) --
indentation level, in spaces (pass an even number)
</li>
<li><code>quote_keys: false</code> &ndash; if you pass <code>true</code> it will quote all keys in
literal objects
</li>
<li><code>space_colon: false</code> (only applies when <code>beautify</code> is <code>true</code>) &ndash; wether
to put a space before the colon in object literals
</li>
<li><code>ascii_only: false</code> &ndash; pass <code>true</code> if you want to encode non-ASCII
characters as <code>\uXXXX</code>.
</li>
<li><code>inline_script: false</code> &ndash; pass <code>true</code> to escape occurrences of
<code>&lt;/script</code> in strings
</li>
</ul>
</li>
</ul>
</div>
</div>
<div id="outline-container-1-4-2" class="outline-4">
<h4 id="sec-1-4-2"><span class="section-number-4">1.4.2</span> Beautifier shortcoming &ndash; no more comments </h4>
<div class="outline-text-4" id="text-1-4-2">
<p>
The beautifier can be used as a general purpose indentation tool. It's
useful when you want to make a minified file readable. One limitation,
though, is that it discards all comments, so you don't really want to use it
to reformat your code, unless you don't have, or don't care about, comments.
</p>
<p>
In fact it's not the beautifier who discards comments &mdash; they are dumped at
the parsing stage, when we build the initial AST. Comments don't really
make sense in the AST, and while we could add nodes for them, it would be
inconvenient because we'd have to add special rules to ignore them at all
the processing stages.
</p>
</div>
</div>
<div id="outline-container-1-4-3" class="outline-4">
<h4 id="sec-1-4-3"><span class="section-number-4">1.4.3</span> Use as a code pre-processor </h4>
<div class="outline-text-4" id="text-1-4-3">
<p>
The <code>--define</code> option can be used, particularly when combined with the
constant folding logic, as a form of pre-processor to enable or remove
particular constructions, such as might be used for instrumenting
development code, or to produce variations aimed at a specific
platform.
</p>
<p>
The code below illustrates the way this can be done, and how the
symbol replacement is performed.
</p>
<pre class="src src-js">CLAUSE1: <span class="org-keyword">if</span> (<span class="org-keyword">typeof</span> DEVMODE === <span class="org-string">'undefined'</span>) {
DEVMODE = <span class="org-constant">true</span>;
}
<span class="org-function-name">CLAUSE2</span>: <span class="org-keyword">function</span> init() {
<span class="org-keyword">if</span> (DEVMODE) {
console.log(<span class="org-string">"init() called"</span>);
}
....
DEVMODE &amp;amp;&amp;amp; console.log(<span class="org-string">"init() complete"</span>);
}
<span class="org-function-name">CLAUSE3</span>: <span class="org-keyword">function</span> reportDeviceStatus(<span class="org-variable-name">device</span>) {
<span class="org-keyword">var</span> <span class="org-variable-name">DEVMODE</span> = device.mode, <span class="org-variable-name">DEVNAME</span> = device.name;
<span class="org-keyword">if</span> (DEVMODE === <span class="org-string">'open'</span>) {
....
}
}
</pre>
<p>
When the above code is normally executed, the undeclared global
variable <code>DEVMODE</code> will be assigned the value <b>true</b> (see <code>CLAUSE1</code>)
and so the <code>init()</code> function (<code>CLAUSE2</code>) will write messages to the
console log when executed, but in <code>CLAUSE3</code> a locally declared
variable will mask access to the <code>DEVMODE</code> global symbol.
</p>
<p>
If the above code is processed by UglifyJS with an argument of
<code>--define DEVMODE=false</code> then UglifyJS will replace <code>DEVMODE</code> with the
boolean constant value <b>false</b> within <code>CLAUSE1</code> and <code>CLAUSE2</code>, but it
will leave <code>CLAUSE3</code> as it stands because there <code>DEVMODE</code> resolves to
a validly declared variable.
</p>
<p>
And more so, the constant-folding features of UglifyJS will recognise
that the <code>if</code> condition of <code>CLAUSE1</code> is thus always false, and so will
remove the test and body of <code>CLAUSE1</code> altogether (including the
otherwise slightly problematical statement <code>false = true;</code> which it
will have formed by replacing <code>DEVMODE</code> in the body). Similarly,
within <code>CLAUSE2</code> both calls to <code>console.log()</code> will be removed
altogether.
</p>
<p>
In this way you can mimic, to a limited degree, the functionality of
the C/C++ pre-processor to enable or completely remove blocks
depending on how certain symbols are defined - perhaps using UglifyJS
to generate different versions of source aimed at different
environments
</p>
<p>
It is recommmended (but not made mandatory) that symbols designed for
this purpose are given names consisting of <code>UPPER_CASE_LETTERS</code> to
distinguish them from other (normal) symbols and avoid the sort of
clash that <code>CLAUSE3</code> above illustrates.
</p>
</div>
</div>
</div>
<div id="outline-container-1-5" class="outline-3">
<h3 id="sec-1-5"><span class="section-number-3">1.5</span> Compression &ndash; how good is it? </h3>
<div class="outline-text-3" id="text-1-5">
<p>
Here are updated statistics. (I also updated my Google Closure and YUI
installations).
</p>
<p>
We're still a lot better than YUI in terms of compression, though slightly
slower. We're still a lot faster than Closure, and compression after gzip
is comparable.
</p>
<table border="2" cellspacing="0" cellpadding="6" rules="groups" frame="hsides">
<caption></caption>
<colgroup><col class="left" /><col class="left" /><col class="right" /><col class="left" /><col class="right" /><col class="left" /><col class="right" />
</colgroup>
<thead>
<tr><th scope="col" class="left">File</th><th scope="col" class="left">UglifyJS</th><th scope="col" class="right">UglifyJS+gzip</th><th scope="col" class="left">Closure</th><th scope="col" class="right">Closure+gzip</th><th scope="col" class="left">YUI</th><th scope="col" class="right">YUI+gzip</th></tr>
</thead>
<tbody>
<tr><td class="left">jquery-1.6.2.js</td><td class="left">91001 (0:01.59)</td><td class="right">31896</td><td class="left">90678 (0:07.40)</td><td class="right">31979</td><td class="left">101527 (0:01.82)</td><td class="right">34646</td></tr>
<tr><td class="left">paper.js</td><td class="left">142023 (0:01.65)</td><td class="right">43334</td><td class="left">134301 (0:07.42)</td><td class="right">42495</td><td class="left">173383 (0:01.58)</td><td class="right">48785</td></tr>
<tr><td class="left">prototype.js</td><td class="left">88544 (0:01.09)</td><td class="right">26680</td><td class="left">86955 (0:06.97)</td><td class="right">26326</td><td class="left">92130 (0:00.79)</td><td class="right">28624</td></tr>
<tr><td class="left">thelib-full.js (DynarchLIB)</td><td class="left">251939 (0:02.55)</td><td class="right">72535</td><td class="left">249911 (0:09.05)</td><td class="right">72696</td><td class="left">258869 (0:01.94)</td><td class="right">76584</td></tr>
</tbody>
</table>
</div>
</div>
<div id="outline-container-1-6" class="outline-3">
<h3 id="sec-1-6"><span class="section-number-3">1.6</span> Bugs? </h3>
<div class="outline-text-3" id="text-1-6">
<p>
Unfortunately, for the time being there is no automated test suite. But I
ran the compressor manually on non-trivial code, and then I tested that the
generated code works as expected. A few hundred times.
</p>
<p>
DynarchLIB was started in times when there was no good JS minifier.
Therefore I was quite religious about trying to write short code manually,
and as such DL contains a lot of syntactic hacks<sup><a class="footref" name="fnr.1" href="#fn.1">1</a></sup> such as “foo == bar ? a
= 10 : b = 20”, though the more readable version would clearly be to use
“if/else”.
</p>
<p>
Since the parser/compressor runs fine on DL and jQuery, I'm quite confident
that it's solid enough for production use. If you can identify any bugs,
I'd love to hear about them (<a href="http://groups.google.com/group/uglifyjs">use the Google Group</a> or email me directly).
</p>
</div>
</div>
<div id="outline-container-1-7" class="outline-3">
<h3 id="sec-1-7"><span class="section-number-3">1.7</span> Links </h3>
<div class="outline-text-3" id="text-1-7">
<ul>
<li>Twitter: <a href="http://twitter.com/UglifyJS">@UglifyJS</a>
</li>
<li>Project at GitHub: <a href="http://github.com/mishoo/UglifyJS">http://github.com/mishoo/UglifyJS</a>
</li>
<li>Google Group: <a href="http://groups.google.com/group/uglifyjs">http://groups.google.com/group/uglifyjs</a>
</li>
<li>Common Lisp JS parser: <a href="http://marijn.haverbeke.nl/parse-js/">http://marijn.haverbeke.nl/parse-js/</a>
</li>
<li>JS-to-Lisp compiler: <a href="http://github.com/marijnh/js">http://github.com/marijnh/js</a>
</li>
<li>Common Lisp JS uglifier: <a href="http://github.com/mishoo/cl-uglify-js">http://github.com/mishoo/cl-uglify-js</a>
</li>
</ul>
</div>
</div>
<div id="outline-container-1-8" class="outline-3">
<h3 id="sec-1-8"><span class="section-number-3">1.8</span> License </h3>
<div class="outline-text-3" id="text-1-8">
<p>
UglifyJS is released under the BSD license:
</p>
<pre class="example">Copyright 2010 (c) Mihai Bazon &lt;mihai.bazon@gmail.com&gt;
Based on parse-js (http://marijn.haverbeke.nl/parse-js/).
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
* Redistributions of source code must retain the above
copyright notice, this list of conditions and the following
disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials
provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “AS IS” AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
</pre>
<div id="footnotes">
<h2 class="footnotes">Footnotes: </h2>
<div id="text-footnotes">
<p class="footnote"><sup><a class="footnum" name="fn.1" href="#fnr.1">1</a></sup> I even reported a few bugs and suggested some fixes in the original
<a href="http://marijn.haverbeke.nl/parse-js/">parse-js</a> library, and Marijn pushed fixes literally in minutes.
</p></div>
</div>
</div>
</div>
</div>
</div>
<div id="postamble">
<p class="date">Date: 2011-12-09 14:59:08 EET</p>
<p class="author">Author: Mihai Bazon</p>
<p class="creator">Org version 7.7 with Emacs version 23</p>
<a href="http://validator.w3.org/check?uri=referer">Validate XHTML 1.0</a>
</div>
</body>
</html>

View File

@@ -0,0 +1,588 @@
UglifyJS 2
==========
[![Build Status](https://travis-ci.org/mishoo/UglifyJS2.png)](https://travis-ci.org/mishoo/UglifyJS2)
UglifyJS is a JavaScript parser, minifier, compressor or beautifier toolkit.
This page documents the command line utility. For
[API and internals documentation see my website](http://lisperator.net/uglifyjs/).
There's also an
[in-browser online demo](http://lisperator.net/uglifyjs/#demo) (for Firefox,
Chrome and probably Safari).
Install
-------
First make sure you have installed the latest version of [node.js](http://nodejs.org/)
(You may need to restart your computer after this step).
From NPM for use as a command line app:
npm install uglify-js -g
From NPM for programmatic use:
npm install uglify-js
From Git:
git clone git://github.com/mishoo/UglifyJS2.git
cd UglifyJS2
npm link .
Usage
-----
uglifyjs [input files] [options]
UglifyJS2 can take multiple input files. It's recommended that you pass the
input files first, then pass the options. UglifyJS will parse input files
in sequence and apply any compression options. The files are parsed in the
same global scope, that is, a reference from a file to some
variable/function declared in another file will be matched properly.
If you want to read from STDIN instead, pass a single dash instead of input
files.
The available options are:
--source-map Specify an output file where to generate source map.
[string]
--source-map-root The path to the original source to be included in the
source map. [string]
--source-map-url The path to the source map to be added in //@
sourceMappingURL. Defaults to the value passed with
--source-map. [string]
--in-source-map Input source map, useful if you're compressing JS that was
generated from some other original code.
--screw-ie8 Pass this flag if you don't care about full compliance with
Internet Explorer 6-8 quirks (by default UglifyJS will try
to be IE-proof).
-p, --prefix Skip prefix for original filenames that appear in source
maps. For example -p 3 will drop 3 directories from file
names and ensure they are relative paths.
-o, --output Output file (default STDOUT).
-b, --beautify Beautify output/specify output options. [string]
-m, --mangle Mangle names/pass mangler options. [string]
-r, --reserved Reserved names to exclude from mangling.
-c, --compress Enable compressor/pass compressor options. Pass options
like -c hoist_vars=false,if_return=false. Use -c with no
argument to use the default compression options. [string]
-d, --define Global definitions [string]
--comments Preserve copyright comments in the output. By default this
works like Google Closure, keeping JSDoc-style comments
that contain "@license" or "@preserve". You can optionally
pass one of the following arguments to this flag:
- "all" to keep all comments
- a valid JS regexp (needs to start with a slash) to keep
only comments that match.
Note that currently not *all* comments can be kept when
compression is on, because of dead code removal or
cascading statements into sequences. [string]
--stats Display operations run time on STDERR. [boolean]
--acorn Use Acorn for parsing. [boolean]
--spidermonkey Assume input files are SpiderMonkey AST format (as JSON).
[boolean]
--self Build itself (UglifyJS2) as a library (implies
--wrap=UglifyJS --export-all) [boolean]
--wrap Embed everything in a big function, making the “exports”
and “global” variables available. You need to pass an
argument to this option to specify the name that your
module will take when included in, say, a browser.
[string]
--export-all Only used when --wrap, this tells UglifyJS to add code to
automatically export all globals. [boolean]
--lint Display some scope warnings [boolean]
-v, --verbose Verbose [boolean]
-V, --version Print version number and exit. [boolean]
Specify `--output` (`-o`) to declare the output file. Otherwise the output
goes to STDOUT.
## Source map options
UglifyJS2 can generate a source map file, which is highly useful for
debugging your compressed JavaScript. To get a source map, pass
`--source-map output.js.map` (full path to the file where you want the
source map dumped).
Additionally you might need `--source-map-root` to pass the URL where the
original files can be found. In case you are passing full paths to input
files to UglifyJS, you can use `--prefix` (`-p`) to specify the number of
directories to drop from the path prefix when declaring files in the source
map.
For example:
uglifyjs /home/doe/work/foo/src/js/file1.js \
/home/doe/work/foo/src/js/file2.js \
-o foo.min.js \
--source-map foo.min.js.map \
--source-map-root http://foo.com/src \
-p 5 -c -m
The above will compress and mangle `file1.js` and `file2.js`, will drop the
output in `foo.min.js` and the source map in `foo.min.js.map`. The source
mapping will refer to `http://foo.com/src/js/file1.js` and
`http://foo.com/src/js/file2.js` (in fact it will list `http://foo.com/src`
as the source map root, and the original files as `js/file1.js` and
`js/file2.js`).
### Composed source map
When you're compressing JS code that was output by a compiler such as
CoffeeScript, mapping to the JS code won't be too helpful. Instead, you'd
like to map back to the original code (i.e. CoffeeScript). UglifyJS has an
option to take an input source map. Assuming you have a mapping from
CoffeeScript → compiled JS, UglifyJS can generate a map from CoffeeScript →
compressed JS by mapping every token in the compiled JS to its original
location.
To use this feature you need to pass `--in-source-map
/path/to/input/source.map`. Normally the input source map should also point
to the file containing the generated JS, so if that's correct you can omit
input files from the command line.
## Mangler options
To enable the mangler you need to pass `--mangle` (`-m`). The following
(comma-separated) options are supported:
- `sort` — to assign shorter names to most frequently used variables. This
saves a few hundred bytes on jQuery before gzip, but the output is
_bigger_ after gzip (and seems to happen for other libraries I tried it
on) therefore it's not enabled by default.
- `toplevel` — mangle names declared in the toplevel scope (disabled by
default).
- `eval` — mangle names visible in scopes where `eval` or `when` are used
(disabled by default).
When mangling is enabled but you want to prevent certain names from being
mangled, you can declare those names with `--reserved` (`-r`) — pass a
comma-separated list of names. For example:
uglifyjs ... -m -r '$,require,exports'
to prevent the `require`, `exports` and `$` names from being changed.
## Compressor options
You need to pass `--compress` (`-c`) to enable the compressor. Optionally
you can pass a comma-separated list of options. Options are in the form
`foo=bar`, or just `foo` (the latter implies a boolean option that you want
to set `true`; it's effectively a shortcut for `foo=true`).
- `sequences` -- join consecutive simple statements using the comma operator
- `properties` -- rewrite property access using the dot notation, for
example `foo["bar"] → foo.bar`
- `dead_code` -- remove unreachable code
- `drop_debugger` -- remove `debugger;` statements
- `unsafe` (default: false) -- apply "unsafe" transformations (discussion below)
- `conditionals` -- apply optimizations for `if`-s and conditional
expressions
- `comparisons` -- apply certain optimizations to binary nodes, for example:
`!(a <= b) → a > b` (only when `unsafe`), attempts to negate binary nodes,
e.g. `a = !b && !c && !d && !e → a=!(b||c||d||e)` etc.
- `evaluate` -- attempt to evaluate constant expressions
- `booleans` -- various optimizations for boolean context, for example `!!a
? b : c → a ? b : c`
- `loops` -- optimizations for `do`, `while` and `for` loops when we can
statically determine the condition
- `unused` -- drop unreferenced functions and variables
- `hoist_funs` -- hoist function declarations
- `hoist_vars` (default: false) -- hoist `var` declarations (this is `false`
by default because it seems to increase the size of the output in general)
- `if_return` -- optimizations for if/return and if/continue
- `join_vars` -- join consecutive `var` statements
- `cascade` -- small optimization for sequences, transform `x, x` into `x`
and `x = something(), x` into `x = something()`
- `warnings` -- display warnings when dropping unreachable code or unused
declarations etc.
### The `unsafe` option
It enables some transformations that *might* break code logic in certain
contrived cases, but should be fine for most code. You might want to try it
on your own code, it should reduce the minified size. Here's what happens
when this flag is on:
- `new Array(1, 2, 3)` or `Array(1, 2, 3)` → `[1, 2, 3 ]`
- `new Object()` → `{}`
- `String(exp)` or `exp.toString()` → `"" + exp`
- `new Object/RegExp/Function/Error/Array (...)` → we discard the `new`
- `typeof foo == "undefined"` → `foo === void 0`
- `void 0` → `"undefined"` (if there is a variable named "undefined" in
scope; we do it because the variable name will be mangled, typically
reduced to a single character).
### Conditional compilation
You can use the `--define` (`-d`) switch in order to declare global
variables that UglifyJS will assume to be constants (unless defined in
scope). For example if you pass `--define DEBUG=false` then, coupled with
dead code removal UglifyJS will discard the following from the output:
```javascript
if (DEBUG) {
console.log("debug stuff");
}
```
UglifyJS will warn about the condition being always false and about dropping
unreachable code; for now there is no option to turn off only this specific
warning, you can pass `warnings=false` to turn off *all* warnings.
Another way of doing that is to declare your globals as constants in a
separate file and include it into the build. For example you can have a
`build/defines.js` file with the following:
```javascript
const DEBUG = false;
const PRODUCTION = true;
// etc.
```
and build your code like this:
uglifyjs build/defines.js js/foo.js js/bar.js... -c
UglifyJS will notice the constants and, since they cannot be altered, it
will evaluate references to them to the value itself and drop unreachable
code as usual. The possible downside of this approach is that the build
will contain the `const` declarations.
<a name="codegen-options"></a>
## Beautifier options
The code generator tries to output shortest code possible by default. In
case you want beautified output, pass `--beautify` (`-b`). Optionally you
can pass additional arguments that control the code output:
- `beautify` (default `true`) -- whether to actually beautify the output.
Passing `-b` will set this to true, but you might need to pass `-b` even
when you want to generate minified code, in order to specify additional
arguments, so you can use `-b beautify=false` to override it.
- `indent-level` (default 4)
- `indent-start` (default 0) -- prefix all lines by that many spaces
- `quote-keys` (default `false`) -- pass `true` to quote all keys in literal
objects
- `space-colon` (default `true`) -- insert a space after the colon signs
- `ascii-only` (default `false`) -- escape Unicode characters in strings and
regexps
- `inline-script` (default `false`) -- escape the slash in occurrences of
`</script` in strings
- `width` (default 80) -- only takes effect when beautification is on, this
specifies an (orientative) line width that the beautifier will try to
obey. It refers to the width of the line text (excluding indentation).
It doesn't work very well currently, but it does make the code generated
by UglifyJS more readable.
- `max-line-len` (default 32000) -- maximum line length (for uglified code)
- `ie-proof` (default `true`) -- generate “IE-proof” code (for now this
means add brackets around the do/while in code like this: `if (foo) do
something(); while (bar); else ...`.
- `bracketize` (default `false`) -- always insert brackets in `if`, `for`,
`do`, `while` or `with` statements, even if their body is a single
statement.
- `semicolons` (default `true`) -- separate statements with semicolons. If
you pass `false` then whenever possible we will use a newline instead of a
semicolon, leading to more readable output of uglified code (size before
gzip could be smaller; size after gzip insignificantly larger).
- `negate-iife` (default `!beautify`) -- prefer negation, rather than
parens, for "Immediately-Called Function Expressions". This defaults to
`true` when beautification is off, and `false` if beautification is on;
pass it manually to force a value.
### Keeping copyright notices or other comments
You can pass `--comments` to retain certain comments in the output. By
default it will keep JSDoc-style comments that contain "@preserve",
"@license" or "@cc_on" (conditional compilation for IE). You can pass
`--comments all` to keep all the comments, or a valid JavaScript regexp to
keep only comments that match this regexp. For example `--comments
'/foo|bar/'` will keep only comments that contain "foo" or "bar".
Note, however, that there might be situations where comments are lost. For
example:
```javascript
function f() {
/** @preserve Foo Bar */
function g() {
// this function is never called
}
return something();
}
```
Even though it has "@preserve", the comment will be lost because the inner
function `g` (which is the AST node to which the comment is attached to) is
discarded by the compressor as not referenced.
The safest comments where to place copyright information (or other info that
needs to be kept in the output) are comments attached to toplevel nodes.
## Support for the SpiderMonkey AST
UglifyJS2 has its own abstract syntax tree format; for
[practical reasons](http://lisperator.net/blog/uglifyjs-why-not-switching-to-spidermonkey-ast/)
we can't easily change to using the SpiderMonkey AST internally. However,
UglifyJS now has a converter which can import a SpiderMonkey AST.
For example [Acorn][acorn] is a super-fast parser that produces a
SpiderMonkey AST. It has a small CLI utility that parses one file and dumps
the AST in JSON on the standard output. To use UglifyJS to mangle and
compress that:
acorn file.js | uglifyjs --spidermonkey -m -c
The `--spidermonkey` option tells UglifyJS that all input files are not
JavaScript, but JS code described in SpiderMonkey AST in JSON. Therefore we
don't use our own parser in this case, but just transform that AST into our
internal AST.
### Use Acorn for parsing
More for fun, I added the `--acorn` option which will use Acorn to do all
the parsing. If you pass this option, UglifyJS will `require("acorn")`.
Acorn is really fast (e.g. 250ms instead of 380ms on some 650K code), but
converting the SpiderMonkey tree that Acorn produces takes another 150ms so
in total it's a bit more than just using UglifyJS's own parser.
API Reference
-------------
Assuming installation via NPM, you can load UglifyJS in your application
like this:
```javascript
var UglifyJS = require("uglify-js");
```
It exports a lot of names, but I'll discuss here the basics that are needed
for parsing, mangling and compressing a piece of code. The sequence is (1)
parse, (2) compress, (3) mangle, (4) generate output code.
### The simple way
There's a single toplevel function which combines all the steps. If you
don't need additional customization, you might want to go with `minify`.
Example:
```javascript
var result = UglifyJS.minify("/path/to/file.js");
console.log(result.code); // minified output
// if you need to pass code instead of file name
var result = UglifyJS.minify("var b = function () {};", {fromString: true});
```
You can also compress multiple files:
```javascript
var result = UglifyJS.minify([ "file1.js", "file2.js", "file3.js" ]);
console.log(result.code);
```
To generate a source map:
```javascript
var result = UglifyJS.minify([ "file1.js", "file2.js", "file3.js" ], {
outSourceMap: "out.js.map"
});
console.log(result.code); // minified output
console.log(result.map);
```
Note that the source map is not saved in a file, it's just returned in
`result.map`. The value passed for `outSourceMap` is only used to set the
`file` attribute in the source map (see [the spec][sm-spec]).
You can also specify sourceRoot property to be included in source map:
```javascript
var result = UglifyJS.minify([ "file1.js", "file2.js", "file3.js" ], {
outSourceMap: "out.js.map",
sourceRoot: "http://example.com/src"
});
```
If you're compressing compiled JavaScript and have a source map for it, you
can use the `inSourceMap` argument:
```javascript
var result = UglifyJS.minify("compiled.js", {
inSourceMap: "compiled.js.map",
outSourceMap: "minified.js.map"
});
// same as before, it returns `code` and `map`
```
The `inSourceMap` is only used if you also request `outSourceMap` (it makes
no sense otherwise).
Other options:
- `warnings` (default `false`) — pass `true` to display compressor warnings.
- `fromString` (default `false`) — if you pass `true` then you can pass
JavaScript source code, rather than file names.
- `mangle` — pass `false` to skip mangling names.
- `output` (default `null`) — pass an object if you wish to specify
additional [output options][codegen]. The defaults are optimized
for best compression.
- `compress` (default `{}`) — pass `false` to skip compressing entirely.
Pass an object to specify custom [compressor options][compressor].
We could add more options to `UglifyJS.minify` — if you need additional
functionality please suggest!
### The hard way
Following there's more detailed API info, in case the `minify` function is
too simple for your needs.
#### The parser
```javascript
var toplevel_ast = UglifyJS.parse(code, options);
```
`options` is optional and if present it must be an object. The following
properties are available:
- `strict` — disable automatic semicolon insertion and support for trailing
comma in arrays and objects
- `filename` — the name of the file where this code is coming from
- `toplevel` — a `toplevel` node (as returned by a previous invocation of
`parse`)
The last two options are useful when you'd like to minify multiple files and
get a single file as the output and a proper source map. Our CLI tool does
something like this:
```javascript
var toplevel = null;
files.forEach(function(file){
var code = fs.readFileSync(file);
toplevel = UglifyJS.parse(code, {
filename: file,
toplevel: toplevel
});
});
```
After this, we have in `toplevel` a big AST containing all our files, with
each token having proper information about where it came from.
#### Scope information
UglifyJS contains a scope analyzer that you need to call manually before
compressing or mangling. Basically it augments various nodes in the AST
with information about where is a name defined, how many times is a name
referenced, if it is a global or not, if a function is using `eval` or the
`with` statement etc. I will discuss this some place else, for now what's
important to know is that you need to call the following before doing
anything with the tree:
```javascript
toplevel.figure_out_scope()
```
#### Compression
Like this:
```javascript
var compressor = UglifyJS.Compressor(options);
var compressed_ast = toplevel.transform(compressor);
```
The `options` can be missing. Available options are discussed above in
“Compressor options”. Defaults should lead to best compression in most
scripts.
The compressor is destructive, so don't rely that `toplevel` remains the
original tree.
#### Mangling
After compression it is a good idea to call again `figure_out_scope` (since
the compressor might drop unused variables / unreachable code and this might
change the number of identifiers or their position). Optionally, you can
call a trick that helps after Gzip (counting character frequency in
non-mangleable words). Example:
```javascript
compressed_ast.figure_out_scope();
compressed_ast.compute_char_frequency();
compressed_ast.mangle_names();
```
#### Generating output
AST nodes have a `print` method that takes an output stream. Essentially,
to generate code you do this:
```javascript
var stream = UglifyJS.OutputStream(options);
compressed_ast.print(stream);
var code = stream.toString(); // this is your minified code
```
or, for a shortcut you can do:
```javascript
var code = compressed_ast.print_to_string(options);
```
As usual, `options` is optional. The output stream accepts a lot of otions,
most of them documented above in section “Beautifier options”. The two
which we care about here are `source_map` and `comments`.
#### Keeping comments in the output
In order to keep certain comments in the output you need to pass the
`comments` option. Pass a RegExp or a function. If you pass a RegExp, only
those comments whose body matches the regexp will be kept. Note that body
means without the initial `//` or `/*`. If you pass a function, it will be
called for every comment in the tree and will receive two arguments: the
node that the comment is attached to, and the comment token itself.
The comment token has these properties:
- `type`: "comment1" for single-line comments or "comment2" for multi-line
comments
- `value`: the comment body
- `pos` and `endpos`: the start/end positions (zero-based indexes) in the
original code where this comment appears
- `line` and `col`: the line and column where this comment appears in the
original code
- `file` — the file name of the original file
- `nlb` — true if there was a newline before this comment in the original
code, or if this comment contains a newline.
Your function should return `true` to keep the comment, or a falsy value
otherwise.
#### Generating a source mapping
You need to pass the `source_map` argument when calling `print`. It needs
to be a `SourceMap` object (which is a thin wrapper on top of the
[source-map][source-map] library).
Example:
```javascript
var source_map = UglifyJS.SourceMap(source_map_options);
var stream = UglifyJS.OutputStream({
...
source_map: source_map
});
compressed_ast.print(stream);
var code = stream.toString();
var map = source_map.toString(); // json output for your source map
```
The `source_map_options` (optional) can contain the following properties:
- `file`: the name of the JavaScript output file that this mapping refers to
- `root`: the `sourceRoot` property (see the [spec][sm-spec])
- `orig`: the "original source map", handy when you compress generated JS
and want to map the minified output back to the original code where it
came from. It can be simply a string in JSON, or a JSON object containing
the original source map.
[acorn]: https://github.com/marijnh/acorn
[source-map]: https://github.com/mozilla/source-map
[sm-spec]: https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit
[codegen]: http://lisperator.net/uglifyjs/codegen
[compressor]: http://lisperator.net/uglifyjs/compress

View File

@@ -1,578 +0,0 @@
#+TITLE: UglifyJS -- a JavaScript parser/compressor/beautifier
#+KEYWORDS: javascript, js, parser, compiler, compressor, mangle, minify, minifier
#+DESCRIPTION: a JavaScript parser/compressor/beautifier in JavaScript
#+STYLE: <link rel="stylesheet" type="text/css" href="docstyle.css" />
#+AUTHOR: Mihai Bazon
#+EMAIL: mihai.bazon@gmail.com
* UglifyJS --- a JavaScript parser/compressor/beautifier
This package implements a general-purpose JavaScript
parser/compressor/beautifier toolkit. It is developed on [[http://nodejs.org/][NodeJS]], but it
should work on any JavaScript platform supporting the CommonJS module system
(and if your platform of choice doesn't support CommonJS, you can easily
implement it, or discard the =exports.*= lines from UglifyJS sources).
The tokenizer/parser generates an abstract syntax tree from JS code. You
can then traverse the AST to learn more about the code, or do various
manipulations on it. This part is implemented in [[../lib/parse-js.js][parse-js.js]] and it's a
port to JavaScript of the excellent [[http://marijn.haverbeke.nl/parse-js/][parse-js]] Common Lisp library from [[http://marijn.haverbeke.nl/][Marijn
Haverbeke]].
( See [[http://github.com/mishoo/cl-uglify-js][cl-uglify-js]] if you're looking for the Common Lisp version of
UglifyJS. )
The second part of this package, implemented in [[../lib/process.js][process.js]], inspects and
manipulates the AST generated by the parser to provide the following:
- ability to re-generate JavaScript code from the AST. Optionally
indented---you can use this if you want to “beautify” a program that has
been compressed, so that you can inspect the source. But you can also run
our code generator to print out an AST without any whitespace, so you
achieve compression as well.
- shorten variable names (usually to single characters). Our mangler will
analyze the code and generate proper variable names, depending on scope
and usage, and is smart enough to deal with globals defined elsewhere, or
with =eval()= calls or =with{}= statements. In short, if =eval()= or
=with{}= are used in some scope, then all variables in that scope and any
variables in the parent scopes will remain unmangled, and any references
to such variables remain unmangled as well.
- various small optimizations that may lead to faster code but certainly
lead to smaller code. Where possible, we do the following:
- foo["bar"] ==> foo.bar
- remove block brackets ={}=
- join consecutive var declarations:
var a = 10; var b = 20; ==> var a=10,b=20;
- resolve simple constant expressions: 1 +2 * 3 ==> 7. We only do the
replacement if the result occupies less bytes; for example 1/3 would
translate to 0.333333333333, so in this case we don't replace it.
- consecutive statements in blocks are merged into a sequence; in many
cases, this leaves blocks with a single statement, so then we can remove
the block brackets.
- various optimizations for IF statements:
- if (foo) bar(); else baz(); ==> foo?bar():baz();
- if (!foo) bar(); else baz(); ==> foo?baz():bar();
- if (foo) bar(); ==> foo&&bar();
- if (!foo) bar(); ==> foo||bar();
- if (foo) return bar(); else return baz(); ==> return foo?bar():baz();
- if (foo) return bar(); else something(); ==> {if(foo)return bar();something()}
- remove some unreachable code and warn about it (code that follows a
=return=, =throw=, =break= or =continue= statement, except
function/variable declarations).
- act a limited version of a pre-processor (c.f. the pre-processor of
C/C++) to allow you to safely replace selected global symbols with
specified values. When combined with the optimisations above this can
make UglifyJS operate slightly more like a compilation process, in
that when certain symbols are replaced by constant values, entire code
blocks may be optimised away as unreachable.
** <<Unsafe transformations>>
The following transformations can in theory break code, although they're
probably safe in most practical cases. To enable them you need to pass the
=--unsafe= flag.
*** Calls involving the global Array constructor
The following transformations occur:
#+BEGIN_SRC js
new Array(1, 2, 3, 4) => [1,2,3,4]
Array(a, b, c) => [a,b,c]
new Array(5) => Array(5)
new Array(a) => Array(a)
#+END_SRC
These are all safe if the Array name isn't redefined. JavaScript does allow
one to globally redefine Array (and pretty much everything, in fact) but I
personally don't see why would anyone do that.
UglifyJS does handle the case where Array is redefined locally, or even
globally but with a =function= or =var= declaration. Therefore, in the
following cases UglifyJS *doesn't touch* calls or instantiations of Array:
#+BEGIN_SRC js
// case 1. globally declared variable
var Array;
new Array(1, 2, 3);
Array(a, b);
// or (can be declared later)
new Array(1, 2, 3);
var Array;
// or (can be a function)
new Array(1, 2, 3);
function Array() { ... }
// case 2. declared in a function
(function(){
a = new Array(1, 2, 3);
b = Array(5, 6);
var Array;
})();
// or
(function(Array){
return Array(5, 6, 7);
})();
// or
(function(){
return new Array(1, 2, 3, 4);
function Array() { ... }
})();
// etc.
#+END_SRC
*** =obj.toString()= ==> =obj+“”=
** Install (NPM)
UglifyJS is now available through NPM --- =npm install uglify-js= should do
the job.
** Install latest code from GitHub
#+BEGIN_SRC sh
## clone the repository
mkdir -p /where/you/wanna/put/it
cd /where/you/wanna/put/it
git clone git://github.com/mishoo/UglifyJS.git
## make the module available to Node
mkdir -p ~/.node_libraries/
cd ~/.node_libraries/
ln -s /where/you/wanna/put/it/UglifyJS/uglify-js.js
## and if you want the CLI script too:
mkdir -p ~/bin
cd ~/bin
ln -s /where/you/wanna/put/it/UglifyJS/bin/uglifyjs
# (then add ~/bin to your $PATH if it's not there already)
#+END_SRC
** Usage
There is a command-line tool that exposes the functionality of this library
for your shell-scripting needs:
#+BEGIN_SRC sh
uglifyjs [ options... ] [ filename ]
#+END_SRC
=filename= should be the last argument and should name the file from which
to read the JavaScript code. If you don't specify it, it will read code
from STDIN.
Supported options:
- =-b= or =--beautify= --- output indented code; when passed, additional
options control the beautifier:
- =-i N= or =--indent N= --- indentation level (number of spaces)
- =-q= or =--quote-keys= --- quote keys in literal objects (by default,
only keys that cannot be identifier names will be quotes).
- =-c= or =----consolidate-primitive-values= --- consolidates null, Boolean,
and String values. Known as aliasing in the Closure Compiler. Worsens the
data compression ratio of gzip.
- =--ascii= --- pass this argument to encode non-ASCII characters as
=\uXXXX= sequences. By default UglifyJS won't bother to do it and will
output Unicode characters instead. (the output is always encoded in UTF8,
but if you pass this option you'll only get ASCII).
- =-nm= or =--no-mangle= --- don't mangle names.
- =-nmf= or =--no-mangle-functions= -- in case you want to mangle variable
names, but not touch function names.
- =-ns= or =--no-squeeze= --- don't call =ast_squeeze()= (which does various
optimizations that result in smaller, less readable code).
- =-mt= or =--mangle-toplevel= --- mangle names in the toplevel scope too
(by default we don't do this).
- =--no-seqs= --- when =ast_squeeze()= is called (thus, unless you pass
=--no-squeeze=) it will reduce consecutive statements in blocks into a
sequence. For example, "a = 10; b = 20; foo();" will be written as
"a=10,b=20,foo();". In various occasions, this allows us to discard the
block brackets (since the block becomes a single statement). This is ON
by default because it seems safe and saves a few hundred bytes on some
libs that I tested it on, but pass =--no-seqs= to disable it.
- =--no-dead-code= --- by default, UglifyJS will remove code that is
obviously unreachable (code that follows a =return=, =throw=, =break= or
=continue= statement and is not a function/variable declaration). Pass
this option to disable this optimization.
- =-nc= or =--no-copyright= --- by default, =uglifyjs= will keep the initial
comment tokens in the generated code (assumed to be copyright information
etc.). If you pass this it will discard it.
- =-o filename= or =--output filename= --- put the result in =filename=. If
this isn't given, the result goes to standard output (or see next one).
- =--overwrite= --- if the code is read from a file (not from STDIN) and you
pass =--overwrite= then the output will be written in the same file.
- =--ast= --- pass this if you want to get the Abstract Syntax Tree instead
of JavaScript as output. Useful for debugging or learning more about the
internals.
- =-v= or =--verbose= --- output some notes on STDERR (for now just how long
each operation takes).
- =-d SYMBOL[=VALUE]= or =--define SYMBOL[=VALUE]= --- will replace
all instances of the specified symbol where used as an identifier
(except where symbol has properly declared by a var declaration or
use as function parameter or similar) with the specified value. This
argument may be specified multiple times to define multiple
symbols - if no value is specified the symbol will be replaced with
the value =true=, or you can specify a numeric value (such as
=1024=), a quoted string value (such as ="object"= or
='https://github.com'=), or the name of another symbol or keyword
(such as =null= or =document=).
This allows you, for example, to assign meaningful names to key
constant values but discard the symbolic names in the uglified
version for brevity/efficiency, or when used wth care, allows
UglifyJS to operate as a form of *conditional compilation*
whereby defining appropriate values may, by dint of the constant
folding and dead code removal features above, remove entire
superfluous code blocks (e.g. completely remove instrumentation or
trace code for production use).
Where string values are being defined, the handling of quotes are
likely to be subject to the specifics of your command shell
environment, so you may need to experiment with quoting styles
depending on your platform, or you may find the option
=--define-from-module= more suitable for use.
- =-define-from-module SOMEMODULE= --- will load the named module (as
per the NodeJS =require()= function) and iterate all the exported
properties of the module defining them as symbol names to be defined
(as if by the =--define= option) per the name of each property
(i.e. without the module name prefix) and given the value of the
property. This is a much easier way to handle and document groups of
symbols to be defined rather than a large number of =--define=
options.
- =--unsafe= --- enable other additional optimizations that are known to be
unsafe in some contrived situations, but could still be generally useful.
For now only these:
- foo.toString() ==> foo+""
- new Array(x,...) ==> [x,...]
- new Array(x) ==> Array(x)
- =--max-line-len= (default 32K characters) --- add a newline after around
32K characters. I've seen both FF and Chrome croak when all the code was
on a single line of around 670K. Pass --max-line-len 0 to disable this
safety feature.
- =--reserved-names= --- some libraries rely on certain names to be used, as
pointed out in issue #92 and #81, so this option allow you to exclude such
names from the mangler. For example, to keep names =require= and =$super=
intact you'd specify --reserved-names "require,$super".
- =--inline-script= -- when you want to include the output literally in an
HTML =<script>= tag you can use this option to prevent =</script= from
showing up in the output.
- =--lift-vars= -- when you pass this, UglifyJS will apply the following
transformations (see the notes in API, =ast_lift_variables=):
- put all =var= declarations at the start of the scope
- make sure a variable is declared only once
- discard unused function arguments
- discard unused inner (named) functions
- finally, try to merge assignments into that one =var= declaration, if
possible.
*** API
To use the library from JavaScript, you'd do the following (example for
NodeJS):
#+BEGIN_SRC js
var jsp = require("uglify-js").parser;
var pro = require("uglify-js").uglify;
var orig_code = "... JS code here";
var ast = jsp.parse(orig_code); // parse code and get the initial AST
ast = pro.ast_mangle(ast); // get a new AST with mangled names
ast = pro.ast_squeeze(ast); // get an AST with compression optimizations
var final_code = pro.gen_code(ast); // compressed code here
#+END_SRC
The above performs the full compression that is possible right now. As you
can see, there are a sequence of steps which you can apply. For example if
you want compressed output but for some reason you don't want to mangle
variable names, you would simply skip the line that calls
=pro.ast_mangle(ast)=.
Some of these functions take optional arguments. Here's a description:
- =jsp.parse(code, strict_semicolons)= -- parses JS code and returns an AST.
=strict_semicolons= is optional and defaults to =false=. If you pass
=true= then the parser will throw an error when it expects a semicolon and
it doesn't find it. For most JS code you don't want that, but it's useful
if you want to strictly sanitize your code.
- =pro.ast_lift_variables(ast)= -- merge and move =var= declarations to the
scop of the scope; discard unused function arguments or variables; discard
unused (named) inner functions. It also tries to merge assignments
following the =var= declaration into it.
If your code is very hand-optimized concerning =var= declarations, this
lifting variable declarations might actually increase size. For me it
helps out. On jQuery it adds 865 bytes (243 after gzip). YMMV. Also
note that (since it's not enabled by default) this operation isn't yet
heavily tested (please report if you find issues!).
Note that although it might increase the image size (on jQuery it gains
865 bytes, 243 after gzip) it's technically more correct: in certain
situations, dead code removal might drop variable declarations, which
would not happen if the variables are lifted in advance.
Here's an example of what it does:
#+BEGIN_SRC js
function f(a, b, c, d, e) {
var q;
var w;
w = 10;
q = 20;
for (var i = 1; i < 10; ++i) {
var boo = foo(a);
}
for (var i = 0; i < 1; ++i) {
var boo = bar(c);
}
function foo(){ ... }
function bar(){ ... }
function baz(){ ... }
}
// transforms into ==>
function f(a, b, c) {
var i, boo, w = 10, q = 20;
for (i = 1; i < 10; ++i) {
boo = foo(a);
}
for (i = 0; i < 1; ++i) {
boo = bar(c);
}
function foo() { ... }
function bar() { ... }
}
#+END_SRC
- =pro.ast_mangle(ast, options)= -- generates a new AST containing mangled
(compressed) variable and function names. It supports the following
options:
- =toplevel= -- mangle toplevel names (by default we don't touch them).
- =except= -- an array of names to exclude from compression.
- =defines= -- an object with properties named after symbols to
replace (see the =--define= option for the script) and the values
representing the AST replacement value.
- =pro.ast_squeeze(ast, options)= -- employs further optimizations designed
to reduce the size of the code that =gen_code= would generate from the
AST. Returns a new AST. =options= can be a hash; the supported options
are:
- =make_seqs= (default true) which will cause consecutive statements in a
block to be merged using the "sequence" (comma) operator
- =dead_code= (default true) which will remove unreachable code.
- =pro.gen_code(ast, options)= -- generates JS code from the AST. By
default it's minified, but using the =options= argument you can get nicely
formatted output. =options= is, well, optional :-) and if you pass it it
must be an object and supports the following properties (below you can see
the default values):
- =beautify: false= -- pass =true= if you want indented output
- =indent_start: 0= (only applies when =beautify= is =true=) -- initial
indentation in spaces
- =indent_level: 4= (only applies when =beautify= is =true=) --
indentation level, in spaces (pass an even number)
- =quote_keys: false= -- if you pass =true= it will quote all keys in
literal objects
- =space_colon: false= (only applies when =beautify= is =true=) -- wether
to put a space before the colon in object literals
- =ascii_only: false= -- pass =true= if you want to encode non-ASCII
characters as =\uXXXX=.
- =inline_script: false= -- pass =true= to escape occurrences of
=</script= in strings
*** Beautifier shortcoming -- no more comments
The beautifier can be used as a general purpose indentation tool. It's
useful when you want to make a minified file readable. One limitation,
though, is that it discards all comments, so you don't really want to use it
to reformat your code, unless you don't have, or don't care about, comments.
In fact it's not the beautifier who discards comments --- they are dumped at
the parsing stage, when we build the initial AST. Comments don't really
make sense in the AST, and while we could add nodes for them, it would be
inconvenient because we'd have to add special rules to ignore them at all
the processing stages.
*** Use as a code pre-processor
The =--define= option can be used, particularly when combined with the
constant folding logic, as a form of pre-processor to enable or remove
particular constructions, such as might be used for instrumenting
development code, or to produce variations aimed at a specific
platform.
The code below illustrates the way this can be done, and how the
symbol replacement is performed.
#+BEGIN_SRC js
CLAUSE1: if (typeof DEVMODE === 'undefined') {
DEVMODE = true;
}
CLAUSE2: function init() {
if (DEVMODE) {
console.log("init() called");
}
....
DEVMODE &amp;&amp; console.log("init() complete");
}
CLAUSE3: function reportDeviceStatus(device) {
var DEVMODE = device.mode, DEVNAME = device.name;
if (DEVMODE === 'open') {
....
}
}
#+END_SRC
When the above code is normally executed, the undeclared global
variable =DEVMODE= will be assigned the value *true* (see =CLAUSE1=)
and so the =init()= function (=CLAUSE2=) will write messages to the
console log when executed, but in =CLAUSE3= a locally declared
variable will mask access to the =DEVMODE= global symbol.
If the above code is processed by UglifyJS with an argument of
=--define DEVMODE=false= then UglifyJS will replace =DEVMODE= with the
boolean constant value *false* within =CLAUSE1= and =CLAUSE2=, but it
will leave =CLAUSE3= as it stands because there =DEVMODE= resolves to
a validly declared variable.
And more so, the constant-folding features of UglifyJS will recognise
that the =if= condition of =CLAUSE1= is thus always false, and so will
remove the test and body of =CLAUSE1= altogether (including the
otherwise slightly problematical statement =false = true;= which it
will have formed by replacing =DEVMODE= in the body). Similarly,
within =CLAUSE2= both calls to =console.log()= will be removed
altogether.
In this way you can mimic, to a limited degree, the functionality of
the C/C++ pre-processor to enable or completely remove blocks
depending on how certain symbols are defined - perhaps using UglifyJS
to generate different versions of source aimed at different
environments
It is recommmended (but not made mandatory) that symbols designed for
this purpose are given names consisting of =UPPER_CASE_LETTERS= to
distinguish them from other (normal) symbols and avoid the sort of
clash that =CLAUSE3= above illustrates.
** Compression -- how good is it?
Here are updated statistics. (I also updated my Google Closure and YUI
installations).
We're still a lot better than YUI in terms of compression, though slightly
slower. We're still a lot faster than Closure, and compression after gzip
is comparable.
| File | UglifyJS | UglifyJS+gzip | Closure | Closure+gzip | YUI | YUI+gzip |
|-----------------------------+------------------+---------------+------------------+--------------+------------------+----------|
| jquery-1.6.2.js | 91001 (0:01.59) | 31896 | 90678 (0:07.40) | 31979 | 101527 (0:01.82) | 34646 |
| paper.js | 142023 (0:01.65) | 43334 | 134301 (0:07.42) | 42495 | 173383 (0:01.58) | 48785 |
| prototype.js | 88544 (0:01.09) | 26680 | 86955 (0:06.97) | 26326 | 92130 (0:00.79) | 28624 |
| thelib-full.js (DynarchLIB) | 251939 (0:02.55) | 72535 | 249911 (0:09.05) | 72696 | 258869 (0:01.94) | 76584 |
** Bugs?
Unfortunately, for the time being there is no automated test suite. But I
ran the compressor manually on non-trivial code, and then I tested that the
generated code works as expected. A few hundred times.
DynarchLIB was started in times when there was no good JS minifier.
Therefore I was quite religious about trying to write short code manually,
and as such DL contains a lot of syntactic hacks[1] such as “foo == bar ? a
= 10 : b = 20”, though the more readable version would clearly be to use
“if/else”.
Since the parser/compressor runs fine on DL and jQuery, I'm quite confident
that it's solid enough for production use. If you can identify any bugs,
I'd love to hear about them ([[http://groups.google.com/group/uglifyjs][use the Google Group]] or email me directly).
[1] I even reported a few bugs and suggested some fixes in the original
[[http://marijn.haverbeke.nl/parse-js/][parse-js]] library, and Marijn pushed fixes literally in minutes.
** Links
- Twitter: [[http://twitter.com/UglifyJS][@UglifyJS]]
- Project at GitHub: [[http://github.com/mishoo/UglifyJS][http://github.com/mishoo/UglifyJS]]
- Google Group: [[http://groups.google.com/group/uglifyjs][http://groups.google.com/group/uglifyjs]]
- Common Lisp JS parser: [[http://marijn.haverbeke.nl/parse-js/][http://marijn.haverbeke.nl/parse-js/]]
- JS-to-Lisp compiler: [[http://github.com/marijnh/js][http://github.com/marijnh/js]]
- Common Lisp JS uglifier: [[http://github.com/mishoo/cl-uglify-js][http://github.com/mishoo/cl-uglify-js]]
** License
UglifyJS is released under the BSD license:
#+BEGIN_EXAMPLE
Copyright 2010 (c) Mihai Bazon <mihai.bazon@gmail.com>
Based on parse-js (http://marijn.haverbeke.nl/parse-js/).
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
* Redistributions of source code must retain the above
copyright notice, this list of conditions and the following
disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials
provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “AS IS” AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
#+END_EXAMPLE

View File

@@ -1,332 +1,402 @@
#!/usr/bin/env node
#! /usr/bin/env node
// -*- js -*-
global.sys = require(/^v0\.[012]/.test(process.version) ? "sys" : "util");
var fs = require("fs");
var uglify = require("uglify-js"), // symlink ~/.node_libraries/uglify-js.js to ../uglify-js.js
consolidator = uglify.consolidator,
jsp = uglify.parser,
pro = uglify.uglify;
"use strict";
var options = {
ast: false,
consolidate: false,
mangle: true,
mangle_toplevel: false,
no_mangle_functions: false,
squeeze: true,
make_seqs: true,
dead_code: true,
verbose: false,
show_copyright: true,
out_same_file: false,
max_line_length: 32 * 1024,
unsafe: false,
reserved_names: null,
defines: { },
lift_vars: false,
codegen_options: {
ascii_only: false,
beautify: false,
indent_level: 4,
indent_start: 0,
quote_keys: false,
space_colon: false,
inline_script: false
},
make: false,
output: true // stdout
var UglifyJS = require("../tools/node");
var sys = require("util");
var optimist = require("optimist");
var fs = require("fs");
var async = require("async");
var acorn;
var ARGS = optimist
.usage("$0 input1.js [input2.js ...] [options]\n\
Use a single dash to read input from the standard input.\
\n\n\
NOTE: by default there is no mangling/compression.\n\
Without [options] it will simply parse input files and dump the AST\n\
with whitespace and comments discarded. To achieve compression and\n\
mangling you need to use `-c` and `-m`.\
")
.describe("source-map", "Specify an output file where to generate source map.")
.describe("source-map-root", "The path to the original source to be included in the source map.")
.describe("source-map-url", "The path to the source map to be added in //# sourceMappingURL. Defaults to the value passed with --source-map.")
.describe("in-source-map", "Input source map, useful if you're compressing JS that was generated from some other original code.")
.describe("screw-ie8", "Pass this flag if you don't care about full compliance with Internet Explorer 6-8 quirks (by default UglifyJS will try to be IE-proof).")
.describe("expr", "Parse a single expression, rather than a program (for parsing JSON)")
.describe("p", "Skip prefix for original filenames that appear in source maps. \
For example -p 3 will drop 3 directories from file names and ensure they are relative paths.")
.describe("o", "Output file (default STDOUT).")
.describe("b", "Beautify output/specify output options.")
.describe("m", "Mangle names/pass mangler options.")
.describe("r", "Reserved names to exclude from mangling.")
.describe("c", "Enable compressor/pass compressor options. \
Pass options like -c hoist_vars=false,if_return=false. \
Use -c with no argument to use the default compression options.")
.describe("d", "Global definitions")
.describe("e", "Embed everything in a big function, with a configurable parameter/argument list.")
.describe("comments", "Preserve copyright comments in the output. \
By default this works like Google Closure, keeping JSDoc-style comments that contain \"@license\" or \"@preserve\". \
You can optionally pass one of the following arguments to this flag:\n\
- \"all\" to keep all comments\n\
- a valid JS regexp (needs to start with a slash) to keep only comments that match.\n\
\
Note that currently not *all* comments can be kept when compression is on, \
because of dead code removal or cascading statements into sequences.")
.describe("stats", "Display operations run time on STDERR.")
.describe("acorn", "Use Acorn for parsing.")
.describe("spidermonkey", "Assume input files are SpiderMonkey AST format (as JSON).")
.describe("self", "Build itself (UglifyJS2) as a library (implies --wrap=UglifyJS --export-all)")
.describe("wrap", "Embed everything in a big function, making the “exports” and “global” variables available. \
You need to pass an argument to this option to specify the name that your module will take when included in, say, a browser.")
.describe("export-all", "Only used when --wrap, this tells UglifyJS to add code to automatically export all globals.")
.describe("lint", "Display some scope warnings")
.describe("v", "Verbose")
.describe("V", "Print version number and exit.")
.alias("p", "prefix")
.alias("o", "output")
.alias("v", "verbose")
.alias("b", "beautify")
.alias("m", "mangle")
.alias("c", "compress")
.alias("d", "define")
.alias("r", "reserved")
.alias("V", "version")
.alias("e", "enclose")
.string("source-map")
.string("source-map-root")
.string("source-map-url")
.string("b")
.string("m")
.string("c")
.string("d")
.string("e")
.string("comments")
.string("wrap")
.boolean("expr")
.boolean("screw-ie8")
.boolean("export-all")
.boolean("self")
.boolean("v")
.boolean("stats")
.boolean("acorn")
.boolean("spidermonkey")
.boolean("lint")
.boolean("V")
.wrap(80)
.argv
;
normalize(ARGS);
if (ARGS.version || ARGS.V) {
var json = require("../package.json");
sys.puts(json.name + ' ' + json.version);
process.exit(0);
}
if (ARGS.ast_help) {
var desc = UglifyJS.describe_ast();
sys.puts(typeof desc == "string" ? desc : JSON.stringify(desc, null, 2));
process.exit(0);
}
if (ARGS.h || ARGS.help) {
sys.puts(optimist.help());
process.exit(0);
}
if (ARGS.acorn) {
acorn = require("acorn");
}
var COMPRESS = getOptions("c", true);
var MANGLE = getOptions("m", true);
var BEAUTIFY = getOptions("b", true);
if (ARGS.d) {
if (COMPRESS) COMPRESS.global_defs = getOptions("d");
}
if (ARGS.screw_ie8) {
if (COMPRESS) COMPRESS.screw_ie8 = true;
if (MANGLE) MANGLE.screw_ie8 = true;
}
if (ARGS.r) {
if (MANGLE) MANGLE.except = ARGS.r.replace(/^\s+|\s+$/g).split(/\s*,+\s*/);
}
var OUTPUT_OPTIONS = {
beautify: BEAUTIFY ? true : false
};
var args = jsp.slice(process.argv, 2);
var filename;
if (BEAUTIFY)
UglifyJS.merge(OUTPUT_OPTIONS, BEAUTIFY);
out: while (args.length > 0) {
var v = args.shift();
switch (v) {
case "-b":
case "--beautify":
options.codegen_options.beautify = true;
break;
case "-c":
case "--consolidate-primitive-values":
options.consolidate = true;
break;
case "-i":
case "--indent":
options.codegen_options.indent_level = args.shift();
break;
case "-q":
case "--quote-keys":
options.codegen_options.quote_keys = true;
break;
case "-mt":
case "--mangle-toplevel":
options.mangle_toplevel = true;
break;
case "-nmf":
case "--no-mangle-functions":
options.no_mangle_functions = true;
break;
case "--no-mangle":
case "-nm":
options.mangle = false;
break;
case "--no-squeeze":
case "-ns":
options.squeeze = false;
break;
case "--no-seqs":
options.make_seqs = false;
break;
case "--no-dead-code":
options.dead_code = false;
break;
case "--no-copyright":
case "-nc":
options.show_copyright = false;
break;
case "-o":
case "--output":
options.output = args.shift();
break;
case "--overwrite":
options.out_same_file = true;
break;
case "-v":
case "--verbose":
options.verbose = true;
break;
case "--ast":
options.ast = true;
break;
case "--unsafe":
options.unsafe = true;
break;
case "--max-line-len":
options.max_line_length = parseInt(args.shift(), 10);
break;
case "--reserved-names":
options.reserved_names = args.shift().split(",");
break;
case "--lift-vars":
options.lift_vars = true;
break;
case "-d":
case "--define":
var defarg = args.shift();
try {
var defsym = function(sym) {
// KEYWORDS_ATOM doesn't include NaN or Infinity - should we check
// for them too ?? We don't check reserved words and the like as the
// define values are only substituted AFTER parsing
if (jsp.KEYWORDS_ATOM.hasOwnProperty(sym)) {
throw "Don't define values for inbuilt constant '"+sym+"'";
}
return sym;
},
defval = function(v) {
if (v.match(/^"(.*)"$/) || v.match(/^'(.*)'$/)) {
return [ "string", RegExp.$1 ];
}
else if (!isNaN(parseFloat(v))) {
return [ "num", parseFloat(v) ];
}
else if (v.match(/^[a-z\$_][a-z\$_0-9]*$/i)) {
return [ "name", v ];
}
else if (!v.match(/"/)) {
return [ "string", v ];
}
else if (!v.match(/'/)) {
return [ "string", v ];
}
throw "Can't understand the specified value: "+v;
};
if (defarg.match(/^([a-z_\$][a-z_\$0-9]*)(=(.*))?$/i)) {
var sym = defsym(RegExp.$1),
val = RegExp.$2 ? defval(RegExp.$2.substr(1)) : [ 'name', 'true' ];
options.defines[sym] = val;
}
else {
throw "The --define option expects SYMBOL[=value]";
}
} catch(ex) {
sys.print("ERROR: In option --define "+defarg+"\n"+ex+"\n");
process.exit(1);
}
break;
case "--define-from-module":
var defmodarg = args.shift(),
defmodule = require(defmodarg),
sym,
val;
for (sym in defmodule) {
if (defmodule.hasOwnProperty(sym)) {
options.defines[sym] = function(val) {
if (typeof val == "string")
return [ "string", val ];
if (typeof val == "number")
return [ "num", val ];
if (val === true)
return [ 'name', 'true' ];
if (val === false)
return [ 'name', 'false' ];
if (val === null)
return [ 'name', 'null' ];
if (val === undefined)
return [ 'name', 'undefined' ];
sys.print("ERROR: In option --define-from-module "+defmodarg+"\n");
sys.print("ERROR: Unknown object type for: "+sym+"="+val+"\n");
process.exit(1);
return null;
}(defmodule[sym]);
}
}
break;
case "--ascii":
options.codegen_options.ascii_only = true;
break;
case "--make":
options.make = true;
break;
case "--inline-script":
options.codegen_options.inline_script = true;
break;
default:
filename = v;
break out;
if (ARGS.comments) {
if (/^\//.test(ARGS.comments)) {
OUTPUT_OPTIONS.comments = new Function("return(" + ARGS.comments + ")")();
} else if (ARGS.comments == "all") {
OUTPUT_OPTIONS.comments = true;
} else {
OUTPUT_OPTIONS.comments = function(node, comment) {
var text = comment.value;
var type = comment.type;
if (type == "comment2") {
// multiline comment
return /@preserve|@license|@cc_on/i.test(text);
}
}
}
}
if (options.verbose) {
pro.set_logger(function(msg){
sys.debug(msg);
var files = ARGS._.slice();
if (ARGS.self) {
if (files.length > 0) {
sys.error("WARN: Ignoring input files since --self was passed");
}
files = UglifyJS.FILES;
if (!ARGS.wrap) ARGS.wrap = "UglifyJS";
ARGS.export_all = true;
}
var ORIG_MAP = ARGS.in_source_map;
if (ORIG_MAP) {
ORIG_MAP = JSON.parse(fs.readFileSync(ORIG_MAP));
if (files.length == 0) {
sys.error("INFO: Using file from the input source map: " + ORIG_MAP.file);
files = [ ORIG_MAP.file ];
}
if (ARGS.source_map_root == null) {
ARGS.source_map_root = ORIG_MAP.sourceRoot;
}
}
if (files.length == 0) {
files = [ "-" ];
}
if (files.indexOf("-") >= 0 && ARGS.source_map) {
sys.error("ERROR: Source map doesn't work with input from STDIN");
process.exit(1);
}
if (files.filter(function(el){ return el == "-" }).length > 1) {
sys.error("ERROR: Can read a single file from STDIN (two or more dashes specified)");
process.exit(1);
}
var STATS = {};
var OUTPUT_FILE = ARGS.o;
var TOPLEVEL = null;
var SOURCE_MAP = ARGS.source_map ? UglifyJS.SourceMap({
file: OUTPUT_FILE,
root: ARGS.source_map_root,
orig: ORIG_MAP,
}) : null;
OUTPUT_OPTIONS.source_map = SOURCE_MAP;
try {
var output = UglifyJS.OutputStream(OUTPUT_OPTIONS);
var compressor = COMPRESS && UglifyJS.Compressor(COMPRESS);
} catch(ex) {
if (ex instanceof UglifyJS.DefaultsError) {
sys.error(ex.msg);
sys.error("Supported options:");
sys.error(sys.inspect(ex.defs));
process.exit(1);
}
}
async.eachLimit(files, 1, function (file, cb) {
read_whole_file(file, function (err, code) {
if (err) {
sys.error("ERROR: can't read file: " + filename);
process.exit(1);
}
if (ARGS.p != null) {
file = file.replace(/^\/+/, "").split(/\/+/).slice(ARGS.p).join("/");
}
time_it("parse", function(){
if (ARGS.spidermonkey) {
var program = JSON.parse(code);
if (!TOPLEVEL) TOPLEVEL = program;
else TOPLEVEL.body = TOPLEVEL.body.concat(program.body);
}
else if (ARGS.acorn) {
TOPLEVEL = acorn.parse(code, {
locations : true,
trackComments : true,
sourceFile : file,
program : TOPLEVEL
});
}
else {
TOPLEVEL = UglifyJS.parse(code, {
filename : file,
toplevel : TOPLEVEL,
expression : ARGS.expr,
});
};
});
}
cb();
});
}, function () {
if (ARGS.acorn || ARGS.spidermonkey) time_it("convert_ast", function(){
TOPLEVEL = UglifyJS.AST_Node.from_mozilla_ast(TOPLEVEL);
});
jsp.set_logger(function(msg){
sys.debug(msg);
if (ARGS.wrap) {
TOPLEVEL = TOPLEVEL.wrap_commonjs(ARGS.wrap, ARGS.export_all);
}
if (ARGS.enclose) {
var arg_parameter_list = ARGS.enclose;
if (!(arg_parameter_list instanceof Array)) {
arg_parameter_list = [arg_parameter_list];
}
TOPLEVEL = TOPLEVEL.wrap_enclose(arg_parameter_list);
}
var SCOPE_IS_NEEDED = COMPRESS || MANGLE || ARGS.lint;
if (SCOPE_IS_NEEDED) {
time_it("scope", function(){
TOPLEVEL.figure_out_scope({ screw_ie8: ARGS.screw_ie8 });
if (ARGS.lint) {
TOPLEVEL.scope_warnings();
}
});
}
if (COMPRESS) {
time_it("squeeze", function(){
TOPLEVEL = TOPLEVEL.transform(compressor);
});
}
if (SCOPE_IS_NEEDED) {
time_it("scope", function(){
TOPLEVEL.figure_out_scope({ screw_ie8: ARGS.screw_ie8 });
if (MANGLE) {
TOPLEVEL.compute_char_frequency(MANGLE);
}
});
}
if (MANGLE) time_it("mangle", function(){
TOPLEVEL.mangle_names(MANGLE);
});
time_it("generate", function(){
TOPLEVEL.print(output);
});
output = output.get();
if (SOURCE_MAP) {
fs.writeFileSync(ARGS.source_map, SOURCE_MAP, "utf8");
output += "\n//# sourceMappingURL=" + (ARGS.source_map_url || ARGS.source_map);
}
if (OUTPUT_FILE) {
fs.writeFileSync(OUTPUT_FILE, output, "utf8");
} else {
sys.print(output);
sys.error("\n");
}
if (ARGS.stats) {
sys.error(UglifyJS.string_template("Timing information (compressed {count} files):", {
count: files.length
}));
for (var i in STATS) if (STATS.hasOwnProperty(i)) {
sys.error(UglifyJS.string_template("- {name}: {time}s", {
name: i,
time: (STATS[i] / 1000).toFixed(3)
}));
}
}
});
if (options.make) {
options.out_same_file = false; // doesn't make sense in this case
var makefile = JSON.parse(fs.readFileSync(filename || "Makefile.uglify.js").toString());
output(makefile.files.map(function(file){
var code = fs.readFileSync(file.name);
if (file.module) {
code = "!function(exports, global){global = this;\n" + code + "\n;this." + file.module + " = exports;}({})";
}
else if (file.hide) {
code = "(function(){" + code + "}());";
}
return squeeze_it(code);
}).join("\n"));
}
else if (filename) {
fs.readFile(filename, "utf8", function(err, text){
if (err) throw err;
output(squeeze_it(text));
});
}
else {
var stdin = process.openStdin();
stdin.setEncoding("utf8");
var text = "";
stdin.on("data", function(chunk){
text += chunk;
});
stdin.on("end", function() {
output(squeeze_it(text));
});
/* -----[ functions ]----- */
function normalize(o) {
for (var i in o) if (o.hasOwnProperty(i) && /-/.test(i)) {
o[i.replace(/-/g, "_")] = o[i];
delete o[i];
}
}
function output(text) {
var out;
if (options.out_same_file && filename)
options.output = filename;
if (options.output === true) {
out = process.stdout;
} else {
out = fs.createWriteStream(options.output, {
flags: "w",
encoding: "utf8",
mode: 0644
});
}
out.write(text.replace(/;*$/, ";"));
if (options.output !== true) {
out.end();
}
};
// --------- main ends here.
function show_copyright(comments) {
var ret = "";
for (var i = 0; i < comments.length; ++i) {
var c = comments[i];
if (c.type == "comment1") {
ret += "//" + c.value + "\n";
} else {
ret += "/*" + c.value + "*/";
}
}
return ret;
};
function squeeze_it(code) {
var result = "";
if (options.show_copyright) {
var tok = jsp.tokenizer(code), c;
c = tok();
result += show_copyright(c.comments_before);
}
function getOptions(x, constants) {
x = ARGS[x];
if (!x) return null;
var ret = {};
if (x !== true) {
var ast;
try {
var ast = time_it("parse", function(){ return jsp.parse(code); });
if (options.consolidate) ast = time_it("consolidate", function(){
return consolidator.ast_consolidate(ast);
});
if (options.lift_vars) {
ast = time_it("lift", function(){ return pro.ast_lift_variables(ast); });
}
if (options.mangle) ast = time_it("mangle", function(){
return pro.ast_mangle(ast, {
toplevel : options.mangle_toplevel,
defines : options.defines,
except : options.reserved_names,
no_functions : options.no_mangle_functions
});
});
if (options.squeeze) ast = time_it("squeeze", function(){
ast = pro.ast_squeeze(ast, {
make_seqs : options.make_seqs,
dead_code : options.dead_code,
keep_comps : !options.unsafe
});
if (options.unsafe)
ast = pro.ast_squeeze_more(ast);
return ast;
});
if (options.ast)
return sys.inspect(ast, null, null);
result += time_it("generate", function(){ return pro.gen_code(ast, options.codegen_options) });
if (!options.codegen_options.beautify && options.max_line_length) {
result = time_it("split", function(){ return pro.split_lines(result, options.max_line_length) });
}
return result;
ast = UglifyJS.parse(x);
} catch(ex) {
sys.debug(ex.stack);
sys.debug(sys.inspect(ex));
sys.debug(JSON.stringify(ex));
if (ex instanceof UglifyJS.JS_Parse_Error) {
sys.error("Error parsing arguments in: " + x);
process.exit(1);
}
}
};
ast.walk(new UglifyJS.TreeWalker(function(node){
if (node instanceof UglifyJS.AST_Toplevel) return; // descend
if (node instanceof UglifyJS.AST_SimpleStatement) return; // descend
if (node instanceof UglifyJS.AST_Seq) return; // descend
if (node instanceof UglifyJS.AST_Assign) {
var name = node.left.print_to_string({ beautify: false }).replace(/-/g, "_");
var value = node.right;
if (constants)
value = new Function("return (" + value.print_to_string() + ")")();
ret[name] = value;
return true; // no descend
}
sys.error(node.TYPE)
sys.error("Error parsing arguments in: " + x);
process.exit(1);
}));
}
return ret;
}
function read_whole_file(filename, cb) {
if (filename == "-") {
var chunks = [];
process.stdin.setEncoding('utf-8');
process.stdin.on('data', function (chunk) {
chunks.push(chunk);
}).on('end', function () {
cb(null, chunks.join(""));
});
process.openStdin();
} else {
fs.readFile(filename, "utf-8", cb);
}
}
function time_it(name, cont) {
if (!options.verbose)
return cont();
var t1 = new Date().getTime();
try { return cont(); }
finally { sys.debug("// " + name + ": " + ((new Date().getTime() - t1) / 1000).toFixed(3) + " sec."); }
};
var t1 = new Date().getTime();
var ret = cont();
if (ARGS.stats) {
var spent = new Date().getTime() - t1;
if (STATS[name]) STATS[name] += spent;
else STATS[name] = spent;
}
return ret;
}

View File

@@ -1,75 +0,0 @@
html { font-family: "Lucida Grande","Trebuchet MS",sans-serif; font-size: 12pt; }
body { max-width: 60em; }
.title { text-align: center; }
.todo { color: red; }
.done { color: green; }
.tag { background-color:lightblue; font-weight:normal }
.target { }
.timestamp { color: grey }
.timestamp-kwd { color: CadetBlue }
p.verse { margin-left: 3% }
pre {
border: 1pt solid #AEBDCC;
background-color: #F3F5F7;
padding: 5pt;
font-family: monospace;
font-size: 90%;
overflow:auto;
}
pre.src {
background-color: #eee; color: #112; border: 1px solid #000;
}
table { border-collapse: collapse; }
td, th { vertical-align: top; }
dt { font-weight: bold; }
div.figure { padding: 0.5em; }
div.figure p { text-align: center; }
.linenr { font-size:smaller }
.code-highlighted {background-color:#ffff00;}
.org-info-js_info-navigation { border-style:none; }
#org-info-js_console-label { font-size:10px; font-weight:bold;
white-space:nowrap; }
.org-info-js_search-highlight {background-color:#ffff00; color:#000000;
font-weight:bold; }
sup {
vertical-align: baseline;
position: relative;
top: -0.5em;
font-size: 80%;
}
sup a:link, sup a:visited {
text-decoration: none;
color: #c00;
}
sup a:before { content: "["; color: #999; }
sup a:after { content: "]"; color: #999; }
h1.title { border-bottom: 4px solid #000; padding-bottom: 5px; margin-bottom: 2em; }
#postamble {
color: #777;
font-size: 90%;
padding-top: 1em; padding-bottom: 1em; border-top: 1px solid #999;
margin-top: 2em;
padding-left: 2em;
padding-right: 2em;
text-align: right;
}
#postamble p { margin: 0; }
#footnotes { border-top: 1px solid #000; }
h1 { font-size: 200% }
h2 { font-size: 175% }
h3 { font-size: 150% }
h4 { font-size: 125% }
h1, h2, h3, h4 { font-family: "Bookman",Georgia,"Times New Roman",serif; font-weight: normal; }
@media print {
html { font-size: 11pt; }
}

View File

@@ -0,0 +1,985 @@
/***********************************************************************
A JavaScript tokenizer / parser / beautifier / compressor.
https://github.com/mishoo/UglifyJS2
-------------------------------- (C) ---------------------------------
Author: Mihai Bazon
<mihai.bazon@gmail.com>
http://mihai.bazon.net/blog
Distributed under the BSD license:
Copyright 2012 (c) Mihai Bazon <mihai.bazon@gmail.com>
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
* Redistributions of source code must retain the above
copyright notice, this list of conditions and the following
disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials
provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “AS IS” AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
***********************************************************************/
"use strict";
function DEFNODE(type, props, methods, base) {
if (arguments.length < 4) base = AST_Node;
if (!props) props = [];
else props = props.split(/\s+/);
var self_props = props;
if (base && base.PROPS)
props = props.concat(base.PROPS);
var code = "return function AST_" + type + "(props){ if (props) { ";
for (var i = props.length; --i >= 0;) {
code += "this." + props[i] + " = props." + props[i] + ";";
}
var proto = base && new base;
if (proto && proto.initialize || (methods && methods.initialize))
code += "this.initialize();";
code += "}}";
var ctor = new Function(code)();
if (proto) {
ctor.prototype = proto;
ctor.BASE = base;
}
if (base) base.SUBCLASSES.push(ctor);
ctor.prototype.CTOR = ctor;
ctor.PROPS = props || null;
ctor.SELF_PROPS = self_props;
ctor.SUBCLASSES = [];
if (type) {
ctor.prototype.TYPE = ctor.TYPE = type;
}
if (methods) for (i in methods) if (methods.hasOwnProperty(i)) {
if (/^\$/.test(i)) {
ctor[i.substr(1)] = methods[i];
} else {
ctor.prototype[i] = methods[i];
}
}
ctor.DEFMETHOD = function(name, method) {
this.prototype[name] = method;
};
return ctor;
};
var AST_Token = DEFNODE("Token", "type value line col pos endpos nlb comments_before file", {
}, null);
var AST_Node = DEFNODE("Node", "start end", {
clone: function() {
return new this.CTOR(this);
},
$documentation: "Base class of all AST nodes",
$propdoc: {
start: "[AST_Token] The first token of this node",
end: "[AST_Token] The last token of this node"
},
_walk: function(visitor) {
return visitor._visit(this);
},
walk: function(visitor) {
return this._walk(visitor); // not sure the indirection will be any help
}
}, null);
AST_Node.warn_function = null;
AST_Node.warn = function(txt, props) {
if (AST_Node.warn_function)
AST_Node.warn_function(string_template(txt, props));
};
/* -----[ statements ]----- */
var AST_Statement = DEFNODE("Statement", null, {
$documentation: "Base class of all statements",
});
var AST_Debugger = DEFNODE("Debugger", null, {
$documentation: "Represents a debugger statement",
}, AST_Statement);
var AST_Directive = DEFNODE("Directive", "value scope", {
$documentation: "Represents a directive, like \"use strict\";",
$propdoc: {
value: "[string] The value of this directive as a plain string (it's not an AST_String!)",
scope: "[AST_Scope/S] The scope that this directive affects"
},
}, AST_Statement);
var AST_SimpleStatement = DEFNODE("SimpleStatement", "body", {
$documentation: "A statement consisting of an expression, i.e. a = 1 + 2",
$propdoc: {
body: "[AST_Node] an expression node (should not be instanceof AST_Statement)"
},
_walk: function(visitor) {
return visitor._visit(this, function(){
this.body._walk(visitor);
});
}
}, AST_Statement);
function walk_body(node, visitor) {
if (node.body instanceof AST_Statement) {
node.body._walk(visitor);
}
else node.body.forEach(function(stat){
stat._walk(visitor);
});
};
var AST_Block = DEFNODE("Block", "body", {
$documentation: "A body of statements (usually bracketed)",
$propdoc: {
body: "[AST_Statement*] an array of statements"
},
_walk: function(visitor) {
return visitor._visit(this, function(){
walk_body(this, visitor);
});
}
}, AST_Statement);
var AST_BlockStatement = DEFNODE("BlockStatement", null, {
$documentation: "A block statement",
}, AST_Block);
var AST_EmptyStatement = DEFNODE("EmptyStatement", null, {
$documentation: "The empty statement (empty block or simply a semicolon)",
_walk: function(visitor) {
return visitor._visit(this);
}
}, AST_Statement);
var AST_StatementWithBody = DEFNODE("StatementWithBody", "body", {
$documentation: "Base class for all statements that contain one nested body: `For`, `ForIn`, `Do`, `While`, `With`",
$propdoc: {
body: "[AST_Statement] the body; this should always be present, even if it's an AST_EmptyStatement"
},
_walk: function(visitor) {
return visitor._visit(this, function(){
this.body._walk(visitor);
});
}
}, AST_Statement);
var AST_LabeledStatement = DEFNODE("LabeledStatement", "label", {
$documentation: "Statement with a label",
$propdoc: {
label: "[AST_Label] a label definition"
},
_walk: function(visitor) {
return visitor._visit(this, function(){
this.label._walk(visitor);
this.body._walk(visitor);
});
}
}, AST_StatementWithBody);
var AST_DWLoop = DEFNODE("DWLoop", "condition", {
$documentation: "Base class for do/while statements",
$propdoc: {
condition: "[AST_Node] the loop condition. Should not be instanceof AST_Statement"
},
_walk: function(visitor) {
return visitor._visit(this, function(){
this.condition._walk(visitor);
this.body._walk(visitor);
});
}
}, AST_StatementWithBody);
var AST_Do = DEFNODE("Do", null, {
$documentation: "A `do` statement",
}, AST_DWLoop);
var AST_While = DEFNODE("While", null, {
$documentation: "A `while` statement",
}, AST_DWLoop);
var AST_For = DEFNODE("For", "init condition step", {
$documentation: "A `for` statement",
$propdoc: {
init: "[AST_Node?] the `for` initialization code, or null if empty",
condition: "[AST_Node?] the `for` termination clause, or null if empty",
step: "[AST_Node?] the `for` update clause, or null if empty"
},
_walk: function(visitor) {
return visitor._visit(this, function(){
if (this.init) this.init._walk(visitor);
if (this.condition) this.condition._walk(visitor);
if (this.step) this.step._walk(visitor);
this.body._walk(visitor);
});
}
}, AST_StatementWithBody);
var AST_ForIn = DEFNODE("ForIn", "init name object", {
$documentation: "A `for ... in` statement",
$propdoc: {
init: "[AST_Node] the `for/in` initialization code",
name: "[AST_SymbolRef?] the loop variable, only if `init` is AST_Var",
object: "[AST_Node] the object that we're looping through"
},
_walk: function(visitor) {
return visitor._visit(this, function(){
this.init._walk(visitor);
this.object._walk(visitor);
this.body._walk(visitor);
});
}
}, AST_StatementWithBody);
var AST_With = DEFNODE("With", "expression", {
$documentation: "A `with` statement",
$propdoc: {
expression: "[AST_Node] the `with` expression"
},
_walk: function(visitor) {
return visitor._visit(this, function(){
this.expression._walk(visitor);
this.body._walk(visitor);
});
}
}, AST_StatementWithBody);
/* -----[ scope and functions ]----- */
var AST_Scope = DEFNODE("Scope", "directives variables functions uses_with uses_eval parent_scope enclosed cname", {
$documentation: "Base class for all statements introducing a lexical scope",
$propdoc: {
directives: "[string*/S] an array of directives declared in this scope",
variables: "[Object/S] a map of name -> SymbolDef for all variables/functions defined in this scope",
functions: "[Object/S] like `variables`, but only lists function declarations",
uses_with: "[boolean/S] tells whether this scope uses the `with` statement",
uses_eval: "[boolean/S] tells whether this scope contains a direct call to the global `eval`",
parent_scope: "[AST_Scope?/S] link to the parent scope",
enclosed: "[SymbolDef*/S] a list of all symbol definitions that are accessed from this scope or any subscopes",
cname: "[integer/S] current index for mangling variables (used internally by the mangler)",
},
}, AST_Block);
var AST_Toplevel = DEFNODE("Toplevel", "globals", {
$documentation: "The toplevel scope",
$propdoc: {
globals: "[Object/S] a map of name -> SymbolDef for all undeclared names",
},
wrap_enclose: function(arg_parameter_pairs) {
var self = this;
var args = [];
var parameters = [];
arg_parameter_pairs.forEach(function(pair) {
var split = pair.split(":");
args.push(split[0]);
parameters.push(split[1]);
});
var wrapped_tl = "(function(" + parameters.join(",") + "){ '$ORIG'; })(" + args.join(",") + ")";
wrapped_tl = parse(wrapped_tl);
wrapped_tl = wrapped_tl.transform(new TreeTransformer(function before(node){
if (node instanceof AST_Directive && node.value == "$ORIG") {
return MAP.splice(self.body);
}
}));
return wrapped_tl;
},
wrap_commonjs: function(name, export_all) {
var self = this;
var to_export = [];
if (export_all) {
self.figure_out_scope();
self.walk(new TreeWalker(function(node){
if (node instanceof AST_SymbolDeclaration && node.definition().global) {
if (!find_if(function(n){ return n.name == node.name }, to_export))
to_export.push(node);
}
}));
}
var wrapped_tl = "(function(exports, global){ global['" + name + "'] = exports; '$ORIG'; '$EXPORTS'; }({}, (function(){return this}())))";
wrapped_tl = parse(wrapped_tl);
wrapped_tl = wrapped_tl.transform(new TreeTransformer(function before(node){
if (node instanceof AST_SimpleStatement) {
node = node.body;
if (node instanceof AST_String) switch (node.getValue()) {
case "$ORIG":
return MAP.splice(self.body);
case "$EXPORTS":
var body = [];
to_export.forEach(function(sym){
body.push(new AST_SimpleStatement({
body: new AST_Assign({
left: new AST_Sub({
expression: new AST_SymbolRef({ name: "exports" }),
property: new AST_String({ value: sym.name }),
}),
operator: "=",
right: new AST_SymbolRef(sym),
}),
}));
});
return MAP.splice(body);
}
}
}));
return wrapped_tl;
}
}, AST_Scope);
var AST_Lambda = DEFNODE("Lambda", "name argnames uses_arguments", {
$documentation: "Base class for functions",
$propdoc: {
name: "[AST_SymbolDeclaration?] the name of this function",
argnames: "[AST_SymbolFunarg*] array of function arguments",
uses_arguments: "[boolean/S] tells whether this function accesses the arguments array"
},
_walk: function(visitor) {
return visitor._visit(this, function(){
if (this.name) this.name._walk(visitor);
this.argnames.forEach(function(arg){
arg._walk(visitor);
});
walk_body(this, visitor);
});
}
}, AST_Scope);
var AST_Accessor = DEFNODE("Accessor", null, {
$documentation: "A setter/getter function"
}, AST_Lambda);
var AST_Function = DEFNODE("Function", null, {
$documentation: "A function expression"
}, AST_Lambda);
var AST_Defun = DEFNODE("Defun", null, {
$documentation: "A function definition"
}, AST_Lambda);
/* -----[ JUMPS ]----- */
var AST_Jump = DEFNODE("Jump", null, {
$documentation: "Base class for “jumps” (for now that's `return`, `throw`, `break` and `continue`)"
}, AST_Statement);
var AST_Exit = DEFNODE("Exit", "value", {
$documentation: "Base class for “exits” (`return` and `throw`)",
$propdoc: {
value: "[AST_Node?] the value returned or thrown by this statement; could be null for AST_Return"
},
_walk: function(visitor) {
return visitor._visit(this, this.value && function(){
this.value._walk(visitor);
});
}
}, AST_Jump);
var AST_Return = DEFNODE("Return", null, {
$documentation: "A `return` statement"
}, AST_Exit);
var AST_Throw = DEFNODE("Throw", null, {
$documentation: "A `throw` statement"
}, AST_Exit);
var AST_LoopControl = DEFNODE("LoopControl", "label", {
$documentation: "Base class for loop control statements (`break` and `continue`)",
$propdoc: {
label: "[AST_LabelRef?] the label, or null if none",
},
_walk: function(visitor) {
return visitor._visit(this, this.label && function(){
this.label._walk(visitor);
});
}
}, AST_Jump);
var AST_Break = DEFNODE("Break", null, {
$documentation: "A `break` statement"
}, AST_LoopControl);
var AST_Continue = DEFNODE("Continue", null, {
$documentation: "A `continue` statement"
}, AST_LoopControl);
/* -----[ IF ]----- */
var AST_If = DEFNODE("If", "condition alternative", {
$documentation: "A `if` statement",
$propdoc: {
condition: "[AST_Node] the `if` condition",
alternative: "[AST_Statement?] the `else` part, or null if not present"
},
_walk: function(visitor) {
return visitor._visit(this, function(){
this.condition._walk(visitor);
this.body._walk(visitor);
if (this.alternative) this.alternative._walk(visitor);
});
}
}, AST_StatementWithBody);
/* -----[ SWITCH ]----- */
var AST_Switch = DEFNODE("Switch", "expression", {
$documentation: "A `switch` statement",
$propdoc: {
expression: "[AST_Node] the `switch` “discriminant”"
},
_walk: function(visitor) {
return visitor._visit(this, function(){
this.expression._walk(visitor);
walk_body(this, visitor);
});
}
}, AST_Block);
var AST_SwitchBranch = DEFNODE("SwitchBranch", null, {
$documentation: "Base class for `switch` branches",
}, AST_Block);
var AST_Default = DEFNODE("Default", null, {
$documentation: "A `default` switch branch",
}, AST_SwitchBranch);
var AST_Case = DEFNODE("Case", "expression", {
$documentation: "A `case` switch branch",
$propdoc: {
expression: "[AST_Node] the `case` expression"
},
_walk: function(visitor) {
return visitor._visit(this, function(){
this.expression._walk(visitor);
walk_body(this, visitor);
});
}
}, AST_SwitchBranch);
/* -----[ EXCEPTIONS ]----- */
var AST_Try = DEFNODE("Try", "bcatch bfinally", {
$documentation: "A `try` statement",
$propdoc: {
bcatch: "[AST_Catch?] the catch block, or null if not present",
bfinally: "[AST_Finally?] the finally block, or null if not present"
},
_walk: function(visitor) {
return visitor._visit(this, function(){
walk_body(this, visitor);
if (this.bcatch) this.bcatch._walk(visitor);
if (this.bfinally) this.bfinally._walk(visitor);
});
}
}, AST_Block);
// XXX: this is wrong according to ECMA-262 (12.4). the catch block
// should introduce another scope, as the argname should be visible
// only inside the catch block. However, doing it this way because of
// IE which simply introduces the name in the surrounding scope. If
// we ever want to fix this then AST_Catch should inherit from
// AST_Scope.
var AST_Catch = DEFNODE("Catch", "argname", {
$documentation: "A `catch` node; only makes sense as part of a `try` statement",
$propdoc: {
argname: "[AST_SymbolCatch] symbol for the exception"
},
_walk: function(visitor) {
return visitor._visit(this, function(){
this.argname._walk(visitor);
walk_body(this, visitor);
});
}
}, AST_Block);
var AST_Finally = DEFNODE("Finally", null, {
$documentation: "A `finally` node; only makes sense as part of a `try` statement"
}, AST_Block);
/* -----[ VAR/CONST ]----- */
var AST_Definitions = DEFNODE("Definitions", "definitions", {
$documentation: "Base class for `var` or `const` nodes (variable declarations/initializations)",
$propdoc: {
definitions: "[AST_VarDef*] array of variable definitions"
},
_walk: function(visitor) {
return visitor._visit(this, function(){
this.definitions.forEach(function(def){
def._walk(visitor);
});
});
}
}, AST_Statement);
var AST_Var = DEFNODE("Var", null, {
$documentation: "A `var` statement"
}, AST_Definitions);
var AST_Const = DEFNODE("Const", null, {
$documentation: "A `const` statement"
}, AST_Definitions);
var AST_VarDef = DEFNODE("VarDef", "name value", {
$documentation: "A variable declaration; only appears in a AST_Definitions node",
$propdoc: {
name: "[AST_SymbolVar|AST_SymbolConst] name of the variable",
value: "[AST_Node?] initializer, or null of there's no initializer"
},
_walk: function(visitor) {
return visitor._visit(this, function(){
this.name._walk(visitor);
if (this.value) this.value._walk(visitor);
});
}
});
/* -----[ OTHER ]----- */
var AST_Call = DEFNODE("Call", "expression args", {
$documentation: "A function call expression",
$propdoc: {
expression: "[AST_Node] expression to invoke as function",
args: "[AST_Node*] array of arguments"
},
_walk: function(visitor) {
return visitor._visit(this, function(){
this.expression._walk(visitor);
this.args.forEach(function(arg){
arg._walk(visitor);
});
});
}
});
var AST_New = DEFNODE("New", null, {
$documentation: "An object instantiation. Derives from a function call since it has exactly the same properties"
}, AST_Call);
var AST_Seq = DEFNODE("Seq", "car cdr", {
$documentation: "A sequence expression (two comma-separated expressions)",
$propdoc: {
car: "[AST_Node] first element in sequence",
cdr: "[AST_Node] second element in sequence"
},
$cons: function(x, y) {
var seq = new AST_Seq(x);
seq.car = x;
seq.cdr = y;
return seq;
},
$from_array: function(array) {
if (array.length == 0) return null;
if (array.length == 1) return array[0].clone();
var list = null;
for (var i = array.length; --i >= 0;) {
list = AST_Seq.cons(array[i], list);
}
var p = list;
while (p) {
if (p.cdr && !p.cdr.cdr) {
p.cdr = p.cdr.car;
break;
}
p = p.cdr;
}
return list;
},
to_array: function() {
var p = this, a = [];
while (p) {
a.push(p.car);
if (p.cdr && !(p.cdr instanceof AST_Seq)) {
a.push(p.cdr);
break;
}
p = p.cdr;
}
return a;
},
add: function(node) {
var p = this;
while (p) {
if (!(p.cdr instanceof AST_Seq)) {
var cell = AST_Seq.cons(p.cdr, node);
return p.cdr = cell;
}
p = p.cdr;
}
},
_walk: function(visitor) {
return visitor._visit(this, function(){
this.car._walk(visitor);
if (this.cdr) this.cdr._walk(visitor);
});
}
});
var AST_PropAccess = DEFNODE("PropAccess", "expression property", {
$documentation: "Base class for property access expressions, i.e. `a.foo` or `a[\"foo\"]`",
$propdoc: {
expression: "[AST_Node] the “container” expression",
property: "[AST_Node|string] the property to access. For AST_Dot this is always a plain string, while for AST_Sub it's an arbitrary AST_Node"
}
});
var AST_Dot = DEFNODE("Dot", null, {
$documentation: "A dotted property access expression",
_walk: function(visitor) {
return visitor._visit(this, function(){
this.expression._walk(visitor);
});
}
}, AST_PropAccess);
var AST_Sub = DEFNODE("Sub", null, {
$documentation: "Index-style property access, i.e. `a[\"foo\"]`",
_walk: function(visitor) {
return visitor._visit(this, function(){
this.expression._walk(visitor);
this.property._walk(visitor);
});
}
}, AST_PropAccess);
var AST_Unary = DEFNODE("Unary", "operator expression", {
$documentation: "Base class for unary expressions",
$propdoc: {
operator: "[string] the operator",
expression: "[AST_Node] expression that this unary operator applies to"
},
_walk: function(visitor) {
return visitor._visit(this, function(){
this.expression._walk(visitor);
});
}
});
var AST_UnaryPrefix = DEFNODE("UnaryPrefix", null, {
$documentation: "Unary prefix expression, i.e. `typeof i` or `++i`"
}, AST_Unary);
var AST_UnaryPostfix = DEFNODE("UnaryPostfix", null, {
$documentation: "Unary postfix expression, i.e. `i++`"
}, AST_Unary);
var AST_Binary = DEFNODE("Binary", "left operator right", {
$documentation: "Binary expression, i.e. `a + b`",
$propdoc: {
left: "[AST_Node] left-hand side expression",
operator: "[string] the operator",
right: "[AST_Node] right-hand side expression"
},
_walk: function(visitor) {
return visitor._visit(this, function(){
this.left._walk(visitor);
this.right._walk(visitor);
});
}
});
var AST_Conditional = DEFNODE("Conditional", "condition consequent alternative", {
$documentation: "Conditional expression using the ternary operator, i.e. `a ? b : c`",
$propdoc: {
condition: "[AST_Node]",
consequent: "[AST_Node]",
alternative: "[AST_Node]"
},
_walk: function(visitor) {
return visitor._visit(this, function(){
this.condition._walk(visitor);
this.consequent._walk(visitor);
this.alternative._walk(visitor);
});
}
});
var AST_Assign = DEFNODE("Assign", null, {
$documentation: "An assignment expression — `a = b + 5`",
}, AST_Binary);
/* -----[ LITERALS ]----- */
var AST_Array = DEFNODE("Array", "elements", {
$documentation: "An array literal",
$propdoc: {
elements: "[AST_Node*] array of elements"
},
_walk: function(visitor) {
return visitor._visit(this, function(){
this.elements.forEach(function(el){
el._walk(visitor);
});
});
}
});
var AST_Object = DEFNODE("Object", "properties", {
$documentation: "An object literal",
$propdoc: {
properties: "[AST_ObjectProperty*] array of properties"
},
_walk: function(visitor) {
return visitor._visit(this, function(){
this.properties.forEach(function(prop){
prop._walk(visitor);
});
});
}
});
var AST_ObjectProperty = DEFNODE("ObjectProperty", "key value", {
$documentation: "Base class for literal object properties",
$propdoc: {
key: "[string] the property name; it's always a plain string in our AST, no matter if it was a string, number or identifier in original code",
value: "[AST_Node] property value. For setters and getters this is an AST_Function."
},
_walk: function(visitor) {
return visitor._visit(this, function(){
this.value._walk(visitor);
});
}
});
var AST_ObjectKeyVal = DEFNODE("ObjectKeyVal", null, {
$documentation: "A key: value object property",
}, AST_ObjectProperty);
var AST_ObjectSetter = DEFNODE("ObjectSetter", null, {
$documentation: "An object setter property",
}, AST_ObjectProperty);
var AST_ObjectGetter = DEFNODE("ObjectGetter", null, {
$documentation: "An object getter property",
}, AST_ObjectProperty);
var AST_Symbol = DEFNODE("Symbol", "scope name thedef", {
$propdoc: {
name: "[string] name of this symbol",
scope: "[AST_Scope/S] the current scope (not necessarily the definition scope)",
thedef: "[SymbolDef/S] the definition of this symbol"
},
$documentation: "Base class for all symbols",
});
var AST_SymbolAccessor = DEFNODE("SymbolAccessor", null, {
$documentation: "The name of a property accessor (setter/getter function)"
}, AST_Symbol);
var AST_SymbolDeclaration = DEFNODE("SymbolDeclaration", "init", {
$documentation: "A declaration symbol (symbol in var/const, function name or argument, symbol in catch)",
$propdoc: {
init: "[AST_Node*/S] array of initializers for this declaration."
}
}, AST_Symbol);
var AST_SymbolVar = DEFNODE("SymbolVar", null, {
$documentation: "Symbol defining a variable",
}, AST_SymbolDeclaration);
var AST_SymbolConst = DEFNODE("SymbolConst", null, {
$documentation: "A constant declaration"
}, AST_SymbolDeclaration);
var AST_SymbolFunarg = DEFNODE("SymbolFunarg", null, {
$documentation: "Symbol naming a function argument",
}, AST_SymbolVar);
var AST_SymbolDefun = DEFNODE("SymbolDefun", null, {
$documentation: "Symbol defining a function",
}, AST_SymbolDeclaration);
var AST_SymbolLambda = DEFNODE("SymbolLambda", null, {
$documentation: "Symbol naming a function expression",
}, AST_SymbolDeclaration);
var AST_SymbolCatch = DEFNODE("SymbolCatch", null, {
$documentation: "Symbol naming the exception in catch",
}, AST_SymbolDeclaration);
var AST_Label = DEFNODE("Label", "references", {
$documentation: "Symbol naming a label (declaration)",
$propdoc: {
references: "[AST_LabelRef*] a list of nodes referring to this label"
}
}, AST_Symbol);
var AST_SymbolRef = DEFNODE("SymbolRef", null, {
$documentation: "Reference to some symbol (not definition/declaration)",
}, AST_Symbol);
var AST_LabelRef = DEFNODE("LabelRef", null, {
$documentation: "Reference to a label symbol",
}, AST_Symbol);
var AST_This = DEFNODE("This", null, {
$documentation: "The `this` symbol",
}, AST_Symbol);
var AST_Constant = DEFNODE("Constant", null, {
$documentation: "Base class for all constants",
getValue: function() {
return this.value;
}
});
var AST_String = DEFNODE("String", "value", {
$documentation: "A string literal",
$propdoc: {
value: "[string] the contents of this string"
}
}, AST_Constant);
var AST_Number = DEFNODE("Number", "value", {
$documentation: "A number literal",
$propdoc: {
value: "[number] the numeric value"
}
}, AST_Constant);
var AST_RegExp = DEFNODE("RegExp", "value", {
$documentation: "A regexp literal",
$propdoc: {
value: "[RegExp] the actual regexp"
}
}, AST_Constant);
var AST_Atom = DEFNODE("Atom", null, {
$documentation: "Base class for atoms",
}, AST_Constant);
var AST_Null = DEFNODE("Null", null, {
$documentation: "The `null` atom",
value: null
}, AST_Atom);
var AST_NaN = DEFNODE("NaN", null, {
$documentation: "The impossible value",
value: 0/0
}, AST_Atom);
var AST_Undefined = DEFNODE("Undefined", null, {
$documentation: "The `undefined` value",
value: (function(){}())
}, AST_Atom);
var AST_Hole = DEFNODE("Hole", null, {
$documentation: "A hole in an array",
value: (function(){}())
}, AST_Atom);
var AST_Infinity = DEFNODE("Infinity", null, {
$documentation: "The `Infinity` value",
value: 1/0
}, AST_Atom);
var AST_Boolean = DEFNODE("Boolean", null, {
$documentation: "Base class for booleans",
}, AST_Atom);
var AST_False = DEFNODE("False", null, {
$documentation: "The `false` atom",
value: false
}, AST_Boolean);
var AST_True = DEFNODE("True", null, {
$documentation: "The `true` atom",
value: true
}, AST_Boolean);
/* -----[ TreeWalker ]----- */
function TreeWalker(callback) {
this.visit = callback;
this.stack = [];
};
TreeWalker.prototype = {
_visit: function(node, descend) {
this.stack.push(node);
var ret = this.visit(node, descend ? function(){
descend.call(node);
} : noop);
if (!ret && descend) {
descend.call(node);
}
this.stack.pop();
return ret;
},
parent: function(n) {
return this.stack[this.stack.length - 2 - (n || 0)];
},
push: function (node) {
this.stack.push(node);
},
pop: function() {
return this.stack.pop();
},
self: function() {
return this.stack[this.stack.length - 1];
},
find_parent: function(type) {
var stack = this.stack;
for (var i = stack.length; --i >= 0;) {
var x = stack[i];
if (x instanceof type) return x;
}
},
in_boolean_context: function() {
var stack = this.stack;
var i = stack.length, self = stack[--i];
while (i > 0) {
var p = stack[--i];
if ((p instanceof AST_If && p.condition === self) ||
(p instanceof AST_Conditional && p.condition === self) ||
(p instanceof AST_DWLoop && p.condition === self) ||
(p instanceof AST_For && p.condition === self) ||
(p instanceof AST_UnaryPrefix && p.operator == "!" && p.expression === self))
{
return true;
}
if (!(p instanceof AST_Binary && (p.operator == "&&" || p.operator == "||")))
return false;
self = p;
}
},
loopcontrol_target: function(label) {
var stack = this.stack;
if (label) {
for (var i = stack.length; --i >= 0;) {
var x = stack[i];
if (x instanceof AST_LabeledStatement && x.label.name == label.name) {
return x.body;
}
}
} else {
for (var i = stack.length; --i >= 0;) {
var x = stack[i];
if (x instanceof AST_Switch
|| x instanceof AST_For
|| x instanceof AST_ForIn
|| x instanceof AST_DWLoop) return x;
}
}
}
};

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,267 @@
/***********************************************************************
A JavaScript tokenizer / parser / beautifier / compressor.
https://github.com/mishoo/UglifyJS2
-------------------------------- (C) ---------------------------------
Author: Mihai Bazon
<mihai.bazon@gmail.com>
http://mihai.bazon.net/blog
Distributed under the BSD license:
Copyright 2012 (c) Mihai Bazon <mihai.bazon@gmail.com>
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
* Redistributions of source code must retain the above
copyright notice, this list of conditions and the following
disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials
provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “AS IS” AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
***********************************************************************/
"use strict";
(function(){
var MOZ_TO_ME = {
TryStatement : function(M) {
return new AST_Try({
start : my_start_token(M),
end : my_end_token(M),
body : from_moz(M.block).body,
bcatch : from_moz(M.handlers[0]),
bfinally : M.finalizer ? new AST_Finally(from_moz(M.finalizer)) : null
});
},
CatchClause : function(M) {
return new AST_Catch({
start : my_start_token(M),
end : my_end_token(M),
argname : from_moz(M.param),
body : from_moz(M.body).body
});
},
ObjectExpression : function(M) {
return new AST_Object({
start : my_start_token(M),
end : my_end_token(M),
properties : M.properties.map(function(prop){
var key = prop.key;
var name = key.type == "Identifier" ? key.name : key.value;
var args = {
start : my_start_token(key),
end : my_end_token(prop.value),
key : name,
value : from_moz(prop.value)
};
switch (prop.kind) {
case "init":
return new AST_ObjectKeyVal(args);
case "set":
args.value.name = from_moz(key);
return new AST_ObjectSetter(args);
case "get":
args.value.name = from_moz(key);
return new AST_ObjectGetter(args);
}
})
});
},
SequenceExpression : function(M) {
return AST_Seq.from_array(M.expressions.map(from_moz));
},
MemberExpression : function(M) {
return new (M.computed ? AST_Sub : AST_Dot)({
start : my_start_token(M),
end : my_end_token(M),
property : M.computed ? from_moz(M.property) : M.property.name,
expression : from_moz(M.object)
});
},
SwitchCase : function(M) {
return new (M.test ? AST_Case : AST_Default)({
start : my_start_token(M),
end : my_end_token(M),
expression : from_moz(M.test),
body : M.consequent.map(from_moz)
});
},
Literal : function(M) {
var val = M.value, args = {
start : my_start_token(M),
end : my_end_token(M)
};
if (val === null) return new AST_Null(args);
switch (typeof val) {
case "string":
args.value = val;
return new AST_String(args);
case "number":
args.value = val;
return new AST_Number(args);
case "boolean":
return new (val ? AST_True : AST_False)(args);
default:
args.value = val;
return new AST_RegExp(args);
}
},
UnaryExpression: From_Moz_Unary,
UpdateExpression: From_Moz_Unary,
Identifier: function(M) {
var p = FROM_MOZ_STACK[FROM_MOZ_STACK.length - 2];
return new (M.name == "this" ? AST_This
: p.type == "LabeledStatement" ? AST_Label
: p.type == "VariableDeclarator" && p.id === M ? (p.kind == "const" ? AST_SymbolConst : AST_SymbolVar)
: p.type == "FunctionExpression" ? (p.id === M ? AST_SymbolLambda : AST_SymbolFunarg)
: p.type == "FunctionDeclaration" ? (p.id === M ? AST_SymbolDefun : AST_SymbolFunarg)
: p.type == "CatchClause" ? AST_SymbolCatch
: p.type == "BreakStatement" || p.type == "ContinueStatement" ? AST_LabelRef
: AST_SymbolRef)({
start : my_start_token(M),
end : my_end_token(M),
name : M.name
});
}
};
function From_Moz_Unary(M) {
var prefix = "prefix" in M ? M.prefix
: M.type == "UnaryExpression" ? true : false;
return new (prefix ? AST_UnaryPrefix : AST_UnaryPostfix)({
start : my_start_token(M),
end : my_end_token(M),
operator : M.operator,
expression : from_moz(M.argument)
});
};
var ME_TO_MOZ = {};
map("Node", AST_Node);
map("Program", AST_Toplevel, "body@body");
map("Function", AST_Function, "id>name, params@argnames, body%body");
map("EmptyStatement", AST_EmptyStatement);
map("BlockStatement", AST_BlockStatement, "body@body");
map("ExpressionStatement", AST_SimpleStatement, "expression>body");
map("IfStatement", AST_If, "test>condition, consequent>body, alternate>alternative");
map("LabeledStatement", AST_LabeledStatement, "label>label, body>body");
map("BreakStatement", AST_Break, "label>label");
map("ContinueStatement", AST_Continue, "label>label");
map("WithStatement", AST_With, "object>expression, body>body");
map("SwitchStatement", AST_Switch, "discriminant>expression, cases@body");
map("ReturnStatement", AST_Return, "argument>value");
map("ThrowStatement", AST_Throw, "argument>value");
map("WhileStatement", AST_While, "test>condition, body>body");
map("DoWhileStatement", AST_Do, "test>condition, body>body");
map("ForStatement", AST_For, "init>init, test>condition, update>step, body>body");
map("ForInStatement", AST_ForIn, "left>init, right>object, body>body");
map("DebuggerStatement", AST_Debugger);
map("FunctionDeclaration", AST_Defun, "id>name, params@argnames, body%body");
map("VariableDeclaration", AST_Var, "declarations@definitions");
map("VariableDeclarator", AST_VarDef, "id>name, init>value");
map("ThisExpression", AST_This);
map("ArrayExpression", AST_Array, "elements@elements");
map("FunctionExpression", AST_Function, "id>name, params@argnames, body%body");
map("BinaryExpression", AST_Binary, "operator=operator, left>left, right>right");
map("AssignmentExpression", AST_Assign, "operator=operator, left>left, right>right");
map("LogicalExpression", AST_Binary, "operator=operator, left>left, right>right");
map("ConditionalExpression", AST_Conditional, "test>condition, consequent>consequent, alternate>alternative");
map("NewExpression", AST_New, "callee>expression, arguments@args");
map("CallExpression", AST_Call, "callee>expression, arguments@args");
/* -----[ tools ]----- */
function my_start_token(moznode) {
return new AST_Token({
file : moznode.loc && moznode.loc.source,
line : moznode.loc && moznode.loc.start.line,
col : moznode.loc && moznode.loc.start.column,
pos : moznode.start,
endpos : moznode.start
});
};
function my_end_token(moznode) {
return new AST_Token({
file : moznode.loc && moznode.loc.source,
line : moznode.loc && moznode.loc.end.line,
col : moznode.loc && moznode.loc.end.column,
pos : moznode.end,
endpos : moznode.end
});
};
function map(moztype, mytype, propmap) {
var moz_to_me = "function From_Moz_" + moztype + "(M){\n";
moz_to_me += "return new mytype({\n" +
"start: my_start_token(M),\n" +
"end: my_end_token(M)";
if (propmap) propmap.split(/\s*,\s*/).forEach(function(prop){
var m = /([a-z0-9$_]+)(=|@|>|%)([a-z0-9$_]+)/i.exec(prop);
if (!m) throw new Error("Can't understand property map: " + prop);
var moz = "M." + m[1], how = m[2], my = m[3];
moz_to_me += ",\n" + my + ": ";
if (how == "@") {
moz_to_me += moz + ".map(from_moz)";
} else if (how == ">") {
moz_to_me += "from_moz(" + moz + ")";
} else if (how == "=") {
moz_to_me += moz;
} else if (how == "%") {
moz_to_me += "from_moz(" + moz + ").body";
} else throw new Error("Can't understand operator in propmap: " + prop);
});
moz_to_me += "\n})}";
// moz_to_me = parse(moz_to_me).print_to_string({ beautify: true });
// console.log(moz_to_me);
moz_to_me = new Function("mytype", "my_start_token", "my_end_token", "from_moz", "return(" + moz_to_me + ")")(
mytype, my_start_token, my_end_token, from_moz
);
return MOZ_TO_ME[moztype] = moz_to_me;
};
var FROM_MOZ_STACK = null;
function from_moz(node) {
FROM_MOZ_STACK.push(node);
var ret = node != null ? MOZ_TO_ME[node.type](node) : null;
FROM_MOZ_STACK.pop();
return ret;
};
AST_Node.from_mozilla_ast = function(node){
var save_stack = FROM_MOZ_STACK;
FROM_MOZ_STACK = [];
var ast = from_moz(node);
FROM_MOZ_STACK = save_stack;
return ast;
};
})();

View File

@@ -1,75 +0,0 @@
var jsp = require("./parse-js"),
pro = require("./process");
var BY_TYPE = {};
function HOP(obj, prop) {
return Object.prototype.hasOwnProperty.call(obj, prop);
};
function AST_Node(parent) {
this.parent = parent;
};
AST_Node.prototype.init = function(){};
function DEFINE_NODE_CLASS(type, props, methods) {
var base = methods && methods.BASE || AST_Node;
if (!base) base = AST_Node;
function D(parent, data) {
base.apply(this, arguments);
if (props) props.forEach(function(name, i){
this["_" + name] = data[i];
});
this.init();
};
var P = D.prototype = new AST_Node;
P.node_type = function(){ return type };
if (props) props.forEach(function(name){
var propname = "_" + name;
P["set_" + name] = function(val) {
this[propname] = val;
return this;
};
P["get_" + name] = function() {
return this[propname];
};
});
if (type != null) BY_TYPE[type] = D;
if (methods) for (var i in methods) if (HOP(methods, i)) {
P[i] = methods[i];
}
return D;
};
var AST_String_Node = DEFINE_NODE_CLASS("string", ["value"]);
var AST_Number_Node = DEFINE_NODE_CLASS("num", ["value"]);
var AST_Name_Node = DEFINE_NODE_CLASS("name", ["value"]);
var AST_Statlist_Node = DEFINE_NODE_CLASS(null, ["body"]);
var AST_Root_Node = DEFINE_NODE_CLASS("toplevel", null, { BASE: AST_Statlist_Node });
var AST_Block_Node = DEFINE_NODE_CLASS("block", null, { BASE: AST_Statlist_Node });
var AST_Splice_Node = DEFINE_NODE_CLASS("splice", null, { BASE: AST_Statlist_Node });
var AST_Var_Node = DEFINE_NODE_CLASS("var", ["definitions"]);
var AST_Const_Node = DEFINE_NODE_CLASS("const", ["definitions"]);
var AST_Try_Node = DEFINE_NODE_CLASS("try", ["body", "catch", "finally"]);
var AST_Throw_Node = DEFINE_NODE_CLASS("throw", ["exception"]);
var AST_New_Node = DEFINE_NODE_CLASS("new", ["constructor", "arguments"]);
var AST_Switch_Node = DEFINE_NODE_CLASS("switch", ["expression", "branches"]);
var AST_Switch_Branch_Node = DEFINE_NODE_CLASS(null, ["expression", "body"]);
var AST_Break_Node = DEFINE_NODE_CLASS("break", ["label"]);
var AST_Continue_Node = DEFINE_NODE_CLASS("continue", ["label"]);
var AST_Assign_Node = DEFINE_NODE_CLASS("assign", ["operator", "lvalue", "rvalue"]);
var AST_Dot_Node = DEFINE_NODE_CLASS("dot", ["expression", "name"]);
var AST_Call_Node = DEFINE_NODE_CLASS("call", ["function", "arguments"]);
var AST_Lambda_Node = DEFINE_NODE_CLASS(null, ["name", "arguments", "body"])
var AST_Function_Node = DEFINE_NODE_CLASS("function", null, AST_Lambda_Node);
var AST_Defun_Node = DEFINE_NODE_CLASS("defun", null, AST_Lambda_Node);
var AST_If_Node = DEFINE_NODE_CLASS("if", ["condition", "then", "else"]);

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,581 @@
/***********************************************************************
A JavaScript tokenizer / parser / beautifier / compressor.
https://github.com/mishoo/UglifyJS2
-------------------------------- (C) ---------------------------------
Author: Mihai Bazon
<mihai.bazon@gmail.com>
http://mihai.bazon.net/blog
Distributed under the BSD license:
Copyright 2012 (c) Mihai Bazon <mihai.bazon@gmail.com>
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
* Redistributions of source code must retain the above
copyright notice, this list of conditions and the following
disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials
provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “AS IS” AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
***********************************************************************/
"use strict";
function SymbolDef(scope, index, orig) {
this.name = orig.name;
this.orig = [ orig ];
this.scope = scope;
this.references = [];
this.global = false;
this.mangled_name = null;
this.undeclared = false;
this.constant = false;
this.index = index;
};
SymbolDef.prototype = {
unmangleable: function(options) {
return (this.global && !(options && options.toplevel))
|| this.undeclared
|| (!(options && options.eval) && (this.scope.uses_eval || this.scope.uses_with));
},
mangle: function(options) {
if (!this.mangled_name && !this.unmangleable(options)) {
var s = this.scope;
if (this.orig[0] instanceof AST_SymbolLambda && !options.screw_ie8)
s = s.parent_scope;
this.mangled_name = s.next_mangled(options);
}
}
};
AST_Toplevel.DEFMETHOD("figure_out_scope", function(){
// This does what ast_add_scope did in UglifyJS v1.
//
// Part of it could be done at parse time, but it would complicate
// the parser (and it's already kinda complex). It's also worth
// having it separated because we might need to call it multiple
// times on the same tree.
// pass 1: setup scope chaining and handle definitions
var self = this;
var scope = self.parent_scope = null;
var labels = new Dictionary();
var nesting = 0;
var tw = new TreeWalker(function(node, descend){
if (node instanceof AST_Scope) {
node.init_scope_vars(nesting);
var save_scope = node.parent_scope = scope;
var save_labels = labels;
++nesting;
scope = node;
labels = new Dictionary();
descend();
labels = save_labels;
scope = save_scope;
--nesting;
return true; // don't descend again in TreeWalker
}
if (node instanceof AST_Directive) {
node.scope = scope;
push_uniq(scope.directives, node.value);
return true;
}
if (node instanceof AST_With) {
for (var s = scope; s; s = s.parent_scope)
s.uses_with = true;
return;
}
if (node instanceof AST_LabeledStatement) {
var l = node.label;
if (labels.has(l.name))
throw new Error(string_template("Label {name} defined twice", l));
labels.set(l.name, l);
descend();
labels.del(l.name);
return true; // no descend again
}
if (node instanceof AST_Symbol) {
node.scope = scope;
}
if (node instanceof AST_Label) {
node.thedef = node;
node.init_scope_vars();
}
if (node instanceof AST_SymbolLambda) {
scope.def_function(node);
}
else if (node instanceof AST_SymbolDefun) {
// Careful here, the scope where this should be defined is
// the parent scope. The reason is that we enter a new
// scope when we encounter the AST_Defun node (which is
// instanceof AST_Scope) but we get to the symbol a bit
// later.
(node.scope = scope.parent_scope).def_function(node);
}
else if (node instanceof AST_SymbolVar
|| node instanceof AST_SymbolConst) {
var def = scope.def_variable(node);
def.constant = node instanceof AST_SymbolConst;
def.init = tw.parent().value;
}
else if (node instanceof AST_SymbolCatch) {
// XXX: this is wrong according to ECMA-262 (12.4). the
// `catch` argument name should be visible only inside the
// catch block. For a quick fix AST_Catch should inherit
// from AST_Scope. Keeping it this way because of IE,
// which doesn't obey the standard. (it introduces the
// identifier in the enclosing scope)
scope.def_variable(node);
}
if (node instanceof AST_LabelRef) {
var sym = labels.get(node.name);
if (!sym) throw new Error(string_template("Undefined label {name} [{line},{col}]", {
name: node.name,
line: node.start.line,
col: node.start.col
}));
node.thedef = sym;
}
});
self.walk(tw);
// pass 2: find back references and eval
var func = null;
var globals = self.globals = new Dictionary();
var tw = new TreeWalker(function(node, descend){
if (node instanceof AST_Lambda) {
var prev_func = func;
func = node;
descend();
func = prev_func;
return true;
}
if (node instanceof AST_LabelRef) {
node.reference();
return true;
}
if (node instanceof AST_SymbolRef) {
var name = node.name;
var sym = node.scope.find_variable(name);
if (!sym) {
var g;
if (globals.has(name)) {
g = globals.get(name);
} else {
g = new SymbolDef(self, globals.size(), node);
g.undeclared = true;
g.global = true;
globals.set(name, g);
}
node.thedef = g;
if (name == "eval" && tw.parent() instanceof AST_Call) {
for (var s = node.scope; s && !s.uses_eval; s = s.parent_scope)
s.uses_eval = true;
}
if (name == "arguments") {
func.uses_arguments = true;
}
} else {
node.thedef = sym;
}
node.reference();
return true;
}
});
self.walk(tw);
});
AST_Scope.DEFMETHOD("init_scope_vars", function(nesting){
this.directives = []; // contains the directives defined in this scope, i.e. "use strict"
this.variables = new Dictionary(); // map name to AST_SymbolVar (variables defined in this scope; includes functions)
this.functions = new Dictionary(); // map name to AST_SymbolDefun (functions defined in this scope)
this.uses_with = false; // will be set to true if this or some nested scope uses the `with` statement
this.uses_eval = false; // will be set to true if this or nested scope uses the global `eval`
this.parent_scope = null; // the parent scope
this.enclosed = []; // a list of variables from this or outer scope(s) that are referenced from this or inner scopes
this.cname = -1; // the current index for mangling functions/variables
this.nesting = nesting; // the nesting level of this scope (0 means toplevel)
});
AST_Scope.DEFMETHOD("strict", function(){
return this.has_directive("use strict");
});
AST_Lambda.DEFMETHOD("init_scope_vars", function(){
AST_Scope.prototype.init_scope_vars.apply(this, arguments);
this.uses_arguments = false;
});
AST_SymbolRef.DEFMETHOD("reference", function() {
var def = this.definition();
def.references.push(this);
var s = this.scope;
while (s) {
push_uniq(s.enclosed, def);
if (s === def.scope) break;
s = s.parent_scope;
}
this.frame = this.scope.nesting - def.scope.nesting;
});
AST_Label.DEFMETHOD("init_scope_vars", function(){
this.references = [];
});
AST_LabelRef.DEFMETHOD("reference", function(){
this.thedef.references.push(this);
});
AST_Scope.DEFMETHOD("find_variable", function(name){
if (name instanceof AST_Symbol) name = name.name;
return this.variables.get(name)
|| (this.parent_scope && this.parent_scope.find_variable(name));
});
AST_Scope.DEFMETHOD("has_directive", function(value){
return this.parent_scope && this.parent_scope.has_directive(value)
|| (this.directives.indexOf(value) >= 0 ? this : null);
});
AST_Scope.DEFMETHOD("def_function", function(symbol){
this.functions.set(symbol.name, this.def_variable(symbol));
});
AST_Scope.DEFMETHOD("def_variable", function(symbol){
var def;
if (!this.variables.has(symbol.name)) {
def = new SymbolDef(this, this.variables.size(), symbol);
this.variables.set(symbol.name, def);
def.global = !this.parent_scope;
} else {
def = this.variables.get(symbol.name);
def.orig.push(symbol);
}
return symbol.thedef = def;
});
AST_Scope.DEFMETHOD("next_mangled", function(options){
var ext = this.enclosed;
out: while (true) {
var m = base54(++this.cname);
if (!is_identifier(m)) continue; // skip over "do"
// we must ensure that the mangled name does not shadow a name
// from some parent scope that is referenced in this or in
// inner scopes.
for (var i = ext.length; --i >= 0;) {
var sym = ext[i];
var name = sym.mangled_name || (sym.unmangleable(options) && sym.name);
if (m == name) continue out;
}
return m;
}
});
AST_Scope.DEFMETHOD("references", function(sym){
if (sym instanceof AST_Symbol) sym = sym.definition();
return this.enclosed.indexOf(sym) < 0 ? null : sym;
});
AST_Symbol.DEFMETHOD("unmangleable", function(options){
return this.definition().unmangleable(options);
});
// property accessors are not mangleable
AST_SymbolAccessor.DEFMETHOD("unmangleable", function(){
return true;
});
// labels are always mangleable
AST_Label.DEFMETHOD("unmangleable", function(){
return false;
});
AST_Symbol.DEFMETHOD("unreferenced", function(){
return this.definition().references.length == 0
&& !(this.scope.uses_eval || this.scope.uses_with);
});
AST_Symbol.DEFMETHOD("undeclared", function(){
return this.definition().undeclared;
});
AST_LabelRef.DEFMETHOD("undeclared", function(){
return false;
});
AST_Label.DEFMETHOD("undeclared", function(){
return false;
});
AST_Symbol.DEFMETHOD("definition", function(){
return this.thedef;
});
AST_Symbol.DEFMETHOD("global", function(){
return this.definition().global;
});
AST_Toplevel.DEFMETHOD("_default_mangler_options", function(options){
return defaults(options, {
except : [],
eval : false,
sort : false,
toplevel : false,
screw_ie8 : false
});
});
AST_Toplevel.DEFMETHOD("mangle_names", function(options){
options = this._default_mangler_options(options);
// We only need to mangle declaration nodes. Special logic wired
// into the code generator will display the mangled name if it's
// present (and for AST_SymbolRef-s it'll use the mangled name of
// the AST_SymbolDeclaration that it points to).
var lname = -1;
var to_mangle = [];
var tw = new TreeWalker(function(node, descend){
if (node instanceof AST_LabeledStatement) {
// lname is incremented when we get to the AST_Label
var save_nesting = lname;
descend();
lname = save_nesting;
return true; // don't descend again in TreeWalker
}
if (node instanceof AST_Scope) {
var p = tw.parent(), a = [];
node.variables.each(function(symbol){
if (options.except.indexOf(symbol.name) < 0) {
a.push(symbol);
}
});
if (options.sort) a.sort(function(a, b){
return b.references.length - a.references.length;
});
to_mangle.push.apply(to_mangle, a);
return;
}
if (node instanceof AST_Label) {
var name;
do name = base54(++lname); while (!is_identifier(name));
node.mangled_name = name;
return true;
}
});
this.walk(tw);
to_mangle.forEach(function(def){ def.mangle(options) });
});
AST_Toplevel.DEFMETHOD("compute_char_frequency", function(options){
options = this._default_mangler_options(options);
var tw = new TreeWalker(function(node){
if (node instanceof AST_Constant)
base54.consider(node.print_to_string());
else if (node instanceof AST_Return)
base54.consider("return");
else if (node instanceof AST_Throw)
base54.consider("throw");
else if (node instanceof AST_Continue)
base54.consider("continue");
else if (node instanceof AST_Break)
base54.consider("break");
else if (node instanceof AST_Debugger)
base54.consider("debugger");
else if (node instanceof AST_Directive)
base54.consider(node.value);
else if (node instanceof AST_While)
base54.consider("while");
else if (node instanceof AST_Do)
base54.consider("do while");
else if (node instanceof AST_If) {
base54.consider("if");
if (node.alternative) base54.consider("else");
}
else if (node instanceof AST_Var)
base54.consider("var");
else if (node instanceof AST_Const)
base54.consider("const");
else if (node instanceof AST_Lambda)
base54.consider("function");
else if (node instanceof AST_For)
base54.consider("for");
else if (node instanceof AST_ForIn)
base54.consider("for in");
else if (node instanceof AST_Switch)
base54.consider("switch");
else if (node instanceof AST_Case)
base54.consider("case");
else if (node instanceof AST_Default)
base54.consider("default");
else if (node instanceof AST_With)
base54.consider("with");
else if (node instanceof AST_ObjectSetter)
base54.consider("set" + node.key);
else if (node instanceof AST_ObjectGetter)
base54.consider("get" + node.key);
else if (node instanceof AST_ObjectKeyVal)
base54.consider(node.key);
else if (node instanceof AST_New)
base54.consider("new");
else if (node instanceof AST_This)
base54.consider("this");
else if (node instanceof AST_Try)
base54.consider("try");
else if (node instanceof AST_Catch)
base54.consider("catch");
else if (node instanceof AST_Finally)
base54.consider("finally");
else if (node instanceof AST_Symbol && node.unmangleable(options))
base54.consider(node.name);
else if (node instanceof AST_Unary || node instanceof AST_Binary)
base54.consider(node.operator);
else if (node instanceof AST_Dot)
base54.consider(node.property);
});
this.walk(tw);
base54.sort();
});
var base54 = (function() {
var string = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ$_0123456789";
var chars, frequency;
function reset() {
frequency = Object.create(null);
chars = string.split("").map(function(ch){ return ch.charCodeAt(0) });
chars.forEach(function(ch){ frequency[ch] = 0 });
}
base54.consider = function(str){
for (var i = str.length; --i >= 0;) {
var code = str.charCodeAt(i);
if (code in frequency) ++frequency[code];
}
};
base54.sort = function() {
chars = mergeSort(chars, function(a, b){
if (is_digit(a) && !is_digit(b)) return 1;
if (is_digit(b) && !is_digit(a)) return -1;
return frequency[b] - frequency[a];
});
};
base54.reset = reset;
reset();
base54.get = function(){ return chars };
base54.freq = function(){ return frequency };
function base54(num) {
var ret = "", base = 54;
do {
ret += String.fromCharCode(chars[num % base]);
num = Math.floor(num / base);
base = 64;
} while (num > 0);
return ret;
};
return base54;
})();
AST_Toplevel.DEFMETHOD("scope_warnings", function(options){
options = defaults(options, {
undeclared : false, // this makes a lot of noise
unreferenced : true,
assign_to_global : true,
func_arguments : true,
nested_defuns : true,
eval : true
});
var tw = new TreeWalker(function(node){
if (options.undeclared
&& node instanceof AST_SymbolRef
&& node.undeclared())
{
// XXX: this also warns about JS standard names,
// i.e. Object, Array, parseInt etc. Should add a list of
// exceptions.
AST_Node.warn("Undeclared symbol: {name} [{file}:{line},{col}]", {
name: node.name,
file: node.start.file,
line: node.start.line,
col: node.start.col
});
}
if (options.assign_to_global)
{
var sym = null;
if (node instanceof AST_Assign && node.left instanceof AST_SymbolRef)
sym = node.left;
else if (node instanceof AST_ForIn && node.init instanceof AST_SymbolRef)
sym = node.init;
if (sym
&& (sym.undeclared()
|| (sym.global() && sym.scope !== sym.definition().scope))) {
AST_Node.warn("{msg}: {name} [{file}:{line},{col}]", {
msg: sym.undeclared() ? "Accidental global?" : "Assignment to global",
name: sym.name,
file: sym.start.file,
line: sym.start.line,
col: sym.start.col
});
}
}
if (options.eval
&& node instanceof AST_SymbolRef
&& node.undeclared()
&& node.name == "eval") {
AST_Node.warn("Eval is used [{file}:{line},{col}]", node.start);
}
if (options.unreferenced
&& (node instanceof AST_SymbolDeclaration || node instanceof AST_Label)
&& node.unreferenced()) {
AST_Node.warn("{type} {name} is declared but not referenced [{file}:{line},{col}]", {
type: node instanceof AST_Label ? "Label" : "Symbol",
name: node.name,
file: node.start.file,
line: node.start.line,
col: node.start.col
});
}
if (options.func_arguments
&& node instanceof AST_Lambda
&& node.uses_arguments) {
AST_Node.warn("arguments used in function {name} [{file}:{line},{col}]", {
name: node.name ? node.name.name : "anonymous",
file: node.start.file,
line: node.start.line,
col: node.start.col
});
}
if (options.nested_defuns
&& node instanceof AST_Defun
&& !(tw.parent() instanceof AST_Scope)) {
AST_Node.warn("Function {name} declared in nested statement \"{type}\" [{file}:{line},{col}]", {
name: node.name.name,
type: tw.parent().TYPE,
file: node.start.file,
line: node.start.line,
col: node.start.col
});
}
});
this.walk(tw);
});

View File

@@ -0,0 +1,81 @@
/***********************************************************************
A JavaScript tokenizer / parser / beautifier / compressor.
https://github.com/mishoo/UglifyJS2
-------------------------------- (C) ---------------------------------
Author: Mihai Bazon
<mihai.bazon@gmail.com>
http://mihai.bazon.net/blog
Distributed under the BSD license:
Copyright 2012 (c) Mihai Bazon <mihai.bazon@gmail.com>
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
* Redistributions of source code must retain the above
copyright notice, this list of conditions and the following
disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials
provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “AS IS” AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
***********************************************************************/
"use strict";
// a small wrapper around fitzgen's source-map library
function SourceMap(options) {
options = defaults(options, {
file : null,
root : null,
orig : null,
});
var generator = new MOZ_SourceMap.SourceMapGenerator({
file : options.file,
sourceRoot : options.root
});
var orig_map = options.orig && new MOZ_SourceMap.SourceMapConsumer(options.orig);
function add(source, gen_line, gen_col, orig_line, orig_col, name) {
if (orig_map) {
var info = orig_map.originalPositionFor({
line: orig_line,
column: orig_col
});
source = info.source;
orig_line = info.line;
orig_col = info.column;
name = info.name;
}
generator.addMapping({
generated : { line: gen_line, column: gen_col },
original : { line: orig_line, column: orig_col },
source : source,
name : name
});
};
return {
add : add,
get : function() { return generator },
toString : function() { return generator.toString() }
};
};

View File

@@ -1,73 +0,0 @@
var jsp = require("./parse-js"),
pro = require("./process"),
slice = jsp.slice,
member = jsp.member,
curry = jsp.curry,
MAP = pro.MAP,
PRECEDENCE = jsp.PRECEDENCE,
OPERATORS = jsp.OPERATORS;
function ast_squeeze_more(ast) {
var w = pro.ast_walker(), walk = w.walk, scope;
function with_scope(s, cont) {
var save = scope, ret;
scope = s;
ret = cont();
scope = save;
return ret;
};
function _lambda(name, args, body) {
return [ this[0], name, args, with_scope(body.scope, curry(MAP, body, walk)) ];
};
return w.with_walkers({
"toplevel": function(body) {
return [ this[0], with_scope(this.scope, curry(MAP, body, walk)) ];
},
"function": _lambda,
"defun": _lambda,
"new": function(ctor, args) {
if (ctor[0] == "name") {
if (ctor[1] == "Array" && !scope.has("Array")) {
if (args.length != 1) {
return [ "array", args ];
} else {
return walk([ "call", [ "name", "Array" ], args ]);
}
} else if (ctor[1] == "Object" && !scope.has("Object")) {
if (!args.length) {
return [ "object", [] ];
} else {
return walk([ "call", [ "name", "Object" ], args ]);
}
} else if ((ctor[1] == "RegExp" || ctor[1] == "Function" || ctor[1] == "Error") && !scope.has(ctor[1])) {
return walk([ "call", [ "name", ctor[1] ], args]);
}
}
},
"call": function(expr, args) {
if (expr[0] == "dot" && expr[1][0] == "string" && args.length == 1
&& (args[0][1] > 0 && expr[2] == "substring" || expr[2] == "substr")) {
return [ "call", [ "dot", expr[1], "slice"], args];
}
if (expr[0] == "dot" && expr[2] == "toString" && args.length == 0) {
// foo.toString() ==> foo+""
return [ "binary", "+", expr[1], [ "string", "" ]];
}
if (expr[0] == "name") {
if (expr[1] == "Array" && args.length != 1 && !scope.has("Array")) {
return [ "array", args ];
}
if (expr[1] == "Object" && !args.length && !scope.has("Object")) {
return [ "object", [] ];
}
if (expr[1] == "String" && !scope.has("String")) {
return [ "binary", "+", args[0], [ "string", "" ]];
}
}
}
}, function() {
return walk(pro.ast_add_scope(ast));
});
};
exports.ast_squeeze_more = ast_squeeze_more;

View File

@@ -0,0 +1,217 @@
/***********************************************************************
A JavaScript tokenizer / parser / beautifier / compressor.
https://github.com/mishoo/UglifyJS2
-------------------------------- (C) ---------------------------------
Author: Mihai Bazon
<mihai.bazon@gmail.com>
http://mihai.bazon.net/blog
Distributed under the BSD license:
Copyright 2012 (c) Mihai Bazon <mihai.bazon@gmail.com>
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
* Redistributions of source code must retain the above
copyright notice, this list of conditions and the following
disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials
provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “AS IS” AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
***********************************************************************/
"use strict";
// Tree transformer helpers.
function TreeTransformer(before, after) {
TreeWalker.call(this);
this.before = before;
this.after = after;
}
TreeTransformer.prototype = new TreeWalker;
(function(undefined){
function _(node, descend) {
node.DEFMETHOD("transform", function(tw, in_list){
var x, y;
tw.push(this);
if (tw.before) x = tw.before(this, descend, in_list);
if (x === undefined) {
if (!tw.after) {
x = this;
descend(x, tw);
} else {
tw.stack[tw.stack.length - 1] = x = this.clone();
descend(x, tw);
y = tw.after(x, in_list);
if (y !== undefined) x = y;
}
}
tw.pop();
return x;
});
};
function do_list(list, tw) {
return MAP(list, function(node){
return node.transform(tw, true);
});
};
_(AST_Node, noop);
_(AST_LabeledStatement, function(self, tw){
self.label = self.label.transform(tw);
self.body = self.body.transform(tw);
});
_(AST_SimpleStatement, function(self, tw){
self.body = self.body.transform(tw);
});
_(AST_Block, function(self, tw){
self.body = do_list(self.body, tw);
});
_(AST_DWLoop, function(self, tw){
self.condition = self.condition.transform(tw);
self.body = self.body.transform(tw);
});
_(AST_For, function(self, tw){
if (self.init) self.init = self.init.transform(tw);
if (self.condition) self.condition = self.condition.transform(tw);
if (self.step) self.step = self.step.transform(tw);
self.body = self.body.transform(tw);
});
_(AST_ForIn, function(self, tw){
self.init = self.init.transform(tw);
self.object = self.object.transform(tw);
self.body = self.body.transform(tw);
});
_(AST_With, function(self, tw){
self.expression = self.expression.transform(tw);
self.body = self.body.transform(tw);
});
_(AST_Exit, function(self, tw){
if (self.value) self.value = self.value.transform(tw);
});
_(AST_LoopControl, function(self, tw){
if (self.label) self.label = self.label.transform(tw);
});
_(AST_If, function(self, tw){
self.condition = self.condition.transform(tw);
self.body = self.body.transform(tw);
if (self.alternative) self.alternative = self.alternative.transform(tw);
});
_(AST_Switch, function(self, tw){
self.expression = self.expression.transform(tw);
self.body = do_list(self.body, tw);
});
_(AST_Case, function(self, tw){
self.expression = self.expression.transform(tw);
self.body = do_list(self.body, tw);
});
_(AST_Try, function(self, tw){
self.body = do_list(self.body, tw);
if (self.bcatch) self.bcatch = self.bcatch.transform(tw);
if (self.bfinally) self.bfinally = self.bfinally.transform(tw);
});
_(AST_Catch, function(self, tw){
self.argname = self.argname.transform(tw);
self.body = do_list(self.body, tw);
});
_(AST_Definitions, function(self, tw){
self.definitions = do_list(self.definitions, tw);
});
_(AST_VarDef, function(self, tw){
if (self.value) self.value = self.value.transform(tw);
});
_(AST_Lambda, function(self, tw){
if (self.name) self.name = self.name.transform(tw);
self.argnames = do_list(self.argnames, tw);
self.body = do_list(self.body, tw);
});
_(AST_Call, function(self, tw){
self.expression = self.expression.transform(tw);
self.args = do_list(self.args, tw);
});
_(AST_Seq, function(self, tw){
self.car = self.car.transform(tw);
self.cdr = self.cdr.transform(tw);
});
_(AST_Dot, function(self, tw){
self.expression = self.expression.transform(tw);
});
_(AST_Sub, function(self, tw){
self.expression = self.expression.transform(tw);
self.property = self.property.transform(tw);
});
_(AST_Unary, function(self, tw){
self.expression = self.expression.transform(tw);
});
_(AST_Binary, function(self, tw){
self.left = self.left.transform(tw);
self.right = self.right.transform(tw);
});
_(AST_Conditional, function(self, tw){
self.condition = self.condition.transform(tw);
self.consequent = self.consequent.transform(tw);
self.alternative = self.alternative.transform(tw);
});
_(AST_Array, function(self, tw){
self.elements = do_list(self.elements, tw);
});
_(AST_Object, function(self, tw){
self.properties = do_list(self.properties, tw);
});
_(AST_ObjectProperty, function(self, tw){
self.value = self.value.transform(tw);
});
})();

View File

@@ -0,0 +1,295 @@
/***********************************************************************
A JavaScript tokenizer / parser / beautifier / compressor.
https://github.com/mishoo/UglifyJS2
-------------------------------- (C) ---------------------------------
Author: Mihai Bazon
<mihai.bazon@gmail.com>
http://mihai.bazon.net/blog
Distributed under the BSD license:
Copyright 2012 (c) Mihai Bazon <mihai.bazon@gmail.com>
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
* Redistributions of source code must retain the above
copyright notice, this list of conditions and the following
disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials
provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “AS IS” AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
***********************************************************************/
"use strict";
function array_to_hash(a) {
var ret = Object.create(null);
for (var i = 0; i < a.length; ++i)
ret[a[i]] = true;
return ret;
};
function slice(a, start) {
return Array.prototype.slice.call(a, start || 0);
};
function characters(str) {
return str.split("");
};
function member(name, array) {
for (var i = array.length; --i >= 0;)
if (array[i] == name)
return true;
return false;
};
function find_if(func, array) {
for (var i = 0, n = array.length; i < n; ++i) {
if (func(array[i]))
return array[i];
}
};
function repeat_string(str, i) {
if (i <= 0) return "";
if (i == 1) return str;
var d = repeat_string(str, i >> 1);
d += d;
if (i & 1) d += str;
return d;
};
function DefaultsError(msg, defs) {
this.msg = msg;
this.defs = defs;
};
function defaults(args, defs, croak) {
if (args === true)
args = {};
var ret = args || {};
if (croak) for (var i in ret) if (ret.hasOwnProperty(i) && !defs.hasOwnProperty(i))
throw new DefaultsError("`" + i + "` is not a supported option", defs);
for (var i in defs) if (defs.hasOwnProperty(i)) {
ret[i] = (args && args.hasOwnProperty(i)) ? args[i] : defs[i];
}
return ret;
};
function merge(obj, ext) {
for (var i in ext) if (ext.hasOwnProperty(i)) {
obj[i] = ext[i];
}
return obj;
};
function noop() {};
var MAP = (function(){
function MAP(a, f, backwards) {
var ret = [], top = [], i;
function doit() {
var val = f(a[i], i);
var is_last = val instanceof Last;
if (is_last) val = val.v;
if (val instanceof AtTop) {
val = val.v;
if (val instanceof Splice) {
top.push.apply(top, backwards ? val.v.slice().reverse() : val.v);
} else {
top.push(val);
}
}
else if (val !== skip) {
if (val instanceof Splice) {
ret.push.apply(ret, backwards ? val.v.slice().reverse() : val.v);
} else {
ret.push(val);
}
}
return is_last;
};
if (a instanceof Array) {
if (backwards) {
for (i = a.length; --i >= 0;) if (doit()) break;
ret.reverse();
top.reverse();
} else {
for (i = 0; i < a.length; ++i) if (doit()) break;
}
}
else {
for (i in a) if (a.hasOwnProperty(i)) if (doit()) break;
}
return top.concat(ret);
};
MAP.at_top = function(val) { return new AtTop(val) };
MAP.splice = function(val) { return new Splice(val) };
MAP.last = function(val) { return new Last(val) };
var skip = MAP.skip = {};
function AtTop(val) { this.v = val };
function Splice(val) { this.v = val };
function Last(val) { this.v = val };
return MAP;
})();
function push_uniq(array, el) {
if (array.indexOf(el) < 0)
array.push(el);
};
function string_template(text, props) {
return text.replace(/\{(.+?)\}/g, function(str, p){
return props[p];
});
};
function remove(array, el) {
for (var i = array.length; --i >= 0;) {
if (array[i] === el) array.splice(i, 1);
}
};
function mergeSort(array, cmp) {
if (array.length < 2) return array.slice();
function merge(a, b) {
var r = [], ai = 0, bi = 0, i = 0;
while (ai < a.length && bi < b.length) {
cmp(a[ai], b[bi]) <= 0
? r[i++] = a[ai++]
: r[i++] = b[bi++];
}
if (ai < a.length) r.push.apply(r, a.slice(ai));
if (bi < b.length) r.push.apply(r, b.slice(bi));
return r;
};
function _ms(a) {
if (a.length <= 1)
return a;
var m = Math.floor(a.length / 2), left = a.slice(0, m), right = a.slice(m);
left = _ms(left);
right = _ms(right);
return merge(left, right);
};
return _ms(array);
};
function set_difference(a, b) {
return a.filter(function(el){
return b.indexOf(el) < 0;
});
};
function set_intersection(a, b) {
return a.filter(function(el){
return b.indexOf(el) >= 0;
});
};
// this function is taken from Acorn [1], written by Marijn Haverbeke
// [1] https://github.com/marijnh/acorn
function makePredicate(words) {
if (!(words instanceof Array)) words = words.split(" ");
var f = "", cats = [];
out: for (var i = 0; i < words.length; ++i) {
for (var j = 0; j < cats.length; ++j)
if (cats[j][0].length == words[i].length) {
cats[j].push(words[i]);
continue out;
}
cats.push([words[i]]);
}
function compareTo(arr) {
if (arr.length == 1) return f += "return str === " + JSON.stringify(arr[0]) + ";";
f += "switch(str){";
for (var i = 0; i < arr.length; ++i) f += "case " + JSON.stringify(arr[i]) + ":";
f += "return true}return false;";
}
// When there are more than three length categories, an outer
// switch first dispatches on the lengths, to save on comparisons.
if (cats.length > 3) {
cats.sort(function(a, b) {return b.length - a.length;});
f += "switch(str.length){";
for (var i = 0; i < cats.length; ++i) {
var cat = cats[i];
f += "case " + cat[0].length + ":";
compareTo(cat);
}
f += "}";
// Otherwise, simply generate a flat `switch` statement.
} else {
compareTo(words);
}
return new Function("str", f);
};
function all(array, predicate) {
for (var i = array.length; --i >= 0;)
if (!predicate(array[i]))
return false;
return true;
};
function Dictionary() {
this._values = Object.create(null);
this._size = 0;
};
Dictionary.prototype = {
set: function(key, val) {
if (!this.has(key)) ++this._size;
this._values["$" + key] = val;
return this;
},
add: function(key, val) {
if (this.has(key)) {
this.get(key).push(val);
} else {
this.set(key, [ val ]);
}
return this;
},
get: function(key) { return this._values["$" + key] },
del: function(key) {
if (this.has(key)) {
--this._size;
delete this._values["$" + key];
}
return this;
},
has: function(key) { return ("$" + key) in this._values },
each: function(f) {
for (var i in this._values)
f(this._values[i], i.substr(1));
},
size: function() {
return this._size;
},
map: function(f) {
var ret = [];
for (var i in this._values)
ret.push(f(this._values[i], i.substr(1)));
return ret;
}
};

View File

@@ -0,0 +1,19 @@
Copyright (c) 2010 Caolan McMahon
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,11 @@
{
"name": "async",
"repo": "caolan/async",
"description": "Higher-order functions and common patterns for asynchronous code",
"version": "0.1.23",
"keywords": [],
"dependencies": {},
"development": {},
"main": "lib/async.js",
"scripts": [ "lib/async.js" ]
}

View File

@@ -0,0 +1,955 @@
/*global setImmediate: false, setTimeout: false, console: false */
(function () {
var async = {};
// global on the server, window in the browser
var root, previous_async;
root = this;
if (root != null) {
previous_async = root.async;
}
async.noConflict = function () {
root.async = previous_async;
return async;
};
function only_once(fn) {
var called = false;
return function() {
if (called) throw new Error("Callback was already called.");
called = true;
fn.apply(root, arguments);
}
}
//// cross-browser compatiblity functions ////
var _each = function (arr, iterator) {
if (arr.forEach) {
return arr.forEach(iterator);
}
for (var i = 0; i < arr.length; i += 1) {
iterator(arr[i], i, arr);
}
};
var _map = function (arr, iterator) {
if (arr.map) {
return arr.map(iterator);
}
var results = [];
_each(arr, function (x, i, a) {
results.push(iterator(x, i, a));
});
return results;
};
var _reduce = function (arr, iterator, memo) {
if (arr.reduce) {
return arr.reduce(iterator, memo);
}
_each(arr, function (x, i, a) {
memo = iterator(memo, x, i, a);
});
return memo;
};
var _keys = function (obj) {
if (Object.keys) {
return Object.keys(obj);
}
var keys = [];
for (var k in obj) {
if (obj.hasOwnProperty(k)) {
keys.push(k);
}
}
return keys;
};
//// exported async module functions ////
//// nextTick implementation with browser-compatible fallback ////
if (typeof process === 'undefined' || !(process.nextTick)) {
if (typeof setImmediate === 'function') {
async.nextTick = function (fn) {
// not a direct alias for IE10 compatibility
setImmediate(fn);
};
async.setImmediate = async.nextTick;
}
else {
async.nextTick = function (fn) {
setTimeout(fn, 0);
};
async.setImmediate = async.nextTick;
}
}
else {
async.nextTick = process.nextTick;
if (typeof setImmediate !== 'undefined') {
async.setImmediate = setImmediate;
}
else {
async.setImmediate = async.nextTick;
}
}
async.each = function (arr, iterator, callback) {
callback = callback || function () {};
if (!arr.length) {
return callback();
}
var completed = 0;
_each(arr, function (x) {
iterator(x, only_once(function (err) {
if (err) {
callback(err);
callback = function () {};
}
else {
completed += 1;
if (completed >= arr.length) {
callback(null);
}
}
}));
});
};
async.forEach = async.each;
async.eachSeries = function (arr, iterator, callback) {
callback = callback || function () {};
if (!arr.length) {
return callback();
}
var completed = 0;
var iterate = function () {
iterator(arr[completed], function (err) {
if (err) {
callback(err);
callback = function () {};
}
else {
completed += 1;
if (completed >= arr.length) {
callback(null);
}
else {
iterate();
}
}
});
};
iterate();
};
async.forEachSeries = async.eachSeries;
async.eachLimit = function (arr, limit, iterator, callback) {
var fn = _eachLimit(limit);
fn.apply(null, [arr, iterator, callback]);
};
async.forEachLimit = async.eachLimit;
var _eachLimit = function (limit) {
return function (arr, iterator, callback) {
callback = callback || function () {};
if (!arr.length || limit <= 0) {
return callback();
}
var completed = 0;
var started = 0;
var running = 0;
(function replenish () {
if (completed >= arr.length) {
return callback();
}
while (running < limit && started < arr.length) {
started += 1;
running += 1;
iterator(arr[started - 1], function (err) {
if (err) {
callback(err);
callback = function () {};
}
else {
completed += 1;
running -= 1;
if (completed >= arr.length) {
callback();
}
else {
replenish();
}
}
});
}
})();
};
};
var doParallel = function (fn) {
return function () {
var args = Array.prototype.slice.call(arguments);
return fn.apply(null, [async.each].concat(args));
};
};
var doParallelLimit = function(limit, fn) {
return function () {
var args = Array.prototype.slice.call(arguments);
return fn.apply(null, [_eachLimit(limit)].concat(args));
};
};
var doSeries = function (fn) {
return function () {
var args = Array.prototype.slice.call(arguments);
return fn.apply(null, [async.eachSeries].concat(args));
};
};
var _asyncMap = function (eachfn, arr, iterator, callback) {
var results = [];
arr = _map(arr, function (x, i) {
return {index: i, value: x};
});
eachfn(arr, function (x, callback) {
iterator(x.value, function (err, v) {
results[x.index] = v;
callback(err);
});
}, function (err) {
callback(err, results);
});
};
async.map = doParallel(_asyncMap);
async.mapSeries = doSeries(_asyncMap);
async.mapLimit = function (arr, limit, iterator, callback) {
return _mapLimit(limit)(arr, iterator, callback);
};
var _mapLimit = function(limit) {
return doParallelLimit(limit, _asyncMap);
};
// reduce only has a series version, as doing reduce in parallel won't
// work in many situations.
async.reduce = function (arr, memo, iterator, callback) {
async.eachSeries(arr, function (x, callback) {
iterator(memo, x, function (err, v) {
memo = v;
callback(err);
});
}, function (err) {
callback(err, memo);
});
};
// inject alias
async.inject = async.reduce;
// foldl alias
async.foldl = async.reduce;
async.reduceRight = function (arr, memo, iterator, callback) {
var reversed = _map(arr, function (x) {
return x;
}).reverse();
async.reduce(reversed, memo, iterator, callback);
};
// foldr alias
async.foldr = async.reduceRight;
var _filter = function (eachfn, arr, iterator, callback) {
var results = [];
arr = _map(arr, function (x, i) {
return {index: i, value: x};
});
eachfn(arr, function (x, callback) {
iterator(x.value, function (v) {
if (v) {
results.push(x);
}
callback();
});
}, function (err) {
callback(_map(results.sort(function (a, b) {
return a.index - b.index;
}), function (x) {
return x.value;
}));
});
};
async.filter = doParallel(_filter);
async.filterSeries = doSeries(_filter);
// select alias
async.select = async.filter;
async.selectSeries = async.filterSeries;
var _reject = function (eachfn, arr, iterator, callback) {
var results = [];
arr = _map(arr, function (x, i) {
return {index: i, value: x};
});
eachfn(arr, function (x, callback) {
iterator(x.value, function (v) {
if (!v) {
results.push(x);
}
callback();
});
}, function (err) {
callback(_map(results.sort(function (a, b) {
return a.index - b.index;
}), function (x) {
return x.value;
}));
});
};
async.reject = doParallel(_reject);
async.rejectSeries = doSeries(_reject);
var _detect = function (eachfn, arr, iterator, main_callback) {
eachfn(arr, function (x, callback) {
iterator(x, function (result) {
if (result) {
main_callback(x);
main_callback = function () {};
}
else {
callback();
}
});
}, function (err) {
main_callback();
});
};
async.detect = doParallel(_detect);
async.detectSeries = doSeries(_detect);
async.some = function (arr, iterator, main_callback) {
async.each(arr, function (x, callback) {
iterator(x, function (v) {
if (v) {
main_callback(true);
main_callback = function () {};
}
callback();
});
}, function (err) {
main_callback(false);
});
};
// any alias
async.any = async.some;
async.every = function (arr, iterator, main_callback) {
async.each(arr, function (x, callback) {
iterator(x, function (v) {
if (!v) {
main_callback(false);
main_callback = function () {};
}
callback();
});
}, function (err) {
main_callback(true);
});
};
// all alias
async.all = async.every;
async.sortBy = function (arr, iterator, callback) {
async.map(arr, function (x, callback) {
iterator(x, function (err, criteria) {
if (err) {
callback(err);
}
else {
callback(null, {value: x, criteria: criteria});
}
});
}, function (err, results) {
if (err) {
return callback(err);
}
else {
var fn = function (left, right) {
var a = left.criteria, b = right.criteria;
return a < b ? -1 : a > b ? 1 : 0;
};
callback(null, _map(results.sort(fn), function (x) {
return x.value;
}));
}
});
};
async.auto = function (tasks, callback) {
callback = callback || function () {};
var keys = _keys(tasks);
if (!keys.length) {
return callback(null);
}
var results = {};
var listeners = [];
var addListener = function (fn) {
listeners.unshift(fn);
};
var removeListener = function (fn) {
for (var i = 0; i < listeners.length; i += 1) {
if (listeners[i] === fn) {
listeners.splice(i, 1);
return;
}
}
};
var taskComplete = function () {
_each(listeners.slice(0), function (fn) {
fn();
});
};
addListener(function () {
if (_keys(results).length === keys.length) {
callback(null, results);
callback = function () {};
}
});
_each(keys, function (k) {
var task = (tasks[k] instanceof Function) ? [tasks[k]]: tasks[k];
var taskCallback = function (err) {
var args = Array.prototype.slice.call(arguments, 1);
if (args.length <= 1) {
args = args[0];
}
if (err) {
var safeResults = {};
_each(_keys(results), function(rkey) {
safeResults[rkey] = results[rkey];
});
safeResults[k] = args;
callback(err, safeResults);
// stop subsequent errors hitting callback multiple times
callback = function () {};
}
else {
results[k] = args;
async.setImmediate(taskComplete);
}
};
var requires = task.slice(0, Math.abs(task.length - 1)) || [];
var ready = function () {
return _reduce(requires, function (a, x) {
return (a && results.hasOwnProperty(x));
}, true) && !results.hasOwnProperty(k);
};
if (ready()) {
task[task.length - 1](taskCallback, results);
}
else {
var listener = function () {
if (ready()) {
removeListener(listener);
task[task.length - 1](taskCallback, results);
}
};
addListener(listener);
}
});
};
async.waterfall = function (tasks, callback) {
callback = callback || function () {};
if (tasks.constructor !== Array) {
var err = new Error('First argument to waterfall must be an array of functions');
return callback(err);
}
if (!tasks.length) {
return callback();
}
var wrapIterator = function (iterator) {
return function (err) {
if (err) {
callback.apply(null, arguments);
callback = function () {};
}
else {
var args = Array.prototype.slice.call(arguments, 1);
var next = iterator.next();
if (next) {
args.push(wrapIterator(next));
}
else {
args.push(callback);
}
async.setImmediate(function () {
iterator.apply(null, args);
});
}
};
};
wrapIterator(async.iterator(tasks))();
};
var _parallel = function(eachfn, tasks, callback) {
callback = callback || function () {};
if (tasks.constructor === Array) {
eachfn.map(tasks, function (fn, callback) {
if (fn) {
fn(function (err) {
var args = Array.prototype.slice.call(arguments, 1);
if (args.length <= 1) {
args = args[0];
}
callback.call(null, err, args);
});
}
}, callback);
}
else {
var results = {};
eachfn.each(_keys(tasks), function (k, callback) {
tasks[k](function (err) {
var args = Array.prototype.slice.call(arguments, 1);
if (args.length <= 1) {
args = args[0];
}
results[k] = args;
callback(err);
});
}, function (err) {
callback(err, results);
});
}
};
async.parallel = function (tasks, callback) {
_parallel({ map: async.map, each: async.each }, tasks, callback);
};
async.parallelLimit = function(tasks, limit, callback) {
_parallel({ map: _mapLimit(limit), each: _eachLimit(limit) }, tasks, callback);
};
async.series = function (tasks, callback) {
callback = callback || function () {};
if (tasks.constructor === Array) {
async.mapSeries(tasks, function (fn, callback) {
if (fn) {
fn(function (err) {
var args = Array.prototype.slice.call(arguments, 1);
if (args.length <= 1) {
args = args[0];
}
callback.call(null, err, args);
});
}
}, callback);
}
else {
var results = {};
async.eachSeries(_keys(tasks), function (k, callback) {
tasks[k](function (err) {
var args = Array.prototype.slice.call(arguments, 1);
if (args.length <= 1) {
args = args[0];
}
results[k] = args;
callback(err);
});
}, function (err) {
callback(err, results);
});
}
};
async.iterator = function (tasks) {
var makeCallback = function (index) {
var fn = function () {
if (tasks.length) {
tasks[index].apply(null, arguments);
}
return fn.next();
};
fn.next = function () {
return (index < tasks.length - 1) ? makeCallback(index + 1): null;
};
return fn;
};
return makeCallback(0);
};
async.apply = function (fn) {
var args = Array.prototype.slice.call(arguments, 1);
return function () {
return fn.apply(
null, args.concat(Array.prototype.slice.call(arguments))
);
};
};
var _concat = function (eachfn, arr, fn, callback) {
var r = [];
eachfn(arr, function (x, cb) {
fn(x, function (err, y) {
r = r.concat(y || []);
cb(err);
});
}, function (err) {
callback(err, r);
});
};
async.concat = doParallel(_concat);
async.concatSeries = doSeries(_concat);
async.whilst = function (test, iterator, callback) {
if (test()) {
iterator(function (err) {
if (err) {
return callback(err);
}
async.whilst(test, iterator, callback);
});
}
else {
callback();
}
};
async.doWhilst = function (iterator, test, callback) {
iterator(function (err) {
if (err) {
return callback(err);
}
if (test()) {
async.doWhilst(iterator, test, callback);
}
else {
callback();
}
});
};
async.until = function (test, iterator, callback) {
if (!test()) {
iterator(function (err) {
if (err) {
return callback(err);
}
async.until(test, iterator, callback);
});
}
else {
callback();
}
};
async.doUntil = function (iterator, test, callback) {
iterator(function (err) {
if (err) {
return callback(err);
}
if (!test()) {
async.doUntil(iterator, test, callback);
}
else {
callback();
}
});
};
async.queue = function (worker, concurrency) {
if (concurrency === undefined) {
concurrency = 1;
}
function _insert(q, data, pos, callback) {
if(data.constructor !== Array) {
data = [data];
}
_each(data, function(task) {
var item = {
data: task,
callback: typeof callback === 'function' ? callback : null
};
if (pos) {
q.tasks.unshift(item);
} else {
q.tasks.push(item);
}
if (q.saturated && q.tasks.length === concurrency) {
q.saturated();
}
async.setImmediate(q.process);
});
}
var workers = 0;
var q = {
tasks: [],
concurrency: concurrency,
saturated: null,
empty: null,
drain: null,
push: function (data, callback) {
_insert(q, data, false, callback);
},
unshift: function (data, callback) {
_insert(q, data, true, callback);
},
process: function () {
if (workers < q.concurrency && q.tasks.length) {
var task = q.tasks.shift();
if (q.empty && q.tasks.length === 0) {
q.empty();
}
workers += 1;
var next = function () {
workers -= 1;
if (task.callback) {
task.callback.apply(task, arguments);
}
if (q.drain && q.tasks.length + workers === 0) {
q.drain();
}
q.process();
};
var cb = only_once(next);
worker(task.data, cb);
}
},
length: function () {
return q.tasks.length;
},
running: function () {
return workers;
}
};
return q;
};
async.cargo = function (worker, payload) {
var working = false,
tasks = [];
var cargo = {
tasks: tasks,
payload: payload,
saturated: null,
empty: null,
drain: null,
push: function (data, callback) {
if(data.constructor !== Array) {
data = [data];
}
_each(data, function(task) {
tasks.push({
data: task,
callback: typeof callback === 'function' ? callback : null
});
if (cargo.saturated && tasks.length === payload) {
cargo.saturated();
}
});
async.setImmediate(cargo.process);
},
process: function process() {
if (working) return;
if (tasks.length === 0) {
if(cargo.drain) cargo.drain();
return;
}
var ts = typeof payload === 'number'
? tasks.splice(0, payload)
: tasks.splice(0);
var ds = _map(ts, function (task) {
return task.data;
});
if(cargo.empty) cargo.empty();
working = true;
worker(ds, function () {
working = false;
var args = arguments;
_each(ts, function (data) {
if (data.callback) {
data.callback.apply(null, args);
}
});
process();
});
},
length: function () {
return tasks.length;
},
running: function () {
return working;
}
};
return cargo;
};
var _console_fn = function (name) {
return function (fn) {
var args = Array.prototype.slice.call(arguments, 1);
fn.apply(null, args.concat([function (err) {
var args = Array.prototype.slice.call(arguments, 1);
if (typeof console !== 'undefined') {
if (err) {
if (console.error) {
console.error(err);
}
}
else if (console[name]) {
_each(args, function (x) {
console[name](x);
});
}
}
}]));
};
};
async.log = _console_fn('log');
async.dir = _console_fn('dir');
/*async.info = _console_fn('info');
async.warn = _console_fn('warn');
async.error = _console_fn('error');*/
async.memoize = function (fn, hasher) {
var memo = {};
var queues = {};
hasher = hasher || function (x) {
return x;
};
var memoized = function () {
var args = Array.prototype.slice.call(arguments);
var callback = args.pop();
var key = hasher.apply(null, args);
if (key in memo) {
callback.apply(null, memo[key]);
}
else if (key in queues) {
queues[key].push(callback);
}
else {
queues[key] = [callback];
fn.apply(null, args.concat([function () {
memo[key] = arguments;
var q = queues[key];
delete queues[key];
for (var i = 0, l = q.length; i < l; i++) {
q[i].apply(null, arguments);
}
}]));
}
};
memoized.memo = memo;
memoized.unmemoized = fn;
return memoized;
};
async.unmemoize = function (fn) {
return function () {
return (fn.unmemoized || fn).apply(null, arguments);
};
};
async.times = function (count, iterator, callback) {
var counter = [];
for (var i = 0; i < count; i++) {
counter.push(i);
}
return async.map(counter, iterator, callback);
};
async.timesSeries = function (count, iterator, callback) {
var counter = [];
for (var i = 0; i < count; i++) {
counter.push(i);
}
return async.mapSeries(counter, iterator, callback);
};
async.compose = function (/* functions... */) {
var fns = Array.prototype.reverse.call(arguments);
return function () {
var that = this;
var args = Array.prototype.slice.call(arguments);
var callback = args.pop();
async.reduce(fns, args, function (newargs, fn, cb) {
fn.apply(that, newargs.concat([function () {
var err = arguments[0];
var nextargs = Array.prototype.slice.call(arguments, 1);
cb(err, nextargs);
}]))
},
function (err, results) {
callback.apply(that, [err].concat(results));
});
};
};
var _applyEach = function (eachfn, fns /*args...*/) {
var go = function () {
var that = this;
var args = Array.prototype.slice.call(arguments);
var callback = args.pop();
return eachfn(fns, function (fn, cb) {
fn.apply(that, args.concat([cb]));
},
callback);
};
if (arguments.length > 2) {
var args = Array.prototype.slice.call(arguments, 2);
return go.apply(this, args);
}
else {
return go;
}
};
async.applyEach = doParallel(_applyEach);
async.applyEachSeries = doSeries(_applyEach);
async.forever = function (fn, callback) {
function next(err) {
if (err) {
if (callback) {
return callback(err);
}
throw err;
}
fn(next);
}
next();
};
// AMD / RequireJS
if (typeof define !== 'undefined' && define.amd) {
define([], function () {
return async;
});
}
// Node.js
else if (typeof module !== 'undefined' && module.exports) {
module.exports = async;
}
// included directly via <script> tag
else {
root.async = async;
}
}());

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,2 @@
dist/*
node_modules/*

View File

@@ -0,0 +1,4 @@
language: node_js
node_js:
- 0.8
- "0.10"

View File

@@ -0,0 +1,112 @@
# Change Log
## 0.1.31
* Delay parsing the mappings in SourceMapConsumer until queried for a source
location.
* Support Sass source maps (which at the time of writing deviate from the spec
in small ways) in SourceMapConsumer.
## 0.1.30
* Do not join source root with a source, when the source is a data URI.
* Extend the test runner to allow running single specific test files at a time.
* Performance improvements in `SourceNode.prototype.walk` and
`SourceMapConsumer.prototype.eachMapping`.
* Source map browser builds will now work inside Workers.
* Better error messages when attempting to add an invalid mapping to a
`SourceMapGenerator`.
## 0.1.29
* Allow duplicate entries in the `names` and `sources` arrays of source maps
(usually from TypeScript) we are parsing. Fixes github isse 72.
## 0.1.28
* Skip duplicate mappings when creating source maps from SourceNode; github
issue 75.
## 0.1.27
* Don't throw an error when the `file` property is missing in SourceMapConsumer,
we don't use it anyway.
## 0.1.26
* Fix SourceNode.fromStringWithSourceMap for empty maps. Fixes github issue 70.
## 0.1.25
* Make compatible with browserify
## 0.1.24
* Fix issue with absolute paths and `file://` URIs. See
https://bugzilla.mozilla.org/show_bug.cgi?id=885597
## 0.1.23
* Fix issue with absolute paths and sourcesContent, github issue 64.
## 0.1.22
* Ignore duplicate mappings in SourceMapGenerator. Fixes github issue 21.
## 0.1.21
* Fixed handling of sources that start with a slash so that they are relative to
the source root's host.
## 0.1.20
* Fixed github issue #43: absolute URLs aren't joined with the source root
anymore.
## 0.1.19
* Using Travis CI to run tests.
## 0.1.18
* Fixed a bug in the handling of sourceRoot.
## 0.1.17
* Added SourceNode.fromStringWithSourceMap.
## 0.1.16
* Added missing documentation.
* Fixed the generating of empty mappings in SourceNode.
## 0.1.15
* Added SourceMapGenerator.applySourceMap.
## 0.1.14
* The sourceRoot is now handled consistently.
## 0.1.13
* Added SourceMapGenerator.fromSourceMap.
## 0.1.12
* SourceNode now generates empty mappings too.
## 0.1.11
* Added name support to SourceNode.
## 0.1.10
* Added sourcesContent support to the customer and generator.

View File

@@ -0,0 +1,28 @@
Copyright (c) 2009-2011, Mozilla Foundation and contributors
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the names of the Mozilla Foundation nor the names of project
contributors may be used to endorse or promote products derived from this
software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@@ -0,0 +1,166 @@
/* -*- Mode: js; js-indent-level: 2; -*- */
/*
* Copyright 2011 Mozilla Foundation and contributors
* Licensed under the New BSD license. See LICENSE or:
* http://opensource.org/licenses/BSD-3-Clause
*/
var path = require('path');
var fs = require('fs');
var copy = require('dryice').copy;
function removeAmdefine(src) {
src = String(src).replace(
/if\s*\(typeof\s*define\s*!==\s*'function'\)\s*{\s*var\s*define\s*=\s*require\('amdefine'\)\(module,\s*require\);\s*}\s*/g,
'');
src = src.replace(
/\b(define\(.*)('amdefine',?)/gm,
'$1');
return src;
}
removeAmdefine.onRead = true;
function makeNonRelative(src) {
return src
.replace(/require\('.\//g, 'require(\'source-map/')
.replace(/\.\.\/\.\.\/lib\//g, '');
}
makeNonRelative.onRead = true;
function buildBrowser() {
console.log('\nCreating dist/source-map.js');
var project = copy.createCommonJsProject({
roots: [ path.join(__dirname, 'lib') ]
});
copy({
source: [
'build/mini-require.js',
{
project: project,
require: [ 'source-map/source-map-generator',
'source-map/source-map-consumer',
'source-map/source-node']
},
'build/suffix-browser.js'
],
filter: [
copy.filter.moduleDefines,
removeAmdefine
],
dest: 'dist/source-map.js'
});
}
function buildBrowserMin() {
console.log('\nCreating dist/source-map.min.js');
copy({
source: 'dist/source-map.js',
filter: copy.filter.uglifyjs,
dest: 'dist/source-map.min.js'
});
}
function buildFirefox() {
console.log('\nCreating dist/SourceMap.jsm');
var project = copy.createCommonJsProject({
roots: [ path.join(__dirname, 'lib') ]
});
copy({
source: [
'build/prefix-source-map.jsm',
{
project: project,
require: [ 'source-map/source-map-consumer',
'source-map/source-map-generator',
'source-map/source-node' ]
},
'build/suffix-source-map.jsm'
],
filter: [
copy.filter.moduleDefines,
removeAmdefine,
makeNonRelative
],
dest: 'dist/SourceMap.jsm'
});
// Create dist/test/Utils.jsm
console.log('\nCreating dist/test/Utils.jsm');
project = copy.createCommonJsProject({
roots: [ __dirname, path.join(__dirname, 'lib') ]
});
copy({
source: [
'build/prefix-utils.jsm',
'build/assert-shim.js',
{
project: project,
require: [ 'test/source-map/util' ]
},
'build/suffix-utils.jsm'
],
filter: [
copy.filter.moduleDefines,
removeAmdefine,
makeNonRelative
],
dest: 'dist/test/Utils.jsm'
});
function isTestFile(f) {
return /^test\-.*?\.js/.test(f);
}
var testFiles = fs.readdirSync(path.join(__dirname, 'test', 'source-map')).filter(isTestFile);
testFiles.forEach(function (testFile) {
console.log('\nCreating', path.join('dist', 'test', testFile.replace(/\-/g, '_')));
copy({
source: [
'build/test-prefix.js',
path.join('test', 'source-map', testFile),
'build/test-suffix.js'
],
filter: [
removeAmdefine,
makeNonRelative,
function (input, source) {
return input.replace('define(',
'define("'
+ path.join('test', 'source-map', testFile.replace(/\.js$/, ''))
+ '", ["require", "exports", "module"], ');
},
function (input, source) {
return input.replace('{THIS_MODULE}', function () {
return "test/source-map/" + testFile.replace(/\.js$/, '');
});
}
],
dest: path.join('dist', 'test', testFile.replace(/\-/g, '_'))
});
});
}
function ensureDir(name) {
var dirExists = false;
try {
dirExists = fs.statSync(name).isDirectory();
} catch (err) {}
if (!dirExists) {
fs.mkdirSync(name, 0777);
}
}
ensureDir("dist");
ensureDir("dist/test");
buildFirefox();
buildBrowser();
buildBrowserMin();

View File

@@ -0,0 +1,434 @@
# Source Map
This is a library to generate and consume the source map format
[described here][format].
This library is written in the Asynchronous Module Definition format, and works
in the following environments:
* Modern Browsers supporting ECMAScript 5 (either after the build, or with an
AMD loader such as RequireJS)
* Inside Firefox (as a JSM file, after the build)
* With NodeJS versions 0.8.X and higher
## Node
$ npm install source-map
## Building from Source (for everywhere else)
Install Node and then run
$ git clone https://fitzgen@github.com/mozilla/source-map.git
$ cd source-map
$ npm link .
Next, run
$ node Makefile.dryice.js
This should spew a bunch of stuff to stdout, and create the following files:
* `dist/source-map.js` - The unminified browser version.
* `dist/source-map.min.js` - The minified browser version.
* `dist/SourceMap.jsm` - The JavaScript Module for inclusion in Firefox source.
## Examples
### Consuming a source map
var rawSourceMap = {
version: 3,
file: 'min.js',
names: ['bar', 'baz', 'n'],
sources: ['one.js', 'two.js'],
sourceRoot: 'http://example.com/www/js/',
mappings: 'CAAC,IAAI,IAAM,SAAUA,GAClB,OAAOC,IAAID;CCDb,IAAI,IAAM,SAAUE,GAClB,OAAOA'
};
var smc = new SourceMapConsumer(rawSourceMap);
console.log(smc.sources);
// [ 'http://example.com/www/js/one.js',
// 'http://example.com/www/js/two.js' ]
console.log(smc.originalPositionFor({
line: 2,
column: 28
}));
// { source: 'http://example.com/www/js/two.js',
// line: 2,
// column: 10,
// name: 'n' }
console.log(smc.generatedPositionFor({
source: 'http://example.com/www/js/two.js',
line: 2,
column: 10
}));
// { line: 2, column: 28 }
smc.eachMapping(function (m) {
// ...
});
### Generating a source map
In depth guide:
[**Compiling to JavaScript, and Debugging with Source Maps**](https://hacks.mozilla.org/2013/05/compiling-to-javascript-and-debugging-with-source-maps/)
#### With SourceNode (high level API)
function compile(ast) {
switch (ast.type) {
case 'BinaryExpression':
return new SourceNode(
ast.location.line,
ast.location.column,
ast.location.source,
[compile(ast.left), " + ", compile(ast.right)]
);
case 'Literal':
return new SourceNode(
ast.location.line,
ast.location.column,
ast.location.source,
String(ast.value)
);
// ...
default:
throw new Error("Bad AST");
}
}
var ast = parse("40 + 2", "add.js");
console.log(compile(ast).toStringWithSourceMap({
file: 'add.js'
}));
// { code: '40 + 2',
// map: [object SourceMapGenerator] }
#### With SourceMapGenerator (low level API)
var map = new SourceMapGenerator({
file: "source-mapped.js"
});
map.addMapping({
generated: {
line: 10,
column: 35
},
source: "foo.js",
original: {
line: 33,
column: 2
},
name: "christopher"
});
console.log(map.toString());
// '{"version":3,"file":"source-mapped.js","sources":["foo.js"],"names":["christopher"],"mappings":";;;;;;;;;mCAgCEA"}'
## API
Get a reference to the module:
// NodeJS
var sourceMap = require('source-map');
// Browser builds
var sourceMap = window.sourceMap;
// Inside Firefox
let sourceMap = {};
Components.utils.import('resource:///modules/devtools/SourceMap.jsm', sourceMap);
### SourceMapConsumer
A SourceMapConsumer instance represents a parsed source map which we can query
for information about the original file positions by giving it a file position
in the generated source.
#### new SourceMapConsumer(rawSourceMap)
The only parameter is the raw source map (either as a string which can be
`JSON.parse`'d, or an object). According to the spec, source maps have the
following attributes:
* `version`: Which version of the source map spec this map is following.
* `sources`: An array of URLs to the original source files.
* `names`: An array of identifiers which can be referrenced by individual
mappings.
* `sourceRoot`: Optional. The URL root from which all sources are relative.
* `sourcesContent`: Optional. An array of contents of the original source files.
* `mappings`: A string of base64 VLQs which contain the actual mappings.
* `file`: The generated filename this source map is associated with.
#### SourceMapConsumer.prototype.originalPositionFor(generatedPosition)
Returns the original source, line, and column information for the generated
source's line and column positions provided. The only argument is an object with
the following properties:
* `line`: The line number in the generated source.
* `column`: The column number in the generated source.
and an object is returned with the following properties:
* `source`: The original source file, or null if this information is not
available.
* `line`: The line number in the original source, or null if this information is
not available.
* `column`: The column number in the original source, or null or null if this
information is not available.
* `name`: The original identifier, or null if this information is not available.
#### SourceMapConsumer.prototype.generatedPositionFor(originalPosition)
Returns the generated line and column information for the original source,
line, and column positions provided. The only argument is an object with
the following properties:
* `source`: The filename of the original source.
* `line`: The line number in the original source.
* `column`: The column number in the original source.
and an object is returned with the following properties:
* `line`: The line number in the generated source, or null.
* `column`: The column number in the generated source, or null.
#### SourceMapConsumer.prototype.sourceContentFor(source)
Returns the original source content for the source provided. The only
argument is the URL of the original source file.
#### SourceMapConsumer.prototype.eachMapping(callback, context, order)
Iterate over each mapping between an original source/line/column and a
generated line/column in this source map.
* `callback`: The function that is called with each mapping. Mappings have the
form `{ source, generatedLine, generatedColumn, originalLine, originalColumn,
name }`
* `context`: Optional. If specified, this object will be the value of `this`
every time that `callback` is called.
* `order`: Either `SourceMapConsumer.GENERATED_ORDER` or
`SourceMapConsumer.ORIGINAL_ORDER`. Specifies whether you want to iterate over
the mappings sorted by the generated file's line/column order or the
original's source/line/column order, respectively. Defaults to
`SourceMapConsumer.GENERATED_ORDER`.
### SourceMapGenerator
An instance of the SourceMapGenerator represents a source map which is being
built incrementally.
#### new SourceMapGenerator(startOfSourceMap)
To create a new one, you must pass an object with the following properties:
* `file`: The filename of the generated source that this source map is
associated with.
* `sourceRoot`: An optional root for all relative URLs in this source map.
#### SourceMapGenerator.fromSourceMap(sourceMapConsumer)
Creates a new SourceMapGenerator based on a SourceMapConsumer
* `sourceMapConsumer` The SourceMap.
#### SourceMapGenerator.prototype.addMapping(mapping)
Add a single mapping from original source line and column to the generated
source's line and column for this source map being created. The mapping object
should have the following properties:
* `generated`: An object with the generated line and column positions.
* `original`: An object with the original line and column positions.
* `source`: The original source file (relative to the sourceRoot).
* `name`: An optional original token name for this mapping.
#### SourceMapGenerator.prototype.setSourceContent(sourceFile, sourceContent)
Set the source content for an original source file.
* `sourceFile` the URL of the original source file.
* `sourceContent` the content of the source file.
#### SourceMapGenerator.prototype.applySourceMap(sourceMapConsumer[, sourceFile])
Applies a SourceMap for a source file to the SourceMap.
Each mapping to the supplied source file is rewritten using the
supplied SourceMap. Note: The resolution for the resulting mappings
is the minimium of this map and the supplied map.
* `sourceMapConsumer`: The SourceMap to be applied.
* `sourceFile`: Optional. The filename of the source file.
If omitted, sourceMapConsumer.file will be used.
#### SourceMapGenerator.prototype.toString()
Renders the source map being generated to a string.
### SourceNode
SourceNodes provide a way to abstract over interpolating and/or concatenating
snippets of generated JavaScript source code, while maintaining the line and
column information associated between those snippets and the original source
code. This is useful as the final intermediate representation a compiler might
use before outputting the generated JS and source map.
#### new SourceNode(line, column, source[, chunk[, name]])
* `line`: The original line number associated with this source node, or null if
it isn't associated with an original line.
* `column`: The original column number associated with this source node, or null
if it isn't associated with an original column.
* `source`: The original source's filename.
* `chunk`: Optional. Is immediately passed to `SourceNode.prototype.add`, see
below.
* `name`: Optional. The original identifier.
#### SourceNode.fromStringWithSourceMap(code, sourceMapConsumer)
Creates a SourceNode from generated code and a SourceMapConsumer.
* `code`: The generated code
* `sourceMapConsumer` The SourceMap for the generated code
#### SourceNode.prototype.add(chunk)
Add a chunk of generated JS to this source node.
* `chunk`: A string snippet of generated JS code, another instance of
`SourceNode`, or an array where each member is one of those things.
#### SourceNode.prototype.prepend(chunk)
Prepend a chunk of generated JS to this source node.
* `chunk`: A string snippet of generated JS code, another instance of
`SourceNode`, or an array where each member is one of those things.
#### SourceNode.prototype.setSourceContent(sourceFile, sourceContent)
Set the source content for a source file. This will be added to the
`SourceMap` in the `sourcesContent` field.
* `sourceFile`: The filename of the source file
* `sourceContent`: The content of the source file
#### SourceNode.prototype.walk(fn)
Walk over the tree of JS snippets in this node and its children. The walking
function is called once for each snippet of JS and is passed that snippet and
the its original associated source's line/column location.
* `fn`: The traversal function.
#### SourceNode.prototype.walkSourceContents(fn)
Walk over the tree of SourceNodes. The walking function is called for each
source file content and is passed the filename and source content.
* `fn`: The traversal function.
#### SourceNode.prototype.join(sep)
Like `Array.prototype.join` except for SourceNodes. Inserts the separator
between each of this source node's children.
* `sep`: The separator.
#### SourceNode.prototype.replaceRight(pattern, replacement)
Call `String.prototype.replace` on the very right-most source snippet. Useful
for trimming whitespace from the end of a source node, etc.
* `pattern`: The pattern to replace.
* `replacement`: The thing to replace the pattern with.
#### SourceNode.prototype.toString()
Return the string representation of this source node. Walks over the tree and
concatenates all the various snippets together to one string.
### SourceNode.prototype.toStringWithSourceMap(startOfSourceMap)
Returns the string representation of this tree of source nodes, plus a
SourceMapGenerator which contains all the mappings between the generated and
original sources.
The arguments are the same as those to `new SourceMapGenerator`.
## Tests
[![Build Status](https://travis-ci.org/mozilla/source-map.png?branch=master)](https://travis-ci.org/mozilla/source-map)
Install NodeJS version 0.8.0 or greater, then run `node test/run-tests.js`.
To add new tests, create a new file named `test/test-<your new test name>.js`
and export your test functions with names that start with "test", for example
exports["test doing the foo bar"] = function (assert, util) {
...
};
The new test will be located automatically when you run the suite.
The `util` argument is the test utility module located at `test/source-map/util`.
The `assert` argument is a cut down version of node's assert module. You have
access to the following assertion functions:
* `doesNotThrow`
* `equal`
* `ok`
* `strictEqual`
* `throws`
(The reason for the restricted set of test functions is because we need the
tests to run inside Firefox's test suite as well and so the assert module is
shimmed in that environment. See `build/assert-shim.js`.)
[format]: https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit
[feature]: https://wiki.mozilla.org/DevTools/Features/SourceMap
[Dryice]: https://github.com/mozilla/dryice

View File

@@ -0,0 +1,56 @@
/* -*- Mode: js; js-indent-level: 2; -*- */
/*
* Copyright 2011 Mozilla Foundation and contributors
* Licensed under the New BSD license. See LICENSE or:
* http://opensource.org/licenses/BSD-3-Clause
*/
define('test/source-map/assert', ['exports'], function (exports) {
let do_throw = function (msg) {
throw new Error(msg);
};
exports.init = function (throw_fn) {
do_throw = throw_fn;
};
exports.doesNotThrow = function (fn) {
try {
fn();
}
catch (e) {
do_throw(e.message);
}
};
exports.equal = function (actual, expected, msg) {
msg = msg || String(actual) + ' != ' + String(expected);
if (actual != expected) {
do_throw(msg);
}
};
exports.ok = function (val, msg) {
msg = msg || String(val) + ' is falsey';
if (!Boolean(val)) {
do_throw(msg);
}
};
exports.strictEqual = function (actual, expected, msg) {
msg = msg || String(actual) + ' !== ' + String(expected);
if (actual !== expected) {
do_throw(msg);
}
};
exports.throws = function (fn) {
try {
fn();
do_throw('Expected an error to be thrown, but it wasn\'t.');
}
catch (e) {
}
};
});

View File

@@ -0,0 +1,152 @@
/* -*- Mode: js; js-indent-level: 2; -*- */
/*
* Copyright 2011 Mozilla Foundation and contributors
* Licensed under the New BSD license. See LICENSE or:
* http://opensource.org/licenses/BSD-3-Clause
*/
/**
* Define a module along with a payload.
* @param {string} moduleName Name for the payload
* @param {ignored} deps Ignored. For compatibility with CommonJS AMD Spec
* @param {function} payload Function with (require, exports, module) params
*/
function define(moduleName, deps, payload) {
if (typeof moduleName != "string") {
throw new TypeError('Expected string, got: ' + moduleName);
}
if (arguments.length == 2) {
payload = deps;
}
if (moduleName in define.modules) {
throw new Error("Module already defined: " + moduleName);
}
define.modules[moduleName] = payload;
};
/**
* The global store of un-instantiated modules
*/
define.modules = {};
/**
* We invoke require() in the context of a Domain so we can have multiple
* sets of modules running separate from each other.
* This contrasts with JSMs which are singletons, Domains allows us to
* optionally load a CommonJS module twice with separate data each time.
* Perhaps you want 2 command lines with a different set of commands in each,
* for example.
*/
function Domain() {
this.modules = {};
this._currentModule = null;
}
(function () {
/**
* Lookup module names and resolve them by calling the definition function if
* needed.
* There are 2 ways to call this, either with an array of dependencies and a
* callback to call when the dependencies are found (which can happen
* asynchronously in an in-page context) or with a single string an no callback
* where the dependency is resolved synchronously and returned.
* The API is designed to be compatible with the CommonJS AMD spec and
* RequireJS.
* @param {string[]|string} deps A name, or names for the payload
* @param {function|undefined} callback Function to call when the dependencies
* are resolved
* @return {undefined|object} The module required or undefined for
* array/callback method
*/
Domain.prototype.require = function(deps, callback) {
if (Array.isArray(deps)) {
var params = deps.map(function(dep) {
return this.lookup(dep);
}, this);
if (callback) {
callback.apply(null, params);
}
return undefined;
}
else {
return this.lookup(deps);
}
};
function normalize(path) {
var bits = path.split('/');
var i = 1;
while (i < bits.length) {
if (bits[i] === '..') {
bits.splice(i-1, 1);
} else if (bits[i] === '.') {
bits.splice(i, 1);
} else {
i++;
}
}
return bits.join('/');
}
function join(a, b) {
a = a.trim();
b = b.trim();
if (/^\//.test(b)) {
return b;
} else {
return a.replace(/\/*$/, '/') + b;
}
}
function dirname(path) {
var bits = path.split('/');
bits.pop();
return bits.join('/');
}
/**
* Lookup module names and resolve them by calling the definition function if
* needed.
* @param {string} moduleName A name for the payload to lookup
* @return {object} The module specified by aModuleName or null if not found.
*/
Domain.prototype.lookup = function(moduleName) {
if (/^\./.test(moduleName)) {
moduleName = normalize(join(dirname(this._currentModule), moduleName));
}
if (moduleName in this.modules) {
var module = this.modules[moduleName];
return module;
}
if (!(moduleName in define.modules)) {
throw new Error("Module not defined: " + moduleName);
}
var module = define.modules[moduleName];
if (typeof module == "function") {
var exports = {};
var previousModule = this._currentModule;
this._currentModule = moduleName;
module(this.require.bind(this), exports, { id: moduleName, uri: "" });
this._currentModule = previousModule;
module = exports;
}
// cache the resulting module object for next time
this.modules[moduleName] = module;
return module;
};
}());
define.Domain = Domain;
define.globalDomain = new Domain();
var require = define.globalDomain.require.bind(define.globalDomain);

View File

@@ -0,0 +1,20 @@
/* -*- Mode: js; js-indent-level: 2; -*- */
/*
* Copyright 2011 Mozilla Foundation and contributors
* Licensed under the New BSD license. See LICENSE or:
* http://opensource.org/licenses/BSD-3-Clause
*/
/*
* WARNING!
*
* Do not edit this file directly, it is built from the sources at
* https://github.com/mozilla/source-map/
*/
///////////////////////////////////////////////////////////////////////////////
this.EXPORTED_SYMBOLS = [ "SourceMapConsumer", "SourceMapGenerator", "SourceNode" ];
Components.utils.import('resource://gre/modules/devtools/Require.jsm');

View File

@@ -0,0 +1,18 @@
/* -*- Mode: js; js-indent-level: 2; -*- */
/*
* Copyright 2011 Mozilla Foundation and contributors
* Licensed under the New BSD license. See LICENSE or:
* http://opensource.org/licenses/BSD-3-Clause
*/
/*
* WARNING!
*
* Do not edit this file directly, it is built from the sources at
* https://github.com/mozilla/source-map/
*/
Components.utils.import('resource://gre/modules/devtools/Require.jsm');
Components.utils.import('resource://gre/modules/devtools/SourceMap.jsm');
this.EXPORTED_SYMBOLS = [ "define", "runSourceMapTests" ];

View File

@@ -0,0 +1,8 @@
/* -*- Mode: js; js-indent-level: 2; -*- */
///////////////////////////////////////////////////////////////////////////////
this.sourceMap = {
SourceMapConsumer: require('source-map/source-map-consumer').SourceMapConsumer,
SourceMapGenerator: require('source-map/source-map-generator').SourceMapGenerator,
SourceNode: require('source-map/source-node').SourceNode
};

View File

@@ -0,0 +1,6 @@
/* -*- Mode: js; js-indent-level: 2; -*- */
///////////////////////////////////////////////////////////////////////////////
this.SourceMapConsumer = require('source-map/source-map-consumer').SourceMapConsumer;
this.SourceMapGenerator = require('source-map/source-map-generator').SourceMapGenerator;
this.SourceNode = require('source-map/source-node').SourceNode;

View File

@@ -0,0 +1,21 @@
/* -*- Mode: js; js-indent-level: 2; -*- */
/*
* Copyright 2011 Mozilla Foundation and contributors
* Licensed under the New BSD license. See LICENSE or:
* http://opensource.org/licenses/BSD-3-Clause
*/
function runSourceMapTests(modName, do_throw) {
let mod = require(modName);
let assert = require('test/source-map/assert');
let util = require('test/source-map/util');
assert.init(do_throw);
for (let k in mod) {
if (/^test/.test(k)) {
mod[k](assert, util);
}
}
}
this.runSourceMapTests = runSourceMapTests;

View File

@@ -0,0 +1,8 @@
/*
* WARNING!
*
* Do not edit this file directly, it is built from the sources at
* https://github.com/mozilla/source-map/
*/
Components.utils.import('resource://test/Utils.jsm');

View File

@@ -0,0 +1,3 @@
function run_test() {
runSourceMapTests('{THIS_MODULE}', do_throw);
}

View File

@@ -0,0 +1,8 @@
/*
* Copyright 2009-2011 Mozilla Foundation and contributors
* Licensed under the New BSD license. See LICENSE.txt or:
* http://opensource.org/licenses/BSD-3-Clause
*/
exports.SourceMapGenerator = require('./source-map/source-map-generator').SourceMapGenerator;
exports.SourceMapConsumer = require('./source-map/source-map-consumer').SourceMapConsumer;
exports.SourceNode = require('./source-map/source-node').SourceNode;

View File

@@ -0,0 +1,97 @@
/* -*- Mode: js; js-indent-level: 2; -*- */
/*
* Copyright 2011 Mozilla Foundation and contributors
* Licensed under the New BSD license. See LICENSE or:
* http://opensource.org/licenses/BSD-3-Clause
*/
if (typeof define !== 'function') {
var define = require('amdefine')(module, require);
}
define(function (require, exports, module) {
var util = require('./util');
/**
* A data structure which is a combination of an array and a set. Adding a new
* member is O(1), testing for membership is O(1), and finding the index of an
* element is O(1). Removing elements from the set is not supported. Only
* strings are supported for membership.
*/
function ArraySet() {
this._array = [];
this._set = {};
}
/**
* Static method for creating ArraySet instances from an existing array.
*/
ArraySet.fromArray = function ArraySet_fromArray(aArray, aAllowDuplicates) {
var set = new ArraySet();
for (var i = 0, len = aArray.length; i < len; i++) {
set.add(aArray[i], aAllowDuplicates);
}
return set;
};
/**
* Add the given string to this set.
*
* @param String aStr
*/
ArraySet.prototype.add = function ArraySet_add(aStr, aAllowDuplicates) {
var isDuplicate = this.has(aStr);
var idx = this._array.length;
if (!isDuplicate || aAllowDuplicates) {
this._array.push(aStr);
}
if (!isDuplicate) {
this._set[util.toSetString(aStr)] = idx;
}
};
/**
* Is the given string a member of this set?
*
* @param String aStr
*/
ArraySet.prototype.has = function ArraySet_has(aStr) {
return Object.prototype.hasOwnProperty.call(this._set,
util.toSetString(aStr));
};
/**
* What is the index of the given string in the array?
*
* @param String aStr
*/
ArraySet.prototype.indexOf = function ArraySet_indexOf(aStr) {
if (this.has(aStr)) {
return this._set[util.toSetString(aStr)];
}
throw new Error('"' + aStr + '" is not in the set.');
};
/**
* What is the element at the given index?
*
* @param Number aIdx
*/
ArraySet.prototype.at = function ArraySet_at(aIdx) {
if (aIdx >= 0 && aIdx < this._array.length) {
return this._array[aIdx];
}
throw new Error('No element indexed by ' + aIdx);
};
/**
* Returns the array representation of this set (which has the proper indices
* indicated by indexOf). Note that this is a copy of the internal array used
* for storing the members so that no one can mess with internal state.
*/
ArraySet.prototype.toArray = function ArraySet_toArray() {
return this._array.slice();
};
exports.ArraySet = ArraySet;
});

View File

@@ -0,0 +1,144 @@
/* -*- Mode: js; js-indent-level: 2; -*- */
/*
* Copyright 2011 Mozilla Foundation and contributors
* Licensed under the New BSD license. See LICENSE or:
* http://opensource.org/licenses/BSD-3-Clause
*
* Based on the Base 64 VLQ implementation in Closure Compiler:
* https://code.google.com/p/closure-compiler/source/browse/trunk/src/com/google/debugging/sourcemap/Base64VLQ.java
*
* Copyright 2011 The Closure Compiler Authors. All rights reserved.
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
if (typeof define !== 'function') {
var define = require('amdefine')(module, require);
}
define(function (require, exports, module) {
var base64 = require('./base64');
// A single base 64 digit can contain 6 bits of data. For the base 64 variable
// length quantities we use in the source map spec, the first bit is the sign,
// the next four bits are the actual value, and the 6th bit is the
// continuation bit. The continuation bit tells us whether there are more
// digits in this value following this digit.
//
// Continuation
// | Sign
// | |
// V V
// 101011
var VLQ_BASE_SHIFT = 5;
// binary: 100000
var VLQ_BASE = 1 << VLQ_BASE_SHIFT;
// binary: 011111
var VLQ_BASE_MASK = VLQ_BASE - 1;
// binary: 100000
var VLQ_CONTINUATION_BIT = VLQ_BASE;
/**
* Converts from a two-complement value to a value where the sign bit is
* is placed in the least significant bit. For example, as decimals:
* 1 becomes 2 (10 binary), -1 becomes 3 (11 binary)
* 2 becomes 4 (100 binary), -2 becomes 5 (101 binary)
*/
function toVLQSigned(aValue) {
return aValue < 0
? ((-aValue) << 1) + 1
: (aValue << 1) + 0;
}
/**
* Converts to a two-complement value from a value where the sign bit is
* is placed in the least significant bit. For example, as decimals:
* 2 (10 binary) becomes 1, 3 (11 binary) becomes -1
* 4 (100 binary) becomes 2, 5 (101 binary) becomes -2
*/
function fromVLQSigned(aValue) {
var isNegative = (aValue & 1) === 1;
var shifted = aValue >> 1;
return isNegative
? -shifted
: shifted;
}
/**
* Returns the base 64 VLQ encoded value.
*/
exports.encode = function base64VLQ_encode(aValue) {
var encoded = "";
var digit;
var vlq = toVLQSigned(aValue);
do {
digit = vlq & VLQ_BASE_MASK;
vlq >>>= VLQ_BASE_SHIFT;
if (vlq > 0) {
// There are still more digits in this value, so we must make sure the
// continuation bit is marked.
digit |= VLQ_CONTINUATION_BIT;
}
encoded += base64.encode(digit);
} while (vlq > 0);
return encoded;
};
/**
* Decodes the next base 64 VLQ value from the given string and returns the
* value and the rest of the string.
*/
exports.decode = function base64VLQ_decode(aStr) {
var i = 0;
var strLen = aStr.length;
var result = 0;
var shift = 0;
var continuation, digit;
do {
if (i >= strLen) {
throw new Error("Expected more digits in base 64 VLQ value.");
}
digit = base64.decode(aStr.charAt(i++));
continuation = !!(digit & VLQ_CONTINUATION_BIT);
digit &= VLQ_BASE_MASK;
result = result + (digit << shift);
shift += VLQ_BASE_SHIFT;
} while (continuation);
return {
value: fromVLQSigned(result),
rest: aStr.slice(i)
};
};
});

View File

@@ -0,0 +1,42 @@
/* -*- Mode: js; js-indent-level: 2; -*- */
/*
* Copyright 2011 Mozilla Foundation and contributors
* Licensed under the New BSD license. See LICENSE or:
* http://opensource.org/licenses/BSD-3-Clause
*/
if (typeof define !== 'function') {
var define = require('amdefine')(module, require);
}
define(function (require, exports, module) {
var charToIntMap = {};
var intToCharMap = {};
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
.split('')
.forEach(function (ch, index) {
charToIntMap[ch] = index;
intToCharMap[index] = ch;
});
/**
* Encode an integer in the range of 0 to 63 to a single base 64 digit.
*/
exports.encode = function base64_encode(aNumber) {
if (aNumber in intToCharMap) {
return intToCharMap[aNumber];
}
throw new TypeError("Must be between 0 and 63: " + aNumber);
};
/**
* Decode a single base 64 digit to an integer.
*/
exports.decode = function base64_decode(aChar) {
if (aChar in charToIntMap) {
return charToIntMap[aChar];
}
throw new TypeError("Not a valid base 64 digit: " + aChar);
};
});

View File

@@ -0,0 +1,81 @@
/* -*- Mode: js; js-indent-level: 2; -*- */
/*
* Copyright 2011 Mozilla Foundation and contributors
* Licensed under the New BSD license. See LICENSE or:
* http://opensource.org/licenses/BSD-3-Clause
*/
if (typeof define !== 'function') {
var define = require('amdefine')(module, require);
}
define(function (require, exports, module) {
/**
* Recursive implementation of binary search.
*
* @param aLow Indices here and lower do not contain the needle.
* @param aHigh Indices here and higher do not contain the needle.
* @param aNeedle The element being searched for.
* @param aHaystack The non-empty array being searched.
* @param aCompare Function which takes two elements and returns -1, 0, or 1.
*/
function recursiveSearch(aLow, aHigh, aNeedle, aHaystack, aCompare) {
// This function terminates when one of the following is true:
//
// 1. We find the exact element we are looking for.
//
// 2. We did not find the exact element, but we can return the next
// closest element that is less than that element.
//
// 3. We did not find the exact element, and there is no next-closest
// element which is less than the one we are searching for, so we
// return null.
var mid = Math.floor((aHigh - aLow) / 2) + aLow;
var cmp = aCompare(aNeedle, aHaystack[mid], true);
if (cmp === 0) {
// Found the element we are looking for.
return aHaystack[mid];
}
else if (cmp > 0) {
// aHaystack[mid] is greater than our needle.
if (aHigh - mid > 1) {
// The element is in the upper half.
return recursiveSearch(mid, aHigh, aNeedle, aHaystack, aCompare);
}
// We did not find an exact match, return the next closest one
// (termination case 2).
return aHaystack[mid];
}
else {
// aHaystack[mid] is less than our needle.
if (mid - aLow > 1) {
// The element is in the lower half.
return recursiveSearch(aLow, mid, aNeedle, aHaystack, aCompare);
}
// The exact needle element was not found in this haystack. Determine if
// we are in termination case (2) or (3) and return the appropriate thing.
return aLow < 0
? null
: aHaystack[aLow];
}
}
/**
* This is an implementation of binary search which will always try and return
* the next lowest value checked if there is no exact hit. This is because
* mappings between original and generated line/col pairs are single points,
* and there is an implicit region between each of them, so a miss just means
* that you aren't on the very start of a region.
*
* @param aNeedle The element you are looking for.
* @param aHaystack The array that is being searched.
* @param aCompare A function which takes the needle and an element in the
* array and returns -1, 0, or 1 depending on whether the needle is less
* than, equal to, or greater than the element, respectively.
*/
exports.search = function search(aNeedle, aHaystack, aCompare) {
return aHaystack.length > 0
? recursiveSearch(-1, aHaystack.length, aNeedle, aHaystack, aCompare)
: null;
};
});

View File

@@ -0,0 +1,477 @@
/* -*- Mode: js; js-indent-level: 2; -*- */
/*
* Copyright 2011 Mozilla Foundation and contributors
* Licensed under the New BSD license. See LICENSE or:
* http://opensource.org/licenses/BSD-3-Clause
*/
if (typeof define !== 'function') {
var define = require('amdefine')(module, require);
}
define(function (require, exports, module) {
var util = require('./util');
var binarySearch = require('./binary-search');
var ArraySet = require('./array-set').ArraySet;
var base64VLQ = require('./base64-vlq');
/**
* A SourceMapConsumer instance represents a parsed source map which we can
* query for information about the original file positions by giving it a file
* position in the generated source.
*
* The only parameter is the raw source map (either as a JSON string, or
* already parsed to an object). According to the spec, source maps have the
* following attributes:
*
* - version: Which version of the source map spec this map is following.
* - sources: An array of URLs to the original source files.
* - names: An array of identifiers which can be referrenced by individual mappings.
* - sourceRoot: Optional. The URL root from which all sources are relative.
* - sourcesContent: Optional. An array of contents of the original source files.
* - mappings: A string of base64 VLQs which contain the actual mappings.
* - file: The generated file this source map is associated with.
*
* Here is an example source map, taken from the source map spec[0]:
*
* {
* version : 3,
* file: "out.js",
* sourceRoot : "",
* sources: ["foo.js", "bar.js"],
* names: ["src", "maps", "are", "fun"],
* mappings: "AA,AB;;ABCDE;"
* }
*
* [0]: https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit?pli=1#
*/
function SourceMapConsumer(aSourceMap) {
var sourceMap = aSourceMap;
if (typeof aSourceMap === 'string') {
sourceMap = JSON.parse(aSourceMap.replace(/^\)\]\}'/, ''));
}
var version = util.getArg(sourceMap, 'version');
var sources = util.getArg(sourceMap, 'sources');
// Sass 3.3 leaves out the 'names' array, so we deviate from the spec (which
// requires the array) to play nice here.
var names = util.getArg(sourceMap, 'names', []);
var sourceRoot = util.getArg(sourceMap, 'sourceRoot', null);
var sourcesContent = util.getArg(sourceMap, 'sourcesContent', null);
var mappings = util.getArg(sourceMap, 'mappings');
var file = util.getArg(sourceMap, 'file', null);
// Once again, Sass deviates from the spec and supplies the version as a
// string rather than a number, so we use loose equality checking here.
if (version != this._version) {
throw new Error('Unsupported version: ' + version);
}
// Pass `true` below to allow duplicate names and sources. While source maps
// are intended to be compressed and deduplicated, the TypeScript compiler
// sometimes generates source maps with duplicates in them. See Github issue
// #72 and bugzil.la/889492.
this._names = ArraySet.fromArray(names, true);
this._sources = ArraySet.fromArray(sources, true);
this.sourceRoot = sourceRoot;
this.sourcesContent = sourcesContent;
this._mappings = mappings;
this.file = file;
}
/**
* Create a SourceMapConsumer from a SourceMapGenerator.
*
* @param SourceMapGenerator aSourceMap
* The source map that will be consumed.
* @returns SourceMapConsumer
*/
SourceMapConsumer.fromSourceMap =
function SourceMapConsumer_fromSourceMap(aSourceMap) {
var smc = Object.create(SourceMapConsumer.prototype);
smc._names = ArraySet.fromArray(aSourceMap._names.toArray(), true);
smc._sources = ArraySet.fromArray(aSourceMap._sources.toArray(), true);
smc.sourceRoot = aSourceMap._sourceRoot;
smc.sourcesContent = aSourceMap._generateSourcesContent(smc._sources.toArray(),
smc.sourceRoot);
smc.file = aSourceMap._file;
smc.__generatedMappings = aSourceMap._mappings.slice()
.sort(util.compareByGeneratedPositions);
smc.__originalMappings = aSourceMap._mappings.slice()
.sort(util.compareByOriginalPositions);
return smc;
};
/**
* The version of the source mapping spec that we are consuming.
*/
SourceMapConsumer.prototype._version = 3;
/**
* The list of original sources.
*/
Object.defineProperty(SourceMapConsumer.prototype, 'sources', {
get: function () {
return this._sources.toArray().map(function (s) {
return this.sourceRoot ? util.join(this.sourceRoot, s) : s;
}, this);
}
});
// `__generatedMappings` and `__originalMappings` are arrays that hold the
// parsed mapping coordinates from the source map's "mappings" attribute. They
// are lazily instantiated, accessed via the `_generatedMappings` and
// `_originalMappings` getters respectively, and we only parse the mappings
// and create these arrays once queried for a source location. We jump through
// these hoops because there can be many thousands of mappings, and parsing
// them is expensive, so we only want to do it if we must.
//
// Each object in the arrays is of the form:
//
// {
// generatedLine: The line number in the generated code,
// generatedColumn: The column number in the generated code,
// source: The path to the original source file that generated this
// chunk of code,
// originalLine: The line number in the original source that
// corresponds to this chunk of generated code,
// originalColumn: The column number in the original source that
// corresponds to this chunk of generated code,
// name: The name of the original symbol which generated this chunk of
// code.
// }
//
// All properties except for `generatedLine` and `generatedColumn` can be
// `null`.
//
// `_generatedMappings` is ordered by the generated positions.
//
// `_originalMappings` is ordered by the original positions.
SourceMapConsumer.prototype.__generatedMappings = null;
Object.defineProperty(SourceMapConsumer.prototype, '_generatedMappings', {
get: function () {
if (!this.__generatedMappings) {
this.__generatedMappings = [];
this.__originalMappings = [];
this._parseMappings(this._mappings, this.sourceRoot);
}
return this.__generatedMappings;
}
});
SourceMapConsumer.prototype.__originalMappings = null;
Object.defineProperty(SourceMapConsumer.prototype, '_originalMappings', {
get: function () {
if (!this.__originalMappings) {
this.__generatedMappings = [];
this.__originalMappings = [];
this._parseMappings(this._mappings, this.sourceRoot);
}
return this.__originalMappings;
}
});
/**
* Parse the mappings in a string in to a data structure which we can easily
* query (the ordered arrays in the `this.__generatedMappings` and
* `this.__originalMappings` properties).
*/
SourceMapConsumer.prototype._parseMappings =
function SourceMapConsumer_parseMappings(aStr, aSourceRoot) {
var generatedLine = 1;
var previousGeneratedColumn = 0;
var previousOriginalLine = 0;
var previousOriginalColumn = 0;
var previousSource = 0;
var previousName = 0;
var mappingSeparator = /^[,;]/;
var str = aStr;
var mapping;
var temp;
while (str.length > 0) {
if (str.charAt(0) === ';') {
generatedLine++;
str = str.slice(1);
previousGeneratedColumn = 0;
}
else if (str.charAt(0) === ',') {
str = str.slice(1);
}
else {
mapping = {};
mapping.generatedLine = generatedLine;
// Generated column.
temp = base64VLQ.decode(str);
mapping.generatedColumn = previousGeneratedColumn + temp.value;
previousGeneratedColumn = mapping.generatedColumn;
str = temp.rest;
if (str.length > 0 && !mappingSeparator.test(str.charAt(0))) {
// Original source.
temp = base64VLQ.decode(str);
mapping.source = this._sources.at(previousSource + temp.value);
previousSource += temp.value;
str = temp.rest;
if (str.length === 0 || mappingSeparator.test(str.charAt(0))) {
throw new Error('Found a source, but no line and column');
}
// Original line.
temp = base64VLQ.decode(str);
mapping.originalLine = previousOriginalLine + temp.value;
previousOriginalLine = mapping.originalLine;
// Lines are stored 0-based
mapping.originalLine += 1;
str = temp.rest;
if (str.length === 0 || mappingSeparator.test(str.charAt(0))) {
throw new Error('Found a source and line, but no column');
}
// Original column.
temp = base64VLQ.decode(str);
mapping.originalColumn = previousOriginalColumn + temp.value;
previousOriginalColumn = mapping.originalColumn;
str = temp.rest;
if (str.length > 0 && !mappingSeparator.test(str.charAt(0))) {
// Original name.
temp = base64VLQ.decode(str);
mapping.name = this._names.at(previousName + temp.value);
previousName += temp.value;
str = temp.rest;
}
}
this.__generatedMappings.push(mapping);
if (typeof mapping.originalLine === 'number') {
this.__originalMappings.push(mapping);
}
}
}
this.__originalMappings.sort(util.compareByOriginalPositions);
};
/**
* Find the mapping that best matches the hypothetical "needle" mapping that
* we are searching for in the given "haystack" of mappings.
*/
SourceMapConsumer.prototype._findMapping =
function SourceMapConsumer_findMapping(aNeedle, aMappings, aLineName,
aColumnName, aComparator) {
// To return the position we are searching for, we must first find the
// mapping for the given position and then return the opposite position it
// points to. Because the mappings are sorted, we can use binary search to
// find the best mapping.
if (aNeedle[aLineName] <= 0) {
throw new TypeError('Line must be greater than or equal to 1, got '
+ aNeedle[aLineName]);
}
if (aNeedle[aColumnName] < 0) {
throw new TypeError('Column must be greater than or equal to 0, got '
+ aNeedle[aColumnName]);
}
return binarySearch.search(aNeedle, aMappings, aComparator);
};
/**
* Returns the original source, line, and column information for the generated
* source's line and column positions provided. The only argument is an object
* with the following properties:
*
* - line: The line number in the generated source.
* - column: The column number in the generated source.
*
* and an object is returned with the following properties:
*
* - source: The original source file, or null.
* - line: The line number in the original source, or null.
* - column: The column number in the original source, or null.
* - name: The original identifier, or null.
*/
SourceMapConsumer.prototype.originalPositionFor =
function SourceMapConsumer_originalPositionFor(aArgs) {
var needle = {
generatedLine: util.getArg(aArgs, 'line'),
generatedColumn: util.getArg(aArgs, 'column')
};
var mapping = this._findMapping(needle,
this._generatedMappings,
"generatedLine",
"generatedColumn",
util.compareByGeneratedPositions);
if (mapping) {
var source = util.getArg(mapping, 'source', null);
if (source && this.sourceRoot) {
source = util.join(this.sourceRoot, source);
}
return {
source: source,
line: util.getArg(mapping, 'originalLine', null),
column: util.getArg(mapping, 'originalColumn', null),
name: util.getArg(mapping, 'name', null)
};
}
return {
source: null,
line: null,
column: null,
name: null
};
};
/**
* Returns the original source content. The only argument is the url of the
* original source file. Returns null if no original source content is
* availible.
*/
SourceMapConsumer.prototype.sourceContentFor =
function SourceMapConsumer_sourceContentFor(aSource) {
if (!this.sourcesContent) {
return null;
}
if (this.sourceRoot) {
aSource = util.relative(this.sourceRoot, aSource);
}
if (this._sources.has(aSource)) {
return this.sourcesContent[this._sources.indexOf(aSource)];
}
var url;
if (this.sourceRoot
&& (url = util.urlParse(this.sourceRoot))) {
// XXX: file:// URIs and absolute paths lead to unexpected behavior for
// many users. We can help them out when they expect file:// URIs to
// behave like it would if they were running a local HTTP server. See
// https://bugzilla.mozilla.org/show_bug.cgi?id=885597.
var fileUriAbsPath = aSource.replace(/^file:\/\//, "");
if (url.scheme == "file"
&& this._sources.has(fileUriAbsPath)) {
return this.sourcesContent[this._sources.indexOf(fileUriAbsPath)]
}
if ((!url.path || url.path == "/")
&& this._sources.has("/" + aSource)) {
return this.sourcesContent[this._sources.indexOf("/" + aSource)];
}
}
throw new Error('"' + aSource + '" is not in the SourceMap.');
};
/**
* Returns the generated line and column information for the original source,
* line, and column positions provided. The only argument is an object with
* the following properties:
*
* - source: The filename of the original source.
* - line: The line number in the original source.
* - column: The column number in the original source.
*
* and an object is returned with the following properties:
*
* - line: The line number in the generated source, or null.
* - column: The column number in the generated source, or null.
*/
SourceMapConsumer.prototype.generatedPositionFor =
function SourceMapConsumer_generatedPositionFor(aArgs) {
var needle = {
source: util.getArg(aArgs, 'source'),
originalLine: util.getArg(aArgs, 'line'),
originalColumn: util.getArg(aArgs, 'column')
};
if (this.sourceRoot) {
needle.source = util.relative(this.sourceRoot, needle.source);
}
var mapping = this._findMapping(needle,
this._originalMappings,
"originalLine",
"originalColumn",
util.compareByOriginalPositions);
if (mapping) {
return {
line: util.getArg(mapping, 'generatedLine', null),
column: util.getArg(mapping, 'generatedColumn', null)
};
}
return {
line: null,
column: null
};
};
SourceMapConsumer.GENERATED_ORDER = 1;
SourceMapConsumer.ORIGINAL_ORDER = 2;
/**
* Iterate over each mapping between an original source/line/column and a
* generated line/column in this source map.
*
* @param Function aCallback
* The function that is called with each mapping.
* @param Object aContext
* Optional. If specified, this object will be the value of `this` every
* time that `aCallback` is called.
* @param aOrder
* Either `SourceMapConsumer.GENERATED_ORDER` or
* `SourceMapConsumer.ORIGINAL_ORDER`. Specifies whether you want to
* iterate over the mappings sorted by the generated file's line/column
* order or the original's source/line/column order, respectively. Defaults to
* `SourceMapConsumer.GENERATED_ORDER`.
*/
SourceMapConsumer.prototype.eachMapping =
function SourceMapConsumer_eachMapping(aCallback, aContext, aOrder) {
var context = aContext || null;
var order = aOrder || SourceMapConsumer.GENERATED_ORDER;
var mappings;
switch (order) {
case SourceMapConsumer.GENERATED_ORDER:
mappings = this._generatedMappings;
break;
case SourceMapConsumer.ORIGINAL_ORDER:
mappings = this._originalMappings;
break;
default:
throw new Error("Unknown order of iteration.");
}
var sourceRoot = this.sourceRoot;
mappings.map(function (mapping) {
var source = mapping.source;
if (source && sourceRoot) {
source = util.join(sourceRoot, source);
}
return {
source: source,
generatedLine: mapping.generatedLine,
generatedColumn: mapping.generatedColumn,
originalLine: mapping.originalLine,
originalColumn: mapping.originalColumn,
name: mapping.name
};
}).forEach(aCallback, context);
};
exports.SourceMapConsumer = SourceMapConsumer;
});

View File

@@ -0,0 +1,380 @@
/* -*- Mode: js; js-indent-level: 2; -*- */
/*
* Copyright 2011 Mozilla Foundation and contributors
* Licensed under the New BSD license. See LICENSE or:
* http://opensource.org/licenses/BSD-3-Clause
*/
if (typeof define !== 'function') {
var define = require('amdefine')(module, require);
}
define(function (require, exports, module) {
var base64VLQ = require('./base64-vlq');
var util = require('./util');
var ArraySet = require('./array-set').ArraySet;
/**
* An instance of the SourceMapGenerator represents a source map which is
* being built incrementally. To create a new one, you must pass an object
* with the following properties:
*
* - file: The filename of the generated source.
* - sourceRoot: An optional root for all URLs in this source map.
*/
function SourceMapGenerator(aArgs) {
this._file = util.getArg(aArgs, 'file');
this._sourceRoot = util.getArg(aArgs, 'sourceRoot', null);
this._sources = new ArraySet();
this._names = new ArraySet();
this._mappings = [];
this._sourcesContents = null;
}
SourceMapGenerator.prototype._version = 3;
/**
* Creates a new SourceMapGenerator based on a SourceMapConsumer
*
* @param aSourceMapConsumer The SourceMap.
*/
SourceMapGenerator.fromSourceMap =
function SourceMapGenerator_fromSourceMap(aSourceMapConsumer) {
var sourceRoot = aSourceMapConsumer.sourceRoot;
var generator = new SourceMapGenerator({
file: aSourceMapConsumer.file,
sourceRoot: sourceRoot
});
aSourceMapConsumer.eachMapping(function (mapping) {
var newMapping = {
generated: {
line: mapping.generatedLine,
column: mapping.generatedColumn
}
};
if (mapping.source) {
newMapping.source = mapping.source;
if (sourceRoot) {
newMapping.source = util.relative(sourceRoot, newMapping.source);
}
newMapping.original = {
line: mapping.originalLine,
column: mapping.originalColumn
};
if (mapping.name) {
newMapping.name = mapping.name;
}
}
generator.addMapping(newMapping);
});
aSourceMapConsumer.sources.forEach(function (sourceFile) {
var content = aSourceMapConsumer.sourceContentFor(sourceFile);
if (content) {
generator.setSourceContent(sourceFile, content);
}
});
return generator;
};
/**
* Add a single mapping from original source line and column to the generated
* source's line and column for this source map being created. The mapping
* object should have the following properties:
*
* - generated: An object with the generated line and column positions.
* - original: An object with the original line and column positions.
* - source: The original source file (relative to the sourceRoot).
* - name: An optional original token name for this mapping.
*/
SourceMapGenerator.prototype.addMapping =
function SourceMapGenerator_addMapping(aArgs) {
var generated = util.getArg(aArgs, 'generated');
var original = util.getArg(aArgs, 'original', null);
var source = util.getArg(aArgs, 'source', null);
var name = util.getArg(aArgs, 'name', null);
this._validateMapping(generated, original, source, name);
if (source && !this._sources.has(source)) {
this._sources.add(source);
}
if (name && !this._names.has(name)) {
this._names.add(name);
}
this._mappings.push({
generatedLine: generated.line,
generatedColumn: generated.column,
originalLine: original != null && original.line,
originalColumn: original != null && original.column,
source: source,
name: name
});
};
/**
* Set the source content for a source file.
*/
SourceMapGenerator.prototype.setSourceContent =
function SourceMapGenerator_setSourceContent(aSourceFile, aSourceContent) {
var source = aSourceFile;
if (this._sourceRoot) {
source = util.relative(this._sourceRoot, source);
}
if (aSourceContent !== null) {
// Add the source content to the _sourcesContents map.
// Create a new _sourcesContents map if the property is null.
if (!this._sourcesContents) {
this._sourcesContents = {};
}
this._sourcesContents[util.toSetString(source)] = aSourceContent;
} else {
// Remove the source file from the _sourcesContents map.
// If the _sourcesContents map is empty, set the property to null.
delete this._sourcesContents[util.toSetString(source)];
if (Object.keys(this._sourcesContents).length === 0) {
this._sourcesContents = null;
}
}
};
/**
* Applies the mappings of a sub-source-map for a specific source file to the
* source map being generated. Each mapping to the supplied source file is
* rewritten using the supplied source map. Note: The resolution for the
* resulting mappings is the minimium of this map and the supplied map.
*
* @param aSourceMapConsumer The source map to be applied.
* @param aSourceFile Optional. The filename of the source file.
* If omitted, SourceMapConsumer's file property will be used.
*/
SourceMapGenerator.prototype.applySourceMap =
function SourceMapGenerator_applySourceMap(aSourceMapConsumer, aSourceFile) {
// If aSourceFile is omitted, we will use the file property of the SourceMap
if (!aSourceFile) {
aSourceFile = aSourceMapConsumer.file;
}
var sourceRoot = this._sourceRoot;
// Make "aSourceFile" relative if an absolute Url is passed.
if (sourceRoot) {
aSourceFile = util.relative(sourceRoot, aSourceFile);
}
// Applying the SourceMap can add and remove items from the sources and
// the names array.
var newSources = new ArraySet();
var newNames = new ArraySet();
// Find mappings for the "aSourceFile"
this._mappings.forEach(function (mapping) {
if (mapping.source === aSourceFile && mapping.originalLine) {
// Check if it can be mapped by the source map, then update the mapping.
var original = aSourceMapConsumer.originalPositionFor({
line: mapping.originalLine,
column: mapping.originalColumn
});
if (original.source !== null) {
// Copy mapping
if (sourceRoot) {
mapping.source = util.relative(sourceRoot, original.source);
} else {
mapping.source = original.source;
}
mapping.originalLine = original.line;
mapping.originalColumn = original.column;
if (original.name !== null && mapping.name !== null) {
// Only use the identifier name if it's an identifier
// in both SourceMaps
mapping.name = original.name;
}
}
}
var source = mapping.source;
if (source && !newSources.has(source)) {
newSources.add(source);
}
var name = mapping.name;
if (name && !newNames.has(name)) {
newNames.add(name);
}
}, this);
this._sources = newSources;
this._names = newNames;
// Copy sourcesContents of applied map.
aSourceMapConsumer.sources.forEach(function (sourceFile) {
var content = aSourceMapConsumer.sourceContentFor(sourceFile);
if (content) {
if (sourceRoot) {
sourceFile = util.relative(sourceRoot, sourceFile);
}
this.setSourceContent(sourceFile, content);
}
}, this);
};
/**
* A mapping can have one of the three levels of data:
*
* 1. Just the generated position.
* 2. The Generated position, original position, and original source.
* 3. Generated and original position, original source, as well as a name
* token.
*
* To maintain consistency, we validate that any new mapping being added falls
* in to one of these categories.
*/
SourceMapGenerator.prototype._validateMapping =
function SourceMapGenerator_validateMapping(aGenerated, aOriginal, aSource,
aName) {
if (aGenerated && 'line' in aGenerated && 'column' in aGenerated
&& aGenerated.line > 0 && aGenerated.column >= 0
&& !aOriginal && !aSource && !aName) {
// Case 1.
return;
}
else if (aGenerated && 'line' in aGenerated && 'column' in aGenerated
&& aOriginal && 'line' in aOriginal && 'column' in aOriginal
&& aGenerated.line > 0 && aGenerated.column >= 0
&& aOriginal.line > 0 && aOriginal.column >= 0
&& aSource) {
// Cases 2 and 3.
return;
}
else {
throw new Error('Invalid mapping: ' + JSON.stringify({
generated: aGenerated,
source: aSource,
orginal: aOriginal,
name: aName
}));
}
};
/**
* Serialize the accumulated mappings in to the stream of base 64 VLQs
* specified by the source map format.
*/
SourceMapGenerator.prototype._serializeMappings =
function SourceMapGenerator_serializeMappings() {
var previousGeneratedColumn = 0;
var previousGeneratedLine = 1;
var previousOriginalColumn = 0;
var previousOriginalLine = 0;
var previousName = 0;
var previousSource = 0;
var result = '';
var mapping;
// The mappings must be guaranteed to be in sorted order before we start
// serializing them or else the generated line numbers (which are defined
// via the ';' separators) will be all messed up. Note: it might be more
// performant to maintain the sorting as we insert them, rather than as we
// serialize them, but the big O is the same either way.
this._mappings.sort(util.compareByGeneratedPositions);
for (var i = 0, len = this._mappings.length; i < len; i++) {
mapping = this._mappings[i];
if (mapping.generatedLine !== previousGeneratedLine) {
previousGeneratedColumn = 0;
while (mapping.generatedLine !== previousGeneratedLine) {
result += ';';
previousGeneratedLine++;
}
}
else {
if (i > 0) {
if (!util.compareByGeneratedPositions(mapping, this._mappings[i - 1])) {
continue;
}
result += ',';
}
}
result += base64VLQ.encode(mapping.generatedColumn
- previousGeneratedColumn);
previousGeneratedColumn = mapping.generatedColumn;
if (mapping.source) {
result += base64VLQ.encode(this._sources.indexOf(mapping.source)
- previousSource);
previousSource = this._sources.indexOf(mapping.source);
// lines are stored 0-based in SourceMap spec version 3
result += base64VLQ.encode(mapping.originalLine - 1
- previousOriginalLine);
previousOriginalLine = mapping.originalLine - 1;
result += base64VLQ.encode(mapping.originalColumn
- previousOriginalColumn);
previousOriginalColumn = mapping.originalColumn;
if (mapping.name) {
result += base64VLQ.encode(this._names.indexOf(mapping.name)
- previousName);
previousName = this._names.indexOf(mapping.name);
}
}
}
return result;
};
SourceMapGenerator.prototype._generateSourcesContent =
function SourceMapGenerator_generateSourcesContent(aSources, aSourceRoot) {
return aSources.map(function (source) {
if (!this._sourcesContents) {
return null;
}
if (aSourceRoot) {
source = util.relative(aSourceRoot, source);
}
var key = util.toSetString(source);
return Object.prototype.hasOwnProperty.call(this._sourcesContents,
key)
? this._sourcesContents[key]
: null;
}, this);
};
/**
* Externalize the source map.
*/
SourceMapGenerator.prototype.toJSON =
function SourceMapGenerator_toJSON() {
var map = {
version: this._version,
file: this._file,
sources: this._sources.toArray(),
names: this._names.toArray(),
mappings: this._serializeMappings()
};
if (this._sourceRoot) {
map.sourceRoot = this._sourceRoot;
}
if (this._sourcesContents) {
map.sourcesContent = this._generateSourcesContent(map.sources, map.sourceRoot);
}
return map;
};
/**
* Render the source map being generated to a string.
*/
SourceMapGenerator.prototype.toString =
function SourceMapGenerator_toString() {
return JSON.stringify(this);
};
exports.SourceMapGenerator = SourceMapGenerator;
});

View File

@@ -0,0 +1,371 @@
/* -*- Mode: js; js-indent-level: 2; -*- */
/*
* Copyright 2011 Mozilla Foundation and contributors
* Licensed under the New BSD license. See LICENSE or:
* http://opensource.org/licenses/BSD-3-Clause
*/
if (typeof define !== 'function') {
var define = require('amdefine')(module, require);
}
define(function (require, exports, module) {
var SourceMapGenerator = require('./source-map-generator').SourceMapGenerator;
var util = require('./util');
/**
* SourceNodes provide a way to abstract over interpolating/concatenating
* snippets of generated JavaScript source code while maintaining the line and
* column information associated with the original source code.
*
* @param aLine The original line number.
* @param aColumn The original column number.
* @param aSource The original source's filename.
* @param aChunks Optional. An array of strings which are snippets of
* generated JS, or other SourceNodes.
* @param aName The original identifier.
*/
function SourceNode(aLine, aColumn, aSource, aChunks, aName) {
this.children = [];
this.sourceContents = {};
this.line = aLine === undefined ? null : aLine;
this.column = aColumn === undefined ? null : aColumn;
this.source = aSource === undefined ? null : aSource;
this.name = aName === undefined ? null : aName;
if (aChunks != null) this.add(aChunks);
}
/**
* Creates a SourceNode from generated code and a SourceMapConsumer.
*
* @param aGeneratedCode The generated code
* @param aSourceMapConsumer The SourceMap for the generated code
*/
SourceNode.fromStringWithSourceMap =
function SourceNode_fromStringWithSourceMap(aGeneratedCode, aSourceMapConsumer) {
// The SourceNode we want to fill with the generated code
// and the SourceMap
var node = new SourceNode();
// The generated code
// Processed fragments are removed from this array.
var remainingLines = aGeneratedCode.split('\n');
// We need to remember the position of "remainingLines"
var lastGeneratedLine = 1, lastGeneratedColumn = 0;
// The generate SourceNodes we need a code range.
// To extract it current and last mapping is used.
// Here we store the last mapping.
var lastMapping = null;
aSourceMapConsumer.eachMapping(function (mapping) {
if (lastMapping === null) {
// We add the generated code until the first mapping
// to the SourceNode without any mapping.
// Each line is added as separate string.
while (lastGeneratedLine < mapping.generatedLine) {
node.add(remainingLines.shift() + "\n");
lastGeneratedLine++;
}
if (lastGeneratedColumn < mapping.generatedColumn) {
var nextLine = remainingLines[0];
node.add(nextLine.substr(0, mapping.generatedColumn));
remainingLines[0] = nextLine.substr(mapping.generatedColumn);
lastGeneratedColumn = mapping.generatedColumn;
}
} else {
// We add the code from "lastMapping" to "mapping":
// First check if there is a new line in between.
if (lastGeneratedLine < mapping.generatedLine) {
var code = "";
// Associate full lines with "lastMapping"
do {
code += remainingLines.shift() + "\n";
lastGeneratedLine++;
lastGeneratedColumn = 0;
} while (lastGeneratedLine < mapping.generatedLine);
// When we reached the correct line, we add code until we
// reach the correct column too.
if (lastGeneratedColumn < mapping.generatedColumn) {
var nextLine = remainingLines[0];
code += nextLine.substr(0, mapping.generatedColumn);
remainingLines[0] = nextLine.substr(mapping.generatedColumn);
lastGeneratedColumn = mapping.generatedColumn;
}
// Create the SourceNode.
addMappingWithCode(lastMapping, code);
} else {
// There is no new line in between.
// Associate the code between "lastGeneratedColumn" and
// "mapping.generatedColumn" with "lastMapping"
var nextLine = remainingLines[0];
var code = nextLine.substr(0, mapping.generatedColumn -
lastGeneratedColumn);
remainingLines[0] = nextLine.substr(mapping.generatedColumn -
lastGeneratedColumn);
lastGeneratedColumn = mapping.generatedColumn;
addMappingWithCode(lastMapping, code);
}
}
lastMapping = mapping;
}, this);
// We have processed all mappings.
// Associate the remaining code in the current line with "lastMapping"
// and add the remaining lines without any mapping
addMappingWithCode(lastMapping, remainingLines.join("\n"));
// Copy sourcesContent into SourceNode
aSourceMapConsumer.sources.forEach(function (sourceFile) {
var content = aSourceMapConsumer.sourceContentFor(sourceFile);
if (content) {
node.setSourceContent(sourceFile, content);
}
});
return node;
function addMappingWithCode(mapping, code) {
if (mapping === null || mapping.source === undefined) {
node.add(code);
} else {
node.add(new SourceNode(mapping.originalLine,
mapping.originalColumn,
mapping.source,
code,
mapping.name));
}
}
};
/**
* Add a chunk of generated JS to this source node.
*
* @param aChunk A string snippet of generated JS code, another instance of
* SourceNode, or an array where each member is one of those things.
*/
SourceNode.prototype.add = function SourceNode_add(aChunk) {
if (Array.isArray(aChunk)) {
aChunk.forEach(function (chunk) {
this.add(chunk);
}, this);
}
else if (aChunk instanceof SourceNode || typeof aChunk === "string") {
if (aChunk) {
this.children.push(aChunk);
}
}
else {
throw new TypeError(
"Expected a SourceNode, string, or an array of SourceNodes and strings. Got " + aChunk
);
}
return this;
};
/**
* Add a chunk of generated JS to the beginning of this source node.
*
* @param aChunk A string snippet of generated JS code, another instance of
* SourceNode, or an array where each member is one of those things.
*/
SourceNode.prototype.prepend = function SourceNode_prepend(aChunk) {
if (Array.isArray(aChunk)) {
for (var i = aChunk.length-1; i >= 0; i--) {
this.prepend(aChunk[i]);
}
}
else if (aChunk instanceof SourceNode || typeof aChunk === "string") {
this.children.unshift(aChunk);
}
else {
throw new TypeError(
"Expected a SourceNode, string, or an array of SourceNodes and strings. Got " + aChunk
);
}
return this;
};
/**
* Walk over the tree of JS snippets in this node and its children. The
* walking function is called once for each snippet of JS and is passed that
* snippet and the its original associated source's line/column location.
*
* @param aFn The traversal function.
*/
SourceNode.prototype.walk = function SourceNode_walk(aFn) {
var chunk;
for (var i = 0, len = this.children.length; i < len; i++) {
chunk = this.children[i];
if (chunk instanceof SourceNode) {
chunk.walk(aFn);
}
else {
if (chunk !== '') {
aFn(chunk, { source: this.source,
line: this.line,
column: this.column,
name: this.name });
}
}
}
};
/**
* Like `String.prototype.join` except for SourceNodes. Inserts `aStr` between
* each of `this.children`.
*
* @param aSep The separator.
*/
SourceNode.prototype.join = function SourceNode_join(aSep) {
var newChildren;
var i;
var len = this.children.length;
if (len > 0) {
newChildren = [];
for (i = 0; i < len-1; i++) {
newChildren.push(this.children[i]);
newChildren.push(aSep);
}
newChildren.push(this.children[i]);
this.children = newChildren;
}
return this;
};
/**
* Call String.prototype.replace on the very right-most source snippet. Useful
* for trimming whitespace from the end of a source node, etc.
*
* @param aPattern The pattern to replace.
* @param aReplacement The thing to replace the pattern with.
*/
SourceNode.prototype.replaceRight = function SourceNode_replaceRight(aPattern, aReplacement) {
var lastChild = this.children[this.children.length - 1];
if (lastChild instanceof SourceNode) {
lastChild.replaceRight(aPattern, aReplacement);
}
else if (typeof lastChild === 'string') {
this.children[this.children.length - 1] = lastChild.replace(aPattern, aReplacement);
}
else {
this.children.push(''.replace(aPattern, aReplacement));
}
return this;
};
/**
* Set the source content for a source file. This will be added to the SourceMapGenerator
* in the sourcesContent field.
*
* @param aSourceFile The filename of the source file
* @param aSourceContent The content of the source file
*/
SourceNode.prototype.setSourceContent =
function SourceNode_setSourceContent(aSourceFile, aSourceContent) {
this.sourceContents[util.toSetString(aSourceFile)] = aSourceContent;
};
/**
* Walk over the tree of SourceNodes. The walking function is called for each
* source file content and is passed the filename and source content.
*
* @param aFn The traversal function.
*/
SourceNode.prototype.walkSourceContents =
function SourceNode_walkSourceContents(aFn) {
for (var i = 0, len = this.children.length; i < len; i++) {
if (this.children[i] instanceof SourceNode) {
this.children[i].walkSourceContents(aFn);
}
}
var sources = Object.keys(this.sourceContents);
for (var i = 0, len = sources.length; i < len; i++) {
aFn(util.fromSetString(sources[i]), this.sourceContents[sources[i]]);
}
};
/**
* Return the string representation of this source node. Walks over the tree
* and concatenates all the various snippets together to one string.
*/
SourceNode.prototype.toString = function SourceNode_toString() {
var str = "";
this.walk(function (chunk) {
str += chunk;
});
return str;
};
/**
* Returns the string representation of this source node along with a source
* map.
*/
SourceNode.prototype.toStringWithSourceMap = function SourceNode_toStringWithSourceMap(aArgs) {
var generated = {
code: "",
line: 1,
column: 0
};
var map = new SourceMapGenerator(aArgs);
var sourceMappingActive = false;
var lastOriginalSource = null;
var lastOriginalLine = null;
var lastOriginalColumn = null;
var lastOriginalName = null;
this.walk(function (chunk, original) {
generated.code += chunk;
if (original.source !== null
&& original.line !== null
&& original.column !== null) {
if(lastOriginalSource !== original.source
|| lastOriginalLine !== original.line
|| lastOriginalColumn !== original.column
|| lastOriginalName !== original.name) {
map.addMapping({
source: original.source,
original: {
line: original.line,
column: original.column
},
generated: {
line: generated.line,
column: generated.column
},
name: original.name
});
}
lastOriginalSource = original.source;
lastOriginalLine = original.line;
lastOriginalColumn = original.column;
lastOriginalName = original.name;
sourceMappingActive = true;
} else if (sourceMappingActive) {
map.addMapping({
generated: {
line: generated.line,
column: generated.column
}
});
lastOriginalSource = null;
sourceMappingActive = false;
}
chunk.split('').forEach(function (ch) {
if (ch === '\n') {
generated.line++;
generated.column = 0;
} else {
generated.column++;
}
});
});
this.walkSourceContents(function (sourceFile, sourceContent) {
map.setSourceContent(sourceFile, sourceContent);
});
return { code: generated.code, map: map };
};
exports.SourceNode = SourceNode;
});

View File

@@ -0,0 +1,205 @@
/* -*- Mode: js; js-indent-level: 2; -*- */
/*
* Copyright 2011 Mozilla Foundation and contributors
* Licensed under the New BSD license. See LICENSE or:
* http://opensource.org/licenses/BSD-3-Clause
*/
if (typeof define !== 'function') {
var define = require('amdefine')(module, require);
}
define(function (require, exports, module) {
/**
* This is a helper function for getting values from parameter/options
* objects.
*
* @param args The object we are extracting values from
* @param name The name of the property we are getting.
* @param defaultValue An optional value to return if the property is missing
* from the object. If this is not specified and the property is missing, an
* error will be thrown.
*/
function getArg(aArgs, aName, aDefaultValue) {
if (aName in aArgs) {
return aArgs[aName];
} else if (arguments.length === 3) {
return aDefaultValue;
} else {
throw new Error('"' + aName + '" is a required argument.');
}
}
exports.getArg = getArg;
var urlRegexp = /([\w+\-.]+):\/\/((\w+:\w+)@)?([\w.]+)?(:(\d+))?(\S+)?/;
var dataUrlRegexp = /^data:.+\,.+/;
function urlParse(aUrl) {
var match = aUrl.match(urlRegexp);
if (!match) {
return null;
}
return {
scheme: match[1],
auth: match[3],
host: match[4],
port: match[6],
path: match[7]
};
}
exports.urlParse = urlParse;
function urlGenerate(aParsedUrl) {
var url = aParsedUrl.scheme + "://";
if (aParsedUrl.auth) {
url += aParsedUrl.auth + "@"
}
if (aParsedUrl.host) {
url += aParsedUrl.host;
}
if (aParsedUrl.port) {
url += ":" + aParsedUrl.port
}
if (aParsedUrl.path) {
url += aParsedUrl.path;
}
return url;
}
exports.urlGenerate = urlGenerate;
function join(aRoot, aPath) {
var url;
if (aPath.match(urlRegexp) || aPath.match(dataUrlRegexp)) {
return aPath;
}
if (aPath.charAt(0) === '/' && (url = urlParse(aRoot))) {
url.path = aPath;
return urlGenerate(url);
}
return aRoot.replace(/\/$/, '') + '/' + aPath;
}
exports.join = join;
/**
* Because behavior goes wacky when you set `__proto__` on objects, we
* have to prefix all the strings in our set with an arbitrary character.
*
* See https://github.com/mozilla/source-map/pull/31 and
* https://github.com/mozilla/source-map/issues/30
*
* @param String aStr
*/
function toSetString(aStr) {
return '$' + aStr;
}
exports.toSetString = toSetString;
function fromSetString(aStr) {
return aStr.substr(1);
}
exports.fromSetString = fromSetString;
function relative(aRoot, aPath) {
aRoot = aRoot.replace(/\/$/, '');
var url = urlParse(aRoot);
if (aPath.charAt(0) == "/" && url && url.path == "/") {
return aPath.slice(1);
}
return aPath.indexOf(aRoot + '/') === 0
? aPath.substr(aRoot.length + 1)
: aPath;
}
exports.relative = relative;
function strcmp(aStr1, aStr2) {
var s1 = aStr1 || "";
var s2 = aStr2 || "";
return (s1 > s2) - (s1 < s2);
}
/**
* Comparator between two mappings where the original positions are compared.
*
* Optionally pass in `true` as `onlyCompareGenerated` to consider two
* mappings with the same original source/line/column, but different generated
* line and column the same. Useful when searching for a mapping with a
* stubbed out mapping.
*/
function compareByOriginalPositions(mappingA, mappingB, onlyCompareOriginal) {
var cmp;
cmp = strcmp(mappingA.source, mappingB.source);
if (cmp) {
return cmp;
}
cmp = mappingA.originalLine - mappingB.originalLine;
if (cmp) {
return cmp;
}
cmp = mappingA.originalColumn - mappingB.originalColumn;
if (cmp || onlyCompareOriginal) {
return cmp;
}
cmp = strcmp(mappingA.name, mappingB.name);
if (cmp) {
return cmp;
}
cmp = mappingA.generatedLine - mappingB.generatedLine;
if (cmp) {
return cmp;
}
return mappingA.generatedColumn - mappingB.generatedColumn;
};
exports.compareByOriginalPositions = compareByOriginalPositions;
/**
* Comparator between two mappings where the generated positions are
* compared.
*
* Optionally pass in `true` as `onlyCompareGenerated` to consider two
* mappings with the same generated line and column, but different
* source/name/original line and column the same. Useful when searching for a
* mapping with a stubbed out mapping.
*/
function compareByGeneratedPositions(mappingA, mappingB, onlyCompareGenerated) {
var cmp;
cmp = mappingA.generatedLine - mappingB.generatedLine;
if (cmp) {
return cmp;
}
cmp = mappingA.generatedColumn - mappingB.generatedColumn;
if (cmp || onlyCompareGenerated) {
return cmp;
}
cmp = strcmp(mappingA.source, mappingB.source);
if (cmp) {
return cmp;
}
cmp = mappingA.originalLine - mappingB.originalLine;
if (cmp) {
return cmp;
}
cmp = mappingA.originalColumn - mappingB.originalColumn;
if (cmp) {
return cmp;
}
return strcmp(mappingA.name, mappingB.name);
};
exports.compareByGeneratedPositions = compareByGeneratedPositions;
});

View File

@@ -0,0 +1,58 @@
amdefine is released under two licenses: new BSD, and MIT. You may pick the
license that best suits your development needs. The text of both licenses are
provided below.
The "New" BSD License:
----------------------
Copyright (c) 2011, The Dojo Foundation
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of the Dojo Foundation nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
MIT License
-----------
Copyright (c) 2011, The Dojo Foundation
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -0,0 +1,171 @@
# amdefine
A module that can be used to implement AMD's define() in Node. This allows you
to code to the AMD API and have the module work in node programs without
requiring those other programs to use AMD.
## Usage
**1)** Update your package.json to indicate amdefine as a dependency:
```javascript
"dependencies": {
"amdefine": ">=0.1.0"
}
```
Then run `npm install` to get amdefine into your project.
**2)** At the top of each module that uses define(), place this code:
```javascript
if (typeof define !== 'function') { var define = require('amdefine')(module) }
```
**Only use these snippets** when loading amdefine. If you preserve the basic structure,
with the braces, it will be stripped out when using the [RequireJS optimizer](#optimizer).
You can add spaces, line breaks and even require amdefine with a local path, but
keep the rest of the structure to get the stripping behavior.
As you may know, because `if` statements in JavaScript don't have their own scope, the var
declaration in the above snippet is made whether the `if` expression is truthy or not. If
RequireJS is loaded then the declaration is superfluous because `define` is already already
declared in the same scope in RequireJS. Fortunately JavaScript handles multiple `var`
declarations of the same variable in the same scope gracefully.
If you want to deliver amdefine.js with your code rather than specifying it as a dependency
with npm, then just download the latest release and refer to it using a relative path:
[Latest Version](https://github.com/jrburke/amdefine/raw/latest/amdefine.js)
### amdefine/intercept
Consider this very experimental.
Instead of pasting the piece of text for the amdefine setup of a `define`
variable in each module you create or consume, you can use `amdefine/intercept`
instead. It will automatically insert the above snippet in each .js file loaded
by Node.
**Warning**: you should only use this if you are creating an application that
is consuming AMD style defined()'d modules that are distributed via npm and want
to run that code in Node.
For library code where you are not sure if it will be used by others in Node or
in the browser, then explicitly depending on amdefine and placing the code
snippet above is suggested path, instead of using `amdefine/intercept`. The
intercept module affects all .js files loaded in the Node app, and it is
inconsiderate to modify global state like that unless you are also controlling
the top level app.
#### Why distribute AMD-style nodes via npm?
npm has a lot of weaknesses for front-end use (installed layout is not great,
should have better support for the `baseUrl + moduleID + '.js' style of loading,
single file JS installs), but some people want a JS package manager and are
willing to live with those constraints. If that is you, but still want to author
in AMD style modules to get dynamic require([]), better direct source usage and
powerful loader plugin support in the browser, then this tool can help.
#### amdefine/intercept usage
Just require it in your top level app module (for example index.js, server.js):
```javascript
require('amdefine/intercept');
```
The module does not return a value, so no need to assign the result to a local
variable.
Then just require() code as you normally would with Node's require(). Any .js
loaded after the intercept require will have the amdefine check injected in
the .js source as it is loaded. It does not modify the source on disk, just
prepends some content to the text of the module as it is loaded by Node.
#### How amdefine/intercept works
It overrides the `Module._extensions['.js']` in Node to automatically prepend
the amdefine snippet above. So, it will affect any .js file loaded by your
app.
## define() usage
It is best if you use the anonymous forms of define() in your module:
```javascript
define(function (require) {
var dependency = require('dependency');
});
```
or
```javascript
define(['dependency'], function (dependency) {
});
```
## RequireJS optimizer integration. <a name="optimizer"></name>
Version 1.0.3 of the [RequireJS optimizer](http://requirejs.org/docs/optimization.html)
will have support for stripping the `if (typeof define !== 'function')` check
mentioned above, so you can include this snippet for code that runs in the
browser, but avoid taking the cost of the if() statement once the code is
optimized for deployment.
## Node 0.4 Support
If you want to support Node 0.4, then add `require` as the second parameter to amdefine:
```javascript
//Only if you want Node 0.4. If using 0.5 or later, use the above snippet.
if (typeof define !== 'function') { var define = require('amdefine')(module, require) }
```
## Limitations
### Synchronous vs Asynchronous
amdefine creates a define() function that is callable by your code. It will
execute and trace dependencies and call the factory function *synchronously*,
to keep the behavior in line with Node's synchronous dependency tracing.
The exception: calling AMD's callback-style require() from inside a factory
function. The require callback is called on process.nextTick():
```javascript
define(function (require) {
require(['a'], function(a) {
//'a' is loaded synchronously, but
//this callback is called on process.nextTick().
});
});
```
### Loader Plugins
Loader plugins are supported as long as they call their load() callbacks
synchronously. So ones that do network requests will not work. However plugins
like [text](http://requirejs.org/docs/api.html#text) can load text files locally.
The plugin API's `load.fromText()` is **not supported** in amdefine, so this means
transpiler plugins like the [CoffeeScript loader plugin](https://github.com/jrburke/require-cs)
will not work. This may be fixable, but it is a bit complex, and I do not have
enough node-fu to figure it out yet. See the source for amdefine.js if you want
to get an idea of the issues involved.
## Tests
To run the tests, cd to **tests** and run:
```
node all.js
node all-intercept.js
```
## License
New BSD and MIT. Check the LICENSE file for all the details.

View File

@@ -0,0 +1,299 @@
/** vim: et:ts=4:sw=4:sts=4
* @license amdefine 0.1.0 Copyright (c) 2011, The Dojo Foundation All Rights Reserved.
* Available via the MIT or new BSD license.
* see: http://github.com/jrburke/amdefine for details
*/
/*jslint node: true */
/*global module, process */
'use strict';
/**
* Creates a define for node.
* @param {Object} module the "module" object that is defined by Node for the
* current module.
* @param {Function} [requireFn]. Node's require function for the current module.
* It only needs to be passed in Node versions before 0.5, when module.require
* did not exist.
* @returns {Function} a define function that is usable for the current node
* module.
*/
function amdefine(module, requireFn) {
'use strict';
var defineCache = {},
loaderCache = {},
alreadyCalled = false,
path = require('path'),
makeRequire, stringRequire;
/**
* Trims the . and .. from an array of path segments.
* It will keep a leading path segment if a .. will become
* the first path segment, to help with module name lookups,
* which act like paths, but can be remapped. But the end result,
* all paths that use this function should look normalized.
* NOTE: this method MODIFIES the input array.
* @param {Array} ary the array of path segments.
*/
function trimDots(ary) {
var i, part;
for (i = 0; ary[i]; i+= 1) {
part = ary[i];
if (part === '.') {
ary.splice(i, 1);
i -= 1;
} else if (part === '..') {
if (i === 1 && (ary[2] === '..' || ary[0] === '..')) {
//End of the line. Keep at least one non-dot
//path segment at the front so it can be mapped
//correctly to disk. Otherwise, there is likely
//no path mapping for a path starting with '..'.
//This can still fail, but catches the most reasonable
//uses of ..
break;
} else if (i > 0) {
ary.splice(i - 1, 2);
i -= 2;
}
}
}
}
function normalize(name, baseName) {
var baseParts;
//Adjust any relative paths.
if (name && name.charAt(0) === '.') {
//If have a base name, try to normalize against it,
//otherwise, assume it is a top-level require that will
//be relative to baseUrl in the end.
if (baseName) {
baseParts = baseName.split('/');
baseParts = baseParts.slice(0, baseParts.length - 1);
baseParts = baseParts.concat(name.split('/'));
trimDots(baseParts);
name = baseParts.join('/');
}
}
return name;
}
/**
* Create the normalize() function passed to a loader plugin's
* normalize method.
*/
function makeNormalize(relName) {
return function (name) {
return normalize(name, relName);
};
}
function makeLoad(id) {
function load(value) {
loaderCache[id] = value;
}
load.fromText = function (id, text) {
//This one is difficult because the text can/probably uses
//define, and any relative paths and requires should be relative
//to that id was it would be found on disk. But this would require
//bootstrapping a module/require fairly deeply from node core.
//Not sure how best to go about that yet.
throw new Error('amdefine does not implement load.fromText');
};
return load;
}
makeRequire = function (systemRequire, exports, module, relId) {
function amdRequire(deps, callback) {
if (typeof deps === 'string') {
//Synchronous, single module require('')
return stringRequire(systemRequire, exports, module, deps, relId);
} else {
//Array of dependencies with a callback.
//Convert the dependencies to modules.
deps = deps.map(function (depName) {
return stringRequire(systemRequire, exports, module, depName, relId);
});
//Wait for next tick to call back the require call.
process.nextTick(function () {
callback.apply(null, deps);
});
}
}
amdRequire.toUrl = function (filePath) {
if (filePath.indexOf('.') === 0) {
return normalize(filePath, path.dirname(module.filename));
} else {
return filePath;
}
};
return amdRequire;
};
//Favor explicit value, passed in if the module wants to support Node 0.4.
requireFn = requireFn || function req() {
return module.require.apply(module, arguments);
};
function runFactory(id, deps, factory) {
var r, e, m, result;
if (id) {
e = loaderCache[id] = {};
m = {
id: id,
uri: __filename,
exports: e
};
r = makeRequire(requireFn, e, m, id);
} else {
//Only support one define call per file
if (alreadyCalled) {
throw new Error('amdefine with no module ID cannot be called more than once per file.');
}
alreadyCalled = true;
//Use the real variables from node
//Use module.exports for exports, since
//the exports in here is amdefine exports.
e = module.exports;
m = module;
r = makeRequire(requireFn, e, m, module.id);
}
//If there are dependencies, they are strings, so need
//to convert them to dependency values.
if (deps) {
deps = deps.map(function (depName) {
return r(depName);
});
}
//Call the factory with the right dependencies.
if (typeof factory === 'function') {
result = factory.apply(m.exports, deps);
} else {
result = factory;
}
if (result !== undefined) {
m.exports = result;
if (id) {
loaderCache[id] = m.exports;
}
}
}
stringRequire = function (systemRequire, exports, module, id, relId) {
//Split the ID by a ! so that
var index = id.indexOf('!'),
originalId = id,
prefix, plugin;
if (index === -1) {
id = normalize(id, relId);
//Straight module lookup. If it is one of the special dependencies,
//deal with it, otherwise, delegate to node.
if (id === 'require') {
return makeRequire(systemRequire, exports, module, relId);
} else if (id === 'exports') {
return exports;
} else if (id === 'module') {
return module;
} else if (loaderCache.hasOwnProperty(id)) {
return loaderCache[id];
} else if (defineCache[id]) {
runFactory.apply(null, defineCache[id]);
return loaderCache[id];
} else {
if(systemRequire) {
return systemRequire(originalId);
} else {
throw new Error('No module with ID: ' + id);
}
}
} else {
//There is a plugin in play.
prefix = id.substring(0, index);
id = id.substring(index + 1, id.length);
plugin = stringRequire(systemRequire, exports, module, prefix, relId);
if (plugin.normalize) {
id = plugin.normalize(id, makeNormalize(relId));
} else {
//Normalize the ID normally.
id = normalize(id, relId);
}
if (loaderCache[id]) {
return loaderCache[id];
} else {
plugin.load(id, makeRequire(systemRequire, exports, module, relId), makeLoad(id), {});
return loaderCache[id];
}
}
};
//Create a define function specific to the module asking for amdefine.
function define(id, deps, factory) {
if (Array.isArray(id)) {
factory = deps;
deps = id;
id = undefined;
} else if (typeof id !== 'string') {
factory = id;
id = deps = undefined;
}
if (deps && !Array.isArray(deps)) {
factory = deps;
deps = undefined;
}
if (!deps) {
deps = ['require', 'exports', 'module'];
}
//Set up properties for this module. If an ID, then use
//internal cache. If no ID, then use the external variables
//for this node module.
if (id) {
//Put the module in deep freeze until there is a
//require call for it.
defineCache[id] = [id, deps, factory];
} else {
runFactory(id, deps, factory);
}
}
//define.require, which has access to all the values in the
//cache. Useful for AMD modules that all have IDs in the file,
//but need to finally export a value to node based on one of those
//IDs.
define.require = function (id) {
if (loaderCache[id]) {
return loaderCache[id];
}
if (defineCache[id]) {
runFactory.apply(null, defineCache[id]);
return loaderCache[id];
}
};
define.amd = {};
return define;
}
module.exports = amdefine;

View File

@@ -0,0 +1,36 @@
/*jshint node: true */
var inserted,
Module = require('module'),
fs = require('fs'),
existingExtFn = Module._extensions['.js'],
amdefineRegExp = /amdefine\.js/;
inserted = "if (typeof define !== 'function') {var define = require('amdefine')(module)}";
//From the node/lib/module.js source:
function stripBOM(content) {
// Remove byte order marker. This catches EF BB BF (the UTF-8 BOM)
// because the buffer-to-string conversion in `fs.readFileSync()`
// translates it to FEFF, the UTF-16 BOM.
if (content.charCodeAt(0) === 0xFEFF) {
content = content.slice(1);
}
return content;
}
//Also adapted from the node/lib/module.js source:
function intercept(module, filename) {
var content = stripBOM(fs.readFileSync(filename, 'utf8'));
if (!amdefineRegExp.test(module.id)) {
content = inserted + content;
}
module._compile(content, filename);
}
intercept._id = 'amdefine/intercept';
if (!existingExtFn._id || existingExtFn._id !== intercept._id) {
Module._extensions['.js'] = intercept;
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,71 @@
#!/usr/bin/env node
/* -*- Mode: js; js-indent-level: 2; -*- */
/*
* Copyright 2011 Mozilla Foundation and contributors
* Licensed under the New BSD license. See LICENSE or:
* http://opensource.org/licenses/BSD-3-Clause
*/
var assert = require('assert');
var fs = require('fs');
var path = require('path');
var util = require('./source-map/util');
function run(tests) {
var failures = [];
var total = 0;
var passed = 0;
for (var i = 0; i < tests.length; i++) {
for (var k in tests[i].testCase) {
if (/^test/.test(k)) {
total++;
try {
tests[i].testCase[k](assert, util);
passed++;
}
catch (e) {
console.log('FAILED ' + tests[i].name + ': ' + k + '!');
console.log(e.stack);
}
}
}
}
console.log("");
console.log(passed + ' / ' + total + ' tests passed.');
console.log("");
failures.forEach(function (f) {
});
return failures.length;
}
var code;
process.stdout.on('close', function () {
process.exit(code);
});
function isTestFile(f) {
var testToRun = process.argv[2];
return testToRun
? path.basename(testToRun) === f
: /^test\-.*?\.js/.test(f);
}
function toModule(f) {
return './source-map/' + f.replace(/\.js$/, '');
}
var requires = fs.readdirSync(path.join(__dirname, 'source-map'))
.filter(isTestFile)
.map(toModule);
code = run(requires.map(require).map(function (mod, i) {
return {
name: requires[i],
testCase: mod
};
}));
process.exit(code);

View File

@@ -0,0 +1,26 @@
/* -*- Mode: js; js-indent-level: 2; -*- */
/*
* Copyright 2012 Mozilla Foundation and contributors
* Licensed under the New BSD license. See LICENSE or:
* http://opensource.org/licenses/BSD-3-Clause
*/
if (typeof define !== 'function') {
var define = require('amdefine')(module, require);
}
define(function (require, exports, module) {
var sourceMap;
try {
sourceMap = require('../../lib/source-map');
} catch (e) {
sourceMap = {};
Components.utils.import('resource:///modules/devtools/SourceMap.jsm', sourceMap);
}
exports['test that the api is properly exposed in the top level'] = function (assert, util) {
assert.equal(typeof sourceMap.SourceMapGenerator, "function");
assert.equal(typeof sourceMap.SourceMapConsumer, "function");
assert.equal(typeof sourceMap.SourceNode, "function");
};
});

View File

@@ -0,0 +1,104 @@
/* -*- Mode: js; js-indent-level: 2; -*- */
/*
* Copyright 2011 Mozilla Foundation and contributors
* Licensed under the New BSD license. See LICENSE or:
* http://opensource.org/licenses/BSD-3-Clause
*/
if (typeof define !== 'function') {
var define = require('amdefine')(module, require);
}
define(function (require, exports, module) {
var ArraySet = require('../../lib/source-map/array-set').ArraySet;
function makeTestSet() {
var set = new ArraySet();
for (var i = 0; i < 100; i++) {
set.add(String(i));
}
return set;
}
exports['test .has() membership'] = function (assert, util) {
var set = makeTestSet();
for (var i = 0; i < 100; i++) {
assert.ok(set.has(String(i)));
}
};
exports['test .indexOf() elements'] = function (assert, util) {
var set = makeTestSet();
for (var i = 0; i < 100; i++) {
assert.strictEqual(set.indexOf(String(i)), i);
}
};
exports['test .at() indexing'] = function (assert, util) {
var set = makeTestSet();
for (var i = 0; i < 100; i++) {
assert.strictEqual(set.at(i), String(i));
}
};
exports['test creating from an array'] = function (assert, util) {
var set = ArraySet.fromArray(['foo', 'bar', 'baz', 'quux', 'hasOwnProperty']);
assert.ok(set.has('foo'));
assert.ok(set.has('bar'));
assert.ok(set.has('baz'));
assert.ok(set.has('quux'));
assert.ok(set.has('hasOwnProperty'));
assert.strictEqual(set.indexOf('foo'), 0);
assert.strictEqual(set.indexOf('bar'), 1);
assert.strictEqual(set.indexOf('baz'), 2);
assert.strictEqual(set.indexOf('quux'), 3);
assert.strictEqual(set.at(0), 'foo');
assert.strictEqual(set.at(1), 'bar');
assert.strictEqual(set.at(2), 'baz');
assert.strictEqual(set.at(3), 'quux');
};
exports['test that you can add __proto__; see github issue #30'] = function (assert, util) {
var set = new ArraySet();
set.add('__proto__');
assert.ok(set.has('__proto__'));
assert.strictEqual(set.at(0), '__proto__');
assert.strictEqual(set.indexOf('__proto__'), 0);
};
exports['test .fromArray() with duplicates'] = function (assert, util) {
var set = ArraySet.fromArray(['foo', 'foo']);
assert.ok(set.has('foo'));
assert.strictEqual(set.at(0), 'foo');
assert.strictEqual(set.indexOf('foo'), 0);
assert.strictEqual(set.toArray().length, 1);
set = ArraySet.fromArray(['foo', 'foo'], true);
assert.ok(set.has('foo'));
assert.strictEqual(set.at(0), 'foo');
assert.strictEqual(set.at(1), 'foo');
assert.strictEqual(set.indexOf('foo'), 0);
assert.strictEqual(set.toArray().length, 2);
};
exports['test .add() with duplicates'] = function (assert, util) {
var set = new ArraySet();
set.add('foo');
set.add('foo');
assert.ok(set.has('foo'));
assert.strictEqual(set.at(0), 'foo');
assert.strictEqual(set.indexOf('foo'), 0);
assert.strictEqual(set.toArray().length, 1);
set.add('foo', true);
assert.ok(set.has('foo'));
assert.strictEqual(set.at(0), 'foo');
assert.strictEqual(set.at(1), 'foo');
assert.strictEqual(set.indexOf('foo'), 0);
assert.strictEqual(set.toArray().length, 2);
};
});

View File

@@ -0,0 +1,24 @@
/* -*- Mode: js; js-indent-level: 2; -*- */
/*
* Copyright 2011 Mozilla Foundation and contributors
* Licensed under the New BSD license. See LICENSE or:
* http://opensource.org/licenses/BSD-3-Clause
*/
if (typeof define !== 'function') {
var define = require('amdefine')(module, require);
}
define(function (require, exports, module) {
var base64VLQ = require('../../lib/source-map/base64-vlq');
exports['test normal encoding and decoding'] = function (assert, util) {
var result;
for (var i = -255; i < 256; i++) {
result = base64VLQ.decode(base64VLQ.encode(i));
assert.ok(result);
assert.equal(result.value, i);
assert.equal(result.rest, "");
}
};
});

View File

@@ -0,0 +1,35 @@
/* -*- Mode: js; js-indent-level: 2; -*- */
/*
* Copyright 2011 Mozilla Foundation and contributors
* Licensed under the New BSD license. See LICENSE or:
* http://opensource.org/licenses/BSD-3-Clause
*/
if (typeof define !== 'function') {
var define = require('amdefine')(module, require);
}
define(function (require, exports, module) {
var base64 = require('../../lib/source-map/base64');
exports['test out of range encoding'] = function (assert, util) {
assert.throws(function () {
base64.encode(-1);
});
assert.throws(function () {
base64.encode(64);
});
};
exports['test out of range decoding'] = function (assert, util) {
assert.throws(function () {
base64.decode('=');
});
};
exports['test normal encoding and decoding'] = function (assert, util) {
for (var i = 0; i < 64; i++) {
assert.equal(base64.decode(base64.encode(i)), i);
}
};
});

View File

@@ -0,0 +1,54 @@
/* -*- Mode: js; js-indent-level: 2; -*- */
/*
* Copyright 2011 Mozilla Foundation and contributors
* Licensed under the New BSD license. See LICENSE or:
* http://opensource.org/licenses/BSD-3-Clause
*/
if (typeof define !== 'function') {
var define = require('amdefine')(module, require);
}
define(function (require, exports, module) {
var binarySearch = require('../../lib/source-map/binary-search');
function numberCompare(a, b) {
return a - b;
}
exports['test too high'] = function (assert, util) {
var needle = 30;
var haystack = [2,4,6,8,10,12,14,16,18,20];
assert.doesNotThrow(function () {
binarySearch.search(needle, haystack, numberCompare);
});
assert.equal(binarySearch.search(needle, haystack, numberCompare), 20);
};
exports['test too low'] = function (assert, util) {
var needle = 1;
var haystack = [2,4,6,8,10,12,14,16,18,20];
assert.doesNotThrow(function () {
binarySearch.search(needle, haystack, numberCompare);
});
assert.equal(binarySearch.search(needle, haystack, numberCompare), null);
};
exports['test exact search'] = function (assert, util) {
var needle = 4;
var haystack = [2,4,6,8,10,12,14,16,18,20];
assert.equal(binarySearch.search(needle, haystack, numberCompare), 4);
};
exports['test fuzzy search'] = function (assert, util) {
var needle = 19;
var haystack = [2,4,6,8,10,12,14,16,18,20];
assert.equal(binarySearch.search(needle, haystack, numberCompare), 18);
};
});

View File

@@ -0,0 +1,72 @@
/* -*- Mode: js; js-indent-level: 2; -*- */
/*
* Copyright 2011 Mozilla Foundation and contributors
* Licensed under the New BSD license. See LICENSE or:
* http://opensource.org/licenses/BSD-3-Clause
*/
if (typeof define !== 'function') {
var define = require('amdefine')(module, require);
}
define(function (require, exports, module) {
var SourceMapConsumer = require('../../lib/source-map/source-map-consumer').SourceMapConsumer;
var SourceMapGenerator = require('../../lib/source-map/source-map-generator').SourceMapGenerator;
exports['test eating our own dog food'] = function (assert, util) {
var smg = new SourceMapGenerator({
file: 'testing.js',
sourceRoot: '/wu/tang'
});
smg.addMapping({
source: 'gza.coffee',
original: { line: 1, column: 0 },
generated: { line: 2, column: 2 }
});
smg.addMapping({
source: 'gza.coffee',
original: { line: 2, column: 0 },
generated: { line: 3, column: 2 }
});
smg.addMapping({
source: 'gza.coffee',
original: { line: 3, column: 0 },
generated: { line: 4, column: 2 }
});
smg.addMapping({
source: 'gza.coffee',
original: { line: 4, column: 0 },
generated: { line: 5, column: 2 }
});
var smc = new SourceMapConsumer(smg.toString());
// Exact
util.assertMapping(2, 2, '/wu/tang/gza.coffee', 1, 0, null, smc, assert);
util.assertMapping(3, 2, '/wu/tang/gza.coffee', 2, 0, null, smc, assert);
util.assertMapping(4, 2, '/wu/tang/gza.coffee', 3, 0, null, smc, assert);
util.assertMapping(5, 2, '/wu/tang/gza.coffee', 4, 0, null, smc, assert);
// Fuzzy
// Original to generated
util.assertMapping(2, 0, null, null, null, null, smc, assert, true);
util.assertMapping(2, 9, '/wu/tang/gza.coffee', 1, 0, null, smc, assert, true);
util.assertMapping(3, 0, '/wu/tang/gza.coffee', 1, 0, null, smc, assert, true);
util.assertMapping(3, 9, '/wu/tang/gza.coffee', 2, 0, null, smc, assert, true);
util.assertMapping(4, 0, '/wu/tang/gza.coffee', 2, 0, null, smc, assert, true);
util.assertMapping(4, 9, '/wu/tang/gza.coffee', 3, 0, null, smc, assert, true);
util.assertMapping(5, 0, '/wu/tang/gza.coffee', 3, 0, null, smc, assert, true);
util.assertMapping(5, 9, '/wu/tang/gza.coffee', 4, 0, null, smc, assert, true);
// Generated to original
util.assertMapping(2, 2, '/wu/tang/gza.coffee', 1, 1, null, smc, assert, null, true);
util.assertMapping(3, 2, '/wu/tang/gza.coffee', 2, 3, null, smc, assert, null, true);
util.assertMapping(4, 2, '/wu/tang/gza.coffee', 3, 6, null, smc, assert, null, true);
util.assertMapping(5, 2, '/wu/tang/gza.coffee', 4, 9, null, smc, assert, null, true);
};
});

View File

@@ -0,0 +1,451 @@
/* -*- Mode: js; js-indent-level: 2; -*- */
/*
* Copyright 2011 Mozilla Foundation and contributors
* Licensed under the New BSD license. See LICENSE or:
* http://opensource.org/licenses/BSD-3-Clause
*/
if (typeof define !== 'function') {
var define = require('amdefine')(module, require);
}
define(function (require, exports, module) {
var SourceMapConsumer = require('../../lib/source-map/source-map-consumer').SourceMapConsumer;
var SourceMapGenerator = require('../../lib/source-map/source-map-generator').SourceMapGenerator;
exports['test that we can instantiate with a string or an objects'] = function (assert, util) {
assert.doesNotThrow(function () {
var map = new SourceMapConsumer(util.testMap);
});
assert.doesNotThrow(function () {
var map = new SourceMapConsumer(JSON.stringify(util.testMap));
});
};
exports['test that the `sources` field has the original sources'] = function (assert, util) {
var map = new SourceMapConsumer(util.testMap);
var sources = map.sources;
assert.equal(sources[0], '/the/root/one.js');
assert.equal(sources[1], '/the/root/two.js');
assert.equal(sources.length, 2);
};
exports['test that the source root is reflected in a mapping\'s source field'] = function (assert, util) {
var map = new SourceMapConsumer(util.testMap);
var mapping;
mapping = map.originalPositionFor({
line: 2,
column: 1
});
assert.equal(mapping.source, '/the/root/two.js');
mapping = map.originalPositionFor({
line: 1,
column: 1
});
assert.equal(mapping.source, '/the/root/one.js');
};
exports['test mapping tokens back exactly'] = function (assert, util) {
var map = new SourceMapConsumer(util.testMap);
util.assertMapping(1, 1, '/the/root/one.js', 1, 1, null, map, assert);
util.assertMapping(1, 5, '/the/root/one.js', 1, 5, null, map, assert);
util.assertMapping(1, 9, '/the/root/one.js', 1, 11, null, map, assert);
util.assertMapping(1, 18, '/the/root/one.js', 1, 21, 'bar', map, assert);
util.assertMapping(1, 21, '/the/root/one.js', 2, 3, null, map, assert);
util.assertMapping(1, 28, '/the/root/one.js', 2, 10, 'baz', map, assert);
util.assertMapping(1, 32, '/the/root/one.js', 2, 14, 'bar', map, assert);
util.assertMapping(2, 1, '/the/root/two.js', 1, 1, null, map, assert);
util.assertMapping(2, 5, '/the/root/two.js', 1, 5, null, map, assert);
util.assertMapping(2, 9, '/the/root/two.js', 1, 11, null, map, assert);
util.assertMapping(2, 18, '/the/root/two.js', 1, 21, 'n', map, assert);
util.assertMapping(2, 21, '/the/root/two.js', 2, 3, null, map, assert);
util.assertMapping(2, 28, '/the/root/two.js', 2, 10, 'n', map, assert);
};
exports['test mapping tokens fuzzy'] = function (assert, util) {
var map = new SourceMapConsumer(util.testMap);
// Finding original positions
util.assertMapping(1, 20, '/the/root/one.js', 1, 21, 'bar', map, assert, true);
util.assertMapping(1, 30, '/the/root/one.js', 2, 10, 'baz', map, assert, true);
util.assertMapping(2, 12, '/the/root/two.js', 1, 11, null, map, assert, true);
// Finding generated positions
util.assertMapping(1, 18, '/the/root/one.js', 1, 22, 'bar', map, assert, null, true);
util.assertMapping(1, 28, '/the/root/one.js', 2, 13, 'baz', map, assert, null, true);
util.assertMapping(2, 9, '/the/root/two.js', 1, 16, null, map, assert, null, true);
};
exports['test creating source map consumers with )]}\' prefix'] = function (assert, util) {
assert.doesNotThrow(function () {
var map = new SourceMapConsumer(")]}'" + JSON.stringify(util.testMap));
});
};
exports['test eachMapping'] = function (assert, util) {
var map = new SourceMapConsumer(util.testMap);
var previousLine = -Infinity;
var previousColumn = -Infinity;
map.eachMapping(function (mapping) {
assert.ok(mapping.generatedLine >= previousLine);
if (mapping.source) {
assert.equal(mapping.source.indexOf(util.testMap.sourceRoot), 0);
}
if (mapping.generatedLine === previousLine) {
assert.ok(mapping.generatedColumn >= previousColumn);
previousColumn = mapping.generatedColumn;
}
else {
previousLine = mapping.generatedLine;
previousColumn = -Infinity;
}
});
};
exports['test iterating over mappings in a different order'] = function (assert, util) {
var map = new SourceMapConsumer(util.testMap);
var previousLine = -Infinity;
var previousColumn = -Infinity;
var previousSource = "";
map.eachMapping(function (mapping) {
assert.ok(mapping.source >= previousSource);
if (mapping.source === previousSource) {
assert.ok(mapping.originalLine >= previousLine);
if (mapping.originalLine === previousLine) {
assert.ok(mapping.originalColumn >= previousColumn);
previousColumn = mapping.originalColumn;
}
else {
previousLine = mapping.originalLine;
previousColumn = -Infinity;
}
}
else {
previousSource = mapping.source;
previousLine = -Infinity;
previousColumn = -Infinity;
}
}, null, SourceMapConsumer.ORIGINAL_ORDER);
};
exports['test that we can set the context for `this` in eachMapping'] = function (assert, util) {
var map = new SourceMapConsumer(util.testMap);
var context = {};
map.eachMapping(function () {
assert.equal(this, context);
}, context);
};
exports['test that the `sourcesContent` field has the original sources'] = function (assert, util) {
var map = new SourceMapConsumer(util.testMapWithSourcesContent);
var sourcesContent = map.sourcesContent;
assert.equal(sourcesContent[0], ' ONE.foo = function (bar) {\n return baz(bar);\n };');
assert.equal(sourcesContent[1], ' TWO.inc = function (n) {\n return n + 1;\n };');
assert.equal(sourcesContent.length, 2);
};
exports['test that we can get the original sources for the sources'] = function (assert, util) {
var map = new SourceMapConsumer(util.testMapWithSourcesContent);
var sources = map.sources;
assert.equal(map.sourceContentFor(sources[0]), ' ONE.foo = function (bar) {\n return baz(bar);\n };');
assert.equal(map.sourceContentFor(sources[1]), ' TWO.inc = function (n) {\n return n + 1;\n };');
assert.equal(map.sourceContentFor("one.js"), ' ONE.foo = function (bar) {\n return baz(bar);\n };');
assert.equal(map.sourceContentFor("two.js"), ' TWO.inc = function (n) {\n return n + 1;\n };');
assert.throws(function () {
map.sourceContentFor("");
}, Error);
assert.throws(function () {
map.sourceContentFor("/the/root/three.js");
}, Error);
assert.throws(function () {
map.sourceContentFor("three.js");
}, Error);
};
exports['test sourceRoot + generatedPositionFor'] = function (assert, util) {
var map = new SourceMapGenerator({
sourceRoot: 'foo/bar',
file: 'baz.js'
});
map.addMapping({
original: { line: 1, column: 1 },
generated: { line: 2, column: 2 },
source: 'bang.coffee'
});
map.addMapping({
original: { line: 5, column: 5 },
generated: { line: 6, column: 6 },
source: 'bang.coffee'
});
map = new SourceMapConsumer(map.toString());
// Should handle without sourceRoot.
var pos = map.generatedPositionFor({
line: 1,
column: 1,
source: 'bang.coffee'
});
assert.equal(pos.line, 2);
assert.equal(pos.column, 2);
// Should handle with sourceRoot.
var pos = map.generatedPositionFor({
line: 1,
column: 1,
source: 'foo/bar/bang.coffee'
});
assert.equal(pos.line, 2);
assert.equal(pos.column, 2);
};
exports['test sourceRoot + originalPositionFor'] = function (assert, util) {
var map = new SourceMapGenerator({
sourceRoot: 'foo/bar',
file: 'baz.js'
});
map.addMapping({
original: { line: 1, column: 1 },
generated: { line: 2, column: 2 },
source: 'bang.coffee'
});
map = new SourceMapConsumer(map.toString());
var pos = map.originalPositionFor({
line: 2,
column: 2,
});
// Should always have the prepended source root
assert.equal(pos.source, 'foo/bar/bang.coffee');
assert.equal(pos.line, 1);
assert.equal(pos.column, 1);
};
exports['test github issue #56'] = function (assert, util) {
var map = new SourceMapGenerator({
sourceRoot: 'http://',
file: 'www.example.com/foo.js'
});
map.addMapping({
original: { line: 1, column: 1 },
generated: { line: 2, column: 2 },
source: 'www.example.com/original.js'
});
map = new SourceMapConsumer(map.toString());
var sources = map.sources;
assert.equal(sources.length, 1);
assert.equal(sources[0], 'http://www.example.com/original.js');
};
exports['test github issue #43'] = function (assert, util) {
var map = new SourceMapGenerator({
sourceRoot: 'http://example.com',
file: 'foo.js'
});
map.addMapping({
original: { line: 1, column: 1 },
generated: { line: 2, column: 2 },
source: 'http://cdn.example.com/original.js'
});
map = new SourceMapConsumer(map.toString());
var sources = map.sources;
assert.equal(sources.length, 1,
'Should only be one source.');
assert.equal(sources[0], 'http://cdn.example.com/original.js',
'Should not be joined with the sourceRoot.');
};
exports['test absolute path, but same host sources'] = function (assert, util) {
var map = new SourceMapGenerator({
sourceRoot: 'http://example.com/foo/bar',
file: 'foo.js'
});
map.addMapping({
original: { line: 1, column: 1 },
generated: { line: 2, column: 2 },
source: '/original.js'
});
map = new SourceMapConsumer(map.toString());
var sources = map.sources;
assert.equal(sources.length, 1,
'Should only be one source.');
assert.equal(sources[0], 'http://example.com/original.js',
'Source should be relative the host of the source root.');
};
exports['test github issue #64'] = function (assert, util) {
var map = new SourceMapConsumer({
"version": 3,
"file": "foo.js",
"sourceRoot": "http://example.com/",
"sources": ["/a"],
"names": [],
"mappings": "AACA",
"sourcesContent": ["foo"]
});
assert.equal(map.sourceContentFor("a"), "foo");
assert.equal(map.sourceContentFor("/a"), "foo");
};
exports['test bug 885597'] = function (assert, util) {
var map = new SourceMapConsumer({
"version": 3,
"file": "foo.js",
"sourceRoot": "file:///Users/AlGore/Invented/The/Internet/",
"sources": ["/a"],
"names": [],
"mappings": "AACA",
"sourcesContent": ["foo"]
});
var s = map.sources[0];
assert.equal(map.sourceContentFor(s), "foo");
};
exports['test github issue #72, duplicate sources'] = function (assert, util) {
var map = new SourceMapConsumer({
"version": 3,
"file": "foo.js",
"sources": ["source1.js", "source1.js", "source3.js"],
"names": [],
"mappings": ";EAAC;;IAEE;;MEEE",
"sourceRoot": "http://example.com"
});
var pos = map.originalPositionFor({
line: 2,
column: 2
});
assert.equal(pos.source, 'http://example.com/source1.js');
assert.equal(pos.line, 1);
assert.equal(pos.column, 1);
var pos = map.originalPositionFor({
line: 4,
column: 4
});
assert.equal(pos.source, 'http://example.com/source1.js');
assert.equal(pos.line, 3);
assert.equal(pos.column, 3);
var pos = map.originalPositionFor({
line: 6,
column: 6
});
assert.equal(pos.source, 'http://example.com/source3.js');
assert.equal(pos.line, 5);
assert.equal(pos.column, 5);
};
exports['test github issue #72, duplicate names'] = function (assert, util) {
var map = new SourceMapConsumer({
"version": 3,
"file": "foo.js",
"sources": ["source.js"],
"names": ["name1", "name1", "name3"],
"mappings": ";EAACA;;IAEEA;;MAEEE",
"sourceRoot": "http://example.com"
});
var pos = map.originalPositionFor({
line: 2,
column: 2
});
assert.equal(pos.name, 'name1');
assert.equal(pos.line, 1);
assert.equal(pos.column, 1);
var pos = map.originalPositionFor({
line: 4,
column: 4
});
assert.equal(pos.name, 'name1');
assert.equal(pos.line, 3);
assert.equal(pos.column, 3);
var pos = map.originalPositionFor({
line: 6,
column: 6
});
assert.equal(pos.name, 'name3');
assert.equal(pos.line, 5);
assert.equal(pos.column, 5);
};
exports['test SourceMapConsumer.fromSourceMap'] = function (assert, util) {
var smg = new SourceMapGenerator({
sourceRoot: 'http://example.com/',
file: 'foo.js'
});
smg.addMapping({
original: { line: 1, column: 1 },
generated: { line: 2, column: 2 },
source: 'bar.js'
});
smg.addMapping({
original: { line: 2, column: 2 },
generated: { line: 4, column: 4 },
source: 'baz.js',
name: 'dirtMcGirt'
});
smg.setSourceContent('baz.js', 'baz.js content');
var smc = SourceMapConsumer.fromSourceMap(smg);
assert.equal(smc.file, 'foo.js');
assert.equal(smc.sourceRoot, 'http://example.com/');
assert.equal(smc.sources.length, 2);
assert.equal(smc.sources[0], 'http://example.com/bar.js');
assert.equal(smc.sources[1], 'http://example.com/baz.js');
assert.equal(smc.sourceContentFor('baz.js'), 'baz.js content');
var pos = smc.originalPositionFor({
line: 2,
column: 2
});
assert.equal(pos.line, 1);
assert.equal(pos.column, 1);
assert.equal(pos.source, 'http://example.com/bar.js');
assert.equal(pos.name, null);
pos = smc.generatedPositionFor({
line: 1,
column: 1,
source: 'http://example.com/bar.js'
});
assert.equal(pos.line, 2);
assert.equal(pos.column, 2);
pos = smc.originalPositionFor({
line: 4,
column: 4
});
assert.equal(pos.line, 2);
assert.equal(pos.column, 2);
assert.equal(pos.source, 'http://example.com/baz.js');
assert.equal(pos.name, 'dirtMcGirt');
pos = smc.generatedPositionFor({
line: 2,
column: 2,
source: 'http://example.com/baz.js'
});
assert.equal(pos.line, 4);
assert.equal(pos.column, 4);
};
});

View File

@@ -0,0 +1,417 @@
/* -*- Mode: js; js-indent-level: 2; -*- */
/*
* Copyright 2011 Mozilla Foundation and contributors
* Licensed under the New BSD license. See LICENSE or:
* http://opensource.org/licenses/BSD-3-Clause
*/
if (typeof define !== 'function') {
var define = require('amdefine')(module, require);
}
define(function (require, exports, module) {
var SourceMapGenerator = require('../../lib/source-map/source-map-generator').SourceMapGenerator;
var SourceMapConsumer = require('../../lib/source-map/source-map-consumer').SourceMapConsumer;
var SourceNode = require('../../lib/source-map/source-node').SourceNode;
var util = require('./util');
exports['test some simple stuff'] = function (assert, util) {
var map = new SourceMapGenerator({
file: 'foo.js',
sourceRoot: '.'
});
assert.ok(true);
};
exports['test JSON serialization'] = function (assert, util) {
var map = new SourceMapGenerator({
file: 'foo.js',
sourceRoot: '.'
});
assert.equal(map.toString(), JSON.stringify(map));
};
exports['test adding mappings (case 1)'] = function (assert, util) {
var map = new SourceMapGenerator({
file: 'generated-foo.js',
sourceRoot: '.'
});
assert.doesNotThrow(function () {
map.addMapping({
generated: { line: 1, column: 1 }
});
});
};
exports['test adding mappings (case 2)'] = function (assert, util) {
var map = new SourceMapGenerator({
file: 'generated-foo.js',
sourceRoot: '.'
});
assert.doesNotThrow(function () {
map.addMapping({
generated: { line: 1, column: 1 },
source: 'bar.js',
original: { line: 1, column: 1 }
});
});
};
exports['test adding mappings (case 3)'] = function (assert, util) {
var map = new SourceMapGenerator({
file: 'generated-foo.js',
sourceRoot: '.'
});
assert.doesNotThrow(function () {
map.addMapping({
generated: { line: 1, column: 1 },
source: 'bar.js',
original: { line: 1, column: 1 },
name: 'someToken'
});
});
};
exports['test adding mappings (invalid)'] = function (assert, util) {
var map = new SourceMapGenerator({
file: 'generated-foo.js',
sourceRoot: '.'
});
// Not enough info.
assert.throws(function () {
map.addMapping({});
});
// Original file position, but no source.
assert.throws(function () {
map.addMapping({
generated: { line: 1, column: 1 },
original: { line: 1, column: 1 }
});
});
};
exports['test that the correct mappings are being generated'] = function (assert, util) {
var map = new SourceMapGenerator({
file: 'min.js',
sourceRoot: '/the/root'
});
map.addMapping({
generated: { line: 1, column: 1 },
original: { line: 1, column: 1 },
source: 'one.js'
});
map.addMapping({
generated: { line: 1, column: 5 },
original: { line: 1, column: 5 },
source: 'one.js'
});
map.addMapping({
generated: { line: 1, column: 9 },
original: { line: 1, column: 11 },
source: 'one.js'
});
map.addMapping({
generated: { line: 1, column: 18 },
original: { line: 1, column: 21 },
source: 'one.js',
name: 'bar'
});
map.addMapping({
generated: { line: 1, column: 21 },
original: { line: 2, column: 3 },
source: 'one.js'
});
map.addMapping({
generated: { line: 1, column: 28 },
original: { line: 2, column: 10 },
source: 'one.js',
name: 'baz'
});
map.addMapping({
generated: { line: 1, column: 32 },
original: { line: 2, column: 14 },
source: 'one.js',
name: 'bar'
});
map.addMapping({
generated: { line: 2, column: 1 },
original: { line: 1, column: 1 },
source: 'two.js'
});
map.addMapping({
generated: { line: 2, column: 5 },
original: { line: 1, column: 5 },
source: 'two.js'
});
map.addMapping({
generated: { line: 2, column: 9 },
original: { line: 1, column: 11 },
source: 'two.js'
});
map.addMapping({
generated: { line: 2, column: 18 },
original: { line: 1, column: 21 },
source: 'two.js',
name: 'n'
});
map.addMapping({
generated: { line: 2, column: 21 },
original: { line: 2, column: 3 },
source: 'two.js'
});
map.addMapping({
generated: { line: 2, column: 28 },
original: { line: 2, column: 10 },
source: 'two.js',
name: 'n'
});
map = JSON.parse(map.toString());
util.assertEqualMaps(assert, map, util.testMap);
};
exports['test that source content can be set'] = function (assert, util) {
var map = new SourceMapGenerator({
file: 'min.js',
sourceRoot: '/the/root'
});
map.addMapping({
generated: { line: 1, column: 1 },
original: { line: 1, column: 1 },
source: 'one.js'
});
map.addMapping({
generated: { line: 2, column: 1 },
original: { line: 1, column: 1 },
source: 'two.js'
});
map.setSourceContent('one.js', 'one file content');
map = JSON.parse(map.toString());
assert.equal(map.sources[0], 'one.js');
assert.equal(map.sources[1], 'two.js');
assert.equal(map.sourcesContent[0], 'one file content');
assert.equal(map.sourcesContent[1], null);
};
exports['test .fromSourceMap'] = function (assert, util) {
var map = SourceMapGenerator.fromSourceMap(new SourceMapConsumer(util.testMap));
util.assertEqualMaps(assert, map.toJSON(), util.testMap);
};
exports['test .fromSourceMap with sourcesContent'] = function (assert, util) {
var map = SourceMapGenerator.fromSourceMap(
new SourceMapConsumer(util.testMapWithSourcesContent));
util.assertEqualMaps(assert, map.toJSON(), util.testMapWithSourcesContent);
};
exports['test applySourceMap'] = function (assert, util) {
var node = new SourceNode(null, null, null, [
new SourceNode(2, 0, 'fileX', 'lineX2\n'),
'genA1\n',
new SourceNode(2, 0, 'fileY', 'lineY2\n'),
'genA2\n',
new SourceNode(1, 0, 'fileX', 'lineX1\n'),
'genA3\n',
new SourceNode(1, 0, 'fileY', 'lineY1\n')
]);
var mapStep1 = node.toStringWithSourceMap({
file: 'fileA'
}).map;
mapStep1.setSourceContent('fileX', 'lineX1\nlineX2\n');
mapStep1 = mapStep1.toJSON();
node = new SourceNode(null, null, null, [
'gen1\n',
new SourceNode(1, 0, 'fileA', 'lineA1\n'),
new SourceNode(2, 0, 'fileA', 'lineA2\n'),
new SourceNode(3, 0, 'fileA', 'lineA3\n'),
new SourceNode(4, 0, 'fileA', 'lineA4\n'),
new SourceNode(1, 0, 'fileB', 'lineB1\n'),
new SourceNode(2, 0, 'fileB', 'lineB2\n'),
'gen2\n'
]);
var mapStep2 = node.toStringWithSourceMap({
file: 'fileGen'
}).map;
mapStep2.setSourceContent('fileB', 'lineB1\nlineB2\n');
mapStep2 = mapStep2.toJSON();
node = new SourceNode(null, null, null, [
'gen1\n',
new SourceNode(2, 0, 'fileX', 'lineA1\n'),
new SourceNode(2, 0, 'fileA', 'lineA2\n'),
new SourceNode(2, 0, 'fileY', 'lineA3\n'),
new SourceNode(4, 0, 'fileA', 'lineA4\n'),
new SourceNode(1, 0, 'fileB', 'lineB1\n'),
new SourceNode(2, 0, 'fileB', 'lineB2\n'),
'gen2\n'
]);
var expectedMap = node.toStringWithSourceMap({
file: 'fileGen'
}).map;
expectedMap.setSourceContent('fileX', 'lineX1\nlineX2\n');
expectedMap.setSourceContent('fileB', 'lineB1\nlineB2\n');
expectedMap = expectedMap.toJSON();
// apply source map "mapStep1" to "mapStep2"
var generator = SourceMapGenerator.fromSourceMap(new SourceMapConsumer(mapStep2));
generator.applySourceMap(new SourceMapConsumer(mapStep1));
var actualMap = generator.toJSON();
util.assertEqualMaps(assert, actualMap, expectedMap);
};
exports['test sorting with duplicate generated mappings'] = function (assert, util) {
var map = new SourceMapGenerator({
file: 'test.js'
});
map.addMapping({
generated: { line: 3, column: 0 },
original: { line: 2, column: 0 },
source: 'a.js'
});
map.addMapping({
generated: { line: 2, column: 0 }
});
map.addMapping({
generated: { line: 2, column: 0 }
});
map.addMapping({
generated: { line: 1, column: 0 },
original: { line: 1, column: 0 },
source: 'a.js'
});
util.assertEqualMaps(assert, map.toJSON(), {
version: 3,
file: 'test.js',
sources: ['a.js'],
names: [],
mappings: 'AAAA;A;AACA'
});
};
exports['test ignore duplicate mappings.'] = function (assert, util) {
var init = { file: 'min.js', sourceRoot: '/the/root' };
var map1, map2;
// null original source location
var nullMapping1 = {
generated: { line: 1, column: 0 }
};
var nullMapping2 = {
generated: { line: 2, column: 2 }
};
map1 = new SourceMapGenerator(init);
map2 = new SourceMapGenerator(init);
map1.addMapping(nullMapping1);
map1.addMapping(nullMapping1);
map2.addMapping(nullMapping1);
util.assertEqualMaps(assert, map1.toJSON(), map2.toJSON());
map1.addMapping(nullMapping2);
map1.addMapping(nullMapping1);
map2.addMapping(nullMapping2);
util.assertEqualMaps(assert, map1.toJSON(), map2.toJSON());
// original source location
var srcMapping1 = {
generated: { line: 1, column: 0 },
original: { line: 11, column: 0 },
source: 'srcMapping1.js'
};
var srcMapping2 = {
generated: { line: 2, column: 2 },
original: { line: 11, column: 0 },
source: 'srcMapping2.js'
};
map1 = new SourceMapGenerator(init);
map2 = new SourceMapGenerator(init);
map1.addMapping(srcMapping1);
map1.addMapping(srcMapping1);
map2.addMapping(srcMapping1);
util.assertEqualMaps(assert, map1.toJSON(), map2.toJSON());
map1.addMapping(srcMapping2);
map1.addMapping(srcMapping1);
map2.addMapping(srcMapping2);
util.assertEqualMaps(assert, map1.toJSON(), map2.toJSON());
// full original source and name information
var fullMapping1 = {
generated: { line: 1, column: 0 },
original: { line: 11, column: 0 },
source: 'fullMapping1.js',
name: 'fullMapping1'
};
var fullMapping2 = {
generated: { line: 2, column: 2 },
original: { line: 11, column: 0 },
source: 'fullMapping2.js',
name: 'fullMapping2'
};
map1 = new SourceMapGenerator(init);
map2 = new SourceMapGenerator(init);
map1.addMapping(fullMapping1);
map1.addMapping(fullMapping1);
map2.addMapping(fullMapping1);
util.assertEqualMaps(assert, map1.toJSON(), map2.toJSON());
map1.addMapping(fullMapping2);
map1.addMapping(fullMapping1);
map2.addMapping(fullMapping2);
util.assertEqualMaps(assert, map1.toJSON(), map2.toJSON());
};
exports['test github issue #72, check for duplicate names or sources'] = function (assert, util) {
var map = new SourceMapGenerator({
file: 'test.js'
});
map.addMapping({
generated: { line: 1, column: 1 },
original: { line: 2, column: 2 },
source: 'a.js',
name: 'foo'
});
map.addMapping({
generated: { line: 3, column: 3 },
original: { line: 4, column: 4 },
source: 'a.js',
name: 'foo'
});
util.assertEqualMaps(assert, map.toJSON(), {
version: 3,
file: 'test.js',
sources: ['a.js'],
names: ['foo'],
mappings: 'CACEA;;GAEEA'
});
};
});

View File

@@ -0,0 +1,365 @@
/* -*- Mode: js; js-indent-level: 2; -*- */
/*
* Copyright 2011 Mozilla Foundation and contributors
* Licensed under the New BSD license. See LICENSE or:
* http://opensource.org/licenses/BSD-3-Clause
*/
if (typeof define !== 'function') {
var define = require('amdefine')(module, require);
}
define(function (require, exports, module) {
var SourceMapGenerator = require('../../lib/source-map/source-map-generator').SourceMapGenerator;
var SourceMapConsumer = require('../../lib/source-map/source-map-consumer').SourceMapConsumer;
var SourceNode = require('../../lib/source-map/source-node').SourceNode;
exports['test .add()'] = function (assert, util) {
var node = new SourceNode(null, null, null);
// Adding a string works.
node.add('function noop() {}');
// Adding another source node works.
node.add(new SourceNode(null, null, null));
// Adding an array works.
node.add(['function foo() {',
new SourceNode(null, null, null,
'return 10;'),
'}']);
// Adding other stuff doesn't.
assert.throws(function () {
node.add({});
});
assert.throws(function () {
node.add(function () {});
});
};
exports['test .prepend()'] = function (assert, util) {
var node = new SourceNode(null, null, null);
// Prepending a string works.
node.prepend('function noop() {}');
assert.equal(node.children[0], 'function noop() {}');
assert.equal(node.children.length, 1);
// Prepending another source node works.
node.prepend(new SourceNode(null, null, null));
assert.equal(node.children[0], '');
assert.equal(node.children[1], 'function noop() {}');
assert.equal(node.children.length, 2);
// Prepending an array works.
node.prepend(['function foo() {',
new SourceNode(null, null, null,
'return 10;'),
'}']);
assert.equal(node.children[0], 'function foo() {');
assert.equal(node.children[1], 'return 10;');
assert.equal(node.children[2], '}');
assert.equal(node.children[3], '');
assert.equal(node.children[4], 'function noop() {}');
assert.equal(node.children.length, 5);
// Prepending other stuff doesn't.
assert.throws(function () {
node.prepend({});
});
assert.throws(function () {
node.prepend(function () {});
});
};
exports['test .toString()'] = function (assert, util) {
assert.equal((new SourceNode(null, null, null,
['function foo() {',
new SourceNode(null, null, null, 'return 10;'),
'}'])).toString(),
'function foo() {return 10;}');
};
exports['test .join()'] = function (assert, util) {
assert.equal((new SourceNode(null, null, null,
['a', 'b', 'c', 'd'])).join(', ').toString(),
'a, b, c, d');
};
exports['test .walk()'] = function (assert, util) {
var node = new SourceNode(null, null, null,
['(function () {\n',
' ', new SourceNode(1, 0, 'a.js', ['someCall()']), ';\n',
' ', new SourceNode(2, 0, 'b.js', ['if (foo) bar()']), ';\n',
'}());']);
var expected = [
{ str: '(function () {\n', source: null, line: null, column: null },
{ str: ' ', source: null, line: null, column: null },
{ str: 'someCall()', source: 'a.js', line: 1, column: 0 },
{ str: ';\n', source: null, line: null, column: null },
{ str: ' ', source: null, line: null, column: null },
{ str: 'if (foo) bar()', source: 'b.js', line: 2, column: 0 },
{ str: ';\n', source: null, line: null, column: null },
{ str: '}());', source: null, line: null, column: null },
];
var i = 0;
node.walk(function (chunk, loc) {
assert.equal(expected[i].str, chunk);
assert.equal(expected[i].source, loc.source);
assert.equal(expected[i].line, loc.line);
assert.equal(expected[i].column, loc.column);
i++;
});
};
exports['test .replaceRight'] = function (assert, util) {
var node;
// Not nested
node = new SourceNode(null, null, null, 'hello world');
node.replaceRight(/world/, 'universe');
assert.equal(node.toString(), 'hello universe');
// Nested
node = new SourceNode(null, null, null,
[new SourceNode(null, null, null, 'hey sexy mama, '),
new SourceNode(null, null, null, 'want to kill all humans?')]);
node.replaceRight(/kill all humans/, 'watch Futurama');
assert.equal(node.toString(), 'hey sexy mama, want to watch Futurama?');
};
exports['test .toStringWithSourceMap()'] = function (assert, util) {
var node = new SourceNode(null, null, null,
['(function () {\n',
' ',
new SourceNode(1, 0, 'a.js', 'someCall', 'originalCall'),
new SourceNode(1, 8, 'a.js', '()'),
';\n',
' ', new SourceNode(2, 0, 'b.js', ['if (foo) bar()']), ';\n',
'}());']);
var map = node.toStringWithSourceMap({
file: 'foo.js'
}).map;
assert.ok(map instanceof SourceMapGenerator, 'map instanceof SourceMapGenerator');
map = new SourceMapConsumer(map.toString());
var actual;
actual = map.originalPositionFor({
line: 1,
column: 4
});
assert.equal(actual.source, null);
assert.equal(actual.line, null);
assert.equal(actual.column, null);
actual = map.originalPositionFor({
line: 2,
column: 2
});
assert.equal(actual.source, 'a.js');
assert.equal(actual.line, 1);
assert.equal(actual.column, 0);
assert.equal(actual.name, 'originalCall');
actual = map.originalPositionFor({
line: 3,
column: 2
});
assert.equal(actual.source, 'b.js');
assert.equal(actual.line, 2);
assert.equal(actual.column, 0);
actual = map.originalPositionFor({
line: 3,
column: 16
});
assert.equal(actual.source, null);
assert.equal(actual.line, null);
assert.equal(actual.column, null);
actual = map.originalPositionFor({
line: 4,
column: 2
});
assert.equal(actual.source, null);
assert.equal(actual.line, null);
assert.equal(actual.column, null);
};
exports['test .fromStringWithSourceMap()'] = function (assert, util) {
var node = SourceNode.fromStringWithSourceMap(
util.testGeneratedCode,
new SourceMapConsumer(util.testMap));
var result = node.toStringWithSourceMap({
file: 'min.js'
});
var map = result.map;
var code = result.code;
assert.equal(code, util.testGeneratedCode);
assert.ok(map instanceof SourceMapGenerator, 'map instanceof SourceMapGenerator');
map = map.toJSON();
assert.equal(map.version, util.testMap.version);
assert.equal(map.file, util.testMap.file);
assert.equal(map.mappings, util.testMap.mappings);
};
exports['test .fromStringWithSourceMap() empty map'] = function (assert, util) {
var node = SourceNode.fromStringWithSourceMap(
util.testGeneratedCode,
new SourceMapConsumer(util.emptyMap));
var result = node.toStringWithSourceMap({
file: 'min.js'
});
var map = result.map;
var code = result.code;
assert.equal(code, util.testGeneratedCode);
assert.ok(map instanceof SourceMapGenerator, 'map instanceof SourceMapGenerator');
map = map.toJSON();
assert.equal(map.version, util.emptyMap.version);
assert.equal(map.file, util.emptyMap.file);
assert.equal(map.mappings.length, util.emptyMap.mappings.length);
assert.equal(map.mappings, util.emptyMap.mappings);
};
exports['test .fromStringWithSourceMap() complex version'] = function (assert, util) {
var input = new SourceNode(null, null, null, [
"(function() {\n",
" var Test = {};\n",
" ", new SourceNode(1, 0, "a.js", "Test.A = { value: 1234 };\n"),
" ", new SourceNode(2, 0, "a.js", "Test.A.x = 'xyz';"), "\n",
"}());\n",
"/* Generated Source */"]);
input = input.toStringWithSourceMap({
file: 'foo.js'
});
var node = SourceNode.fromStringWithSourceMap(
input.code,
new SourceMapConsumer(input.map.toString()));
var result = node.toStringWithSourceMap({
file: 'foo.js'
});
var map = result.map;
var code = result.code;
assert.equal(code, input.code);
assert.ok(map instanceof SourceMapGenerator, 'map instanceof SourceMapGenerator');
map = map.toJSON();
var inputMap = input.map.toJSON();
util.assertEqualMaps(assert, map, inputMap);
};
exports['test .fromStringWithSourceMap() merging duplicate mappings'] = function (assert, util) {
var input = new SourceNode(null, null, null, [
new SourceNode(1, 0, "a.js", "(function"),
new SourceNode(1, 0, "a.js", "() {\n"),
" ",
new SourceNode(1, 0, "a.js", "var Test = "),
new SourceNode(1, 0, "b.js", "{};\n"),
new SourceNode(2, 0, "b.js", "Test"),
new SourceNode(2, 0, "b.js", ".A", "A"),
new SourceNode(2, 20, "b.js", " = { value: 1234 };\n", "A"),
"}());\n",
"/* Generated Source */"
]);
input = input.toStringWithSourceMap({
file: 'foo.js'
});
var correctMap = new SourceMapGenerator({
file: 'foo.js'
});
correctMap.addMapping({
generated: { line: 1, column: 0 },
source: 'a.js',
original: { line: 1, column: 0 }
});
correctMap.addMapping({
generated: { line: 2, column: 0 }
});
correctMap.addMapping({
generated: { line: 2, column: 2 },
source: 'a.js',
original: { line: 1, column: 0 }
});
correctMap.addMapping({
generated: { line: 2, column: 13 },
source: 'b.js',
original: { line: 1, column: 0 }
});
correctMap.addMapping({
generated: { line: 3, column: 0 },
source: 'b.js',
original: { line: 2, column: 0 }
});
correctMap.addMapping({
generated: { line: 3, column: 4 },
source: 'b.js',
name: 'A',
original: { line: 2, column: 0 }
});
correctMap.addMapping({
generated: { line: 3, column: 6 },
source: 'b.js',
name: 'A',
original: { line: 2, column: 20 }
});
correctMap.addMapping({
generated: { line: 4, column: 0 }
});
var inputMap = input.map.toJSON();
correctMap = correctMap.toJSON();
util.assertEqualMaps(assert, correctMap, inputMap);
};
exports['test setSourceContent with toStringWithSourceMap'] = function (assert, util) {
var aNode = new SourceNode(1, 1, 'a.js', 'a');
aNode.setSourceContent('a.js', 'someContent');
var node = new SourceNode(null, null, null,
['(function () {\n',
' ', aNode,
' ', new SourceNode(1, 1, 'b.js', 'b'),
'}());']);
node.setSourceContent('b.js', 'otherContent');
var map = node.toStringWithSourceMap({
file: 'foo.js'
}).map;
assert.ok(map instanceof SourceMapGenerator, 'map instanceof SourceMapGenerator');
map = new SourceMapConsumer(map.toString());
assert.equal(map.sources.length, 2);
assert.equal(map.sources[0], 'a.js');
assert.equal(map.sources[1], 'b.js');
assert.equal(map.sourcesContent.length, 2);
assert.equal(map.sourcesContent[0], 'someContent');
assert.equal(map.sourcesContent[1], 'otherContent');
};
exports['test walkSourceContents'] = function (assert, util) {
var aNode = new SourceNode(1, 1, 'a.js', 'a');
aNode.setSourceContent('a.js', 'someContent');
var node = new SourceNode(null, null, null,
['(function () {\n',
' ', aNode,
' ', new SourceNode(1, 1, 'b.js', 'b'),
'}());']);
node.setSourceContent('b.js', 'otherContent');
var results = [];
node.walkSourceContents(function (sourceFile, sourceContent) {
results.push([sourceFile, sourceContent]);
});
assert.equal(results.length, 2);
assert.equal(results[0][0], 'a.js');
assert.equal(results[0][1], 'someContent');
assert.equal(results[1][0], 'b.js');
assert.equal(results[1][1], 'otherContent');
};
});

View File

@@ -0,0 +1,161 @@
/* -*- Mode: js; js-indent-level: 2; -*- */
/*
* Copyright 2011 Mozilla Foundation and contributors
* Licensed under the New BSD license. See LICENSE or:
* http://opensource.org/licenses/BSD-3-Clause
*/
if (typeof define !== 'function') {
var define = require('amdefine')(module, require);
}
define(function (require, exports, module) {
var util = require('../../lib/source-map/util');
// This is a test mapping which maps functions from two different files
// (one.js and two.js) to a minified generated source.
//
// Here is one.js:
//
// ONE.foo = function (bar) {
// return baz(bar);
// };
//
// Here is two.js:
//
// TWO.inc = function (n) {
// return n + 1;
// };
//
// And here is the generated code (min.js):
//
// ONE.foo=function(a){return baz(a);};
// TWO.inc=function(a){return a+1;};
exports.testGeneratedCode = " ONE.foo=function(a){return baz(a);};\n"+
" TWO.inc=function(a){return a+1;};";
exports.testMap = {
version: 3,
file: 'min.js',
names: ['bar', 'baz', 'n'],
sources: ['one.js', 'two.js'],
sourceRoot: '/the/root',
mappings: 'CAAC,IAAI,IAAM,SAAUA,GAClB,OAAOC,IAAID;CCDb,IAAI,IAAM,SAAUE,GAClB,OAAOA'
};
exports.testMapWithSourcesContent = {
version: 3,
file: 'min.js',
names: ['bar', 'baz', 'n'],
sources: ['one.js', 'two.js'],
sourcesContent: [
' ONE.foo = function (bar) {\n' +
' return baz(bar);\n' +
' };',
' TWO.inc = function (n) {\n' +
' return n + 1;\n' +
' };'
],
sourceRoot: '/the/root',
mappings: 'CAAC,IAAI,IAAM,SAAUA,GAClB,OAAOC,IAAID;CCDb,IAAI,IAAM,SAAUE,GAClB,OAAOA'
};
exports.emptyMap = {
version: 3,
file: 'min.js',
names: [],
sources: [],
mappings: ''
};
function assertMapping(generatedLine, generatedColumn, originalSource,
originalLine, originalColumn, name, map, assert,
dontTestGenerated, dontTestOriginal) {
if (!dontTestOriginal) {
var origMapping = map.originalPositionFor({
line: generatedLine,
column: generatedColumn
});
assert.equal(origMapping.name, name,
'Incorrect name, expected ' + JSON.stringify(name)
+ ', got ' + JSON.stringify(origMapping.name));
assert.equal(origMapping.line, originalLine,
'Incorrect line, expected ' + JSON.stringify(originalLine)
+ ', got ' + JSON.stringify(origMapping.line));
assert.equal(origMapping.column, originalColumn,
'Incorrect column, expected ' + JSON.stringify(originalColumn)
+ ', got ' + JSON.stringify(origMapping.column));
var expectedSource;
if (originalSource && map.sourceRoot && originalSource.indexOf(map.sourceRoot) === 0) {
expectedSource = originalSource;
} else if (originalSource) {
expectedSource = map.sourceRoot
? util.join(map.sourceRoot, originalSource)
: originalSource;
} else {
expectedSource = null;
}
assert.equal(origMapping.source, expectedSource,
'Incorrect source, expected ' + JSON.stringify(expectedSource)
+ ', got ' + JSON.stringify(origMapping.source));
}
if (!dontTestGenerated) {
var genMapping = map.generatedPositionFor({
source: originalSource,
line: originalLine,
column: originalColumn
});
assert.equal(genMapping.line, generatedLine,
'Incorrect line, expected ' + JSON.stringify(generatedLine)
+ ', got ' + JSON.stringify(genMapping.line));
assert.equal(genMapping.column, generatedColumn,
'Incorrect column, expected ' + JSON.stringify(generatedColumn)
+ ', got ' + JSON.stringify(genMapping.column));
}
}
exports.assertMapping = assertMapping;
function assertEqualMaps(assert, actualMap, expectedMap) {
assert.equal(actualMap.version, expectedMap.version, "version mismatch");
assert.equal(actualMap.file, expectedMap.file, "file mismatch");
assert.equal(actualMap.names.length,
expectedMap.names.length,
"names length mismatch: " +
actualMap.names.join(", ") + " != " + expectedMap.names.join(", "));
for (var i = 0; i < actualMap.names.length; i++) {
assert.equal(actualMap.names[i],
expectedMap.names[i],
"names[" + i + "] mismatch: " +
actualMap.names.join(", ") + " != " + expectedMap.names.join(", "));
}
assert.equal(actualMap.sources.length,
expectedMap.sources.length,
"sources length mismatch: " +
actualMap.sources.join(", ") + " != " + expectedMap.sources.join(", "));
for (var i = 0; i < actualMap.sources.length; i++) {
assert.equal(actualMap.sources[i],
expectedMap.sources[i],
"sources[" + i + "] length mismatch: " +
actualMap.sources.join(", ") + " != " + expectedMap.sources.join(", "));
}
assert.equal(actualMap.sourceRoot,
expectedMap.sourceRoot,
"sourceRoot mismatch: " +
actualMap.sourceRoot + " != " + expectedMap.sourceRoot);
assert.equal(actualMap.mappings, expectedMap.mappings,
"mappings mismatch:\nActual: " + actualMap.mappings + "\nExpected: " + expectedMap.mappings);
if (actualMap.sourcesContent) {
assert.equal(actualMap.sourcesContent.length,
expectedMap.sourcesContent.length,
"sourcesContent length mismatch");
for (var i = 0; i < actualMap.sourcesContent.length; i++) {
assert.equal(actualMap.sourcesContent[i],
expectedMap.sourcesContent[i],
"sourcesContent[" + i + "] mismatch");
}
}
}
exports.assertEqualMaps = assertEqualMaps;
});

File diff suppressed because one or more lines are too long

View File

@@ -1,28 +0,0 @@
#! /usr/bin/env node
global.sys = require("sys");
var fs = require("fs");
var jsp = require("../lib/parse-js");
var pro = require("../lib/process");
var filename = process.argv[2];
fs.readFile(filename, "utf8", function(err, text){
try {
var ast = time_it("parse", function(){ return jsp.parse(text); });
ast = time_it("mangle", function(){ return pro.ast_mangle(ast); });
ast = time_it("squeeze", function(){ return pro.ast_squeeze(ast); });
var gen = time_it("generate", function(){ return pro.gen_code(ast, false); });
sys.puts(gen);
} catch(ex) {
sys.debug(ex.stack);
sys.debug(sys.inspect(ex));
sys.debug(JSON.stringify(ex));
}
});
function time_it(name, cont) {
var t1 = new Date().getTime();
try { return cont(); }
finally { sys.debug("// " + name + ": " + ((new Date().getTime() - t1) / 1000).toFixed(3) + " sec."); }
};

View File

@@ -0,0 +1,12 @@
holes_and_undefined: {
input: {
x = [1, 2, undefined];
y = [1, , 2, ];
z = [1, undefined, 3];
}
expect: {
x=[1,2,void 0];
y=[1,,2];
z=[1,void 0,3];
}
}

View File

@@ -0,0 +1,49 @@
remove_blocks: {
input: {
{;}
foo();
{};
{
{};
};
bar();
{}
}
expect: {
foo();
bar();
}
}
keep_some_blocks: {
input: {
// 1.
if (foo) {
{{{}}}
if (bar) { baz(); }
{{}}
} else {
stuff();
}
// 2.
if (foo) {
for (var i = 0; i < 5; ++i)
if (bar) baz();
} else {
stuff();
}
}
expect: {
// 1.
if (foo) {
if (bar) baz();
} else stuff();
// 2.
if (foo) {
for (var i = 0; i < 5; ++i)
if (bar) baz();
} else stuff();
}
}

View File

@@ -0,0 +1,143 @@
ifs_1: {
options = {
conditionals: true
};
input: {
if (foo) bar();
if (!foo); else bar();
if (foo); else bar();
if (foo); else;
}
expect: {
foo&&bar();
foo&&bar();
foo||bar();
foo;
}
}
ifs_2: {
options = {
conditionals: true
};
input: {
if (foo) {
x();
} else if (bar) {
y();
} else if (baz) {
z();
}
if (foo) {
x();
} else if (bar) {
y();
} else if (baz) {
z();
} else {
t();
}
}
expect: {
foo ? x() : bar ? y() : baz && z();
foo ? x() : bar ? y() : baz ? z() : t();
}
}
ifs_3_should_warn: {
options = {
conditionals : true,
dead_code : true,
evaluate : true,
booleans : true
};
input: {
if (x && !(x + "1") && y) { // 1
var qq;
foo();
} else {
bar();
}
if (x || !!(x + "1") || y) { // 2
foo();
} else {
var jj;
bar();
}
}
expect: {
var qq; bar(); // 1
var jj; foo(); // 2
}
}
ifs_4: {
options = {
conditionals: true
};
input: {
if (foo && bar) {
x(foo)[10].bar.baz = something();
} else
x(foo)[10].bar.baz = something_else();
}
expect: {
x(foo)[10].bar.baz = (foo && bar) ? something() : something_else();
}
}
ifs_5: {
options = {
if_return: true,
conditionals: true,
comparisons: true,
};
input: {
function f() {
if (foo) return;
bar();
baz();
}
function g() {
if (foo) return;
if (bar) return;
if (baz) return;
if (baa) return;
a();
b();
}
}
expect: {
function f() {
if (!foo) {
bar();
baz();
}
}
function g() {
if (!(foo || bar || baz || baa)) {
a();
b();
}
}
}
}
ifs_6: {
options = {
conditionals: true,
comparisons: true
};
input: {
if (!foo && !bar && !baz && !boo) {
x = 10;
} else {
x = 20;
}
}
expect: {
x = foo || bar || baz || boo ? 20 : 10;
}
}

View File

@@ -0,0 +1,89 @@
dead_code_1: {
options = {
dead_code: true
};
input: {
function f() {
a();
b();
x = 10;
return;
if (x) {
y();
}
}
}
expect: {
function f() {
a();
b();
x = 10;
return;
}
}
}
dead_code_2_should_warn: {
options = {
dead_code: true
};
input: {
function f() {
g();
x = 10;
throw "foo";
// completely discarding the `if` would introduce some
// bugs. UglifyJS v1 doesn't deal with this issue; in v2
// we copy any declarations to the upper scope.
if (x) {
y();
var x;
function g(){};
// but nested declarations should not be kept.
(function(){
var q;
function y(){};
})();
}
}
}
expect: {
function f() {
g();
x = 10;
throw "foo";
var x;
function g(){};
}
}
}
dead_code_constant_boolean_should_warn_more: {
options = {
dead_code : true,
loops : true,
booleans : true,
conditionals : true,
evaluate : true
};
input: {
while (!((foo && bar) || (x + "0"))) {
console.log("unreachable");
var foo;
function bar() {}
}
for (var x = 10; x && (y || x) && (!typeof x); ++x) {
asdf();
foo();
var moo;
}
}
expect: {
var foo;
function bar() {}
// nothing for the while
// as for the for, it should keep:
var x = 10;
var moo;
}
}

View File

@@ -0,0 +1,24 @@
keep_debugger: {
options = {
drop_debugger: false
};
input: {
debugger;
}
expect: {
debugger;
}
}
drop_debugger: {
options = {
drop_debugger: true
};
input: {
debugger;
if (foo) debugger;
}
expect: {
if (foo);
}
}

View File

@@ -0,0 +1,97 @@
unused_funarg_1: {
options = { unused: true };
input: {
function f(a, b, c, d, e) {
return a + b;
}
}
expect: {
function f(a, b) {
return a + b;
}
}
}
unused_funarg_2: {
options = { unused: true };
input: {
function f(a, b, c, d, e) {
return a + c;
}
}
expect: {
function f(a, b, c) {
return a + c;
}
}
}
unused_nested_function: {
options = { unused: true };
input: {
function f(x, y) {
function g() {
something();
}
return x + y;
}
};
expect: {
function f(x, y) {
return x + y;
}
}
}
unused_circular_references_1: {
options = { unused: true };
input: {
function f(x, y) {
// circular reference
function g() {
return h();
}
function h() {
return g();
}
return x + y;
}
};
expect: {
function f(x, y) {
return x + y;
}
}
}
unused_circular_references_2: {
options = { unused: true };
input: {
function f(x, y) {
var foo = 1, bar = baz, baz = foo + bar, qwe = moo();
return x + y;
}
};
expect: {
function f(x, y) {
moo(); // keeps side effect
return x + y;
}
}
}
unused_circular_references_3: {
options = { unused: true };
input: {
function f(x, y) {
var g = function() { return h() };
var h = function() { return g() };
return x + y;
}
};
expect: {
function f(x, y) {
return x + y;
}
}
}

View File

@@ -0,0 +1,25 @@
typeof_eq_undefined: {
options = {
comparisons: true
};
input: { a = typeof b.c != "undefined" }
expect: { a = "undefined" != typeof b.c }
}
typeof_eq_undefined_unsafe: {
options = {
comparisons: true,
unsafe: true
};
input: { a = typeof b.c != "undefined" }
expect: { a = void 0 !== b.c }
}
typeof_eq_undefined_unsafe2: {
options = {
comparisons: true,
unsafe: true
};
input: { a = "undefined" != typeof b.c }
expect: { a = void 0 !== b.c }
}

View File

@@ -0,0 +1,11 @@
keep_name_of_getter: {
options = { unused: true };
input: { a = { get foo () {} } }
expect: { a = { get foo () {} } }
}
keep_name_of_setter: {
options = { unused: true };
input: { a = { set foo () {} } }
expect: { a = { set foo () {} } }
}

View File

@@ -0,0 +1,48 @@
/**
* There was an incorrect sort behaviour documented in issue #143:
* (x = f(…)) <= x → x >= (x = f(…))
*
* For example, let the equation be:
* (a = parseInt('100')) <= a
*
* If a was an integer and has the value of 99,
* (a = parseInt('100')) <= a → 100 <= 100 → true
*
* When transformed incorrectly:
* a >= (a = parseInt('100')) → 99 >= 100 → false
*/
tranformation_sort_order_equal: {
options = {
comparisons: true,
};
input: { (a = parseInt('100')) == a }
expect: { (a = parseInt('100')) == a }
}
tranformation_sort_order_unequal: {
options = {
comparisons: true,
};
input: { (a = parseInt('100')) != a }
expect: { (a = parseInt('100')) != a }
}
tranformation_sort_order_lesser_or_equal: {
options = {
comparisons: true,
};
input: { (a = parseInt('100')) <= a }
expect: { (a = parseInt('100')) <= a }
}
tranformation_sort_order_greater_or_equal: {
options = {
comparisons: true,
};
input: { (a = parseInt('100')) >= a }
expect: { (a = parseInt('100')) >= a }
}

View File

@@ -0,0 +1,17 @@
return_with_no_value_in_if_body: {
options = { conditionals: true };
input: {
function foo(bar) {
if (bar) {
return;
} else {
return 1;
}
}
}
expect: {
function foo (bar) {
return bar ? void 0 : 1;
}
}
}

View File

@@ -0,0 +1,31 @@
issue_44_valid_ast_1: {
options = { unused: true };
input: {
function a(b) {
for (var i = 0, e = b.qoo(); ; i++) {}
}
}
expect: {
function a(b) {
var i = 0;
for (b.qoo(); ; i++);
}
}
}
issue_44_valid_ast_2: {
options = { unused: true };
input: {
function a(b) {
if (foo) for (var i = 0, e = b.qoo(); ; i++) {}
}
}
expect: {
function a(b) {
if (foo) {
var i = 0;
for (b.qoo(); ; i++);
}
}
}
}

View File

@@ -0,0 +1,30 @@
keep_continue: {
options = {
dead_code: true,
evaluate: true
};
input: {
while (a) {
if (b) {
switch (true) {
case c():
d();
}
continue;
}
f();
}
}
expect: {
while (a) {
if (b) {
switch (true) {
case c():
d();
}
continue;
}
f();
}
}
}

View File

@@ -0,0 +1,163 @@
labels_1: {
options = { if_return: true, conditionals: true, dead_code: true };
input: {
out: {
if (foo) break out;
console.log("bar");
}
};
expect: {
foo || console.log("bar");
}
}
labels_2: {
options = { if_return: true, conditionals: true, dead_code: true };
input: {
out: {
if (foo) print("stuff");
else break out;
console.log("here");
}
};
expect: {
if (foo) {
print("stuff");
console.log("here");
}
}
}
labels_3: {
options = { if_return: true, conditionals: true, dead_code: true };
input: {
for (var i = 0; i < 5; ++i) {
if (i < 3) continue;
console.log(i);
}
};
expect: {
for (var i = 0; i < 5; ++i)
i < 3 || console.log(i);
}
}
labels_4: {
options = { if_return: true, conditionals: true, dead_code: true };
input: {
out: for (var i = 0; i < 5; ++i) {
if (i < 3) continue out;
console.log(i);
}
};
expect: {
for (var i = 0; i < 5; ++i)
i < 3 || console.log(i);
}
}
labels_5: {
options = { if_return: true, conditionals: true, dead_code: true };
// should keep the break-s in the following
input: {
while (foo) {
if (bar) break;
console.log("foo");
}
out: while (foo) {
if (bar) break out;
console.log("foo");
}
};
expect: {
while (foo) {
if (bar) break;
console.log("foo");
}
out: while (foo) {
if (bar) break out;
console.log("foo");
}
}
}
labels_6: {
input: {
out: break out;
};
expect: {}
}
labels_7: {
options = { if_return: true, conditionals: true, dead_code: true };
input: {
while (foo) {
x();
y();
continue;
}
};
expect: {
while (foo) {
x();
y();
}
}
}
labels_8: {
options = { if_return: true, conditionals: true, dead_code: true };
input: {
while (foo) {
x();
y();
break;
}
};
expect: {
while (foo) {
x();
y();
break;
}
}
}
labels_9: {
options = { if_return: true, conditionals: true, dead_code: true };
input: {
out: while (foo) {
x();
y();
continue out;
z();
k();
}
};
expect: {
while (foo) {
x();
y();
}
}
}
labels_10: {
options = { if_return: true, conditionals: true, dead_code: true };
input: {
out: while (foo) {
x();
y();
break out;
z();
k();
}
};
expect: {
out: while (foo) {
x();
y();
break out;
}
}
}

View File

@@ -0,0 +1,123 @@
while_becomes_for: {
options = { loops: true };
input: {
while (foo()) bar();
}
expect: {
for (; foo(); ) bar();
}
}
drop_if_break_1: {
options = { loops: true };
input: {
for (;;)
if (foo()) break;
}
expect: {
for (; !foo(););
}
}
drop_if_break_2: {
options = { loops: true };
input: {
for (;bar();)
if (foo()) break;
}
expect: {
for (; bar() && !foo(););
}
}
drop_if_break_3: {
options = { loops: true };
input: {
for (;bar();) {
if (foo()) break;
stuff1();
stuff2();
}
}
expect: {
for (; bar() && !foo();) {
stuff1();
stuff2();
}
}
}
drop_if_break_4: {
options = { loops: true, sequences: true };
input: {
for (;bar();) {
x();
y();
if (foo()) break;
z();
k();
}
}
expect: {
for (; bar() && (x(), y(), !foo());) z(), k();
}
}
drop_if_else_break_1: {
options = { loops: true };
input: {
for (;;) if (foo()) bar(); else break;
}
expect: {
for (; foo(); ) bar();
}
}
drop_if_else_break_2: {
options = { loops: true };
input: {
for (;bar();) {
if (foo()) baz();
else break;
}
}
expect: {
for (; bar() && foo();) baz();
}
}
drop_if_else_break_3: {
options = { loops: true };
input: {
for (;bar();) {
if (foo()) baz();
else break;
stuff1();
stuff2();
}
}
expect: {
for (; bar() && foo();) {
baz();
stuff1();
stuff2();
}
}
}
drop_if_else_break_4: {
options = { loops: true, sequences: true };
input: {
for (;bar();) {
x();
y();
if (foo()) baz();
else break;
z();
k();
}
}
expect: {
for (; bar() && (x(), y(), foo());) baz(), z(), k();
}
}

View File

@@ -0,0 +1,54 @@
keep_properties: {
options = {
properties: false
};
input: {
a["foo"] = "bar";
}
expect: {
a["foo"] = "bar";
}
}
dot_properties: {
options = {
properties: true
};
input: {
a["foo"] = "bar";
a["if"] = "if";
a["*"] = "asterisk";
a["\u0EB3"] = "unicode";
a[""] = "whitespace";
a["1_1"] = "foo";
}
expect: {
a.foo = "bar";
a["if"] = "if";
a["*"] = "asterisk";
a.\u0EB3 = "unicode";
a[""] = "whitespace";
a["1_1"] = "foo";
}
}
dot_properties_es5: {
options = {
properties: true,
screw_ie8: true
};
input: {
a["foo"] = "bar";
a["if"] = "if";
a["*"] = "asterisk";
a["\u0EB3"] = "unicode";
a[""] = "whitespace";
}
expect: {
a.foo = "bar";
a.if = "if";
a["*"] = "asterisk";
a.\u0EB3 = "unicode";
a[""] = "whitespace";
}
}

View File

@@ -0,0 +1,161 @@
make_sequences_1: {
options = {
sequences: true
};
input: {
foo();
bar();
baz();
}
expect: {
foo(),bar(),baz();
}
}
make_sequences_2: {
options = {
sequences: true
};
input: {
if (boo) {
foo();
bar();
baz();
} else {
x();
y();
z();
}
}
expect: {
if (boo) foo(),bar(),baz();
else x(),y(),z();
}
}
make_sequences_3: {
options = {
sequences: true
};
input: {
function f() {
foo();
bar();
return baz();
}
function g() {
foo();
bar();
throw new Error();
}
}
expect: {
function f() {
return foo(), bar(), baz();
}
function g() {
throw foo(), bar(), new Error();
}
}
}
make_sequences_4: {
options = {
sequences: true
};
input: {
x = 5;
if (y) z();
x = 5;
for (i = 0; i < 5; i++) console.log(i);
x = 5;
for (; i < 5; i++) console.log(i);
x = 5;
switch (y) {}
x = 5;
with (obj) {}
}
expect: {
if (x = 5, y) z();
for (x = 5, i = 0; i < 5; i++) console.log(i);
for (x = 5; i < 5; i++) console.log(i);
switch (x = 5, y) {}
with (x = 5, obj);
}
}
lift_sequences_1: {
options = { sequences: true };
input: {
foo = !(x(), y(), bar());
}
expect: {
x(), y(), foo = !bar();
}
}
lift_sequences_2: {
options = { sequences: true, evaluate: true };
input: {
q = 1 + (foo(), bar(), 5) + 7 * (5 / (3 - (a(), (QW=ER), c(), 2))) - (x(), y(), 5);
}
expect: {
foo(), bar(), a(), QW = ER, c(), x(), y(), q = 36
}
}
lift_sequences_3: {
options = { sequences: true, conditionals: true };
input: {
x = (foo(), bar(), baz()) ? 10 : 20;
}
expect: {
foo(), bar(), x = baz() ? 10 : 20;
}
}
lift_sequences_4: {
options = { side_effects: true };
input: {
x = (foo, bar, baz);
}
expect: {
x = baz;
}
}
for_sequences: {
options = { sequences: true };
input: {
// 1
foo();
bar();
for (; false;);
// 2
foo();
bar();
for (x = 5; false;);
// 3
x = (foo in bar);
for (; false;);
// 4
x = (foo in bar);
for (y = 5; false;);
}
expect: {
// 1
for (foo(), bar(); false;);
// 2
for (foo(), bar(), x = 5; false;);
// 3
x = (foo in bar);
for (; false;);
// 4
x = (foo in bar);
for (y = 5; false;);
}
}

View File

@@ -0,0 +1,260 @@
constant_switch_1: {
options = { dead_code: true, evaluate: true };
input: {
switch (1+1) {
case 1: foo(); break;
case 1+1: bar(); break;
case 1+1+1: baz(); break;
}
}
expect: {
bar();
}
}
constant_switch_2: {
options = { dead_code: true, evaluate: true };
input: {
switch (1) {
case 1: foo();
case 1+1: bar(); break;
case 1+1+1: baz();
}
}
expect: {
foo();
bar();
}
}
constant_switch_3: {
options = { dead_code: true, evaluate: true };
input: {
switch (10) {
case 1: foo();
case 1+1: bar(); break;
case 1+1+1: baz();
default:
def();
}
}
expect: {
def();
}
}
constant_switch_4: {
options = { dead_code: true, evaluate: true };
input: {
switch (2) {
case 1:
x();
if (foo) break;
y();
break;
case 1+1:
bar();
default:
def();
}
}
expect: {
bar();
def();
}
}
constant_switch_5: {
options = { dead_code: true, evaluate: true };
input: {
switch (1) {
case 1:
x();
if (foo) break;
y();
break;
case 1+1:
bar();
default:
def();
}
}
expect: {
// the break inside the if ruins our job
// we can still get rid of irrelevant cases.
switch (1) {
case 1:
x();
if (foo) break;
y();
}
// XXX: we could optimize this better by inventing an outer
// labeled block, but that's kinda tricky.
}
}
constant_switch_6: {
options = { dead_code: true, evaluate: true };
input: {
OUT: {
foo();
switch (1) {
case 1:
x();
if (foo) break OUT;
y();
case 1+1:
bar();
break;
default:
def();
}
}
}
expect: {
OUT: {
foo();
x();
if (foo) break OUT;
y();
bar();
}
}
}
constant_switch_7: {
options = { dead_code: true, evaluate: true };
input: {
OUT: {
foo();
switch (1) {
case 1:
x();
if (foo) break OUT;
for (var x = 0; x < 10; x++) {
if (x > 5) break; // this break refers to the for, not to the switch; thus it
// shouldn't ruin our optimization
console.log(x);
}
y();
case 1+1:
bar();
break;
default:
def();
}
}
}
expect: {
OUT: {
foo();
x();
if (foo) break OUT;
for (var x = 0; x < 10; x++) {
if (x > 5) break;
console.log(x);
}
y();
bar();
}
}
}
constant_switch_8: {
options = { dead_code: true, evaluate: true };
input: {
OUT: switch (1) {
case 1:
x();
for (;;) break OUT;
y();
break;
case 1+1:
bar();
default:
def();
}
}
expect: {
OUT: {
x();
for (;;) break OUT;
y();
}
}
}
constant_switch_9: {
options = { dead_code: true, evaluate: true };
input: {
OUT: switch (1) {
case 1:
x();
for (;;) if (foo) break OUT;
y();
case 1+1:
bar();
default:
def();
}
}
expect: {
OUT: {
x();
for (;;) if (foo) break OUT;
y();
bar();
def();
}
}
}
drop_default_1: {
options = { dead_code: true };
input: {
switch (foo) {
case 'bar': baz();
default:
}
}
expect: {
switch (foo) {
case 'bar': baz();
}
}
}
drop_default_2: {
options = { dead_code: true };
input: {
switch (foo) {
case 'bar': baz(); break;
default:
break;
}
}
expect: {
switch (foo) {
case 'bar': baz();
}
}
}
keep_default: {
options = { dead_code: true };
input: {
switch (foo) {
case 'bar': baz();
default:
something();
break;
}
}
expect: {
switch (foo) {
case 'bar': baz();
default:
something();
}
}
}

View File

@@ -0,0 +1,25 @@
typeof_evaluation: {
options = {
evaluate: true
};
input: {
a = typeof 1;
b = typeof 'test';
c = typeof [];
d = typeof {};
e = typeof /./;
f = typeof false;
g = typeof function(){};
h = typeof undefined;
}
expect: {
a='number';
b='string';
c=typeof[];
d=typeof{};
e=typeof/./;
f='boolean';
g='function';
h='undefined';
}
}

View File

@@ -0,0 +1,170 @@
#! /usr/bin/env node
var U = require("../tools/node");
var path = require("path");
var fs = require("fs");
var assert = require("assert");
var sys = require("util");
var tests_dir = path.dirname(module.filename);
run_compress_tests();
/* -----[ utils ]----- */
function tmpl() {
return U.string_template.apply(this, arguments);
}
function log() {
var txt = tmpl.apply(this, arguments);
sys.puts(txt);
}
function log_directory(dir) {
log("*** Entering [{dir}]", { dir: dir });
}
function log_start_file(file) {
log("--- {file}", { file: file });
}
function log_test(name) {
log(" Running test [{name}]", { name: name });
}
function find_test_files(dir) {
var files = fs.readdirSync(dir).filter(function(name){
return /\.js$/i.test(name);
});
if (process.argv.length > 2) {
var x = process.argv.slice(2);
files = files.filter(function(f){
return x.indexOf(f) >= 0;
});
}
return files;
}
function test_directory(dir) {
return path.resolve(tests_dir, dir);
}
function as_toplevel(input) {
if (input instanceof U.AST_BlockStatement) input = input.body;
else if (input instanceof U.AST_Statement) input = [ input ];
else throw new Error("Unsupported input syntax");
var toplevel = new U.AST_Toplevel({ body: input });
toplevel.figure_out_scope();
return toplevel;
}
function run_compress_tests() {
var dir = test_directory("compress");
log_directory("compress");
var files = find_test_files(dir);
function test_file(file) {
log_start_file(file);
function test_case(test) {
log_test(test.name);
var options = U.defaults(test.options, {
warnings: false
});
var cmp = new U.Compressor(options, true);
var expect = make_code(as_toplevel(test.expect), false);
var input = as_toplevel(test.input);
var input_code = make_code(test.input);
var output = input.transform(cmp);
output.figure_out_scope();
output = make_code(output, false);
if (expect != output) {
log("!!! failed\n---INPUT---\n{input}\n---OUTPUT---\n{output}\n---EXPECTED---\n{expected}\n\n", {
input: input_code,
output: output,
expected: expect
});
}
}
var tests = parse_test(path.resolve(dir, file));
for (var i in tests) if (tests.hasOwnProperty(i)) {
test_case(tests[i]);
}
}
files.forEach(function(file){
test_file(file);
});
}
function parse_test(file) {
var script = fs.readFileSync(file, "utf8");
var ast = U.parse(script, {
filename: file
});
var tests = {};
var tw = new U.TreeWalker(function(node, descend){
if (node instanceof U.AST_LabeledStatement
&& tw.parent() instanceof U.AST_Toplevel) {
var name = node.label.name;
tests[name] = get_one_test(name, node.body);
return true;
}
if (!(node instanceof U.AST_Toplevel)) croak(node);
});
ast.walk(tw);
return tests;
function croak(node) {
throw new Error(tmpl("Can't understand test file {file} [{line},{col}]\n{code}", {
file: file,
line: node.start.line,
col: node.start.col,
code: make_code(node, false)
}));
}
function get_one_test(name, block) {
var test = { name: name, options: {} };
var tw = new U.TreeWalker(function(node, descend){
if (node instanceof U.AST_Assign) {
if (!(node.left instanceof U.AST_SymbolRef)) {
croak(node);
}
var name = node.left.name;
test[name] = evaluate(node.right);
return true;
}
if (node instanceof U.AST_LabeledStatement) {
assert.ok(
node.label.name == "input" || node.label.name == "expect",
tmpl("Unsupported label {name} [{line},{col}]", {
name: node.label.name,
line: node.label.start.line,
col: node.label.start.col
})
);
var stat = node.body;
if (stat instanceof U.AST_BlockStatement) {
if (stat.body.length == 1) stat = stat.body[0];
else if (stat.body.length == 0) stat = new U.AST_EmptyStatement();
}
test[node.label.name] = stat;
return true;
}
});
block.walk(tw);
return test;
};
}
function make_code(ast, beautify) {
if (arguments.length == 1) beautify = true;
var stream = U.OutputStream({ beautify: beautify });
ast.print(stream);
return stream.get();
}
function evaluate(code) {
if (code instanceof U.AST_Node)
code = make_code(code);
return new Function("return(" + code + ")")();
}

View File

@@ -1,403 +0,0 @@
#! /usr/bin/env node
var parseJS = require("../lib/parse-js");
var sys = require("sys");
// write debug in a very straightforward manner
var debug = function(){
sys.log(Array.prototype.slice.call(arguments).join(', '));
};
ParserTestSuite(function(i, input, desc){
try {
parseJS.parse(input);
debug("ok " + i + ": " + desc);
} catch(e){
debug("FAIL " + i + " " + desc + " (" + e + ")");
}
});
function ParserTestSuite(callback){
var inps = [
["var abc;", "Regular variable statement w/o assignment"],
["var abc = 5;", "Regular variable statement with assignment"],
["/* */;", "Multiline comment"],
['/** **/;', 'Double star multiline comment'],
["var f = function(){;};", "Function expression in var assignment"],
['hi; // moo\n;', 'single line comment'],
['var varwithfunction;', 'Dont match keywords as substrings'], // difference between `var withsomevar` and `"str"` (local search and lits)
['a + b;', 'addition'],
["'a';", 'single string literal'],
["'a\\n';", 'single string literal with escaped return'],
['"a";', 'double string literal'],
['"a\\n";', 'double string literal with escaped return'],
['"var";', 'string is a keyword'],
['"variable";', 'string starts with a keyword'],
['"somevariable";', 'string contains a keyword'],
['"somevar";', 'string ends with a keyword'],
['500;', 'int literal'],
['500.;', 'float literal w/o decimals'],
['500.432;', 'float literal with decimals'],
['.432432;', 'float literal w/o int'],
['(a,b,c);', 'parens and comma'],
['[1,2,abc];', 'array literal'],
['var o = {a:1};', 'object literal unquoted key'],
['var o = {"b":2};', 'object literal quoted key'], // opening curly may not be at the start of a statement...
['var o = {c:c};', 'object literal keyname is identifier'],
['var o = {a:1,"b":2,c:c};', 'object literal combinations'],
['var x;\nvar y;', 'two lines'],
['var x;\nfunction n(){; }', 'function def'],
['var x;\nfunction n(abc){; }', 'function def with arg'],
['var x;\nfunction n(abc, def){ ;}', 'function def with args'],
['function n(){ "hello"; }', 'function def with body'],
['/a/;', 'regex literal'],
['/a/b;', 'regex literal with flag'],
['/a/ / /b/;', 'regex div regex'],
['a/b/c;', 'triple division looks like regex'],
['+function(){/regex/;};', 'regex at start of function body'],
// http://code.google.com/p/es-lab/source/browse/trunk/tests/parser/parsertests.js?r=86
// http://code.google.com/p/es-lab/source/browse/trunk/tests/parser/parsertests.js?r=430
// first tests for the lexer, should also parse as program (when you append a semi)
// comments
['//foo!@#^&$1234\nbar;', 'single line comment'],
['/* abcd!@#@$* { } && null*/;', 'single line multi line comment'],
['/*foo\nbar*/;','multi line comment'],
['/*x*x*/;','multi line comment with *'],
['/**/;','empty comment'],
// identifiers
["x;",'1 identifier'],
["_x;",'2 identifier'],
["xyz;",'3 identifier'],
["$x;",'4 identifier'],
["x$;",'5 identifier'],
["_;",'6 identifier'],
["x5;",'7 identifier'],
["x_y;",'8 identifier'],
["x+5;",'9 identifier'],
["xyz123;",'10 identifier'],
["x1y1z1;",'11 identifier'],
["foo\\u00D8bar;",'12 identifier unicode escape'],
//["foo<6F>bar;",'13 identifier unicode embedded (might fail)'],
// numbers
["5;", '1 number'],
["5.5;", '2 number'],
["0;", '3 number'],
["0.0;", '4 number'],
["0.001;", '5 number'],
["1.e2;", '6 number'],
["1.e-2;", '7 number'],
["1.E2;", '8 number'],
["1.E-2;", '9 number'],
[".5;", '10 number'],
[".5e3;", '11 number'],
[".5e-3;", '12 number'],
["0.5e3;", '13 number'],
["55;", '14 number'],
["123;", '15 number'],
["55.55;", '16 number'],
["55.55e10;", '17 number'],
["123.456;", '18 number'],
["1+e;", '20 number'],
["0x01;", '22 number'],
["0XCAFE;", '23 number'],
["0x12345678;", '24 number'],
["0x1234ABCD;", '25 number'],
["0x0001;", '26 number'],
// strings
["\"foo\";", '1 string'],
["\'foo\';", '2 string'],
["\"x\";", '3 string'],
["\'\';", '4 string'],
["\"foo\\tbar\";", '5 string'],
["\"!@#$%^&*()_+{}[]\";", '6 string'],
["\"/*test*/\";", '7 string'],
["\"//test\";", '8 string'],
["\"\\\\\";", '9 string'],
["\"\\u0001\";", '10 string'],
["\"\\uFEFF\";", '11 string'],
["\"\\u10002\";", '12 string'],
["\"\\x55\";", '13 string'],
["\"\\x55a\";", '14 string'],
["\"a\\\\nb\";", '15 string'],
['";"', '16 string: semi in a string'],
['"a\\\nb";', '17 string: line terminator escape'],
// literals
["null;", "null"],
["true;", "true"],
["false;", "false"],
// regex
["/a/;", "1 regex"],
["/abc/;", "2 regex"],
["/abc[a-z]*def/g;", "3 regex"],
["/\\b/;", "4 regex"],
["/[a-zA-Z]/;", "5 regex"],
// program tests (for as far as they havent been covered above)
// regexp
["/foo(.*)/g;", "another regexp"],
// arrays
["[];", "1 array"],
["[ ];", "2 array"],
["[1];", "3 array"],
["[1,2];", "4 array"],
["[1,2,,];", "5 array"],
["[1,2,3];", "6 array"],
["[1,2,3,,,];", "7 array"],
// objects
["{};", "1 object"],
["({x:5});", "2 object"],
["({x:5,y:6});", "3 object"],
["({x:5,});", "4 object"],
["({if:5});", "5 object"],
["({ get x() {42;} });", "6 object"],
["({ set y(a) {1;} });", "7 object"],
// member expression
["o.m;", "1 member expression"],
["o['m'];", "2 member expression"],
["o['n']['m'];", "3 member expression"],
["o.n.m;", "4 member expression"],
["o.if;", "5 member expression"],
// call and invoke expressions
["f();", "1 call/invoke expression"],
["f(x);", "2 call/invoke expression"],
["f(x,y);", "3 call/invoke expression"],
["o.m();", "4 call/invoke expression"],
["o['m'];", "5 call/invoke expression"],
["o.m(x);", "6 call/invoke expression"],
["o['m'](x);", "7 call/invoke expression"],
["o.m(x,y);", "8 call/invoke expression"],
["o['m'](x,y);", "9 call/invoke expression"],
["f(x)(y);", "10 call/invoke expression"],
["f().x;", "11 call/invoke expression"],
// eval
["eval('x');", "1 eval"],
["(eval)('x');", "2 eval"],
["(1,eval)('x');", "3 eval"],
["eval(x,y);", "4 eval"],
// new expression
["new f();", "1 new expression"],
["new o;", "2 new expression"],
["new o.m;", "3 new expression"],
["new o.m(x);", "4 new expression"],
["new o.m(x,y);", "5 new expression"],
// prefix/postfix
["++x;", "1 pre/postfix"],
["x++;", "2 pre/postfix"],
["--x;", "3 pre/postfix"],
["x--;", "4 pre/postfix"],
["x ++;", "5 pre/postfix"],
["x /* comment */ ++;", "6 pre/postfix"],
["++ /* comment */ x;", "7 pre/postfix"],
// unary operators
["delete x;", "1 unary operator"],
["void x;", "2 unary operator"],
["+ x;", "3 unary operator"],
["-x;", "4 unary operator"],
["~x;", "5 unary operator"],
["!x;", "6 unary operator"],
// meh
["new Date++;", "new date ++"],
["+x++;", " + x ++"],
// expression expressions
["1 * 2;", "1 expression expressions"],
["1 / 2;", "2 expression expressions"],
["1 % 2;", "3 expression expressions"],
["1 + 2;", "4 expression expressions"],
["1 - 2;", "5 expression expressions"],
["1 << 2;", "6 expression expressions"],
["1 >>> 2;", "7 expression expressions"],
["1 >> 2;", "8 expression expressions"],
["1 * 2 + 3;", "9 expression expressions"],
["(1+2)*3;", "10 expression expressions"],
["1*(2+3);", "11 expression expressions"],
["x<y;", "12 expression expressions"],
["x>y;", "13 expression expressions"],
["x<=y;", "14 expression expressions"],
["x>=y;", "15 expression expressions"],
["x instanceof y;", "16 expression expressions"],
["x in y;", "17 expression expressions"],
["x&y;", "18 expression expressions"],
["x^y;", "19 expression expressions"],
["x|y;", "20 expression expressions"],
["x+y<z;", "21 expression expressions"],
["x<y+z;", "22 expression expressions"],
["x+y+z;", "23 expression expressions"],
["x+y<z;", "24 expression expressions"],
["x<y+z;", "25 expression expressions"],
["x&y|z;", "26 expression expressions"],
["x&&y;", "27 expression expressions"],
["x||y;", "28 expression expressions"],
["x&&y||z;", "29 expression expressions"],
["x||y&&z;", "30 expression expressions"],
["x<y?z:w;", "31 expression expressions"],
// assignment
["x >>>= y;", "1 assignment"],
["x <<= y;", "2 assignment"],
["x = y;", "3 assignment"],
["x += y;", "4 assignment"],
["x /= y;", "5 assignment"],
// comma
["x, y;", "comma"],
// block
["{};", "1 block"],
["{x;};", "2 block"],
["{x;y;};", "3 block"],
// vars
["var x;", "1 var"],
["var x,y;", "2 var"],
["var x=1,y=2;", "3 var"],
["var x,y=2;", "4 var"],
// empty
[";", "1 empty"],
["\n;", "2 empty"],
// expression statement
["x;", "1 expression statement"],
["5;", "2 expression statement"],
["1+2;", "3 expression statement"],
// if
["if (c) x; else y;", "1 if statement"],
["if (c) x;", "2 if statement"],
["if (c) {} else {};", "3 if statement"],
["if (c1) if (c2) s1; else s2;", "4 if statement"],
// while
["do s; while (e);", "1 while statement"],
["do { s; } while (e);", "2 while statement"],
["while (e) s;", "3 while statement"],
["while (e) { s; };", "4 while statement"],
// for
["for (;;) ;", "1 for statement"],
["for (;c;x++) x;", "2 for statement"],
["for (i;i<len;++i){};", "3 for statement"],
["for (var i=0;i<len;++i) {};", "4 for statement"],
["for (var i=0,j=0;;){};", "5 for statement"],
//["for (x in b; c; u) {};", "6 for statement"],
["for ((x in b); c; u) {};", "7 for statement"],
["for (x in a);", "8 for statement"],
["for (var x in a){};", "9 for statement"],
["for (var x=5 in a) {};", "10 for statement"],
["for (var x = a in b in c) {};", "11 for statement"],
["for (var x=function(){a+b;}; a<b; ++i) some;", "11 for statement, testing for parsingForHeader reset with the function"],
["for (var x=function(){for (x=0; x<15; ++x) alert(foo); }; a<b; ++i) some;", "11 for statement, testing for parsingForHeader reset with the function"],
// flow statements
["while(1){ continue; }", "1 flow statement"],
["label: while(1){ continue label; }", "2 flow statement"],
["while(1){ break; }", "3 flow statement"],
["somewhere: while(1){ break somewhere; }", "4 flow statement"],
["while(1){ continue /* comment */ ; }", "5 flow statement"],
["while(1){ continue \n; }", "6 flow statement"],
["(function(){ return; })()", "7 flow statement"],
["(function(){ return 0; })()", "8 flow statement"],
["(function(){ return 0 + \n 1; })()", "9 flow statement"],
// with
["with (e) s;", "with statement"],
// switch
["switch (e) { case x: s; };", "1 switch statement"],
["switch (e) { case x: s1;s2; default: s3; case y: s4; };", "2 switch statement"],
["switch (e) { default: s1; case x: s2; case y: s3; };", "3 switch statement"],
["switch (e) { default: s; };", "4 switch statement"],
["switch (e) { case x: s1; case y: s2; };", "5 switch statement"],
// labels
["foo : x;", " flow statement"],
// throw
["throw x;", "1 throw statement"],
["throw x\n;", "2 throw statement"],
// try catch finally
["try { s1; } catch (e) { s2; };", "1 trycatchfinally statement"],
["try { s1; } finally { s2; };", "2 trycatchfinally statement"],
["try { s1; } catch (e) { s2; } finally { s3; };", "3 trycatchfinally statement"],
// debugger
["debugger;", "debuger statement"],
// function decl
["function f(x) { e; return x; };", "1 function declaration"],
["function f() { x; y; };", "2 function declaration"],
["function f(x,y) { var z; return x; };", "3 function declaration"],
// function exp
["(function f(x) { return x; });", "1 function expression"],
["(function empty() {;});", "2 function expression"],
["(function empty() {;});", "3 function expression"],
["(function (x) {; });", "4 function expression"],
// program
["var x; function f(){;}; null;", "1 program"],
[";;", "2 program"],
["{ x; y; z; }", "3 program"],
["function f(){ function g(){;}};", "4 program"],
["x;\n/*foo*/\n ;", "5 program"],
// asi
["foo: while(1){ continue \n foo; }", "1 asi"],
["foo: while(1){ break \n foo; }", "2 asi"],
["(function(){ return\nfoo; })()", "3 asi"],
["var x; { 1 \n 2 } 3", "4 asi"],
["ab /* hi */\ncd", "5 asi"],
["ab/*\n*/cd", "6 asi (multi line multilinecomment counts as eol)"],
["foo: while(1){ continue /* wtf \n busta */ foo; }", "7 asi illegal with multi line comment"],
["function f() { s }", "8 asi"],
["function f() { return }", "9 asi"],
// use strict
// XXX: some of these should actually fail?
// no support for "use strict" yet...
['"use strict"; \'bla\'\n; foo;', "1 directive"],
['(function() { "use strict"; \'bla\';\n foo; });', "2 directive"],
['"use\\n strict";', "3 directive"],
['foo; "use strict";', "4 directive"],
// tests from http://es5conform.codeplex.com/
['"use strict"; var o = { eval: 42};', "8.7.2-3-1-s: the use of eval as property name is allowed"],
['({foo:0,foo:1});', 'Duplicate property name allowed in not strict mode'],
['function foo(a,a){}', 'Duplicate parameter name allowed in not strict mode'],
['(function foo(eval){})', 'Eval allowed as parameter name in non strict mode'],
['(function foo(arguments){})', 'Arguments allowed as parameter name in non strict mode'],
// empty programs
['', '1 Empty program'],
['// test', '2 Empty program'],
['//test\n', '3 Empty program'],
['\n// test', '4 Empty program'],
['\n// test\n', '5 Empty program'],
['/* */', '6 Empty program'],
['/*\ns,fd\n*/', '7 Empty program'],
['/*\ns,fd\n*/\n', '8 Empty program'],
[' ', '9 Empty program'],
[' /*\nsmeh*/ \n ', '10 Empty program'],
// trailing whitespace
['a ', '1 Trailing whitespace'],
['a /* something */', '2 Trailing whitespace'],
['a\n // hah', '3 Trailing whitespace'],
['/abc/de//f', '4 Trailing whitespace'],
['/abc/de/*f*/\n ', '5 Trailing whitespace'],
// things the parser tripped over at one point or the other (prevents regression bugs)
['for (x;function(){ a\nb };z) x;', 'for header with function body forcing ASI'],
['c=function(){return;return};', 'resetting noAsi after literal'],
['d\nd()', 'asi exception causing token overflow'],
['for(;;){x=function(){}}', 'function expression in a for header'],
['for(var k;;){}', 'parser failing due to ASI accepting the incorrect "for" rule'],
['({get foo(){ }})', 'getter with empty function body'],
['\nreturnr', 'eol causes return statement to ignore local search requirement'],
[' / /', '1 whitespace before regex causes regex to fail?'],
['/ // / /', '2 whitespace before regex causes regex to fail?'],
['/ / / / /', '3 whitespace before regex causes regex to fail?'],
['\n\t// Used for trimming whitespace\n\ttrimLeft = /^\\s+/;\n\ttrimRight = /\\s+$/;\t\n','turned out this didnt crash (the test below did), but whatever.'],
['/[\\/]/;', 'escaped forward slash inside class group (would choke on fwd slash)'],
['/[/]/;', 'also broke but is valid in es5 (not es3)'],
['({get:5});','get property name thats not a getter'],
['({set:5});','set property name thats not a setter'],
['l !== "px" && (d.style(h, c, (k || 1) + l), j = (k || 1) / f.cur() * j, d.style(h, c, j + l)), i[1] && (k = (i[1] === "-=" ? -1 : 1) * k + j), f.custom(j, k, l)', 'this choked regex/div at some point'],
['(/\'/g, \'\\\\\\\'\') + "\'";', 'the sequence of escaped characters confused the tokenizer'],
['if (true) /=a/.test("a");', 'regexp starting with "=" in not obvious context (not implied by preceding token)']
];
for (var i=0; i<inps.length; ++i) {
callback(i, inps[i][0], inps[i][1]);
};
};

View File

@@ -1 +0,0 @@
[],Array(1),[1,2,3]

View File

@@ -1 +0,0 @@
(function(){var a=function(){};return new a(1,2,3,4)})()

View File

@@ -1 +0,0 @@
(function(){function a(){}return new a(1,2,3,4)})()

View File

@@ -1 +0,0 @@
(function(){function a(){}(function(){return new a(1,2,3)})()})()

View File

@@ -1 +0,0 @@
a=1,b=a,c=1,d=b,e=d,longname=2;if(longname+1){x=3;if(x)var z=7}z=1,y=1,x=1,g+=1,h=g,++i,j=i,i++,j=i+17

View File

@@ -1 +0,0 @@
var a=a+"a"+"b"+1+c,b=a+"c"+"ds"+123+c,c=a+"c"+123+d+"ds"+c

View File

@@ -1 +0,0 @@
var a=13,b=1/3

View File

@@ -1 +0,0 @@
function bar(){return--x}function foo(){while(bar());}function mak(){for(;;);}var x=5

View File

@@ -1 +0,0 @@
a=func(),b=z;for(a++;i<10;i++)alert(i);var z=1;g=2;for(;i<10;i++)alert(i);var a=2;for(var i=1;i<10;i++)alert(i)

View File

@@ -1 +0,0 @@
var a=1;a==1?a=2:a=17

Some files were not shown because too many files have changed in this diff Show More