UglifyJS/lib/output.js

1549 lines
51 KiB
JavaScript
Raw Normal View History

2012-08-22 18:28:59 +00:00
/***********************************************************************
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:
2012-08-27 08:01:27 +00:00
Copyright 2012 (c) Mihai Bazon <mihai.bazon@gmail.com>
2012-08-22 18:28:59 +00:00
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.
***********************************************************************/
2012-10-02 09:45:31 +00:00
"use strict";
2018-06-09 17:40:40 +00:00
var utils = require("./utils");
var repeat_string = utils.repeat_string;
var defaults = utils.defaults;
var noop = utils.noop;
var return_false = utils.return_false;
var return_true = utils.return_true;
var makePredicate = utils.makePredicate;
var all = utils.all;
var merge = utils.merge;
2018-06-13 10:50:22 +00:00
var AST = require("./ast");
var first_in_statement = AST.first_in_statement;
var TreeWalker = AST.TreeWalker;
2018-06-09 17:40:40 +00:00
var parse = require("./parse");
var is_surrogate_pair_head = parse.is_surrogate_pair_head;
var is_surrogate_pair_tail = parse.is_surrogate_pair_tail;
var is_identifier_char = parse.is_identifier_char;
var is_identifier_string = parse.is_identifier_string;
var PRECEDENCE = parse.PRECEDENCE;
var RESERVED_WORDS = parse.RESERVED_WORDS;
var EXPECT_DIRECTIVE = /^$|[;{][\s\n]*$/;
function is_some_comments(comment) {
// multiline comment
return comment.type == "comment2" && /@preserve|@license|@cc_on/i.test(comment.value);
}
2012-05-27 14:25:31 +00:00
function OutputStream(options) {
var readonly = !options;
2012-05-27 14:25:31 +00:00
options = defaults(options, {
ascii_only : false,
beautify : false,
2018-03-15 07:46:45 +00:00
braces : false,
comments : false,
unify CLI & API under `minify()` (#1811) - rename `screw_ie8` to `ie8` - rename `mangle.except` to `mangle.reserved` - rename `mangle.properties.ignore_quoted` to `mangle.properties.keep_quoted` - compact `sourceMap` options - more stringent verification on input `options` - toplevel shorthands - `ie8` - `keep_fnames` - `toplevel` - `warnings` - support arrays and unquoted string values on CLI - drop `fromString` from `minify()` - `minify()` no longer handles any `fs` operations - unify order of operations for `mangle_properties()` on CLI & API - `bin/uglifyjs` used to `mangle_properties()` before even `Compressor` - `minify()` used to `mangle_properties()` after `Compressor` but before `mangle_names()` - both will now do `Compressor`, `mangle_names()` then `mangle_properties()` - `options.parse` / `--parse` for parser options beyond `bare_returns` - add `mangle.properties.builtins` to disable built-in reserved list - disable with `--mangle-props builtins` on CLI - `warnings` now off by default - add `--warn` and `--verbose` on CLI - drop `--enclose` - drop `--export-all` - drop `--reserved-file` - use `--mangle reserved` instead - drop `--reserve-domprops` - enabled by default, disable with `--mangle-props domprops` - drop `--prefix` - use `--source-map base` instead - drop `--lint` - remove `bin/extract-props.js` - limit exposure of internal APIs - update documentations closes #96 closes #102 closes #136 closes #166 closes #243 closes #254 closes #261 closes #311 closes #700 closes #748 closes #912 closes #1072 closes #1366 fixes #101 fixes #123 fixes #124 fixes #263 fixes #379 fixes #419 fixes #423 fixes #461 fixes #465 fixes #576 fixes #737 fixes #772 fixes #958 fixes #1036 fixes #1142 fixes #1175 fixes #1220 fixes #1223 fixes #1280 fixes #1359 fixes #1368
2017-04-15 15:50:50 +00:00
ie8 : false,
indent_level : 4,
indent_start : 0,
inline_script : true,
keep_quoted_props: false,
max_line_len : false,
preamble : null,
preserve_line : false,
quote_keys : false,
quote_style : 0,
semicolons : true,
shebang : true,
source_map : null,
webkit : false,
width : 80,
wrap_iife : false,
}, true);
2012-05-27 14:25:31 +00:00
// Convert comment option to RegExp if neccessary and set up comments filter
var comment_filter = return_false; // Default case, throw all comments away
if (options.comments) {
var comments = options.comments;
if (typeof options.comments === "string" && /^\/.*\/[a-zA-Z]*$/.test(options.comments)) {
var regex_pos = options.comments.lastIndexOf("/");
comments = new RegExp(
options.comments.substr(1, regex_pos - 1),
options.comments.substr(regex_pos + 1)
);
}
if (comments instanceof RegExp) {
comment_filter = function(comment) {
return comment.type != "comment5" && comments.test(comment.value);
};
}
else if (typeof comments === "function") {
comment_filter = function(comment) {
return comment.type != "comment5" && comments(this, comment);
};
}
else if (comments === "some") {
comment_filter = is_some_comments;
} else { // NOTE includes "all" option
comment_filter = return_true;
}
}
2012-05-27 14:25:31 +00:00
var indentation = 0;
var current_col = 0;
2012-10-02 13:39:53 +00:00
var current_line = 1;
2012-08-16 15:11:04 +00:00
var current_pos = 0;
2012-05-27 14:25:31 +00:00
var OUTPUT = "";
var to_utf8 = options.ascii_only ? function(str, identifier) {
return str.replace(/[\u0000-\u001f\u007f-\uffff]/g, function(ch) {
2012-05-27 14:25:31 +00:00
var code = ch.charCodeAt(0).toString(16);
if (code.length <= 2 && !identifier) {
while (code.length < 2) code = "0" + code;
return "\\x" + code;
} else {
while (code.length < 4) code = "0" + code;
return "\\u" + code;
}
2012-05-27 14:25:31 +00:00
});
} : function(str) {
var s = "";
for (var i = 0, len = str.length; i < len; i++) {
if (is_surrogate_pair_head(str[i]) && !is_surrogate_pair_tail(str[i + 1])
|| is_surrogate_pair_tail(str[i]) && !is_surrogate_pair_head(str[i - 1])) {
s += "\\u" + str.charCodeAt(i).toString(16);
} else {
s += str[i];
}
}
return s;
2012-05-27 14:25:31 +00:00
};
function make_string(str, quote) {
2012-05-27 14:25:31 +00:00
var dq = 0, sq = 0;
str = str.replace(/[\\\b\f\n\r\v\t\x22\x27\u2028\u2029\0\ufeff]/g,
2018-06-06 09:50:56 +00:00
function(s, i) {
2012-05-27 14:25:31 +00:00
switch (s) {
case '"': ++dq; return '"';
case "'": ++sq; return "'";
2012-05-27 14:25:31 +00:00
case "\\": return "\\\\";
case "\n": return "\\n";
case "\r": return "\\r";
case "\t": return "\\t";
case "\b": return "\\b";
case "\f": return "\\f";
unify CLI & API under `minify()` (#1811) - rename `screw_ie8` to `ie8` - rename `mangle.except` to `mangle.reserved` - rename `mangle.properties.ignore_quoted` to `mangle.properties.keep_quoted` - compact `sourceMap` options - more stringent verification on input `options` - toplevel shorthands - `ie8` - `keep_fnames` - `toplevel` - `warnings` - support arrays and unquoted string values on CLI - drop `fromString` from `minify()` - `minify()` no longer handles any `fs` operations - unify order of operations for `mangle_properties()` on CLI & API - `bin/uglifyjs` used to `mangle_properties()` before even `Compressor` - `minify()` used to `mangle_properties()` after `Compressor` but before `mangle_names()` - both will now do `Compressor`, `mangle_names()` then `mangle_properties()` - `options.parse` / `--parse` for parser options beyond `bare_returns` - add `mangle.properties.builtins` to disable built-in reserved list - disable with `--mangle-props builtins` on CLI - `warnings` now off by default - add `--warn` and `--verbose` on CLI - drop `--enclose` - drop `--export-all` - drop `--reserved-file` - use `--mangle reserved` instead - drop `--reserve-domprops` - enabled by default, disable with `--mangle-props domprops` - drop `--prefix` - use `--source-map base` instead - drop `--lint` - remove `bin/extract-props.js` - limit exposure of internal APIs - update documentations closes #96 closes #102 closes #136 closes #166 closes #243 closes #254 closes #261 closes #311 closes #700 closes #748 closes #912 closes #1072 closes #1366 fixes #101 fixes #123 fixes #124 fixes #263 fixes #379 fixes #419 fixes #423 fixes #461 fixes #465 fixes #576 fixes #737 fixes #772 fixes #958 fixes #1036 fixes #1142 fixes #1175 fixes #1220 fixes #1223 fixes #1280 fixes #1359 fixes #1368
2017-04-15 15:50:50 +00:00
case "\x0B": return options.ie8 ? "\\x0B" : "\\v";
2012-05-27 14:25:31 +00:00
case "\u2028": return "\\u2028";
case "\u2029": return "\\u2029";
case "\ufeff": return "\\ufeff";
case "\0":
return /[0-9]/.test(str.charAt(i+1)) ? "\\x00" : "\\0";
2012-05-27 14:25:31 +00:00
}
return s;
});
function quote_single() {
return "'" + str.replace(/\x27/g, "\\'") + "'";
}
function quote_double() {
return '"' + str.replace(/\x22/g, '\\"') + '"';
}
str = to_utf8(str);
switch (options.quote_style) {
case 1:
return quote_single();
case 2:
return quote_double();
case 3:
return quote == "'" ? quote_single() : quote_double();
default:
return dq > sq ? quote_single() : quote_double();
}
}
2012-05-27 14:25:31 +00:00
function encode_string(str, quote) {
var ret = make_string(str, quote);
if (options.inline_script) {
ret = ret.replace(/<\x2f(script)([>\/\t\n\f\r ])/gi, "<\\/$1$2");
ret = ret.replace(/\x3c!--/g, "\\x3c!--");
ret = ret.replace(/--\x3e/g, "--\\x3e");
}
2012-05-27 14:25:31 +00:00
return ret;
}
2012-05-27 14:25:31 +00:00
function make_name(name) {
name = name.toString();
name = to_utf8(name, true);
2012-05-27 14:25:31 +00:00
return name;
}
2012-05-27 14:25:31 +00:00
function make_indent(back) {
return repeat_string(" ", options.indent_start + indentation - back * options.indent_level);
}
2012-05-27 14:25:31 +00:00
2012-08-16 15:11:04 +00:00
/* -----[ beautification/minification ]----- */
var has_parens = false;
2012-08-16 15:11:04 +00:00
var might_need_space = false;
var might_need_semicolon = false;
var might_add_newline = 0;
var need_newline_indented = false;
var need_space = false;
var newline_insert = -1;
var last = "";
var mapping_token, mapping_name, mappings = options.source_map && [];
var do_add_mapping = mappings ? function() {
mappings.forEach(function(mapping) {
try {
options.source_map.add(
mapping.token.file,
mapping.line, mapping.col,
mapping.token.line, mapping.token.col,
!mapping.name && mapping.token.type == "name" ? mapping.token.value : mapping.name
);
} catch(ex) {
2018-06-13 10:50:22 +00:00
AST.Node.warn("Couldn't figure out mapping for {file}:{line},{col} → {cline},{ccol} [{name}]", {
file: mapping.token.file,
line: mapping.token.line,
col: mapping.token.col,
cline: mapping.line,
ccol: mapping.col,
name: mapping.name || ""
})
}
});
mappings = [];
} : noop;
var ensure_line_len = options.max_line_len ? function() {
if (current_col > options.max_line_len) {
if (might_add_newline) {
var left = OUTPUT.slice(0, might_add_newline);
var right = OUTPUT.slice(might_add_newline);
if (mappings) {
var delta = right.length - current_col;
mappings.forEach(function(mapping) {
mapping.line++;
mapping.col += delta;
});
}
OUTPUT = left + "\n" + right;
current_line++;
current_pos++;
current_col = right.length;
}
if (current_col > options.max_line_len) {
2018-06-13 10:50:22 +00:00
AST.Node.warn("Output exceeds {max_line_len} characters", options);
}
}
if (might_add_newline) {
might_add_newline = 0;
do_add_mapping();
}
} : noop;
var requireSemicolonChars = makePredicate("( [ + * / - , .");
2012-08-16 15:11:04 +00:00
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();
}
}
if (need_space && ch) {
need_space = false;
if (!/[\s;})]/.test(ch)) {
space();
}
}
newline_insert = -1;
var prev = last.charAt(last.length - 1);
if (might_need_semicolon) {
might_need_semicolon = false;
if (prev == ":" && ch == "}" || (!ch || ";}".indexOf(ch) < 0) && prev != ";") {
if (options.semicolons || requireSemicolonChars[ch]) {
OUTPUT += ";";
current_col++;
current_pos++;
} else {
ensure_line_len();
OUTPUT += "\n";
current_pos++;
current_line++;
current_col = 0;
if (/^\s+$/.test(str)) {
// reset the semicolon flag, since we didn't print one
// now and might still have to later
might_need_semicolon = true;
}
}
if (!options.beautify)
might_need_space = false;
}
}
if (!options.beautify && options.preserve_line && stack[stack.length - 1]) {
var target_line = stack[stack.length - 1].start.line;
while (current_line < target_line) {
ensure_line_len();
OUTPUT += "\n";
current_pos++;
current_line++;
current_col = 0;
might_need_space = false;
}
}
2012-08-16 15:11:04 +00:00
if (might_need_space) {
if ((is_identifier_char(prev)
&& (is_identifier_char(ch) || ch == "\\"))
|| (ch == "/" && ch == prev)
|| ((ch == "+" || ch == "-") && ch == last))
2012-08-16 15:11:04 +00:00
{
OUTPUT += " ";
current_col++;
current_pos++;
}
might_need_space = false;
}
if (mapping_token) {
mappings.push({
token: mapping_token,
name: mapping_name,
line: current_line,
col: current_col
});
mapping_token = false;
if (!might_add_newline) do_add_mapping();
}
OUTPUT += str;
has_parens = str[str.length - 1] == "(";
current_pos += str.length;
var a = str.split(/\r?\n/), n = a.length - 1;
2012-08-16 15:11:04 +00:00
current_line += n;
current_col += a[0].length;
if (n > 0) {
ensure_line_len();
current_col = a[n].length;
}
last = str;
}
2012-08-16 15:11:04 +00:00
var space = options.beautify ? function() {
print(" ");
} : function() {
might_need_space = true;
};
var indent = options.beautify ? function(half) {
2012-08-16 15:11:04 +00:00
if (options.beautify) {
print(make_indent(half ? 0.5 : 0));
2012-08-16 15:11:04 +00:00
}
} : noop;
var with_indent = options.beautify ? function(col, cont) {
2012-08-15 10:32:37 +00:00
if (col === true) col = next_indent();
2012-05-27 14:25:31 +00:00
var save_indentation = indentation;
indentation = col;
var ret = cont();
indentation = save_indentation;
return ret;
2012-08-16 15:11:04 +00:00
} : function(col, cont) { return cont() };
2012-05-27 14:25:31 +00:00
var may_add_newline = options.max_line_len ? function() {
ensure_line_len();
might_add_newline = OUTPUT.length;
} : noop;
var newline = options.beautify ? function() {
if (newline_insert < 0) return print("\n");
if (OUTPUT[newline_insert] != "\n") {
OUTPUT = OUTPUT.slice(0, newline_insert) + "\n" + OUTPUT.slice(newline_insert);
current_pos++;
current_line++;
}
newline_insert++;
} : may_add_newline;
2012-05-27 14:25:31 +00:00
var semicolon = options.beautify ? function() {
print(";");
} : function() {
might_need_semicolon = true;
};
function force_semicolon() {
might_need_semicolon = false;
print(";");
}
2012-05-27 14:25:31 +00:00
function next_indent() {
return indentation + options.indent_level;
}
2012-05-27 14:25:31 +00:00
2012-08-16 15:11:04 +00:00
function with_block(cont) {
2012-05-27 14:25:31 +00:00
var ret;
print("{");
2012-08-16 15:11:04 +00:00
newline();
2018-06-06 09:50:56 +00:00
with_indent(next_indent(), function() {
2012-05-27 14:25:31 +00:00
ret = cont();
});
2012-08-16 15:11:04 +00:00
indent();
2012-05-27 14:25:31 +00:00
print("}");
return ret;
}
2012-05-27 14:25:31 +00:00
2012-08-16 15:11:04 +00:00
function with_parens(cont) {
2012-05-27 14:25:31 +00:00
print("(");
may_add_newline();
//XXX: still nice to have that for argument lists
//var ret = with_indent(current_col, cont);
var ret = cont();
may_add_newline();
2012-05-27 14:25:31 +00:00
print(")");
return ret;
}
2012-05-27 14:25:31 +00:00
2012-08-16 15:11:04 +00:00
function with_square(cont) {
print("[");
may_add_newline();
//var ret = with_indent(current_col, cont);
var ret = cont();
may_add_newline();
2012-08-16 15:11:04 +00:00
print("]");
return ret;
}
2012-08-16 15:11:04 +00:00
function comma() {
may_add_newline();
2012-08-16 15:11:04 +00:00
print(",");
may_add_newline();
2012-08-16 15:11:04 +00:00
space();
}
2012-08-16 15:11:04 +00:00
function colon() {
print(":");
space();
}
2012-08-16 15:11:04 +00:00
var add_mapping = mappings ? function(token, name) {
mapping_token = token;
mapping_name = name;
} : noop;
function get() {
if (might_add_newline) {
ensure_line_len();
}
return OUTPUT;
}
2018-01-17 18:57:33 +00:00
function has_nlb() {
var index = OUTPUT.lastIndexOf("\n");
return /^ *$/.test(OUTPUT.slice(index + 1));
}
function prepend_comments(node) {
var self = this;
var start = node.start;
2018-01-11 17:05:49 +00:00
if (!start) return;
if (start.comments_before && start.comments_before._dumped === self) return;
var comments = start.comments_before;
if (!comments) {
comments = start.comments_before = [];
}
comments._dumped = self;
2018-06-13 10:50:22 +00:00
if (node instanceof AST.Exit && node.value) {
2018-01-11 17:05:49 +00:00
var tw = new TreeWalker(function(node) {
var parent = tw.parent();
2018-06-13 10:50:22 +00:00
if (parent instanceof AST.Exit
|| parent instanceof AST.Binary && parent.left === node
2018-01-11 17:05:49 +00:00
|| parent.TYPE == "Call" && parent.expression === node
2018-06-13 10:50:22 +00:00
|| 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) {
2018-01-11 17:05:49 +00:00
var text = node.start.comments_before;
if (text && text._dumped !== self) {
text._dumped = self;
comments = comments.concat(text);
}
2018-01-11 17:05:49 +00:00
} else {
return true;
}
2018-01-11 17:05:49 +00:00
});
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"));
}
2018-01-11 17:05:49 +00:00
}
2018-01-11 17:05:49 +00:00
comments = comments.filter(comment_filter, node);
if (comments.length == 0) return;
2018-01-17 18:57:33 +00:00
var last_nlb = has_nlb();
2018-01-11 17:05:49 +00:00
comments.forEach(function(c, i) {
if (!last_nlb) {
2018-01-11 17:05:49 +00:00
if (c.nlb) {
print("\n");
indent();
2018-01-11 17:05:49 +00:00
last_nlb = true;
} else if (i > 0) {
space();
}
}
2018-01-11 17:05:49 +00:00
if (/comment[134]/.test(c.type)) {
print("//" + c.value.replace(/[@#]__PURE__/g, ' ') + "\n");
indent();
last_nlb = true;
} else if (c.type == "comment2") {
print("/*" + c.value.replace(/[@#]__PURE__/g, ' ') + "*/");
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"];
2018-01-11 17:05:49 +00:00
if (!comments || comments._dumped === self) return;
2018-06-13 10:50:22 +00:00
if (!(node instanceof AST.Statement || all(comments, function(c) {
2018-01-11 17:05:49 +00:00
return !/comment[134]/.test(c.type);
}))) return;
comments._dumped = self;
var insert = OUTPUT.length;
comments.filter(comment_filter, node).forEach(function(c, i) {
need_space = false;
if (need_newline_indented) {
print("\n");
indent();
need_newline_indented = false;
2018-01-17 18:57:33 +00:00
} else if (c.nlb && (i > 0 || !has_nlb())) {
2018-01-11 17:05:49 +00:00
print("\n");
indent();
} else if (i > 0 || !tail) {
space();
}
if (/comment[134]/.test(c.type)) {
print("//" + c.value.replace(/[@#]__PURE__/g, ' '));
need_newline_indented = true;
} else if (c.type == "comment2") {
print("/*" + c.value.replace(/[@#]__PURE__/g, ' ') + "*/");
need_space = true;
}
});
if (OUTPUT.length > insert) newline_insert = insert;
}
var stack = [];
2012-05-27 14:25:31 +00:00
return {
get : get,
toString : get,
indent : indent,
indentation : function() { return indentation },
current_width : function() { return current_col - indentation },
should_break : function() { return options.width && this.current_width() >= options.width },
has_parens : function() { return has_parens },
newline : newline,
print : print,
space : space,
comma : comma,
colon : colon,
last : function() { return last },
semicolon : semicolon,
force_semicolon : force_semicolon,
to_utf8 : to_utf8,
print_name : function(name) { print(make_name(name)) },
print_string : function(str, quote, escape_directive) {
var encoded = encode_string(str, quote);
if (escape_directive === true && encoded.indexOf("\\") === -1) {
// Insert semicolons to break directive prologue
if (!EXPECT_DIRECTIVE.test(OUTPUT)) {
force_semicolon();
}
force_semicolon();
}
print(encoded);
},
encode_string : encode_string,
next_indent : next_indent,
with_indent : with_indent,
with_block : with_block,
with_parens : with_parens,
with_square : with_square,
add_mapping : add_mapping,
option : function(opt) { return options[opt] },
prepend_comments: readonly ? noop : prepend_comments,
append_comments : readonly || comment_filter === return_false ? noop : append_comments,
line : function() { return current_line },
col : function() { return current_col },
pos : function() { return current_pos },
push_node : function(node) { stack.push(node) },
pop_node : function() { return stack.pop() },
parent : function(n) {
return stack[stack.length - 2 - (n || 0)];
}
2012-05-27 14:25:31 +00:00
};
}
2012-08-16 15:11:04 +00:00
/* -----[ code generators ]----- */
2018-06-06 09:50:56 +00:00
(function() {
/* -----[ utils ]----- */
function DEFPRINT(nodetype, generator) {
2012-12-12 09:50:03 +00:00
nodetype.DEFMETHOD("_codegen", generator);
}
2012-12-12 09:50:03 +00:00
var in_directive = false;
var active_scope = null;
var use_asm = null;
2018-06-13 10:50:22 +00:00
AST.Node.DEFMETHOD("print", function(stream, force_parens) {
var self = this, generator = self._codegen;
2018-06-13 10:50:22 +00:00
if (self instanceof AST.Scope) {
active_scope = self;
}
2018-06-13 10:50:22 +00:00
else if (!use_asm && self instanceof AST.Directive && self.value == "use asm") {
use_asm = active_scope;
}
function doit() {
stream.prepend_comments(self);
2012-12-12 09:50:03 +00:00
self.add_source_map(stream);
generator(self, stream);
stream.append_comments(self);
2012-12-12 09:50:03 +00:00
}
stream.push_node(self);
if (force_parens || self.needs_parens(stream)) {
stream.with_parens(doit);
} else {
doit();
}
2012-12-12 09:50:03 +00:00
stream.pop_node();
if (self === use_asm) {
use_asm = null;
}
2012-12-12 09:50:03 +00:00
});
2018-06-13 10:50:22 +00:00
AST.Node.DEFMETHOD("_print", AST.Node.prototype.print);
2018-06-13 10:50:22 +00:00
AST.Node.DEFMETHOD("print_to_string", function(options) {
2012-08-27 07:59:33 +00:00
var s = OutputStream(options);
this.print(s);
return s.get();
});
/* -----[ PARENTHESES ]----- */
function PARENS(nodetype, func) {
if (Array.isArray(nodetype)) {
2018-06-06 09:50:56 +00:00
nodetype.forEach(function(nodetype) {
PARENS(nodetype, func);
});
} else {
nodetype.DEFMETHOD("needs_parens", func);
}
}
2018-06-13 10:50:22 +00:00
PARENS(AST.Node, return_false);
// a function expression needs parens around it when it's provably
// the first token to appear in a statement.
2018-06-13 10:50:22 +00:00
PARENS(AST.Function, function(output) {
if (!output.has_parens() && first_in_statement(output)) {
return true;
}
if (output.option('webkit')) {
var p = output.parent();
2018-06-13 10:50:22 +00:00
if (p instanceof AST.PropAccess && p.expression === this) {
return true;
}
}
if (output.option('wrap_iife')) {
var p = output.parent();
2018-06-13 10:50:22 +00:00
return p instanceof AST.Call && p.expression === this;
}
return false;
});
// same goes for an object literal, because otherwise it would be
// interpreted as a block of code.
2018-06-13 10:50:22 +00:00
PARENS(AST.Object, function(output) {
return !output.has_parens() && first_in_statement(output);
});
2018-06-13 10:50:22 +00:00
PARENS(AST.Unary, function(output) {
var p = output.parent();
2018-06-13 10:50:22 +00:00
return p instanceof AST.PropAccess && p.expression === this
|| p instanceof AST.Call && p.expression === this;
});
2018-06-13 10:50:22 +00:00
PARENS(AST.Sequence, function(output) {
var p = output.parent();
2018-06-13 10:50:22 +00:00
return p instanceof AST.Call // (foo, bar)() or foo(1, (2, 3), 4)
|| p instanceof AST.Unary // !(foo, bar, baz)
|| p instanceof AST.Binary // 1 + (2, 3) + 4 ==> 8
|| p instanceof AST.VarDef // var a = (1, 2), b = a + a; ==> b == 4
|| p instanceof AST.PropAccess // (1, {foo:2}).foo or (1, {foo:2})["foo"] ==> 2
|| p instanceof AST.Array // [ 1, (2, 3), 4 ] ==> [ 1, 3, 4 ]
|| p instanceof AST.ObjectProperty // { foo: (1, 2) }.foo ==> 2
|| p instanceof AST.Conditional /* (false, true) ? (a = 10, b = 20) : (c = 30)
* ==> 20 (side effect, set a := 10 and b := 20) */
;
});
2018-06-13 10:50:22 +00:00
PARENS(AST.Binary, function(output) {
var p = output.parent();
// (foo && bar)()
2018-06-13 10:50:22 +00:00
if (p instanceof AST.Call && p.expression === this)
return true;
// typeof (foo && bar)
2018-06-13 10:50:22 +00:00
if (p instanceof AST.Unary)
return true;
// (foo && bar)["prop"], (foo && bar).prop
2018-06-13 10:50:22 +00:00
if (p instanceof AST.PropAccess && p.expression === this)
return true;
// this deals with precedence: 3 * (2 + 1)
2018-06-13 10:50:22 +00:00
if (p instanceof AST.Binary) {
var po = p.operator, pp = PRECEDENCE[po];
var so = this.operator, sp = PRECEDENCE[so];
if (pp > sp
|| (pp == sp
&& this === p.right)) {
return true;
}
}
});
2018-06-13 10:50:22 +00:00
PARENS(AST.PropAccess, function(output) {
var p = output.parent();
2018-06-13 10:50:22 +00:00
if (p instanceof AST.New && p.expression === this) {
// i.e. new (foo.bar().baz)
//
// if there's one call into this subtree, then we need
// parens around it too, otherwise the call will be
// interpreted as passing the arguments to the upper New
// expression.
var parens = false;
this.walk(new TreeWalker(function(node) {
2018-06-13 10:50:22 +00:00
if (parens || node instanceof AST.Scope) return true;
if (node instanceof AST.Call) {
parens = true;
return true;
}
}));
return parens;
}
});
2018-06-13 10:50:22 +00:00
PARENS(AST.Call, function(output) {
2013-10-30 11:59:59 +00:00
var p = output.parent(), p1;
2018-06-13 10:50:22 +00:00
if (p instanceof AST.New && p.expression === this)
2013-10-30 11:59:59 +00:00
return true;
// workaround for Safari bug.
// https://bugs.webkit.org/show_bug.cgi?id=123506
2018-06-13 10:50:22 +00:00
return this.expression instanceof AST.Function
&& p instanceof AST.PropAccess
2013-10-30 11:59:59 +00:00
&& p.expression === this
2018-06-13 10:50:22 +00:00
&& (p1 = output.parent(1)) instanceof AST.Assign
2013-10-30 11:59:59 +00:00
&& p1.left === p;
});
2018-06-13 10:50:22 +00:00
PARENS(AST.New, function(output) {
var p = output.parent();
2016-06-10 13:42:55 +00:00
if (!need_constructor_parens(this, output)
2018-06-13 10:50:22 +00:00
&& (p instanceof AST.PropAccess // (new Date).getTime(), (new Date)["getTime"]()
|| p instanceof AST.Call && p.expression === this)) // (new foo)(bar)
return true;
});
2018-06-13 10:50:22 +00:00
PARENS(AST.Number, function(output) {
var p = output.parent();
2018-06-13 10:50:22 +00:00
if (p instanceof AST.PropAccess && p.expression === this) {
var value = this.getValue();
if (value < 0 || /^0/.test(make_num(value))) {
return true;
}
}
});
2018-06-13 10:50:22 +00:00
PARENS([ AST.Assign, AST.Conditional ], function(output) {
var p = output.parent();
// !(a = false) → true
2018-06-13 10:50:22 +00:00
if (p instanceof AST.Unary)
return true;
// 1 + (a = 2) + 3 → 6, side effect setting a = 2
2018-06-13 10:50:22 +00:00
if (p instanceof AST.Binary && !(p instanceof AST.Assign))
return true;
// (a = func)() —or— new (a = Object)()
2018-06-13 10:50:22 +00:00
if (p instanceof AST.Call && p.expression === this)
return true;
// (a = foo) ? bar : baz
2018-06-13 10:50:22 +00:00
if (p instanceof AST.Conditional && p.condition === this)
return true;
// (a = foo)["prop"] —or— (a = foo).prop
2018-06-13 10:50:22 +00:00
if (p instanceof AST.PropAccess && p.expression === this)
return true;
});
/* -----[ PRINTERS ]----- */
2018-06-13 10:50:22 +00:00
DEFPRINT(AST.Directive, function(self, output) {
output.print_string(self.value, self.quote);
2012-09-18 10:21:09 +00:00
output.semicolon();
2012-08-16 15:11:04 +00:00
});
2018-06-13 10:50:22 +00:00
DEFPRINT(AST.Debugger, function(self, output) {
output.print("debugger");
output.semicolon();
2012-08-16 15:11:04 +00:00
});
/* -----[ statements ]----- */
function display_body(body, is_toplevel, output, allow_directives) {
var last = body.length - 1;
in_directive = allow_directives;
2018-06-06 09:50:56 +00:00
body.forEach(function(stmt, i) {
2018-06-13 10:50:22 +00:00
if (in_directive === true && !(stmt instanceof AST.Directive ||
stmt instanceof AST.EmptyStatement ||
(stmt instanceof AST.SimpleStatement && stmt.body instanceof AST.String)
)) {
in_directive = false;
}
2018-06-13 10:50:22 +00:00
if (!(stmt instanceof AST.EmptyStatement)) {
2012-08-16 15:11:04 +00:00
output.indent();
stmt.print(output);
if (!(i == last && is_toplevel)) {
output.newline();
if (is_toplevel) output.newline();
}
}
if (in_directive === true &&
2018-06-13 10:50:22 +00:00
stmt instanceof AST.SimpleStatement &&
stmt.body instanceof AST.String
) {
in_directive = false;
}
2012-08-16 15:11:04 +00:00
});
in_directive = false;
}
2018-06-13 10:50:22 +00:00
AST.StatementWithBody.DEFMETHOD("_do_print_body", function(output) {
2012-09-03 09:11:44 +00:00
force_statement(this.body, output);
});
2018-06-13 10:50:22 +00:00
DEFPRINT(AST.Statement, function(self, output) {
self.body.print(output);
output.semicolon();
});
2018-06-13 10:50:22 +00:00
DEFPRINT(AST.Toplevel, function(self, output) {
display_body(self.body, true, output, true);
2012-11-05 11:09:39 +00:00
output.print("");
});
2018-06-13 10:50:22 +00:00
DEFPRINT(AST.LabeledStatement, function(self, output) {
self.label.print(output);
output.colon();
self.body.print(output);
2012-08-16 15:11:04 +00:00
});
2018-06-13 10:50:22 +00:00
DEFPRINT(AST.SimpleStatement, function(self, output) {
2012-08-16 15:11:04 +00:00
self.body.print(output);
output.semicolon();
});
2018-03-15 07:46:45 +00:00
function print_braced_empty(self, output) {
output.print("{");
output.with_indent(output.next_indent(), function() {
output.append_comments(self, true);
});
output.print("}");
}
2018-03-15 07:46:45 +00:00
function print_braced(self, output, allow_directives) {
if (self.body.length > 0) {
output.with_block(function() {
display_body(self.body, false, output, allow_directives);
});
2018-03-15 07:46:45 +00:00
} else print_braced_empty(self, output);
}
2018-06-13 10:50:22 +00:00
DEFPRINT(AST.BlockStatement, function(self, output) {
2018-03-15 07:46:45 +00:00
print_braced(self, output);
2012-08-16 15:11:04 +00:00
});
2018-06-13 10:50:22 +00:00
DEFPRINT(AST.EmptyStatement, function(self, output) {
output.semicolon();
2012-08-16 15:11:04 +00:00
});
2018-06-13 10:50:22 +00:00
DEFPRINT(AST.Do, function(self, output) {
2012-08-16 15:11:04 +00:00
output.print("do");
output.space();
make_block(self.body, output);
2012-08-16 15:11:04 +00:00
output.space();
output.print("while");
output.space();
2018-06-06 09:50:56 +00:00
output.with_parens(function() {
self.condition.print(output);
});
output.semicolon();
});
2018-06-13 10:50:22 +00:00
DEFPRINT(AST.While, function(self, output) {
output.print("while");
output.space();
2018-06-06 09:50:56 +00:00
output.with_parens(function() {
self.condition.print(output);
});
output.space();
2012-09-03 09:11:44 +00:00
self._do_print_body(output);
2012-08-16 15:11:04 +00:00
});
2018-06-13 10:50:22 +00:00
DEFPRINT(AST.For, function(self, output) {
2012-08-16 15:11:04 +00:00
output.print("for");
output.space();
2018-06-06 09:50:56 +00:00
output.with_parens(function() {
if (self.init) {
2018-06-13 10:50:22 +00:00
if (self.init instanceof AST.Definitions) {
self.init.print(output);
} else {
parenthesize_for_noin(self.init, output, true);
}
output.print(";");
output.space();
} else {
output.print(";");
}
if (self.condition) {
self.condition.print(output);
output.print(";");
output.space();
} else {
output.print(";");
}
if (self.step) {
self.step.print(output);
}
2012-08-16 15:11:04 +00:00
});
output.space();
2012-09-03 09:11:44 +00:00
self._do_print_body(output);
2012-08-16 15:11:04 +00:00
});
2018-06-13 10:50:22 +00:00
DEFPRINT(AST.ForIn, function(self, output) {
2012-08-16 15:11:04 +00:00
output.print("for");
output.space();
2018-06-06 09:50:56 +00:00
output.with_parens(function() {
self.init.print(output);
output.space();
output.print("in");
output.space();
2012-08-16 15:11:04 +00:00
self.object.print(output);
});
output.space();
2012-09-03 09:11:44 +00:00
self._do_print_body(output);
2012-08-16 15:11:04 +00:00
});
2018-06-13 10:50:22 +00:00
DEFPRINT(AST.With, function(self, output) {
2012-08-16 15:11:04 +00:00
output.print("with");
output.space();
2018-06-06 09:50:56 +00:00
output.with_parens(function() {
2012-08-16 15:11:04 +00:00
self.expression.print(output);
});
output.space();
2012-09-03 09:11:44 +00:00
self._do_print_body(output);
2012-08-16 15:11:04 +00:00
});
2012-08-16 15:11:04 +00:00
/* -----[ functions ]----- */
2018-06-13 10:50:22 +00:00
AST.Lambda.DEFMETHOD("_do_print", function(output, nokeyword) {
var self = this;
if (!nokeyword) {
output.print("function");
}
2012-08-16 15:11:04 +00:00
if (self.name) {
output.space();
2012-08-16 15:11:04 +00:00
self.name.print(output);
}
2018-06-06 09:50:56 +00:00
output.with_parens(function() {
self.argnames.forEach(function(arg, i) {
2012-08-16 15:11:04 +00:00
if (i) output.comma();
arg.print(output);
});
});
output.space();
2018-03-15 07:46:45 +00:00
print_braced(self, output, true);
2012-08-16 15:11:04 +00:00
});
2018-06-13 10:50:22 +00:00
DEFPRINT(AST.Lambda, function(self, output) {
self._do_print(output);
});
2018-06-06 09:50:56 +00:00
/* -----[ jumps ]----- */
function print_jump(output, kind, target) {
2012-08-16 15:11:04 +00:00
output.print(kind);
2018-06-06 09:50:56 +00:00
if (target) {
output.space();
2018-06-06 09:50:56 +00:00
target.print(output);
}
2012-08-16 15:11:04 +00:00
output.semicolon();
2018-06-06 09:50:56 +00:00
}
2018-06-13 10:50:22 +00:00
DEFPRINT(AST.Return, function(self, output) {
2018-06-06 09:50:56 +00:00
print_jump(output, "return", self.value);
});
2018-06-13 10:50:22 +00:00
DEFPRINT(AST.Throw, function(self, output) {
2018-06-06 09:50:56 +00:00
print_jump(output, "throw", self.value);
2012-08-16 15:11:04 +00:00
});
2018-06-13 10:50:22 +00:00
DEFPRINT(AST.Break, function(self, output) {
2018-06-06 09:50:56 +00:00
print_jump(output, "break", self.label);
2012-08-16 15:11:04 +00:00
});
2018-06-13 10:50:22 +00:00
DEFPRINT(AST.Continue, function(self, output) {
2018-06-06 09:50:56 +00:00
print_jump(output, "continue", self.label);
2012-08-16 15:11:04 +00:00
});
2012-08-16 15:11:04 +00:00
/* -----[ if ]----- */
function make_then(self, output) {
var b = self.body;
2018-03-15 07:46:45 +00:00
if (output.option("braces")
2018-06-13 10:50:22 +00:00
|| output.option("ie8") && b instanceof AST.Do)
return make_block(b, output);
// The squeezer replaces "block"-s that contain only a single
// statement with the statement itself; technically, the AST
// is correct, but this can create problems when we output an
// IF having an ELSE clause where the THEN clause ends in an
// IF *without* an ELSE block (then the outer ELSE would refer
// to the inner IF). This function checks for this case and
2018-03-15 07:46:45 +00:00
// adds the block braces if needed.
if (!b) return output.force_semicolon();
while (true) {
2018-06-13 10:50:22 +00:00
if (b instanceof AST.If) {
if (!b.alternative) {
2012-09-03 09:11:44 +00:00
make_block(self.body, output);
return;
}
b = b.alternative;
}
2018-06-13 10:50:22 +00:00
else if (b instanceof AST.StatementWithBody) {
b = b.body;
}
else break;
}
force_statement(self.body, output);
}
2018-06-13 10:50:22 +00:00
DEFPRINT(AST.If, function(self, output) {
2012-08-16 15:11:04 +00:00
output.print("if");
output.space();
2018-06-06 09:50:56 +00:00
output.with_parens(function() {
2012-08-16 15:11:04 +00:00
self.condition.print(output);
});
output.space();
if (self.alternative) {
make_then(self, output);
output.space();
output.print("else");
2012-08-16 15:11:04 +00:00
output.space();
2018-06-13 10:50:22 +00:00
if (self.alternative instanceof AST.If)
self.alternative.print(output);
else
force_statement(self.alternative, output);
} else {
2012-09-03 09:11:44 +00:00
self._do_print_body(output);
2012-08-16 15:11:04 +00:00
}
});
2012-08-16 15:11:04 +00:00
/* -----[ switch ]----- */
2018-06-13 10:50:22 +00:00
DEFPRINT(AST.Switch, function(self, output) {
2012-08-16 15:11:04 +00:00
output.print("switch");
output.space();
2018-06-06 09:50:56 +00:00
output.with_parens(function() {
2012-08-16 15:11:04 +00:00
self.expression.print(output);
});
output.space();
var last = self.body.length - 1;
2018-03-15 07:46:45 +00:00
if (last < 0) print_braced_empty(self, output);
2018-06-06 09:50:56 +00:00
else output.with_block(function() {
self.body.forEach(function(branch, i) {
output.indent(true);
branch.print(output);
if (i < last && branch.body.length > 0)
output.newline();
});
});
});
2018-06-13 10:50:22 +00:00
AST.SwitchBranch.DEFMETHOD("_do_print_body", function(output) {
output.newline();
2018-06-06 09:50:56 +00:00
this.body.forEach(function(stmt) {
output.indent();
stmt.print(output);
2012-08-16 15:11:04 +00:00
output.newline();
});
2012-08-16 15:11:04 +00:00
});
2018-06-13 10:50:22 +00:00
DEFPRINT(AST.Default, function(self, output) {
2012-08-16 15:11:04 +00:00
output.print("default:");
self._do_print_body(output);
});
2018-06-13 10:50:22 +00:00
DEFPRINT(AST.Case, function(self, output) {
2012-08-16 15:11:04 +00:00
output.print("case");
output.space();
self.expression.print(output);
output.print(":");
self._do_print_body(output);
});
2012-08-16 15:11:04 +00:00
/* -----[ exceptions ]----- */
2018-06-13 10:50:22 +00:00
DEFPRINT(AST.Try, function(self, output) {
2012-08-16 15:11:04 +00:00
output.print("try");
output.space();
2018-03-15 07:46:45 +00:00
print_braced(self, output);
2012-08-16 15:11:04 +00:00
if (self.bcatch) {
output.space();
self.bcatch.print(output);
}
if (self.bfinally) {
output.space();
self.bfinally.print(output);
}
});
2018-06-13 10:50:22 +00:00
DEFPRINT(AST.Catch, function(self, output) {
2012-08-16 15:11:04 +00:00
output.print("catch");
output.space();
2018-06-06 09:50:56 +00:00
output.with_parens(function() {
self.argname.print(output);
});
output.space();
2018-03-15 07:46:45 +00:00
print_braced(self, output);
2012-08-16 15:11:04 +00:00
});
2018-06-13 10:50:22 +00:00
DEFPRINT(AST.Finally, function(self, output) {
2012-08-16 15:11:04 +00:00
output.print("finally");
output.space();
2018-03-15 07:46:45 +00:00
print_braced(self, output);
2012-08-16 15:11:04 +00:00
});
2018-06-13 10:50:22 +00:00
DEFPRINT(AST.Var, function(self, output) {
2018-06-06 09:50:56 +00:00
output.print("var");
2012-08-16 15:11:04 +00:00
output.space();
2018-06-06 09:50:56 +00:00
self.definitions.forEach(function(def, i) {
if (i) output.comma();
2012-08-16 15:11:04 +00:00
def.print(output);
});
var p = output.parent();
2018-06-13 10:50:22 +00:00
if (p.init !== self || !(p instanceof AST.For || p instanceof AST.ForIn)) output.semicolon();
2012-08-16 15:11:04 +00:00
});
function parenthesize_for_noin(node, output, noin) {
var parens = false;
// need to take some precautions here:
// https://github.com/mishoo/UglifyJS2/issues/60
if (noin) node.walk(new TreeWalker(function(node) {
2018-06-13 10:50:22 +00:00
if (parens || node instanceof AST.Scope) return true;
if (node instanceof AST.Binary && node.operator == "in") {
parens = true;
return true;
}
}));
node.print(output, parens);
}
2018-06-13 10:50:22 +00:00
DEFPRINT(AST.VarDef, function(self, output) {
2012-08-16 15:11:04 +00:00
self.name.print(output);
if (self.value) {
output.space();
output.print("=");
output.space();
var p = output.parent(1);
2018-06-13 10:50:22 +00:00
var noin = p instanceof AST.For || p instanceof AST.ForIn;
parenthesize_for_noin(self.value, output, noin);
2012-08-16 15:11:04 +00:00
}
});
2012-08-16 15:11:04 +00:00
/* -----[ other expressions ]----- */
2018-06-13 10:50:22 +00:00
DEFPRINT(AST.Call, function(self, output) {
2012-08-16 15:11:04 +00:00
self.expression.print(output);
2018-06-13 10:50:22 +00:00
if (self instanceof AST.New && !need_constructor_parens(self, output))
return;
2018-06-13 10:50:22 +00:00
if (self.expression instanceof AST.Call || self.expression instanceof AST.Lambda) {
output.add_mapping(self.start);
}
2018-06-06 09:50:56 +00:00
output.with_parens(function() {
self.args.forEach(function(expr, i) {
2012-08-16 15:11:04 +00:00
if (i) output.comma();
expr.print(output);
2012-08-16 15:11:04 +00:00
});
});
});
2018-06-13 10:50:22 +00:00
DEFPRINT(AST.New, function(self, output) {
2012-08-16 15:11:04 +00:00
output.print("new");
output.space();
2018-06-13 10:50:22 +00:00
AST.Call.prototype._codegen(self, output);
2012-08-16 15:11:04 +00:00
});
2018-06-13 10:50:22 +00:00
DEFPRINT(AST.Sequence, function(self, output) {
2018-06-06 09:50:56 +00:00
self.expressions.forEach(function(node, index) {
if (index > 0) {
output.comma();
if (output.should_break()) {
output.newline();
output.indent();
}
}
node.print(output);
});
2012-08-16 15:11:04 +00:00
});
2018-06-13 10:50:22 +00:00
DEFPRINT(AST.Dot, function(self, output) {
var expr = self.expression;
expr.print(output);
var prop = self.property;
if (output.option("ie8") && RESERVED_WORDS[prop]) {
output.print("[");
output.add_mapping(self.end);
output.print_string(prop);
output.print("]");
} else {
2018-06-13 10:50:22 +00:00
if (expr instanceof AST.Number && expr.getValue() >= 0) {
if (!/[xa-f.)]/i.test(output.last())) {
output.print(".");
}
}
output.print(".");
// the name after dot would be mapped about here.
output.add_mapping(self.end);
output.print_name(prop);
}
2012-08-16 15:11:04 +00:00
});
2018-06-13 10:50:22 +00:00
DEFPRINT(AST.Sub, function(self, output) {
2012-08-16 15:11:04 +00:00
self.expression.print(output);
output.print("[");
self.property.print(output);
output.print("]");
});
2018-06-13 10:50:22 +00:00
DEFPRINT(AST.UnaryPrefix, function(self, output) {
var op = self.operator;
output.print(op);
if (/^[a-z]/i.test(op)
|| (/[+-]$/.test(op)
2018-06-13 10:50:22 +00:00
&& self.expression instanceof AST.UnaryPrefix
&& /^[+-]/.test(self.expression.operator))) {
2012-08-17 13:27:43 +00:00
output.space();
}
2012-08-16 15:11:04 +00:00
self.expression.print(output);
});
2018-06-13 10:50:22 +00:00
DEFPRINT(AST.UnaryPostfix, function(self, output) {
2012-08-16 15:11:04 +00:00
self.expression.print(output);
output.print(self.operator);
});
2018-06-13 10:50:22 +00:00
DEFPRINT(AST.Binary, function(self, output) {
var op = self.operator;
self.left.print(output);
if (op[0] == ">" /* ">>" ">>>" ">" ">=" */
2018-06-13 10:50:22 +00:00
&& self.left instanceof AST.UnaryPostfix
&& self.left.operator == "--") {
// space is mandatory to avoid outputting -->
output.print(" ");
} else {
// the space is optional depending on "beautify"
output.space();
}
output.print(op);
if ((op == "<" || op == "<<")
2018-06-13 10:50:22 +00:00
&& self.right instanceof AST.UnaryPrefix
&& self.right.operator == "!"
2018-06-13 10:50:22 +00:00
&& self.right.expression instanceof AST.UnaryPrefix
&& self.right.expression.operator == "--") {
// space is mandatory to avoid outputting <!--
output.print(" ");
} else {
// the space is optional depending on "beautify"
output.space();
}
self.right.print(output);
2012-08-16 15:11:04 +00:00
});
2018-06-13 10:50:22 +00:00
DEFPRINT(AST.Conditional, function(self, output) {
2012-08-16 15:11:04 +00:00
self.condition.print(output);
output.space();
output.print("?");
output.space();
self.consequent.print(output);
output.space();
2012-08-16 15:11:04 +00:00
output.colon();
self.alternative.print(output);
});
2012-08-16 15:11:04 +00:00
/* -----[ literals ]----- */
2018-06-13 10:50:22 +00:00
DEFPRINT(AST.Array, function(self, output) {
2018-06-06 09:50:56 +00:00
output.with_square(function() {
2012-08-17 20:08:09 +00:00
var a = self.elements, len = a.length;
if (len > 0) output.space();
2018-06-06 09:50:56 +00:00
a.forEach(function(exp, i) {
2012-08-16 15:11:04 +00:00
if (i) output.comma();
exp.print(output);
// If the final element is a hole, we need to make sure it
// doesn't look like a trailing comma, by inserting an actual
// trailing comma.
2018-06-13 10:50:22 +00:00
if (i === len - 1 && exp instanceof AST.Hole)
output.comma();
2012-08-16 15:11:04 +00:00
});
2012-08-17 20:08:09 +00:00
if (len > 0) output.space();
2012-08-16 15:11:04 +00:00
});
});
2018-06-13 10:50:22 +00:00
DEFPRINT(AST.Object, function(self, output) {
2018-06-06 09:50:56 +00:00
if (self.properties.length > 0) output.with_block(function() {
self.properties.forEach(function(prop, i) {
if (i) {
2012-08-17 20:08:09 +00:00
output.print(",");
output.newline();
}
output.indent();
2012-08-16 15:11:04 +00:00
prop.print(output);
});
output.newline();
2012-08-16 15:11:04 +00:00
});
2018-03-15 07:46:45 +00:00
else print_braced_empty(self, output);
2012-08-16 15:11:04 +00:00
});
function print_property_name(key, quote, output) {
if (output.option("quote_keys")) {
output.print_string(key);
} else if ("" + +key == key && key >= 0) {
output.print(make_num(key));
2018-06-09 17:40:40 +00:00
} else if (parse.RESERVED_WORDS[key] ? !output.option("ie8") : is_identifier_string(key)) {
if (quote && output.option("keep_quoted_props")) {
output.print_string(key, quote);
} else {
output.print_name(key);
}
} else {
output.print_string(key, quote);
}
}
2018-06-13 10:50:22 +00:00
DEFPRINT(AST.ObjectKeyVal, function(self, output) {
print_property_name(self.key, self.quote, output);
2012-08-16 15:11:04 +00:00
output.colon();
self.value.print(output);
});
2018-06-13 10:50:22 +00:00
AST.ObjectProperty.DEFMETHOD("_print_getter_setter", function(type, output) {
output.print(type);
output.space();
print_property_name(this.key.name, this.quote, output);
this.value._do_print(output, true);
});
2018-06-13 10:50:22 +00:00
DEFPRINT(AST.ObjectSetter, function(self, output) {
self._print_getter_setter("set", output);
2012-08-16 15:11:04 +00:00
});
2018-06-13 10:50:22 +00:00
DEFPRINT(AST.ObjectGetter, function(self, output) {
self._print_getter_setter("get", output);
2012-08-16 15:11:04 +00:00
});
2018-06-13 10:50:22 +00:00
DEFPRINT(AST.Symbol, function(self, output) {
2012-08-21 08:53:19 +00:00
var def = self.definition();
output.print_name(def ? def.mangled_name || def.name : self.name);
2012-08-20 14:19:30 +00:00
});
2018-06-13 10:50:22 +00:00
DEFPRINT(AST.Hole, noop);
DEFPRINT(AST.This, function(self, output) {
2012-08-16 15:11:04 +00:00
output.print("this");
});
2018-06-13 10:50:22 +00:00
DEFPRINT(AST.Constant, function(self, output) {
2012-08-16 15:11:04 +00:00
output.print(self.getValue());
});
2018-06-13 10:50:22 +00:00
DEFPRINT(AST.String, function(self, output) {
output.print_string(self.getValue(), self.quote, in_directive);
2012-08-16 15:11:04 +00:00
});
2018-06-13 10:50:22 +00:00
DEFPRINT(AST.Number, function(self, output) {
if (use_asm && self.start && self.start.raw != null) {
output.print(self.start.raw);
} else {
output.print(make_num(self.getValue()));
}
});
2018-06-13 10:50:22 +00:00
DEFPRINT(AST.RegExp, function(self, output) {
var regexp = self.getValue();
var str = regexp.toString();
if (regexp.raw_source) {
str = "/" + regexp.raw_source + str.slice(str.lastIndexOf("/"));
}
str = output.to_utf8(str);
output.print(str);
var p = output.parent();
2018-06-13 10:50:22 +00:00
if (p instanceof AST.Binary && /^in/.test(p.operator) && p.left === self)
output.print(" ");
2012-08-16 15:11:04 +00:00
});
function force_statement(stat, output) {
2018-03-15 07:46:45 +00:00
if (output.option("braces")) {
make_block(stat, output);
} else {
2018-06-13 10:50:22 +00:00
if (!stat || stat instanceof AST.EmptyStatement)
output.force_semicolon();
else
stat.print(output);
}
}
2018-06-13 10:50:22 +00:00
// self should be AST.New. decide if we want to show parens or not.
2016-06-10 13:42:55 +00:00
function need_constructor_parens(self, output) {
// Always print parentheses with arguments
if (self.args.length > 0) return true;
return output.option("beautify");
}
function best_of(a) {
var best = a[0], len = best.length;
for (var i = 1; i < a.length; ++i) {
if (a[i].length < len) {
best = a[i];
len = best.length;
}
}
return best;
}
function make_num(num) {
2018-04-27 18:47:49 +00:00
var str = num.toString(10).replace(/^0\./, ".").replace("e+", "e");
var candidates = [ str ];
if (Math.floor(num) === num) {
2018-04-27 18:47:49 +00:00
if (num < 0) {
candidates.push("-0x" + (-num).toString(16).toLowerCase());
} else {
2018-04-27 18:47:49 +00:00
candidates.push("0x" + num.toString(16).toLowerCase());
}
}
2018-04-27 18:47:49 +00:00
var match, len, digits;
if (match = /^\.0+/.exec(str)) {
len = match[0].length;
digits = str.slice(len);
candidates.push(digits + "e-" + (digits.length + len - 1));
} else if (match = /0+$/.exec(str)) {
len = match[0].length;
candidates.push(str.slice(0, -len) + "e" + len);
} else if (match = /^(\d)\.(\d+)e(-?\d+)$/.exec(str)) {
candidates.push(match[1] + match[2] + "e" + (match[3] - match[2].length));
}
return best_of(candidates);
}
function make_block(stmt, output) {
2018-06-13 10:50:22 +00:00
if (!stmt || stmt instanceof AST.EmptyStatement)
output.print("{}");
2018-06-13 10:50:22 +00:00
else if (stmt instanceof AST.BlockStatement)
stmt.print(output);
2018-06-06 09:50:56 +00:00
else output.with_block(function() {
output.indent();
stmt.print(output);
output.newline();
});
}
/* -----[ source map generators ]----- */
function DEFMAP(nodetype, generator) {
nodetype.forEach(function(nodetype) {
nodetype.DEFMETHOD("add_source_map", generator);
});
}
DEFMAP([
// 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.
2018-06-13 10:50:22 +00:00
AST.Node,
// since the label symbol will mark it
2018-06-13 10:50:22 +00:00
AST.LabeledStatement,
AST.Toplevel,
], noop);
// XXX: I'm not exactly sure if we need it for all of these nodes,
// or if we should add even more.
DEFMAP([
2018-06-13 10:50:22 +00:00
AST.Array,
AST.BlockStatement,
AST.Catch,
AST.Constant,
AST.Debugger,
AST.Definitions,
AST.Directive,
AST.Finally,
AST.Jump,
AST.Lambda,
AST.New,
AST.Object,
AST.StatementWithBody,
AST.Symbol,
AST.Switch,
AST.SwitchBranch,
AST.Try,
], function(output) {
output.add_mapping(this.start);
});
DEFMAP([
2018-06-13 10:50:22 +00:00
AST.ObjectGetter,
AST.ObjectSetter,
], function(output) {
output.add_mapping(this.start, this.key.name);
});
2018-06-13 10:50:22 +00:00
DEFMAP([ AST.ObjectProperty ], function(output) {
output.add_mapping(this.start, this.key);
});
})();
2018-06-09 17:40:40 +00:00
merge(exports, {
EXPECT_DIRECTIVE : EXPECT_DIRECTIVE,
is_some_comments : is_some_comments,
OutputStream : OutputStream,
});