blob: 7a0240009e30b592d493decadeb619cf939f9067 [file] [log] [blame]
#!/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()