diff options
author | Alex Lam S.L <alexlamsl@gmail.com> | 2021-02-23 14:55:08 +0000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-02-23 22:55:08 +0800 |
commit | d68d155f93a355a9f6f0451150186b7fad8c58b8 (patch) | |
tree | e3cba7df7cd9d2221ff2b97ea30d8c9a07e7bba1 /test | |
parent | e535f1918915251681df6ffe80a56d56672685ea (diff) | |
download | tracifyjs-d68d155f93a355a9f6f0451150186b7fad8c58b8.tar.gz tracifyjs-d68d155f93a355a9f6f0451150186b7fad8c58b8.zip |
support `class` literals (#4658)
Diffstat (limited to 'test')
-rw-r--r-- | test/compress/classes.js | 574 | ||||
-rw-r--r-- | test/compress/conditionals.js | 36 | ||||
-rw-r--r-- | test/compress/exports.js | 44 | ||||
-rw-r--r-- | test/compress/hoist_props.js | 26 | ||||
-rw-r--r-- | test/compress/imports.js | 21 | ||||
-rw-r--r-- | test/compress/objects.js | 66 | ||||
-rw-r--r-- | test/compress/properties.js | 22 | ||||
-rw-r--r-- | test/exports.js | 1 | ||||
-rw-r--r-- | test/reduce.js | 14 | ||||
-rw-r--r-- | test/sandbox.js | 8 | ||||
-rw-r--r-- | test/ufuzz/index.js | 298 |
11 files changed, 1044 insertions, 66 deletions
diff --git a/test/compress/classes.js b/test/compress/classes.js new file mode 100644 index 00000000..8c5575c0 --- /dev/null +++ b/test/compress/classes.js @@ -0,0 +1,574 @@ +constructor_1: { + input: { + "use strict"; + console.log(new class { + constructor(a) { + this.a = a; + } + }("PASS").a); + } + expect_exact: '"use strict";console.log(new class{constructor(a){this.a=a}}("PASS").a);' + expect_stdout: "PASS" + node_version: ">=4" +} + +constructor_2: { + input: { + "use strict"; + console.log(new class { + "constructor"(a) { + this.a = a; + } + }("PASS").a); + } + expect_exact: '"use strict";console.log(new class{constructor(a){this.a=a}}("PASS").a);' + expect_stdout: "PASS" + node_version: ">=4" +} + +constructor_3: { + input: { + "use strict"; + console.log(new class { + ["constructor"](a) { + this.a = a; + } + }("FAIL").a || "PASS"); + } + expect_exact: '"use strict";console.log(new class{["constructor"](a){this.a=a}}("FAIL").a||"PASS");' + expect_stdout: "PASS" + node_version: ">=4" +} + +constructor_4: { + input: { + "use strict"; + class A { + static constructor(a) { + console.log(a); + } + } + A.constructor("PASS"); + } + expect_exact: '"use strict";class A{static constructor(a){console.log(a)}}A.constructor("PASS");' + expect_stdout: "PASS" + node_version: ">=4" +} + +fields: { + input: { + var o = new class A { + "#p"; + static #p = "PASS"; + async + get + q() { + return A.#p; + } + ; + [6 * 7] = console ? "foo" : "bar" + }; + for (var k in o) + console.log(k, o[k]); + console.log(o.q); + } + expect_exact: 'var o=new class A{"#p";static #p="PASS";async;get q(){return A.#p}[6*7]=console?"foo":"bar"};for(var k in o)console.log(k,o[k]);console.log(o.q);' + expect_stdout: [ + "42 foo", + "#p undefined", + "async undefined", + "PASS", + ] + node_version: ">=12" +} + +methods: { + input: { + "use strict"; + class A { + static f() { + return "foo"; + } + *g() { + yield A.f(); + yield "bar"; + } + } + for (var a of new A().g()) + console.log(a); + } + expect_exact: '"use strict";class A{static f(){return"foo"}*g(){yield A.f();yield"bar"}}for(var a of(new A).g())console.log(a);' + expect_stdout: [ + "foo", + "bar", + ] + node_version: ">=4" +} + +private_methods: { + input: { + new class A { + static *#f() { + yield A.#p * 3; + } + async #g() { + for (var a of A.#f()) + return a * await 2; + } + static get #p() { + return 7; + } + get q() { + return this.#g(); + } + }().q.then(console.log); + } + expect_exact: "(new class A{static*#f(){yield 3*A.#p}async #g(){for(var a of A.#f())return a*await 2}static get #p(){return 7}get q(){return this.#g()}}).q.then(console.log);" + expect_stdout: "42" + node_version: ">=14" +} + +await: { + input: { + var await = "PASS"; + (async function() { + return await new class extends (await function() {}) { [await 42] = await }; + })().then(function(o) { + console.log(o[42]); + }); + } + expect_exact: 'var await="PASS";(async function(){return await new class extends(await function(){}){[await 42]=await}})().then(function(o){console.log(o[42])});' + expect_stdout: "PASS" + node_version: ">=12" +} + +yield: { + input: { + var a = function*() { + yield new class { [yield "foo"] = "bar" }; + }(); + console.log(a.next().value); + console.log(a.next(42).value[42]); + } + expect_exact: 'var a=function*(){yield new class{[yield"foo"]="bar"}}();console.log(a.next().value);console.log(a.next(42).value[42]);' + expect_stdout: [ + "foo", + "bar", + ] + node_version: ">=12" +} + +conditional_parenthesis: { + options = { + conditionals: true, + } + input: { + "use strict"; + if (class {}) + console.log("PASS"); + } + expect_exact: '"use strict";(class{})&&console.log("PASS");' + expect_stdout: "PASS" + node_version: ">=4" +} + +class_super: { + input: { + "use strict"; + class A { + static get p() { + return "Foo"; + } + static get q() { + return super.p || 42; + } + constructor() { + console.log("a.p", super.p, this.p); + console.log("a.q", super.q, this.q); + } + get p() { + return "foo"; + } + get q() { + return super.p || null; + } + } + class B extends A { + static get p() { + return "Bar"; + } + static get q() { + return super.p; + } + constructor() { + super(); + console.log("b.p", super.p, this.p); + console.log("b.q", super.q, this.q); + } + get p() { + return "bar"; + } + get q() { + return super.p; + } + } + console.log("A", A.p, A.q); + console.log("B", B.p, B.q); + new B(); + } + expect_exact: '"use strict";class A{static get p(){return"Foo"}static get q(){return super.p||42}constructor(){console.log("a.p",super.p,this.p);console.log("a.q",super.q,this.q)}get p(){return"foo"}get q(){return super.p||null}}class B extends A{static get p(){return"Bar"}static get q(){return super.p}constructor(){super();console.log("b.p",super.p,this.p);console.log("b.q",super.q,this.q)}get p(){return"bar"}get q(){return super.p}}console.log("A",A.p,A.q);console.log("B",B.p,B.q);new B;' + expect_stdout: [ + "A Foo 42", + "B Bar Foo", + "a.p undefined bar", + "a.q undefined foo", + "b.p foo bar", + "b.q null foo", + ] + node_version: ">=4" +} + +block_scoped: { + options = { + evaluate: true, + dead_code: true, + loops: true, + } + input: { + "use strict"; + while (0) { + class A {} + } + if (console) { + class B {} + } + console.log(typeof A, typeof B); + } + expect: { + "use strict"; + 0; + if (console) { + class B {} + } + console.log(typeof A, typeof B); + } + expect_stdout: "undefined undefined" + node_version: ">=4" +} + +keep_extends: { + options = { + toplevel: true, + unused: true, + } + input: { + "use strict"; + try { + class A extends 42 {} + } catch (e) { + console.log("PASS"); + } + } + expect: { + "use strict"; + try { + (class extends 42 {}); + } catch (e) { + console.log("PASS"); + } + } + expect_stdout: "PASS" + node_version: ">=4" +} + +drop_name: { + options = { + unused: true, + } + input: { + "use strict"; + try { + console.log(class A extends 42 {}) + } catch (e) { + console.log("PASS"); + } + } + expect: { + "use strict"; + try { + console.log(class extends 42 {}) + } catch (e) { + console.log("PASS"); + } + } + expect_stdout: "PASS" + node_version: ">=4" +} + +separate_name: { + options = { + merge_vars: true, + toplevel: true, + } + input: { + "use strict"; + class A { + constructor(v) { + this.p = v; + } + } + var a = new A("PASS"); + console.log(a.p); + } + expect: { + "use strict"; + class A { + constructor(v) { + this.p = v; + } + } + var a = new A("PASS"); + console.log(a.p); + } + expect_stdout: "PASS" + node_version: ">=4" +} + +static_side_effects: { + options = { + inline: true, + toplevel: true, + unused: true, + } + input: { + var a = "FAIL 1"; + class A { + static p = a = "PASS"; + q = a = "FAIL 2"; + } + console.log(a); + } + expect: { + var a = "FAIL 1"; + a = "PASS"; + console.log(a); + } + expect_stdout: "PASS" + node_version: ">=12" +} + +single_use: { + options = { + reduce_vars: true, + toplevel: true, + unused: true, + } + input: { + "use strict"; + class A {} + console.log(typeof new A()); + } + expect: { + "use strict"; + console.log(typeof new class {}()); + } + expect_stdout: "object" + node_version: ">=4" +} + +collapse_non_strict: { + options = { + collapse_vars: true, + toplevel: true, + } + input: { + var a = 42..p++; + new class extends (a || function() { + console.log("PASS"); + }) {} + } + expect: { + var a = 42..p++; + new class extends (a || function() { + console.log("PASS"); + }) {} + } + expect_stdout: "PASS" + node_version: ">=6" +} + +collapse_rhs: { + options = { + collapse_vars: true, + } + input: { + "use strict"; + var a = "FAIL"; + a = "PASS"; + class A { + p = "PASS"; + } + console.log(a); + } + expect: { + "use strict"; + var a = "FAIL"; + a = "PASS"; + class A { + p = "PASS"; + } + console.log(a); + } + expect_stdout: "PASS" + node_version: ">=12" +} + +collapse_rhs_static: { + options = { + collapse_vars: true, + } + input: { + "use strict"; + var a = "FAIL"; + a = "PASS"; + class A { + static p = "PASS"; + } + console.log(a); + } + expect: { + "use strict"; + var a = "FAIL"; + class A { + static p = a = "PASS"; + } + console.log(a); + } + expect_stdout: "PASS" + node_version: ">=12" +} + +property_side_effects: { + options = { + inline: true, + keep_fargs: false, + unused: true, + } + input: { + "use strict"; + (function f(a, b) { + class A { + [a.log("PASS")]() { + b.log("FAIL"); + } + } + })(console, console); + } + expect: { + "use strict"; + (function(a) { + a.log("PASS"); + })(console, console); + } + expect_stdout: "PASS" + node_version: ">=4" +} + +property_side_effects_static: { + options = { + inline: true, + keep_fargs: false, + unused: true, + } + input: { + "use strict"; + (function f(a, b) { + class A { + static [a.log("PASS")]() { + b.log("FAIL"); + } + } + })(console, console); + } + expect: { + "use strict"; + (function(a) { + a.log("PASS"); + })(console, console); + } + expect_stdout: "PASS" + node_version: ">=4" +} + +unused_await: { + options = { + inline: true, + unused: true, + } + input: { + var await = "PASS"; + (async function() { + class A { + static p = console.log(await); + } + })(); + } + expect: { + var await = "PASS"; + (async function() { + (() => console.log(await))(); + })(); + } + expect_stdout: "PASS" + node_version: ">=12" +} + +computed_key_side_effects: { + options = { + evaluate: true, + reduce_vars: true, + side_effects: true, + toplevel: true, + unused: true, + } + input: { + "use strict"; + var a = 0; + class A { + [(a++, 0)]() {} + } + console.log(a); + } + expect: { + "use strict"; + console.log(1); + } + expect_stdout: "1" + node_version: ">=4" +} + +computed_key_generator: { + options = { + unused: true, + } + input: { + "use strict"; + var a = function*() { + class A { + static [console.log(yield)]() {} + } + }(); + a.next("FAIL"); + a.next("PASS"); + } + expect: { + "use strict"; + var a = function*() { + console.log(yield); + }(); + a.next("FAIL"); + a.next("PASS"); + } + expect_stdout: "PASS" + node_version: ">=4" +} diff --git a/test/compress/conditionals.js b/test/compress/conditionals.js index 64088eb2..4187c152 100644 --- a/test/compress/conditionals.js +++ b/test/compress/conditionals.js @@ -1861,3 +1861,39 @@ issue_3808_2: { } expect_stdout: " PASS" } + +object_super: { + options = { + conditionals: true, + } + input: { + Object.setPrototypeOf({ + f(a) { + a ? this.g("FAIL") : super.g("FAIL"); + }, + g(b) { + console.log(b); + }, + }, { + g() { + console.log("PASS"); + }, + }).f(); + } + expect: { + Object.setPrototypeOf({ + f(a) { + a ? this.g("FAIL") : super.g("FAIL"); + }, + g(b) { + console.log(b); + }, + }, { + g() { + console.log("PASS"); + }, + }).f(); + } + expect_stdout: "PASS" + node_version: ">=4" +} diff --git a/test/compress/exports.js b/test/compress/exports.js index 24c1d8c0..d3a4ffce 100644 --- a/test/compress/exports.js +++ b/test/compress/exports.js @@ -17,22 +17,25 @@ var_defs: { defuns: { input: { + export class A {} export function e() {} export function* f(a) {} export async function g(b, c) {} export async function* h({}, ...[]) {} } - expect_exact: "export function e(){}export function*f(a){}export async function g(b,c){}export async function*h({},...[]){}" + expect_exact: "export class A{}export function e(){}export function*f(a){}export async function g(b,c){}export async function*h({},...[]){}" } defaults: { input: { export default 42; + export default async; export default (x, y) => x * x; + export default class {}; export default function*(a, b) {}; export default async function f({ c }, ...[ d ]) {}; } - expect_exact: "export default 42;export default(x,y)=>x*x;export default function*(a,b){}export default async function f({c:c},...[d]){}" + expect_exact: "export default 42;export default async;export default(x,y)=>x*x;export default class{}export default function*(a,b){}export default async function f({c:c},...[d]){}" } defaults_parenthesis_1: { @@ -88,18 +91,22 @@ drop_unused: { input: { export default 42; export default (x, y) => x * x; - export default function*(a, b) {}; - export default async function f({ c }, ...[ d ]) {}; + export default class A extends B { get p() { h() } } + export default function*(a, b) {} + export default async function f({ c }, ...[ d ]) {} export var e; export function g(x, [ y ], ...z) {} + function h() {} } expect: { export default 42; export default (x, y) => x * x; - export default function*(a, b) {}; - export default async function({}) {}; + export default class extends B { get p() { h() } } + export default function*(a, b) {} + export default async function({}) {} export var e; export function g(x, []) {} + function h() {} } } @@ -195,3 +202,28 @@ hoist_exports: { export { f as bbb, o as ccc, c as fff }; } } + +keep_return_values: { + options = { + booleans: true, + evaluate: true, + reduce_vars: true, + toplevel: true, + } + input: { + export default function() { + return []; + } + export default function f() { + return null; + } + } + expect: { + export default function() { + return []; + } + export default function f() { + return null; + } + } +} diff --git a/test/compress/hoist_props.js b/test/compress/hoist_props.js index dbee9b42..597db4bc 100644 --- a/test/compress/hoist_props.js +++ b/test/compress/hoist_props.js @@ -1068,3 +1068,29 @@ issue_4023: { } expect_stdout: "true" } + +object_super: { + options = { + hoist_props: true, + reduce_vars: true, + toplevel: true, + } + input: { + var o = { + f(a) { + return a ? console.log("PASS") : super.log("PASS"); + }, + }; + o.f(42); + } + expect: { + var o = { + f(a) { + return a ? console.log("PASS") : super.log("PASS"); + }, + }; + o.f(42); + } + expect_stdout: "PASS" + node_version: ">=4" +} diff --git a/test/compress/imports.js b/test/compress/imports.js index f8313b06..558dd6a8 100644 --- a/test/compress/imports.js +++ b/test/compress/imports.js @@ -144,3 +144,24 @@ keep_ref: { foo(); } } + +forbid_merge: { + options = { + merge_vars: true, + toplevel: true, + } + input: { + import A from "foo"; + export default class extends A {} + var f = () => () => {}; + f(); + f(); + } + expect: { + import A from "foo"; + export default class extends A {} + var f = () => () => {}; + f(); + f(); + } +} diff --git a/test/compress/objects.js b/test/compress/objects.js index 67acefd6..3c7f2d18 100644 --- a/test/compress/objects.js +++ b/test/compress/objects.js @@ -280,6 +280,72 @@ shorthand_keywords: { node_version: ">=6" } +object_super: { + input: { + var o = { + f() { + return super.p; + }, + p: "FAIL", + }; + Object.setPrototypeOf(o, { p: "PASS" }); + console.log(o.f()); + } + expect_exact: 'var o={f(){return super.p},p:"FAIL"};Object.setPrototypeOf(o,{p:"PASS"});console.log(o.f());' + expect_stdout: "PASS" + node_version: ">=4" +} + +object_super_async: { + input: { + var o = { + async f() { + return super.p; + }, + p: "FAIL", + }; + Object.setPrototypeOf(o, { p: "PASS" }); + o.f().then(console.log); + } + expect_exact: 'var o={async f(){return super.p},p:"FAIL"};Object.setPrototypeOf(o,{p:"PASS"});o.f().then(console.log);' + expect_stdout: "PASS" + node_version: ">=8" +} + +object_super_generator: { + input: { + var o = { + *f() { + yield super.p; + }, + p: "FAIL", + }; + Object.setPrototypeOf(o, { p: "PASS" }); + console.log(o.f().next().value); + } + expect_exact: 'var o={*f(){yield super.p},p:"FAIL"};Object.setPrototypeOf(o,{p:"PASS"});console.log(o.f().next().value);' + expect_stdout: "PASS" + node_version: ">=4" +} + +object_super_async_generator: { + input: { + var o = { + async *f() { + return super.p; + }, + p: "FAIL", + }; + Object.setPrototypeOf(o, { p: "PASS" }); + o.f().next().then(function(v) { + console.log(v.value, v.done); + }); + } + expect_exact: 'var o={async*f(){return super.p},p:"FAIL"};Object.setPrototypeOf(o,{p:"PASS"});o.f().next().then(function(v){console.log(v.value,v.done)});' + expect_stdout: "PASS true" + node_version: ">=10" +} + issue_4269_1: { options = { evaluate: true, diff --git a/test/compress/properties.js b/test/compress/properties.js index 43f08b42..eb47624a 100644 --- a/test/compress/properties.js +++ b/test/compress/properties.js @@ -1378,3 +1378,25 @@ issue_3389: { } expect_stdout: "PASS" } + +object_super: { + options = { + properties: true, + } + input: { + ({ + f(a) { + return a ? console.log("PASS") : super.log("PASS"); + }, + }).f(console); + } + expect: { + ({ + f(a) { + return a ? console.log("PASS") : super.log("PASS"); + }, + }).f(console); + } + expect_stdout: "PASS" + node_version: ">=4" +} diff --git a/test/exports.js b/test/exports.js index fd1237f3..9cebfaaf 100644 --- a/test/exports.js +++ b/test/exports.js @@ -1,5 +1,6 @@ exports["Compressor"] = Compressor; exports["defaults"] = defaults; +exports["is_statement"] = is_statement; exports["JS_Parse_Error"] = JS_Parse_Error; exports["List"] = List; exports["mangle_properties"] = mangle_properties; diff --git a/test/reduce.js b/test/reduce.js index 4d628aa2..25dc14ae 100644 --- a/test/reduce.js +++ b/test/reduce.js @@ -181,7 +181,7 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options) } else if (node instanceof U.AST_Call) { var expr = [ - node.expression, + !(node.expression instanceof U.AST_Super) && node.expression, node.args[0], null, // intentional ][ ((node.start._permute += step) * steps | 0) % 3 ]; @@ -202,7 +202,7 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options) var seq = []; body.forEach(function(node) { var expr = expr instanceof U.AST_Exit ? node.value : node.body; - if (expr instanceof U.AST_Node && !is_statement(expr)) { + if (expr instanceof U.AST_Node && !U.is_statement(expr)) { // collect expressions from each statements' body seq.push(expr); } @@ -358,7 +358,7 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options) } else if (node instanceof U.AST_PropAccess) { var expr = [ - node.expression, + !(node.expression instanceof U.AST_Super) && node.expression, node.property instanceof U.AST_Node && !(parent instanceof U.AST_Destructured) && node.property, ][ node.start._permute++ % 2 ]; if (expr) { @@ -468,7 +468,7 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options) } // replace this node - var newNode = is_statement(node) ? new U.AST_EmptyStatement({ + var newNode = U.is_statement(node) ? new U.AST_EmptyStatement({ start: {}, }) : U.parse(REPLACEMENTS[node.start._permute % REPLACEMENTS.length | 0], { expression: true, @@ -666,10 +666,6 @@ function is_timed_out(result) { return sandbox.is_error(result) && /timed out/.test(result.message); } -function is_statement(node) { - return node instanceof U.AST_Statement && !(node instanceof U.AST_LambdaExpression); -} - function merge_sequence(array, node) { if (node instanceof U.AST_Sequence) { array.push.apply(array, node.expressions); @@ -689,7 +685,7 @@ function to_sequence(expressions) { } function to_statement(node) { - return is_statement(node) ? node : new U.AST_SimpleStatement({ + return U.is_statement(node) ? node : new U.AST_SimpleStatement({ body: node, start: {}, }); diff --git a/test/sandbox.js b/test/sandbox.js index d8d2da0d..1aee7753 100644 --- a/test/sandbox.js +++ b/test/sandbox.js @@ -25,7 +25,13 @@ exports.run_code = semver.satisfies(process.version, "0.8") ? function(code, top } while (prev !== stdout); return stdout; } : semver.satisfies(process.version, "<0.12") ? run_code_vm : function(code, toplevel, timeout) { - if (/\basync([ \t]+[^\s()[\]{},.&|!~=*%/+-]+|[ \t]*\([\s\S]*?\))[ \t]*=>|\b(async[ \t]+function|setInterval|setTimeout)\b/.test(code)) { + if ([ + /\basync[ \t]*\([\s\S]*?\)[ \t]*=>/, + /\b(async[ \t]+function|setInterval|setTimeout)\b/, + /\basync([ \t]+|[ \t]*\*[ \t]*)[^\s()[\]{},.&|!~=*%/+-]+(\s*\(|[ \t]*=>)/, + ].some(function(pattern) { + return pattern.test(code); + })) { return run_code_exec(code, toplevel, timeout); } else { return run_code_vm(code, toplevel, timeout); diff --git a/test/ufuzz/index.js b/test/ufuzz/index.js index 9df41929..0625a267 100644 --- a/test/ufuzz/index.js +++ b/test/ufuzz/index.js @@ -137,6 +137,9 @@ var SUPPORT = function(matrix) { async_generator: "async function* f(){}", bigint: "42n", catch_omit_var: "try {} catch {}", + class: "class C { f() {} }", + class_field: "class C { p = 0; }", + class_private: "class C { #f() {} }", computed_key: "({[0]: 0});", const_block: "var a; { const a = 0; }", default_value: "[ a = 0 ] = [];", @@ -312,6 +315,8 @@ var DEFUN_OK = true; var DONT_STORE = true; var NO_CONST = true; var NO_DUPLICATE = true; +var NO_LAMBDA = true; +var NO_TEMPLATE = true; var VAR_NAMES = [ "a", @@ -352,11 +357,15 @@ var TYPEOF_OUTCOMES = [ var avoid_vars = []; var block_vars = []; +var lambda_vars = []; var unique_vars = []; +var classes = []; var async = false; var generator = false; var loops = 0; var funcs = 0; +var clazz = 0; +var in_class = 0; var called = Object.create(null); var labels = 10000; @@ -372,11 +381,15 @@ function strictMode() { function createTopLevelCode() { VAR_NAMES.length = INITIAL_NAMES_LEN; // prune any previous names still in the list block_vars.length = 0; + lambda_vars.length = 0; unique_vars.length = 0; + classes.length = 0; async = false; generator = false; loops = 0; funcs = 0; + clazz = 0; + in_class = 0; called = Object.create(null); return [ strictMode(), @@ -411,7 +424,7 @@ function createParams(was_async, was_generator, noDuplicate) { var params = []; for (var n = rng(4); --n >= 0;) { var name = createVarName(MANDATORY); - if (noDuplicate) unique_vars.push(name); + if (noDuplicate || in_class) unique_vars.push(name); params.push(name); } unique_vars.length = len; @@ -428,7 +441,7 @@ function createArgs(recurmax, stmtDepth, canThrow, noTemplate) { case 0: case 1: var name = getVarName(); - if (canThrow && rng(8) === 0) { + if (canThrow && rng(20) == 0) { args.push("..." + name); } else { args.push('...("" + ' + name + ")"); @@ -457,9 +470,9 @@ function createAssignmentPairs(recurmax, stmtDepth, canThrow, nameLenBefore, was var side_effects = []; values.forEach(function(value, index) { value = fn(value, index); - if (/]:|=/.test(value) ? canThrow && rng(10) == 0 : rng(5)) { + if (/]:|=/.test(value) ? canThrow && rng(20) == 0 : rng(5)) { declare_only.splice(rng(declare_only.length + 1), 0, value); - } else if (canThrow && rng(5) == 0) { + } else if (canThrow && rng(20) == 0) { side_effects.splice(rng(side_effects.length + 1), 0, value); } else { side_effects.push(value); @@ -654,6 +667,7 @@ function filterDirective(s) { function createBlockVariables(recurmax, stmtDepth, canThrow, fn) { var block_len = block_vars.length; + var class_len = classes.length; var nameLenBefore = VAR_NAMES.length; var consts = []; var lets = []; @@ -671,13 +685,22 @@ function createBlockVariables(recurmax, stmtDepth, canThrow, fn) { fn(function() { consts.forEach(addAvoidVar); lets.forEach(addAvoidVar); + var s = []; + if (SUPPORT.class) while (rng(100) == 0) { + var name = "C" + clazz++; + classes.push(name); + s.push(createClassLiteral(recurmax,stmtDepth, canThrow, name)); + } if (rng(2)) { - return createDefinitions("const", consts) + "\n" + createDefinitions("let", lets) + "\n"; + s.push(createDefinitions("const", consts), createDefinitions("let", lets)); } else { - return createDefinitions("let", lets) + "\n" + createDefinitions("const", consts) + "\n"; + s.push(createDefinitions("let", lets), createDefinitions("const", consts)); } + s.push(""); + return s.join("\n"); }); VAR_NAMES.length = nameLenBefore; + classes.length = class_len; block_vars.length = block_len; function createDefinitions(type, names) { @@ -730,6 +753,7 @@ function mayCreateBlockVariables(recurmax, stmtDepth, canThrow, fn) { } function makeFunction(name) { + lambda_vars.push(name); if (generator) { name = "function* " + name; } else { @@ -757,6 +781,7 @@ function createFunction(recurmax, allowDefun, canThrow, stmtDepth) { var s = []; var name, args; var nameLenBefore = VAR_NAMES.length; + var lambda_len = lambda_vars.length; var save_async = async; async = SUPPORT.async && rng(50) == 0; var save_generator = generator; @@ -801,6 +826,7 @@ function createFunction(recurmax, allowDefun, canThrow, stmtDepth) { var call_next = invokeGenerator(save_generator); generator = save_generator; async = save_async; + lambda_vars.length = lambda_len; VAR_NAMES.length = nameLenBefore; if (!allowDefun) { @@ -920,7 +946,7 @@ function createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn } else { init = "const "; } - if (!SUPPORT.destructuring || of && !(canThrow && rng(10) == 0) || rng(10)) { + if (!SUPPORT.destructuring || of && !(canThrow && rng(20) == 0) || rng(10)) { init += key; } else if (rng(5)) { init += "[ " + key + " ]"; @@ -932,7 +958,7 @@ function createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn var await = SUPPORT.for_await_of && async && rng(20) == 0; if (SUPPORT.generator && rng(20) == 0) { var gen = getVarName(); - if (canThrow && rng(10) == 0) { + if (canThrow && rng(20) == 0) { s += gen + "; "; } else { s += gen + " && typeof " + gen + "[Symbol."; @@ -941,7 +967,7 @@ function createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn } } else if (rng(5)) { s += createArrayLiteral(recurmax, stmtDepth, canThrow) + "; "; - } else if (canThrow && rng(10) == 0) { + } else if (canThrow && rng(20) == 0) { s += createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + "; "; } else { s += '"" + (' + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + "); "; @@ -1150,7 +1176,7 @@ function _createExpression(recurmax, noComma, stmtDepth, canThrow) { return [ "[ ", new Array(rng(3)).join(), - getVarName(NO_CONST), + getVarName(NO_CONST, NO_LAMBDA), new Array(rng(3)).join(), " ] = ", createArrayLiteral(recurmax, stmtDepth, canThrow), @@ -1159,13 +1185,13 @@ function _createExpression(recurmax, noComma, stmtDepth, canThrow) { return [ "{ ", rng(2) ? "" : createObjectKey(recurmax, stmtDepth, canThrow) + ": ", - getVarName(NO_CONST), + getVarName(NO_CONST, NO_LAMBDA), " } = ", createExpression(recurmax, COMMA_OK, stmtDepth, canThrow), " || {}", ].join(""); default: - return getVarName(NO_CONST) + createAssignment() + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow); + return getVarName(NO_CONST, NO_LAMBDA) + createAssignment() + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow); } case p++: return createExpression(recurmax, COMMA_OK, stmtDepth, canThrow); @@ -1174,6 +1200,7 @@ function _createExpression(recurmax, noComma, stmtDepth, canThrow) { case p++: case p++: var nameLenBefore = VAR_NAMES.length; + var lambda_len = lambda_vars.length; var save_async = async; async = SUPPORT.async && rng(50) == 0; var save_generator = generator; @@ -1185,9 +1212,9 @@ function _createExpression(recurmax, noComma, stmtDepth, canThrow) { generator = false; } } - unique_vars.push("c"); + unique_vars.push("a", "b", "c"); var name = createVarName(MAYBE); // note: this name is only accessible from _within_ the function. and immutable at that. - unique_vars.pop(); + unique_vars.length -= 3; var s = []; switch (rng(5)) { case 0: @@ -1218,8 +1245,10 @@ function _createExpression(recurmax, noComma, stmtDepth, canThrow) { s.push("(" + params); switch (rng(10)) { case 0: - s.push('(typeof arguments != "undefined" && arguments && arguments[' + rng(3) + "])"); - break; + if (!in_class) { + s.push('(typeof arguments != "undefined" && arguments && arguments[' + rng(3) + "])"); + break; + } case 1: s.push("(this && this." + getDotKey() + ")"); break; @@ -1232,6 +1261,7 @@ function _createExpression(recurmax, noComma, stmtDepth, canThrow) { }); generator = save_generator; async = save_async; + lambda_vars.length = lambda_len; VAR_NAMES.length = nameLenBefore; if (!args && rng(2)) args = createArgs(recurmax, stmtDepth, canThrow); if (args) suffix += args; @@ -1275,24 +1305,25 @@ function _createExpression(recurmax, noComma, stmtDepth, canThrow) { var instantiate = rng(4) ? "new " : ""; createBlockVariables(recurmax, stmtDepth, canThrow, function(defns) { s.push( - instantiate + "function " + name + "(" + createParams(save_async, save_generator) + "){", + instantiate + makeFunction(name) + "(" + createParams(save_async, save_generator) + "){", strictMode(), defns() ); if (instantiate) for (var i = rng(4); --i >= 0;) { - if (rng(2)) s.push("this." + getDotKey(true) + createAssignment() + _createBinaryExpr(recurmax, noComma, stmtDepth, canThrow) + ";"); - else s.push("this[" + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + "]" + createAssignment() + _createBinaryExpr(recurmax, noComma, stmtDepth, canThrow) + ";"); + s.push((in_class ? "if (this) " : "") + createThisAssignment(recurmax, stmtDepth, canThrow)); } s.push(_createStatements(rng(5) + 1, recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth)); }); generator = save_generator; async = save_async; + lambda_vars.length = lambda_len; VAR_NAMES.length = nameLenBefore; s.push(rng(2) ? "}" : "}" + createArgs(recurmax, stmtDepth, canThrow, instantiate)); break; } generator = save_generator; async = save_async; + lambda_vars.length = lambda_len; VAR_NAMES.length = nameLenBefore; return filterDirective(s).join("\n"); case p++: @@ -1358,18 +1389,40 @@ function _createExpression(recurmax, noComma, stmtDepth, canThrow) { case p++: var name = getVarName(); var s = name + "[" + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + "]"; - return canThrow && rng(8) == 0 ? s : name + " && " + s; + return canThrow && rng(20) == 0 ? s : name + " && " + s; case p++: var name = getVarName(); var s = name + "." + getDotKey(); - return canThrow && rng(8) == 0 ? s : name + " && " + s; + return canThrow && rng(20) == 0 ? s : name + " && " + s; case p++: case p++: var name = getVarName(); var s = name + "." + getDotKey(); s = "typeof " + s + ' == "function" && --_calls_ >= 0 && ' + s + createArgs(recurmax, stmtDepth, canThrow); - return canThrow && rng(8) == 0 ? s : name + " && " + s; + return canThrow && rng(20) == 0 ? s : name + " && " + s; case p++: + if (SUPPORT.class && classes.length) switch (rng(20)) { + case 0: + return "--_calls_ >= 0 && new " + classes[rng(classes.length)] + createArgs(recurmax, stmtDepth, canThrow, NO_TEMPLATE); + case 1: + var s = "--_calls_ >= 0 && new "; + var nameLenBefore = VAR_NAMES.length; + var class_len = classes.length; + var name; + if (canThrow && rng(20) == 0) { + in_class++; + name = createVarName(MAYBE); + in_class--; + } else if (rng(2)) { + name = "C" + clazz++; + classes.push(name); + } + s += createClassLiteral(recurmax, stmtDepth, canThrow, name); + classes.length = class_len; + VAR_NAMES.length = nameLenBefore; + s += createArgs(recurmax, stmtDepth, canThrow, NO_TEMPLATE); + return s; + } case p++: case p++: case p++: @@ -1391,7 +1444,7 @@ function createArrayLiteral(recurmax, stmtDepth, canThrow) { case 0: case 1: var name = getVarName(); - if (canThrow && rng(8) === 0) { + if (canThrow && rng(20) == 0) { arr.push("..." + name); } else { arr.push('...("" + ' + name + ")"); @@ -1475,14 +1528,33 @@ function createObjectKey(recurmax, stmtDepth, canThrow) { return KEYS[rng(KEYS.length)]; } -function createObjectFunction(recurmax, stmtDepth, canThrow) { +function createSuperAssignment(recurmax, stmtDepth, canThrow) { + var s = rng(2) ? "super." + getDotKey() : "super[" + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + "]"; + return getVarName(NO_CONST, NO_LAMBDA) + createAssignment() + s + ";"; +} + +function createThisAssignment(recurmax, stmtDepth, canThrow) { + var s = rng(2) ? "this." + getDotKey(true) : "this[" + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + "]"; + return s + createAssignment() + _createBinaryExpr(recurmax, COMMA_OK, stmtDepth, canThrow) + ";"; +} + +function createObjectFunction(recurmax, stmtDepth, canThrow, internal, isClazz) { var nameLenBefore = VAR_NAMES.length; var save_async = async; var save_generator = generator; var s; - var name = createObjectKey(recurmax, stmtDepth, canThrow); + var name; + if (internal) { + name = internal; + } else if (isClazz) { + var clazzName = classes.pop(); + name = createObjectKey(recurmax, stmtDepth, canThrow); + classes.push(clazzName); + } else { + name = createObjectKey(recurmax, stmtDepth, canThrow); + } var fn; - switch (rng(SUPPORT.computed_key ? 3 : 2)) { + switch (internal ? 2 : rng(SUPPORT.computed_key ? 3 : 2)) { case 0: async = false; generator = false; @@ -1493,7 +1565,7 @@ function createObjectFunction(recurmax, stmtDepth, canThrow) { defns(), _createStatements(2, recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth), createStatement(recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth, STMT_RETURN_ETC), - "},", + "}", ]; }; break; @@ -1511,18 +1583,24 @@ function createObjectFunction(recurmax, stmtDepth, canThrow) { defns(), _createStatements(2, recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth), "this." + prop + createAssignment() + _createBinaryExpr(recurmax, COMMA_OK, stmtDepth, canThrow) + ";", - "},", + "}", ]; }; break; default: - async = SUPPORT.async && rng(50) == 0; - generator = SUPPORT.generator && rng(50) == 0; - if (async && generator && !SUPPORT.async_generator) { - if (rng(2)) { - async = false; - } else { - generator = false; + if (/^(constructor|super)$/.test(internal)) { + async = false; + generator = false; + name = "constructor"; + } else { + async = SUPPORT.async && rng(50) == 0; + generator = SUPPORT.generator && rng(50) == 0; + if (async && generator && !SUPPORT.async_generator) { + if (rng(2)) { + async = false; + } else { + generator = false; + } } } fn = function(defns) { @@ -1532,9 +1610,13 @@ function createObjectFunction(recurmax, stmtDepth, canThrow) { name + "(" + createParams(save_async, save_generator, NO_DUPLICATE) + "){", strictMode(), defns(), - _createStatements(3, recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth), - "},", - ] + ]; + s.push(_createStatements(2, recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CANNOT_RETURN, stmtDepth)); + if (internal == "super") s.push("super" + createArgs(recurmax, stmtDepth, canThrow, NO_TEMPLATE) + ";"); + if (/^(constructor|super)$/.test(internal) || rng(10) == 0) for (var i = rng(4); --i >= 0;) { + s.push(rng(2) ? createSuperAssignment(recurmax, stmtDepth, canThrow) : createThisAssignment(recurmax, stmtDepth, canThrow)); + } + s.push(_createStatements(2, recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth), "}"); }; break; } @@ -1561,7 +1643,7 @@ function createObjectLiteral(recurmax, stmtDepth, canThrow) { obj.push(getVarName() + ","); break; case 4: - obj.push(createObjectFunction(recurmax, stmtDepth, canThrow)); + obj.push(createObjectFunction(recurmax, stmtDepth, canThrow) + ","); break; default: obj.push(createObjectKey(recurmax, stmtDepth, canThrow) + ": " + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ","); @@ -1571,6 +1653,63 @@ function createObjectLiteral(recurmax, stmtDepth, canThrow) { return obj.join("\n"); } +function createClassLiteral(recurmax, stmtDepth, canThrow, name) { + recurmax--; + var save_async = async; + var save_generator = generator; + in_class++; + var s = "class", constructor = "constructor"; + var isClazz = /^C/.test(name); + if (name) s += " " + name; + if (rng(10) == 0) { + constructor = "super"; + s += " extends "; + var p = getVarName(); + if (canThrow && rng(20) == 0) { + s += p; + } else { + s += "(" + p + " && " + p + ".constructor === Function ? " + p + " : function() {})"; + } + } + s += " {\n"; + var declared = []; + for (var i = rng(6); --i >= 0;) { + var fixed = false; + if (rng(5) == 0) { + fixed = true; + s += "static "; + } + var internal = null; + if (SUPPORT.class_private && rng(10) == 0) { + do { + internal = "#" + getDotKey(); + } while (declared.indexOf(internal) >= 0); + declared.push(internal); + } + if (SUPPORT.class_field && rng(2)) { + s += internal || createObjectKey(recurmax, stmtDepth, canThrow); + if (rng(5)) { + async = false; + generator = false; + s += " = " + createExpression(recurmax, NO_COMMA, stmtDepth, fixed ? canThrow : CANNOT_THROW); + generator = save_generator; + async = save_async; + } + s += ";\n"; + } else { + if (!fixed && !internal && constructor && rng(10) == 0) { + internal = constructor; + constructor = null; + } + s += createObjectFunction(recurmax, stmtDepth, canThrow, internal, isClazz) + "\n"; + } + } + in_class--; + generator = save_generator; + async = save_async; + return s + "}"; +} + function createNestedBinaryExpr(recurmax, noComma, stmtDepth, canThrow) { recurmax = 3; // note that this generates 2^recurmax expression parts... make sure to cap it return _createSimpleBinaryExpr(recurmax, noComma, stmtDepth, canThrow); @@ -1589,7 +1728,7 @@ function _createSimpleBinaryExpr(recurmax, noComma, stmtDepth, canThrow) { case 1: return "(" + createUnarySafePrefix() + "(" + _createSimpleBinaryExpr(recurmax, noComma, stmtDepth, canThrow) + "))"; case 2: - assignee = getVarName(NO_CONST); + assignee = getVarName(NO_CONST, NO_LAMBDA); return "(" + assignee + createAssignment() + _createBinaryExpr(recurmax, noComma, stmtDepth, canThrow) + ")"; case 3: assignee = getVarName(); @@ -1627,7 +1766,8 @@ function _createSimpleBinaryExpr(recurmax, noComma, stmtDepth, canThrow) { ].join(""); break; } - return canThrow && rng(10) == 0 ? expr : "(" + assignee + " && " + expr + ")"; + if (in_class) return "(Object.isExtensible(" + assignee + ") && " + expr + ")"; + return canThrow && rng(20) == 0 ? expr : "(" + assignee + " && " + expr + ")"; case 4: assignee = getVarName(); switch (SUPPORT.destructuring ? rng(20) : 2) { @@ -1664,7 +1804,8 @@ function _createSimpleBinaryExpr(recurmax, noComma, stmtDepth, canThrow) { ].join(""); break; } - return canThrow && rng(10) == 0 ? expr : "(" + assignee + " && " + expr + ")"; + if (in_class) return "(Object.isExtensible(" + assignee + ") && " + expr + ")"; + return canThrow && rng(20) == 0 ? expr : "(" + assignee + " && " + expr + ")"; default: return _createBinaryExpr(recurmax, noComma, stmtDepth, canThrow); } @@ -1715,11 +1856,19 @@ function createAssignment() { } function createUnarySafePrefix() { - return UNARY_SAFE[rng(UNARY_SAFE.length)]; + var prefix; + do { + prefix = UNARY_SAFE[rng(UNARY_SAFE.length)]; + } while (prefix == "delete " && in_class); + return prefix; } function createUnaryPrefix() { - return UNARY_PREFIX[rng(UNARY_PREFIX.length)]; + var prefix; + do { + prefix = UNARY_PREFIX[rng(UNARY_PREFIX.length)]; + } while (prefix == "delete " && in_class); + return prefix; } function createUnaryPostfix() { @@ -1735,7 +1884,18 @@ function removeAvoidVar(name) { if (index >= 0) avoid_vars.splice(index, 1); } -function getVarName(noConst) { +function isBannedKeyword(name) { + switch (name) { + case "arguments": + return in_class; + case "await": + return async; + case "yield": + return generator || in_class; + } +} + +function getVarName(noConst, noLambda) { // try to get a generated name reachable from current scope. default to just `a` var name, tries = 10; do { @@ -1743,9 +1903,14 @@ function getVarName(noConst) { name = VAR_NAMES[INITIAL_NAMES_LEN + rng(VAR_NAMES.length - INITIAL_NAMES_LEN)]; } while (!name || avoid_vars.indexOf(name) >= 0 - || noConst && block_vars.indexOf(name) >= 0 - || async && name == "await" - || generator && name == "yield"); + || noConst && (block_vars.indexOf(name) >= 0 + || in_class && [ + "Infinity", + "NaN", + "undefined", + ].indexOf(name) >= 0) + || noLambda && lambda_vars.indexOf(name) >= 0 + || isBannedKeyword(name)); return name; } @@ -1759,8 +1924,7 @@ function createVarName(maybe, dontStore) { if (suffix) name += "_" + suffix; } while (unique_vars.indexOf(name) >= 0 || block_vars.indexOf(name) >= 0 - || async && name == "await" - || generator && name == "yield"); + || isBannedKeyword(name)); if (!dontStore) VAR_NAMES.push(name); return name; } @@ -2006,6 +2170,16 @@ function is_error_destructuring(ex) { return ex.name == "TypeError" && /^Cannot destructure /.test(ex.message); } +function is_error_class_constructor(ex) { + return ex.name == "TypeError" && /\bconstructors?\b/.test(ex.message) && /\bnew\b/.test(ex.message); +} + +function is_error_getter_only_property(ex) { + return ex.name == "TypeError" && [ "getter", "only", "property" ].every(function(keyword) { + return ex.message.indexOf(keyword) >= 0; + }); +} + function patch_try_catch(orig, toplevel) { var stack = [ { code: orig, @@ -2067,6 +2241,12 @@ function patch_try_catch(orig, toplevel) { } else if (is_error_destructuring(result)) { index = result.ufuzz_catch; return orig.slice(0, index) + result.ufuzz_var + ' = new Error("cannot destructure");' + orig.slice(index); + } else if (is_error_class_constructor(result)) { + index = result.ufuzz_catch; + return orig.slice(0, index) + result.ufuzz_var + ' = new Error("missing new for class");' + orig.slice(index); + } else if (is_error_getter_only_property(result)) { + index = result.ufuzz_catch; + return orig.slice(0, index) + result.ufuzz_var + ' = new Error("setting getter-only property");' + orig.slice(index); } } stack.filled = true; @@ -2168,6 +2348,16 @@ for (var round = 1; round <= num_iterations; round++) { } // ignore difference in error message caused by Temporal Dead Zone if (!ok && errored && uglify_result.name == "ReferenceError" && original_result.name == "ReferenceError") ok = true; + // ignore difference due to implicit strict-mode in `class` + if (!ok && /\bclass\b/.test(original_code)) { + var original_strict = sandbox.run_code('"use strict";' + original_code, toplevel); + var uglify_strict = sandbox.run_code('"use strict";' + uglify_code, toplevel); + if (typeof original_strict != "string" && /strict/.test(original_strict.message)) { + ok = typeof uglify_strict != "string" && /strict/.test(uglify_strict.message); + } else { + ok = sandbox.same_stdout(original_strict, uglify_strict); + } + } // ignore difference in error message caused by `in` if (!ok && errored && is_error_in(uglify_result) && is_error_in(original_result)) ok = true; // ignore difference in error message caused by spread syntax @@ -2180,6 +2370,14 @@ for (var round = 1; round <= num_iterations; round++) { if (!ok && errored && is_error_destructuring(uglify_result) && is_error_destructuring(original_result)) { ok = true; } + // ignore difference in error message caused by call on class + if (!ok && errored && is_error_class_constructor(uglify_result) && is_error_class_constructor(original_result)) { + ok = true; + } + // ignore difference in error message caused by setting getter-only property + if (!ok && errored && is_error_getter_only_property(uglify_result) && is_error_getter_only_property(original_result)) { + ok = true; + } // ignore errors above when caught by try-catch if (!ok) { var orig_skipped = patch_try_catch(original_code, toplevel); |