From 4d7746baf31405427209de0d8c44d9c8263a2563 Mon Sep 17 00:00:00 2001 From: Anthony Van de Gejuchte Date: Fri, 20 May 2016 10:25:35 +0200 Subject: [PATCH 1/5] Throw errors in strict mode for octal strings Adds a directive tracker for the parser/tokenizer to allow parsing depending on directive context. --- lib/parse.js | 58 +++++++++++++++++++++++++++++----- test/mocha/directives.js | 61 ++++++++++++++++++++++++++++++++++++ test/mocha/string-literal.js | 47 +++++++++++++++++++++++++++ 3 files changed, 159 insertions(+), 7 deletions(-) create mode 100644 test/mocha/directives.js diff --git a/lib/parse.js b/lib/parse.js index 467fc60b..4530c2d9 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -223,7 +223,9 @@ function tokenizer($TEXT, filename, html5_comments, shebang) { tokcol : 0, newline_before : false, regex_allowed : false, - comments_before : [] + comments_before : [], + directives : {}, + directive_stack : [] }; function peek() { return S.text.charAt(S.pos); }; @@ -392,8 +394,6 @@ function tokenizer($TEXT, filename, html5_comments, shebang) { for (;;) { var ch = next(true, true); if (ch == "\\") { - // read OctalEscapeSequence (XXX: deprecated if "strict mode") - // https://github.com/mishoo/UglifyJS/issues/178 var octal_len = 0, first = null; ch = read_while(function(ch){ if (ch >= "0" && ch <= "7") { @@ -406,8 +406,13 @@ function tokenizer($TEXT, filename, html5_comments, shebang) { } return false; }); - if (octal_len > 0) ch = String.fromCharCode(parseInt(ch, 8)); - else ch = read_escaped_char(true); + if (octal_len > 0) { + if (ch !== "0" && next_token.has_directive("use strict")) + parse_error("Octal literals are not allowed in strict mode"); + ch = String.fromCharCode(parseInt(ch, 8)); + } else { + ch = read_escaped_char(true); + } } else if ("\r\n\u2028\u2029".indexOf(ch) >= 0) parse_error("Unterminated string constant"); else if (ch == quote) break; @@ -608,6 +613,35 @@ function tokenizer($TEXT, filename, html5_comments, shebang) { return S; }; + next_token.add_directive = function(directive) { + S.directive_stack[S.directive_stack.length - 1].push(directive); + + if (S.directives[directive] === undefined) { + S.directives[directive] = 1; + } else { + S.directives[directive]++; + } + } + + next_token.push_directives_stack = function() { + S.directive_stack.push([]); + } + + next_token.pop_directives_stack = function() { + var directives = S.directive_stack[S.directive_stack.length - 1]; + + for (var i = 0; i < directives.length; i++) { + S.directives[directives[i]]--; + } + + S.directive_stack.pop(); + } + + next_token.has_directive = function(directive) { + return S.directives[directive] !== undefined && + S.directives[directive] > 0; + } + return next_token; }; @@ -781,9 +815,15 @@ function parse($TEXT, options) { handle_regexp(); switch (S.token.type) { case "string": + if (S.in_directives) { + if (is_token(peek(), "punc", ";") || peek().nlb) { + S.input.add_directive(S.token.raw.substr(1, S.token.raw.length - 2)); + } else { + S.in_directives = false; + } + } var dir = S.in_directives, stat = simple_statement(); - // XXXv2: decide how to fix directives - if (dir && stat.body instanceof AST_String && !is("punc", ",")) { + if (dir) { return new AST_Directive({ start : stat.body.start, end : stat.body.end, @@ -1012,9 +1052,11 @@ function parse($TEXT, options) { body: (function(loop, labels){ ++S.in_function; S.in_directives = true; + S.input.push_directives_stack(); S.in_loop = 0; S.labels = []; var a = block_(); + S.input.pop_directives_stack(); --S.in_function; S.in_loop = loop; S.labels = labels; @@ -1514,8 +1556,10 @@ function parse($TEXT, options) { return (function(){ var start = S.token; var body = []; + S.input.push_directives_stack(); while (!is("eof")) body.push(statement()); + S.input.pop_directives_stack(); var end = prev(); var toplevel = options.toplevel; if (toplevel) { diff --git a/test/mocha/directives.js b/test/mocha/directives.js new file mode 100644 index 00000000..4433e429 --- /dev/null +++ b/test/mocha/directives.js @@ -0,0 +1,61 @@ +var assert = require("assert"); +var uglify = require("../../"); + +describe("Directives", function() { + it ("Should allow tokenizer to store directives state", function() { + var tokenizer = uglify.tokenizer("", "foo.js"); + + // Stack level 0 + assert.strictEqual(tokenizer.has_directive("use strict"), false); + assert.strictEqual(tokenizer.has_directive("use asm"), false); + assert.strictEqual(tokenizer.has_directive("use thing"), false); + + // Stack level 2 + tokenizer.push_directives_stack(); + tokenizer.push_directives_stack(); + tokenizer.add_directive("use strict"); + assert.strictEqual(tokenizer.has_directive("use strict"), true); + assert.strictEqual(tokenizer.has_directive("use asm"), false); + assert.strictEqual(tokenizer.has_directive("use thing"), false); + + // Stack level 3 + tokenizer.push_directives_stack(); + tokenizer.add_directive("use strict"); + tokenizer.add_directive("use asm"); + assert.strictEqual(tokenizer.has_directive("use strict"), true); + assert.strictEqual(tokenizer.has_directive("use asm"), true); + assert.strictEqual(tokenizer.has_directive("use thing"), false); + + // Stack level 2 + tokenizer.pop_directives_stack(); + assert.strictEqual(tokenizer.has_directive("use strict"), true); + assert.strictEqual(tokenizer.has_directive("use asm"), false); + assert.strictEqual(tokenizer.has_directive("use thing"), false); + + // Stack level 3 + tokenizer.push_directives_stack(); + tokenizer.add_directive("use thing"); + tokenizer.add_directive("use\\\nasm"); + assert.strictEqual(tokenizer.has_directive("use strict"), true); + assert.strictEqual(tokenizer.has_directive("use asm"), false); // Directives are strict! + assert.strictEqual(tokenizer.has_directive("use thing"), true); + + // Stack level 2 + tokenizer.pop_directives_stack(); + assert.strictEqual(tokenizer.has_directive("use strict"), true); + assert.strictEqual(tokenizer.has_directive("use asm"), false); + assert.strictEqual(tokenizer.has_directive("use thing"), false); + + // Stack level 1 + tokenizer.pop_directives_stack(); + assert.strictEqual(tokenizer.has_directive("use strict"), false); + assert.strictEqual(tokenizer.has_directive("use asm"), false); + assert.strictEqual(tokenizer.has_directive("use thing"), false); + + // Stack level 0 + tokenizer.pop_directives_stack(); + assert.strictEqual(tokenizer.has_directive("use strict"), false); + assert.strictEqual(tokenizer.has_directive("use asm"), false); + assert.strictEqual(tokenizer.has_directive("use thing"), false); + }); +}); \ No newline at end of file diff --git a/test/mocha/string-literal.js b/test/mocha/string-literal.js index 84aaad7e..c54c161c 100644 --- a/test/mocha/string-literal.js +++ b/test/mocha/string-literal.js @@ -31,4 +31,51 @@ describe("String literals", function() { var output = UglifyJS.parse('var a = "a\\\nb";').print_to_string(); assert.equal(output, 'var a="ab";'); }); + + it("Should throw error in strict mode if string contains escaped octalIntegerLiteral", function() { + var inputs = [ + '"use strict";\n"\\76";', + '"use strict";\nvar foo = "\\76";', + '"use strict";\n"\\1";', + '"use strict";\n"\\07";', + '"use strict";\n"\\011"' + ]; + + var test = function(input) { + return function() { + var output = UglifyJS.parse(input); + } + }; + + var error = function(e) { + return e instanceof UglifyJS.JS_Parse_Error && + e.message === "Octal literals are not allowed in strict mode"; + } + + for (var input in inputs) { + assert.throws(test(inputs[input]), error); + } + }); + + it("Should not throw error outside strict mode if string contains escaped octalIntegerLiteral", function() { + var tests = [ + ['"\\76";', '">";'], + ['"\\0"', '"\\x00";'], + ['"\\08"', '"\\x008";'], + ['"\\008"', '"\\x008";'], + ['"\\0008"', '"\\x008";'], + ['"use strict" === "use strict";\n"\\76";', '"use strict"==="use strict";">";'], + // ['"use\\\n strict";\n"\\07";', '"use\\\n strict";\n"\\u0007";'] // TODO No way to store this content literally yet as directive + ]; + + for (var test in tests) { + var output = UglifyJS.parse(tests[test][0]).print_to_string(); + assert.equal(output, tests[test][1]); + } + }); + + it("Should not throw error when digit is 8 or 9", function() { + assert.equal(UglifyJS.parse('"use strict";"\\08"').print_to_string(), '"use strict";"\\x008";'); + assert.equal(UglifyJS.parse('"use strict";"\\09"').print_to_string(), '"use strict";"\\x009";'); + }); }); \ No newline at end of file From ea31da24559b095a18f7615cfd3c992ecaf495a3 Mon Sep 17 00:00:00 2001 From: Anthony Van de Gejuchte Date: Fri, 10 Jun 2016 00:34:20 +0200 Subject: [PATCH 2/5] Don't drop unused if scope uses with statement Fix provided by @kzc --- lib/compress.js | 1 + test/compress/issue-1105.js | 147 ++++++++++++++++++++++++++++++++++++ 2 files changed, 148 insertions(+) create mode 100644 test/compress/issue-1105.js diff --git a/lib/compress.js b/lib/compress.js index 461c3c47..4e04e961 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -1326,6 +1326,7 @@ merge(Compressor.prototype, { if (compressor.option("unused") && !(self instanceof AST_Toplevel) && !self.uses_eval + && !self.uses_with ) { var in_use = []; var in_use_ids = {}; // avoid expensive linear scans of in_use diff --git a/test/compress/issue-1105.js b/test/compress/issue-1105.js new file mode 100644 index 00000000..4205fdf5 --- /dev/null +++ b/test/compress/issue-1105.js @@ -0,0 +1,147 @@ +with_in_global_scope: { + options = { + unused: true + } + input: { + var o = 42; + with(o) { + var foo = 'something' + } + doSomething(o); + } + expect: { + var o=42; + with(o) + var foo = "something"; + doSomething(o); + } +} +with_in_function_scope: { + options = { + unused: true + } + input: { + function foo() { + var o = 42; + with(o) { + var foo = "something" + } + doSomething(o); + } + } + expect: { + function foo() { + var o=42; + with(o) + var foo = "something"; + doSomething(o) + } + } +} +compress_with_with_in_other_scope: { + options = { + unused: true + } + input: { + function foo() { + var o = 42; + with(o) { + var foo = "something" + } + doSomething(o); + } + function bar() { + var unused = 42; + return something(); + } + } + expect: { + function foo() { + var o = 42; + with(o) + var foo = "something"; + doSomething(o) + } + function bar() { + return something() + } + } +} +with_using_existing_variable_outside_scope: { + options = { + unused: true + } + input: { + function f() { + var o = {}; + var unused = {}; // Doesn't get removed because upper scope uses with + function foo() { + with(o) { + var foo = "something" + } + doSomething(o); + } + foo() + } + } + expect: { + function f() { + var o = {}; + var unused = {}; + function foo() { + with(o) + var foo = "something"; + doSomething(o) + } + foo() + } + } +} +check_drop_unused_in_peer_function: { + options = { + unused: true + } + input: { + function outer() { + var o = {}; + var unused = {}; // should be kept + function foo() { // should be kept + function not_in_use() { + var nested_unused = "foo"; // should be dropped + return 24; + } + var unused = {}; // should be kept + with (o) { + var foo = "something"; + } + doSomething(o); + } + function bar() { + var unused = {}; // should be dropped + doSomethingElse(); + } + foo(); + bar(); + } + } + expect: { + function outer() { + var o = {}; + var unused = {}; // should be kept + function foo() { // should be kept + function not_in_use() { + return 24; + } + var unused = {}; // should be kept + with (o) + var foo = "something"; + doSomething(o); + } + function bar() { + doSomethingElse(); + } + foo(); + bar(); + } + } +} \ No newline at end of file From f99b7b630daf85f85393d4508e2676456f2a3c4b Mon Sep 17 00:00:00 2001 From: David Bau Date: Mon, 23 Dec 2013 16:05:04 +0000 Subject: [PATCH 3/5] Escape null characters as \0 unless followed by 0-7. --- lib/output.js | 6 ++++-- test/compress/ascii.js | 10 ++++++---- test/compress/concat-strings.js | 4 ++++ 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/lib/output.js b/lib/output.js index f5ca0261..907818e0 100644 --- a/lib/output.js +++ b/lib/output.js @@ -88,7 +88,8 @@ function OutputStream(options) { function make_string(str, quote) { var dq = 0, sq = 0; - str = str.replace(/[\\\b\f\n\r\v\t\x22\x27\u2028\u2029\0\ufeff]/g, function(s){ + str = str.replace(/[\\\b\f\n\r\v\t\x22\x27\u2028\u2029\0\ufeff]/g, + function(s, i){ switch (s) { case '"': ++dq; return '"'; case "'": ++sq; return "'"; @@ -101,8 +102,9 @@ function OutputStream(options) { case "\x0B": return options.screw_ie8 ? "\\v" : "\\x0B"; case "\u2028": return "\\u2028"; case "\u2029": return "\\u2029"; - case "\0": return "\\x00"; case "\ufeff": return "\\ufeff"; + case "\0": + return /[0-7]/.test(str.charAt(i+1)) ? "\\x00" : "\\0"; } return s; }); diff --git a/test/compress/ascii.js b/test/compress/ascii.js index 5c6b3b8e..7686ac3f 100644 --- a/test/compress/ascii.js +++ b/test/compress/ascii.js @@ -6,12 +6,13 @@ ascii_only_true: { } input: { function f() { - return "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f" + + return "\x000\x001\x007\x008\x00" + + "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f" + "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" + "\x20\x21\x22\x23 ... \x7d\x7e\x7f\x80\x81 ... \xfe\xff\u0fff\uffff"; } } - expect_exact: 'function f(){return"\\x00\\x01\\x02\\x03\\x04\\x05\\x06\\x07\\b\\t\\n\\x0B\\f\\r\\x0e\\x0f"+"\\x10\\x11\\x12\\x13\\x14\\x15\\x16\\x17\\x18\\x19\\x1a\\x1b\\x1c\\x1d\\x1e\\x1f"+\' !"# ... }~\\x7f\\x80\\x81 ... \\xfe\\xff\\u0fff\\uffff\'}' + expect_exact: 'function f(){return"\\x000\\x001\\x007\\08\\0"+"\\0\\x01\\x02\\x03\\x04\\x05\\x06\\x07\\b\\t\\n\\x0B\\f\\r\\x0e\\x0f"+"\\x10\\x11\\x12\\x13\\x14\\x15\\x16\\x17\\x18\\x19\\x1a\\x1b\\x1c\\x1d\\x1e\\x1f"+\' !"# ... }~\\x7f\\x80\\x81 ... \\xfe\\xff\\u0fff\\uffff\'}' } ascii_only_false: { @@ -22,11 +23,12 @@ ascii_only_false: { } input: { function f() { - return "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f" + + return "\x000\x001\x007\x008\x00" + + "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f" + "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" + "\x20\x21\x22\x23 ... \x7d\x7e\x7f\x80\x81 ... \xfe\xff\u0fff\uffff"; } } - expect_exact: 'function f(){return"\\x00\x01\x02\x03\x04\x05\x06\x07\\b\\t\\n\\x0B\\f\\r\x0e\x0f"+"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f"+\' !"# ... }~\x7f\x80\x81 ... \xfe\xff\u0fff\uffff\'}' + expect_exact: 'function f(){return"\\x000\\x001\\x007\\08\\0"+"\\0\x01\x02\x03\x04\x05\x06\x07\\b\\t\\n\\x0B\\f\\r\x0e\x0f"+"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f"+\' !"# ... }~\x7f\x80\x81 ... \xfe\xff\u0fff\uffff\'}' } diff --git a/test/compress/concat-strings.js b/test/compress/concat-strings.js index 79192987..50eef8b8 100644 --- a/test/compress/concat-strings.js +++ b/test/compress/concat-strings.js @@ -11,6 +11,9 @@ concat_1: { var d = 1 + x() + 2 + 3 + "boo"; var e = 1 + x() + 2 + "X" + 3 + "boo"; + + // be careful with concatentation with "\0" with octal-looking strings. + var f = "\0" + 360 + "\0" + 8 + "\0"; } expect: { var a = "foobar" + x() + "moofoo" + y() + "xyz" + q(); @@ -18,5 +21,6 @@ concat_1: { var c = 1 + x() + 2 + "boo"; var d = 1 + x() + 2 + 3 + "boo"; var e = 1 + x() + 2 + "X3boo"; + var f = "\x00360\08\0"; } } From 9c53c7ada739ece69a9330811e5413a7da74a19b Mon Sep 17 00:00:00 2001 From: Richard van Velzen Date: Sun, 12 Jun 2016 14:35:43 +0200 Subject: [PATCH 4/5] Fix octal string strict mode tests --- test/mocha/string-literal.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/test/mocha/string-literal.js b/test/mocha/string-literal.js index c54c161c..b363a07f 100644 --- a/test/mocha/string-literal.js +++ b/test/mocha/string-literal.js @@ -60,10 +60,10 @@ describe("String literals", function() { it("Should not throw error outside strict mode if string contains escaped octalIntegerLiteral", function() { var tests = [ ['"\\76";', '">";'], - ['"\\0"', '"\\x00";'], - ['"\\08"', '"\\x008";'], - ['"\\008"', '"\\x008";'], - ['"\\0008"', '"\\x008";'], + ['"\\0"', '"\\0";'], + ['"\\08"', '"\\08";'], + ['"\\008"', '"\\08";'], + ['"\\0008"', '"\\08";'], ['"use strict" === "use strict";\n"\\76";', '"use strict"==="use strict";">";'], // ['"use\\\n strict";\n"\\07";', '"use\\\n strict";\n"\\u0007";'] // TODO No way to store this content literally yet as directive ]; @@ -75,7 +75,7 @@ describe("String literals", function() { }); it("Should not throw error when digit is 8 or 9", function() { - assert.equal(UglifyJS.parse('"use strict";"\\08"').print_to_string(), '"use strict";"\\x008";'); - assert.equal(UglifyJS.parse('"use strict";"\\09"').print_to_string(), '"use strict";"\\x009";'); + assert.equal(UglifyJS.parse('"use strict";"\\08"').print_to_string(), '"use strict";"\\08";'); + assert.equal(UglifyJS.parse('"use strict";"\\09"').print_to_string(), '"use strict";"\\09";'); }); -}); \ No newline at end of file +}); From 6c8e001feeeb957279814aa58be44d1ece8bdb6e Mon Sep 17 00:00:00 2001 From: Anthony Van de Gejuchte Date: Fri, 10 Jun 2016 15:42:55 +0200 Subject: [PATCH 5/5] Stop dropping args in new expressions --- lib/output.js | 14 +++++--------- test/compress/new.js | 32 ++++++++++++++++++++++++++++++++ test/mocha/new.js | 16 ++++++++++++++-- 3 files changed, 51 insertions(+), 11 deletions(-) diff --git a/lib/output.js b/lib/output.js index 907818e0..7ddee484 100644 --- a/lib/output.js +++ b/lib/output.js @@ -594,7 +594,7 @@ function OutputStream(options) { PARENS(AST_New, function(output){ var p = output.parent(); - if (no_constructor_parens(this, output) + if (!need_constructor_parens(this, output) && (p instanceof AST_PropAccess // (new Date).getTime(), (new Date)["getTime"]() || p instanceof AST_Call && p.expression === this)) // (new foo)(bar) return true; @@ -995,7 +995,7 @@ function OutputStream(options) { /* -----[ other expressions ]----- */ DEFPRINT(AST_Call, function(self, output){ self.expression.print(output); - if (self instanceof AST_New && no_constructor_parens(self, output)) + if (self instanceof AST_New && !need_constructor_parens(self, output)) return; output.with_parens(function(){ self.args.forEach(function(expr, i){ @@ -1285,13 +1285,9 @@ function OutputStream(options) { }; // self should be AST_New. decide if we want to show parens or not. - function no_constructor_parens(self, output) { - return self.args.length == 0 && !output.option("beautify") || - !(self.expression instanceof AST_SymbolRef || - self.expression instanceof AST_Call || - self.expression instanceof AST_Function || - self.expression instanceof AST_Assign - ); + function need_constructor_parens(self, output) { + // Always print parentheses with arguments + return self.args.length > 0; }; function best_of(a) { diff --git a/test/compress/new.js b/test/compress/new.js index d956ae27..78a1026e 100644 --- a/test/compress/new.js +++ b/test/compress/new.js @@ -11,6 +11,30 @@ new_statement: { expect_exact: "new x(1);new x(1)(2);new x(1)(2)(3);new new x(1);new new x(1)(2);new new x(1)(2);(new new x(1))(2);" } +new_statements_2: { + input: { + new x; + new new x; + new new new x; + new true; + new (0); + new (!0); + new (bar = function(foo) {this.foo=foo;})(123); + new (bar = function(foo) {this.foo=foo;})(); + } + expect_exact: "new x;new(new x);new(new(new x));new true;new 0;new(!0);new(bar=function(foo){this.foo=foo})(123);new(bar=function(foo){this.foo=foo});" +} + +new_statements_3: { + input: { + new (function(foo){this.foo=foo;})(1); + new (function(foo){this.foo=foo;})(); + new (function test(foo){this.foo=foo;})(1); + new (function test(foo){this.foo=foo;})(); + } + expect_exact: "new function(foo){this.foo=foo}(1);new function(foo){this.foo=foo};new function test(foo){this.foo=foo}(1);new function test(foo){this.foo=foo};" +} + new_with_rewritten_true_value: { options = { booleans: true } input: { @@ -18,3 +42,11 @@ new_with_rewritten_true_value: { } expect_exact: "new(!0);" } + +new_with_many_parameters: { + input: { + new foo.bar("baz"); + new x(/123/, 456); + } + expect_exact: 'new foo.bar("baz");new x(/123/,456);' +} diff --git a/test/mocha/new.js b/test/mocha/new.js index 7e7aea7d..8c0f24bc 100644 --- a/test/mocha/new.js +++ b/test/mocha/new.js @@ -5,19 +5,31 @@ describe("New", function() { it("Should attach callback parens after some tokens", function() { var tests = [ "new x(1);", + "new x;", + "new new x;", "new (function(foo){this.foo=foo;})(1);", + "new (function(foo){this.foo=foo;})();", + "new (function test(foo){this.foo=foo;})(1);", + "new (function test(foo){this.foo=foo;})();", "new true;", "new (0);", "new (!0);", - "new (bar = function(foo) {this.foo=foo;})(123);" + "new (bar = function(foo) {this.foo=foo;})(123);", + "new (bar = function(foo) {this.foo=foo;})();" ]; var expected = [ "new x(1);", + "new x;", + "new (new x);", "new function(foo) {\n this.foo = foo;\n}(1);", + "new function(foo) {\n this.foo = foo;\n};", + "new function test(foo) {\n this.foo = foo;\n}(1);", + "new function test(foo) {\n this.foo = foo;\n};", "new true;", "new 0;", "new (!0);", - "new (bar = function(foo) {\n this.foo = foo;\n})(123);" + "new (bar = function(foo) {\n this.foo = foo;\n})(123);", + "new (bar = function(foo) {\n this.foo = foo;\n});" ]; for (var i = 0; i < tests.length; i++) { assert.strictEqual(