diff --git a/src/com/google/javascript/jscomp/Es6RewriteBlockScopedDeclaration.java b/src/com/google/javascript/jscomp/Es6RewriteBlockScopedDeclaration.java index 32086ed6de1..680726793da 100644 --- a/src/com/google/javascript/jscomp/Es6RewriteBlockScopedDeclaration.java +++ b/src/com/google/javascript/jscomp/Es6RewriteBlockScopedDeclaration.java @@ -23,7 +23,6 @@ import com.google.common.collect.Multimap; import com.google.common.collect.Table; import com.google.javascript.jscomp.NodeTraversal.AbstractPostOrderCallback; -import com.google.javascript.jscomp.Normalize.NormalizeStatements; import com.google.javascript.jscomp.parsing.parser.FeatureSet; import com.google.javascript.jscomp.parsing.parser.FeatureSet.Feature; import com.google.javascript.rhino.IR; @@ -124,25 +123,6 @@ public void process(Node externs, Node root) { NodeTraversal.traverseEs6(compiler, root, transformer); transformer.transformLoopClosure(); rewriteDeclsToVars(); - - // Block scoped function declarations can occur in any language mode, however for - // transpilation to ES3 and ES5, we want to hoist the functions from the block-scope by - // redeclaring them in var assignments - // - // If block-scope-declared function is the only "ES6 feature" for which we want to transpile, - // then the transpilation process will not rewriteFunctions. Thus, we manually check whether - // we need to rewrite in that case. - if (compiler.getOptions().needsTranspilationFrom(FeatureSet.ES6)) { - RewriteBlockScopedFunctionDeclaration rewriteFunction = - new RewriteBlockScopedFunctionDeclaration(); - - for (Node singleRoot : root.children()) { - FeatureSet features = (FeatureSet) singleRoot.getProp(Node.FEATURE_SET); - if (features.has(Feature.BLOCK_SCOPED_FUNCTION_DECLARATION)) { - NodeTraversal.traverseEs6(compiler, singleRoot, rewriteFunction); - } - } - } } @Override @@ -154,7 +134,6 @@ public void hotSwapScript(Node scriptRoot, Node originalRoot) { NodeTraversal.traverseEs6(compiler, scriptRoot, transformer); transformer.transformLoopClosure(); rewriteDeclsToVars(); - NodeTraversal.traverseEs6(compiler, scriptRoot, new RewriteBlockScopedFunctionDeclaration()); } /** @@ -230,15 +209,6 @@ private void rewriteDeclsToVars() { } } - private class RewriteBlockScopedFunctionDeclaration extends AbstractPostOrderCallback { - @Override - public void visit(NodeTraversal t, Node n, Node parent) { - if (n.isFunction()) { - NormalizeStatements.visitFunction(n, compiler); - } - } - } - /** * Records undeclared names and aggressively rename possible references to them. * Eg: In "{ let inner; } use(inner);", we rename the let declared variable. diff --git a/src/com/google/javascript/jscomp/Es6RewriteBlockScopedFunctionDeclaration.java b/src/com/google/javascript/jscomp/Es6RewriteBlockScopedFunctionDeclaration.java new file mode 100644 index 00000000000..34d0615268f --- /dev/null +++ b/src/com/google/javascript/jscomp/Es6RewriteBlockScopedFunctionDeclaration.java @@ -0,0 +1,100 @@ +/* + * Copyright 2017 The Closure Compiler Authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.javascript.jscomp; + +import com.google.javascript.jscomp.NodeTraversal.AbstractPostOrderCallback; +import com.google.javascript.jscomp.parsing.parser.FeatureSet; +import com.google.javascript.jscomp.parsing.parser.FeatureSet.Feature; +import com.google.javascript.rhino.IR; +import com.google.javascript.rhino.Node; +import com.google.javascript.rhino.Token; + +/** + * Rewrite block-scoped function declarations as "let"s. This pass must happen before + * Es6RewriteBlockScopedDeclaration, which rewrites "let" to "var". + */ +public final class Es6RewriteBlockScopedFunctionDeclaration extends AbstractPostOrderCallback + implements HotSwapCompilerPass { + + private final AbstractCompiler compiler; + private static final FeatureSet transpiledFeatures = + FeatureSet.BARE_MINIMUM.with(Feature.BLOCK_SCOPED_FUNCTION_DECLARATION); + + public Es6RewriteBlockScopedFunctionDeclaration(AbstractCompiler compiler) { + this.compiler = compiler; + } + + @Override + public void process(Node externs, Node root) { + TranspilationPasses.processTranspile(compiler, externs, transpiledFeatures, this); + TranspilationPasses.processTranspile(compiler, root, transpiledFeatures, this); + } + + @Override + public void hotSwapScript(Node scriptRoot, Node originalRoot) { + TranspilationPasses.hotSwapTranspile(compiler, scriptRoot, transpiledFeatures, this); + } + + @Override + public void visit(NodeTraversal t, Node n, Node parent) { + if (n.isFunction() + && parent != null + && parent.isNormalBlock() + && !parent.getParent().isFunction()) { + // Only consider declarations (all expressions have non-block parents) that are not directly + // within a function or top-level. + visitBlockScopedFunctionDeclaration(n, parent); + } + } + + /** + * Rewrite the function declaration from: + *
+   *   function f() {}
+   *   FUNCTION
+   *     NAME x
+   *     PARAM_LIST
+   *     BLOCK
+   * 
to
+   *   let f = function() {};
+   *   LET
+   *     NAME f
+   *       FUNCTION
+   *         NAME (w/ empty string)
+   *         PARAM_LIST
+   *         BLOCK
+   * 
+ * This is similar to {@link Normalize.NormalizeStatements#rewriteFunctionDeclaration} but + * rewrites to "let" instead of "var". + */ + private void visitBlockScopedFunctionDeclaration(Node n, Node parent) { + // Prepare a spot for the function. + Node oldNameNode = n.getFirstChild(); + Node fnNameNode = oldNameNode.cloneNode(); + Node let = IR.declaration(fnNameNode, Token.LET).srcref(n); + + // Prepare the function. + oldNameNode.setString(""); + compiler.reportChangeToEnclosingScope(oldNameNode); + + // Move the function to the front of the parent. + parent.removeChild(n); + parent.addChildToFront(let); + compiler.reportChangeToEnclosingScope(let); + fnNameNode.addChildToFront(n); + } +} diff --git a/src/com/google/javascript/jscomp/TranspilationPasses.java b/src/com/google/javascript/jscomp/TranspilationPasses.java index ed8513353a6..b6937453aa5 100644 --- a/src/com/google/javascript/jscomp/TranspilationPasses.java +++ b/src/com/google/javascript/jscomp/TranspilationPasses.java @@ -67,6 +67,7 @@ public static void addEs6LatePasses(List passes) { passes.add(es6RewriteClass); passes.add(earlyConvertEs6ToEs3); passes.add(lateConvertEs6ToEs3); + passes.add(rewriteBlockScopedFunctionDeclaration); passes.add(rewriteBlockScopedDeclaration); passes.add(rewriteGenerators); } @@ -84,6 +85,7 @@ public static void addEs6PassesBeforeNTI(List passes) { passes.add(es6ExtractClasses); passes.add(es6RewriteClass); passes.add(earlyConvertEs6ToEs3); + passes.add(rewriteBlockScopedFunctionDeclaration); passes.add(rewriteBlockScopedDeclaration); } @@ -320,6 +322,19 @@ protected FeatureSet featureSet() { } }; + static final HotSwapPassFactory rewriteBlockScopedFunctionDeclaration = + new HotSwapPassFactory("Es6RewriteBlockScopedFunctionDeclaration") { + @Override + protected HotSwapCompilerPass create(final AbstractCompiler compiler) { + return new Es6RewriteBlockScopedFunctionDeclaration(compiler); + } + + @Override + protected FeatureSet featureSet() { + return ES8; + } + }; + static final HotSwapPassFactory rewriteBlockScopedDeclaration = new HotSwapPassFactory("Es6RewriteBlockScopedDeclaration") { @Override diff --git a/src/com/google/javascript/jscomp/parsing/IRFactory.java b/src/com/google/javascript/jscomp/parsing/IRFactory.java index 83508b960ca..c8233d37230 100644 --- a/src/com/google/javascript/jscomp/parsing/IRFactory.java +++ b/src/com/google/javascript/jscomp/parsing/IRFactory.java @@ -420,6 +420,7 @@ private void validate(Node n) { validateReturn(n); validateNewDotTarget(n); validateLabel(n); + validateBlockScopedFunctions(n); } private void validateReturn(Node n) { @@ -572,6 +573,12 @@ private void validateParameters(Node n) { } } + private void validateBlockScopedFunctions(Node n) { + if (n.isFunction() && n.getParent().isNormalBlock() && !n.getGrandparent().isFunction()) { + maybeWarnForFeature(n, Feature.BLOCK_SCOPED_FUNCTION_DECLARATION); + } + } + JSDocInfo recordJsDoc(SourceRange location, JSDocInfo info) { if (info != null && info.hasTypeInformation()) { hasJsDocTypeAnnotations = true; @@ -880,6 +887,19 @@ void maybeWarnForFeature( } } + void maybeWarnForFeature(Node node, Feature feature) { + features = features.with(feature); + if (!isSupportedForInputLanguageMode(feature)) { + errorReporter.warning( + "this language feature is only supported for " + + LanguageMode.minimumRequiredFor(feature) + + " mode or better: " + + feature, + sourceName, + node.getLineno(), node.getCharno()); + } + } + void setSourceInfo(Node node, Node ref) { node.setLineno(ref.getLineno()); node.setCharno(ref.getCharno()); @@ -1302,12 +1322,6 @@ Node processFunction(FunctionDeclarationTree functionTree) { maybeWarnForFeature(functionTree, Feature.ASYNC_FUNCTIONS); } - // Add a feature so that transpilation process hoists block scoped functions through - // var redeclaration in ES3 and ES5 - if (isDeclaration) { - features = features.with(Feature.BLOCK_SCOPED_FUNCTION_DECLARATION); - } - IdentifierToken name = functionTree.name; Node newName; if (name != null) { diff --git a/src/com/google/javascript/jscomp/parsing/parser/FeatureSet.java b/src/com/google/javascript/jscomp/parsing/parser/FeatureSet.java index 94735357e32..11087fdcd50 100644 --- a/src/com/google/javascript/jscomp/parsing/parser/FeatureSet.java +++ b/src/com/google/javascript/jscomp/parsing/parser/FeatureSet.java @@ -104,15 +104,6 @@ private Set features() { /** Specific features that can be included in a FeatureSet. */ public enum Feature { - // ES3 features - // Functions can be declared in the block scope for all ES versions. However, for ES3 and ES5, - // we want to hoist the functions from the block scope by redeclaring them as vars (i.e. - // { function f() {} } becomes { var f = function {} }. To prevent this feature from causing - // code to run additional transpilation passes beyond rewriting block scoped functions, we mark - // block scoped functions as an ES3 feature and then manually determine whether to rewrite - // functions inside the processTranspile method of TranspilationPasses.java - BLOCK_SCOPED_FUNCTION_DECLARATION("block function", LangVersion.ES3), - // ES5 features ES3_KEYWORDS_AS_IDENTIFIERS("ES3 keywords as identifiers", LangVersion.ES5), GETTER("getters", LangVersion.ES5), @@ -125,6 +116,7 @@ public enum Feature { ARRAY_PATTERN_REST("array pattern rest", LangVersion.ES6), ARROW_FUNCTIONS("arrow function", LangVersion.ES6), BINARY_LITERALS("binary literal", LangVersion.ES6), + BLOCK_SCOPED_FUNCTION_DECLARATION("block-scoped function declaration", LangVersion.ES6), CLASSES("class", LangVersion.ES6), COMPUTED_PROPERTIES("computed property", LangVersion.ES6), CONST_DECLARATIONS("const declaration", LangVersion.ES6), diff --git a/src/com/google/javascript/jscomp/parsing/parser/testing/FeatureSetSubject.java b/src/com/google/javascript/jscomp/parsing/parser/testing/FeatureSetSubject.java index 19c66cbceaf..6bc5ebccd0d 100644 --- a/src/com/google/javascript/jscomp/parsing/parser/testing/FeatureSetSubject.java +++ b/src/com/google/javascript/jscomp/parsing/parser/testing/FeatureSetSubject.java @@ -29,6 +29,7 @@ * import static com.google.javascript.jscomp.parsing.parser.testing.FeatureSetSubject.assertFS; * ... * assertFS(features).contains(otherFeatures); + * assertFS(features).containsNoneOf(otherFeatures); * */ public class FeatureSetSubject extends Subject { @@ -50,6 +51,13 @@ public void contains(FeatureSet other) { } } + public void containsNoneOf(FeatureSet other) { + if (!other.without(actual()).equals(other)) { + failWithRawMessage("Expected a FeatureSet containing none of: %s\nBut got: %s", + other, actual()); + } + } + public void has(Feature feature) { if (!actual().has(feature)) { failWithRawMessage("Expected a FeatureSet that has: %s\nBut got: %s", feature, actual()); diff --git a/test/com/google/javascript/jscomp/Es6RewriteBlockScopedDeclarationTest.java b/test/com/google/javascript/jscomp/Es6RewriteBlockScopedDeclarationTest.java index 506763b1b66..d2046ea9c3f 100644 --- a/test/com/google/javascript/jscomp/Es6RewriteBlockScopedDeclarationTest.java +++ b/test/com/google/javascript/jscomp/Es6RewriteBlockScopedDeclarationTest.java @@ -290,7 +290,7 @@ public void testForLoop() { test( lines( "for (let i in [0, 1]) {", - " function f() {", + " let f = function() {", " let i = 0;", " if (true) {", " let i = 1;", @@ -329,7 +329,7 @@ public void testForLoop() { test(lines( "for (const i in [0, 1]) {", - " function f() {", + " let f = function() {", " let i = 0;", " if (true) {", " let i = 1;", @@ -347,22 +347,6 @@ public void testForLoop() { "}")); } - public void testFunctionInLoop() { - test( - lines( - "for (var x of y) {", - " function f() {", - " let z;", - " }", - "}"), - lines( - "for (var x of y) {", - " var f = function() {", - " var z;", - " };", - "}")); - } - public void testLoopClosure() { test( lines( @@ -932,7 +916,7 @@ public void testLabeledLoop() { "label1:", "label2:", "for (let x = 1;;) {", - " function f() {", + " let f = function() {", " return x;", " }", "}"), @@ -943,7 +927,7 @@ public void testLabeledLoop() { "label2:", "for (;; $jscomp$loop$0 = {x: $jscomp$loop$0.x}) {", " var f = function($jscomp$loop$0) {", - " return function f() {", + " return function() {", " return $jscomp$loop$0.x;", " }", " }($jscomp$loop$0);", @@ -1073,7 +1057,7 @@ public void testFunctionsInLoop() { lines( "while (true) {", " let x = null;", - " function f() {", + " let f = function() {", " x();", " }", "}"), @@ -1082,7 +1066,7 @@ public void testFunctionsInLoop() { "while (true) {", " $jscomp$loop$0.x = null;", " var f = function($jscomp$loop$0) {", - " return function f() {", + " return function() {", " ($jscomp$loop$0.x)();", " };", " }($jscomp$loop$0);", @@ -1102,7 +1086,7 @@ public void testFunctionsInLoop() { "while (true) {", " $jscomp$loop$0.x = null;", " (function($jscomp$loop$0) {", - " return function () {", + " return function() {", " ($jscomp$loop$0.x)();", " };", " })($jscomp$loop$0)();", @@ -1115,7 +1099,7 @@ public void testNormalizeDeclarations() { test(lines( "while(true) {", " let x, y;", - " function f() {", + " let f = function() {", " x = 1;", " y = 2;", " }", @@ -1125,7 +1109,7 @@ public void testNormalizeDeclarations() { "while(true) {", " $jscomp$loop$0.x = undefined;", " var f = function($jscomp$loop$0) {", - " return function f() {", + " return function() {", " $jscomp$loop$0.x = 1;", " $jscomp$loop$0.y = 2;", " }", @@ -1136,7 +1120,7 @@ public void testNormalizeDeclarations() { test(lines( "while(true) {", " let x, y;", - " function f() {", + " let f = function() {", " y = 2;", " x = 1;", " }", @@ -1146,7 +1130,7 @@ public void testNormalizeDeclarations() { "while(true) {", " $jscomp$loop$0.x = undefined;", " var f = function($jscomp$loop$0) {", - " return function f() {", + " return function() {", " $jscomp$loop$0.y = 2;", " $jscomp$loop$0.x = 1;", " }", @@ -1244,27 +1228,6 @@ public void testLetForInitializers() { "}")); } - public void testBlockScopedFunctionDeclaration() { - test( - lines( - "function f() {", - " var x = 1;", - " if (a) {", - " function x() { return x; }", - " }", - " return x;", - "}"), - lines( - "function f() {", - " var x = 1;", - " if (a) {", - " var x$0 = function() { return x$0; };", - " }", - " return x;", - "}")); - } - - public void testClass() { test(lines( "class C {}", @@ -1362,7 +1325,7 @@ public void testCatch() { test(lines( "function f(e) {", " try {", - " function f(e) {", + " let f = function(e) {", " try {} catch (e) { e++; }", " }", " } catch (e) { e--; }", @@ -1377,13 +1340,6 @@ public void testCatch() { "}")); } - public void testBlockScopedGeneratorFunction() { - // Functions defined in a block get translated to a var - test( - "{ function *f() {yield 1;} }", - "{ var f = function*() { yield 1; }; }"); - } - public void testExterns() { testExternChanges("let x;", "", "var x;"); } diff --git a/test/com/google/javascript/jscomp/Es6RewriteBlockScopedFunctionDeclarationTest.java b/test/com/google/javascript/jscomp/Es6RewriteBlockScopedFunctionDeclarationTest.java new file mode 100644 index 00000000000..67953408485 --- /dev/null +++ b/test/com/google/javascript/jscomp/Es6RewriteBlockScopedFunctionDeclarationTest.java @@ -0,0 +1,108 @@ +/* + * Copyright 2014 The Closure Compiler Authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.javascript.jscomp; + +import com.google.javascript.jscomp.CompilerOptions.LanguageMode; + +/** Test case for {@link Es6RewriteBlockScopedFunctionDeclaration}. */ +public final class Es6RewriteBlockScopedFunctionDeclarationTest extends TypeICompilerTestCase { + + @Override + protected void setUp() throws Exception { + super.setUp(); + setAcceptedLanguage(LanguageMode.ECMASCRIPT_2015); + enableRunTypeCheckAfterProcessing(); + this.mode = TypeInferenceMode.NEITHER; + } + + @Override + protected CompilerOptions getOptions() { + CompilerOptions options = super.getOptions(); + options.setLanguageOut(LanguageMode.ECMASCRIPT3); + return options; + } + + @Override + protected CompilerPass getProcessor(Compiler compiler) { + return new Es6RewriteBlockScopedFunctionDeclaration(compiler); + } + + @Override + protected int getNumRepetitions() { + return 1; + } + + public void testRewritesBlockScopedFunctionDeclaration() { + test("{ function f(){} }", "{ let f = function(){}; }"); + } + + public void testHoistsFunctionToStartOfBlock() { + test("{ console.log(f()); function f(){} }", "{ let f = function(){}; console.log(f()); }"); + } + + public void testBlockScopedGeneratorFunction() { + test("{ function* f() {yield 1;} }", "{ let f = function*() { yield 1; }; }"); + } + + public void testBlockNestedInsideFunction() { + test( + lines( + "function f() {", + " var x = 1;", + " if (a) {", + " x();", + " function x() { return x; }", + " }", + " return x;", + "}"), + lines( + "function f() {", + " var x = 1;", + " if (a) {", + " let x = function() { return x; };", + " x();", + " }", + " return x;", + "}")); + } + + public void testFunctionInLoop() { + test( + lines( + "for (var x of y) {", + " y();", + " function f() {", + " let z;", + " }", + "}"), + lines( + "for (var x of y) {", + " let f = function() {", + " let z;", + " };", + " y();", + "}")); + } + + public void testDoesNotRewriteTopLevelDeclarations() { + testSame("function f(){}"); + } + + public void testDoesNotRewriteFunctionScopedDeclarations() { + testSame("function g() {function f(){}}"); + } +} diff --git a/test/com/google/javascript/jscomp/NodeTraversalTest.java b/test/com/google/javascript/jscomp/NodeTraversalTest.java index fac43ea54b3..06b91e61cae 100644 --- a/test/com/google/javascript/jscomp/NodeTraversalTest.java +++ b/test/com/google/javascript/jscomp/NodeTraversalTest.java @@ -298,23 +298,23 @@ public void visit(NodeTraversal t, Node n, Node parent) { // Note the char numbers are 0-indexed but the line numbers are 1-indexed. String expectedResult = - "" - + "visit NAME a [source_file: [testcode]] @1:4\n" - + "visit VAR [source_file: [testcode]] @1:0\n" - + "visit NAME foo [source_file: [testcode]] @2:9\n" - + "visit PARAM_LIST [source_file: [testcode]] @2:12\n" - + "visit NAME b [source_file: [testcode]] @3:6\n" - + "visit VAR [source_file: [testcode]] @3:2\n" - + "visit NAME a [source_file: [testcode]] @4:6\n" - + "visit NAME c [source_file: [testcode]] @4:15\n" - + "visit VAR [source_file: [testcode]] @4:11\n" - + "visit BLOCK [source_file: [testcode]] @4:9\n" - + "visit IF [source_file: [testcode]] @4:2\n" - + "visit BLOCK [source_file: [testcode]] @2:15\n" - + "visit FUNCTION foo [source_file: [testcode]] @2:0\n" - + "visit SCRIPT [source_file: [testcode]] " - + "[input_id: InputId: [testcode]] " - + "[feature_set: [block function]] @1:0\n"; + lines( + "visit NAME a [source_file: [testcode]] @1:4", + "visit VAR [source_file: [testcode]] @1:0", + "visit NAME foo [source_file: [testcode]] @2:9", + "visit PARAM_LIST [source_file: [testcode]] @2:12", + "visit NAME b [source_file: [testcode]] @3:6", + "visit VAR [source_file: [testcode]] @3:2", + "visit NAME a [source_file: [testcode]] @4:6", + "visit NAME c [source_file: [testcode]] @4:15", + "visit VAR [source_file: [testcode]] @4:11", + "visit BLOCK [source_file: [testcode]] @4:9", + "visit IF [source_file: [testcode]] @4:2", + "visit BLOCK [source_file: [testcode]] @2:15", + "visit FUNCTION foo [source_file: [testcode]] @2:0", + "visit SCRIPT [source_file: [testcode]]" + + " [input_id: InputId: [testcode]]" + + " [feature_set: []] @1:0\n"); assertEquals(expectedResult, builder.toString()); } @@ -813,4 +813,3 @@ private static Node parseRoots(Compiler compiler, String externs, String js) { return IR.root(IR.root(extern), IR.root(main)); } } - diff --git a/test/com/google/javascript/jscomp/parsing/ParserTest.java b/test/com/google/javascript/jscomp/parsing/ParserTest.java index 026207e06be..c8c259f448a 100644 --- a/test/com/google/javascript/jscomp/parsing/ParserTest.java +++ b/test/com/google/javascript/jscomp/parsing/ParserTest.java @@ -184,7 +184,7 @@ public void testContinue() { } public void testBreakCrossFunction() { - parseError("while(1) { function f() { break; } }", UNLABELED_BREAK); + parseError("while(1) { var f = function() { break; } }", UNLABELED_BREAK); } public void testBreakCrossFunctionInFor() { @@ -228,12 +228,12 @@ public void testContinueOutsideSwitch() { } public void testContinueNotCrossFunction1() { - parse("a:switch(1){case(1):function f(){a:while(1){continue a;}}}"); + parse("a:switch(1){case(1):var f = function(){a:while(1){continue a;}}}"); } public void testContinueNotCrossFunction2() { parseError( - "a:switch(1){case(1):function f(){while(1){continue a;}}}", + "a:switch(1){case(1):var f = function(){while(1){continue a;}}}", UNDEFINED_LABEL + " \"a\""); } @@ -1871,19 +1871,31 @@ public void testLetForbidden2() { getRequiresEs6Message(Feature.LET_DECLARATIONS)); } - // Ensure that we only add the feature for function declarations (i.e. - // function f() {} ) and not function expressions (e.g. var f = function() {} ) - public void testBlockScopeFunctionDeclaration() { + public void testBlockScopedFunctionDeclaration() { + mode = LanguageMode.ECMASCRIPT6; + expectFeatures(Feature.BLOCK_SCOPED_FUNCTION_DECLARATION); - parse("if (1) { function f() {} }"); - // NOTE: currently we set this feature for all function declarations, regardless - // of whether they're scoped to the global scope, or a function, or a different block. - parse("function f() {}"); + parse("{ function foo() {} }"); + parse("if (true) { function foo() {} }"); + parse("{ function* gen() {} }"); + parse("if (true) function foo() {}"); + parse("if (true) function foo() {} else {}"); + parse("if (true) {} else function foo() {}"); + parse("if (true) function foo() {} else function foo() {}"); + mode = LanguageMode.ECMASCRIPT5; expectFeatures(); - Node result = parse("if (1) { var f = function() {} }"); - FeatureSet features = (FeatureSet) result.getProp(Node.FEATURE_SET); - assertFS(features).doesNotHave(Feature.BLOCK_SCOPED_FUNCTION_DECLARATION); + // Function expressions and functions directly inside other functions do not trigger this + parse("function foo() {}"); + parse("(function foo() {})"); + parse("function foo() { function bar() {} }"); + parse("{ var foo = function() {}; }"); + parse("{ var foo = function bar() {}; }"); + parse("{ (function() {})(); }"); + parse("{ (function foo() {})(); }"); + + parseWarning( + "{ function f() {} }", getRequiresEs6Message(Feature.BLOCK_SCOPED_FUNCTION_DECLARATION)); } public void testLetForbidden3() { @@ -3845,9 +3857,7 @@ private Node parseError(String source, String... errors) { Node script = result.ast; // check expected features if specified - if (expectedFeatures != null) { - assertFS(result.features).contains(expectedFeatures); - } + assertFS(result.features).contains(expectedFeatures); // verifying that all errors were seen testErrorReporter.assertHasEncounteredAllErrors(); @@ -3874,9 +3884,7 @@ private ParserRunner.ParseResult doParse(String string, String... warnings) { testErrorReporter); // check expected features if specified - if (expectedFeatures != null) { - assertFS(result.features).contains(expectedFeatures); - } + assertFS(result.features).contains(expectedFeatures); // verifying that all warnings were seen testErrorReporter.assertHasEncounteredAllErrors();