diff --git a/lib/ast.js b/lib/ast.js index d117f320..2042332d 100644 --- a/lib/ast.js +++ b/lib/ast.js @@ -1292,12 +1292,15 @@ TreeWalker.prototype = { this.directives = Object.create(this.directives); } else if (node instanceof AST_Directive) { this.directives[node.value] = this.directives[node.value] ? "up" : true; + } else if (node instanceof AST_Class) { + this.directives = Object.create(this.directives); + this.directives["use strict"] = this.directives["use strict"] ? "up" : true; } this.stack.push(node); }, pop: function(node) { this.stack.pop(); - if (node instanceof AST_Lambda) { + if (node instanceof AST_Lambda || node instanceof AST_Class) { this.directives = Object.getPrototypeOf(this.directives); } }, diff --git a/lib/parse.js b/lib/parse.js index 5093812f..7c9b1cd5 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -2039,6 +2039,9 @@ function parse($TEXT, options) { function class_(KindOfClass) { var start, method, class_name, extends_, a = []; + S.input.push_directives_stack(); // Push directive stack, but not scope stack + S.input.add_directive("use strict"); + if (S.token.type == "name" && S.token.value != "extends") { class_name = as_symbol(KindOfClass === AST_DefClass ? AST_SymbolDefClass : AST_SymbolClass); } @@ -2063,6 +2066,8 @@ function parse($TEXT, options) { if (is("punc", ";")) { next(); } } + S.input.pop_directives_stack(); + next(); return new KindOfClass({ diff --git a/test/compress/directives.js b/test/compress/directives.js new file mode 100644 index 00000000..51587b95 --- /dev/null +++ b/test/compress/directives.js @@ -0,0 +1,10 @@ +class_directives_compression: { + input: { + class foo { + foo() { + "use strict"; + } + } + } + expect_exact: "class foo{foo(){}}" +} diff --git a/test/compress/yield.js b/test/compress/yield.js index c29ce926..4f28b647 100644 --- a/test/compress/yield.js +++ b/test/compress/yield.js @@ -120,7 +120,6 @@ yield_as_identifier_outside_strict_mode: { function foo(...yield){} try { new Error("") } catch (yield) {} var yield = "foo"; - class yield {} } expect: { import yield from "bar"; @@ -137,7 +136,6 @@ yield_as_identifier_outside_strict_mode: { function foo(...yield){} try { new Error("") } catch (yield) {} var yield = "foo"; - class yield {} } } diff --git a/test/mocha/directives.js b/test/mocha/directives.js index 82594758..f53ebc1a 100644 --- a/test/mocha/directives.js +++ b/test/mocha/directives.js @@ -62,10 +62,10 @@ describe("Directives", function() { it("Should know which strings are directive and which ones are not", function() { var test_directive = function(tokenizer, test) { test.directives.map(function(directive) { - assert.strictEqual(tokenizer.has_directive(directive), true, directive + " in " + test.input); + assert.strictEqual(tokenizer.has_directive(directive), true, "Didn't found directive `" + directive + "` at the end of `" + test.input + '`'); }); test.non_directives.map(function(fake_directive) { - assert.strictEqual(tokenizer.has_directive(fake_directive), false, fake_directive + " in " + test.input); + assert.strictEqual(tokenizer.has_directive(fake_directive), false, "Unexpectedly found directive `" + fake_directive + "` at the end of `" + test.input + '`'); }); } @@ -156,6 +156,16 @@ describe("Directives", function() { input: '"use strict";try{"use asm";', directives: ["use strict"], non_directives: ["use\nstrict", "use \nstrict", "use asm"] + }, + { + input: 'class foo {', + directives: ["use strict"], + non_directives: ["use\nstrict", "use asm"] + }, + { + input: 'class foo {}', + directives: [], + non_directives: ["use strict", "use asm", "use\nstrict"] } ]; @@ -367,4 +377,44 @@ describe("Directives", function() { ); } }); + it("Should be detect implicit usages of strict mode from tree walker", function() { + var tests = [ + { + input: 'class foo {bar(){_check_}}', + directives: ["use strict"], + non_directives: ["use bar"] + }, + { + input: 'class foo {bar(){}}_check_', + directives: [], + non_directives: ["use strict", "use bar"] + } + ]; + + var i = 0; + var checked; + var checkWalker = new uglify.TreeWalker(function(node, descend) { + if (node instanceof uglify.AST_Symbol && node.name === "_check_") { + checked = true; + for (var j = 0; j < tests[i].directives.length; j++) { + assert.equal(checkWalker.has_directive(tests[i].directives[j]), true, + "Did not found directive '" + tests[i].directives[j] + "' in test " + tests[i].input) + } + for (var k = 0; k < tests[i].non_directives.length; k++) { + assert.equal(checkWalker.has_directive(tests[i].non_directives[k]), undefined, + "Found directive '" + tests[i].non_directives[k] + "' in test " + tests[i].input) + } + } + }); + + for (; i < tests.length; i++) { + // Do tests - iterate the ast in each test - check only when _check_ occurs - fail when no _check_ has been found + checked = false; + var ast = uglify.parse(tests[i].input); + ast.walk(checkWalker); + if (!checked) { + throw "No _check_ symbol found in " + tests[i].input; + } + } + }); });