function OutputStream(options) {
+ var readonly = !options;
options = defaults(options, {
ascii_only : false,
beautify : false,
var might_need_space = false;
var might_need_semicolon = false;
var might_add_newline = 0;
+ var need_newline_indented = false;
var last = "";
var mapping_token, mapping_name, mappings = options.source_map && [];
function print(str) {
str = String(str);
var ch = str.charAt(0);
+ if (need_newline_indented && ch) {
+ need_newline_indented = false;
+ if (ch != "\n") {
+ print("\n");
+ indent();
+ }
+ }
var prev = last.charAt(last.length - 1);
if (might_need_semicolon) {
might_need_semicolon = false;
return OUTPUT;
};
+ function prepend_comments(node) {
+ var self = this;
+ var start = node.start;
+ if (!(start.comments_before && start.comments_before._dumped === self)) {
+ var comments = start.comments_before;
+ if (!comments) {
+ comments = start.comments_before = [];
+ }
+ comments._dumped = self;
+
+ if (node instanceof AST_Exit && node.value) {
+ var tw = new TreeWalker(function(node) {
+ var parent = tw.parent();
+ if (parent instanceof AST_Exit
+ || parent instanceof AST_Binary && parent.left === node
+ || parent.TYPE == "Call" && parent.expression === node
+ || parent instanceof AST_Conditional && parent.condition === node
+ || parent instanceof AST_Dot && parent.expression === node
+ || parent instanceof AST_Sequence && parent.expressions[0] === node
+ || parent instanceof AST_Sub && parent.expression === node
+ || parent instanceof AST_UnaryPostfix) {
+ var text = node.start.comments_before;
+ if (text && text._dumped !== self) {
+ text._dumped = self;
+ comments = comments.concat(text);
+ }
+ } else {
+ return true;
+ }
+ });
+ tw.push(node);
+ node.value.walk(tw);
+ }
+
+ if (current_pos == 0) {
+ if (comments.length > 0 && options.shebang && comments[0].type == "comment5") {
+ print("#!" + comments.shift().value + "\n");
+ indent();
+ }
+ var preamble = options.preamble;
+ if (preamble) {
+ print(preamble.replace(/\r\n?|[\n\u2028\u2029]|\s*$/g, "\n"));
+ }
+ }
+
+ comments = comments.filter(comment_filter, node);
+ if (comments.length == 0) return;
+ var last_nlb = /(^|\n) *$/.test(OUTPUT);
+ comments.forEach(function(c, i) {
+ if (!last_nlb) {
+ if (c.nlb) {
+ print("\n");
+ indent();
+ last_nlb = true;
+ } else if (i > 0) {
+ space();
+ }
+ }
+ if (/comment[134]/.test(c.type)) {
+ print("//" + c.value + "\n");
+ indent();
+ last_nlb = true;
+ } else if (c.type == "comment2") {
+ print("/*" + c.value + "*/");
+ last_nlb = false;
+ }
+ });
+ if (!last_nlb) {
+ if (start.nlb) {
+ print("\n");
+ indent();
+ } else {
+ space();
+ }
+ }
+ }
+ }
+
+ function append_comments(node, tail) {
+ var self = this;
+ var token = node.end;
+ if (!token) return;
+ var comments = token[tail ? "comments_before" : "comments_after"];
+ if (comments && comments._dumped !== self) {
+ comments._dumped = self;
+ comments.filter(comment_filter, node).forEach(function(c, i) {
+ if (need_newline_indented || c.nlb) {
+ print("\n");
+ indent();
+ need_newline_indented = false;
+ } else if (i > 0 || !tail) {
+ space();
+ }
+ if (/comment[134]/.test(c.type)) {
+ print("//" + c.value);
+ need_newline_indented = true;
+ } else if (c.type == "comment2") {
+ print("/*" + c.value + "*/");
+ }
+ });
+ }
+ }
+
var stack = [];
return {
get : get,
with_square : with_square,
add_mapping : add_mapping,
option : function(opt) { return options[opt] },
- comment_filter : comment_filter,
+ prepend_comments: readonly ? noop : prepend_comments,
+ append_comments : readonly ? noop : append_comments,
line : function() { return current_line },
col : function() { return current_col },
pos : function() { return current_pos },
use_asm = active_scope;
}
function doit() {
- self.add_comments(stream);
+ stream.prepend_comments(self);
self.add_source_map(stream);
generator(self, stream);
+ stream.append_comments(self);
}
stream.push_node(self);
if (force_parens || self.needs_parens(stream)) {
AST_Node.DEFMETHOD("print_to_string", function(options){
var s = OutputStream(options);
- if (!options) s._readonly = true;
this.print(s);
return s.get();
});
- /* -----[ comments ]----- */
-
- AST_Node.DEFMETHOD("add_comments", function(output){
- if (output._readonly) return;
- var self = this;
- var start = self.start;
- if (start && !start._comments_dumped) {
- start._comments_dumped = true;
- var comments = start.comments_before || [];
-
- // XXX: ugly fix for https://github.com/mishoo/UglifyJS2/issues/112
- // and https://github.com/mishoo/UglifyJS2/issues/372
- if (self instanceof AST_Exit && self.value) {
- self.value.walk(new TreeWalker(function(node){
- if (node.start && node.start.comments_before) {
- comments = comments.concat(node.start.comments_before);
- node.start.comments_before = [];
- }
- if (node instanceof AST_Function ||
- node instanceof AST_Array ||
- node instanceof AST_Object)
- {
- return true; // don't go inside.
- }
- }));
- }
-
- if (output.pos() == 0) {
- if (comments.length > 0 && output.option("shebang") && comments[0].type == "comment5") {
- output.print("#!" + comments.shift().value + "\n");
- output.indent();
- }
- var preamble = output.option("preamble");
- if (preamble) {
- output.print(preamble.replace(/\r\n?|[\n\u2028\u2029]|\s*$/g, "\n"));
- }
- }
-
- comments = comments.filter(output.comment_filter, self);
-
- // Keep single line comments after nlb, after nlb
- if (!output.option("beautify") && comments.length > 0 &&
- /comment[134]/.test(comments[0].type) &&
- output.col() !== 0 && comments[0].nlb)
- {
- output.print("\n");
- }
-
- comments.forEach(function(c){
- if (/comment[134]/.test(c.type)) {
- output.print("//" + c.value + "\n");
- output.indent();
- }
- else if (c.type == "comment2") {
- output.print("/*" + c.value + "*/");
- if (start.nlb) {
- output.print("\n");
- output.indent();
- } else {
- output.space();
- }
- }
- });
- }
- });
-
/* -----[ PARENTHESES ]----- */
function PARENS(nodetype, func) {
self.body.print(output);
output.semicolon();
});
- function print_bracketed(body, output, allow_directives) {
- if (body.length > 0) output.with_block(function(){
- display_body(body, false, output, allow_directives);
- });
- else output.print("{}");
+ function print_bracketed(self, output, allow_directives) {
+ if (self.body.length > 0) {
+ output.with_block(function() {
+ display_body(self.body, false, output, allow_directives);
+ });
+ } else {
+ output.print("{");
+ output.with_indent(output.next_indent(), function() {
+ output.append_comments(self, true);
+ });
+ output.print("}");
+ }
};
DEFPRINT(AST_BlockStatement, function(self, output){
- print_bracketed(self.body, output);
+ print_bracketed(self, output);
});
DEFPRINT(AST_EmptyStatement, function(self, output){
output.semicolon();
});
});
output.space();
- print_bracketed(self.body, output, true);
+ print_bracketed(self, output, true);
});
DEFPRINT(AST_Lambda, function(self, output){
self._do_print(output);
DEFPRINT(AST_Try, function(self, output){
output.print("try");
output.space();
- print_bracketed(self.body, output);
+ print_bracketed(self, output);
if (self.bcatch) {
output.space();
self.bcatch.print(output);
self.argname.print(output);
});
output.space();
- print_bracketed(self.body, output);
+ print_bracketed(self, output);
});
DEFPRINT(AST_Finally, function(self, output){
output.print("finally");
output.space();
- print_bracketed(self.body, output);
+ print_bracketed(self, output);
});
/* -----[ var/const ]----- */
}, fail, tests[i]);
}
});
+
+ it("Should handle comment within return correctly", function() {
+ var result = uglify.minify([
+ "function unequal(x, y) {",
+ " return (",
+ " // Either one",
+ " x < y",
+ " ||",
+ " y < x",
+ " );",
+ "}",
+ ].join("\n"), {
+ compress: false,
+ mangle: false,
+ output: {
+ beautify: true,
+ comments: "all",
+ },
+ });
+ if (result.error) throw result.error;
+ assert.strictEqual(result.code, [
+ "function unequal(x, y) {",
+ " // Either one",
+ " return x < y || y < x;",
+ "}",
+ ].join("\n"));
+ });
+
+ it("Should handle comment folded into return correctly", function() {
+ var result = uglify.minify([
+ "function f() {",
+ " /* boo */ x();",
+ " return y();",
+ "}",
+ ].join("\n"), {
+ mangle: false,
+ output: {
+ beautify: true,
+ comments: "all",
+ },
+ });
+ if (result.error) throw result.error;
+ assert.strictEqual(result.code, [
+ "function f() {",
+ " /* boo */",
+ " return x(), y();",
+ "}",
+ ].join("\n"));
+ });
+
+ it("Should not drop comments after first OutputStream", function() {
+ var code = "/* boo */\nx();";
+ var ast = uglify.parse(code);
+ var out1 = uglify.OutputStream({
+ beautify: true,
+ comments: "all",
+ });
+ ast.print(out1);
+ var out2 = uglify.OutputStream({
+ beautify: true,
+ comments: "all",
+ });
+ ast.print(out2);
+ assert.strictEqual(out1.get(), code);
+ assert.strictEqual(out2.get(), out1.get());
+ });
+
+ it("Should retain trailing comments", function() {
+ var code = [
+ "if (foo /* lost comment */ && bar /* lost comment */) {",
+ " // this one is kept",
+ " {/* lost comment */}",
+ " !function() {",
+ " // lost comment",
+ " }();",
+ " function baz() {/* lost comment */}",
+ " // lost comment",
+ "}",
+ "// comments right before EOF are lost as well",
+ ].join("\n");
+ var result = uglify.minify(code, {
+ compress: false,
+ mangle: false,
+ output: {
+ beautify: true,
+ comments: "all",
+ },
+ });
+ if (result.error) throw result.error;
+ assert.strictEqual(result.code, code);
+ });
+
+ it("Should correctly preserve new lines around comments", function() {
+ var tests = [
+ [
+ "// foo",
+ "// bar",
+ "x();",
+ ].join("\n"),
+ [
+ "// foo",
+ "/* bar */",
+ "x();",
+ ].join("\n"),
+ [
+ "// foo",
+ "/* bar */ x();",
+ ].join("\n"),
+ [
+ "/* foo */",
+ "// bar",
+ "x();",
+ ].join("\n"),
+ [
+ "/* foo */ // bar",
+ "x();",
+ ].join("\n"),
+ [
+ "/* foo */",
+ "/* bar */",
+ "x();",
+ ].join("\n"),
+ [
+ "/* foo */",
+ "/* bar */ x();",
+ ].join("\n"),
+ [
+ "/* foo */ /* bar */",
+ "x();",
+ ].join("\n"),
+ "/* foo */ /* bar */ x();",
+ ].forEach(function(code) {
+ var result = uglify.minify(code, {
+ compress: false,
+ mangle: false,
+ output: {
+ beautify: true,
+ comments: "all",
+ },
+ });
+ if (result.error) throw result.error;
+ assert.strictEqual(result.code, code);
+ });
+ });
+
+ it("Should preserve new line before comment without beautify", function() {
+ var code = [
+ "function f(){",
+ "/* foo */bar()}",
+ ].join("\n");
+ var result = uglify.minify(code, {
+ compress: false,
+ mangle: false,
+ output: {
+ comments: "all",
+ },
+ });
+ if (result.error) throw result.error;
+ assert.strictEqual(result.code, code);
+ });
+
+ it("Should preserve comments around IIFE", function() {
+ var result = uglify.minify("/*a*/(/*b*/function(){/*c*/}/*d*/)/*e*/();", {
+ compress: false,
+ mangle: false,
+ output: {
+ comments: "all",
+ },
+ });
+ if (result.error) throw result.error;
+ assert.strictEqual(result.code, "/*a*/ /*b*/(function(){/*c*/}/*d*/ /*e*/)();");
+ });
});