Skip to content

Commit

Permalink
implement --expression (#5607)
Browse files Browse the repository at this point in the history
  • Loading branch information
alexlamsl authored Aug 7, 2022
1 parent 07953b3 commit f451a7a
Show file tree
Hide file tree
Showing 10 changed files with 143 additions and 50 deletions.
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,6 @@ a double dash to prevent input files being used as option arguments:
modules and Userscripts that may
be anonymous function wrapped (IIFE)
by the .user.js engine `caller`.
`expression` Parse a single expression, rather than
a program (for parsing JSON).
`spidermonkey` Assume input files are SpiderMonkey
AST format (as JSON).
-c, --compress [options] Enable compressor/specify compressor options:
Expand Down Expand Up @@ -111,6 +109,8 @@ a double dash to prevent input files being used as option arguments:
-d, --define <expr>[=value] Global definitions.
-e, --enclose [arg[:value]] Embed everything in a big function, with configurable
argument(s) & value(s).
--expression Parse a single expression, rather than a program
(for parsing JSON).
--ie Support non-standard Internet Explorer.
Equivalent to setting `ie: true` in `minify()`
for `compress`, `mangle` and `output` options.
Expand Down Expand Up @@ -504,6 +504,8 @@ if (result.error) throw result.error;
- `compress` (default: `{}`) — pass `false` to skip compressing entirely.
Pass an object to specify custom [compress options](#compress-options).

- `expression` (default: `false`) — parse as a single expression, e.g. JSON.

- `ie` (default: `false`) — enable workarounds for Internet Explorer bugs.

- `keep_fargs` (default: `false`) — pass `true` to prevent discarding or mangling
Expand Down Expand Up @@ -633,8 +635,6 @@ to be `false` and all symbol names will be omitted.

- `bare_returns` (default: `false`) — support top level `return` statements

- `expression` (default: `false`) — parse as a single expression, e.g. JSON

- `html5_comments` (default: `true`) — process HTML comment as workaround for
browsers which do not recognise `<script>` tags

Expand Down
2 changes: 2 additions & 0 deletions bin/uglifyjs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ function process_option(name, no_value) {
" --config-file <file> Read minify() options from JSON file.",
" -d, --define <expr>[=value] Global definitions.",
" -e, --enclose [arg[,...][:value[,...]]] Embed everything in a big function, with configurable argument(s) & value(s).",
" --expression Parse a single expression, rather than a program.",
" --ie Support non-standard Internet Explorer.",
" --keep-fargs Do not mangle/drop function arguments.",
" --keep-fnames Do not mangle/drop function names. Useful for code relying on Function.prototype.name.",
Expand Down Expand Up @@ -151,6 +152,7 @@ function process_option(name, no_value) {
options[name] = read_value();
break;
case "annotations":
case "expression":
case "ie":
case "ie8":
case "module":
Expand Down
29 changes: 29 additions & 0 deletions lib/compress.js
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,35 @@ Compressor.prototype.compress = function(node) {
});
self.transform(tt);
});
AST_Toplevel.DEFMETHOD("unwrap_expression", function() {
var self = this;
switch (self.body.length) {
case 0:
return make_node(AST_UnaryPrefix, self, {
operator: "void",
expression: make_node(AST_Number, self, { value: 0 }),
});
case 1:
var stat = self.body[0];
if (stat instanceof AST_Directive) return make_node(AST_String, self, stat);
if (stat instanceof AST_SimpleStatement) return stat.body;
default:
return make_node(AST_Call, self, {
expression: make_node(AST_Function, self, {
argnames: [],
body: self.body,
}).init_vars(self),
args: [],
});
}
return self;
});
AST_Node.DEFMETHOD("wrap_expression", function() {
var self = this;
if (!is_statement(self)) self = make_node(AST_SimpleStatement, self, { body: self });
if (!(self instanceof AST_Toplevel)) self = make_node(AST_Toplevel, self, { body: [ self ] });
return self;
});

function read_property(obj, node) {
var key = node.get_property();
Expand Down
20 changes: 9 additions & 11 deletions lib/minify.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ function minify(files, options) {
annotations: undefined,
compress: {},
enclose: false,
expression: false,
ie: false,
ie8: false,
keep_fargs: false,
Expand All @@ -98,6 +99,7 @@ function minify(files, options) {
if (options.validate) AST_Node.enable_validation();
var timings = options.timings && { start: Date.now() };
if (options.annotations !== undefined) set_shorthand("annotations", options, [ "compress", "output" ]);
if (options.expression) set_shorthand("expression", options, [ "compress", "parse" ]);
if (options.ie8) options.ie = options.ie || options.ie8;
if (options.ie) set_shorthand("ie", options, [ "compress", "mangle", "output", "rename" ]);
if (options.keep_fargs) set_shorthand("keep_fargs", options, [ "compress", "mangle", "rename" ]);
Expand Down Expand Up @@ -153,13 +155,11 @@ function minify(files, options) {
}, options.warnings == "verbose");
if (timings) timings.parse = Date.now();
var toplevel;
if (files instanceof AST_Toplevel) {
options.parse = options.parse || {};
if (files instanceof AST_Node) {
toplevel = files;
} else {
if (typeof files == "string") {
files = [ files ];
}
options.parse = options.parse || {};
if (typeof files == "string") files = [ files ];
options.parse.toplevel = null;
var source_map_content = options.sourceMap && options.sourceMap.content;
if (typeof source_map_content == "string" && source_map_content != "inline") {
Expand All @@ -171,17 +171,14 @@ function minify(files, options) {
options.parse.toplevel = toplevel = parse(files[name], options.parse);
if (source_map_content == "inline") {
var inlined_content = read_source_map(name, toplevel);
if (inlined_content) {
options.sourceMap.orig[name] = parse_source_map(inlined_content);
}
if (inlined_content) options.sourceMap.orig[name] = parse_source_map(inlined_content);
} else if (source_map_content) {
options.sourceMap.orig[name] = source_map_content;
}
}
}
if (quoted_props) {
reserve_quoted_keys(toplevel, quoted_props);
}
if (options.parse.expression) toplevel = toplevel.wrap_expression();
if (quoted_props) reserve_quoted_keys(toplevel, quoted_props);
[ "enclose", "wrap" ].forEach(function(action) {
var option = options[action];
if (!option) return;
Expand Down Expand Up @@ -209,6 +206,7 @@ function minify(files, options) {
}
if (timings) timings.properties = Date.now();
if (options.mangle && options.mangle.properties) mangle_properties(toplevel, options.mangle.properties);
if (options.parse.expression) toplevel = toplevel.unwrap_expression();
if (timings) timings.output = Date.now();
var result = {};
var output = defaults(options.output, {
Expand Down
66 changes: 45 additions & 21 deletions test/compress.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,9 @@ function log() {
console.log("%s", tmpl.apply(null, arguments));
}

function make_code(ast, options) {
function make_code(ast, options, expression) {
var stream = U.OutputStream(options);
if (expression) ast = ast.clone(true).unwrap_expression();
ast.print(stream);
return stream.get();
}
Expand Down Expand Up @@ -178,9 +179,18 @@ function parse_test(file) {

// Try to reminify original input with standard options
// to see if it matches expect_stdout.
function reminify(orig_options, input_code, input_formatted, stdout) {
function reminify(expression, orig_options, input_code, input_formatted, stdout) {
for (var i = 0; i < minify_options.length; i++) {
var options = JSON.parse(minify_options[i]);
if (expression) {
if (!options.parse || typeof options.parse != "object") options.parse = {};
options.parse.expression = true;
if (options.compress == null) options.compress = {};
if (options.compress) {
if (typeof options.compress != "object") options.compress = {};
options.compress.expression = true;
}
}
[
"keep_fargs",
"keep_fnames",
Expand Down Expand Up @@ -210,7 +220,7 @@ function reminify(orig_options, input_code, input_formatted, stdout) {
} else {
var toplevel = sandbox.has_toplevel(options);
var expected = stdout[toplevel ? 1 : 0];
var actual = sandbox.run_code(result.code, toplevel);
var actual = run_code(expression, result.code, toplevel);
if (typeof expected != "string" && typeof actual != "string" && expected.name == actual.name) {
actual = expected;
}
Expand Down Expand Up @@ -245,18 +255,23 @@ function reminify(orig_options, input_code, input_formatted, stdout) {
return true;
}

function run_code(expression, code, toplevel) {
return sandbox.run_code(expression ? "console.log(" + code + ");" : code, toplevel);
}

function test_case(test) {
log(" Running test [{name}]", { name: test.name });
U.AST_Node.enable_validation();
var output_options = test.beautify || {};
var expect;
if (test.expect) {
expect = make_code(to_toplevel(test.expect, test.mangle), output_options);
expect = to_toplevel(test.expect, test.mangle, test.expression);
expect = make_code(expect, output_options, test.expression);
} else {
expect = test.expect_exact;
}
var input = to_toplevel(test.input, test.mangle);
var input_code = make_code(input);
var input = to_toplevel(test.input, test.mangle, test.expression);
var input_code = make_code(input, {}, test.expression);
var input_formatted = make_code(test.input, {
annotations: true,
beautify: true,
Expand All @@ -266,7 +281,7 @@ function test_case(test) {
});
try {
input.validate_ast();
U.parse(input_code);
U.parse(input_code, { expression: test.expression });
} catch (ex) {
log([
"!!! Cannot parse input",
Expand Down Expand Up @@ -310,7 +325,7 @@ function test_case(test) {
output.mangle_names(test.mangle);
if (test.mangle.properties) U.mangle_properties(output, test.mangle.properties);
}
var output_code = make_code(output, output_options);
var output_code = make_code(output, output_options, test.expression);
U.AST_Node.log_function();
if (expect != output_code) {
log([
Expand All @@ -333,7 +348,7 @@ function test_case(test) {
// expect == output
try {
output.validate_ast();
U.parse(output_code);
U.parse(output_code, { expression: test.expression });
} catch (ex) {
log([
"!!! Test matched expected result but cannot parse output",
Expand Down Expand Up @@ -377,7 +392,7 @@ function test_case(test) {
}
}
if (test.expect_stdout && (!test.node_version || semver.satisfies(process.version, test.node_version))) {
var stdout = [ sandbox.run_code(input_code), sandbox.run_code(input_code, true) ];
var stdout = [ run_code(test.expression, input_code), run_code(test.expression, input_code, true) ];
var toplevel = sandbox.has_toplevel({
compress: test.options,
mangle: test.mangle
Expand Down Expand Up @@ -406,7 +421,7 @@ function test_case(test) {
});
return false;
}
actual = sandbox.run_code(output_code, toplevel);
actual = run_code(test.expression, output_code, toplevel);
if (!sandbox.same_stdout(test.expect_stdout, actual)) {
log([
"!!! failed",
Expand All @@ -427,7 +442,7 @@ function test_case(test) {
});
return false;
}
if (!reminify(test.options, input_code, input_formatted, stdout)) {
if (!reminify(test.expression, test.options, input_code, input_formatted, stdout)) {
return false;
}
}
Expand All @@ -438,20 +453,29 @@ function tmpl() {
return U.string_template.apply(null, arguments);
}

function to_toplevel(input, mangle_options) {
function to_toplevel(input, mangle_options, expression) {
if (!(input instanceof U.AST_BlockStatement)) throw new Error("Unsupported input syntax");
var directive = true;
var offset = input.start.line;
var tokens = [];
var toplevel = new U.AST_Toplevel(input.transform(new U.TreeTransformer(function(node) {
input.walk(new U.TreeWalker(function(node) {
if (U.push_uniq(tokens, node.start)) node.start.line -= offset;
if (!directive || node === input) return;
if (node instanceof U.AST_SimpleStatement && node.body instanceof U.AST_String) {
return new U.AST_Directive(node.body);
} else {
}));
var toplevel;
if (!expression) {
var directive = true;
toplevel = new U.AST_Toplevel(input.transform(new U.TreeTransformer(function(node) {
if (!directive) return node;
if (node === input) return;
if (node instanceof U.AST_SimpleStatement && node.body instanceof U.AST_String) {
return new U.AST_Directive(node.body);
}
directive = false;
}
})));
})));
} else if (input.body.length == 1) {
toplevel = input.body[0].wrap_expression();
} else {
throw new Error("Invalid expression");
}
toplevel.figure_out_scope(mangle_options);
return toplevel;
}
23 changes: 19 additions & 4 deletions test/compress/directives.js
Original file line number Diff line number Diff line change
Expand Up @@ -131,19 +131,34 @@ valid_after_invalid_2: {
}

issue_5368_1: {
expression = true
options = {
directives: true,
expression: true,
}
input: {
"foo";
}
expect: {
"foo";
"foo"
}
expect_exact: '"foo"'
expect_stdout: "foo"
}

issue_5368_2: {
expression = true
options = {
directives: true,
expression: true,
}
input: {
(function() {
"foo";
})()
}
expect_exact: "function(){}()"
expect_stdout: "undefined"
}

issue_5368_3: {
options = {
directives: true,
expression: true,
Expand Down
8 changes: 6 additions & 2 deletions test/compress/issue-281.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ safe_undefined: {
}

negate_iife_3: {
expression = true
options = {
conditionals: true,
expression: true,
Expand All @@ -123,6 +124,7 @@ negate_iife_3: {
}

negate_iife_3_off: {
expression = true
options = {
conditionals: true,
expression: true,
Expand Down Expand Up @@ -203,6 +205,7 @@ negate_iife_5_off: {
}

issue_1254_negate_iife_true: {
expression = true
options = {
expression: true,
inline: true,
Expand All @@ -215,11 +218,12 @@ issue_1254_negate_iife_true: {
};
})()();
}
expect_exact: 'void console.log("test");'
expect_exact: 'void console.log("test")'
expect_stdout: true
}

issue_1254_negate_iife_nested: {
expression = true
options = {
expression: true,
inline: true,
Expand All @@ -232,7 +236,7 @@ issue_1254_negate_iife_nested: {
};
})()()()()();
}
expect_exact: '(void console.log("test"))()()();'
expect_exact: '(void console.log("test"))()()()'
}

negate_iife_issue_1073: {
Expand Down
Loading

0 comments on commit f451a7a

Please sign in to comment.