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();