OLD | NEW |
| (Empty) |
1 #!/usr/bin/env python | |
2 # | |
3 # Copyright 2012 The Closure Linter Authors. All Rights Reserved. | |
4 # Licensed under the Apache License, Version 2.0 (the "License"); | |
5 # you may not use this file except in compliance with the License. | |
6 # You may obtain a copy of the License at | |
7 # | |
8 # http://www.apache.org/licenses/LICENSE-2.0 | |
9 # | |
10 # Unless required by applicable law or agreed to in writing, software | |
11 # distributed under the License is distributed on an "AS-IS" BASIS, | |
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 # See the License for the specific language governing permissions and | |
14 # limitations under the License. | |
15 | |
16 """Unit tests for the scopeutil module.""" | |
17 | |
18 # Allow non-Google copyright | |
19 # pylint: disable=g-bad-file-header | |
20 | |
21 __author__ = ('nnaze@google.com (Nathan Naze)') | |
22 | |
23 | |
24 import unittest as googletest | |
25 | |
26 from closure_linter import ecmametadatapass | |
27 from closure_linter import scopeutil | |
28 from closure_linter import testutil | |
29 | |
30 | |
31 def _FindContexts(start_token): | |
32 """Depth first search of all contexts referenced by a token stream. | |
33 | |
34 Includes contexts' parents, which might not be directly referenced | |
35 by any token in the stream. | |
36 | |
37 Args: | |
38 start_token: First token in the token stream. | |
39 | |
40 Yields: | |
41 All contexts referenced by this token stream. | |
42 """ | |
43 | |
44 seen_contexts = set() | |
45 | |
46 # For each token, yield the context if we haven't seen it before. | |
47 for token in start_token: | |
48 | |
49 token_context = token.metadata.context | |
50 contexts = [token_context] | |
51 | |
52 # Also grab all the context's ancestors. | |
53 parent = token_context.parent | |
54 while parent: | |
55 contexts.append(parent) | |
56 parent = parent.parent | |
57 | |
58 # Yield each of these contexts if we've not seen them. | |
59 for context in contexts: | |
60 if context not in seen_contexts: | |
61 yield context | |
62 | |
63 seen_contexts.add(context) | |
64 | |
65 | |
66 def _FindFirstContextOfType(token, context_type): | |
67 """Returns the first statement context.""" | |
68 for context in _FindContexts(token): | |
69 if context.type == context_type: | |
70 return context | |
71 | |
72 | |
73 def _ParseAssignment(script): | |
74 start_token = testutil.TokenizeSourceAndRunEcmaPass(script) | |
75 statement = _FindFirstContextOfType( | |
76 start_token, ecmametadatapass.EcmaContext.VAR) | |
77 return statement | |
78 | |
79 | |
80 class StatementTest(googletest.TestCase): | |
81 | |
82 def assertAlias(self, expected_match, script): | |
83 statement = _ParseAssignment(script) | |
84 match = scopeutil.MatchAlias(statement) | |
85 self.assertEquals(expected_match, match) | |
86 | |
87 def assertModuleAlias(self, expected_match, script): | |
88 statement = _ParseAssignment(script) | |
89 match = scopeutil.MatchModuleAlias(statement) | |
90 self.assertEquals(expected_match, match) | |
91 | |
92 def testSimpleAliases(self): | |
93 self.assertAlias( | |
94 ('foo', 'goog.foo'), | |
95 'var foo = goog.foo;') | |
96 | |
97 self.assertAlias( | |
98 ('foo', 'goog.foo'), | |
99 'var foo = goog.foo') # No semicolon | |
100 | |
101 def testAliasWithComment(self): | |
102 self.assertAlias( | |
103 ('Component', 'goog.ui.Component'), | |
104 'var Component = /* comment */ goog.ui.Component;') | |
105 | |
106 def testMultilineAlias(self): | |
107 self.assertAlias( | |
108 ('Component', 'goog.ui.Component'), | |
109 'var Component = \n goog.ui.\n Component;') | |
110 | |
111 def testNonSymbolAliasVarStatements(self): | |
112 self.assertAlias(None, 'var foo = 3;') | |
113 self.assertAlias(None, 'var foo = function() {};') | |
114 self.assertAlias(None, 'var foo = bar ? baz : qux;') | |
115 | |
116 def testModuleAlias(self): | |
117 self.assertModuleAlias( | |
118 ('foo', 'goog.foo'), | |
119 'var foo = goog.require("goog.foo");') | |
120 self.assertModuleAlias( | |
121 None, | |
122 'var foo = goog.require(notastring);') | |
123 | |
124 | |
125 class ScopeBlockTest(googletest.TestCase): | |
126 | |
127 @staticmethod | |
128 def _GetBlocks(source): | |
129 start_token = testutil.TokenizeSourceAndRunEcmaPass(source) | |
130 for context in _FindContexts(start_token): | |
131 if context.type is ecmametadatapass.EcmaContext.BLOCK: | |
132 yield context | |
133 | |
134 def assertNoBlocks(self, script): | |
135 blocks = list(self._GetBlocks(script)) | |
136 self.assertEquals([], blocks) | |
137 | |
138 def testNotBlocks(self): | |
139 # Ensure these are not considered blocks. | |
140 self.assertNoBlocks('goog.scope(if{});') | |
141 self.assertNoBlocks('goog.scope(for{});') | |
142 self.assertNoBlocks('goog.scope(switch{});') | |
143 self.assertNoBlocks('goog.scope(function foo{});') | |
144 | |
145 def testNonScopeBlocks(self): | |
146 | |
147 blocks = list(self._GetBlocks('goog.scope(try{});')) | |
148 self.assertEquals(1, len(blocks)) | |
149 self.assertFalse(scopeutil.IsGoogScopeBlock(blocks.pop())) | |
150 | |
151 blocks = list(self._GetBlocks('goog.scope(function(a,b){});')) | |
152 self.assertEquals(1, len(blocks)) | |
153 self.assertFalse(scopeutil.IsGoogScopeBlock(blocks.pop())) | |
154 | |
155 blocks = list(self._GetBlocks('goog.scope(try{} catch(){});')) | |
156 # Two blocks: try and catch. | |
157 self.assertEquals(2, len(blocks)) | |
158 self.assertFalse(scopeutil.IsGoogScopeBlock(blocks.pop())) | |
159 self.assertFalse(scopeutil.IsGoogScopeBlock(blocks.pop())) | |
160 | |
161 blocks = list(self._GetBlocks('goog.scope(try{} catch(){} finally {});')) | |
162 self.assertEquals(3, len(blocks)) | |
163 self.assertFalse(scopeutil.IsGoogScopeBlock(blocks.pop())) | |
164 self.assertFalse(scopeutil.IsGoogScopeBlock(blocks.pop())) | |
165 self.assertFalse(scopeutil.IsGoogScopeBlock(blocks.pop())) | |
166 | |
167 | |
168 class AliasTest(googletest.TestCase): | |
169 | |
170 def setUp(self): | |
171 self.start_token = testutil.TokenizeSourceAndRunEcmaPass(_TEST_SCRIPT) | |
172 | |
173 def testMatchAliasStatement(self): | |
174 matches = set() | |
175 for context in _FindContexts(self.start_token): | |
176 match = scopeutil.MatchAlias(context) | |
177 if match: | |
178 matches.add(match) | |
179 | |
180 self.assertEquals( | |
181 set([('bar', 'baz'), | |
182 ('foo', 'this.foo_'), | |
183 ('Component', 'goog.ui.Component'), | |
184 ('MyClass', 'myproject.foo.MyClass'), | |
185 ('NonClosurizedClass', 'aaa.bbb.NonClosurizedClass')]), | |
186 matches) | |
187 | |
188 def testMatchAliasStatement_withClosurizedNamespaces(self): | |
189 | |
190 closurized_namepaces = frozenset(['goog', 'myproject']) | |
191 | |
192 matches = set() | |
193 for context in _FindContexts(self.start_token): | |
194 match = scopeutil.MatchAlias(context) | |
195 if match: | |
196 unused_alias, symbol = match | |
197 if scopeutil.IsInClosurizedNamespace(symbol, closurized_namepaces): | |
198 matches.add(match) | |
199 | |
200 self.assertEquals( | |
201 set([('MyClass', 'myproject.foo.MyClass'), | |
202 ('Component', 'goog.ui.Component')]), | |
203 matches) | |
204 | |
205 _TEST_SCRIPT = """ | |
206 goog.scope(function() { | |
207 var Component = goog.ui.Component; // scope alias | |
208 var MyClass = myproject.foo.MyClass; // scope alias | |
209 | |
210 // Scope alias of non-Closurized namespace. | |
211 var NonClosurizedClass = aaa.bbb.NonClosurizedClass; | |
212 | |
213 var foo = this.foo_; // non-scope object property alias | |
214 var bar = baz; // variable alias | |
215 | |
216 var component = new Component(); | |
217 }); | |
218 | |
219 """ | |
220 | |
221 if __name__ == '__main__': | |
222 googletest.main() | |
OLD | NEW |