started support for generating source maps (WIP)
authorMihai Bazon <mihai@bazon.net>
Wed, 29 Aug 2012 16:39:19 +0000 (19:39 +0300)
committerMihai Bazon <mihai@bazon.net>
Wed, 29 Aug 2012 16:39:19 +0000 (19:39 +0300)
plugged in @fitzgen's source-map library

lib/output.js
lib/sourcemap.js [new file with mode: 0644]
tmp/test-maps.js [new file with mode: 0755]
tools/node.js

index 5f2f46d..b43d817 100644 (file)
@@ -53,7 +53,8 @@ function OutputStream(options) {
         width         : 80,
         max_line_len  : 32000,
         ie_proof      : true,
-        beautify      : true
+        beautify      : true,
+        source_map    : null
     });
 
     var indentation = 0;
@@ -152,12 +153,12 @@ function OutputStream(options) {
             might_need_space = false;
             maybe_newline();
         }
-        var a = str.split(/\r?\n/), n = a.length;
+        var a = str.split(/\r?\n/), n = a.length - 1;
         current_line += n;
-        if (n == 1) {
-            current_col += a[n - 1].length;
+        if (n == 0) {
+            current_col += a[n].length;
         } else {
-            current_col = a[n - 1].length;
+            current_col = a[n].length;
         }
         current_pos += str.length;
         last = str;
@@ -243,9 +244,22 @@ function OutputStream(options) {
         if (options.space_colon) space();
     };
 
+    var add_mapping = options.source_map ? function(token, name) {
+        options.source_map.add(
+            current_line, current_col,
+            token.line, token.col,
+            (!name && token.type == "name") ? token.value : name
+        );
+    } : noop;
+
+    function get() {
+        return OUTPUT;
+    };
+
     var stack = [];
     return {
-        get             : function() { return OUTPUT },
+        get             : get,
+        toString        : get,
         indent          : indent,
         newline         : newline,
         print           : print,
@@ -261,6 +275,7 @@ function OutputStream(options) {
         with_block      : with_block,
         with_parens     : with_parens,
         with_square     : with_square,
+        add_mapping     : add_mapping,
         option          : function(opt) { return options[opt] },
         line            : function() { return current_line },
         col             : function() { return current_col },
@@ -287,9 +302,11 @@ function OutputStream(options) {
             stream.push_node(self);
             if (self.needs_parens(stream)) {
                 stream.with_parens(function(){
+                    self.add_source_map(stream);
                     generator(self, stream);
                 });
             } else {
+                self.add_source_map(stream);
                 generator(self, stream);
             }
             stream.pop_node();
@@ -868,11 +885,7 @@ function OutputStream(options) {
     });
     DEFPRINT(AST_SymbolRef, function(self, output){
         var def = self.symbol;
-        if (def) {
-            def.print(output);
-        } else {
-            output.print_name(self.name);
-        }
+        output.print_name(def ? def.mangled_name || def.name : self.name);
     });
     DEFPRINT(AST_This, function(self, output){
         output.print("this");
@@ -973,4 +986,32 @@ function OutputStream(options) {
         });
     };
 
+    /* -----[ source map generators ]----- */
+
+    function DEFMAP(nodetype, generator) {
+        nodetype.DEFMETHOD("add_source_map", function(stream){
+            generator(this, stream);
+        });
+    };
+
+    // We could easily add info for ALL nodes, but it seems to me that
+    // would be quite wasteful, hence this noop in the base class.
+    DEFMAP(AST_Node, noop);
+
+    function basic_sourcemap_gen(self, output) {
+        output.add_mapping(self.start);
+    };
+
+    // XXX: I'm not exactly sure if we need it for all of these nodes,
+    // or if we should add even more.
+
+    DEFMAP(AST_Directive, basic_sourcemap_gen);
+    DEFMAP(AST_Debugger, basic_sourcemap_gen);
+    DEFMAP(AST_Symbol, basic_sourcemap_gen);
+    DEFMAP(AST_Jump, basic_sourcemap_gen);
+    DEFMAP(AST_PropAccess, basic_sourcemap_gen);
+    DEFMAP(AST_ObjectProperty, function(self, output){
+        output.add_mapping(self.start, self.key);
+    });
+
 })();
diff --git a/lib/sourcemap.js b/lib/sourcemap.js
new file mode 100644 (file)
index 0000000..6cf63d1
--- /dev/null
@@ -0,0 +1,77 @@
+/***********************************************************************
+
+  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.
+
+ ***********************************************************************/
+
+// a small wrapper around fitzgen's source-map library
+function SourceMap(options) {
+    options = defaults(options, {
+        file : null,
+        root : null
+    });
+    var generator = new MOZ_SourceMap.SourceMapGenerator({
+        file       : options.file,
+        sourceRoot : options.root
+    });
+    var current_source = null;
+    function add(gen_line, gen_col, orig_line, orig_col, name) {
+        // AST_Node.warn("Mapping in {file}: {orig_line},{orig_col} → {gen_line},{gen_col} ({name})", {
+        //     orig_line : orig_line,
+        //     orig_col  : orig_col,
+        //     gen_line  : gen_line,
+        //     gen_col   : gen_col,
+        //     file      : current_source,
+        //     name      : name
+        // });
+        generator.addMapping({
+            generated : { line: gen_line + 1, column: gen_col },
+            original  : { line: orig_line + 1, column: orig_col },
+            source    : current_source,
+            name      : name
+        });
+    };
+    return {
+        add        : add,
+        set_source : function(filename) { current_source = filename },
+        get        : function() { return generator },
+        toString   : function() { return generator.toString() }
+    }
+};
diff --git a/tmp/test-maps.js b/tmp/test-maps.js
new file mode 100755 (executable)
index 0000000..c89d105
--- /dev/null
@@ -0,0 +1,39 @@
+#! /usr/bin/env node
+
+var sys = require("util");
+var fs = require("fs");
+
+var UglifyJS = require("../tools/node");
+
+var files = process.argv.slice(2);
+var map = UglifyJS.SourceMap();
+var output = UglifyJS.OutputStream({
+    beautify   : false,
+    source_map : map
+});
+
+function do_file(file) {
+    var code = fs.readFileSync(file, "utf8");
+
+    // parse
+    var ast = UglifyJS.parse(code);
+
+    // mangle
+    ast.figure_out_scope();
+    ast.mangle_names();
+
+    // compress
+    var compressor = UglifyJS.Compressor();
+    ast.squeeze(compressor);
+
+    // generate source into the output stream
+    // first reset the current file name in the source map.
+    map.set_source(file);
+    ast.print(output);
+};
+
+files.forEach(do_file);
+
+fs.writeFileSync("/tmp/source-map.json", map, "utf8");
+
+sys.print(output);
index d61b44a..be83d17 100644 (file)
@@ -4,8 +4,10 @@ var sys = require("util");
 var path = require("path");
 
 var UglifyJS = vm.createContext({
-    sys     : sys,
-    console : console
+    sys           : sys,
+    console       : console,
+
+    MOZ_SourceMap : require("source-map")
 });
 
 function load_global(file) {
@@ -27,6 +29,7 @@ load_global("../lib/parse.js");
 load_global("../lib/scope.js");
 load_global("../lib/output.js");
 load_global("../lib/compress.js");
+load_global("../lib/sourcemap.js");
 
 UglifyJS.AST_Node.warn_function = function(txt) {
     sys.debug(txt);