Compare commits

...

75 Commits

Author SHA1 Message Date
Alex Lam S.L
30ed8f5580 v3.9.3 2020-05-13 17:23:01 +08:00
Alex Lam S.L
dc9e7cd1fe suppress ufuzz false positives (#3893) 2020-05-13 07:07:49 +08:00
Alex Lam S.L
76f40e2528 fix corner case in collapse_vars (#3892)
fixes #3891
2020-05-12 22:28:55 +08:00
Alex Lam S.L
8024f7f7a8 fix corner case in ie8 (#3890)
fixes #3889
2020-05-12 19:28:29 +08:00
Alex Lam S.L
eb7fa25270 fix corner case in evaluate (#3888)
fixes #3887
2020-05-12 17:58:37 +08:00
Alex Lam S.L
ee7647dc67 fix corner case in collapse_vars (#3885)
fixes #3884
2020-05-12 04:01:14 +08:00
Alex Lam S.L
bd2f53bc8b fix corner case in evaluate (#3883)
fixes #3882
2020-05-12 03:24:44 +08:00
Alex Lam S.L
e8a7956b6f fix corner case in reduce_vars (#3881)
fixes #3880
2020-05-12 02:29:33 +08:00
Alex Lam S.L
2b24dc25fb fix corner cases in evaluate & reduce_vars (#3879)
fixes #3878
2020-05-11 22:46:00 +08:00
Alex Lam S.L
35cc5aa06f extend --reduce-test to cover minify() bugs (#3876) 2020-05-11 07:32:21 +08:00
Alex Lam S.L
c1dd49e075 fix corner case in comparisons (#3877) 2020-05-11 06:33:52 +08:00
Alex Lam S.L
c76ee4b868 enhance if_return (#3875) 2020-05-11 04:29:55 +08:00
Alex Lam S.L
e23bf48052 enhance evaluate & reduce_vars (#3873) 2020-05-11 03:08:05 +08:00
Alex Lam S.L
7e0ad232b0 retain @__PURE__ call when return value is used (#3874) 2020-05-11 01:07:05 +08:00
Alex Lam S.L
63adfb1590 fix corner case in hoist_props (#3872)
fixes #3871
2020-05-10 22:23:09 +08:00
Alex Lam S.L
f9806b43c3 enhance evaluate & reduce_vars (#3870) 2020-05-10 18:38:32 +08:00
Alex Lam S.L
c4c9c6d37d fix corner case in hoist_props (#3869)
fixes #3868
2020-05-10 10:35:24 +01:00
Alex Lam S.L
33f3b0c1d9 fix corner case in reduce_vars (#3867)
fixes #3866
2020-05-10 16:35:03 +08:00
Alex Lam S.L
abb8ae02a5 improve inline of /*@__PURE__*/ calls (#3865) 2020-05-10 07:16:09 +08:00
Alex Lam S.L
97728c4f0b improve AST validation (#3864) 2020-05-10 05:25:44 +08:00
Alex Lam S.L
f74b7f7401 implement AST validation (#3863) 2020-05-09 09:58:03 +08:00
Alex Lam S.L
b06fd8a933 improve fix for #3856 (#3862) 2020-05-09 08:50:25 +08:00
Alex Lam S.L
1bb0804d60 improve ufuzz detection of suspicious options (#3860) 2020-05-08 15:03:48 +08:00
Alex Lam S.L
998245ffd6 fix corner case in inline (#3859)
fixes #3858
2020-05-08 15:03:29 +08:00
Alex Lam S.L
7a033bb825 fix corner case in join_vars (#3857)
fixes #3856
2020-05-08 11:49:17 +08:00
Alex Lam S.L
a441b00951 suppress ufuzz false positives (#3855) 2020-05-08 03:21:44 +08:00
Alex Lam S.L
88985a46ed fix corner case in inline (#3853)
fixes #3852
2020-05-07 20:53:05 +08:00
Alex Lam S.L
34ead0430b enhance dead_code (#3849) 2020-05-06 05:02:35 +08:00
Alex Lam S.L
66ab2df97f fix intermittent CI failures on GitHub Actions (#3851) 2020-05-06 03:29:23 +08:00
kzc
b656f7c083 remove unused returns from tree walk (#3850) 2020-05-06 02:21:36 +08:00
Alex Lam S.L
873db281e8 improve TreeWalker performance (#3848) 2020-05-05 22:45:58 +08:00
Alex Lam S.L
6bf1486935 update links to repository after rename (#3847) 2020-05-05 21:07:33 +08:00
Alex Lam S.L
ffa1943177 fix corner case in reduce_vars (#3845)
fixes #3844
2020-05-04 03:30:10 +08:00
Alex Lam S.L
ac429dc8e1 enhance reduce_vars (#3843) 2020-05-03 22:52:43 +08:00
Alex Lam S.L
3766d5c962 enhance unused (#3839) 2020-05-03 17:38:28 +08:00
Alex Lam S.L
20f9a1d908 v3.9.2 2020-05-03 11:01:18 +08:00
Alex Lam S.L
dcb74f558e fix diagnostic text (#3838) 2020-05-01 18:55:06 +08:00
Alex Lam S.L
0794aaa2c2 fix corner case in inline (#3837)
fixes #3836
2020-05-01 17:20:23 +08:00
Alex Lam S.L
74801de315 fix corner cases in inline (#3834)
fixes #3833
fixes #3835
2020-05-01 09:06:40 +08:00
Alex Lam S.L
f80d5b8c9e enhance inline (#3832) 2020-05-01 04:33:46 +08:00
Alex Lam S.L
d900006973 fix corner case in dead_code (#3831)
fixes #3830
2020-04-30 21:52:57 +08:00
Alex Lam S.L
39f849590b update dependencies (#3828)
- actions/checkout@v2
2020-04-30 05:55:04 +08:00
Alex Lam S.L
818738beec fix corner case in ie8 (#3826)
fixes #3825
2020-04-27 16:51:21 +08:00
Alex Lam S.L
bc2a4a3bb8 fix corner case in ie8 (#3824)
fixes #3823
2020-04-27 06:44:53 +08:00
Alex Lam S.L
a4a8ccea8c fix corner case in inline (#3822)
fixes #3821
2020-04-27 03:31:19 +08:00
Alex Lam S.L
36dcfa3e82 improve suspicious option detection (#3820) 2020-04-27 00:59:26 +08:00
Alex Lam S.L
94f33570e3 fix corner case in --reduce-test (#3819) 2020-04-25 06:49:50 +08:00
Alex Lam S.L
44d6912a55 improve --reduce-test on Error.message (#3816)
closes #3815
2020-04-25 01:30:37 +08:00
Alex Lam S.L
3a4497a1c3 fix corner case in typeofs (#3818)
fixes #3817
2020-04-25 01:29:42 +08:00
Alex Lam S.L
3ee13cae02 improve compress (#3814)
- avoid identifier overflow through consecutive API calls
- simplify `reduce_vars`
- enhance `unsafe` `evaluate`
2020-04-24 06:50:53 +08:00
Alex Lam S.L
99cf3a38c5 improve unused efficiency (#3813) 2020-04-23 08:31:35 +08:00
Alex Lam S.L
3ae24329eb gate various label-related transformations (#3812) 2020-04-23 05:27:26 +08:00
Alex Lam S.L
01b13d797c enhance dead_code (#3811) 2020-04-22 18:22:45 +08:00
Alex Lam S.L
306e8e9873 adjust ufuzz schedule (#3810) 2020-04-22 16:03:15 +08:00
Alex Lam S.L
9577c8c1b7 fix corner case in conditionals (#3809)
fixes #3808
2020-04-22 06:30:08 +08:00
Alex Lam S.L
925a0ca1a0 adjust ufuzz schedule (#3807) 2020-04-22 02:58:02 +08:00
Alex Lam S.L
b694bfa351 speed up ufuzz on GitHub Actions (#3806) 2020-04-21 22:51:42 +08:00
Alex Lam S.L
a2fc32c64b enhance conditionals (#3805) 2020-04-20 09:42:13 +08:00
Alex Lam S.L
88504ab869 enhance join_vars (#3804) 2020-04-20 06:37:46 +08:00
Alex Lam S.L
e38754e802 fix corner case in functions & unused (#3803)
fixes #3802
2020-04-19 06:28:01 +08:00
Alex Lam S.L
eb6f32bfc3 enhance collapse_vars (#3801) 2020-04-19 05:04:21 +08:00
Alex Lam S.L
f110601fb4 enhance unused (#3800) 2020-04-19 00:10:24 +08:00
Alex Lam S.L
2a508c6e5f enhance collapse_vars (#3799) 2020-04-18 23:12:20 +08:00
Alex Lam S.L
fd6144d95b enhance conditionals (#3798) 2020-04-18 22:32:22 +08:00
Alex Lam S.L
60d4e7b09f enhance unused (#3794) 2020-04-18 18:52:44 +08:00
Alex Lam S.L
b38838c6bf fix corner case in join_vars (#3796)
fixes #3795
2020-04-18 18:08:05 +08:00
Alex Lam S.L
708973e51d report top-level default options (#3797) 2020-04-18 18:03:06 +08:00
Alex Lam S.L
dac9e69f9e enhance collapse_vars (#3793) 2020-04-18 10:06:20 +08:00
Alex Lam S.L
39aa33749b expand ufuzz patterns (#3792) 2020-04-18 07:12:13 +08:00
Alex Lam S.L
da68ec6e19 fix corner cases in join_vars (#3790)
fixes #3789
fixes #3791
2020-04-18 02:53:26 +08:00
Alex Lam S.L
15a3ebd467 fix corner cases in join_vars (#3787)
fixes #3786
fixes #3788
2020-04-17 21:19:18 +08:00
Alex Lam S.L
9110fac9a2 suppress mutation of input options (#3785) 2020-04-17 15:13:49 +08:00
Alex Lam S.L
83f42ede36 support optional output of names in source maps (#3784) 2020-04-17 07:20:48 +08:00
Alex Lam S.L
0ce71bbec0 enhance join_vars (#3783) 2020-04-17 05:31:33 +08:00
Alex Lam S.L
46d142cbf6 improve source-map generation (#3782)
- emit singleton segments to mark generated code from input
2020-04-16 23:30:25 +08:00
55 changed files with 4969 additions and 1369 deletions

View File

@@ -8,6 +8,8 @@ jobs:
os: [ ubuntu-latest, windows-latest ] os: [ ubuntu-latest, windows-latest ]
script: [ compress, mocha, release/benchmark, release/jetstream ] script: [ compress, mocha, release/benchmark, release/jetstream ]
exclude: exclude:
- node: "0.8"
script: release/benchmark
- node: "0.8" - node: "0.8"
script: release/jetstream script: release/jetstream
name: ${{ matrix.node }} ${{ matrix.os }} ${{ matrix.script }} name: ${{ matrix.node }} ${{ matrix.os }} ${{ matrix.script }}
@@ -16,7 +18,7 @@ jobs:
NODE: ${{ matrix.node }} NODE: ${{ matrix.node }}
TYPE: ${{ matrix.script }} TYPE: ${{ matrix.script }}
steps: steps:
- uses: actions/checkout@v1 - uses: actions/checkout@v2
- uses: actions/cache@v1 - uses: actions/cache@v1
with: with:
path: tmp path: tmp

View File

@@ -1,7 +1,7 @@
name: Fuzzing name: Fuzzing
on: on:
schedule: schedule:
- cron: "*/15 * * * *" - cron: "*/8 * * * *"
jobs: jobs:
ufuzz: ufuzz:
strategy: strategy:
@@ -11,7 +11,7 @@ jobs:
name: ${{ matrix.os }} name: ${{ matrix.os }}
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
steps: steps:
- uses: actions/checkout@v1 - uses: actions/checkout@v2
- name: Perform fuzzing - name: Perform fuzzing
shell: bash shell: bash
run: | run: |

View File

@@ -4,8 +4,8 @@ UglifyJS 3
UglifyJS is a JavaScript parser, minifier, compressor and beautifier toolkit. UglifyJS is a JavaScript parser, minifier, compressor and beautifier toolkit.
#### Note: #### Note:
- **`uglify-js@3` has a simplified [API](#api-reference) and [CLI](#command-line-usage) that is not backwards compatible with [`uglify-js@2`](https://github.com/mishoo/UglifyJS2/tree/v2.x)**. - **`uglify-js@3` has a simplified [API](#api-reference) and [CLI](#command-line-usage) that is not backwards compatible with [`uglify-js@2`](https://github.com/mishoo/UglifyJS/tree/v2.x)**.
- **Documentation for UglifyJS `2.x` releases can be found [here](https://github.com/mishoo/UglifyJS2/tree/v2.x)**. - **Documentation for UglifyJS `2.x` releases can be found [here](https://github.com/mishoo/UglifyJS/tree/v2.x)**.
- `uglify-js` only supports JavaScript (ECMAScript 5). - `uglify-js` only supports JavaScript (ECMAScript 5).
- To minify ECMAScript 2015 or above, transpile using tools like [Babel](https://babeljs.io/). - To minify ECMAScript 2015 or above, transpile using tools like [Babel](https://babeljs.io/).
@@ -126,6 +126,7 @@ a double dash to prevent input files being used as option arguments:
`includeSources` Pass this flag if you want to include `includeSources` Pass this flag if you want to include
the content of source files in the the content of source files in the
source map as sourcesContent property. source map as sourcesContent property.
`names` Include symbol names in the source map.
`root` Path to the original source to be included in `root` Path to the original source to be included in
the source map. the source map.
`url` If specified, path to the source map to append in `url` If specified, path to the source map to append in
@@ -159,6 +160,9 @@ Additional options:
- `--source-map "root='<URL>'"` to pass the URL where the original files can be found. - `--source-map "root='<URL>'"` to pass the URL where the original files can be found.
- `--source-map "names=false"` to omit symbol names if you want to reduce size
of the source map file.
- `--source-map "url='<URL>'"` to specify the URL where the source map can be found. - `--source-map "url='<URL>'"` to specify the URL where the source map can be found.
Otherwise UglifyJS assumes HTTP `X-SourceMap` is being used and will omit the Otherwise UglifyJS assumes HTTP `X-SourceMap` is being used and will omit the
`//# sourceMappingURL=` directive. `//# sourceMappingURL=` directive.
@@ -593,6 +597,9 @@ var result = UglifyJS.minify({"compiled.js": "compiled code"}, {
If you're using the `X-SourceMap` header instead, you can just omit `sourceMap.url`. If you're using the `X-SourceMap` header instead, you can just omit `sourceMap.url`.
If you wish to reduce file size of the source map, set option `sourceMap.names`
to be `false` and all symbol names will be omitted.
## Parse options ## Parse options
- `bare_returns` (default `false`) -- support top level `return` statements - `bare_returns` (default `false`) -- support top level `return` statements
@@ -906,7 +913,7 @@ can pass additional arguments that control the code output:
- `wrap_iife` (default `false`) -- pass `true` to wrap immediately invoked - `wrap_iife` (default `false`) -- pass `true` to wrap immediately invoked
function expressions. See function expressions. See
[#640](https://github.com/mishoo/UglifyJS2/issues/640) for more details. [#640](https://github.com/mishoo/UglifyJS/issues/640) for more details.
# Miscellaneous # Miscellaneous
@@ -1065,8 +1072,8 @@ var result = UglifyJS.minify(ast, {
### Working with Uglify AST ### Working with Uglify AST
Transversal and transformation of the native AST can be performed through Transversal and transformation of the native AST can be performed through
[`TreeWalker`](https://github.com/mishoo/UglifyJS2/blob/master/lib/ast.js) and [`TreeWalker`](https://github.com/mishoo/UglifyJS/blob/master/lib/ast.js) and
[`TreeTransformer`](https://github.com/mishoo/UglifyJS2/blob/master/lib/transform.js) [`TreeTransformer`](https://github.com/mishoo/UglifyJS/blob/master/lib/transform.js)
respectively. respectively.
### ESTree / SpiderMonkey AST ### ESTree / SpiderMonkey AST

View File

@@ -23,12 +23,29 @@ program.parse = undefined;
if (process.argv.indexOf("ast") >= 0) program.helpInformation = UglifyJS.describe_ast; if (process.argv.indexOf("ast") >= 0) program.helpInformation = UglifyJS.describe_ast;
else if (process.argv.indexOf("options") >= 0) program.helpInformation = function() { else if (process.argv.indexOf("options") >= 0) program.helpInformation = function() {
var text = []; var text = [];
var toplevels = [];
var padding = "";
var options = UglifyJS.default_options(); var options = UglifyJS.default_options();
for (var option in options) { for (var name in options) {
text.push("--" + (option == "output" ? "beautify" : option == "sourceMap" ? "source-map" : option) + " options:"); var option = options[name];
text.push(format_object(options[option])); if (option && typeof option == "object") {
text.push("--" + ({
output: "beautify",
sourceMap: "source-map",
}[name] || name) + " options:");
text.push(format_object(option));
text.push(""); text.push("");
} else {
if (padding.length < name.length) padding = Array(name.length + 1).join(" ");
toplevels.push([ {
keep_fnames: "keep-fnames",
nameCache: "name-cache",
}[name] || name, option ]);
} }
}
toplevels.forEach(function(tokens) {
text.push("--" + tokens[0] + padding.slice(tokens[0].length - 2) + tokens[1]);
});
return text.join("\n"); return text.join("\n");
}; };
program.option("-p, --parse <options>", "Specify parser options.", parse_js()); program.option("-p, --parse <options>", "Specify parser options.", parse_js());
@@ -51,6 +68,7 @@ program.option("--self", "Build UglifyJS as a library (implies --wrap UglifyJS)"
program.option("--source-map [options]", "Enable source map/specify source map options.", parse_js()); program.option("--source-map [options]", "Enable source map/specify source map options.", parse_js());
program.option("--timings", "Display operations run time on STDERR."); program.option("--timings", "Display operations run time on STDERR.");
program.option("--toplevel", "Compress and/or mangle variables in toplevel scope."); program.option("--toplevel", "Compress and/or mangle variables in toplevel scope.");
program.option("--validate", "Perform validation during AST manipulations.");
program.option("--verbose", "Print diagnostic messages."); program.option("--verbose", "Print diagnostic messages.");
program.option("--warn", "Print warning messages."); program.option("--warn", "Print warning messages.");
program.option("--wrap <name>", "Embed everything as a function with “exports” corresponding to “name” globally."); program.option("--wrap <name>", "Embed everything as a function with “exports” corresponding to “name” globally.");
@@ -74,6 +92,7 @@ if (!program.output && program.sourceMap && program.sourceMap.url != "inline") {
"mangle", "mangle",
"sourceMap", "sourceMap",
"toplevel", "toplevel",
"validate",
"wrap" "wrap"
].forEach(function(name) { ].forEach(function(name) {
if (name in program) { if (name in program) {
@@ -220,7 +239,10 @@ function run() {
// load on demand - assumes dev tree checked out // load on demand - assumes dev tree checked out
var reduce_test = require("../test/reduce"); var reduce_test = require("../test/reduce");
var testcase = files[0] || files[Object.keys(files)[0]]; var testcase = files[0] || files[Object.keys(files)[0]];
var result = reduce_test(testcase, options, {verbose: true}); var result = reduce_test(testcase, options, {
log: print_error,
verbose: true,
});
} }
else { else {
var result = UglifyJS.minify(files, options); var result = UglifyJS.minify(files, options);

View File

@@ -1,7 +1,7 @@
/*********************************************************************** /***********************************************************************
A JavaScript tokenizer / parser / beautifier / compressor. A JavaScript tokenizer / parser / beautifier / compressor.
https://github.com/mishoo/UglifyJS2 https://github.com/mishoo/UglifyJS
-------------------------------- (C) --------------------------------- -------------------------------- (C) ---------------------------------
@@ -110,12 +110,16 @@ var AST_Node = DEFNODE("Node", "start end", {
start: "[AST_Token] The first token of this node", start: "[AST_Token] The first token of this node",
end: "[AST_Token] The last token of this node" end: "[AST_Token] The last token of this node"
}, },
_walk: function(visitor) {
return visitor._visit(this);
},
walk: function(visitor) { walk: function(visitor) {
return this._walk(visitor); // not sure the indirection will be any help visitor.visit(this);
} },
_validate: noop,
validate: function() {
var ctor = this.CTOR;
do {
ctor.prototype._validate.call(this);
} while (ctor = ctor.BASE);
},
}, null); }, null);
(AST_Node.log_function = function(fn, verbose) { (AST_Node.log_function = function(fn, verbose) {
@@ -138,6 +142,32 @@ var AST_Node = DEFNODE("Node", "start end", {
} }
})(); })();
var restore_transforms = [];
AST_Node.enable_validation = function() {
AST_Node.disable_validation();
(function validate_transform(ctor) {
var transform = ctor.prototype.transform;
ctor.prototype.transform = function(tw, in_list) {
var node = transform.call(this, tw, in_list);
if (node instanceof AST_Node) {
node.validate();
} else if (!(node === null || in_list && List.is_op(node))) {
throw new Error("invalid transformed value: " + node);
}
return node;
};
restore_transforms.push(function() {
ctor.prototype.transform = transform;
});
ctor.SUBCLASSES.forEach(validate_transform);
})(this);
};
AST_Node.disable_validation = function() {
var restore;
while (restore = restore_transforms.pop()) restore();
};
/* -----[ statements ]----- */ /* -----[ statements ]----- */
var AST_Statement = DEFNODE("Statement", null, { var AST_Statement = DEFNODE("Statement", null, {
@@ -154,23 +184,37 @@ var AST_Directive = DEFNODE("Directive", "value quote", {
value: "[string] The value of this directive as a plain string (it's not an AST_String!)", value: "[string] The value of this directive as a plain string (it's not an AST_String!)",
quote: "[string] the original quote character" quote: "[string] the original quote character"
}, },
_validate: function() {
if (typeof this.value != "string") throw new Error("value must be string");
},
}, AST_Statement); }, AST_Statement);
function must_be_expression(node, prop) {
if (!(node[prop] instanceof AST_Node)) throw new Error(prop + " must be AST_Node");
if (node[prop] instanceof AST_Statement && !(node[prop] instanceof AST_Function)) {
throw new Error(prop + " cannot be AST_Statement");
}
}
var AST_SimpleStatement = DEFNODE("SimpleStatement", "body", { var AST_SimpleStatement = DEFNODE("SimpleStatement", "body", {
$documentation: "A statement consisting of an expression, i.e. a = 1 + 2", $documentation: "A statement consisting of an expression, i.e. a = 1 + 2",
$propdoc: { $propdoc: {
body: "[AST_Node] an expression node (should not be instanceof AST_Statement)" body: "[AST_Node] an expression node (should not be instanceof AST_Statement)"
}, },
_walk: function(visitor) { walk: function(visitor) {
return visitor._visit(this, function() { var node = this;
this.body._walk(visitor); visitor.visit(node, function() {
node.body.walk(visitor);
}); });
} },
_validate: function() {
must_be_expression(this, "body");
},
}, AST_Statement); }, AST_Statement);
function walk_body(node, visitor) { function walk_body(node, visitor) {
node.body.forEach(function(node) { node.body.forEach(function(node) {
node._walk(visitor); node.walk(visitor);
}); });
} }
@@ -179,11 +223,18 @@ var AST_Block = DEFNODE("Block", "body", {
$propdoc: { $propdoc: {
body: "[AST_Statement*] an array of statements" body: "[AST_Statement*] an array of statements"
}, },
_walk: function(visitor) { walk: function(visitor) {
return visitor._visit(this, function() { var node = this;
walk_body(this, visitor); visitor.visit(node, function() {
walk_body(node, visitor);
}); });
} },
_validate: function() {
this.body.forEach(function(node) {
if (!(node instanceof AST_Statement)) throw new Error("body must be AST_Statement[]");
if (node instanceof AST_Function) throw new Error("body cannot contain AST_Function");
});
},
}, AST_Statement); }, AST_Statement);
var AST_BlockStatement = DEFNODE("BlockStatement", null, { var AST_BlockStatement = DEFNODE("BlockStatement", null, {
@@ -198,7 +249,11 @@ var AST_StatementWithBody = DEFNODE("StatementWithBody", "body", {
$documentation: "Base class for all statements that contain one nested body: `For`, `ForIn`, `Do`, `While`, `With`", $documentation: "Base class for all statements that contain one nested body: `For`, `ForIn`, `Do`, `While`, `With`",
$propdoc: { $propdoc: {
body: "[AST_Statement] the body; this should always be present, even if it's an AST_EmptyStatement" body: "[AST_Statement] the body; this should always be present, even if it's an AST_EmptyStatement"
} },
_validate: function() {
if (!(this.body instanceof AST_Statement)) throw new Error("body must be AST_Statement");
if (this.body instanceof AST_Function) throw new Error("body cannot be AST_Function");
},
}, AST_Statement); }, AST_Statement);
var AST_LabeledStatement = DEFNODE("LabeledStatement", "label", { var AST_LabeledStatement = DEFNODE("LabeledStatement", "label", {
@@ -206,10 +261,11 @@ var AST_LabeledStatement = DEFNODE("LabeledStatement", "label", {
$propdoc: { $propdoc: {
label: "[AST_Label] a label definition" label: "[AST_Label] a label definition"
}, },
_walk: function(visitor) { walk: function(visitor) {
return visitor._visit(this, function() { var node = this;
this.label._walk(visitor); visitor.visit(node, function() {
this.body._walk(visitor); node.label.walk(visitor);
node.body.walk(visitor);
}); });
}, },
clone: function(deep) { clone: function(deep) {
@@ -225,7 +281,10 @@ var AST_LabeledStatement = DEFNODE("LabeledStatement", "label", {
})); }));
} }
return node; return node;
} },
_validate: function() {
if (!(this.label instanceof AST_Label)) throw new Error("label must be AST_Label");
},
}, AST_StatementWithBody); }, AST_StatementWithBody);
var AST_IterationStatement = DEFNODE("IterationStatement", null, { var AST_IterationStatement = DEFNODE("IterationStatement", null, {
@@ -236,25 +295,30 @@ var AST_DWLoop = DEFNODE("DWLoop", "condition", {
$documentation: "Base class for do/while statements", $documentation: "Base class for do/while statements",
$propdoc: { $propdoc: {
condition: "[AST_Node] the loop condition. Should not be instanceof AST_Statement" condition: "[AST_Node] the loop condition. Should not be instanceof AST_Statement"
} },
_validate: function() {
must_be_expression(this, "condition");
},
}, AST_IterationStatement); }, AST_IterationStatement);
var AST_Do = DEFNODE("Do", null, { var AST_Do = DEFNODE("Do", null, {
$documentation: "A `do` statement", $documentation: "A `do` statement",
_walk: function(visitor) { walk: function(visitor) {
return visitor._visit(this, function() { var node = this;
this.body._walk(visitor); visitor.visit(node, function() {
this.condition._walk(visitor); node.body.walk(visitor);
node.condition.walk(visitor);
}); });
} }
}, AST_DWLoop); }, AST_DWLoop);
var AST_While = DEFNODE("While", null, { var AST_While = DEFNODE("While", null, {
$documentation: "A `while` statement", $documentation: "A `while` statement",
_walk: function(visitor) { walk: function(visitor) {
return visitor._visit(this, function() { var node = this;
this.condition._walk(visitor); visitor.visit(node, function() {
this.body._walk(visitor); node.condition.walk(visitor);
node.body.walk(visitor);
}); });
} }
}, AST_DWLoop); }, AST_DWLoop);
@@ -266,14 +330,26 @@ var AST_For = DEFNODE("For", "init condition step", {
condition: "[AST_Node?] the `for` termination clause, or null if empty", condition: "[AST_Node?] the `for` termination clause, or null if empty",
step: "[AST_Node?] the `for` update clause, or null if empty" step: "[AST_Node?] the `for` update clause, or null if empty"
}, },
_walk: function(visitor) { walk: function(visitor) {
return visitor._visit(this, function() { var node = this;
if (this.init) this.init._walk(visitor); visitor.visit(node, function() {
if (this.condition) this.condition._walk(visitor); if (node.init) node.init.walk(visitor);
if (this.step) this.step._walk(visitor); if (node.condition) node.condition.walk(visitor);
this.body._walk(visitor); if (node.step) node.step.walk(visitor);
node.body.walk(visitor);
}); });
},
_validate: function() {
if (this.init != null) {
if (!(this.init instanceof AST_Node)) throw new Error("init must be AST_Node");
if (this.init instanceof AST_Statement
&& !(this.init instanceof AST_Definitions || this.init instanceof AST_Function)) {
throw new Error("init cannot be AST_Statement");
} }
}
if (this.condition != null) must_be_expression(this, "condition");
if (this.step != null) must_be_expression(this, "step");
},
}, AST_IterationStatement); }, AST_IterationStatement);
var AST_ForIn = DEFNODE("ForIn", "init object", { var AST_ForIn = DEFNODE("ForIn", "init object", {
@@ -282,13 +358,22 @@ var AST_ForIn = DEFNODE("ForIn", "init object", {
init: "[AST_Node] the `for/in` initialization code", init: "[AST_Node] the `for/in` initialization code",
object: "[AST_Node] the object that we're looping through" object: "[AST_Node] the object that we're looping through"
}, },
_walk: function(visitor) { walk: function(visitor) {
return visitor._visit(this, function() { var node = this;
this.init._walk(visitor); visitor.visit(node, function() {
this.object._walk(visitor); node.init.walk(visitor);
this.body._walk(visitor); node.object.walk(visitor);
node.body.walk(visitor);
}); });
},
_validate: function() {
if (this.init instanceof AST_Definitions) {
if (this.init.definitions.length != 1) throw new Error("init must have single declaration");
} else if (!(this.init instanceof AST_PropAccess || this.init instanceof AST_SymbolRef)) {
throw new Error("init must be assignable");
} }
must_be_expression(this, "object");
},
}, AST_IterationStatement); }, AST_IterationStatement);
var AST_With = DEFNODE("With", "expression", { var AST_With = DEFNODE("With", "expression", {
@@ -296,12 +381,16 @@ var AST_With = DEFNODE("With", "expression", {
$propdoc: { $propdoc: {
expression: "[AST_Node] the `with` expression" expression: "[AST_Node] the `with` expression"
}, },
_walk: function(visitor) { walk: function(visitor) {
return visitor._visit(this, function() { var node = this;
this.expression._walk(visitor); visitor.visit(node, function() {
this.body._walk(visitor); node.expression.walk(visitor);
node.body.walk(visitor);
}); });
} },
_validate: function() {
must_be_expression(this, "expression");
},
}, AST_StatementWithBody); }, AST_StatementWithBody);
/* -----[ scope and functions ]----- */ /* -----[ scope and functions ]----- */
@@ -326,7 +415,12 @@ var AST_Scope = DEFNODE("Scope", "variables functions uses_with uses_eval parent
}, },
pinned: function() { pinned: function() {
return this.uses_eval || this.uses_with; return this.uses_eval || this.uses_with;
},
_validate: function() {
if (this.parent_scope != null) {
if (!(this.parent_scope instanceof AST_Scope)) throw new Error("parent_scope must be AST_Scope");
} }
},
}, AST_Block); }, AST_Block);
var AST_Toplevel = DEFNODE("Toplevel", "globals", { var AST_Toplevel = DEFNODE("Toplevel", "globals", {
@@ -380,27 +474,44 @@ var AST_Lambda = DEFNODE("Lambda", "name argnames uses_arguments length_read", {
argnames: "[AST_SymbolFunarg*] array of function arguments", argnames: "[AST_SymbolFunarg*] array of function arguments",
uses_arguments: "[boolean/S] tells whether this function accesses the arguments array" uses_arguments: "[boolean/S] tells whether this function accesses the arguments array"
}, },
_walk: function(visitor) { walk: function(visitor) {
return visitor._visit(this, function() { var node = this;
if (this.name) this.name._walk(visitor); visitor.visit(node, function() {
this.argnames.forEach(function(argname) { if (node.name) node.name.walk(visitor);
argname._walk(visitor); node.argnames.forEach(function(argname) {
argname.walk(visitor);
}); });
walk_body(this, visitor); walk_body(node, visitor);
}); });
} },
_validate: function() {
this.argnames.forEach(function(node) {
if (!(node instanceof AST_SymbolFunarg)) throw new Error("argnames must be AST_SymbolFunarg[]");
});
},
}, AST_Scope); }, AST_Scope);
var AST_Accessor = DEFNODE("Accessor", null, { var AST_Accessor = DEFNODE("Accessor", null, {
$documentation: "A setter/getter function. The `name` property is always null." $documentation: "A setter/getter function. The `name` property is always null.",
_validate: function() {
if (this.name != null) throw new Error("name must be null");
},
}, AST_Lambda); }, AST_Lambda);
var AST_Function = DEFNODE("Function", "inlined", { var AST_Function = DEFNODE("Function", "inlined", {
$documentation: "A function expression" $documentation: "A function expression",
_validate: function() {
if (this.name != null) {
if (!(this.name instanceof AST_SymbolLambda)) throw new Error("name must be AST_SymbolLambda");
}
},
}, AST_Lambda); }, AST_Lambda);
var AST_Defun = DEFNODE("Defun", "inlined", { var AST_Defun = DEFNODE("Defun", "inlined", {
$documentation: "A function definition" $documentation: "A function definition",
_validate: function() {
if (!(this.name instanceof AST_SymbolDefun)) throw new Error("name must be AST_SymbolDefun");
},
}, AST_Lambda); }, AST_Lambda);
/* -----[ JUMPS ]----- */ /* -----[ JUMPS ]----- */
@@ -414,19 +525,26 @@ var AST_Exit = DEFNODE("Exit", "value", {
$propdoc: { $propdoc: {
value: "[AST_Node?] the value returned or thrown by this statement; could be null for AST_Return" value: "[AST_Node?] the value returned or thrown by this statement; could be null for AST_Return"
}, },
_walk: function(visitor) { walk: function(visitor) {
return visitor._visit(this, this.value && function() { var node = this;
this.value._walk(visitor); visitor.visit(node, function() {
if (node.value) node.value.walk(visitor);
}); });
} }
}, AST_Jump); }, AST_Jump);
var AST_Return = DEFNODE("Return", null, { var AST_Return = DEFNODE("Return", null, {
$documentation: "A `return` statement" $documentation: "A `return` statement",
_validate: function() {
if (this.value != null) must_be_expression(this, "value");
},
}, AST_Exit); }, AST_Exit);
var AST_Throw = DEFNODE("Throw", null, { var AST_Throw = DEFNODE("Throw", null, {
$documentation: "A `throw` statement" $documentation: "A `throw` statement",
_validate: function() {
must_be_expression(this, "value");
},
}, AST_Exit); }, AST_Exit);
var AST_LoopControl = DEFNODE("LoopControl", "label", { var AST_LoopControl = DEFNODE("LoopControl", "label", {
@@ -434,11 +552,17 @@ var AST_LoopControl = DEFNODE("LoopControl", "label", {
$propdoc: { $propdoc: {
label: "[AST_LabelRef?] the label, or null if none", label: "[AST_LabelRef?] the label, or null if none",
}, },
_walk: function(visitor) { walk: function(visitor) {
return visitor._visit(this, this.label && function() { var node = this;
this.label._walk(visitor); visitor.visit(node, function() {
if (node.label) node.label.walk(visitor);
}); });
},
_validate: function() {
if (this.label != null) {
if (!(this.label instanceof AST_LabelRef)) throw new Error("label must be AST_LabelRef");
} }
},
}, AST_Jump); }, AST_Jump);
var AST_Break = DEFNODE("Break", null, { var AST_Break = DEFNODE("Break", null, {
@@ -457,13 +581,21 @@ var AST_If = DEFNODE("If", "condition alternative", {
condition: "[AST_Node] the `if` condition", condition: "[AST_Node] the `if` condition",
alternative: "[AST_Statement?] the `else` part, or null if not present" alternative: "[AST_Statement?] the `else` part, or null if not present"
}, },
_walk: function(visitor) { walk: function(visitor) {
return visitor._visit(this, function() { var node = this;
this.condition._walk(visitor); visitor.visit(node, function() {
this.body._walk(visitor); node.condition.walk(visitor);
if (this.alternative) this.alternative._walk(visitor); node.body.walk(visitor);
if (node.alternative) node.alternative.walk(visitor);
}); });
},
_validate: function() {
must_be_expression(this, "condition");
if (this.alternative != null) {
if (!(this.alternative instanceof AST_Statement)) throw new Error("alternative must be AST_Statement");
if (this.alternative instanceof AST_Function) throw new error("alternative cannot be AST_Function");
} }
},
}, AST_StatementWithBody); }, AST_StatementWithBody);
/* -----[ SWITCH ]----- */ /* -----[ SWITCH ]----- */
@@ -473,12 +605,16 @@ var AST_Switch = DEFNODE("Switch", "expression", {
$propdoc: { $propdoc: {
expression: "[AST_Node] the `switch` “discriminant”" expression: "[AST_Node] the `switch` “discriminant”"
}, },
_walk: function(visitor) { walk: function(visitor) {
return visitor._visit(this, function() { var node = this;
this.expression._walk(visitor); visitor.visit(node, function() {
walk_body(this, visitor); node.expression.walk(visitor);
walk_body(node, visitor);
}); });
} },
_validate: function() {
must_be_expression(this, "expression");
},
}, AST_Block); }, AST_Block);
var AST_SwitchBranch = DEFNODE("SwitchBranch", null, { var AST_SwitchBranch = DEFNODE("SwitchBranch", null, {
@@ -494,12 +630,16 @@ var AST_Case = DEFNODE("Case", "expression", {
$propdoc: { $propdoc: {
expression: "[AST_Node] the `case` expression" expression: "[AST_Node] the `case` expression"
}, },
_walk: function(visitor) { walk: function(visitor) {
return visitor._visit(this, function() { var node = this;
this.expression._walk(visitor); visitor.visit(node, function() {
walk_body(this, visitor); node.expression.walk(visitor);
walk_body(node, visitor);
}); });
} },
_validate: function() {
must_be_expression(this, "expression");
},
}, AST_SwitchBranch); }, AST_SwitchBranch);
/* -----[ EXCEPTIONS ]----- */ /* -----[ EXCEPTIONS ]----- */
@@ -510,13 +650,22 @@ var AST_Try = DEFNODE("Try", "bcatch bfinally", {
bcatch: "[AST_Catch?] the catch block, or null if not present", bcatch: "[AST_Catch?] the catch block, or null if not present",
bfinally: "[AST_Finally?] the finally block, or null if not present" bfinally: "[AST_Finally?] the finally block, or null if not present"
}, },
_walk: function(visitor) { walk: function(visitor) {
return visitor._visit(this, function() { var node = this;
walk_body(this, visitor); visitor.visit(node, function() {
if (this.bcatch) this.bcatch._walk(visitor); walk_body(node, visitor);
if (this.bfinally) this.bfinally._walk(visitor); if (node.bcatch) node.bcatch.walk(visitor);
if (node.bfinally) node.bfinally.walk(visitor);
}); });
},
_validate: function() {
if (this.bcatch != null) {
if (!(this.bcatch instanceof AST_Catch)) throw new Error("bcatch must be AST_Catch");
} }
if (this.bfinally != null) {
if (!(this.bfinally instanceof AST_Finally)) throw new Error("bfinally must be AST_Finally");
}
},
}, AST_Block); }, AST_Block);
var AST_Catch = DEFNODE("Catch", "argname", { var AST_Catch = DEFNODE("Catch", "argname", {
@@ -524,12 +673,16 @@ var AST_Catch = DEFNODE("Catch", "argname", {
$propdoc: { $propdoc: {
argname: "[AST_SymbolCatch] symbol for the exception" argname: "[AST_SymbolCatch] symbol for the exception"
}, },
_walk: function(visitor) { walk: function(visitor) {
return visitor._visit(this, function() { var node = this;
this.argname._walk(visitor); visitor.visit(node, function() {
walk_body(this, visitor); node.argname.walk(visitor);
walk_body(node, visitor);
}); });
} },
_validate: function() {
if (!(this.argname instanceof AST_SymbolCatch)) throw new Error("argname must be AST_SymbolCatch");
},
}, AST_Block); }, AST_Block);
var AST_Finally = DEFNODE("Finally", null, { var AST_Finally = DEFNODE("Finally", null, {
@@ -543,17 +696,23 @@ var AST_Definitions = DEFNODE("Definitions", "definitions", {
$propdoc: { $propdoc: {
definitions: "[AST_VarDef*] array of variable definitions" definitions: "[AST_VarDef*] array of variable definitions"
}, },
_walk: function(visitor) { walk: function(visitor) {
return visitor._visit(this, function() { var node = this;
this.definitions.forEach(function(defn) { visitor.visit(node, function() {
defn._walk(visitor); node.definitions.forEach(function(defn) {
defn.walk(visitor);
}); });
}); });
} }
}, AST_Statement); }, AST_Statement);
var AST_Var = DEFNODE("Var", null, { var AST_Var = DEFNODE("Var", null, {
$documentation: "A `var` statement" $documentation: "A `var` statement",
_validate: function() {
this.definitions.forEach(function(node) {
if (!(node instanceof AST_VarDef)) throw new Error("definitions must be AST_VarDef[]");
});
},
}, AST_Definitions); }, AST_Definitions);
var AST_VarDef = DEFNODE("VarDef", "name value", { var AST_VarDef = DEFNODE("VarDef", "name value", {
@@ -562,30 +721,49 @@ var AST_VarDef = DEFNODE("VarDef", "name value", {
name: "[AST_SymbolVar] name of the variable", name: "[AST_SymbolVar] name of the variable",
value: "[AST_Node?] initializer, or null of there's no initializer" value: "[AST_Node?] initializer, or null of there's no initializer"
}, },
_walk: function(visitor) { walk: function(visitor) {
return visitor._visit(this, function() { var node = this;
this.name._walk(visitor); visitor.visit(node, function() {
if (this.value) this.value._walk(visitor); node.name.walk(visitor);
if (node.value) node.value.walk(visitor);
}); });
} },
_validate: function() {
if (!(this.name instanceof AST_SymbolVar)) throw new Error("name must be AST_SymbolVar");
if (this.value != null) must_be_expression(this, "value");
},
}); });
/* -----[ OTHER ]----- */ /* -----[ OTHER ]----- */
var AST_Call = DEFNODE("Call", "expression args", { function must_be_expressions(node, prop) {
node[prop].forEach(function(node) {
if (!(node instanceof AST_Node)) throw new Error(prop + " must be AST_Node[]");
if (node instanceof AST_Statement && !(node instanceof AST_Function)) {
throw new Error(prop + " cannot contain AST_Statement");
}
});
}
var AST_Call = DEFNODE("Call", "expression args pure", {
$documentation: "A function call expression", $documentation: "A function call expression",
$propdoc: { $propdoc: {
expression: "[AST_Node] expression to invoke as function", expression: "[AST_Node] expression to invoke as function",
args: "[AST_Node*] array of arguments" args: "[AST_Node*] array of arguments"
}, },
_walk: function(visitor) { walk: function(visitor) {
return visitor._visit(this, function() { var node = this;
this.expression._walk(visitor); visitor.visit(node, function() {
this.args.forEach(function(node) { node.expression.walk(visitor);
node._walk(visitor); node.args.forEach(function(arg) {
arg.walk(visitor);
}); });
}); });
} },
_validate: function() {
must_be_expression(this, "expression");
must_be_expressions(this, "args");
},
}); });
var AST_New = DEFNODE("New", null, { var AST_New = DEFNODE("New", null, {
@@ -597,13 +775,18 @@ var AST_Sequence = DEFNODE("Sequence", "expressions", {
$propdoc: { $propdoc: {
expressions: "[AST_Node*] array of expressions (at least two)" expressions: "[AST_Node*] array of expressions (at least two)"
}, },
_walk: function(visitor) { walk: function(visitor) {
return visitor._visit(this, function() { var node = this;
this.expressions.forEach(function(node) { visitor.visit(node, function() {
node._walk(visitor); node.expressions.forEach(function(expr) {
expr.walk(visitor);
}); });
}); });
} },
_validate: function() {
if (this.expressions.length < 2) throw new Error("expressions must contain multiple elements");
must_be_expressions(this, "expressions");
},
}); });
var AST_PropAccess = DEFNODE("PropAccess", "expression property", { var AST_PropAccess = DEFNODE("PropAccess", "expression property", {
@@ -623,26 +806,37 @@ var AST_PropAccess = DEFNODE("PropAccess", "expression property", {
return; return;
} }
return p; return p;
} },
_validate: function() {
must_be_expression(this, "expression");
},
}); });
var AST_Dot = DEFNODE("Dot", null, { var AST_Dot = DEFNODE("Dot", null, {
$documentation: "A dotted property access expression", $documentation: "A dotted property access expression",
_walk: function(visitor) { walk: function(visitor) {
return visitor._visit(this, function() { var node = this;
this.expression._walk(visitor); visitor.visit(node, function() {
node.expression.walk(visitor);
}); });
} },
_validate: function() {
if (typeof this.property != "string") throw new Error("property must be string");
},
}, AST_PropAccess); }, AST_PropAccess);
var AST_Sub = DEFNODE("Sub", null, { var AST_Sub = DEFNODE("Sub", null, {
$documentation: "Index-style property access, i.e. `a[\"foo\"]`", $documentation: "Index-style property access, i.e. `a[\"foo\"]`",
_walk: function(visitor) { walk: function(visitor) {
return visitor._visit(this, function() { var node = this;
this.expression._walk(visitor); visitor.visit(node, function() {
this.property._walk(visitor); node.expression.walk(visitor);
node.property.walk(visitor);
}); });
} },
_validate: function() {
must_be_expression(this, "property");
},
}, AST_PropAccess); }, AST_PropAccess);
var AST_Unary = DEFNODE("Unary", "operator expression", { var AST_Unary = DEFNODE("Unary", "operator expression", {
@@ -651,11 +845,16 @@ var AST_Unary = DEFNODE("Unary", "operator expression", {
operator: "[string] the operator", operator: "[string] the operator",
expression: "[AST_Node] expression that this unary operator applies to" expression: "[AST_Node] expression that this unary operator applies to"
}, },
_walk: function(visitor) { walk: function(visitor) {
return visitor._visit(this, function() { var node = this;
this.expression._walk(visitor); visitor.visit(node, function() {
node.expression.walk(visitor);
}); });
} },
_validate: function() {
if (typeof this.operator != "string") throw new Error("operator must be string");
must_be_expression(this, "expression");
},
}); });
var AST_UnaryPrefix = DEFNODE("UnaryPrefix", null, { var AST_UnaryPrefix = DEFNODE("UnaryPrefix", null, {
@@ -673,12 +872,18 @@ var AST_Binary = DEFNODE("Binary", "operator left right", {
operator: "[string] the operator", operator: "[string] the operator",
right: "[AST_Node] right-hand side expression" right: "[AST_Node] right-hand side expression"
}, },
_walk: function(visitor) { walk: function(visitor) {
return visitor._visit(this, function() { var node = this;
this.left._walk(visitor); visitor.visit(node, function() {
this.right._walk(visitor); node.left.walk(visitor);
node.right.walk(visitor);
}); });
} },
_validate: function() {
must_be_expression(this, "left");
if (typeof this.operator != "string") throw new Error("operator must be string");
must_be_expression(this, "right");
},
}); });
var AST_Conditional = DEFNODE("Conditional", "condition consequent alternative", { var AST_Conditional = DEFNODE("Conditional", "condition consequent alternative", {
@@ -688,17 +893,26 @@ var AST_Conditional = DEFNODE("Conditional", "condition consequent alternative",
consequent: "[AST_Node]", consequent: "[AST_Node]",
alternative: "[AST_Node]" alternative: "[AST_Node]"
}, },
_walk: function(visitor) { walk: function(visitor) {
return visitor._visit(this, function() { var node = this;
this.condition._walk(visitor); visitor.visit(node, function() {
this.consequent._walk(visitor); node.condition.walk(visitor);
this.alternative._walk(visitor); node.consequent.walk(visitor);
node.alternative.walk(visitor);
}); });
} },
_validate: function() {
must_be_expression(this, "condition");
must_be_expression(this, "consequent");
must_be_expression(this, "alternative");
},
}); });
var AST_Assign = DEFNODE("Assign", null, { var AST_Assign = DEFNODE("Assign", null, {
$documentation: "An assignment expression — `a = b + 5`", $documentation: "An assignment expression — `a = b + 5`",
_validate: function() {
if (this.operator.indexOf("=") < 0) throw new Error('operator must contain "="');
},
}, AST_Binary); }, AST_Binary);
/* -----[ LITERALS ]----- */ /* -----[ LITERALS ]----- */
@@ -708,13 +922,17 @@ var AST_Array = DEFNODE("Array", "elements", {
$propdoc: { $propdoc: {
elements: "[AST_Node*] array of elements" elements: "[AST_Node*] array of elements"
}, },
_walk: function(visitor) { walk: function(visitor) {
return visitor._visit(this, function() { var node = this;
this.elements.forEach(function(element) { visitor.visit(node, function() {
element._walk(visitor); node.elements.forEach(function(element) {
element.walk(visitor);
}); });
}); });
} },
_validate: function() {
must_be_expressions(this, "elements");
},
}); });
var AST_Object = DEFNODE("Object", "properties", { var AST_Object = DEFNODE("Object", "properties", {
@@ -722,13 +940,19 @@ var AST_Object = DEFNODE("Object", "properties", {
$propdoc: { $propdoc: {
properties: "[AST_ObjectProperty*] array of properties" properties: "[AST_ObjectProperty*] array of properties"
}, },
_walk: function(visitor) { walk: function(visitor) {
return visitor._visit(this, function() { var node = this;
this.properties.forEach(function(prop) { visitor.visit(node, function() {
prop._walk(visitor); node.properties.forEach(function(prop) {
prop.walk(visitor);
}); });
}); });
} },
_validate: function() {
this.properties.forEach(function(node) {
if (!(node instanceof AST_ObjectProperty)) throw new Error("properties must be AST_ObjectProperty[]");
});
},
}); });
var AST_ObjectProperty = DEFNODE("ObjectProperty", "key value", { var AST_ObjectProperty = DEFNODE("ObjectProperty", "key value", {
@@ -737,9 +961,10 @@ var AST_ObjectProperty = DEFNODE("ObjectProperty", "key value", {
key: "[string|AST_SymbolAccessor] property name. For ObjectKeyVal this is a string. For getters and setters this is an AST_SymbolAccessor.", key: "[string|AST_SymbolAccessor] property name. For ObjectKeyVal this is a string. For getters and setters this is an AST_SymbolAccessor.",
value: "[AST_Node] property value. For getters and setters this is an AST_Accessor." value: "[AST_Node] property value. For getters and setters this is an AST_Accessor."
}, },
_walk: function(visitor) { walk: function(visitor) {
return visitor._visit(this, function() { var node = this;
this.value._walk(visitor); visitor.visit(node, function() {
node.value.walk(visitor);
}); });
} }
}); });
@@ -748,15 +973,27 @@ var AST_ObjectKeyVal = DEFNODE("ObjectKeyVal", "quote", {
$documentation: "A key: value object property", $documentation: "A key: value object property",
$propdoc: { $propdoc: {
quote: "[string] the original quote character" quote: "[string] the original quote character"
} },
_validate: function() {
if (typeof this.key != "string") throw new Error("key must be string");
must_be_expression(this, "value");
},
}, AST_ObjectProperty); }, AST_ObjectProperty);
var AST_ObjectSetter = DEFNODE("ObjectSetter", null, { var AST_ObjectSetter = DEFNODE("ObjectSetter", null, {
$documentation: "An object setter property", $documentation: "An object setter property",
_validate: function() {
if (!(this.key instanceof AST_SymbolAccessor)) throw new Error("key must be AST_SymbolAccessor");
if (!(this.value instanceof AST_Accessor)) throw new Error("value must be AST_Accessor");
},
}, AST_ObjectProperty); }, AST_ObjectProperty);
var AST_ObjectGetter = DEFNODE("ObjectGetter", null, { var AST_ObjectGetter = DEFNODE("ObjectGetter", null, {
$documentation: "An object getter property", $documentation: "An object getter property",
_validate: function() {
if (!(this.key instanceof AST_SymbolAccessor)) throw new Error("key must be AST_SymbolAccessor");
if (!(this.value instanceof AST_Accessor)) throw new Error("value must be AST_Accessor");
},
}, AST_ObjectProperty); }, AST_ObjectProperty);
var AST_Symbol = DEFNODE("Symbol", "scope name thedef", { var AST_Symbol = DEFNODE("Symbol", "scope name thedef", {
@@ -766,6 +1003,9 @@ var AST_Symbol = DEFNODE("Symbol", "scope name thedef", {
thedef: "[SymbolDef/S] the definition of this symbol" thedef: "[SymbolDef/S] the definition of this symbol"
}, },
$documentation: "Base class for all symbols", $documentation: "Base class for all symbols",
_validate: function() {
if (typeof this.name != "string") throw new Error("name must be string");
},
}); });
var AST_SymbolAccessor = DEFNODE("SymbolAccessor", null, { var AST_SymbolAccessor = DEFNODE("SymbolAccessor", null, {
@@ -817,6 +1057,9 @@ var AST_LabelRef = DEFNODE("LabelRef", null, {
var AST_This = DEFNODE("This", null, { var AST_This = DEFNODE("This", null, {
$documentation: "The `this` symbol", $documentation: "The `this` symbol",
_validate: function() {
if (this.name !== "this") throw new Error('name must be "this"');
},
}, AST_Symbol); }, AST_Symbol);
var AST_Constant = DEFNODE("Constant", null, { var AST_Constant = DEFNODE("Constant", null, {
@@ -828,21 +1071,30 @@ var AST_String = DEFNODE("String", "value quote", {
$propdoc: { $propdoc: {
value: "[string] the contents of this string", value: "[string] the contents of this string",
quote: "[string] the original quote character" quote: "[string] the original quote character"
} },
_validate: function() {
if (typeof this.value != "string") throw new Error("value must be string");
},
}, AST_Constant); }, AST_Constant);
var AST_Number = DEFNODE("Number", "value", { var AST_Number = DEFNODE("Number", "value", {
$documentation: "A number literal", $documentation: "A number literal",
$propdoc: { $propdoc: {
value: "[number] the numeric value", value: "[number] the numeric value",
} },
_validate: function() {
if (typeof this.value != "number") throw new Error("value must be number");
},
}, AST_Constant); }, AST_Constant);
var AST_RegExp = DEFNODE("RegExp", "value", { var AST_RegExp = DEFNODE("RegExp", "value", {
$documentation: "A regexp literal", $documentation: "A regexp literal",
$propdoc: { $propdoc: {
value: "[RegExp] the actual regexp" value: "[RegExp] the actual regexp"
} },
_validate: function() {
if (!(this.value instanceof RegExp)) throw new Error("value must be RegExp");
},
}, AST_Constant); }, AST_Constant);
var AST_Atom = DEFNODE("Atom", null, { var AST_Atom = DEFNODE("Atom", null, {
@@ -891,21 +1143,16 @@ var AST_True = DEFNODE("True", null, {
/* -----[ TreeWalker ]----- */ /* -----[ TreeWalker ]----- */
function TreeWalker(callback) { function TreeWalker(callback) {
this.visit = callback; this.callback = callback;
this.stack = [];
this.directives = Object.create(null); this.directives = Object.create(null);
this.stack = [];
} }
TreeWalker.prototype = { TreeWalker.prototype = {
_visit: function(node, descend) { visit: function(node, descend) {
this.push(node); this.push(node);
var ret = this.visit(node, descend ? function() { var done = this.callback(node, descend || noop);
descend.call(node); if (!done && descend) descend();
} : noop);
if (!ret && descend) {
descend.call(node);
}
this.pop(); this.pop();
return ret;
}, },
parent: function(n) { parent: function(n) {
return this.stack[this.stack.length - 2 - (n || 0)]; return this.stack[this.stack.length - 2 - (n || 0)];

File diff suppressed because it is too large Load Diff

View File

@@ -85,9 +85,11 @@ function minify(files, options) {
sourceMap: false, sourceMap: false,
timings: false, timings: false,
toplevel: false, toplevel: false,
validate: false,
warnings: false, warnings: false,
wrap: false, wrap: false,
}, true); }, true);
if (options.validate) AST_Node.enable_validation();
var timings = options.timings && { var timings = options.timings && {
start: Date.now() start: Date.now()
}; };
@@ -129,6 +131,7 @@ function minify(files, options) {
content: null, content: null,
filename: null, filename: null,
includeSources: false, includeSources: false,
names: true,
root: null, root: null,
url: null, url: null,
}, true); }, true);
@@ -138,7 +141,7 @@ function minify(files, options) {
warnings.push(warning); warnings.push(warning);
}, options.warnings == "verbose"); }, options.warnings == "verbose");
if (timings) timings.parse = Date.now(); if (timings) timings.parse = Date.now();
var source_maps, toplevel; var toplevel;
if (files instanceof AST_Toplevel) { if (files instanceof AST_Toplevel) {
toplevel = files; toplevel = files;
} else { } else {
@@ -151,19 +154,17 @@ function minify(files, options) {
if (typeof source_map_content == "string" && source_map_content != "inline") { if (typeof source_map_content == "string" && source_map_content != "inline") {
source_map_content = parse_source_map(source_map_content); source_map_content = parse_source_map(source_map_content);
} }
source_maps = source_map_content && Object.create(null); if (source_map_content) options.sourceMap.orig = Object.create(null);
for (var name in files) if (HOP(files, name)) { for (var name in files) if (HOP(files, name)) {
options.parse.filename = name; options.parse.filename = name;
options.parse.toplevel = toplevel = parse(files[name], options.parse); options.parse.toplevel = toplevel = parse(files[name], options.parse);
if (source_maps) {
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) {
source_maps[name] = parse_source_map(inlined_content); options.sourceMap.orig[name] = parse_source_map(inlined_content);
}
} else {
source_maps[name] = source_map_content;
} }
} else if (source_map_content) {
options.sourceMap.orig[name] = source_map_content;
} }
} }
} }
@@ -202,12 +203,7 @@ function minify(files, options) {
} }
if (!HOP(options.output, "code") || options.output.code) { if (!HOP(options.output, "code") || options.output.code) {
if (options.sourceMap) { if (options.sourceMap) {
options.output.source_map = SourceMap({ options.output.source_map = SourceMap(options.sourceMap);
content: options.sourceMap.includeSources,
file: options.sourceMap.filename,
orig: source_maps,
root: options.sourceMap.root
});
if (options.sourceMap.includeSources) { if (options.sourceMap.includeSources) {
if (files instanceof AST_Toplevel) { if (files instanceof AST_Toplevel) {
throw new Error("original source content unavailable"); throw new Error("original source content unavailable");
@@ -259,5 +255,7 @@ function minify(files, options) {
return result; return result;
} catch (ex) { } catch (ex) {
return { error: ex }; return { error: ex };
} finally {
AST_Node.disable_validation();
} }
} }

View File

@@ -1,7 +1,7 @@
/*********************************************************************** /***********************************************************************
A JavaScript tokenizer / parser / beautifier / compressor. A JavaScript tokenizer / parser / beautifier / compressor.
https://github.com/mishoo/UglifyJS2 https://github.com/mishoo/UglifyJS
-------------------------------- (C) --------------------------------- -------------------------------- (C) ---------------------------------
@@ -111,7 +111,7 @@
var args = { var args = {
start : my_start_token(key), start : my_start_token(key),
end : my_end_token(M.value), end : my_end_token(M.value),
key : key.type == "Identifier" ? key.name : key.value, key : "" + key[key.type == "Identifier" ? "name" : "value"],
value : from_moz(M.value) value : from_moz(M.value)
}; };
if (M.kind == "init") return new AST_ObjectKeyVal(args); if (M.kind == "init") return new AST_ObjectKeyVal(args);
@@ -212,7 +212,14 @@
end : my_end_token(M), end : my_end_token(M),
name : M.name name : M.name
}); });
} },
ThisExpression: function(M) {
return new AST_This({
start : my_start_token(M),
end : my_end_token(M),
name : "this",
});
},
}; };
MOZ_TO_ME.UpdateExpression = MOZ_TO_ME.UpdateExpression =
@@ -245,7 +252,6 @@
map("VariableDeclarator", AST_VarDef, "id>name, init>value"); map("VariableDeclarator", AST_VarDef, "id>name, init>value");
map("CatchClause", AST_Catch, "param>argname, body%body"); map("CatchClause", AST_Catch, "param>argname, body%body");
map("ThisExpression", AST_This);
map("BinaryExpression", AST_Binary, "operator=operator, left>left, right>right"); map("BinaryExpression", AST_Binary, "operator=operator, left>left, right>right");
map("LogicalExpression", AST_Binary, "operator=operator, left>left, right>right"); map("LogicalExpression", AST_Binary, "operator=operator, left>left, right>right");
map("AssignmentExpression", AST_Assign, "operator=operator, left>left, right>right"); map("AssignmentExpression", AST_Assign, "operator=operator, left>left, right>right");
@@ -407,6 +413,10 @@
}; };
}); });
def_to_moz(AST_This, function To_Moz_ThisExpression() {
return { type: "ThisExpression" };
});
def_to_moz(AST_RegExp, function To_Moz_RegExpLiteral(M) { def_to_moz(AST_RegExp, function To_Moz_RegExpLiteral(M) {
var flags = M.value.toString().match(/[gimuy]*$/)[0]; var flags = M.value.toString().match(/[gimuy]*$/)[0];
var value = "/" + M.value.raw_source + "/" + flags; var value = "/" + M.value.raw_source + "/" + flags;

View File

@@ -1,7 +1,7 @@
/*********************************************************************** /***********************************************************************
A JavaScript tokenizer / parser / beautifier / compressor. A JavaScript tokenizer / parser / beautifier / compressor.
https://github.com/mishoo/UglifyJS2 https://github.com/mishoo/UglifyJS
-------------------------------- (C) --------------------------------- -------------------------------- (C) ---------------------------------
@@ -783,8 +783,8 @@ function OutputStream(options) {
var p = output.parent(); var p = output.parent();
if (p instanceof AST_PropAccess && p.expression === this) { if (p instanceof AST_PropAccess && p.expression === this) {
var value = this.value; var value = this.value;
// https://github.com/mishoo/UglifyJS2/issues/115 // https://github.com/mishoo/UglifyJS/issues/115
// https://github.com/mishoo/UglifyJS2/pull/1009 // https://github.com/mishoo/UglifyJS/pull/1009
if (value < 0 || /^0/.test(make_num(value))) { if (value < 0 || /^0/.test(make_num(value))) {
return true; return true;
} }
@@ -1149,7 +1149,7 @@ function OutputStream(options) {
function parenthesize_for_noin(node, output, noin) { function parenthesize_for_noin(node, output, noin) {
var parens = false; var parens = false;
// need to take some precautions here: // need to take some precautions here:
// https://github.com/mishoo/UglifyJS2/issues/60 // https://github.com/mishoo/UglifyJS/issues/60
if (noin) node.walk(new TreeWalker(function(node) { if (noin) node.walk(new TreeWalker(function(node) {
if (parens || node instanceof AST_Scope) return true; if (parens || node instanceof AST_Scope) return true;
if (node instanceof AST_Binary && node.operator == "in") { if (node instanceof AST_Binary && node.operator == "in") {
@@ -1470,7 +1470,6 @@ function OutputStream(options) {
AST_Node, AST_Node,
// since the label symbol will mark it // since the label symbol will mark it
AST_LabeledStatement, AST_LabeledStatement,
AST_Toplevel,
], noop); ], noop);
// XXX: I'm not exactly sure if we need it for all of these nodes, // XXX: I'm not exactly sure if we need it for all of these nodes,
@@ -1482,7 +1481,6 @@ function OutputStream(options) {
AST_Constant, AST_Constant,
AST_Debugger, AST_Debugger,
AST_Definitions, AST_Definitions,
AST_Directive,
AST_Finally, AST_Finally,
AST_Jump, AST_Jump,
AST_Lambda, AST_Lambda,

View File

@@ -1,7 +1,7 @@
/*********************************************************************** /***********************************************************************
A JavaScript tokenizer / parser / beautifier / compressor. A JavaScript tokenizer / parser / beautifier / compressor.
https://github.com/mishoo/UglifyJS2 https://github.com/mishoo/UglifyJS
-------------------------------- (C) --------------------------------- -------------------------------- (C) ---------------------------------
@@ -948,7 +948,7 @@ function parse($TEXT, options) {
if (!(stat instanceof AST_IterationStatement)) { if (!(stat instanceof AST_IterationStatement)) {
// check for `continue` that refers to this label. // check for `continue` that refers to this label.
// those should be reported as syntax errors. // those should be reported as syntax errors.
// https://github.com/mishoo/UglifyJS2/issues/287 // https://github.com/mishoo/UglifyJS/issues/287
label.references.forEach(function(ref) { label.references.forEach(function(ref) {
if (ref instanceof AST_Continue) { if (ref instanceof AST_Continue) {
ref = ref.label.start; ref = ref.label.start;

View File

@@ -1,7 +1,7 @@
/*********************************************************************** /***********************************************************************
A JavaScript tokenizer / parser / beautifier / compressor. A JavaScript tokenizer / parser / beautifier / compressor.
https://github.com/mishoo/UglifyJS2 https://github.com/mishoo/UglifyJS
-------------------------------- (C) --------------------------------- -------------------------------- (C) ---------------------------------

View File

@@ -1,7 +1,7 @@
/*********************************************************************** /***********************************************************************
A JavaScript tokenizer / parser / beautifier / compressor. A JavaScript tokenizer / parser / beautifier / compressor.
https://github.com/mishoo/UglifyJS2 https://github.com/mishoo/UglifyJS
-------------------------------- (C) --------------------------------- -------------------------------- (C) ---------------------------------
@@ -43,23 +43,21 @@
"use strict"; "use strict";
function SymbolDef(scope, orig, init) { function SymbolDef(id, scope, orig, init) {
this.eliminated = 0;
this.global = false;
this.id = id;
this.init = init;
this.lambda = orig instanceof AST_SymbolLambda;
this.mangled_name = null;
this.name = orig.name; this.name = orig.name;
this.orig = [ orig ]; this.orig = [ orig ];
this.init = init;
this.eliminated = 0;
this.scope = scope;
this.references = []; this.references = [];
this.replaced = 0; this.replaced = 0;
this.global = false; this.scope = scope;
this.mangled_name = null;
this.undeclared = false; this.undeclared = false;
this.id = SymbolDef.next_id++;
this.lambda = orig instanceof AST_SymbolLambda;
} }
SymbolDef.next_id = 1;
SymbolDef.prototype = { SymbolDef.prototype = {
unmangleable: function(options) { unmangleable: function(options) {
return this.global && !options.toplevel return this.global && !options.toplevel
@@ -151,6 +149,7 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options) {
scope.def_variable(node).defun = defun; scope.def_variable(node).defun = defun;
} }
}); });
self.next_def_id = 0;
self.walk(tw); self.walk(tw);
// pass 2: find back references and eval // pass 2: find back references and eval
@@ -240,12 +239,18 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options) {
} }
}); });
AST_Scope.DEFMETHOD("make_def", function(orig, init) {
var top = this;
while (top.parent_scope) top = top.parent_scope;
return new SymbolDef(++top.next_def_id, this, orig, init);
});
AST_Toplevel.DEFMETHOD("def_global", function(node) { AST_Toplevel.DEFMETHOD("def_global", function(node) {
var globals = this.globals, name = node.name; var globals = this.globals, name = node.name;
if (globals.has(name)) { if (globals.has(name)) {
return globals.get(name); return globals.get(name);
} else { } else {
var g = new SymbolDef(this, node); var g = this.make_def(node);
g.undeclared = true; g.undeclared = true;
g.global = true; g.global = true;
globals.set(name, g); globals.set(name, g);
@@ -310,7 +315,7 @@ AST_Scope.DEFMETHOD("def_variable", function(symbol, init) {
def.orig.push(symbol); def.orig.push(symbol);
if (def.init instanceof AST_Function) def.init = init; if (def.init instanceof AST_Function) def.init = init;
} else { } else {
def = new SymbolDef(this, symbol, init); def = this.make_def(symbol, init);
this.variables.set(symbol.name, def); this.variables.set(symbol.name, def);
def.global = !this.parent_scope; def.global = !this.parent_scope;
} }

View File

@@ -1,7 +1,7 @@
/*********************************************************************** /***********************************************************************
A JavaScript tokenizer / parser / beautifier / compressor. A JavaScript tokenizer / parser / beautifier / compressor.
https://github.com/mishoo/UglifyJS2 https://github.com/mishoo/UglifyJS
-------------------------------- (C) --------------------------------- -------------------------------- (C) ---------------------------------
@@ -90,14 +90,8 @@ function create_array_map() {
} }
function SourceMap(options) { function SourceMap(options) {
options = defaults(options, {
content: false,
file: null,
root: null,
orig: null,
}, true);
var sources = create_array_map(); var sources = create_array_map();
var sources_content = options.content && Object.create(null); var sources_content = options.includeSources && Object.create(null);
var names = create_array_map(); var names = create_array_map();
var mappings = ""; var mappings = "";
if (options.orig) Object.keys(options.orig).forEach(function(name) { if (options.orig) Object.keys(options.orig).forEach(function(name) {
@@ -119,6 +113,7 @@ function SourceMap(options) {
if (content) sources_content[map.sources[i]] = content; if (content) sources_content[map.sources[i]] = content;
} }
}); });
var prev_source;
var generated_line = 1; var generated_line = 1;
var generated_column = 0; var generated_column = 0;
var source_index = 0; var source_index = 0;
@@ -137,12 +132,15 @@ function SourceMap(options) {
if (orig_col >= col) indices = segments[i]; if (orig_col >= col) indices = segments[i];
if (orig_col <= col) break; if (orig_col <= col) break;
} }
if (!indices || indices.length < 4) return; if (!indices || indices.length < 4) {
source = null;
} else {
source = map.sources[indices[1]]; source = map.sources[indices[1]];
orig_line = indices[2]; orig_line = indices[2];
orig_col = indices[3]; orig_col = indices[3];
if (indices.length > 4) name = map.names[indices[4]]; if (indices.length > 4) name = map.names[indices[4]];
} }
}
add(source, gen_line, gen_col, orig_line, orig_col, name); add(source, gen_line, gen_col, orig_line, orig_col, name);
} : add, } : add,
setSourceContent: sources_content ? function(source, content) { setSourceContent: sources_content ? function(source, content) {
@@ -151,7 +149,7 @@ function SourceMap(options) {
toString: function() { toString: function() {
return JSON.stringify({ return JSON.stringify({
version: 3, version: 3,
file: options.file || undefined, file: options.filename || undefined,
sourceRoot: options.root || undefined, sourceRoot: options.root || undefined,
sources: sources, sources: sources,
sourcesContent: sources_content ? sources.map(function(source) { sourcesContent: sources_content ? sources.map(function(source) {
@@ -164,6 +162,8 @@ function SourceMap(options) {
}; };
function add(source, gen_line, gen_col, orig_line, orig_col, name) { function add(source, gen_line, gen_col, orig_line, orig_col, name) {
if (prev_source == null && source == null) return;
prev_source = source;
if (generated_line < gen_line) { if (generated_line < gen_line) {
generated_column = 0; generated_column = 0;
do { do {
@@ -174,6 +174,7 @@ function SourceMap(options) {
} }
mappings += vlq_encode(gen_col - generated_column); mappings += vlq_encode(gen_col - generated_column);
generated_column = gen_col; generated_column = gen_col;
if (source == null) return;
var src_idx = sources.index(source); var src_idx = sources.index(source);
mappings += vlq_encode(src_idx - source_index); mappings += vlq_encode(src_idx - source_index);
source_index = src_idx; source_index = src_idx;
@@ -181,7 +182,7 @@ function SourceMap(options) {
original_line = orig_line; original_line = orig_line;
mappings += vlq_encode(orig_col - original_column); mappings += vlq_encode(orig_col - original_column);
original_column = orig_col; original_column = orig_col;
if (name != null) { if (options.names && name != null) {
var name_idx = names.index(name); var name_idx = names.index(name);
mappings += vlq_encode(name_idx - name_index); mappings += vlq_encode(name_idx - name_index);
name_index = name_idx; name_index = name_idx;

View File

@@ -1,7 +1,7 @@
/*********************************************************************** /***********************************************************************
A JavaScript tokenizer / parser / beautifier / compressor. A JavaScript tokenizer / parser / beautifier / compressor.
https://github.com/mishoo/UglifyJS2 https://github.com/mishoo/UglifyJS
-------------------------------- (C) --------------------------------- -------------------------------- (C) ---------------------------------

View File

@@ -1,7 +1,7 @@
/*********************************************************************** /***********************************************************************
A JavaScript tokenizer / parser / beautifier / compressor. A JavaScript tokenizer / parser / beautifier / compressor.
https://github.com/mishoo/UglifyJS2 https://github.com/mishoo/UglifyJS
-------------------------------- (C) --------------------------------- -------------------------------- (C) ---------------------------------
@@ -87,15 +87,13 @@ DefaultsError.prototype.name = "DefaultsError";
configure_error_stack(DefaultsError); configure_error_stack(DefaultsError);
function defaults(args, defs, croak) { function defaults(args, defs, croak) {
if (args === true) args = {}; if (croak) for (var i in args) {
var ret = args || {}; if (HOP(args, i) && !HOP(defs, i)) throw new DefaultsError("`" + i + "` is not a supported option", defs);
if (croak) for (var i in ret) if (HOP(ret, i) && !HOP(defs, i)) {
throw new DefaultsError("`" + i + "` is not a supported option", defs);
} }
for (var i in defs) if (HOP(defs, i)) { for (var i in args) {
ret[i] = (args && HOP(args, i)) ? args[i] : defs[i]; if (HOP(args, i)) defs[i] = args[i];
} }
return ret; return defs;
} }
function merge(obj, ext) { function merge(obj, ext) {
@@ -149,6 +147,9 @@ var List = (function() {
} }
return top.concat(ret); return top.concat(ret);
} }
List.is_op = function(val) {
return val === skip || val instanceof AtTop || val instanceof Last || val instanceof Splice;
};
List.at_top = function(val) { return new AtTop(val); }; List.at_top = function(val) { return new AtTop(val); };
List.splice = function(val) { return new Splice(val); }; List.splice = function(val) { return new Splice(val); };
List.last = function(val) { return new Last(val); }; List.last = function(val) { return new Last(val); };

View File

@@ -3,7 +3,7 @@
"description": "JavaScript parser, mangler/compressor and beautifier toolkit", "description": "JavaScript parser, mangler/compressor and beautifier toolkit",
"author": "Mihai Bazon <mihai.bazon@gmail.com> (http://lisperator.net/)", "author": "Mihai Bazon <mihai.bazon@gmail.com> (http://lisperator.net/)",
"license": "BSD-2-Clause", "license": "BSD-2-Clause",
"version": "3.9.1", "version": "3.9.3",
"engines": { "engines": {
"node": ">=0.8.0" "node": ">=0.8.0"
}, },
@@ -11,7 +11,7 @@
"Alex Lam <alexlamsl@gmail.com>", "Alex Lam <alexlamsl@gmail.com>",
"Mihai Bazon <mihai.bazon@gmail.com> (http://lisperator.net/)" "Mihai Bazon <mihai.bazon@gmail.com> (http://lisperator.net/)"
], ],
"repository": "mishoo/UglifyJS2", "repository": "mishoo/UglifyJS",
"main": "tools/node.js", "main": "tools/node.js",
"bin": { "bin": {
"uglifyjs": "bin/uglifyjs" "uglifyjs": "bin/uglifyjs"

View File

@@ -8,9 +8,9 @@ var fetch = require("./fetch");
var spawn = require("child_process").spawn; var spawn = require("child_process").spawn;
var zlib = require("zlib"); var zlib = require("zlib");
var args = process.argv.slice(2); var args = process.argv.slice(2);
args.unshift("bin/uglifyjs");
if (!args.length) args.push("-mc"); if (!args.length) args.push("-mc");
args.push("--timings"); args.unshift("bin/uglifyjs");
args.push("--validate", "--timings");
var urls = [ var urls = [
"https://code.jquery.com/jquery-3.4.1.js", "https://code.jquery.com/jquery-3.4.1.js",
"https://code.angularjs.org/1.7.8/angular.js", "https://code.angularjs.org/1.7.8/angular.js",

View File

@@ -63,7 +63,7 @@ function make_code(ast, options) {
function parse_test(file) { function parse_test(file) {
var script = fs.readFileSync(file, "utf8"); var script = fs.readFileSync(file, "utf8");
// TODO try/catch can be removed after fixing https://github.com/mishoo/UglifyJS2/issues/348 // TODO try/catch can be removed after fixing https://github.com/mishoo/UglifyJS/issues/348
try { try {
var ast = U.parse(script, { var ast = U.parse(script, {
filename: file filename: file
@@ -188,6 +188,7 @@ function reminify(orig_options, input_code, input_formatted, stdout) {
} }
}); });
var options_formatted = JSON.stringify(options, null, 4); var options_formatted = JSON.stringify(options, null, 4);
options.validate = true;
var result = U.minify(input_code, options); var result = U.minify(input_code, options);
if (result.error) { if (result.error) {
log([ log([
@@ -251,6 +252,7 @@ function run_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();
var output_options = test.beautify || {}; var output_options = test.beautify || {};
var expect; var expect;
if (test.expect) { if (test.expect) {

View File

@@ -91,8 +91,7 @@ asm_mixed: {
function no_asm_GeometricMean(stdlib, foreign, buffer) { function no_asm_GeometricMean(stdlib, foreign, buffer) {
function logSum(start, end) { function logSum(start, end) {
start |= 0, end |= 0; start |= 0, end |= 0;
var sum = 0, p = 0, q = 0; for (var sum = 0, p = 0, q = 0, p = start << 3, q = end << 3; (0 | p) < (0 | q); p = p + 8 | 0) sum += +log(values[p >> 3]);
for (p = start << 3, q = end << 3; (0 | p) < (0 | q); p = p + 8 | 0) sum += +log(values[p >> 3]);
return +sum; return +sum;
} }
function geometricMean(start, end) { function geometricMean(start, end) {

View File

@@ -803,8 +803,7 @@ collapse_vars_assignment: {
expect: { expect: {
function log(x) { return console.log(x), x; } function log(x) { return console.log(x), x; }
function f0(c) { function f0(c) {
var a = 3 / c; return 3 / c;
return a = a;
} }
function f1(c) { function f1(c) {
return 1 - 3 / c; return 1 - 3 / c;
@@ -2012,6 +2011,7 @@ issue_1631_3: {
join_vars: true, join_vars: true,
sequences: true, sequences: true,
side_effects: true, side_effects: true,
unused: true,
} }
input: { input: {
function g() { function g() {
@@ -2031,8 +2031,8 @@ issue_1631_3: {
function f() { function f() {
return a = 2, 4; return a = 2, 4;
} }
var a = 0, b = 1, t = f(); var a = 0, t = f();
return b = a + t; return a + t;
} }
console.log(g()); console.log(g());
} }
@@ -2204,8 +2204,8 @@ var_defs: {
} }
expect: { expect: {
var f1 = function(x, y) { var f1 = function(x, y) {
var r = x + y, a = r * r - r, b = 7; var r = x + y, z = r * r - r, b = 7;
console.log(a + b); console.log(z + b);
}; };
f1("1", 0); f1("1", 0);
} }
@@ -2569,8 +2569,7 @@ chained_3: {
} }
expect: { expect: {
console.log(function(a, b) { console.log(function(a, b) {
var c = 1; var c = 1, c = b;
c = b;
b++; b++;
return c; return c;
}(0, 2)); }(0, 2));
@@ -2665,8 +2664,8 @@ double_def_1: {
a(); a();
} }
expect: { expect: {
var a; var a = x;
(a = (a = x) && y)(); (a = a && y)();
} }
} }
@@ -2699,8 +2698,8 @@ toplevel_single_reference: {
} }
expect: { expect: {
for (var b in x) { for (var b in x) {
var a; var a = b;
b(a = b); b(b);
} }
} }
} }
@@ -4243,8 +4242,7 @@ issue_2497: {
if (true) if (true)
for (var i = 0; i < 1; ++i) for (var i = 0; i < 1; ++i)
for (var k = 0; k < 1; ++k) { for (var k = 0; k < 1; ++k) {
value = 1; value = (value = 1) ? value + 1 : 0;
value = value ? value + 1 : 0;
} }
else else
for (i = 0; i < 1; ++i) for (i = 0; i < 1; ++i)
@@ -7806,3 +7804,214 @@ issue_3744: {
} }
expect_stdout: "PASS" expect_stdout: "PASS"
} }
assign_value_def: {
options = {
collapse_vars: true,
unused: true,
}
input: {
function f(a) {
while (1) {
var b = a[0], c = a[1];
d = b;
e = c;
if (c[0] - e[0] > c[1] - d[1]) break;
return "PASS";
}
var d, e;
return "FAIL";
}
console.log(f([
[ 1, 2 ],
[ 3, 4 ],
]));
}
expect: {
function f(a) {
while (1) {
var b = a[0], c = a[1];
if (c[0] - c[0] > c[1] - b[1]) break;
return "PASS";
}
return "FAIL";
}
console.log(f([
[ 1, 2 ],
[ 3, 4 ],
]));
}
expect_stdout: "PASS"
}
join_vars_value_def: {
options = {
collapse_vars: true,
join_vars: true,
unused: true,
}
input: {
function f(a) {
while (1) {
var b = a[0], c = a[1];
d = b;
e = c;
if (c[0] - e[0] > c[1] - d[1]) break;
return "PASS";
}
var d, e;
return "FAIL";
}
console.log(f([
[ 1, 2 ],
[ 3, 4 ],
]));
}
expect: {
function f(a) {
while (1) {
var b = a[0], c = a[1];
if (c[0] - c[0] > c[1] - b[1]) break;
return "PASS";
}
return "FAIL";
}
console.log(f([
[ 1, 2 ],
[ 3, 4 ],
]));
}
expect_stdout: "PASS"
}
var_value_def: {
options = {
collapse_vars: true,
unused: true,
}
input: {
function f(a) {
while (1) {
var b = a[0], c = a[1], d = b, e = c;
if (c[0] - e[0] > c[1] - d[1]) break;
return "PASS";
}
var d, e;
return "FAIL";
}
console.log(f([
[ 1, 2 ],
[ 3, 4 ],
]));
}
expect: {
function f(a) {
while (1) {
var b = a[0], c = a[1];
if (c[0] - c[0] > c[1] - b[1]) break;
return "PASS";
}
return "FAIL";
}
console.log(f([
[ 1, 2 ],
[ 3, 4 ],
]));
}
expect_stdout: "PASS"
}
mangleable_var: {
options = {
collapse_vars: true,
unused: true,
}
input: {
function f(a) {
var b = a(), c = a(), d = b;
return c.p(c, d);
}
console.log(f(function() {
return {
p: function() {
return "PASS"
},
};
}));
}
expect: {
function f(a) {
var b = a(), c = a();
return c.p(c, b);
}
console.log(f(function() {
return {
p: function() {
return "PASS";
}
};
}));
}
expect_stdout: "PASS"
}
issue_3884: {
options = {
collapse_vars: true,
evaluate: true,
reduce_vars: true,
side_effects: true,
toplevel: true,
unused: true,
}
input: {
var a = 100, b = 1;
{
a++ + a || a;
b <<= a;
}
console.log(a, b);
}
expect: {
var a = 100;
++a;
console.log(a, 32);
}
expect_stdout: "101 32"
}
issue_3891: {
options = {
collapse_vars: true,
passes: 2,
reduce_vars: true,
side_effects: true,
unused: true,
}
input: {
function log(a) {
console.log(typeof a);
}
log(function f() {
try {
do {
var b = function() {}();
} while (f = 0, b.p);
} catch (e) {
var f;
b;
}
});
}
expect: {
function log(a) {
console.log(typeof a);
}
log(function() {
try {
do {} while ((void 0).p);
} catch (e) {}
});
}
expect_stdout: "function"
}

View File

@@ -93,6 +93,36 @@ self_comparison_2: {
expect_stdout: "false true" expect_stdout: "false true"
} }
self_comparison_3: {
options = {
comparisons: true,
}
input: {
var a;
function f() {
var b = a;
a = null;
return b;
}
for (var i = 0; i < 2; i++)
console.log(f() === f());
}
expect: {
var a;
function f() {
var b = a;
a = null;
return b;
}
for (var i = 0; i < 2; i++)
console.log(f() === f());
}
expect_stdout: [
"false",
"true",
]
}
issue_2857_1: { issue_2857_1: {
options = { options = {
comparisons: true, comparisons: true,

View File

@@ -1384,7 +1384,7 @@ hoist_decl: {
} }
expect: { expect: {
var a, b; var a, b;
x() ? y() : z(); (x() ? y : z)();
} }
} }
@@ -1666,3 +1666,130 @@ issue_3668: {
} }
expect_stdout: "undefined" expect_stdout: "undefined"
} }
conditional_assignments_1: {
options = {
conditionals: true,
sequences: true,
}
input: {
function f(a, b, c, d) {
a = b;
if (c) a = d;
return a;
}
function g(a, b, c, d) {
a = b;
if (c); else a = d;
return a;
}
console.log(f(0, "FAIL", 1, "PASS"), g(0, "PASS", 1, "FAIL"));
}
expect: {
function f(a, b, c, d) {
return a = c ? d : b, a;
}
function g(a, b, c, d) {
return a = c ? b : d, a;
}
console.log(f(0, "FAIL", 1, "PASS"), g(0, "PASS", 1, "FAIL"));
}
expect_stdout: "PASS PASS"
}
conditional_assignments_2: {
options = {
conditionals: true,
sequences: true,
}
input: {
function f1(b, c, d) {
a = b;
if (c) a = d;
return a;
}
function f2(a, c, d) {
a = b;
if (c) a = d;
return a;
}
function f3(a, b, d) {
a = b;
if (c) a = d;
return a;
}
function f4(a, b, c) {
a = b;
if (c) a = d;
return a;
}
}
expect: {
function f1(b, c, d) {
return a = c ? d : b, a;
}
function f2(a, c, d) {
return a = b, c && (a = d), a;
}
function f3(a, b, d) {
return a = b, c && (a = d), a;
}
function f4(a, b, c) {
return a = b, c && (a = d), a;
}
}
}
conditional_assignments_3: {
options = {
conditionals: true,
sequences: true,
}
input: {
console.log(function(a, b) {
a = "PASS";
if (b) a = a;
return a;
}(0, 1));
}
expect: {
console.log(function(a, b) {
return a = "PASS", b && (a = a), a;
}(0, 1));
}
expect_stdout: "PASS"
}
issue_3808_1: {
options = {
conditionals: true,
side_effects: true,
}
input: {
var a;
a = "PASS", [] + "" && (a = "FAIL");
console.log(a);
}
expect: {
var a;
a = [] + "" ? "FAIL" : "PASS";
console.log(a);
}
expect_stdout: "PASS"
}
issue_3808_2: {
options = {
conditionals: true,
side_effects: true,
}
input: {
var a;
console.log((a = "PASS", [] + "" && (a = "FAIL")), a);
}
expect: {
var a;
console.log((a = "PASS", [] + "" && (a = "FAIL")), a);
}
expect_stdout: " PASS"
}

View File

@@ -97,6 +97,66 @@ dead_code_constant_boolean_should_warn_more: {
node_version: "<=4" node_version: "<=4"
} }
trim_try: {
options = {
dead_code: true,
}
input: {
try {
var a;
} catch (e) {
console.log("FAIL");
} finally {
console.log(a);
}
}
expect: {
var a;
console.log(a);
}
expect_stdout: "undefined"
}
trim_finally_1: {
options = {
dead_code: true,
}
input: {
try {
console.log("PASS");
} finally {
var a;
}
}
expect: {
console.log("PASS");
var a;
}
expect_stdout: "PASS"
}
trim_finally_2: {
options = {
dead_code: true,
}
input: {
try {
console.log("PASS");
} catch (e) {
} finally {
var a;
}
}
expect: {
try {
console.log("PASS");
var a;
} catch (e) {
}
}
expect_stdout: "PASS"
}
try_catch_finally: { try_catch_finally: {
options = { options = {
conditionals: true, conditionals: true,
@@ -130,10 +190,7 @@ try_catch_finally: {
a = 3; a = 3;
console.log("PASS"); console.log("PASS");
}(); }();
try {
console.log(a); console.log(a);
} finally {
}
} }
expect_stdout: [ expect_stdout: [
"PASS", "PASS",
@@ -892,3 +949,222 @@ issue_3578: {
} }
expect_stdout: "PASS" expect_stdout: "PASS"
} }
issue_3830_1: {
options = {
dead_code: true,
}
input: {
var o = {
set p(v) {
o = o.p = o = v;
}
};
o.p = "PASS";
console.log(o);
}
expect: {
var o = {
set p(v) {
o = o.p = o = v;
}
};
o.p = "PASS";
console.log(o);
}
expect_stdout: "PASS"
}
issue_3830_2: {
options = {
dead_code: true,
}
input: {
var a = "FAIL";
var o = {
set FAIL(v) {
a = o[a] = a = v;
}
};
o[a] = "PASS";
console.log(a);
}
expect: {
var a = "FAIL";
var o = {
set FAIL(v) {
a = o[a] = a = v;
}
};
o[a] = "PASS";
console.log(a);
}
expect_stdout: "PASS"
}
issue_3830_3: {
options = {
dead_code: true,
}
input: {
function f() {
return a;
}
var a = "FAIL";
var o = {
set FAIL(v) {
a = o[f()] = a = v;
}
};
o[f()] = "PASS";
console.log(a);
}
expect: {
function f() {
return a;
}
var a = "FAIL";
var o = {
set FAIL(v) {
a = o[f()] = a = v;
}
};
o[f()] = "PASS";
console.log(a);
}
expect_stdout: "PASS"
}
issue_3830_4: {
options = {
dead_code: true,
}
input: {
function f() {
return o;
}
var a = "FAIL";
var o = {
set FAIL(v) {
a = f()[a] = a = v;
}
};
f()[a] = "PASS";
console.log(a);
}
expect: {
function f() {
return o;
}
var a = "FAIL";
var o = {
set FAIL(v) {
a = f()[a] = a = v;
}
};
f()[a] = "PASS";
console.log(a);
}
expect_stdout: "PASS"
}
issue_3830_5: {
options = {
dead_code: true,
}
input: {
function f() {
return o;
}
function g() {
return a;
}
var a = "FAIL";
var o = {
set FAIL(v) {
a = f()[g()] = a = v;
}
};
f()[g()] = "PASS";
console.log(a);
}
expect: {
function f() {
return o;
}
function g() {
return a;
}
var a = "FAIL";
var o = {
set FAIL(v) {
a = f()[g()] = a = v;
}
};
f()[g()] = "PASS";
console.log(a);
}
expect_stdout: "PASS"
}
issue_3830_6: {
options = {
dead_code: true,
}
input: {
function f() {
return o;
}
function g() {
return a;
}
function h(v) {
a = f()[g()] = a = v;
}
var a = "FAIL";
var o = {
set FAIL(v) {
h(v);
}
};
o.FAIL = "PASS";
console.log(a);
}
expect: {
function f() {
return o;
}
function g() {
return a;
}
function h(v) {
a = f()[g()] = a = v;
}
var a = "FAIL";
var o = {
set FAIL(v) {
h(v);
}
};
o.FAIL = "PASS";
console.log(a);
}
expect_stdout: "PASS"
}
redundant_assignments: {
options = {
dead_code: true,
}
input: {
var a = a = "PASS", b = "FAIL";
b = b = "PASS";
console.log(a, b);
}
expect: {
var a = "PASS", b = "FAIL";
b = "PASS";
console.log(a, b);
}
expect_stdout: "PASS PASS"
}

View File

@@ -46,7 +46,7 @@ simple_statement_is_not_a_directive: {
drop_lone_use_strict: { drop_lone_use_strict: {
options = { options = {
directives: true, directives: true,
side_effects: true, unused: true,
} }
input: { input: {
function f1() { function f1() {
@@ -66,10 +66,8 @@ drop_lone_use_strict: {
function f1() { function f1() {
} }
function f2() { function f2() {
"use strict";
function f3() {
}
} }
(function() {})();
} }
} }

View File

@@ -2366,7 +2366,7 @@ function_parameter_ie8: {
} }
expect: { expect: {
(function() { (function() {
(function f() { (function() {
console.log("PASS"); console.log("PASS");
})(); })();
})(); })();
@@ -2444,3 +2444,91 @@ issue_3746: {
} }
expect_stdout: "PASS" expect_stdout: "PASS"
} }
drop_duplicated_side_effects: {
options = {
toplevel: true,
unused: true,
}
input: {
var a = 0;
for (var i = 1; i--;)
var a = 0, b = ++a;
console.log(a);
}
expect: {
var a = 0;
for (var i = 1; i--;)
a = 0, ++a;
console.log(a);
}
expect_stdout: "1"
}
drop_duplicated_var_catch: {
options = {
unused: true,
}
input: {
function f() {
try {
x();
} catch (a) {
var a, a;
}
}
}
expect: {
function f() {
try {
x();
} catch (a) {
var a;
}
}
}
}
issue_3802_1: {
options = {
functions: true,
reduce_vars: true,
toplevel: true,
unused: true,
}
input: {
var a = 0;
a += 0;
var a = function() {};
console.log(typeof a);
}
expect: {
var a = 0;
a += 0;
a = function() {};
console.log(typeof a);
}
expect_stdout: "function"
}
issue_3802_2: {
options = {
functions: true,
reduce_vars: true,
side_effects: true,
toplevel: true,
unused: true,
}
input: {
var a = 0;
a += 0;
var a = function() {};
console.log(typeof a);
}
expect: {
0;
function a() {};
console.log(typeof a);
}
expect_stdout: "function"
}

View File

@@ -2190,3 +2190,231 @@ issue_3755: {
} }
expect_stdout: "undefined" expect_stdout: "undefined"
} }
void_side_effects: {
options = {
evaluate: true,
reduce_vars: true,
toplevel: true,
unused: true,
}
input: {
var a = void console.log("PASS");
console.log(a);
}
expect: {
console.log("PASS");
console.log(void 0);
}
expect_stdout: [
"PASS",
"undefined",
]
}
no_returns: {
options = {
evaluate: true,
reduce_vars: true,
toplevel: true,
unused: true,
}
input: {
var a = function() {
console.log("PASS");
}();
console.log(a);
}
expect: {
(function() {
console.log("PASS");
})();
console.log(void 0);
}
expect_stdout: [
"PASS",
"undefined",
]
}
void_returns: {
options = {
evaluate: true,
reduce_vars: true,
toplevel: true,
unused: true,
}
input: {
var a = function f() {
function g(b) {
if (b) console.log("FAIL");
}
while (1) {
console.log("PASS");
try {
if (console) return;
} catch (e) {
return g(e);
}
}
}();
console.log(a);
}
expect: {
(function() {
function g(b) {
if (b) console.log("FAIL");
}
while (1) {
console.log("PASS");
try {
if (console) return;
} catch (e) {
return g(e);
}
}
})();
console.log(void 0);
}
expect_stdout: [
"PASS",
"undefined",
]
}
void_returns_recursive: {
options = {
evaluate: true,
reduce_vars: true,
toplevel: true,
unused: true,
}
input: {
var a = function f() {
function g(b) {
return f();
}
while (1) {
console.log("PASS");
try {
if (console) return;
} catch (e) {
return g(e);
}
}
}();
console.log(a);
}
expect: {
var a = function f() {
function g(b) {
return f();
}
while (1) {
console.log("PASS");
try {
if (console) return;
} catch (e) {
return g();
}
}
}();
console.log(a);
}
expect_stdout: [
"PASS",
"undefined",
]
}
issue_3878_1: {
options = {
evaluate: true,
reduce_vars: true,
toplevel: true,
unused: true,
}
input: {
var b = function(a) {
return (a = 0) == (a && this > (a += 0));
}();
console.log(b ? "PASS" : "FAIL");
}
expect: {
var b = function(a) {
return (a = 0) == (a && this > (a += 0));
}();
console.log(b ? "PASS" : "FAIL");
}
expect_stdout: "PASS"
}
issue_3878_2: {
options = {
evaluate: true,
reduce_vars: true,
toplevel: true,
}
input: {
var a = "foo";
a++ + a;
a && a;
console.log(a);
}
expect: {
var a = "foo";
a++ + a;
a;
console.log(a);
}
expect_stdout: "NaN"
}
issue_3882: {
options = {
evaluate: true,
reduce_vars: true,
toplevel: true,
unused: true,
}
input: {
function f(a) {
return console.log(a++), a && this;
}
var b = f();
console.log(b);
}
expect: {
var b = function(a) {
return console.log(a++), a && this;
}();
console.log(b);
}
expect_stdout: [
"NaN",
"NaN",
]
}
issue_3887: {
options = {
evaluate: true,
reduce_vars: true,
unused: true,
}
input: {
(function(b) {
try {
b-- && console.log("PASS");
} catch (a_2) {}
})(1);
}
expect: {
(function(b) {
try {
b-- && console.log("PASS");
} catch (a_2) {}
})(1);
}
expect_stdout: "PASS"
}

View File

@@ -2877,7 +2877,7 @@ issue_2437: {
} }
} }
issue_2485: { issue_2485_1: {
options = { options = {
functions: true, functions: true,
reduce_funcs: true, reduce_funcs: true,
@@ -2926,6 +2926,54 @@ issue_2485: {
expect_stdout: "6" expect_stdout: "6"
} }
issue_2485_2: {
options = {
functions: true,
inline: true,
reduce_funcs: true,
reduce_vars: true,
unused: true,
}
input: {
var foo = function(bar) {
var n = function(a, b) {
return a + b;
};
var sumAll = function(arg) {
return arg.reduce(n, 0);
};
var runSumAll = function(arg) {
return sumAll(arg);
};
bar.baz = function(arg) {
var n = runSumAll(arg);
return (n.get = 1), n;
};
return bar;
};
var bar = foo({});
console.log(bar.baz([1, 2, 3]));
}
expect: {
var foo = function(bar) {
function n(a, b) {
return a + b;
}
function runSumAll(arg) {
return arg.reduce(n, 0);
}
bar.baz = function(arg) {
var n = runSumAll(arg);
return (n.get = 1), n;
};
return bar;
};
var bar = foo({});
console.log(bar.baz([1, 2, 3]));
}
expect_stdout: "6"
}
issue_3364: { issue_3364: {
options = { options = {
functions: true, functions: true,
@@ -3113,9 +3161,7 @@ issue_3400_1: {
return console.log(o[g]), o; return console.log(o[g]), o;
} }
function e() { function e() {
return [ 42 ].map(function(v) { return [ 42 ].map(h);
return h(v);
});
} }
return e(); return e();
}()[0].p); }()[0].p);
@@ -3130,7 +3176,7 @@ issue_3400_2: {
options = { options = {
collapse_vars: true, collapse_vars: true,
inline: true, inline: true,
passes: 2, passes: 3,
reduce_funcs: true, reduce_funcs: true,
reduce_vars: true, reduce_vars: true,
unused: true, unused: true,
@@ -3158,11 +3204,11 @@ issue_3400_2: {
} }
expect: { expect: {
void console.log(function g() { void console.log(function g() {
return [ 42 ].map(function(v) { return [ 42 ].map(function(u) {
return o = { var o = {
p: v p: u
}, console.log(o[g]), o; };
var o; return console.log(o[g]), o;
}); });
}()[0].p); }()[0].p);
} }
@@ -3468,10 +3514,9 @@ hoisted_single_use: {
} }
expect: { expect: {
function f(a) { function f(a) {
for (var r in a) g(r); for (var r in a) (function(a) {
}
function g(a) {
console.log(a); console.log(a);
})(r);
} }
(function(a) { (function(a) {
var g = a.bar; var g = a.bar;
@@ -3568,8 +3613,8 @@ pr_3592_2: {
var g = [ "PASS" ]; var g = [ "PASS" ];
console.log((z = "PASS", function(problem) { console.log((z = "PASS", function(problem) {
return g[problem]; return g[problem];
}((y = z, problem(y))))); }(problem(z))));
var z, y; var z;
} }
expect_stdout: "PASS" expect_stdout: "PASS"
} }
@@ -3635,9 +3680,7 @@ pr_3595_1: {
} }
console.log((arg = "PASS", function(problem) { console.log((arg = "PASS", function(problem) {
return g[problem]; return g[problem];
}(function(arg) { }(problem(arg))));
return problem(arg);
}(arg))));
var arg; var arg;
} }
expect_stdout: "PASS" expect_stdout: "PASS"
@@ -3678,9 +3721,7 @@ pr_3595_2: {
} }
console.log(function(problem) { console.log(function(problem) {
return g[problem]; return g[problem];
}(function(arg) { }(problem("PASS")));
return problem(arg);
}("PASS")));
} }
expect_stdout: "PASS" expect_stdout: "PASS"
} }
@@ -4062,3 +4103,580 @@ issue_3777_2: {
} }
expect_stdout: "PASS" expect_stdout: "PASS"
} }
issue_3821_1: {
options = {
inline: true,
}
input: {
var a = 0;
console.log(function(b) {
return +a + b;
}(--a));
}
expect: {
var a = 0;
console.log(function(b) {
return +a + b;
}(--a));
}
expect_stdout: "-2"
}
issue_3821_2: {
options = {
inline: true,
reduce_vars: true,
toplevel: true,
}
input: {
var a = "PASS";
function f(g, b) {
return g(), b;
}
console.log(f(function() {
a = "FAIL";
}, a));
}
expect: {
var a = "PASS";
function f(g, b) {
return g(), b;
}
console.log(f(function() {
a = "FAIL";
}, a));
}
expect_stdout: "PASS"
}
substitute: {
options = {
inline: true,
reduce_vars: true,
toplevel: true,
}
input: {
var o = {};
function f(a) {
return a === o ? "PASS" : "FAIL";
}
[
function() {
return f;
},
function() {
return function(b) {
return f(b);
};
},
function() {
"use strict";
return function(c) {
return f(c);
};
},
function() {
return function(c) {
"use strict";
return f(c);
};
},
function() {
return function(d, e) {
return f(d, e);
};
},
].forEach(function(g) {
console.log(g()(o), g().call(o, o), g().length);
});
}
expect: {
var o = {};
function f(a) {
return a === o ? "PASS" : "FAIL";
}
[
function() {
return f;
},
function() {
return f;
},
function() {
"use strict";
return f;
},
function() {
return f;
},
function() {
return function(d, e) {
return f(d, e);
};
},
].forEach(function(g) {
console.log(g()(o), g().call(o, o), g().length);
});
}
expect_stdout: [
"PASS PASS 1",
"PASS PASS 1",
"PASS PASS 1",
"PASS PASS 1",
"PASS PASS 2",
]
}
substitute_add_farg: {
options = {
inline: true,
keep_fargs: "strict",
}
input: {
function f(g) {
console.log(g.length);
g(null, "FAIL");
}
f(function() {
return function(a, b) {
return function(c) {
do {
console.log("PASS");
} while (c);
}(a, b);
};
}());
}
expect: {
function f(g) {
console.log(g.length);
g(null, "FAIL");
}
f(function(c, argument_1) {
do {
console.log("PASS");
} while (c);
});
}
expect_stdout: [
"2",
"PASS",
]
}
substitute_arguments: {
options = {
inline: true,
reduce_vars: true,
toplevel: true,
}
input: {
var o = {};
function f(a) {
return arguments[0] === o ? "PASS" : "FAIL";
}
[
function() {
return f;
},
function() {
return function(b) {
return f(b);
};
},
function() {
"use strict";
return function(c) {
return f(c);
};
},
function() {
return function(c) {
"use strict";
return f(c);
};
},
function() {
return function(d, e) {
return f(d, e);
};
},
].forEach(function(g) {
console.log(g()(o), g().call(o, o), g().length);
});
}
expect: {
var o = {};
function f(a) {
return arguments[0] === o ? "PASS" : "FAIL";
}
[
function() {
return f;
},
function() {
return function(b) {
return f(b);
};
},
function() {
"use strict";
return function(c) {
return f(c);
};
},
function() {
return function(c) {
"use strict";
return f(c);
};
},
function() {
return function(d, e) {
return f(d, e);
};
},
].forEach(function(g) {
console.log(g()(o), g().call(o, o), g().length);
});
}
expect_stdout: [
"PASS PASS 1",
"PASS PASS 1",
"PASS PASS 1",
"PASS PASS 1",
"PASS PASS 2",
]
}
substitute_drop_farg: {
options = {
inline: true,
keep_fargs: false,
reduce_vars: true,
toplevel: true,
}
input: {
var o = {};
function f(a) {
return a === o ? "PASS" : "FAIL";
}
[
function() {
return f;
},
function() {
return function(b) {
return f(b);
};
},
function() {
"use strict";
return function(c) {
return f(c);
};
},
function() {
return function(c) {
"use strict";
return f(c);
};
},
function() {
return function(d, e) {
return f(d, e);
};
},
].forEach(function(g) {
console.log(g()(o), g().call(o, o));
});
}
expect: {
var o = {};
function f(a) {
return a === o ? "PASS" : "FAIL";
}
[
function() {
return f;
},
function() {
return f;
},
function() {
"use strict";
return f;
},
function() {
return f;
},
function() {
return f;
},
].forEach(function(g) {
console.log(g()(o), g().call(o, o));
});
}
expect_stdout: [
"PASS PASS",
"PASS PASS",
"PASS PASS",
"PASS PASS",
"PASS PASS",
]
}
substitute_this: {
options = {
inline: true,
reduce_vars: true,
toplevel: true,
}
input: {
var o = {};
function f(a) {
return a === o ? this === o : "FAIL";
}
[
function() {
return f;
},
function() {
return function(b) {
return f(b);
};
},
function() {
"use strict";
return function(c) {
return f(c);
};
},
function() {
return function(c) {
"use strict";
return f(c);
};
},
function() {
return function(d, e) {
return f(d, e);
};
},
].forEach(function(g) {
console.log(g()(o), g().call(o, o), g().length);
});
}
expect: {
var o = {};
function f(a) {
return a === o ? this === o : "FAIL";
}
[
function() {
return f;
},
function() {
return function(b) {
return f(b);
};
},
function() {
"use strict";
return function(c) {
return f(c);
};
},
function() {
return function(c) {
"use strict";
return f(c);
};
},
function() {
return function(d, e) {
return f(d, e);
};
},
].forEach(function(g) {
console.log(g()(o), g().call(o, o), g().length);
});
}
expect_stdout: [
"false true 1",
"false false 1",
"false false 1",
"false false 1",
"false false 2",
]
}
substitute_use_strict: {
options = {
inline: true,
reduce_vars: true,
toplevel: true,
}
input: {
var o = {};
function f(a) {
"use strict";
return a === o ? "PASS" : "FAIL";
}
[
function() {
return f;
},
function() {
return function(b) {
return f(b);
};
},
function() {
"use strict";
return function(c) {
return f(c);
};
},
function() {
return function(c) {
"use strict";
return f(c);
};
},
function() {
return function(d, e) {
return f(d, e);
};
},
].forEach(function(g) {
console.log(g()(o), g().call(o, o), g().length);
});
}
expect: {
var o = {};
function f(a) {
"use strict";
return a === o ? "PASS" : "FAIL";
}
[
function() {
return f;
},
function() {
return f;
},
function() {
"use strict";
return f;
},
function() {
return f;
},
function() {
return function(d, e) {
return f(d, e);
};
},
].forEach(function(g) {
console.log(g()(o), g().call(o, o), g().length);
});
}
expect_stdout: [
"PASS PASS 1",
"PASS PASS 1",
"PASS PASS 1",
"PASS PASS 1",
"PASS PASS 2",
]
}
issue_3833: {
options = {
inline: true,
keep_fargs: "strict",
reduce_vars: true,
toplevel: true,
unused: true,
}
input: {
function f(a) {
return function() {
while (a);
console.log("PASS");
}();
}
f();
}
expect: {
(function() {
while (a);
console.log("PASS");
})();
var a;
}
expect_stdout: "PASS"
}
issue_3835: {
options = {
inline: true,
reduce_vars: true,
}
input: {
(function f() {
return function() {
return f();
}();
})();
}
expect: {
(function f() {
return f();
})();
}
expect_stdout: true
}
issue_3836: {
options = {
inline: true,
}
input: {
(function() {
return function() {
for (var a in 0)
console.log(k);
}(console.log("PASS"));
})();
}
expect: {
(function() {
for (var a in 0)
console.log(k);
})(console.log("PASS"));
}
expect_stdout: "PASS"
}
issue_3852: {
options = {
collapse_vars: true,
inline: true,
unused: true,
}
input: {
console.log(function(a) {
return function(b) {
return b && (b[0] = 0), "PASS";
}(a);
}(42));
}
expect: {
console.log(function(a) {
return a && (a[0] = 0), "PASS";
}(42));
}
expect_stdout: "PASS"
}

View File

@@ -767,18 +767,17 @@ issue_3071_1: {
var obj = {}; var obj = {};
obj.one = 1; obj.one = 1;
obj.two = 2; obj.two = 2;
console.log(obj.one); console.log(obj.one, obj.two);
})(); })();
} }
expect: { expect: {
console.log(1); console.log(1, 2);
} }
expect_stdout: "1" expect_stdout: "1 2"
} }
issue_3071_2: { issue_3071_2: {
options = { options = {
evaluate: true,
hoist_props: true, hoist_props: true,
inline: true, inline: true,
join_vars: true, join_vars: true,
@@ -793,19 +792,18 @@ issue_3071_2: {
obj = {}; obj = {};
obj.one = 1; obj.one = 1;
obj.two = 2; obj.two = 2;
console.log(obj.one); console.log(obj.one, obj.two);
var obj; var obj;
})(); })();
} }
expect: { expect: {
console.log(1); console.log(1, 2);
} }
expect_stdout: "1" expect_stdout: "1 2"
} }
issue_3071_2_toplevel: { issue_3071_2_toplevel: {
options = { options = {
evaluate: true,
hoist_props: true, hoist_props: true,
inline: true, inline: true,
join_vars: true, join_vars: true,
@@ -821,14 +819,14 @@ issue_3071_2_toplevel: {
obj = {}; obj = {};
obj.one = 1; obj.one = 1;
obj.two = 2; obj.two = 2;
console.log(obj.one); console.log(obj.one, obj.two);
var obj; var obj;
})(); })();
} }
expect: { expect: {
console.log(1); console.log(1, 2);
} }
expect_stdout: "1" expect_stdout: "1 2"
} }
issue_3071_3: { issue_3071_3: {
@@ -914,3 +912,63 @@ issue_3440: {
} }
expect_stdout: "PASS" expect_stdout: "PASS"
} }
issue_3868: {
options = {
hoist_props: true,
passes: 2,
reduce_vars: true,
side_effects: true,
}
input: {
(function(t) {
t = {};
({
get p() {},
q: (console.log("PASS"), +t),
}).r;
})();
}
expect: {
(function(t) {
t = {};
({
get p() {},
q: (console.log("PASS"), +t),
}).r;
})();
}
expect_stdout: "PASS"
}
issue_3871: {
options = {
hoist_props: true,
reduce_vars: true,
}
input: {
console.log(function() {
do {
var b = {
get null() {
c;
}
};
} while (!b);
return "PASS";
}());
}
expect: {
console.log(function() {
do {
var b = {
get null() {
c;
}
};
} while (!b);
return "PASS";
}());
}
expect_stdout: "PASS"
}

View File

@@ -2389,7 +2389,7 @@ issue_3703: {
var a = "PASS"; var a = "PASS";
(function() { (function() {
var b; var b;
var c = function g() { var c = function() {
a = "FAIL"; a = "FAIL";
}; };
a ? b |= c : b.p; a ? b |= c : b.p;
@@ -2420,3 +2420,72 @@ issue_3750: {
} }
expect_stdout: "PASS" expect_stdout: "PASS"
} }
issue_3823: {
options = {
ie8: true,
toplevel: true,
unused: true,
}
input: {
for (var i = 0; i < 1; i++) {
var a = a ? function f() {
f;
} : 0;
console.log("PASS", typeof f);
}
}
expect: {
for (var i = 0; i < 1; i++) {
(function f() {
f;
});
console.log("PASS", typeof f);
}
}
expect_stdout: "PASS undefined"
}
issue_3825: {
options = {
ie8: true,
pure_getters: "strict",
side_effects: true,
}
input: {
console.log({}[void (0..length ? 1 : 2)]);
}
expect: {
console.log({}[void 0]);
}
expect_stdout: "undefined"
}
issue_3889: {
options = {
evaluate: true,
ie8: true,
reduce_vars: true,
}
input: {
function f(a) {
a = 0;
(function a() {
var a;
console.log(a);
})();
}
f();
}
expect: {
function f(a) {
a = 0;
(function a() {
var a;
console.log(a);
})();
}
f();
}
expect_stdout: "undefined"
}

View File

@@ -573,3 +573,24 @@ issue_3600: {
} }
expect_stdout: "1" expect_stdout: "1"
} }
iife_if_return_simple: {
options = {
conditionals: true,
if_return: true,
inline: true,
sequences: true,
side_effects: true,
}
input: {
(function() {
if (console)
return console.log("PASS");
console.log("FAIL");
})();
}
expect: {
console ? console.log("PASS") : console.log("FAIL");
}
expect_stdout: "PASS"
}

View File

@@ -90,13 +90,13 @@ non_hoisted_function_after_return_2a: {
"WARN: Declarations in unreachable code! [test/compress/issue-1034.js:7,16]", "WARN: Declarations in unreachable code! [test/compress/issue-1034.js:7,16]",
"WARN: Dropping unused variable a [test/compress/issue-1034.js:4,20]", "WARN: Dropping unused variable a [test/compress/issue-1034.js:4,20]",
"WARN: Dropping unused function nope [test/compress/issue-1034.js:11,21]", "WARN: Dropping unused function nope [test/compress/issue-1034.js:11,21]",
"INFO: pass 0: last_count: Infinity, count: 37", "INFO: pass 0: last_count: Infinity, count: 36",
"WARN: Dropping unreachable code [test/compress/issue-1034.js:9,12]", "WARN: Dropping unreachable code [test/compress/issue-1034.js:9,12]",
"WARN: Declarations in unreachable code! [test/compress/issue-1034.js:9,12]", "WARN: Declarations in unreachable code! [test/compress/issue-1034.js:9,12]",
"WARN: Dropping unreachable code [test/compress/issue-1034.js:12,12]", "WARN: Dropping unreachable code [test/compress/issue-1034.js:12,12]",
"INFO: Dropping unused variable b [test/compress/issue-1034.js:7,20]", "INFO: Dropping unused variable b [test/compress/issue-1034.js:7,20]",
"INFO: Dropping unused variable c [test/compress/issue-1034.js:9,16]", "INFO: Dropping unused variable c [test/compress/issue-1034.js:9,16]",
"INFO: pass 1: last_count: 37, count: 18", "INFO: pass 1: last_count: 36, count: 18",
] ]
} }
@@ -248,13 +248,13 @@ non_hoisted_function_after_return_2a_strict: {
"WARN: Declarations in unreachable code! [test/compress/issue-1034.js:8,16]", "WARN: Declarations in unreachable code! [test/compress/issue-1034.js:8,16]",
"WARN: Dropping unused variable a [test/compress/issue-1034.js:5,20]", "WARN: Dropping unused variable a [test/compress/issue-1034.js:5,20]",
"WARN: Dropping unused function nope [test/compress/issue-1034.js:12,21]", "WARN: Dropping unused function nope [test/compress/issue-1034.js:12,21]",
"INFO: pass 0: last_count: Infinity, count: 48", "INFO: pass 0: last_count: Infinity, count: 47",
"WARN: Dropping unreachable code [test/compress/issue-1034.js:10,12]", "WARN: Dropping unreachable code [test/compress/issue-1034.js:10,12]",
"WARN: Declarations in unreachable code! [test/compress/issue-1034.js:10,12]", "WARN: Declarations in unreachable code! [test/compress/issue-1034.js:10,12]",
"WARN: Dropping unreachable code [test/compress/issue-1034.js:13,12]", "WARN: Dropping unreachable code [test/compress/issue-1034.js:13,12]",
"INFO: Dropping unused variable b [test/compress/issue-1034.js:8,20]", "INFO: Dropping unused variable b [test/compress/issue-1034.js:8,20]",
"INFO: Dropping unused variable c [test/compress/issue-1034.js:10,16]", "INFO: Dropping unused variable c [test/compress/issue-1034.js:10,16]",
"INFO: pass 1: last_count: 48, count: 29", "INFO: pass 1: last_count: 47, count: 29",
] ]
} }

View File

@@ -22,8 +22,9 @@ issue_1639_1: {
console.log(a, b); console.log(a, b);
} }
expect: { expect: {
for (var a = 100, b = 10, L1 = 5; --L1 > 0;) for (var a = 100, b = 10, L1 = 5, ignore; --L1 > 0;) {
if (--b, 0) var ignore = 0; --b;
}
console.log(a, b); console.log(a, b);
} }
expect_stdout: "100 6" expect_stdout: "100 6"

View File

@@ -35,11 +35,7 @@ f7: {
console.log(a, b); console.log(a, b);
} }
expect_exact: [ expect_exact: [
"var b = 10;", "console.log(100, 100);",
"",
"!function() {",
" b = 100;",
"}(), console.log(100, b);",
] ]
expect_stdout: true expect_stdout: "100 100"
} }

1025
test/compress/join_vars.js Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1226,8 +1226,7 @@ chained_3: {
} }
expect: { expect: {
console.log(function(b) { console.log(function(b) {
var c = 1; var c = 1, c = b;
c = b;
b++; b++;
return c; return c;
}(2)); }(2));

View File

@@ -3,6 +3,7 @@ labels_1: {
conditionals: true, conditionals: true,
dead_code: true, dead_code: true,
if_return: true, if_return: true,
unused: true,
} }
input: { input: {
out: { out: {
@@ -21,6 +22,7 @@ labels_2: {
conditionals: true, conditionals: true,
dead_code: true, dead_code: true,
if_return: true, if_return: true,
unused: true,
} }
input: { input: {
out: { out: {
@@ -61,6 +63,7 @@ labels_4: {
conditionals: true, conditionals: true,
dead_code: true, dead_code: true,
if_return: true, if_return: true,
unused: true,
} }
input: { input: {
out: for (var i = 0; i < 5; ++i) { out: for (var i = 0; i < 5; ++i) {
@@ -105,6 +108,9 @@ labels_5: {
} }
labels_6: { labels_6: {
options = {
dead_code: true,
}
input: { input: {
out: break out; out: break out;
}; };
@@ -159,6 +165,7 @@ labels_9: {
conditionals: true, conditionals: true,
dead_code: true, dead_code: true,
if_return: true, if_return: true,
unused: true,
} }
input: { input: {
out: while (foo) { out: while (foo) {

View File

@@ -549,6 +549,7 @@ issue_2740_2: {
dead_code: true, dead_code: true,
loops: true, loops: true,
passes: 2, passes: 2,
unused: true,
} }
input: { input: {
L1: while (x()) { L1: while (x()) {
@@ -564,6 +565,7 @@ issue_2740_3: {
options = { options = {
dead_code: true, dead_code: true,
loops: true, loops: true,
unused: true,
} }
input: { input: {
L1: for (var x = 0; x < 3; x++) { L1: for (var x = 0; x < 3; x++) {
@@ -589,6 +591,7 @@ issue_2740_4: {
dead_code: true, dead_code: true,
loops: true, loops: true,
passes: 2, passes: 2,
unused: true,
} }
input: { input: {
L1: for (var x = 0; x < 3; x++) { L1: for (var x = 0; x < 3; x++) {
@@ -613,6 +616,7 @@ issue_2740_5: {
dead_code: true, dead_code: true,
loops: true, loops: true,
passes: 2, passes: 2,
unused: true,
} }
input: { input: {
L1: for (var x = 0; x < 3; x++) { L1: for (var x = 0; x < 3; x++) {
@@ -763,6 +767,7 @@ issue_3631_1: {
loops: true, loops: true,
reduce_vars: true, reduce_vars: true,
toplevel: true, toplevel: true,
unused: true,
} }
input: { input: {
var c = 0; var c = 0;
@@ -789,6 +794,7 @@ issue_3631_2: {
loops: true, loops: true,
reduce_vars: true, reduce_vars: true,
toplevel: true, toplevel: true,
unused: true,
} }
input: { input: {
L: for (var a = 1; a--; console.log(b)) { L: for (var a = 1; a--; console.log(b)) {

View File

@@ -1143,558 +1143,6 @@ const_prop_assign_pure: {
} }
} }
join_object_assignments_1: {
options = {
evaluate: true,
join_vars: true,
}
input: {
console.log(function() {
var x = {
a: 1,
c: (console.log("c"), "C"),
};
x.b = 2;
x[3] = function() {
console.log(x);
},
x["a"] = /foo/,
x.bar = x;
return x;
}());
}
expect: {
console.log(function() {
var x = {
a: 1,
c: (console.log("c"), "C"),
b: 2,
3: function() {
console.log(x);
},
a: /foo/,
};
x.bar = x;
return x;
}());
}
expect_stdout: true
}
join_object_assignments_2: {
options = {
evaluate: true,
hoist_props: true,
join_vars: true,
passes: 3,
reduce_vars: true,
toplevel: true,
unused: true,
}
input: {
var o = {
foo: 1,
};
o.bar = 2;
o.baz = 3;
console.log(o.foo, o.bar + o.bar, o.foo * o.bar * o.baz);
}
expect: {
console.log(1, 4, 6);
}
expect_stdout: "1 4 6"
}
join_object_assignments_3: {
options = {
evaluate: true,
join_vars: true,
}
input: {
console.log(function() {
var o = {
a: "PASS",
}, a = o.a;
o.a = "FAIL";
return a;
}());
}
expect: {
console.log(function() {
var o = {
a: "PASS",
}, a = o.a;
o.a = "FAIL";
return a;
}());
}
expect_stdout: "PASS"
}
join_object_assignments_4: {
options = {
join_vars: true,
sequences: true,
}
input: {
var o;
console.log(o);
o = {};
o.a = "foo";
console.log(o.b);
o.b = "bar";
console.log(o.a);
}
expect: {
var o;
console.log(o),
o = {
a: "foo",
},
console.log(o.b),
o.b = "bar",
console.log(o.a);
}
expect_stdout: [
"undefined",
"undefined",
"foo",
]
}
join_object_assignments_return_1: {
options = {
join_vars: true,
}
input: {
console.log(function() {
var o = {
p: 3
};
return o.q = "foo";
}());
}
expect: {
console.log(function() {
var o = {
p: 3,
q: "foo"
};
return o.q;
}());
}
expect_stdout: "foo"
}
join_object_assignments_return_2: {
options = {
join_vars: true,
}
input: {
console.log(function() {
var o = {
p: 3
};
return o.q = /foo/,
o.r = "bar";
}());
}
expect: {
console.log(function() {
var o = {
p: 3,
q: /foo/,
r: "bar"
};
return o.r;
}());
}
expect_stdout: "bar"
}
join_object_assignments_return_3: {
options = {
join_vars: true,
}
input: {
console.log(function() {
var o = {
p: 3
};
return o.q = "foo",
o.p += "",
console.log(o.q),
o.p;
}());
}
expect: {
console.log(function() {
var o = {
p: 3,
q: "foo"
};
return o.p += "",
console.log(o.q),
o.p;
}());
}
expect_stdout: [
"foo",
"3",
]
}
join_object_assignments_for: {
options = {
join_vars: true,
}
input: {
console.log(function() {
var o = {
p: 3
};
for (o.q = "foo"; console.log(o.q););
return o.p;
}());
}
expect: {
console.log(function() {
for (var o = {
p: 3,
q: "foo"
}; console.log(o.q););
return o.p;
}());
}
expect_stdout: [
"foo",
"3",
]
}
join_object_assignments_if: {
options = {
join_vars: true,
}
input: {
console.log(function() {
var o = {};
if (o.a = "PASS") return o.a;
}())
}
expect: {
console.log(function() {
var o = { a: "PASS" };
if (o.a) return o.a;
}());
}
expect_stdout: "PASS"
}
join_object_assignments_forin: {
options = {
join_vars: true,
}
input: {
console.log(function() {
var o = {};
for (var a in o.a = "PASS", o)
return o[a];
}())
}
expect: {
console.log(function() {
var o = { a: "PASS" };
for (var a in o)
return o[a];
}());
}
expect_stdout: "PASS"
}
join_object_assignments_negative: {
options = {
evaluate: true,
join_vars: true,
properties: true,
}
input: {
var o = {};
o[0] = 0;
o[-0] = 1;
o[-1] = 2;
console.log(o[0], o[-0], o[-1]);
}
expect: {
var o = {
0: 0,
0: 1,
"-1": 2
};
console.log(o[0], o[-0], o[-1]);
}
expect_stdout: "1 1 2"
}
join_object_assignments_NaN_1: {
options = {
join_vars: true,
}
input: {
var o = {};
o[NaN] = 1;
o[0/0] = 2;
console.log(o[NaN], o[NaN]);
}
expect: {
var o = {};
o[NaN] = 1;
o[0/0] = 2;
console.log(o[NaN], o[NaN]);
}
expect_stdout: "2 2"
}
join_object_assignments_NaN_2: {
options = {
evaluate: true,
join_vars: true,
properties: true,
}
input: {
var o = {};
o[NaN] = 1;
o[0/0] = 2;
console.log(o[NaN], o[NaN]);
}
expect: {
var o = {
NaN: 1,
NaN: 2
};
console.log(o.NaN, o.NaN);
}
expect_stdout: "2 2"
}
join_object_assignments_null_0: {
options = {
join_vars: true,
}
input: {
var o = {};
o[null] = 1;
console.log(o[null]);
}
expect: {
var o = {};
o[null] = 1;
console.log(o[null]);
}
expect_stdout: "1"
}
join_object_assignments_null_1: {
options = {
evaluate: true,
join_vars: true,
properties: true,
}
input: {
var o = {};
o[null] = 1;
console.log(o[null]);
}
expect: {
var o = {
null: 1
};
console.log(o.null);
}
expect_stdout: "1"
}
join_object_assignments_void_0: {
options = {
evaluate: true,
join_vars: true,
}
input: {
var o = {};
o[void 0] = 1;
console.log(o[void 0]);
}
expect: {
var o = {
undefined: 1
};
console.log(o[void 0]);
}
expect_stdout: "1"
}
join_object_assignments_undefined_1: {
options = {
join_vars: true,
}
input: {
var o = {};
o[undefined] = 1;
console.log(o[undefined]);
}
expect: {
var o = {};
o[void 0] = 1;
console.log(o[void 0]);
}
expect_stdout: "1"
}
join_object_assignments_undefined_2: {
options = {
evaluate: true,
join_vars: true,
properties: true,
}
input: {
var o = {};
o[undefined] = 1;
console.log(o[undefined]);
}
expect: {
var o = {
undefined : 1
};
console.log(o[void 0]);
}
expect_stdout: "1"
}
join_object_assignments_Infinity: {
options = {
evaluate: true,
join_vars: true,
properties: true,
}
input: {
var o = {};
o[Infinity] = 1;
o[1/0] = 2;
o[-Infinity] = 3;
o[-1/0] = 4;
console.log(o[Infinity], o[1/0], o[-Infinity], o[-1/0]);
}
expect: {
var o = {
Infinity: 1,
Infinity: 2,
"-Infinity": 3,
"-Infinity": 4
};
console.log(o[1/0], o[1/0], o[-1/0], o[-1/0]);
}
expect_stdout: "2 2 4 4"
}
join_object_assignments_regex: {
options = {
evaluate: true,
join_vars: true,
properties: true,
}
input: {
var o = {};
o[/rx/] = 1;
console.log(o[/rx/]);
}
expect: {
var o = {
"/rx/": 1
};
console.log(o[/rx/]);
}
expect_stdout: "1"
}
issue_2816: {
options = {
join_vars: true,
}
input: {
"use strict";
var o = {
a: 1
};
o.b = 2;
o.a = 3;
o.c = 4;
console.log(o.a, o.b, o.c);
}
expect: {
"use strict";
var o = {
a: 1,
b: 2
};
o.a = 3;
o.c = 4;
console.log(o.a, o.b, o.c);
}
expect_stdout: "3 2 4"
}
issue_2893_1: {
options = {
join_vars: true,
}
input: {
var o = {
get a() {
return "PASS";
},
};
o.a = "FAIL";
console.log(o.a);
}
expect: {
var o = {
get a() {
return "PASS";
},
};
o.a = "FAIL";
console.log(o.a);
}
expect_stdout: "PASS"
}
issue_2893_2: {
options = {
join_vars: true,
}
input: {
var o = {
set a(v) {
this.b = v;
},
b: "FAIL",
};
o.a = "PASS";
console.log(o.b);
}
expect: {
var o = {
set a(v) {
this.b = v;
},
b: "FAIL",
};
o.a = "PASS";
console.log(o.b);
}
expect_stdout: "PASS"
}
issue_869_1: { issue_869_1: {
mangle = { mangle = {
properties: { properties: {
@@ -1856,36 +1304,6 @@ issue_3188_3: {
expect_stdout: "PASS" expect_stdout: "PASS"
} }
join_expr: {
options = {
evaluate: true,
join_vars: true,
}
input: {
var c = "FAIL";
(function() {
var a = 0;
switch ((a = {}) && (a.b = 0)) {
case 0:
c = "PASS";
}
})();
console.log(c);
}
expect: {
var c = "FAIL";
(function() {
var a = 0;
switch (a = { b: 0 }, a.b) {
case 0:
c = "PASS";
}
})();
console.log(c);
}
expect_stdout: "PASS"
}
issue_3389: { issue_3389: {
options = { options = {
evaluate: true, evaluate: true,

View File

@@ -680,3 +680,130 @@ issue_3325_2: {
} }
expect_stdout: "PASS" expect_stdout: "PASS"
} }
issue_3858: {
options = {
collapse_vars: true,
inline: true,
keep_fargs: "strict",
unused: true,
}
input: {
var f = function(a) {
return /*@__PURE__*/ function(b) {
console.log(b);
}(a);
};
f("PASS");
}
expect: {
var f = function(a) {
return function() {
console.log(a);
}();
};
f("PASS");
}
expect_stdout: "PASS"
}
inline_pure_call_1: {
options = {
collapse_vars: true,
inline: true,
keep_fargs: "strict",
reduce_vars: true,
sequences: true,
side_effects: true,
toplevel: true,
unused: true,
}
input: {
var f = function(a) {
return /*@__PURE__*/ function(b) {
console.log(b);
}(a);
};
f("PASS");
}
expect: {}
}
inline_pure_call_2: {
options = {
collapse_vars: true,
inline: true,
keep_fargs: "strict",
reduce_vars: true,
sequences: true,
side_effects: true,
toplevel: true,
unused: true,
}
input: {
var f = function(a) {
return /*@__PURE__*/ function(b) {
console.log(b);
}(a);
};
var a = f("PASS");
}
expect: {}
}
inline_pure_call_3: {
options = {
collapse_vars: true,
evaluate: true,
inline: true,
keep_fargs: "strict",
passes: 2,
reduce_vars: true,
toplevel: true,
unused: true,
}
input: {
var f = function(a) {
return /*@__PURE__*/ function(b) {
console.log(b);
}(a);
};
var a = f("PASS");
console.log(a);
}
expect: {
var a = function() {
console.log("PASS");
}();
console.log(a);
}
expect_stdout: [
"PASS",
"undefined",
]
}
inline_pure_call_4: {
options = {
evaluate: true,
reduce_vars: true,
toplevel: true,
unused: true,
}
input: {
var a = /*@__PURE__*/ function() {
return console.log("PASS"), 42;
}();
console.log(a);
}
expect: {
var a = function() {
return console.log("PASS"), 42;
}();
console.log(a);
}
expect_stdout: [
"PASS",
"42",
]
}

View File

@@ -223,6 +223,25 @@ unsafe_evaluate: {
expect_stdout: true expect_stdout: true
} }
unsafe_evaluate_defun: {
options = {
evaluate: true,
reduce_vars: true,
unsafe: true,
unused: true,
}
input: {
console.log(function() {
function f() {}
return ++f;
}());
}
expect: {
console.log(NaN);
}
expect_stdout: "NaN"
}
unsafe_evaluate_side_effect_free_1: { unsafe_evaluate_side_effect_free_1: {
options = { options = {
evaluate: true, evaluate: true,
@@ -1178,11 +1197,10 @@ toplevel_on_loops_1: {
while (x); while (x);
} }
expect: { expect: {
function bar() {
console.log("bar:", --x);
}
var x = 3; var x = 3;
for (;bar(), x;); for (;function() {
console.log("bar:", --x);
}(), x;);
} }
expect_stdout: true expect_stdout: true
} }
@@ -1235,10 +1253,9 @@ toplevel_on_loops_2: {
while (x); while (x);
} }
expect: { expect: {
function bar() { for (;;) (function() {
console.log("bar:"); console.log("bar:");
} })();
for (;;) bar();
} }
} }
@@ -4212,13 +4229,12 @@ issue_2450_4: {
} }
expect: { expect: {
var a; var a;
function f(b) {
console.log(a === b);
a = b;
}
function g() {} function g() {}
for (var i = 3; --i >= 0;) for (var i = 3; --i >= 0;)
f(g); (function(b) {
console.log(a === b);
a = b;
})(g);
} }
expect_stdout: [ expect_stdout: [
"false", "false",
@@ -4319,14 +4335,13 @@ perf_1: {
console.log(sum); console.log(sum);
} }
expect: { expect: {
function indirect_foo(x, y, z) { var sum = 0;
for (var i = 0; i < 100; ++i)
sum += function(x, y, z) {
return function(x, y, z) { return function(x, y, z) {
return x < y ? x * y + z : x * z - y; return x < y ? x * y + z : x * z - y;
}(x, y, z); }(x, y, z);
} }(i, i + 1, 3 * i);
var sum = 0;
for (var i = 0; i < 100; ++i)
sum += indirect_foo(i, i + 1, 3 * i);
console.log(sum); console.log(sum);
} }
expect_stdout: "348150" expect_stdout: "348150"
@@ -4387,14 +4402,13 @@ perf_3: {
console.log(sum); console.log(sum);
} }
expect: { expect: {
var indirect_foo = function(x, y, z) { var sum = 0;
for (var i = 0; i < 100; ++i)
sum += function(x, y, z) {
return function(x, y, z) { return function(x, y, z) {
return x < y ? x * y + z : x * z - y; return x < y ? x * y + z : x * z - y;
}(x, y, z); }(x, y, z);
} }(i, i + 1, 3 * i);
var sum = 0;
for (var i = 0; i < 100; ++i)
sum += indirect_foo(i, i + 1, 3 * i);
console.log(sum); console.log(sum);
} }
expect_stdout: "348150" expect_stdout: "348150"
@@ -4456,14 +4470,13 @@ perf_5: {
console.log(sum); console.log(sum);
} }
expect: { expect: {
function indirect_foo(x, y, z) { var sum = 0;
for (var i = 0; i < 100; ++i)
sum += function(x, y, z) {
return function(x, y, z) { return function(x, y, z) {
return x < y ? x * y + z : x * z - y; return x < y ? x * y + z : x * z - y;
}(x, y, z); }(x, y, z);
} }(i, i + 1, 3 * i);
var sum = 0;
for (var i = 0; i < 100; ++i)
sum += indirect_foo(i, i + 1, 3 * i);
console.log(sum); console.log(sum);
} }
expect_stdout: "348150" expect_stdout: "348150"
@@ -4524,14 +4537,13 @@ perf_7: {
console.log(sum); console.log(sum);
} }
expect: { expect: {
var indirect_foo = function(x, y, z) { var sum = 0;
for (var i = 0; i < 100; ++i)
sum += function(x, y, z) {
return function(x, y, z) { return function(x, y, z) {
return x < y ? x * y + z : x * z - y; return x < y ? x * y + z : x * z - y;
}(x, y, z); }(x, y, z);
} }(i, i + 1, 3 * i);
var sum = 0;
for (var i = 0; i < 100; ++i)
sum += indirect_foo(i, i + 1, 3 * i);
console.log(sum); console.log(sum);
} }
expect_stdout: "348150" expect_stdout: "348150"
@@ -4570,7 +4582,7 @@ perf_8: {
expect_stdout: "348150" expect_stdout: "348150"
} }
issue_2485: { issue_2485_1: {
options = { options = {
reduce_funcs: true, reduce_funcs: true,
reduce_vars: true, reduce_vars: true,
@@ -4618,6 +4630,53 @@ issue_2485: {
expect_stdout: "6" expect_stdout: "6"
} }
issue_2485_2: {
options = {
inline: true,
reduce_funcs: true,
reduce_vars: true,
unused: true,
}
input: {
var foo = function(bar) {
var n = function(a, b) {
return a + b;
};
var sumAll = function(arg) {
return arg.reduce(n, 0);
};
var runSumAll = function(arg) {
return sumAll(arg);
};
bar.baz = function(arg) {
var n = runSumAll(arg);
return (n.get = 1), n;
};
return bar;
};
var bar = foo({});
console.log(bar.baz([1, 2, 3]));
}
expect: {
var foo = function(bar) {
var n = function(a, b) {
return a + b;
};
var runSumAll = function(arg) {
return arg.reduce(n, 0);
};
bar.baz = function(arg) {
var n = runSumAll(arg);
return (n.get = 1), n;
};
return bar;
};
var bar = foo({});
console.log(bar.baz([1, 2, 3]));
}
expect_stdout: "6"
}
issue_2455: { issue_2455: {
options = { options = {
reduce_vars: true, reduce_vars: true,
@@ -5434,8 +5493,7 @@ lvalues_def_1: {
} }
expect: { expect: {
var b = 1; var b = 1;
var a = b++; var a = b++, b = NaN;
b = NaN;
console.log(a, b); console.log(a, b);
} }
expect_stdout: "1 NaN" expect_stdout: "1 NaN"
@@ -5454,8 +5512,7 @@ lvalues_def_2: {
} }
expect: { expect: {
var b = 1; var b = 1;
var a = b += 1; var a = b += 1, b = NaN;
b = NaN;
console.log(a, b); console.log(a, b);
} }
expect_stdout: "2 NaN" expect_stdout: "2 NaN"
@@ -6867,9 +6924,9 @@ issue_3666: {
} finally { } finally {
for (;!a;) for (;!a;)
a++; a++;
var b = a = "PASS"; a = "PASS";
} }
console.log(a, b); console.log(a, "PASS");
} }
expect_stdout: "PASS PASS" expect_stdout: "PASS PASS"
} }
@@ -6912,3 +6969,102 @@ issue_3774: {
} }
expect_stdout: "PASS" expect_stdout: "PASS"
} }
flatten_iife: {
options = {
reduce_vars: true,
side_effects: true,
toplevel: true,
unused: true,
}
input: {
var a = "FAIL";
while (!console);
a++;
(function() {
while (!console);
a = "PASS";
})();
console.log(a);
}
expect: {
var a;
while (!console);
0;
(function() {
while (!console);
a = "PASS";
})();
console.log(a);
}
expect_stdout: "PASS"
}
issue_3844: {
options = {
evaluate: true,
reduce_vars: true,
toplevel: true,
}
input: {
console.log(function() {
if (!console) switch (A = 0) {
case console.log("FAIL"):
return;
}
return typeof A;
}());
}
expect: {
console.log(function() {
if (!console) switch (A = 0) {
case console.log("FAIL"):
return;
}
return typeof A;
}());
}
expect_stdout: "undefined"
}
issue_3866: {
options = {
dead_code: true,
evaluate: true,
reduce_vars: true,
}
input: {
console.log(function() {
{
return "PASS";
var a = 0;
}
return --a;
}());
}
expect: {
console.log("PASS");
}
expect_stdout: "PASS"
}
issue_3880: {
options = {
reduce_funcs: true,
reduce_vars: true,
unused: true,
}
input: {
(function(a) {
while (a.var ^= 1);
console.log("PASS");
})(function() {});
}
expect: {
(function(a) {
while (a.var ^= 1);
console.log("PASS");
})(function() {});
}
expect_stdout: "PASS"
}

View File

@@ -69,6 +69,7 @@ label_if_break: {
dead_code: true, dead_code: true,
evaluate: true, evaluate: true,
side_effects: true, side_effects: true,
unused: true,
} }
input: { input: {
L: if (true) { L: if (true) {

View File

@@ -435,3 +435,25 @@ emberjs_global: {
} }
expect_stdout: Error("PASS") expect_stdout: Error("PASS")
} }
issue_3817: {
options = {
comparisons: true,
conditionals: true,
passes: 2,
typeofs: true,
}
input: {
if ("A" == typeof A || !console.log("PASS")) switch (false) {
case "undefined" == typeof A:
console.log("FAIL");
}
}
expect: {
if ("A" == typeof A || !console.log("PASS")) switch (false) {
case "undefined" == typeof A:
console.log("FAIL");
}
}
expect_stdout: "PASS"
}

View File

@@ -1,2 +1,41 @@
function _toConsumableArray(arr){if(Array.isArray(arr)){for(var i=0,arr2=Array(arr.length);i<arr.length;i++){arr2[i]=arr[i]}return arr2}else{return Array.from(arr)}}var _require=require("bar"),foo=_require.foo;var _require2=require("world"),hello=_require2.hello;foo.x.apply(foo,_toConsumableArray(foo.y(hello.z))); "use strict";
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImlucHV0Mi5qcyJdLCJzb3VyY2VzQ29udGVudCI6WyJjb25zdCB7Zm9vfSA9IHJlcXVpcmUoXCJiYXJcIik7XG5jb25zdCB7aGVsbG99ID0gcmVxdWlyZShcIndvcmxkXCIpO1xuXG5mb28ueCguLi5mb28ueShoZWxsby56KSk7XG4iXSwibmFtZXMiOlsicmVxdWlyZSIsImFyciJdLCJtYXBwaW5ncyI6IjBKQUFjQSxLQUFRQyJ9
function _toConsumableArray(arr) {
return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread();
}
function _nonIterableSpread() {
throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
}
function _unsupportedIterableToArray(o, minLen) {
if (!o) return;
if (typeof o === "string") return _arrayLikeToArray(o, minLen);
var n = Object.prototype.toString.call(o).slice(8, -1);
if (n === "Object" && o.constructor) n = o.constructor.name;
if (n === "Map" || n === "Set") return Array.from(n);
if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen);
}
function _iterableToArray(iter) {
if (typeof Symbol !== "undefined" && Symbol.iterator in Object(iter)) return Array.from(iter);
}
function _arrayWithoutHoles(arr) {
if (Array.isArray(arr)) return _arrayLikeToArray(arr);
}
function _arrayLikeToArray(arr, len) {
if (len == null || len > arr.length) len = arr.length;
for (var i = 0, arr2 = new Array(len); i < len; i++) {
arr2[i] = arr[i];
}
return arr2;
}
var _require = require("bar"), foo = _require.foo;
var _require2 = require("world"), hello = _require2.hello;
foo.x.apply(foo, _toConsumableArray(foo.y(hello.z)));
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImlucHV0LmpzIl0sInNvdXJjZXNDb250ZW50IjpbImNvbnN0IHtmb299ID0gcmVxdWlyZShcImJhclwiKTtcbmNvbnN0IHtoZWxsb30gPSByZXF1aXJlKFwid29ybGRcIik7XG5cbmZvby54KC4uLmZvby55KGhlbGxvLnopKTtcbiJdLCJuYW1lcyI6WyJyZXF1aXJlIiwiZm9vIiwiaGVsbG8iLCJ4IiwiYXBwbHkiLCJfdG9Db25zdW1hYmxlQXJyYXkiLCJ5IiwieiJdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7ZUFBY0EsUUFBUSxRQUFmQyxNLFNBQUFBOztBLGdCQUNTRCxRQUFRLFVBQWpCRSxRLFVBQUFBOztBQUVQRCxJQUFJRSxFQUFKQyxNQUFBSCxLQUFHSSxtQkFBTUosSUFBSUssRUFBRUosTUFBTUsifQ==

View File

@@ -1,4 +1,16 @@
function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } } "use strict";
function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); }
function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(n); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
function _iterableToArray(iter) { if (typeof Symbol !== "undefined" && Symbol.iterator in Object(iter)) return Array.from(iter); }
function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) return _arrayLikeToArray(arr); }
function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; }
var _require = require("bar"), var _require = require("bar"),
foo = _require.foo; foo = _require.foo;

View File

@@ -1 +1,11 @@
{"version":3,"sources":["input2.js"],"names":["require","foo","hello","x","apply","_toConsumableArray","y","z"],"mappings":"kLAAcA,QAAQ,OAAfC,aAAAA,kBACSD,QAAQ,SAAjBE,gBAAAA,MAEPD,IAAIE,EAAJC,MAAAH,IAAAI,mBAASJ,IAAIK,EAAEJ,MAAMK","sourcesContent":["const {foo} = require(\"bar\");\nconst {hello} = require(\"world\");\n\nfoo.x(...foo.y(hello.z));\n"]} {
"version": 3,
"sources": [
"input.js"
],
"names": [],
"mappings": ";;;;;;;;;;;;;;eAAc,OAAO,CAAC,KAAD,C;IAAd,G,YAAA,G;;gBACS,OAAO,CAAC,OAAD,C;IAAhB,K,aAAA,K;;AAEP,GAAG,CAAC,CAAJ,OAAA,GAAG,qBAAM,GAAG,CAAC,CAAJ,CAAM,KAAK,CAAC,CAAZ,CAAN,EAAH",
"sourcesContent": [
"const {foo} = require(\"bar\");\nconst {hello} = require(\"world\");\n\nfoo.x(...foo.y(hello.z));\n"
]
}

View File

@@ -2,4 +2,4 @@ function test(a){
"aaaaaaaaaaaaaaaa" "aaaaaaaaaaaaaaaa"
;a(err,data),a(err, ;a(err,data),a(err,
data)} data)}
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIjAiXSwibmFtZXMiOlsidGVzdCIsImNhbGxiYWNrIiwiZXJyIiwiZGF0YSJdLCJtYXBwaW5ncyI6IkFBQUEsU0FBU0EsS0FBS0M7QUFDVjtDQUNBQSxFQUFTQyxJQUFLQyxNQUNkRixFQUFTQztBQUFLQyJ9 //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIjAiXSwibmFtZXMiOlsidGVzdCIsImNhbGxiYWNrIiwiZXJyIiwiZGF0YSJdLCJtYXBwaW5ncyI6IkFBQUEsU0FBU0EsS0FBS0M7O0NBRVZBLEVBQVNDLElBQUtDLE1BQ2RGLEVBQVNDO0FBQUtDIn0=

View File

@@ -14,9 +14,9 @@ if (typeof phantom == "undefined") {
args.splice(debug, 1); args.splice(debug, 1);
debug = true; debug = true;
} }
args.unshift("bin/uglifyjs");
if (!args.length) args.push("-mcb", "beautify=false,webkit"); if (!args.length) args.push("-mcb", "beautify=false,webkit");
args.push("--timings"); args.unshift("bin/uglifyjs");
args.push("--validate", "--timings");
var child_process = require("child_process"); var child_process = require("child_process");
var fetch = require("./fetch"); var fetch = require("./fetch");
var http = require("http"); var http = require("http");
@@ -41,7 +41,7 @@ if (typeof phantom == "undefined") {
var uglifyjs = child_process.spawn(process.argv[0], args, { var uglifyjs = child_process.spawn(process.argv[0], args, {
silent: true silent: true
}).on("exit", function(code) { }).on("exit", function(code) {
console.log("uglifyjs", url.slice(site.length + 1), args.join(" ")); console.log("uglifyjs", url.slice(site.length + 1), args.slice(1).join(" "));
console.log(stderr); console.log(stderr);
if (code) throw new Error("uglifyjs failed with code " + code); if (code) throw new Error("uglifyjs failed with code " + code);
}); });

View File

@@ -2,6 +2,7 @@ var assert = require("assert");
var exec = require("child_process").exec; var exec = require("child_process").exec;
var fs = require("fs"); var fs = require("fs");
var run_code = require("../sandbox").run_code; var run_code = require("../sandbox").run_code;
var to_ascii = require("../node").to_ascii;
function read(path) { function read(path) {
return fs.readFileSync(path, "utf8"); return fs.readFileSync(path, "utf8");
@@ -48,6 +49,62 @@ describe("bin/uglifyjs", function() {
done(); done();
}); });
}); });
it("Should work with --source-map names=true", function(done) {
exec([
uglifyjscmd,
"--beautify",
"--source-map", [
"names=true",
"url=inline",
].join(","),
].join(" "), function(err, stdout) {
if (err) throw err;
var expected = [
"var obj = {",
" p: a,",
" q: b",
"};",
"//# sourceMappingURL=data:application/json;charset=utf-8;base64,",
].join("\n")
assert.strictEqual(stdout.slice(0, expected.length), expected);
var map = JSON.parse(to_ascii(stdout.slice(expected.length).trim()));
assert.deepEqual(map.names, [ "obj", "p", "a", "q", "b" ]);
done();
}).stdin.end([
"var obj = {",
" p: a,",
" q: b",
"};",
].join("\n"));
});
it("Should work with --source-map names=false", function(done) {
exec([
uglifyjscmd,
"--beautify",
"--source-map", [
"names=false",
"url=inline",
].join(","),
].join(" "), function(err, stdout) {
if (err) throw err;
var expected = [
"var obj = {",
" p: a,",
" q: b",
"};",
"//# sourceMappingURL=data:application/json;charset=utf-8;base64,",
].join("\n")
assert.strictEqual(stdout.slice(0, expected.length), expected);
var map = JSON.parse(to_ascii(stdout.slice(expected.length).trim()));
assert.deepEqual(map.names, []);
done();
}).stdin.end([
"var obj = {",
" p: a,",
" q: b",
"};",
].join("\n"));
});
it("Should give sensible error against invalid input source map", function(done) { it("Should give sensible error against invalid input source map", function(done) {
var command = uglifyjscmd + " test/mocha.js --source-map content=blah,url=inline --verbose"; var command = uglifyjscmd + " test/mocha.js --source-map content=blah,url=inline --verbose";
exec(command, function(err, stdout, stderr) { exec(command, function(err, stdout, stderr) {
@@ -109,9 +166,12 @@ describe("bin/uglifyjs", function() {
} }
var command = [ var command = [
uglifyjscmd, uglifyjscmd,
"--source-map", "content=" + mapFile, "--beautify",
"--source-map", "includeSources=true", "--source-map", [
"--source-map", "url=inline", "content=" + mapFile,
"includeSources",
"url=inline",
].join(","),
].join(" "); ].join(" ");
var child = exec(command, function(err, stdout) { var child = exec(command, function(err, stdout) {

View File

@@ -1,7 +1,7 @@
var assert = require("assert"); var assert = require("assert");
var readFileSync = require("fs").readFileSync; var readFileSync = require("fs").readFileSync;
var run_code = require("../sandbox").run_code; var run_code = require("../sandbox").run_code;
var UglifyJS = require("../../"); var UglifyJS = require("../..");
function read(path) { function read(path) {
return readFileSync(path, "utf8"); return readFileSync(path, "utf8");
@@ -9,18 +9,30 @@ function read(path) {
describe("minify", function() { describe("minify", function() {
it("Should test basic sanity of minify with default options", function() { it("Should test basic sanity of minify with default options", function() {
var js = 'function foo(bar) { if (bar) return 3; else return 7; var u = not_called(); }'; var js = "function foo(bar) { if (bar) return 3; else return 7; var u = not_called(); }";
var result = UglifyJS.minify(js); var result = UglifyJS.minify(js);
assert.strictEqual(result.code, 'function foo(n){return n?3:7}'); if (result.error) throw result.error;
assert.strictEqual(result.code, "function foo(n){return n?3:7}");
}); });
it("Should not mutate minify `options`", function() {
var options = {
compress: true,
mangle: false,
output: {},
};
var value = JSON.stringify(options);
var result = UglifyJS.minify("print(6 * 7);", options);
if (result.error) throw result.error;
assert.strictEqual(result.code, "print(42);");
assert.strictEqual(JSON.stringify(options), value);
})
it("Should skip inherited keys from `files`", function() { it("Should skip inherited keys from `files`", function() {
var files = Object.create({ skip: this }); var files = Object.create({ skip: this });
files[0] = "alert(1 + 1)"; files[0] = "alert(1 + 1)";
var result = UglifyJS.minify(files); var result = UglifyJS.minify(files);
if (result.error) throw result.error;
assert.strictEqual(result.code, "alert(2);"); assert.strictEqual(result.code, "alert(2);");
}); });
it("Should work with mangle.cache", function() { it("Should work with mangle.cache", function() {
var cache = {}; var cache = {};
var original = ""; var original = "";
@@ -53,7 +65,6 @@ describe("minify", function() {
].join("")); ].join(""));
assert.strictEqual(run_code(compressed, true), run_code(original, true)); assert.strictEqual(run_code(compressed, true), run_code(original, true));
}); });
it("Should work with nameCache", function() { it("Should work with nameCache", function() {
var cache = {}; var cache = {};
var original = ""; var original = "";
@@ -86,7 +97,6 @@ describe("minify", function() {
].join("")); ].join(""));
assert.strictEqual(run_code(compressed, true), run_code(original, true)); assert.strictEqual(run_code(compressed, true), run_code(original, true));
}); });
it("Should avoid cached names when mangling top-level variables", function() { it("Should avoid cached names when mangling top-level variables", function() {
var cache = {}; var cache = {};
var original = ""; var original = "";
@@ -115,7 +125,6 @@ describe("minify", function() {
].join("")); ].join(""));
assert.strictEqual(run_code(compressed, true), run_code(original, true)); assert.strictEqual(run_code(compressed, true), run_code(original, true));
}); });
it("Should avoid cached names when mangling inner-scoped variables", function() { it("Should avoid cached names when mangling inner-scoped variables", function() {
var cache = {}; var cache = {};
var original = ""; var original = "";
@@ -139,7 +148,6 @@ describe("minify", function() {
].join("")); ].join(""));
assert.strictEqual(run_code(compressed, true), run_code(original, true)); assert.strictEqual(run_code(compressed, true), run_code(original, true));
}); });
it("Should not parse invalid use of reserved words", function() { it("Should not parse invalid use of reserved words", function() {
assert.strictEqual(UglifyJS.minify("function enum(){}").error, undefined); assert.strictEqual(UglifyJS.minify("function enum(){}").error, undefined);
assert.strictEqual(UglifyJS.minify("function static(){}").error, undefined); assert.strictEqual(UglifyJS.minify("function static(){}").error, undefined);
@@ -155,7 +163,6 @@ describe("minify", function() {
}}); }});
assert.strictEqual(result.code, 'var foo={"x":1,y:2,"z":3};'); assert.strictEqual(result.code, 'var foo={"x":1,y:2,"z":3};');
}); });
it("Should preserve quote styles when quote_style is 3", function() { it("Should preserve quote styles when quote_style is 3", function() {
var js = 'var foo = {"x": 1, y: 2, \'z\': 3};'; var js = 'var foo = {"x": 1, y: 2, \'z\': 3};';
var result = UglifyJS.minify(js, { var result = UglifyJS.minify(js, {
@@ -165,7 +172,6 @@ describe("minify", function() {
}}); }});
assert.strictEqual(result.code, 'var foo={"x":1,y:2,\'z\':3};'); assert.strictEqual(result.code, 'var foo={"x":1,y:2,\'z\':3};');
}); });
it("Should not preserve quotes in object literals when disabled", function() { it("Should not preserve quotes in object literals when disabled", function() {
var js = 'var foo = {"x": 1, y: 2, \'z\': 3};'; var js = 'var foo = {"x": 1, y: 2, \'z\': 3};';
var result = UglifyJS.minify(js, { var result = UglifyJS.minify(js, {

View File

@@ -246,4 +246,54 @@ describe("test/reduce.js", function() {
"// }", "// }",
].join("\n")); ].join("\n"));
}); });
it("Should reduce test case which differs only in Error.message", function() {
var code = [
"var a=0;",
"try{",
"null[function(){}]",
"}catch(e){",
"for(var i in e.toString())",
"a++,console.log()",
"}",
"console.log(a);",
].join("");
var result = reduce_test(code, {
compress: false,
mangle: false,
output: {
beautify: true,
},
});
if (result.error) throw result.error;
assert.deepEqual(result.warnings, []);
assert.strictEqual(result.code.replace(/function \(/g, "function("), (semver.satisfies(process.version, "<=0.10") ? [
"// Can't reproduce test failure",
"// minify options: {",
'// "compress": false,',
'// "mangle": false,',
'// "output": {',
'// "beautify": true',
"// }",
"// }",
] : [
[
"try{",
"null[function(){}]",
"}catch(e){",
"console.log(e)",
"}",
].join(""),
"// output: TypeError: Cannot read property 'function(){}' of null",
"// ",
"// minify: TypeError: Cannot read property 'function() {}' of null",
"// ",
"// options: {",
'// "compress": false,',
'// "mangle": false,',
'// "output": {',
'// "beautify": true',
"// }",
"// }",
]).join("\n"));
});
}); });

View File

@@ -7,11 +7,13 @@ function read(path) {
} }
function source_map(code) { function source_map(code) {
return JSON.parse(UglifyJS.minify(code, { var result = UglifyJS.minify(code, {
compress: false, compress: false,
mangle: false, mangle: false,
sourceMap: true, sourceMap: true,
}).map); });
if (result.error) throw result.error;
return JSON.parse(result.map);
} }
function get_map() { function get_map() {
@@ -65,6 +67,40 @@ describe("sourcemaps", function() {
].join("\n")); ].join("\n"));
assert.deepEqual(map.names, [ "enabled", "x" ]); assert.deepEqual(map.names, [ "enabled", "x" ]);
}); });
it("Should work with sourceMap.names=true", function() {
var result = UglifyJS.minify([
"var obj = {",
" p: a,",
" q: b",
"};",
].join("\n"), {
compress: false,
mangle: false,
sourceMap: {
names: true,
},
});
if (result.error) throw result.error;
var map = JSON.parse(result.map);
assert.deepEqual(map.names, [ "obj", "p", "a", "q", "b" ]);
});
it("Should work with sourceMap.names=false", function() {
var result = UglifyJS.minify([
"var obj = {",
" p: a,",
" q: b",
"};",
].join("\n"), {
compress: false,
mangle: false,
sourceMap: {
names: false,
},
});
if (result.error) throw result.error;
var map = JSON.parse(result.map);
assert.deepEqual(map.names, []);
});
it("Should mark array/object literals", function() { it("Should mark array/object literals", function() {
var result = UglifyJS.minify([ var result = UglifyJS.minify([
"var obj = {};", "var obj = {};",

View File

@@ -24,11 +24,22 @@ function try_beautify(code) {
} }
} }
function test(original, estree, description) { function validate(ast) {
var transformed = UglifyJS.minify(UglifyJS.AST_Node.from_mozilla_ast(estree), { try {
ast.walk(new UglifyJS.TreeWalker(function(node) {
node.validate();
}));
} catch (e) {
return { error: e };
}
return UglifyJS.minify(ast, {
compress: false, compress: false,
mangle: false mangle: false,
}); });
}
function test(original, estree, description) {
var transformed = validate(UglifyJS.AST_Node.from_mozilla_ast(estree));
if (transformed.error || original !== transformed.code) { if (transformed.error || original !== transformed.code) {
console.log("//============================================================="); console.log("//=============================================================");
console.log("// !!!!!! Failed... round", round); console.log("// !!!!!! Failed... round", round);

View File

@@ -1,5 +1,5 @@
var crypto = require("crypto"); var crypto = require("crypto");
var U = require("./node"); var U = require("..");
var List = U.List; var List = U.List;
var os = require("os"); var os = require("os");
var sandbox = require("./sandbox"); var sandbox = require("./sandbox");
@@ -18,20 +18,30 @@ var sandbox = require("./sandbox");
// will produce different output from the code minified with `minify_options`. // will produce different output from the code minified with `minify_options`.
// Returns a `minify` result object with an additonal boolean property `reduced`. // Returns a `minify` result object with an additonal boolean property `reduced`.
Error.stackTraceLimit = Infinity;
module.exports = function reduce_test(testcase, minify_options, reduce_options) { module.exports = function reduce_test(testcase, minify_options, reduce_options) {
if (testcase instanceof U.AST_Node) testcase = testcase.print_to_string(); if (testcase instanceof U.AST_Node) testcase = testcase.print_to_string();
minify_options = minify_options || {}; minify_options = minify_options || {};
reduce_options = reduce_options || {}; reduce_options = reduce_options || {};
var max_iterations = reduce_options.max_iterations || 1000; var max_iterations = reduce_options.max_iterations || 1000;
var max_timeout = reduce_options.max_timeout || 10000; var max_timeout = reduce_options.max_timeout || 10000;
var warnings = [];
var log = reduce_options.log || function(msg) {
warnings.push(msg);
};
var verbose = reduce_options.verbose; var verbose = reduce_options.verbose;
var minify_options_json = JSON.stringify(minify_options, null, 2); var minify_options_json = JSON.stringify(minify_options, null, 2);
var result_cache = Object.create(null); var result_cache = Object.create(null);
var test_for_diff = compare_run_code;
// the initial timeout to assess the viability of the test case must be large // the initial timeout to assess the viability of the test case must be large
var differs = producesDifferentResultWhenMinified(result_cache, testcase, minify_options, max_timeout); var differs = test_for_diff(testcase, minify_options, result_cache, max_timeout);
if (verbose) { if (verbose) {
console.error("// Node.js " + process.version + " on " + os.platform() + " " + os.arch()); log("// Node.js " + process.version + " on " + os.platform() + " " + os.arch());
}
if (differs.error && [ "DefaultsError", "SyntaxError" ].indexOf(differs.error.name) < 0) {
test_for_diff = test_minify;
differs = test_for_diff(testcase, minify_options, result_cache, max_timeout);
} }
if (!differs) { if (!differs) {
// same stdout result produced when minified // same stdout result produced when minified
@@ -39,16 +49,19 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options)
code: [ code: [
"// Can't reproduce test failure", "// Can't reproduce test failure",
"// minify options: " + to_comment(minify_options_json) "// minify options: " + to_comment(minify_options_json)
].join("\n") ].join("\n"),
warnings: warnings,
}; };
} else if (differs.timed_out) { } else if (differs.timed_out) {
return { return {
code: [ code: [
"// Can't reproduce test failure within " + max_timeout + "ms", "// Can't reproduce test failure within " + max_timeout + "ms",
"// minify options: " + to_comment(minify_options_json) "// minify options: " + to_comment(minify_options_json)
].join("\n") ].join("\n"),
warnings: warnings,
}; };
} else if (differs.error) { } else if (differs.error) {
differs.warnings = warnings;
return differs; return differs;
} else if (is_error(differs.unminified_result) } else if (is_error(differs.unminified_result)
&& is_error(differs.minified_result) && is_error(differs.minified_result)
@@ -57,7 +70,8 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options)
code: [ code: [
"// No differences except in error message", "// No differences except in error message",
"// minify options: " + to_comment(minify_options_json) "// minify options: " + to_comment(minify_options_json)
].join("\n") ].join("\n"),
warnings: warnings,
}; };
} else { } else {
max_timeout = Math.min(100 * differs.elapsed, max_timeout); max_timeout = Math.min(100 * differs.elapsed, max_timeout);
@@ -125,40 +139,13 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options)
} }
} }
else if (node instanceof U.AST_Binary) { else if (node instanceof U.AST_Binary) {
CHANGED = true;
var permute = ((node.start._permute += step) * steps | 0) % 4; var permute = ((node.start._permute += step) * steps | 0) % 4;
var expr = [ var expr = [
node.left, node.left,
node.right, node.right,
][ permute & 1 ]; ][ permute & 1 ];
if (permute < 2) return expr;
// wrap with console.log()
return new U.AST_Call({
expression: new U.AST_Dot({
expression: new U.AST_SymbolRef({
name: "console",
start: {},
}),
property: "log",
start: {},
}),
args: [ expr ],
start: {},
});
}
else if (node instanceof U.AST_Catch || node instanceof U.AST_Finally) {
// drop catch or finally block
node.start._permute++;
CHANGED = true; CHANGED = true;
return null; return permute < 2 ? expr : wrap_with_console_log(expr);
}
else if (node instanceof U.AST_Conditional) {
CHANGED = true;
return [
node.condition,
node.consequent,
node.alternative,
][ ((node.start._permute += step) * steps | 0) % 3 ];
} }
else if (node instanceof U.AST_BlockStatement) { else if (node instanceof U.AST_BlockStatement) {
if (in_list) { if (in_list) {
@@ -193,12 +180,26 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options)
return to_sequence(seq); return to_sequence(seq);
} }
} }
else if (node instanceof U.AST_Catch) {
// drop catch block
node.start._permute++;
CHANGED = true;
return null;
}
else if (node instanceof U.AST_Conditional) {
CHANGED = true;
return [
node.condition,
node.consequent,
node.alternative,
][ ((node.start._permute += step) * steps | 0) % 3 ];
}
else if (node instanceof U.AST_Defun) { else if (node instanceof U.AST_Defun) {
switch (((node.start._permute += step) * steps | 0) % 2) { switch (((node.start._permute += step) * steps | 0) % 2) {
case 0: case 0:
CHANGED = true; CHANGED = true;
return List.skip; return List.skip;
case 1: default:
if (!has_exit(node)) { if (!has_exit(node)) {
// hoist function declaration body // hoist function declaration body
var body = node.body; var body = node.body;
@@ -230,15 +231,11 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options)
return to_statement(expr); return to_statement(expr);
} }
} }
else if (node instanceof U.AST_PropAccess) { else if (node instanceof U.AST_Finally) {
var expr = [ // drop finally block
node.expression, node.start._permute++;
node.property instanceof U.AST_Node && node.property,
][ node.start._permute++ % 2 ];
if (expr) {
CHANGED = true; CHANGED = true;
return expr; return null;
}
} }
else if (node instanceof U.AST_For) { else if (node instanceof U.AST_For) {
var expr = [ var expr = [
@@ -287,6 +284,16 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options)
return expr; return expr;
} }
} }
else if (node instanceof U.AST_PropAccess) {
var expr = [
node.expression,
node.property instanceof U.AST_Node && node.property,
][ node.start._permute++ % 2 ];
if (expr) {
CHANGED = true;
return expr;
}
}
else if (node instanceof U.AST_SimpleStatement) { else if (node instanceof U.AST_SimpleStatement) {
if (node.body instanceof U.AST_Call && node.body.expression instanceof U.AST_Function) { if (node.body instanceof U.AST_Call && node.body.expression instanceof U.AST_Function) {
// hoist simple statement IIFE function expression body // hoist simple statement IIFE function expression body
@@ -414,19 +421,41 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options)
} }
}); });
var diff_error_message;
for (var pass = 1; pass <= 3; ++pass) { for (var pass = 1; pass <= 3; ++pass) {
var testcase_ast = U.parse(testcase); var testcase_ast = U.parse(testcase);
if (diff_error_message === testcase) {
// only difference detected is in error message, so expose that and try again
testcase_ast.transform(new U.TreeTransformer(function(node, descend) {
if (node.TYPE == "Call" && node.expression.print_to_string() == "console.log") {
return to_sequence(node.args);
}
if (node instanceof U.AST_Catch) {
descend(node, this);
node.body.unshift(new U.AST_SimpleStatement({
body: wrap_with_console_log(new U.AST_SymbolRef(node.argname)),
start: {},
}));
return node;
}
}));
var code = testcase_ast.print_to_string();
if (diff = test_for_diff(code, minify_options, result_cache, max_timeout)) {
testcase = code;
differs = diff;
} else {
testcase_ast = U.parse(testcase);
}
}
diff_error_message = null;
testcase_ast.walk(new U.TreeWalker(function(node) { testcase_ast.walk(new U.TreeWalker(function(node) {
// unshare start props to retain visit data between iterations // unshare start props to retain visit data between iterations
node.start = JSON.parse(JSON.stringify(node.start)); node.start = JSON.parse(JSON.stringify(node.start));
node.start._permute = 0; node.start._permute = 0;
})); }));
for (var c = 0; c < max_iterations; ++c) { for (var c = 0; c < max_iterations; ++c) {
if (verbose) { if (verbose && pass == 1 && c % 25 == 0) {
if (pass == 1 && c % 25 == 0) { log("// reduce test pass " + pass + ", iteration " + c + ": " + testcase.length + " bytes");
console.error("// reduce test pass "
+ pass + ", iteration " + c + ": " + testcase.length + " bytes");
}
} }
var CHANGED = false; var CHANGED = false;
var code_ast = testcase_ast.clone(true).transform(tt); var code_ast = testcase_ast.clone(true).transform(tt);
@@ -436,12 +465,12 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options)
} catch (ex) { } catch (ex) {
// AST is not well formed. // AST is not well formed.
// no harm done - just log the error, ignore latest change and continue iterating. // no harm done - just log the error, ignore latest change and continue iterating.
console.error("*** Error generating code from AST."); log("*** Error generating code from AST.");
console.error(ex); log(ex.stack);
console.error("*** Discarding permutation and continuing."); log("*** Discarding permutation and continuing.");
continue; continue;
} }
var diff = producesDifferentResultWhenMinified(result_cache, code, minify_options, max_timeout); var diff = test_for_diff(code, minify_options, result_cache, max_timeout);
if (diff) { if (diff) {
if (diff.timed_out) { if (diff.timed_out) {
// can't trust the validity of `code_ast` and `code` when timed out. // can't trust the validity of `code_ast` and `code` when timed out.
@@ -449,14 +478,15 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options)
} else if (diff.error) { } else if (diff.error) {
// something went wrong during minify() - could be malformed AST or genuine bug. // something went wrong during minify() - could be malformed AST or genuine bug.
// no harm done - just log code & error, ignore latest change and continue iterating. // no harm done - just log code & error, ignore latest change and continue iterating.
console.error("*** Error during minification."); log("*** Error during minification.");
console.error(code); log(code);
console.error(diff.error); log(diff.error.stack);
console.error("*** Discarding permutation and continuing."); log("*** Discarding permutation and continuing.");
} else if (is_error(diff.unminified_result) } else if (is_error(diff.unminified_result)
&& is_error(diff.minified_result) && is_error(diff.minified_result)
&& diff.unminified_result.name == diff.minified_result.name) { && diff.unminified_result.name == diff.minified_result.name) {
// ignore difference in error messages caused by minification // ignore difference in error messages caused by minification
diff_error_message = testcase;
} else { } else {
// latest permutation is valid, so use it as the basis of new changes // latest permutation is valid, so use it as the basis of new changes
testcase_ast = code_ast; testcase_ast = code_ast;
@@ -467,11 +497,14 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options)
} }
if (c == 0) break; if (c == 0) break;
if (verbose) { if (verbose) {
console.error("// reduce test pass " + pass + ": " + testcase.length + " bytes"); log("// reduce test pass " + pass + ": " + testcase.length + " bytes");
} }
} }
testcase = try_beautify(result_cache, testcase, minify_options, differs.unminified_result, max_timeout); testcase = try_beautify(testcase, minify_options, differs.unminified_result, result_cache, max_timeout);
var lines = [ "" ]; var lines = [ "" ];
if (isNaN(max_timeout)) {
lines.push("// minify error: " + to_comment(strip_color_codes(differs.minified_result.stack)));
} else {
var unminified_result = strip_color_codes(differs.unminified_result); var unminified_result = strip_color_codes(differs.unminified_result);
var minified_result = strip_color_codes(differs.minified_result); var minified_result = strip_color_codes(differs.minified_result);
if (trim_trailing_whitespace(unminified_result) == trim_trailing_whitespace(minified_result)) { if (trim_trailing_whitespace(unminified_result) == trim_trailing_whitespace(minified_result)) {
@@ -486,8 +519,10 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options)
"// minify: " + to_comment(minified_result) "// minify: " + to_comment(minified_result)
); );
} }
}
lines.push("// options: " + to_comment(minify_options_json)); lines.push("// options: " + to_comment(minify_options_json));
testcase.code += lines.join("\n"); testcase.code += lines.join("\n");
testcase.warnings = warnings;
return testcase; return testcase;
} }
}; };
@@ -504,7 +539,7 @@ function trim_trailing_whitespace(value) {
return ("" + value).replace(/\s+$/, ""); return ("" + value).replace(/\s+$/, "");
} }
function try_beautify(result_cache, testcase, minify_options, expected, timeout) { function try_beautify(testcase, minify_options, expected, result_cache, timeout) {
var result = U.minify(testcase, { var result = U.minify(testcase, {
compress: false, compress: false,
mangle: false, mangle: false,
@@ -518,10 +553,16 @@ function try_beautify(result_cache, testcase, minify_options, expected, timeout)
code: testcase, code: testcase,
}; };
var toplevel = sandbox.has_toplevel(minify_options); var toplevel = sandbox.has_toplevel(minify_options);
var actual = run_code(result_cache, result.code, toplevel, timeout); if (isNaN(timeout)) {
if (!U.minify(result.code, minify_options).error) return {
code: testcase,
};
} else {
var actual = run_code(result.code, toplevel, result_cache, timeout);
if (!sandbox.same_stdout(expected, actual)) return { if (!sandbox.same_stdout(expected, actual)) return {
code: testcase, code: testcase,
}; };
}
result.code = "// (beautified)\n" + result.code; result.code = "// (beautified)\n" + result.code;
return result; return result;
} }
@@ -592,21 +633,37 @@ function to_statement(node) {
}); });
} }
function run_code(result_cache, code, toplevel, timeout) { function wrap_with_console_log(node) {
// wrap with console.log()
return new U.AST_Call({
expression: new U.AST_Dot({
expression: new U.AST_SymbolRef({
name: "console",
start: {},
}),
property: "log",
start: {},
}),
args: [ node ],
start: {},
});
}
function run_code(code, toplevel, result_cache, timeout) {
var key = crypto.createHash("sha1").update(code).digest("base64"); var key = crypto.createHash("sha1").update(code).digest("base64");
return result_cache[key] || (result_cache[key] = sandbox.run_code(code, toplevel, timeout)); return result_cache[key] || (result_cache[key] = sandbox.run_code(code, toplevel, timeout));
} }
function producesDifferentResultWhenMinified(result_cache, code, minify_options, max_timeout) { function compare_run_code(code, minify_options, result_cache, max_timeout) {
var minified = U.minify(code, minify_options); var minified = U.minify(code, minify_options);
if (minified.error) return minified; if (minified.error) return minified;
var toplevel = sandbox.has_toplevel(minify_options); var toplevel = sandbox.has_toplevel(minify_options);
var elapsed = Date.now(); var elapsed = Date.now();
var unminified_result = run_code(result_cache, code, toplevel, max_timeout); var unminified_result = run_code(code, toplevel, result_cache, max_timeout);
elapsed = Date.now() - elapsed; elapsed = Date.now() - elapsed;
var timeout = Math.min(100 * elapsed, max_timeout); var timeout = Math.min(100 * elapsed, max_timeout);
var minified_result = run_code(result_cache, minified.code, toplevel, timeout); var minified_result = run_code(minified.code, toplevel, result_cache, timeout);
if (sandbox.same_stdout(unminified_result, minified_result)) { if (sandbox.same_stdout(unminified_result, minified_result)) {
return is_timed_out(unminified_result) && is_timed_out(minified_result) && { return is_timed_out(unminified_result) && is_timed_out(minified_result) && {
@@ -619,4 +676,10 @@ function producesDifferentResultWhenMinified(result_cache, code, minify_options,
elapsed: elapsed, elapsed: elapsed,
}; };
} }
Error.stackTraceLimit = Infinity;
function test_minify(code, minify_options) {
var minified = U.minify(code, minify_options);
return minified.error && {
minified_result: minified.error,
};
}

View File

@@ -764,20 +764,22 @@ function _createExpression(recurmax, noComma, stmtDepth, canThrow) {
return createObjectLiteral(recurmax, stmtDepth, canThrow) + "." + getDotKey(); return createObjectLiteral(recurmax, stmtDepth, canThrow) + "." + getDotKey();
case p++: case p++:
var name = getVarName(); var name = getVarName();
return name + " && " + name + "[" + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + "]"; var s = name + "[" + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + "]";
return canThrow && rng(8) == 0 ? s : name + " && " + s;
case p++: case p++:
var name = getVarName(); var name = getVarName();
return name + " && " + name + "." + getDotKey(); var s = name + "." + getDotKey();
return canThrow && rng(8) == 0 ? s : name + " && " + s;
case p++: case p++:
case p++: case p++:
case p++:
case p++:
if (rng(16) == 0) {
var name = getVarName(); var name = getVarName();
var fn = name + "." + getDotKey(); var s = name + "." + getDotKey();
called[name] = true; s = "typeof " + s + ' == "function" && --_calls_ >= 0 && ' + s + "(" + createArgs(recurmax, stmtDepth, canThrow) + ")";
return name + " && " + "typeof " + fn + ' == "function" && --_calls_ >= 0 && ' + fn + "(" + createArgs(recurmax, stmtDepth, canThrow) + ")"; return canThrow && rng(8) == 0 ? s : name + " && " + s;
} case p++:
case p++:
case p++:
case p++:
var name = rng(3) == 0 ? getVarName() : "f" + rng(funcs + 2); var name = rng(3) == 0 ? getVarName() : "f" + rng(funcs + 2);
called[name] = true; called[name] = true;
return "typeof " + name + ' == "function" && --_calls_ >= 0 && ' + name + "(" + createArgs(recurmax, stmtDepth, canThrow) + ")"; return "typeof " + name + ' == "function" && --_calls_ >= 0 && ' + name + "(" + createArgs(recurmax, stmtDepth, canThrow) + ")";
@@ -1018,7 +1020,7 @@ function log_suspects(minify_options, component) {
var defs = default_options[component]; var defs = default_options[component];
var suspects = Object.keys(defs).filter(function(name) { var suspects = Object.keys(defs).filter(function(name) {
var flip = name == "keep_fargs"; var flip = name == "keep_fargs";
if (flip === !(name in options ? options : defs)[name]) { if (flip !== (name in options ? options : defs)[name]) {
var m = JSON.parse(JSON.stringify(minify_options)); var m = JSON.parse(JSON.stringify(minify_options));
var o = JSON.parse(JSON.stringify(options)); var o = JSON.parse(JSON.stringify(options));
o[name] = flip; o[name] = flip;
@@ -1029,7 +1031,7 @@ function log_suspects(minify_options, component) {
errorln(result.error); errorln(result.error);
} else { } else {
var r = sandbox.run_code(result.code, sandbox.has_toplevel(m)); var r = sandbox.run_code(result.code, sandbox.has_toplevel(m));
return sandbox.same_stdout(original_result, r); return !sandbox.same_stdout(uglify_result, r);
} }
} }
}); });
@@ -1043,10 +1045,8 @@ function log_suspects(minify_options, component) {
} }
function log_suspects_global(options) { function log_suspects_global(options) {
var o = {}; var suspects = Object.keys(default_options).filter(function(component) {
UglifyJS.minify("", o); return typeof default_options[component] != "object";
var suspects = Object.keys(o).filter(function(component) {
return typeof o[component] != "object";
}).filter(function(component) { }).filter(function(component) {
var m = JSON.parse(options); var m = JSON.parse(options);
m[component] = false; m[component] = false;
@@ -1056,7 +1056,7 @@ function log_suspects_global(options) {
errorln(result.error); errorln(result.error);
} else { } else {
var r = sandbox.run_code(result.code, sandbox.has_toplevel(m)); var r = sandbox.run_code(result.code, sandbox.has_toplevel(m));
return sandbox.same_stdout(original_result, r); return !sandbox.same_stdout(uglify_result, r);
} }
}); });
if (suspects.length > 0) { if (suspects.length > 0) {
@@ -1113,7 +1113,10 @@ function log(options) {
errorln(JSON.stringify(JSON.parse(options), null, 2)); errorln(JSON.stringify(JSON.parse(options), null, 2));
errorln(); errorln();
if (!ok && typeof uglify_code == "string") { if (!ok && typeof uglify_code == "string") {
Object.keys(default_options).forEach(log_suspects.bind(null, JSON.parse(options))); Object.keys(default_options).filter(function(component) {
var defs = default_options[component];
return defs && typeof defs == "object";
}).forEach(log_suspects.bind(null, JSON.parse(options)));
log_suspects_global(options); log_suspects_global(options);
errorln("!!!!!! Failed... round " + round); errorln("!!!!!! Failed... round " + round);
} }
@@ -1133,11 +1136,50 @@ function fuzzy_match(original, uglified) {
return true; return true;
} }
function skip_infinite_recursion(orig, toplevel) {
var code = orig;
var tries = [];
var offset = 0;
var re = /(?:(?:^|[\s{};])try|}\s*catch\s*\(([^)]+)\)|}\s*finally)\s*(?={)/g;
var match;
while (match = re.exec(code)) {
if (/}\s*finally\s*$/.test(match[0])) {
tries.shift();
continue;
}
if (tries.length && tries[0].catch) tries.shift();
var index = match.index + match[0].length + 1;
if (/(?:^|[\s{};])try\s*$/.test(match[0])) {
tries.unshift({ try: index - offset });
continue;
}
tries[0].catch = index;
var insert = "throw " + match[1] + ".ufuzz_skip || (" + match[1] + ".ufuzz_skip = " + tries[0].try + "), " + match[1] + ";";
var new_code = code.slice(0, index) + insert + code.slice(index);
var result = sandbox.run_code(new_code, toplevel);
if (typeof result != "object" || typeof result.name != "string" || typeof result.message != "string") {
offset += insert.length;
code = new_code;
} else if (result.name == "RangeError" && result.message == "Maximum call stack size exceeded") {
index = result.ufuzz_skip;
return orig.slice(0, index) + 'throw new Error("skipping infinite recursion");' + orig.slice(index);
}
}
}
var fallback_options = [ JSON.stringify({ var fallback_options = [ JSON.stringify({
compress: false, compress: false,
mangle: false mangle: false
}) ]; }) ];
var minify_options = require("./options.json").map(JSON.stringify); var minify_options = require("./options.json").map(JSON.stringify);
var sort_globals = [
"Object.keys(this).sort().forEach(function(name) {",
" var value = this[name];",
" delete this[name];",
" this[name] = value;",
"});",
"",
].join("\n");
var original_code, original_result, errored; var original_code, original_result, errored;
var uglify_code, uglify_result, ok; var uglify_code, uglify_result, ok;
for (var round = 1; round <= num_iterations; round++) { for (var round = 1; round <= num_iterations; round++) {
@@ -1150,24 +1192,36 @@ for (var round = 1; round <= num_iterations; round++) {
(errored ? fallback_options : minify_options).forEach(function(options) { (errored ? fallback_options : minify_options).forEach(function(options) {
var o = JSON.parse(options); var o = JSON.parse(options);
var toplevel = sandbox.has_toplevel(o); var toplevel = sandbox.has_toplevel(o);
o.validate = true;
uglify_code = UglifyJS.minify(original_code, o); uglify_code = UglifyJS.minify(original_code, o);
original_result = orig_result[toplevel ? 1 : 0]; original_result = orig_result[toplevel ? 1 : 0];
if (!uglify_code.error) { if (!uglify_code.error) {
uglify_code = uglify_code.code; uglify_code = uglify_code.code;
uglify_result = sandbox.run_code(uglify_code, toplevel); uglify_result = sandbox.run_code(uglify_code, toplevel);
ok = sandbox.same_stdout(original_result, uglify_result); ok = sandbox.same_stdout(original_result, uglify_result);
if (!ok && typeof uglify_result == "string" && o.compress.unsafe_math) { // ignore declaration order of global variables
if (!ok && !toplevel) {
ok = sandbox.same_stdout(sandbox.run_code(sort_globals + original_code), sandbox.run_code(sort_globals + uglify_code));
}
// ignore numerical imprecision caused by `unsafe_math`
if (!ok && typeof uglify_result == "string" && o.compress && o.compress.unsafe_math) {
ok = fuzzy_match(original_result, uglify_result); ok = fuzzy_match(original_result, uglify_result);
if (!ok) { if (!ok) {
var fuzzy_result = sandbox.run_code(original_code.replace(/( - 0\.1){3}/g, " - 0.3"), toplevel); var fuzzy_result = sandbox.run_code(original_code.replace(/( - 0\.1){3}/g, " - 0.3"), toplevel);
ok = sandbox.same_stdout(fuzzy_result, uglify_result); ok = sandbox.same_stdout(fuzzy_result, uglify_result);
} }
} }
// ignore difference in depth of termination caused by infinite recursion
if (!ok) {
var orig_skipped = skip_infinite_recursion(original_code, toplevel);
var uglify_skipped = skip_infinite_recursion(uglify_code, toplevel);
if (orig_skipped && uglify_skipped) {
ok = sandbox.same_stdout(sandbox.run_code(orig_skipped, toplevel), sandbox.run_code(uglify_skipped, toplevel));
}
}
} else { } else {
uglify_code = uglify_code.error; uglify_code = uglify_code.error;
if (errored) { ok = errored && uglify_code.name == original_result.name;
ok = uglify_code.name == original_result.name;
}
} }
if (verbose || (verbose_interval && !(round % INTERVAL_COUNT)) || !ok) log(options); if (verbose || (verbose_interval && !(round % INTERVAL_COUNT)) || !ok) log(options);
else if (errored) { else if (errored) {

View File

@@ -66,8 +66,8 @@ function infer_options(options) {
} }
exports.default_options = function() { exports.default_options = function() {
var defs = {}; var defs = infer_options({ 0: 0 });
Object.keys(infer_options({ 0: 0 })).forEach(function(component) { Object.keys(defs).forEach(function(component) {
var options = {}; var options = {};
options[component] = { 0: 0 }; options[component] = { 0: 0 };
if (options = infer_options(options)) { if (options = infer_options(options)) {