From 52bcca288fe560670584b24cb170f09c250514e6 Mon Sep 17 00:00:00 2001 From: Mihai Bazon Date: Wed, 29 Aug 2012 19:39:19 +0300 Subject: [PATCH] started support for generating source maps (WIP) plugged in @fitzgen's source-map library --- lib/output.js | 63 ++++++++++++++++++++++++++++++++------- lib/sourcemap.js | 77 ++++++++++++++++++++++++++++++++++++++++++++++++ tmp/test-maps.js | 39 ++++++++++++++++++++++++ tools/node.js | 7 +++-- 4 files changed, 173 insertions(+), 13 deletions(-) create mode 100644 lib/sourcemap.js create mode 100755 tmp/test-maps.js diff --git a/lib/output.js b/lib/output.js index 5f2f46d1..b43d817e 100644 --- a/lib/output.js +++ b/lib/output.js @@ -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 index 00000000..6cf63d1b --- /dev/null +++ b/lib/sourcemap.js @@ -0,0 +1,77 @@ +/*********************************************************************** + + A JavaScript tokenizer / parser / beautifier / compressor. + https://github.com/mishoo/UglifyJS2 + + -------------------------------- (C) --------------------------------- + + Author: Mihai Bazon + + http://mihai.bazon.net/blog + + Distributed under the BSD license: + + Copyright 2012 (c) Mihai Bazon + + 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 index 00000000..c89d105e --- /dev/null +++ b/tmp/test-maps.js @@ -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); diff --git a/tools/node.js b/tools/node.js index d61b44a7..be83d171 100644 --- a/tools/node.js +++ b/tools/node.js @@ -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); -- 2.34.1