Index: third_party/closure_linter/closure_linter/scopeutil_test.py |
diff --git a/third_party/closure_linter/closure_linter/scopeutil_test.py b/third_party/closure_linter/closure_linter/scopeutil_test.py |
new file mode 100644 |
index 0000000000000000000000000000000000000000..7a0240009e30b592d493decadeb619cf939f9067 |
--- /dev/null |
+++ b/third_party/closure_linter/closure_linter/scopeutil_test.py |
@@ -0,0 +1,210 @@ |
+#!/usr/bin/env python |
+# |
+# Copyright 2012 The Closure Linter Authors. All Rights Reserved. |
+# 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. |
+ |
+"""Unit tests for the scopeutil module.""" |
+ |
+# Allow non-Google copyright |
+# pylint: disable=g-bad-file-header |
+ |
+__author__ = ('nnaze@google.com (Nathan Naze)') |
+ |
+ |
+import unittest as googletest |
+ |
+from closure_linter import ecmametadatapass |
+from closure_linter import scopeutil |
+from closure_linter import testutil |
+ |
+ |
+def _FindContexts(start_token): |
+ """Depth first search of all contexts referenced by a token stream. |
+ |
+ Includes contexts' parents, which might not be directly referenced |
+ by any token in the stream. |
+ |
+ Args: |
+ start_token: First token in the token stream. |
+ |
+ Yields: |
+ All contexts referenced by this token stream. |
+ """ |
+ |
+ seen_contexts = set() |
+ |
+ # For each token, yield the context if we haven't seen it before. |
+ for token in start_token: |
+ |
+ token_context = token.metadata.context |
+ contexts = [token_context] |
+ |
+ # Also grab all the context's ancestors. |
+ parent = token_context.parent |
+ while parent: |
+ contexts.append(parent) |
+ parent = parent.parent |
+ |
+ # Yield each of these contexts if we've not seen them. |
+ for context in contexts: |
+ if context not in seen_contexts: |
+ yield context |
+ |
+ seen_contexts.add(context) |
+ |
+ |
+def _FindFirstContextOfType(token, context_type): |
+ """Returns the first statement context.""" |
+ for context in _FindContexts(token): |
+ if context.type == context_type: |
+ return context |
+ |
+ |
+def _WrapWithGoogScope(script): |
+ """Wraps source code in a goog.scope statement.""" |
+ return 'goog.scope(function() {\n' + script + '\n});' |
+ |
+ |
+class StatementTest(googletest.TestCase): |
+ |
+ def assertAlias(self, expected_match, script): |
+ start_token = testutil.TokenizeSourceAndRunEcmaPass(script) |
+ statement = _FindFirstContextOfType( |
+ start_token, ecmametadatapass.EcmaContext.VAR) |
+ match = scopeutil.MatchAlias(statement) |
+ self.assertEquals(expected_match, match) |
+ |
+ def testSimpleAliases(self): |
+ self.assertAlias( |
+ ('foo', 'goog.foo'), |
+ _WrapWithGoogScope('var foo = goog.foo;')) |
+ |
+ self.assertAlias( |
+ ('foo', 'goog.foo'), |
+ _WrapWithGoogScope('var foo = goog.foo')) # No semicolon |
+ |
+ def testAliasWithComment(self): |
+ self.assertAlias( |
+ ('Component', 'goog.ui.Component'), |
+ _WrapWithGoogScope('var Component = /* comment */ goog.ui.Component;')) |
+ |
+ def testMultilineAlias(self): |
+ self.assertAlias( |
+ ('Component', 'goog.ui.Component'), |
+ _WrapWithGoogScope('var Component = \n goog.ui.\n Component;')) |
+ |
+ def testNonSymbolAliasVarStatements(self): |
+ self.assertAlias(None, _WrapWithGoogScope('var foo = 3;')) |
+ self.assertAlias(None, _WrapWithGoogScope('var foo = function() {};')) |
+ self.assertAlias(None, _WrapWithGoogScope('for(var foo = bar;;){}')) |
+ self.assertAlias(None, _WrapWithGoogScope('var foo = bar ? baz : qux;')) |
+ |
+ |
+class ScopeBlockTest(googletest.TestCase): |
+ |
+ @staticmethod |
+ def _GetBlocks(source): |
+ start_token = testutil.TokenizeSourceAndRunEcmaPass(source) |
+ for context in _FindContexts(start_token): |
+ if context.type is ecmametadatapass.EcmaContext.BLOCK: |
+ yield context |
+ |
+ def assertNoBlocks(self, script): |
+ blocks = list(self._GetBlocks(script)) |
+ self.assertEquals([], blocks) |
+ |
+ def testNotBlocks(self): |
+ # Ensure these are not considered blocks. |
+ self.assertNoBlocks('goog.scope(if{});') |
+ self.assertNoBlocks('goog.scope(for{});') |
+ self.assertNoBlocks('goog.scope(switch{});') |
+ self.assertNoBlocks('goog.scope(function foo{});') |
+ |
+ def testNonScopeBlocks(self): |
+ |
+ blocks = list(self._GetBlocks('goog.scope(try{});')) |
+ self.assertEquals(1, len(blocks)) |
+ self.assertFalse(scopeutil.IsGoogScopeBlock(blocks.pop())) |
+ |
+ blocks = list(self._GetBlocks('goog.scope(function(a,b){});')) |
+ self.assertEquals(1, len(blocks)) |
+ self.assertFalse(scopeutil.IsGoogScopeBlock(blocks.pop())) |
+ |
+ blocks = list(self._GetBlocks('goog.scope(try{} catch(){});')) |
+ # Two blocks: try and catch. |
+ self.assertEquals(2, len(blocks)) |
+ self.assertFalse(scopeutil.IsGoogScopeBlock(blocks.pop())) |
+ self.assertFalse(scopeutil.IsGoogScopeBlock(blocks.pop())) |
+ |
+ blocks = list(self._GetBlocks('goog.scope(try{} catch(){} finally {});')) |
+ self.assertEquals(3, len(blocks)) |
+ self.assertFalse(scopeutil.IsGoogScopeBlock(blocks.pop())) |
+ self.assertFalse(scopeutil.IsGoogScopeBlock(blocks.pop())) |
+ self.assertFalse(scopeutil.IsGoogScopeBlock(blocks.pop())) |
+ |
+ |
+class AliasTest(googletest.TestCase): |
+ |
+ def setUp(self): |
+ self.start_token = testutil.TokenizeSourceAndRunEcmaPass(_TEST_SCRIPT) |
+ |
+ def testMatchAliasStatement(self): |
+ matches = set() |
+ for context in _FindContexts(self.start_token): |
+ match = scopeutil.MatchAlias(context) |
+ if match: |
+ matches.add(match) |
+ |
+ self.assertEquals( |
+ set([('bar', 'baz'), |
+ ('foo', 'this.foo_'), |
+ ('Component', 'goog.ui.Component'), |
+ ('MyClass', 'myproject.foo.MyClass'), |
+ ('NonClosurizedClass', 'aaa.bbb.NonClosurizedClass')]), |
+ matches) |
+ |
+ def testMatchAliasStatement_withClosurizedNamespaces(self): |
+ |
+ closurized_namepaces = frozenset(['goog', 'myproject']) |
+ |
+ matches = set() |
+ for context in _FindContexts(self.start_token): |
+ match = scopeutil.MatchAlias(context) |
+ if match: |
+ unused_alias, symbol = match |
+ if scopeutil.IsInClosurizedNamespace(symbol, closurized_namepaces): |
+ matches.add(match) |
+ |
+ self.assertEquals( |
+ set([('MyClass', 'myproject.foo.MyClass'), |
+ ('Component', 'goog.ui.Component')]), |
+ matches) |
+ |
+_TEST_SCRIPT = """ |
+goog.scope(function() { |
+ var Component = goog.ui.Component; // scope alias |
+ var MyClass = myproject.foo.MyClass; // scope alias |
+ |
+ // Scope alias of non-Closurized namespace. |
+ var NonClosurizedClass = aaa.bbb.NonClosurizedClass; |
+ |
+ var foo = this.foo_; // non-scope object property alias |
+ var bar = baz; // variable alias |
+ |
+ var component = new Component(); |
+}); |
+ |
+""" |
+ |
+if __name__ == '__main__': |
+ googletest.main() |