implement --expression (#5607)

This commit is contained in:
Alex Lam S.L
2022-08-07 20:42:18 +01:00
committed by GitHub
parent 07953b36b0
commit f451a7ad79
10 changed files with 143 additions and 50 deletions

View File

@@ -54,8 +54,6 @@ a double dash to prevent input files being used as option arguments:
modules and Userscripts that may modules and Userscripts that may
be anonymous function wrapped (IIFE) be anonymous function wrapped (IIFE)
by the .user.js engine `caller`. by the .user.js engine `caller`.
`expression` Parse a single expression, rather than
a program (for parsing JSON).
`spidermonkey` Assume input files are SpiderMonkey `spidermonkey` Assume input files are SpiderMonkey
AST format (as JSON). AST format (as JSON).
-c, --compress [options] Enable compressor/specify compressor options: -c, --compress [options] Enable compressor/specify compressor options:
@@ -111,6 +109,8 @@ a double dash to prevent input files being used as option arguments:
-d, --define <expr>[=value] Global definitions. -d, --define <expr>[=value] Global definitions.
-e, --enclose [arg[:value]] Embed everything in a big function, with configurable -e, --enclose [arg[:value]] Embed everything in a big function, with configurable
argument(s) & value(s). argument(s) & value(s).
--expression Parse a single expression, rather than a program
(for parsing JSON).
--ie Support non-standard Internet Explorer. --ie Support non-standard Internet Explorer.
Equivalent to setting `ie: true` in `minify()` Equivalent to setting `ie: true` in `minify()`
for `compress`, `mangle` and `output` options. for `compress`, `mangle` and `output` options.
@@ -504,6 +504,8 @@ if (result.error) throw result.error;
- `compress` (default: `{}`) — pass `false` to skip compressing entirely. - `compress` (default: `{}`) — pass `false` to skip compressing entirely.
Pass an object to specify custom [compress options](#compress-options). 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. - `ie` (default: `false`) — enable workarounds for Internet Explorer bugs.
- `keep_fargs` (default: `false`) — pass `true` to prevent discarding or mangling - `keep_fargs` (default: `false`) — pass `true` to prevent discarding or mangling
@@ -633,8 +635,6 @@ to be `false` and all symbol names will be omitted.
- `bare_returns` (default: `false`) — support top level `return` statements - `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 - `html5_comments` (default: `true`) — process HTML comment as workaround for
browsers which do not recognise `<script>` tags browsers which do not recognise `<script>` tags

View File

@@ -104,6 +104,7 @@ function process_option(name, no_value) {
" --config-file <file> Read minify() options from JSON file.", " --config-file <file> Read minify() options from JSON file.",
" -d, --define <expr>[=value] Global definitions.", " -d, --define <expr>[=value] Global definitions.",
" -e, --enclose [arg[,...][:value[,...]]] Embed everything in a big function, with configurable argument(s) & value(s).", " -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.", " --ie Support non-standard Internet Explorer.",
" --keep-fargs Do not mangle/drop function arguments.", " --keep-fargs Do not mangle/drop function arguments.",
" --keep-fnames Do not mangle/drop function names. Useful for code relying on Function.prototype.name.", " --keep-fnames Do not mangle/drop function names. Useful for code relying on Function.prototype.name.",
@@ -151,6 +152,7 @@ function process_option(name, no_value) {
options[name] = read_value(); options[name] = read_value();
break; break;
case "annotations": case "annotations":
case "expression":
case "ie": case "ie":
case "ie8": case "ie8":
case "module": case "module":

View File

@@ -343,6 +343,35 @@ Compressor.prototype.compress = function(node) {
}); });
self.transform(tt); 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) { function read_property(obj, node) {
var key = node.get_property(); var key = node.get_property();

View File

@@ -76,6 +76,7 @@ function minify(files, options) {
annotations: undefined, annotations: undefined,
compress: {}, compress: {},
enclose: false, enclose: false,
expression: false,
ie: false, ie: false,
ie8: false, ie8: false,
keep_fargs: false, keep_fargs: false,
@@ -98,6 +99,7 @@ function minify(files, options) {
if (options.validate) AST_Node.enable_validation(); if (options.validate) AST_Node.enable_validation();
var timings = options.timings && { start: Date.now() }; var timings = options.timings && { start: Date.now() };
if (options.annotations !== undefined) set_shorthand("annotations", options, [ "compress", "output" ]); 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.ie8) options.ie = options.ie || options.ie8;
if (options.ie) set_shorthand("ie", options, [ "compress", "mangle", "output", "rename" ]); if (options.ie) set_shorthand("ie", options, [ "compress", "mangle", "output", "rename" ]);
if (options.keep_fargs) set_shorthand("keep_fargs", options, [ "compress", "mangle", "rename" ]); if (options.keep_fargs) set_shorthand("keep_fargs", options, [ "compress", "mangle", "rename" ]);
@@ -153,13 +155,11 @@ function minify(files, options) {
}, options.warnings == "verbose"); }, options.warnings == "verbose");
if (timings) timings.parse = Date.now(); if (timings) timings.parse = Date.now();
var toplevel; var toplevel;
if (files instanceof AST_Toplevel) { options.parse = options.parse || {};
if (files instanceof AST_Node) {
toplevel = files; toplevel = files;
} else { } else {
if (typeof files == "string") { if (typeof files == "string") files = [ files ];
files = [ files ];
}
options.parse = options.parse || {};
options.parse.toplevel = null; options.parse.toplevel = null;
var source_map_content = options.sourceMap && options.sourceMap.content; var source_map_content = options.sourceMap && options.sourceMap.content;
if (typeof source_map_content == "string" && source_map_content != "inline") { if (typeof source_map_content == "string" && source_map_content != "inline") {
@@ -171,17 +171,14 @@ function minify(files, options) {
options.parse.toplevel = toplevel = parse(files[name], options.parse); options.parse.toplevel = toplevel = parse(files[name], options.parse);
if (source_map_content == "inline") { if (source_map_content == "inline") {
var inlined_content = read_source_map(name, toplevel); var inlined_content = read_source_map(name, toplevel);
if (inlined_content) { if (inlined_content) options.sourceMap.orig[name] = parse_source_map(inlined_content);
options.sourceMap.orig[name] = parse_source_map(inlined_content);
}
} else if (source_map_content) { } else if (source_map_content) {
options.sourceMap.orig[name] = source_map_content; options.sourceMap.orig[name] = source_map_content;
} }
} }
} }
if (quoted_props) { if (options.parse.expression) toplevel = toplevel.wrap_expression();
reserve_quoted_keys(toplevel, quoted_props); if (quoted_props) reserve_quoted_keys(toplevel, quoted_props);
}
[ "enclose", "wrap" ].forEach(function(action) { [ "enclose", "wrap" ].forEach(function(action) {
var option = options[action]; var option = options[action];
if (!option) return; if (!option) return;
@@ -209,6 +206,7 @@ function minify(files, options) {
} }
if (timings) timings.properties = Date.now(); if (timings) timings.properties = Date.now();
if (options.mangle && options.mangle.properties) mangle_properties(toplevel, options.mangle.properties); 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(); if (timings) timings.output = Date.now();
var result = {}; var result = {};
var output = defaults(options.output, { var output = defaults(options.output, {

View File

@@ -60,8 +60,9 @@ function log() {
console.log("%s", tmpl.apply(null, arguments)); console.log("%s", tmpl.apply(null, arguments));
} }
function make_code(ast, options) { function make_code(ast, options, expression) {
var stream = U.OutputStream(options); var stream = U.OutputStream(options);
if (expression) ast = ast.clone(true).unwrap_expression();
ast.print(stream); ast.print(stream);
return stream.get(); return stream.get();
} }
@@ -178,9 +179,18 @@ function parse_test(file) {
// Try to reminify original input with standard options // Try to reminify original input with standard options
// to see if it matches expect_stdout. // 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++) { for (var i = 0; i < minify_options.length; i++) {
var options = JSON.parse(minify_options[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_fargs",
"keep_fnames", "keep_fnames",
@@ -210,7 +220,7 @@ function reminify(orig_options, input_code, input_formatted, stdout) {
} else { } else {
var toplevel = sandbox.has_toplevel(options); var toplevel = sandbox.has_toplevel(options);
var expected = stdout[toplevel ? 1 : 0]; 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) { if (typeof expected != "string" && typeof actual != "string" && expected.name == actual.name) {
actual = expected; actual = expected;
} }
@@ -245,18 +255,23 @@ function reminify(orig_options, input_code, input_formatted, stdout) {
return true; return true;
} }
function run_code(expression, code, toplevel) {
return sandbox.run_code(expression ? "console.log(" + code + ");" : code, toplevel);
}
function test_case(test) { function test_case(test) {
log(" Running test [{name}]", { name: test.name }); log(" Running test [{name}]", { name: test.name });
U.AST_Node.enable_validation(); U.AST_Node.enable_validation();
var output_options = test.beautify || {}; var output_options = test.beautify || {};
var expect; var expect;
if (test.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 { } else {
expect = test.expect_exact; expect = test.expect_exact;
} }
var input = to_toplevel(test.input, test.mangle); var input = to_toplevel(test.input, test.mangle, test.expression);
var input_code = make_code(input); var input_code = make_code(input, {}, test.expression);
var input_formatted = make_code(test.input, { var input_formatted = make_code(test.input, {
annotations: true, annotations: true,
beautify: true, beautify: true,
@@ -266,7 +281,7 @@ function test_case(test) {
}); });
try { try {
input.validate_ast(); input.validate_ast();
U.parse(input_code); U.parse(input_code, { expression: test.expression });
} catch (ex) { } catch (ex) {
log([ log([
"!!! Cannot parse input", "!!! Cannot parse input",
@@ -310,7 +325,7 @@ function test_case(test) {
output.mangle_names(test.mangle); output.mangle_names(test.mangle);
if (test.mangle.properties) U.mangle_properties(output, test.mangle.properties); 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(); U.AST_Node.log_function();
if (expect != output_code) { if (expect != output_code) {
log([ log([
@@ -333,7 +348,7 @@ function test_case(test) {
// expect == output // expect == output
try { try {
output.validate_ast(); output.validate_ast();
U.parse(output_code); U.parse(output_code, { expression: test.expression });
} catch (ex) { } catch (ex) {
log([ log([
"!!! Test matched expected result but cannot parse output", "!!! Test matched expected result but cannot parse output",
@@ -377,7 +392,7 @@ function test_case(test) {
} }
} }
if (test.expect_stdout && (!test.node_version || semver.satisfies(process.version, test.node_version))) { 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({ var toplevel = sandbox.has_toplevel({
compress: test.options, compress: test.options,
mangle: test.mangle mangle: test.mangle
@@ -406,7 +421,7 @@ function test_case(test) {
}); });
return false; 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)) { if (!sandbox.same_stdout(test.expect_stdout, actual)) {
log([ log([
"!!! failed", "!!! failed",
@@ -427,7 +442,7 @@ function test_case(test) {
}); });
return false; return false;
} }
if (!reminify(test.options, input_code, input_formatted, stdout)) { if (!reminify(test.expression, test.options, input_code, input_formatted, stdout)) {
return false; return false;
} }
} }
@@ -438,20 +453,29 @@ function tmpl() {
return U.string_template.apply(null, arguments); 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"); if (!(input instanceof U.AST_BlockStatement)) throw new Error("Unsupported input syntax");
var directive = true;
var offset = input.start.line; var offset = input.start.line;
var tokens = []; 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 (U.push_uniq(tokens, node.start)) node.start.line -= offset;
if (!directive || node === input) return; }));
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) { if (node instanceof U.AST_SimpleStatement && node.body instanceof U.AST_String) {
return new U.AST_Directive(node.body); return new U.AST_Directive(node.body);
} else {
directive = false;
} }
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); toplevel.figure_out_scope(mangle_options);
return toplevel; return toplevel;
} }

View File

@@ -131,19 +131,34 @@ valid_after_invalid_2: {
} }
issue_5368_1: { issue_5368_1: {
expression = true
options = { options = {
directives: true, directives: true,
expression: true, expression: true,
} }
input: { input: {
"foo"; "foo"
}
expect: {
"foo";
} }
expect_exact: '"foo"'
expect_stdout: "foo"
} }
issue_5368_2: { issue_5368_2: {
expression = true
options = {
directives: true,
expression: true,
}
input: {
(function() {
"foo";
})()
}
expect_exact: "function(){}()"
expect_stdout: "undefined"
}
issue_5368_3: {
options = { options = {
directives: true, directives: true,
expression: true, expression: true,

View File

@@ -108,6 +108,7 @@ safe_undefined: {
} }
negate_iife_3: { negate_iife_3: {
expression = true
options = { options = {
conditionals: true, conditionals: true,
expression: true, expression: true,
@@ -123,6 +124,7 @@ negate_iife_3: {
} }
negate_iife_3_off: { negate_iife_3_off: {
expression = true
options = { options = {
conditionals: true, conditionals: true,
expression: true, expression: true,
@@ -203,6 +205,7 @@ negate_iife_5_off: {
} }
issue_1254_negate_iife_true: { issue_1254_negate_iife_true: {
expression = true
options = { options = {
expression: true, expression: true,
inline: true, inline: true,
@@ -215,11 +218,12 @@ issue_1254_negate_iife_true: {
}; };
})()(); })()();
} }
expect_exact: 'void console.log("test");' expect_exact: 'void console.log("test")'
expect_stdout: true expect_stdout: true
} }
issue_1254_negate_iife_nested: { issue_1254_negate_iife_nested: {
expression = true
options = { options = {
expression: true, expression: true,
inline: true, inline: true,
@@ -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: { negate_iife_issue_1073: {

View File

@@ -68,6 +68,7 @@ drop_console_2: {
} }
drop_value: { drop_value: {
expression = true
options = { options = {
expression: true, expression: true,
side_effects: true, side_effects: true,
@@ -106,6 +107,7 @@ wrongly_optimized: {
} }
negate_iife_1: { negate_iife_1: {
expression = true
options = { options = {
expression: true, expression: true,
negate_iife: true, negate_iife: true,
@@ -119,6 +121,7 @@ negate_iife_1: {
} }
negate_iife_3: { negate_iife_3: {
expression = true
options = { options = {
conditionals: true, conditionals: true,
expression: true, expression: true,
@@ -133,6 +136,7 @@ negate_iife_3: {
} }
negate_iife_3_off: { negate_iife_3_off: {
expression = true
options = { options = {
conditionals: true, conditionals: true,
expression: true, expression: true,
@@ -215,6 +219,7 @@ negate_iife_5_off: {
} }
issue_1254_negate_iife_true: { issue_1254_negate_iife_true: {
expression = true
options = { options = {
expression: true, expression: true,
negate_iife: true, negate_iife: true,
@@ -226,11 +231,12 @@ issue_1254_negate_iife_true: {
}; };
})()(); })()();
} }
expect_exact: '(function(){return function(){console.log("test")}})()();' expect_exact: 'function(){return function(){console.log("test")}}()()'
expect_stdout: true expect_stdout: true
} }
issue_1254_negate_iife_nested: { issue_1254_negate_iife_nested: {
expression = true
options = { options = {
expression: true, expression: true,
negate_iife: true, negate_iife: true,
@@ -242,7 +248,7 @@ issue_1254_negate_iife_nested: {
}; };
})()()()()(); })()()()()();
} }
expect_exact: '(function(){return function(){console.log("test")}})()()()()();' expect_exact: 'function(){return function(){console.log("test")}}()()()()()'
expect_stdout: true expect_stdout: true
} }

View File

@@ -53,6 +53,23 @@ describe("bin/uglifyjs", function() {
done(); done();
}); });
}); });
it("Should work with --expression", function(done) {
exec([
uglifyjscmd,
"--expression",
"--compress",
"--mangle",
].join(" "), function(err, stdout) {
if (err) throw err;
assert.strictEqual(stdout, "function(n){for(;n(););return 42}(A)\n");
done();
}).stdin.end([
"function(x) {",
" while (x()) {}",
" return 42;",
"}(A)",
].join("\n"));
});
it("Should work with --source-map names=true", function(done) { it("Should work with --source-map names=true", function(done) {
exec([ exec([
uglifyjscmd, uglifyjscmd,
@@ -69,7 +86,7 @@ describe("bin/uglifyjs", function() {
" q: b", " q: b",
"};", "};",
"//# sourceMappingURL=data:application/json;charset=utf-8;base64,", "//# sourceMappingURL=data:application/json;charset=utf-8;base64,",
].join("\n") ].join("\n");
assert.strictEqual(stdout.slice(0, expected.length), expected); assert.strictEqual(stdout.slice(0, expected.length), expected);
var map = JSON.parse(to_ascii(stdout.slice(expected.length).trim())); var map = JSON.parse(to_ascii(stdout.slice(expected.length).trim()));
assert.deepEqual(map.names, [ "obj", "p", "a", "q", "b" ]); assert.deepEqual(map.names, [ "obj", "p", "a", "q", "b" ]);
@@ -97,7 +114,7 @@ describe("bin/uglifyjs", function() {
" q: b", " q: b",
"};", "};",
"//# sourceMappingURL=data:application/json;charset=utf-8;base64,", "//# sourceMappingURL=data:application/json;charset=utf-8;base64,",
].join("\n") ].join("\n");
assert.strictEqual(stdout.slice(0, expected.length), expected); assert.strictEqual(stdout.slice(0, expected.length), expected);
var map = JSON.parse(to_ascii(stdout.slice(expected.length).trim())); var map = JSON.parse(to_ascii(stdout.slice(expected.length).trim()));
assert.deepEqual(map.names, []); assert.deepEqual(map.names, []);

View File

@@ -50,12 +50,10 @@ describe("Number literals", function() {
"0.000_000_004_2e+1_0-0B101_010+0x2_A-0o5_2+4_2", "0.000_000_004_2e+1_0-0B101_010+0x2_A-0o5_2+4_2",
].forEach(function(code) { ].forEach(function(code) {
var result = UglifyJS.minify(code, { var result = UglifyJS.minify(code, {
compress: {
expression: true, expression: true,
},
}); });
if (result.error) throw result.error; if (result.error) throw result.error;
assert.strictEqual(result.code, "42;"); assert.strictEqual(result.code, "42");
}); });
}); });
it("Should reject invalid use of underscore", function() { it("Should reject invalid use of underscore", function() {