Revision: 3853
Author: [email protected]
Date: Tue Nov 17 21:31:46 2009
Log: Address David-Sarah Hopwood's comments on ScopeAnalyzer
http://codereview.appspot.com/156050
I submitted CL 148045 before I addressed
http://codereview.appspot.com/148045/diff/5017/6002. This CL
addresses those comments.
[email protected], [email protected]
http://code.google.com/p/google-caja/source/detail?r=3853
Modified:
/trunk/src/com/google/caja/parser/js/scope/ScopeAnalyzer.java
/trunk/src/com/google/caja/parser/js/scope/ScopeListener.java
/trunk/src/com/google/caja/parser/js/scope/ScopeType.java
/trunk/tests/com/google/caja/parser/js/scope/ScopeAnalyzerTest.java
=======================================
--- /trunk/src/com/google/caja/parser/js/scope/ScopeAnalyzer.java Tue Nov
17 12:49:58 2009
+++ /trunk/src/com/google/caja/parser/js/scope/ScopeAnalyzer.java Tue Nov
17 21:31:46 2009
@@ -69,7 +69,7 @@
* <dd>A symbol masks another symbol when they have the same name and
the
* first appears in a scope that is wholly contained in the other's
scope
* so that any uses of the name will not resolve to the symbol in the
- * containing scope.
+ * containing scope. A symbol does not mask itself.
* E.g. in <code>var x; function (x) { ... x ... }}</code> the formal
* parameter {...@code x} masks the variable {...@code x} since uses of
* {...@code x} in the function no longer resolve to the variable.
@@ -185,7 +185,7 @@
// Now that we're done with all the declaration in the scope, we can
// tell whether a use corresponds to a declaration in the scope.
String symbolName = id.node.getName();
- ScopeTree<S> defSite = defSite(symbolName, s);
+ ScopeTree<S> defSite = definingSite(symbolName, s);
Operator assignOperator = assignOperator(id);
if (assignOperator == null) {
listener.read(id, s.scopeImpl, scopeImpl(defSite));
@@ -196,7 +196,7 @@
listener.assigned(id, s.scopeImpl, scopeImpl(defSite));
}
} else if (n instanceof Declaration) {
- ScopeTree<S> defSite = defSite(id.node.getName(), s);
+ ScopeTree<S> defSite = definingSite(id.node.getName(), s);
listener.assigned(id, s.scopeImpl, scopeImpl(defSite));
} else {
throw new ClassCastException("Unexpected use " + n);
@@ -234,7 +234,7 @@
* variable.
* @param useSite the scope in which symbol is referenced.
*/
- private ScopeTree<S> defSite(String symbolName, ScopeTree<S> useSite) {
+ private ScopeTree<S> definingSite(String symbolName, ScopeTree<S>
useSite) {
if ("this".equals(symbolName)) {
// "this" is defined in function & program scopes, and cannot be
declared.
for (ScopeTree<S> s = useSite; s != null; s = s.outer) {
@@ -330,16 +330,17 @@
id, declScope.scopeImpl, ex.child(exId), s.scopeImpl);
}
}
- ScopeTree<S> maskedScope = defSite(symbolName, declScope);
+ ScopeTree<S> maskedScope = definingSite(symbolName, declScope);
declScope.declared.add(symbolName);
listener.declaration(id, declScope.scopeImpl);
- if (maskedScope != null) {
- if (maskedScope == scope) {
- listener.duplicate(id, declScope.scopeImpl);
- } else if (!(id.parent.node instanceof FunctionConstructor
- && symbolName.equals(nameAssignedTo(id.parent)))) {
+ if (maskedScope != null
// Not a function declaration or a var declaration like
// var x = function x() { ... };
+ && !(id.parent.node instanceof FunctionConstructor
+ && symbolName.equals(nameAssignedTo(id.parent)))) {
+ if (maskedScope == scope) {
+ listener.duplicate(id, declScope.scopeImpl);
+ } else {
listener.masked(id, declScope.scopeImpl, scopeImpl(maskedScope));
}
}
=======================================
--- /trunk/src/com/google/caja/parser/js/scope/ScopeListener.java Tue Nov
17 12:49:58 2009
+++ /trunk/src/com/google/caja/parser/js/scope/ScopeListener.java Tue Nov
17 21:31:46 2009
@@ -89,13 +89,13 @@
* Called when a variable is read.
*
* @param useSite the scope in which id is read.
- * @param defSite the scope in which id is defined or null in the case
+ * @param definingSite the scope in which id is defined or null in the
case
* of undeclared globals. NOTE: be skeptical of this event if there
is a
- * {...@link ScopeType#WITH with} block between useSite and defSite.
- * {...@code defSite} will always be the same as {...@code useSite} or on
its
- * {...@link AbstractScope#getContainingScope() containing scope chain}.
+ * {...@link ScopeType#WITH with} block between useSite and
definingSite.
+ * {...@code definingSite} will always be the same as {...@code useSite}
or on
+ * its {...@link AbstractScope#getContainingScope() containing scope
chain}.
*/
- void read(AncestorChain<Identifier> id, S useSite, S defSite);
+ void read(AncestorChain<Identifier> id, S useSite, S definingSite);
/**
* Called when a variable is assigned.
@@ -105,13 +105,13 @@
* for them unless they are explicitly assigned.</p>
*
* @param useSite the scope in which id is assigned.
- * @param defSite the scope in which id is defined or null in the case
+ * @param definingSite the scope in which id is defined or null in the
case
* of undeclared globals. NOTE: be skeptical of this event if there
is a
- * {...@link ScopeType#WITH with} block between useSite and defSite.
- * {...@code defSite} will always be the same as {...@code useSite} or on
its
- * {...@link AbstractScope#getContainingScope() containing scope chain}.
+ * {...@link ScopeType#WITH with} block between useSite and
definingSite.
+ * {...@code definingSite} will always be the same as {...@code useSite}
or on
+ * its {...@link AbstractScope#getContainingScope() containing scope
chain}.
*/
- void assigned(AncestorChain<Identifier> id, S useSite, S defSite);
+ void assigned(AncestorChain<Identifier> id, S useSite, S definingSite);
/**
* Creates a scope for the given node.
=======================================
--- /trunk/src/com/google/caja/parser/js/scope/ScopeType.java Tue Nov 17
12:49:58 2009
+++ /trunk/src/com/google/caja/parser/js/scope/ScopeType.java Tue Nov 17
21:31:46 2009
@@ -19,6 +19,11 @@
import com.google.caja.parser.js.FunctionConstructor;
import com.google.caja.parser.js.WithStmt;
+/**
+ * Describes the kind of parse tree node that introduces a scope.
+ *
+ * @author [email protected]
+ */
public enum ScopeType {
/**
@@ -74,6 +79,10 @@
WITH,
;
+ /**
+ * True iff {...@code var} declarations that appear inside this scope take
+ * effect in this scope instead of being hoisted to a containing scope.
+ */
public final boolean isDeclarationContainer;
ScopeType() { this(false); }
@@ -81,6 +90,10 @@
this.isDeclarationContainer = isDeclarationContainer;
}
+ /**
+ * The type of scope introduced by the given node or null if the given
node
+ * does not introduce a scope.
+ */
public static ScopeType forNode(ParseTreeNode n) {
if (n instanceof FunctionConstructor) { return FUNCTION; }
if (n instanceof CatchStmt) { return CATCH; }
=======================================
--- /trunk/tests/com/google/caja/parser/js/scope/ScopeAnalyzerTest.java Tue
Nov 17 12:49:58 2009
+++ /trunk/tests/com/google/caja/parser/js/scope/ScopeAnalyzerTest.java Tue
Nov 17 21:31:46 2009
@@ -36,10 +36,11 @@
ScopeListener<TestScope> listener;
public final void testGlobalScope() throws ParseException {
- new ES5ScopeAnalyzer<TestScope>(listener).apply(program(
- "var x = 1, y;",
- "alert(x + z)"));
- assertEvents(
+ assertScoping(
+ program(
+ "var x = 1, y;",
+ "alert(x + z)"),
+ allImplementations(listener),
"enterScope PROGRAM at 0 @ testGlobalScope:1+1 - 2+13",
" locals: [x, y]",
" read: [outer alert at 0, x at 0, outer z at 0]",
@@ -49,11 +50,12 @@
}
public final void testFnDecls() throws ParseException {
- new ES5ScopeAnalyzer<TestScope>(listener).apply(program(
- "var x = 1, y;",
- "alert(x + z);",
- "function alert(msg) { console.log(msg); }"));
- assertEvents(
+ assertScoping(
+ program(
+ "var x = 1, y;",
+ "alert(x + z);",
+ "function alert(msg) { console.log(msg); }"),
+ notJScript(listener),
"enterScope PROGRAM at 0 @ testFnDecls:1+1 - 3+42",
" enterScope FUNCTION at 1 @ testFnDecls:3+1 - 42",
" locals: [alert, msg]",
@@ -125,15 +127,16 @@
}
public final void testHoistingOfDecls1() throws ParseException {
- new ES5ScopeAnalyzer<TestScope>(listener).apply(program(
- "try {",
- " for (var i = 0; i < n; ++i) {",
- " foo(arr[i]);",
- " }",
- "} catch (e) {",
- " alert('stopped at ' + i);",
- "}"));
- assertEvents(
+ assertScoping(
+ program(
+ "try {",
+ " for (var i = 0; i < n; ++i) {",
+ " foo(arr[i]);",
+ " }",
+ "} catch (e) {",
+ " alert('stopped at ' + i);",
+ "}"),
+ allImplementations(listener),
"enterScope PROGRAM at 0 @ testHoistingOfDecls1:1+1 - 7+2",
" enterScope CATCH at 1 @ testHoistingOfDecls1:5+3 - 7+2",
" locals: [e]",
@@ -143,21 +146,21 @@
+ " outer foo at 0, outer arr at 0, i at 0]",
" assigned: [i at 0, i at 0]",
"exitScope at 0");
- assertNoErrors();
}
public final void testHoistingOfDecls2() throws ParseException {
- new ES5ScopeAnalyzer<TestScope>(listener).apply(program(
- "(function () {",
- "try {",
- " for (var i = 0; i < n; ++i) {",
- " foo(arr[i]);",
- " }",
- "} catch (e) {",
- " alert('stopped at ' + i);",
- "}",
- "})();"));
- assertEvents(
+ assertScoping(
+ program(
+ "(function () {",
+ "try {",
+ " for (var i = 0; i < n; ++i) {",
+ " foo(arr[i]);",
+ " }",
+ "} catch (e) {",
+ " alert('stopped at ' + i);",
+ "}",
+ "})();"),
+ allImplementations(listener),
"enterScope PROGRAM at 0 @ testHoistingOfDecls2:1+1 - 9+6",
" enterScope FUNCTION at 1 @ testHoistingOfDecls2:1+2 - 9+2",
" enterScope CATCH at 2 @ testHoistingOfDecls2:6+3 - 8+2",
@@ -174,14 +177,15 @@
}
public final void testUsageOfThis1() throws ParseException {
- new ES5ScopeAnalyzer<TestScope>(listener).apply(program(
- "this.location = 'foo';",
- "try {",
- " new XMLHttpRequest;",
- "} catch (e) {",
- " this.XMLHttpRequest = bar;",
- "}"));
- assertEvents(
+ assertScoping(
+ program(
+ "this.location = 'foo';",
+ "try {",
+ " new XMLHttpRequest;",
+ "} catch (e) {",
+ " this.XMLHttpRequest = bar;",
+ "}"),
+ allImplementations(listener),
"enterScope PROGRAM at 0 @ testUsageOfThis1:1+1 - 6+2",
" enterScope CATCH at 1 @ testUsageOfThis1:4+3 - 6+2",
" locals: [e]",
@@ -193,16 +197,17 @@
}
public final void testUsageOfThis2() throws ParseException {
- new ES5ScopeAnalyzer<TestScope>(listener).apply(program(
- "(function () {",
- "this.location = 'foo';",
- "try {",
- " new XMLHttpRequest;",
- "} catch (e) {",
- " this.XMLHttpRequest = bar;",
- "}",
- "})();"));
- assertEvents(
+ assertScoping(
+ program(
+ "(function () {",
+ "this.location = 'foo';",
+ "try {",
+ " new XMLHttpRequest;",
+ "} catch (e) {",
+ " this.XMLHttpRequest = bar;",
+ "}",
+ "})();"),
+ allImplementations(listener),
"enterScope PROGRAM at 0 @ testUsageOfThis2:1+1 - 8+6",
" enterScope FUNCTION at 1 @ testUsageOfThis2:1+2 - 8+2",
" enterScope CATCH at 2 @ testUsageOfThis2:5+3 - 7+2",
@@ -216,15 +221,16 @@
}
public final void testUsageOfArguments() throws ParseException {
- new ES5ScopeAnalyzer<TestScope>(listener).apply(program(
- "if (typeof arguments === 'undefined') {",
- " try {",
- " arguments = (function () { return arguments; })();",
- " } catch (ex) {",
- " arguments = 'arguments';",
- " }",
- "}"));
- assertEvents(
+ assertScoping(
+ program(
+ "if (typeof arguments === 'undefined') {",
+ " try {",
+ " arguments = (function () { return arguments; })();",
+ " } catch (ex) {",
+ " arguments = 'arguments';",
+ " }",
+ "}"),
+ allImplementations(listener),
"enterScope PROGRAM at 0 @ testUsageOfArguments:1+1 - 7+2",
" enterScope FUNCTION at 1 @ testUsageOfArguments:3+18 - 51",
" read: [arguments at 1]",
@@ -239,15 +245,20 @@
}
public final void testDupesAndMasking() throws ParseException {
- new ES5ScopeAnalyzer<TestScope>(listener).apply(program(
+ AncestorChain<Block> prog = program(
"var ex, arguments;",
"try {",
" var arguments = (function (arguments) { return arguments[z];
})();",
"} catch (ex) {",
" arguments = 'arguments';",
" var z;",
- "}"));
- assertEvents(
+ "}");
+ assertMessage( // Generated by parser
+ true, MessageType.DUPLICATE_FORMAL_PARAM, MessageLevel.ERROR,
+ MessagePart.Factory.valueOf("arguments"));
+ assertScoping(
+ prog,
+ allImplementations(listener),
"enterScope PROGRAM at 0 @ testDupesAndMasking:1+1 - 7+2",
" Dupe arguments",
" enterScope FUNCTION at 1 @ testDupesAndMasking:3+20 - 65",
@@ -263,17 +274,15 @@
" read: [z at 1]",
" assigned: [arguments at 1, arguments at 0]",
"exitScope at 0");
- assertMessage( // Generated by parser
- true, MessageType.DUPLICATE_FORMAL_PARAM, MessageLevel.ERROR,
- MessagePart.Factory.valueOf("arguments"));
assertNoErrors();
}
public final void testMasking1() throws ParseException {
- new ES5ScopeAnalyzer<TestScope>(listener).apply(program(
- "var x;",
- "(function (x) {});"));
- assertEvents(
+ assertScoping(
+ program(
+ "var x;",
+ "(function (x) {});"),
+ allImplementations(listener),
"enterScope PROGRAM at 0 @ testMasking1:1+1 - 2+19",
" enterScope FUNCTION at 1 @ testMasking1:2+2 - 17",
" masked x at 0",
@@ -285,14 +294,15 @@
}
public final void testSplitInitialization() throws ParseException {
- new ES5ScopeAnalyzer<TestScope>(listener).apply(program(
- "try {",
- " throw 1;",
- "} catch (e) {",
- " var e = 1;",
- "}",
- "return e;"));
- assertEvents(
+ assertScoping(
+ program(
+ "try {",
+ " throw 1;",
+ "} catch (e) {",
+ " var e = 1;",
+ "}",
+ "return e;"),
+ allImplementations(listener),
"enterScope PROGRAM at 0 @ testSplitInitialization:1+1 - 6+10",
" split initialization of e at 0 into 1",
" enterScope CATCH at 1 @ testSplitInitialization:3+3 - 5+2",
@@ -308,10 +318,11 @@
}
public final void testMasking2() throws ParseException {
- new ES5ScopeAnalyzer<TestScope>(listener).apply(program(
- "(function (x) {});",
- "var x;"));
- assertEvents(
+ assertScoping(
+ program(
+ "(function (x) {});",
+ "var x;"),
+ allImplementations(listener),
"enterScope PROGRAM at 0 @ testMasking2:1+1 - 2+7",
" enterScope FUNCTION at 1 @ testMasking2:1+2 - 17",
" masked x at 0",
@@ -334,8 +345,7 @@
AncestorChain<Identifier> id, TestScope useSite, TestScope
defSite) {
emit("read " + id.node.getName());
}
- }).apply(program(
- "var x = 1; ++y; w = z += x;"));
+ }).apply(program("var x = 1; ++y; w = z += x;"));
assertEvents(
"enterScope PROGRAM at 0 @ testAssignments:1+1 - 28",
" set x",
@@ -351,9 +361,9 @@
}
public final void testForEachLoopAssignsPropName1() throws
ParseException {
- new ES5ScopeAnalyzer<TestScope>(listener).apply(program(
- "for (var k in obj) { count(); }"));
- assertEvents(
+ assertScoping(
+ program("for (var k in obj) { count(); }"),
+ allImplementations(listener),
"enterScope PROGRAM at 0 @ testForEachLoopAssignsPropName1:1+1 -
32",
" locals: [k]",
" read: [outer obj at 0, outer count at 0]",
@@ -363,9 +373,9 @@
}
public final void testForEachLoopAssignsPropName2() throws
ParseException {
- new ES5ScopeAnalyzer<TestScope>(listener).apply(program(
- "for (k in obj) { count(); }"));
- assertEvents(
+ assertScoping(
+ program("for (k in obj) { count(); }"),
+ allImplementations(listener),
"enterScope PROGRAM at 0 @ testForEachLoopAssignsPropName2:1+1 -
28",
" read: [outer obj at 0, outer count at 0]",
" assigned: [outer k at 0]",
@@ -374,8 +384,8 @@
}
public final void testSameName1() throws ParseException {
- new ES5ScopeAnalyzer<TestScope>(listener).apply(program(
- "var x = function x() {};")); // not masking
+ new ES5ScopeAnalyzer<TestScope>(listener).apply(
+ program("var x = function x() {};")); // not masking
assertEvents(
"enterScope PROGRAM at 0 @ testSameName1:1+1 - 25",
" enterScope FUNCTION at 1 @ testSameName1:1+9 - 24",
@@ -388,12 +398,13 @@
}
public final void testFnDeclInCatch() throws ParseException {
- new ES5ScopeAnalyzer<TestScope>(listener).apply(program(
- "try {",
- "} catch (e) {",
- " function foo() { var e; }",
- "}"));
- assertEvents(
+ assertScoping(
+ program(
+ "try {",
+ "} catch (e) {",
+ " function foo() { var e; }",
+ "}"),
+ notJScript(listener),
"enterScope PROGRAM at 0 @ testFnDeclInCatch:1+1 - 4+2",
" enterScope CATCH at 1 @ testFnDeclInCatch:2+3 - 4+2",
" enterScope FUNCTION at 2 @ testFnDeclInCatch:3+3 - 28",
@@ -408,15 +419,37 @@
assertNoErrors();
}
- public final void testExceptionReadAndAssign() throws ParseException {
- new ES5ScopeAnalyzer<TestScope>(listener).apply(program(
+ public final void testFnDeclInCatchJS() throws ParseException {
+ new JScriptScopeAnalyzer<TestScope>(listener).apply(program(
"try {",
- " throw new Error();",
"} catch (e) {",
- " e = new ErrorWrapper(e);",
- " throw e;",
+ " function foo() { var e; }",
"}"));
assertEvents(
+ "enterScope PROGRAM at 0 @ testFnDeclInCatchJS:1+1 - 4+2",
+ " enterScope CATCH at 1 @ testFnDeclInCatchJS:2+3 - 4+2",
+ " enterScope FUNCTION at 2 @ testFnDeclInCatchJS:3+3 - 28",
+ " masked e at 1",
+ " locals: [e]",
+ " exitScope at 2",
+ " locals: [e]",
+ " exitScope at 1",
+ " locals: [foo]",
+ " assigned: [foo at 1]",
+ "exitScope at 0");
+ assertNoErrors();
+ }
+
+ public final void testExceptionReadAndAssign() throws ParseException {
+ assertScoping(
+ program(
+ "try {",
+ " throw new Error();",
+ "} catch (e) {",
+ " e = new ErrorWrapper(e);",
+ " throw e;",
+ "}"),
+ allImplementations(listener),
"enterScope PROGRAM at 0 @ testExceptionReadAndAssign:1+1 - 6+2",
" enterScope CATCH at 1 @ testExceptionReadAndAssign:3+3 - 6+2",
" locals: [e]",
@@ -442,14 +475,43 @@
"exitScope at 0");
assertNoErrors();
}
-
- public final void testWithBlock() throws ParseException {
- new ES5ScopeAnalyzer<TestScope>(listener).apply(program(
- "with (obj) {",
- " var sum = x + y;",
- " alert(sum);",
- "}"));
+ public final void testSameName3() throws ParseException {
+ new JScriptScopeAnalyzer<TestScope>(listener).apply(program(
+ "var x;",
+ "x = function x() {};")); // not masking
+ assertEvents(
+ "enterScope PROGRAM at 0 @ testSameName3:1+1 - 2+21",
+ " enterScope FUNCTION at 1 @ testSameName3:2+5 - 20",
+ " exitScope at 1",
+ " locals: [x, x]",
+ " assigned: [x at 0]",
+ "exitScope at 0");
+ assertNoErrors();
+ }
+
+ public final void testSameName4() throws ParseException {
+ new WorstCaseScopeAnalyzer<TestScope>(listener).apply(program(
+ "var x;",
+ "x = function x() {};")); // not masking
assertEvents(
+ "enterScope PROGRAM at 0 @ testSameName4:1+1 - 2+21",
+ " enterScope FUNCTION at 1 @ testSameName4:2+5 - 20",
+ " locals: [x]",
+ " exitScope at 1",
+ " locals: [x, x]",
+ " assigned: [x at 0]",
+ "exitScope at 0");
+ assertNoErrors();
+ }
+
+ public final void testWithBlock() throws ParseException {
+ assertScoping(
+ program(
+ "with (obj) {",
+ " var sum = x + y;",
+ " alert(sum);",
+ "}"),
+ allImplementations(listener),
"enterScope PROGRAM at 0 @ testWithBlock:1+1 - 4+2",
" enterScope WITH at 1 @ testWithBlock:1+1 - 4+2",
" exitScope at 1",
@@ -461,21 +523,55 @@
assertNoErrors();
}
- public final void testImmediatelyCalledFns() throws ParseException {
+ public final void testImmediatelyCalledFns1() throws ParseException {
new ES5ScopeAnalyzer<TestScope>(listener).apply(program(
"alert([function () { return 'Hello'; }(),",
" function f() { return ' World!'; }()]);"));
assertEvents(
- "enterScope PROGRAM at 0 @ testImmediatelyCalledFns:1+1 - 2+47",
- " enterScope FUNCTION at 1 @ testImmediatelyCalledFns:1+8 - 39",
+ "enterScope PROGRAM at 0 @ testImmediatelyCalledFns1:1+1 - 2+47",
+ " enterScope FUNCTION at 1 @ testImmediatelyCalledFns1:1+8 - 39",
" exitScope at 1",
- " enterScope FUNCTION at 1 @ testImmediatelyCalledFns:2+8 - 42",
+ " enterScope FUNCTION at 1 @ testImmediatelyCalledFns1:2+8 - 42",
" locals: [f]",
" exitScope at 1",
" read: [outer alert at 0]",
"exitScope at 0");
assertNoErrors();
}
+
+ public final void testImmediatelyCalledFns2() throws ParseException {
+ new JScriptScopeAnalyzer<TestScope>(listener).apply(program(
+ "alert([function () { return 'Hello'; }(),",
+ " function f() { return ' World!'; }()]);"));
+ assertEvents(
+ "enterScope PROGRAM at 0 @ testImmediatelyCalledFns2:1+1 - 2+47",
+ " enterScope FUNCTION at 1 @ testImmediatelyCalledFns2:1+8 - 39",
+ " exitScope at 1",
+ " enterScope FUNCTION at 1 @ testImmediatelyCalledFns2:2+8 - 42",
+ " exitScope at 1",
+ " locals: [f]",
+ " read: [outer alert at 0]",
+ "exitScope at 0");
+ assertNoErrors();
+ }
+
+ public final void testImmediatelyCalledFns3() throws ParseException {
+ new WorstCaseScopeAnalyzer<TestScope>(listener).apply(program(
+ "alert([function () { return 'Hello'; }(),",
+ " function f() { return ' World!'; }()]);"));
+ assertEvents(
+ "enterScope PROGRAM at 0 @ testImmediatelyCalledFns3:1+1 - 2+47",
+ " enterScope FUNCTION at 1 @ testImmediatelyCalledFns3:1+8 - 39",
+ " exitScope at 1",
+ " enterScope FUNCTION at 1 @ testImmediatelyCalledFns3:2+8 - 42",
+ " masked f at 0",
+ " locals: [f]",
+ " exitScope at 1",
+ " locals: [f]",
+ " read: [outer alert at 0]",
+ "exitScope at 0");
+ assertNoErrors();
+ }
@Override
public void setUp() throws Exception {
@@ -607,4 +703,32 @@
private void assertEvents(String... expected) {
MoreAsserts.assertListsEqual(Arrays.asList(expected), events);
}
-}
+
+ private void assertScoping(
+ AncestorChain<Block> program, List<ScopeAnalyzer<TestScope>> impls,
+ String... goldenEvents) {
+ assertTrue(events.isEmpty());
+ for (ScopeAnalyzer<TestScope> impl : impls) {
+ impl.apply(program);
+ assertEvents(goldenEvents);
+ events.clear();
+ }
+ }
+
+ private static <S extends AbstractScope>
+ List<ScopeAnalyzer<S>> allImplementations(ScopeListener<S> listener) {
+ List<ScopeAnalyzer<S>> impls = Lists.newArrayList();
+ impls.add(new ES5ScopeAnalyzer<S>(listener));
+ impls.add(new JScriptScopeAnalyzer<S>(listener));
+ impls.add(new WorstCaseScopeAnalyzer<S>(listener));
+ return impls;
+ }
+
+ private static <S extends AbstractScope>
+ List<ScopeAnalyzer<S>> notJScript(ScopeListener<S> listener) {
+ List<ScopeAnalyzer<S>> impls = Lists.newArrayList();
+ impls.add(new ES5ScopeAnalyzer<S>(listener));
+ impls.add(new WorstCaseScopeAnalyzer<S>(listener));
+ return impls;
+ }
+}