OLD | NEW |
| (Empty) |
1 #!/usr/bin/env python | |
2 # | |
3 # Copyright 2010 The Closure Linter Authors. All Rights Reserved. | |
4 # | |
5 # Licensed under the Apache License, Version 2.0 (the "License"); | |
6 # you may not use this file except in compliance with the License. | |
7 # You may obtain a copy of the License at | |
8 # | |
9 # http://www.apache.org/licenses/LICENSE-2.0 | |
10 # | |
11 # Unless required by applicable law or agreed to in writing, software | |
12 # distributed under the License is distributed on an "AS-IS" BASIS, | |
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
14 # See the License for the specific language governing permissions and | |
15 # limitations under the License. | |
16 | |
17 """Unit tests for ClosurizedNamespacesInfo.""" | |
18 | |
19 | |
20 | |
21 import unittest as googletest | |
22 from closure_linter import aliaspass | |
23 from closure_linter import closurizednamespacesinfo | |
24 from closure_linter import ecmametadatapass | |
25 from closure_linter import javascriptstatetracker | |
26 from closure_linter import javascripttokens | |
27 from closure_linter import testutil | |
28 from closure_linter import tokenutil | |
29 | |
30 # pylint: disable=g-bad-name | |
31 TokenType = javascripttokens.JavaScriptTokenType | |
32 | |
33 | |
34 def _ToLineDict(illegal_alias_stmts): | |
35 """Replaces tokens with the respective line number.""" | |
36 return {k: v.line_number for k, v in illegal_alias_stmts.iteritems()} | |
37 | |
38 | |
39 class ClosurizedNamespacesInfoTest(googletest.TestCase): | |
40 """Tests for ClosurizedNamespacesInfo.""" | |
41 | |
42 _test_cases = { | |
43 'goog.global.anything': None, | |
44 'package.CONSTANT': 'package', | |
45 'package.methodName': 'package', | |
46 'package.subpackage.methodName': 'package.subpackage', | |
47 'package.subpackage.methodName.apply': 'package.subpackage', | |
48 'package.ClassName.something': 'package.ClassName', | |
49 'package.ClassName.Enum.VALUE.methodName': 'package.ClassName', | |
50 'package.ClassName.CONSTANT': 'package.ClassName', | |
51 'package.namespace.CONSTANT.methodName': 'package.namespace', | |
52 'package.ClassName.inherits': 'package.ClassName', | |
53 'package.ClassName.apply': 'package.ClassName', | |
54 'package.ClassName.methodName.apply': 'package.ClassName', | |
55 'package.ClassName.methodName.call': 'package.ClassName', | |
56 'package.ClassName.prototype.methodName': 'package.ClassName', | |
57 'package.ClassName.privateMethod_': 'package.ClassName', | |
58 'package.className.privateProperty_': 'package.className', | |
59 'package.className.privateProperty_.methodName': 'package.className', | |
60 'package.ClassName.PrivateEnum_': 'package.ClassName', | |
61 'package.ClassName.prototype.methodName.apply': 'package.ClassName', | |
62 'package.ClassName.property.subProperty': 'package.ClassName', | |
63 'package.className.prototype.something.somethingElse': 'package.className' | |
64 } | |
65 | |
66 def testGetClosurizedNamespace(self): | |
67 """Tests that the correct namespace is returned for various identifiers.""" | |
68 namespaces_info = closurizednamespacesinfo.ClosurizedNamespacesInfo( | |
69 closurized_namespaces=['package'], ignored_extra_namespaces=[]) | |
70 for identifier, expected_namespace in self._test_cases.items(): | |
71 actual_namespace = namespaces_info.GetClosurizedNamespace(identifier) | |
72 self.assertEqual( | |
73 expected_namespace, | |
74 actual_namespace, | |
75 'expected namespace "' + str(expected_namespace) + | |
76 '" for identifier "' + str(identifier) + '" but was "' + | |
77 str(actual_namespace) + '"') | |
78 | |
79 def testIgnoredExtraNamespaces(self): | |
80 """Tests that ignored_extra_namespaces are ignored.""" | |
81 token = self._GetRequireTokens('package.Something') | |
82 namespaces_info = closurizednamespacesinfo.ClosurizedNamespacesInfo( | |
83 closurized_namespaces=['package'], | |
84 ignored_extra_namespaces=['package.Something']) | |
85 | |
86 self.assertFalse(namespaces_info.IsExtraRequire(token), | |
87 'Should be valid since it is in ignored namespaces.') | |
88 | |
89 namespaces_info = closurizednamespacesinfo.ClosurizedNamespacesInfo( | |
90 ['package'], []) | |
91 | |
92 self.assertTrue(namespaces_info.IsExtraRequire(token), | |
93 'Should be invalid since it is not in ignored namespaces.') | |
94 | |
95 def testIsExtraProvide_created(self): | |
96 """Tests that provides for created namespaces are not extra.""" | |
97 input_lines = [ | |
98 'goog.provide(\'package.Foo\');', | |
99 'package.Foo = function() {};' | |
100 ] | |
101 | |
102 token, namespaces_info = self._GetStartTokenAndNamespacesInfoForScript( | |
103 input_lines, ['package']) | |
104 | |
105 self.assertFalse(namespaces_info.IsExtraProvide(token), | |
106 'Should not be extra since it is created.') | |
107 | |
108 def testIsExtraProvide_createdIdentifier(self): | |
109 """Tests that provides for created identifiers are not extra.""" | |
110 input_lines = [ | |
111 'goog.provide(\'package.Foo.methodName\');', | |
112 'package.Foo.methodName = function() {};' | |
113 ] | |
114 | |
115 token, namespaces_info = self._GetStartTokenAndNamespacesInfoForScript( | |
116 input_lines, ['package']) | |
117 | |
118 self.assertFalse(namespaces_info.IsExtraProvide(token), | |
119 'Should not be extra since it is created.') | |
120 | |
121 def testIsExtraProvide_notCreated(self): | |
122 """Tests that provides for non-created namespaces are extra.""" | |
123 input_lines = ['goog.provide(\'package.Foo\');'] | |
124 | |
125 token, namespaces_info = self._GetStartTokenAndNamespacesInfoForScript( | |
126 input_lines, ['package']) | |
127 | |
128 self.assertTrue(namespaces_info.IsExtraProvide(token), | |
129 'Should be extra since it is not created.') | |
130 | |
131 def testIsExtraProvide_notCreatedMultipartClosurizedNamespace(self): | |
132 """Tests that provides for non-created namespaces are extra.""" | |
133 input_lines = ['goog.provide(\'multi.part.namespace.Foo\');'] | |
134 | |
135 token, namespaces_info = self._GetStartTokenAndNamespacesInfoForScript( | |
136 input_lines, ['multi.part']) | |
137 | |
138 self.assertTrue(namespaces_info.IsExtraProvide(token), | |
139 'Should be extra since it is not created.') | |
140 | |
141 def testIsExtraProvide_duplicate(self): | |
142 """Tests that providing a namespace twice makes the second one extra.""" | |
143 input_lines = [ | |
144 'goog.provide(\'package.Foo\');', | |
145 'goog.provide(\'package.Foo\');', | |
146 'package.Foo = function() {};' | |
147 ] | |
148 | |
149 token, namespaces_info = self._GetStartTokenAndNamespacesInfoForScript( | |
150 input_lines, ['package']) | |
151 | |
152 # Advance to the second goog.provide token. | |
153 token = tokenutil.Search(token.next, TokenType.IDENTIFIER) | |
154 | |
155 self.assertTrue(namespaces_info.IsExtraProvide(token), | |
156 'Should be extra since it is already provided.') | |
157 | |
158 def testIsExtraProvide_notClosurized(self): | |
159 """Tests that provides of non-closurized namespaces are not extra.""" | |
160 input_lines = ['goog.provide(\'notclosurized.Foo\');'] | |
161 | |
162 token, namespaces_info = self._GetStartTokenAndNamespacesInfoForScript( | |
163 input_lines, ['package']) | |
164 | |
165 self.assertFalse(namespaces_info.IsExtraProvide(token), | |
166 'Should not be extra since it is not closurized.') | |
167 | |
168 def testIsExtraRequire_used(self): | |
169 """Tests that requires for used namespaces are not extra.""" | |
170 input_lines = [ | |
171 'goog.require(\'package.Foo\');', | |
172 'var x = package.Foo.methodName();' | |
173 ] | |
174 | |
175 token, namespaces_info = self._GetStartTokenAndNamespacesInfoForScript( | |
176 input_lines, ['package']) | |
177 | |
178 self.assertFalse(namespaces_info.IsExtraRequire(token), | |
179 'Should not be extra since it is used.') | |
180 | |
181 def testIsExtraRequire_usedIdentifier(self): | |
182 """Tests that requires for used methods on classes are extra.""" | |
183 input_lines = [ | |
184 'goog.require(\'package.Foo.methodName\');', | |
185 'var x = package.Foo.methodName();' | |
186 ] | |
187 | |
188 token, namespaces_info = self._GetStartTokenAndNamespacesInfoForScript( | |
189 input_lines, ['package']) | |
190 | |
191 self.assertTrue(namespaces_info.IsExtraRequire(token), | |
192 'Should require the package, not the method specifically.') | |
193 | |
194 def testIsExtraRequire_notUsed(self): | |
195 """Tests that requires for unused namespaces are extra.""" | |
196 input_lines = ['goog.require(\'package.Foo\');'] | |
197 | |
198 token, namespaces_info = self._GetStartTokenAndNamespacesInfoForScript( | |
199 input_lines, ['package']) | |
200 | |
201 self.assertTrue(namespaces_info.IsExtraRequire(token), | |
202 'Should be extra since it is not used.') | |
203 | |
204 def testIsExtraRequire_notUsedMultiPartClosurizedNamespace(self): | |
205 """Tests unused require with multi-part closurized namespaces.""" | |
206 | |
207 input_lines = ['goog.require(\'multi.part.namespace.Foo\');'] | |
208 | |
209 token, namespaces_info = self._GetStartTokenAndNamespacesInfoForScript( | |
210 input_lines, ['multi.part']) | |
211 | |
212 self.assertTrue(namespaces_info.IsExtraRequire(token), | |
213 'Should be extra since it is not used.') | |
214 | |
215 def testIsExtraRequire_notClosurized(self): | |
216 """Tests that requires of non-closurized namespaces are not extra.""" | |
217 input_lines = ['goog.require(\'notclosurized.Foo\');'] | |
218 | |
219 token, namespaces_info = self._GetStartTokenAndNamespacesInfoForScript( | |
220 input_lines, ['package']) | |
221 | |
222 self.assertFalse(namespaces_info.IsExtraRequire(token), | |
223 'Should not be extra since it is not closurized.') | |
224 | |
225 def testIsExtraRequire_objectOnClass(self): | |
226 """Tests that requiring an object on a class is extra.""" | |
227 input_lines = [ | |
228 'goog.require(\'package.Foo.Enum\');', | |
229 'var x = package.Foo.Enum.VALUE1;', | |
230 ] | |
231 | |
232 token, namespaces_info = self._GetStartTokenAndNamespacesInfoForScript( | |
233 input_lines, ['package']) | |
234 | |
235 self.assertTrue(namespaces_info.IsExtraRequire(token), | |
236 'The whole class, not the object, should be required.'); | |
237 | |
238 def testIsExtraRequire_constantOnClass(self): | |
239 """Tests that requiring a constant on a class is extra.""" | |
240 input_lines = [ | |
241 'goog.require(\'package.Foo.CONSTANT\');', | |
242 'var x = package.Foo.CONSTANT', | |
243 ] | |
244 | |
245 token, namespaces_info = self._GetStartTokenAndNamespacesInfoForScript( | |
246 input_lines, ['package']) | |
247 | |
248 self.assertTrue(namespaces_info.IsExtraRequire(token), | |
249 'The class, not the constant, should be required.'); | |
250 | |
251 def testIsExtraRequire_constantNotOnClass(self): | |
252 """Tests that requiring a constant not on a class is OK.""" | |
253 input_lines = [ | |
254 'goog.require(\'package.subpackage.CONSTANT\');', | |
255 'var x = package.subpackage.CONSTANT', | |
256 ] | |
257 | |
258 token, namespaces_info = self._GetStartTokenAndNamespacesInfoForScript( | |
259 input_lines, ['package']) | |
260 | |
261 self.assertFalse(namespaces_info.IsExtraRequire(token), | |
262 'Constants can be required except on classes.'); | |
263 | |
264 def testIsExtraRequire_methodNotOnClass(self): | |
265 """Tests that requiring a method not on a class is OK.""" | |
266 input_lines = [ | |
267 'goog.require(\'package.subpackage.method\');', | |
268 'var x = package.subpackage.method()', | |
269 ] | |
270 | |
271 token, namespaces_info = self._GetStartTokenAndNamespacesInfoForScript( | |
272 input_lines, ['package']) | |
273 | |
274 self.assertFalse(namespaces_info.IsExtraRequire(token), | |
275 'Methods can be required except on classes.'); | |
276 | |
277 def testIsExtraRequire_defaults(self): | |
278 """Tests that there are no warnings about extra requires for test utils""" | |
279 input_lines = ['goog.require(\'goog.testing.jsunit\');'] | |
280 | |
281 token, namespaces_info = self._GetStartTokenAndNamespacesInfoForScript( | |
282 input_lines, ['goog']) | |
283 | |
284 self.assertFalse(namespaces_info.IsExtraRequire(token), | |
285 'Should not be extra since it is for testing.') | |
286 | |
287 def testGetMissingProvides_provided(self): | |
288 """Tests that provided functions don't cause a missing provide.""" | |
289 input_lines = [ | |
290 'goog.provide(\'package.Foo\');', | |
291 'package.Foo = function() {};' | |
292 ] | |
293 | |
294 namespaces_info = self._GetNamespacesInfoForScript( | |
295 input_lines, ['package']) | |
296 | |
297 self.assertEquals(0, len(namespaces_info.GetMissingProvides())) | |
298 | |
299 def testGetMissingProvides_providedIdentifier(self): | |
300 """Tests that provided identifiers don't cause a missing provide.""" | |
301 input_lines = [ | |
302 'goog.provide(\'package.Foo.methodName\');', | |
303 'package.Foo.methodName = function() {};' | |
304 ] | |
305 | |
306 namespaces_info = self._GetNamespacesInfoForScript(input_lines, ['package']) | |
307 self.assertEquals(0, len(namespaces_info.GetMissingProvides())) | |
308 | |
309 def testGetMissingProvides_providedParentIdentifier(self): | |
310 """Tests that provided identifiers on a class don't cause a missing provide | |
311 on objects attached to that class.""" | |
312 input_lines = [ | |
313 'goog.provide(\'package.foo.ClassName\');', | |
314 'package.foo.ClassName.methodName = function() {};', | |
315 'package.foo.ClassName.ObjectName = 1;', | |
316 ] | |
317 | |
318 namespaces_info = self._GetNamespacesInfoForScript(input_lines, ['package']) | |
319 self.assertEquals(0, len(namespaces_info.GetMissingProvides())) | |
320 | |
321 def testGetMissingProvides_unprovided(self): | |
322 """Tests that unprovided functions cause a missing provide.""" | |
323 input_lines = ['package.Foo = function() {};'] | |
324 | |
325 namespaces_info = self._GetNamespacesInfoForScript(input_lines, ['package']) | |
326 | |
327 missing_provides = namespaces_info.GetMissingProvides() | |
328 self.assertEquals(1, len(missing_provides)) | |
329 missing_provide = missing_provides.popitem() | |
330 self.assertEquals('package.Foo', missing_provide[0]) | |
331 self.assertEquals(1, missing_provide[1]) | |
332 | |
333 def testGetMissingProvides_privatefunction(self): | |
334 """Tests that unprovided private functions don't cause a missing provide.""" | |
335 input_lines = ['package.Foo_ = function() {};'] | |
336 | |
337 namespaces_info = self._GetNamespacesInfoForScript(input_lines, ['package']) | |
338 self.assertEquals(0, len(namespaces_info.GetMissingProvides())) | |
339 | |
340 def testGetMissingProvides_required(self): | |
341 """Tests that required namespaces don't cause a missing provide.""" | |
342 input_lines = [ | |
343 'goog.require(\'package.Foo\');', | |
344 'package.Foo.methodName = function() {};' | |
345 ] | |
346 | |
347 namespaces_info = self._GetNamespacesInfoForScript(input_lines, ['package']) | |
348 self.assertEquals(0, len(namespaces_info.GetMissingProvides())) | |
349 | |
350 def testGetMissingRequires_required(self): | |
351 """Tests that required namespaces don't cause a missing require.""" | |
352 input_lines = [ | |
353 'goog.require(\'package.Foo\');', | |
354 'package.Foo();' | |
355 ] | |
356 | |
357 namespaces_info = self._GetNamespacesInfoForScript(input_lines, ['package']) | |
358 missing_requires, _ = namespaces_info.GetMissingRequires() | |
359 self.assertEquals(0, len(missing_requires)) | |
360 | |
361 def testGetMissingRequires_requiredIdentifier(self): | |
362 """Tests that required namespaces satisfy identifiers on that namespace.""" | |
363 input_lines = [ | |
364 'goog.require(\'package.Foo\');', | |
365 'package.Foo.methodName();' | |
366 ] | |
367 | |
368 namespaces_info = self._GetNamespacesInfoForScript(input_lines, ['package']) | |
369 missing_requires, _ = namespaces_info.GetMissingRequires() | |
370 self.assertEquals(0, len(missing_requires)) | |
371 | |
372 def testGetMissingRequires_requiredNamespace(self): | |
373 """Tests that required namespaces satisfy the namespace.""" | |
374 input_lines = [ | |
375 'goog.require(\'package.soy.fooTemplate\');', | |
376 'render(package.soy.fooTemplate);' | |
377 ] | |
378 | |
379 namespaces_info = self._GetNamespacesInfoForScript(input_lines, ['package']) | |
380 missing_requires, _ = namespaces_info.GetMissingRequires() | |
381 self.assertEquals(0, len(missing_requires)) | |
382 | |
383 def testGetMissingRequires_requiredParentClass(self): | |
384 """Tests that requiring a parent class of an object is sufficient to prevent | |
385 a missing require on that object.""" | |
386 input_lines = [ | |
387 'goog.require(\'package.Foo\');', | |
388 'package.Foo.methodName();', | |
389 'package.Foo.methodName(package.Foo.ObjectName);' | |
390 ] | |
391 | |
392 namespaces_info = self._GetNamespacesInfoForScript(input_lines, ['package']) | |
393 missing_requires, _ = namespaces_info.GetMissingRequires() | |
394 self.assertEquals(0, len(missing_requires)) | |
395 | |
396 def testGetMissingRequires_unrequired(self): | |
397 """Tests that unrequired namespaces cause a missing require.""" | |
398 input_lines = ['package.Foo();'] | |
399 | |
400 namespaces_info = self._GetNamespacesInfoForScript(input_lines, ['package']) | |
401 | |
402 missing_requires, _ = namespaces_info.GetMissingRequires() | |
403 self.assertEquals(1, len(missing_requires)) | |
404 missing_req = missing_requires.popitem() | |
405 self.assertEquals('package.Foo', missing_req[0]) | |
406 self.assertEquals(1, missing_req[1]) | |
407 | |
408 def testGetMissingRequires_provided(self): | |
409 """Tests that provided namespaces satisfy identifiers on that namespace.""" | |
410 input_lines = [ | |
411 'goog.provide(\'package.Foo\');', | |
412 'package.Foo.methodName();' | |
413 ] | |
414 | |
415 namespaces_info = self._GetNamespacesInfoForScript(input_lines, ['package']) | |
416 missing_requires, _ = namespaces_info.GetMissingRequires() | |
417 self.assertEquals(0, len(missing_requires)) | |
418 | |
419 def testGetMissingRequires_created(self): | |
420 """Tests that created namespaces do not satisfy usage of an identifier.""" | |
421 input_lines = [ | |
422 'package.Foo = function();', | |
423 'package.Foo.methodName();', | |
424 'package.Foo.anotherMethodName1();', | |
425 'package.Foo.anotherMethodName2();' | |
426 ] | |
427 | |
428 namespaces_info = self._GetNamespacesInfoForScript(input_lines, ['package']) | |
429 | |
430 missing_requires, _ = namespaces_info.GetMissingRequires() | |
431 self.assertEquals(1, len(missing_requires)) | |
432 missing_require = missing_requires.popitem() | |
433 self.assertEquals('package.Foo', missing_require[0]) | |
434 # Make sure line number of first occurrence is reported | |
435 self.assertEquals(2, missing_require[1]) | |
436 | |
437 def testGetMissingRequires_createdIdentifier(self): | |
438 """Tests that created identifiers satisfy usage of the identifier.""" | |
439 input_lines = [ | |
440 'package.Foo.methodName = function();', | |
441 'package.Foo.methodName();' | |
442 ] | |
443 | |
444 namespaces_info = self._GetNamespacesInfoForScript(input_lines, ['package']) | |
445 missing_requires, _ = namespaces_info.GetMissingRequires() | |
446 self.assertEquals(0, len(missing_requires)) | |
447 | |
448 def testGetMissingRequires_implements(self): | |
449 """Tests that a parametrized type requires the correct identifier.""" | |
450 input_lines = [ | |
451 '/** @constructor @implements {package.Bar<T>} */', | |
452 'package.Foo = function();', | |
453 ] | |
454 | |
455 namespaces_info = self._GetNamespacesInfoForScript(input_lines, ['package']) | |
456 missing_requires, _ = namespaces_info.GetMissingRequires() | |
457 self.assertItemsEqual({'package.Bar': 1}, missing_requires) | |
458 | |
459 def testGetMissingRequires_objectOnClass(self): | |
460 """Tests that we should require a class, not the object on the class.""" | |
461 input_lines = [ | |
462 'goog.require(\'package.Foo.Enum\');', | |
463 'var x = package.Foo.Enum.VALUE1;', | |
464 ] | |
465 | |
466 namespaces_info = self._GetNamespacesInfoForScript(input_lines, ['package']) | |
467 missing_requires, _ = namespaces_info.GetMissingRequires() | |
468 self.assertEquals(1, len(missing_requires), | |
469 'The whole class, not the object, should be required.') | |
470 | |
471 def testGetMissingRequires_variableWithSameName(self): | |
472 """Tests that we should not goog.require variables and parameters. | |
473 | |
474 b/5362203 Variables in scope are not missing namespaces. | |
475 """ | |
476 input_lines = [ | |
477 'goog.provide(\'Foo\');', | |
478 'Foo.A = function();', | |
479 'Foo.A.prototype.method = function(ab) {', | |
480 ' if (ab) {', | |
481 ' var docs;', | |
482 ' var lvalue = new Obj();', | |
483 ' // Variable in scope hence not goog.require here.', | |
484 ' docs.foo.abc = 1;', | |
485 ' lvalue.next();', | |
486 ' }', | |
487 ' // Since js is function scope this should also not goog.require.', | |
488 ' docs.foo.func();', | |
489 ' // Its not a variable in scope hence goog.require.', | |
490 ' dummy.xyz.reset();', | |
491 ' return this.method2();', | |
492 '};', | |
493 'Foo.A.prototype.method1 = function(docs, abcd, xyz) {', | |
494 ' // Parameter hence not goog.require.', | |
495 ' docs.nodes.length = 2;', | |
496 ' lvalue.abc.reset();', | |
497 '};' | |
498 ] | |
499 | |
500 namespaces_info = self._GetNamespacesInfoForScript(input_lines, ['Foo', | |
501 'docs', | |
502 'lvalue', | |
503 'dummy']) | |
504 missing_requires, _ = namespaces_info.GetMissingRequires() | |
505 self.assertEquals(2, len(missing_requires)) | |
506 self.assertItemsEqual( | |
507 {'dummy.xyz': 14, | |
508 'lvalue.abc': 20}, missing_requires) | |
509 | |
510 def testIsFirstProvide(self): | |
511 """Tests operation of the isFirstProvide method.""" | |
512 input_lines = [ | |
513 'goog.provide(\'package.Foo\');', | |
514 'package.Foo.methodName();' | |
515 ] | |
516 | |
517 token, namespaces_info = self._GetStartTokenAndNamespacesInfoForScript( | |
518 input_lines, ['package']) | |
519 self.assertTrue(namespaces_info.IsFirstProvide(token)) | |
520 | |
521 def testGetWholeIdentifierString(self): | |
522 """Tests that created identifiers satisfy usage of the identifier.""" | |
523 input_lines = [ | |
524 'package.Foo.', | |
525 ' veryLong.', | |
526 ' identifier;' | |
527 ] | |
528 | |
529 token = testutil.TokenizeSource(input_lines) | |
530 | |
531 self.assertEquals('package.Foo.veryLong.identifier', | |
532 tokenutil.GetIdentifierForToken(token)) | |
533 | |
534 self.assertEquals(None, | |
535 tokenutil.GetIdentifierForToken(token.next)) | |
536 | |
537 def testScopified(self): | |
538 """Tests that a goog.scope call is noticed.""" | |
539 input_lines = [ | |
540 'goog.scope(function() {', | |
541 '});' | |
542 ] | |
543 | |
544 namespaces_info = self._GetNamespacesInfoForScript(input_lines, ['goog']) | |
545 self.assertTrue(namespaces_info._scopified_file) | |
546 | |
547 def testScope_unusedAlias(self): | |
548 """Tests that an unused alias symbol is illegal.""" | |
549 input_lines = [ | |
550 'goog.scope(function() {', | |
551 'var Event = goog.events.Event;', | |
552 '});' | |
553 ] | |
554 | |
555 namespaces_info = self._GetNamespacesInfoForScript(input_lines, ['goog']) | |
556 missing_requires, illegal_alias_stmts = namespaces_info.GetMissingRequires() | |
557 self.assertEquals({}, missing_requires) | |
558 self.assertEquals({'goog.events': 2}, _ToLineDict(illegal_alias_stmts)) | |
559 | |
560 def testScope_usedMultilevelAlias(self): | |
561 """Tests that an used alias symbol in a deep namespace is ok.""" | |
562 input_lines = [ | |
563 'goog.require(\'goog.Events\');', | |
564 'goog.scope(function() {', | |
565 'var Event = goog.Events.DeepNamespace.Event;', | |
566 'Event();', | |
567 '});' | |
568 ] | |
569 | |
570 namespaces_info = self._GetNamespacesInfoForScript(input_lines, ['goog']) | |
571 missing_requires, illegal_alias_stmts = namespaces_info.GetMissingRequires() | |
572 self.assertEquals({}, missing_requires) | |
573 self.assertEquals({}, illegal_alias_stmts) | |
574 | |
575 def testScope_usedAlias(self): | |
576 """Tests that aliased symbols result in correct requires.""" | |
577 input_lines = [ | |
578 'goog.scope(function() {', | |
579 'var Event = goog.events.Event;', | |
580 'var dom = goog.dom;', | |
581 'Event(dom.classes.get);', | |
582 '});' | |
583 ] | |
584 | |
585 namespaces_info = self._GetNamespacesInfoForScript(input_lines, ['goog']) | |
586 missing_requires, illegal_alias_stmts = namespaces_info.GetMissingRequires() | |
587 self.assertEquals({}, illegal_alias_stmts) | |
588 self.assertEquals({'goog.dom.classes': 4, 'goog.events.Event': 4}, | |
589 missing_requires) | |
590 | |
591 def testModule_alias(self): | |
592 """Tests that goog.module style aliases are supported.""" | |
593 input_lines = [ | |
594 'goog.module(\'test.module\');', | |
595 'var Unused = goog.require(\'goog.Unused\');', | |
596 'var AliasedClass = goog.require(\'goog.AliasedClass\');', | |
597 'var x = new AliasedClass();', | |
598 ] | |
599 | |
600 namespaces_info = self._GetNamespacesInfoForScript(input_lines, ['goog']) | |
601 namespaceToken = self._GetRequireTokens('goog.AliasedClass') | |
602 self.assertFalse(namespaces_info.IsExtraRequire(namespaceToken), | |
603 'AliasedClass should be marked as used') | |
604 unusedToken = self._GetRequireTokens('goog.Unused') | |
605 self.assertTrue(namespaces_info.IsExtraRequire(unusedToken), | |
606 'Unused should be marked as not used') | |
607 | |
608 def testModule_aliasInScope(self): | |
609 """Tests that goog.module style aliases are supported.""" | |
610 input_lines = [ | |
611 'goog.module(\'test.module\');', | |
612 'var AliasedClass = goog.require(\'goog.AliasedClass\');', | |
613 'goog.scope(function() {', | |
614 'var x = new AliasedClass();', | |
615 '});', | |
616 ] | |
617 | |
618 namespaces_info = self._GetNamespacesInfoForScript(input_lines, ['goog']) | |
619 namespaceToken = self._GetRequireTokens('goog.AliasedClass') | |
620 self.assertFalse(namespaces_info.IsExtraRequire(namespaceToken), | |
621 'AliasedClass should be marked as used') | |
622 | |
623 def testModule_getAlwaysProvided(self): | |
624 """Tests that goog.module.get is recognized as a built-in.""" | |
625 input_lines = [ | |
626 'goog.provide(\'test.MyClass\');', | |
627 'goog.require(\'goog.someModule\');', | |
628 'goog.scope(function() {', | |
629 'var someModule = goog.module.get(\'goog.someModule\');', | |
630 'test.MyClass = function() {};', | |
631 '});', | |
632 ] | |
633 | |
634 namespaces_info = self._GetNamespacesInfoForScript(input_lines, ['goog']) | |
635 self.assertEquals({}, namespaces_info.GetMissingRequires()[0]) | |
636 | |
637 def testModule_requireForGet(self): | |
638 """Tests that goog.module.get needs a goog.require call.""" | |
639 input_lines = [ | |
640 'goog.provide(\'test.MyClass\');', | |
641 'function foo() {', | |
642 ' var someModule = goog.module.get(\'goog.someModule\');', | |
643 ' someModule.doSth();', | |
644 '}', | |
645 ] | |
646 | |
647 namespaces_info = self._GetNamespacesInfoForScript(input_lines, ['goog']) | |
648 self.assertEquals({'goog.someModule': 3}, | |
649 namespaces_info.GetMissingRequires()[0]) | |
650 | |
651 def testScope_usedTypeAlias(self): | |
652 """Tests aliased symbols in type annotations.""" | |
653 input_lines = [ | |
654 'goog.scope(function() {', | |
655 'var Event = goog.events.Event;', | |
656 '/** @type {Event} */;', | |
657 '});' | |
658 ] | |
659 | |
660 namespaces_info = self._GetNamespacesInfoForScript(input_lines, ['goog']) | |
661 missing_requires, illegal_alias_stmts = namespaces_info.GetMissingRequires() | |
662 self.assertEquals({}, missing_requires) | |
663 self.assertEquals({'goog.events': 2}, _ToLineDict(illegal_alias_stmts)) | |
664 | |
665 def testScope_partialAlias_typeOnly(self): | |
666 """Tests a partial alias only used in type annotations. | |
667 | |
668 In this example, some goog.events namespace would need to be required | |
669 so that evaluating goog.events.bar doesn't throw an error. | |
670 """ | |
671 input_lines = [ | |
672 'goog.scope(function() {', | |
673 'var bar = goog.events.bar;', | |
674 '/** @type {bar.Foo} */;', | |
675 '});' | |
676 ] | |
677 | |
678 namespaces_info = self._GetNamespacesInfoForScript(input_lines, ['goog']) | |
679 missing_requires, illegal_alias_stmts = namespaces_info.GetMissingRequires() | |
680 self.assertEquals({}, missing_requires) | |
681 self.assertEquals({'goog.events': 2}, _ToLineDict(illegal_alias_stmts)) | |
682 | |
683 def testScope_partialAlias(self): | |
684 """Tests a partial alias in conjunction with a type annotation. | |
685 | |
686 In this example, the partial alias is already defined by another type, | |
687 therefore the doc-only type doesn't need to be required. | |
688 """ | |
689 input_lines = [ | |
690 'goog.scope(function() {', | |
691 'var bar = goog.events.bar;', | |
692 '/** @type {bar.Event} */;', | |
693 'bar.EventType();' | |
694 '});' | |
695 ] | |
696 | |
697 namespaces_info = self._GetNamespacesInfoForScript(input_lines, ['goog']) | |
698 missing_requires, illegal_alias_stmts = namespaces_info.GetMissingRequires() | |
699 self.assertEquals({'goog.events.bar.EventType': 4}, missing_requires) | |
700 self.assertEquals({}, illegal_alias_stmts) | |
701 | |
702 def testScope_partialAliasRequires(self): | |
703 """Tests partial aliases with correct requires.""" | |
704 input_lines = [ | |
705 'goog.require(\'goog.events.bar.EventType\');', | |
706 'goog.scope(function() {', | |
707 'var bar = goog.events.bar;', | |
708 '/** @type {bar.Event} */;', | |
709 'bar.EventType();' | |
710 '});' | |
711 ] | |
712 | |
713 namespaces_info = self._GetNamespacesInfoForScript(input_lines, ['goog']) | |
714 missing_requires, illegal_alias_stmts = namespaces_info.GetMissingRequires() | |
715 self.assertEquals({}, missing_requires) | |
716 self.assertEquals({}, illegal_alias_stmts) | |
717 | |
718 def testScope_partialAliasRequiresBoth(self): | |
719 """Tests partial aliases with correct requires.""" | |
720 input_lines = [ | |
721 'goog.require(\'goog.events.bar.Event\');', | |
722 'goog.require(\'goog.events.bar.EventType\');', | |
723 'goog.scope(function() {', | |
724 'var bar = goog.events.bar;', | |
725 '/** @type {bar.Event} */;', | |
726 'bar.EventType();' | |
727 '});' | |
728 ] | |
729 | |
730 namespaces_info = self._GetNamespacesInfoForScript(input_lines, ['goog']) | |
731 missing_requires, illegal_alias_stmts = namespaces_info.GetMissingRequires() | |
732 self.assertEquals({}, missing_requires) | |
733 self.assertEquals({}, illegal_alias_stmts) | |
734 event_token = self._GetRequireTokens('goog.events.bar.Event') | |
735 self.assertTrue(namespaces_info.IsExtraRequire(event_token)) | |
736 | |
737 def testScope_partialAliasNoSubtypeRequires(self): | |
738 """Tests that partial aliases don't yield subtype requires (regression).""" | |
739 input_lines = [ | |
740 'goog.provide(\'goog.events.Foo\');', | |
741 'goog.scope(function() {', | |
742 'goog.events.Foo = {};', | |
743 'var Foo = goog.events.Foo;' | |
744 'Foo.CssName_ = {};' | |
745 'var CssName_ = Foo.CssName_;' | |
746 '});' | |
747 ] | |
748 | |
749 namespaces_info = self._GetNamespacesInfoForScript(input_lines, ['goog']) | |
750 missing_requires, _ = namespaces_info.GetMissingRequires() | |
751 self.assertEquals({}, missing_requires) | |
752 | |
753 def testScope_aliasNamespace(self): | |
754 """Tests that an unused alias namespace is not required when available. | |
755 | |
756 In the example goog.events.Bar is not required, because the namespace | |
757 goog.events is already defined because goog.events.Foo is required. | |
758 """ | |
759 input_lines = [ | |
760 'goog.require(\'goog.events.Foo\');', | |
761 'goog.scope(function() {', | |
762 'var Bar = goog.events.Bar;', | |
763 '/** @type {Bar} */;', | |
764 'goog.events.Foo;', | |
765 '});' | |
766 ] | |
767 | |
768 namespaces_info = self._GetNamespacesInfoForScript(input_lines, ['goog']) | |
769 missing_requires, illegal_alias_stmts = namespaces_info.GetMissingRequires() | |
770 self.assertEquals({}, missing_requires) | |
771 self.assertEquals({}, illegal_alias_stmts) | |
772 | |
773 def testScope_aliasNamespaceIllegal(self): | |
774 """Tests that an unused alias namespace is not required when available.""" | |
775 input_lines = [ | |
776 'goog.scope(function() {', | |
777 'var Bar = goog.events.Bar;', | |
778 '/** @type {Bar} */;', | |
779 '});' | |
780 ] | |
781 | |
782 namespaces_info = self._GetNamespacesInfoForScript(input_lines, ['goog']) | |
783 missing_requires, illegal_alias_stmts = namespaces_info.GetMissingRequires() | |
784 self.assertEquals({}, missing_requires) | |
785 self.assertEquals({'goog.events': 2}, _ToLineDict(illegal_alias_stmts)) | |
786 | |
787 def testScope_provides(self): | |
788 """Tests that aliased symbols result in correct provides.""" | |
789 input_lines = [ | |
790 'goog.scope(function() {', | |
791 'goog.bar = {};', | |
792 'var bar = goog.bar;', | |
793 'bar.Foo = {};', | |
794 '});' | |
795 ] | |
796 | |
797 namespaces_info = self._GetNamespacesInfoForScript(input_lines, ['goog']) | |
798 missing_provides = namespaces_info.GetMissingProvides() | |
799 self.assertEquals({'goog.bar.Foo': 4}, missing_provides) | |
800 _, illegal_alias_stmts = namespaces_info.GetMissingRequires() | |
801 self.assertEquals({}, illegal_alias_stmts) | |
802 | |
803 def testSetTestOnlyNamespaces(self): | |
804 """Tests that a namespace in setTestOnly makes it a valid provide.""" | |
805 namespaces_info = self._GetNamespacesInfoForScript([ | |
806 'goog.setTestOnly(\'goog.foo.barTest\');' | |
807 ], ['goog']) | |
808 | |
809 token = self._GetProvideTokens('goog.foo.barTest') | |
810 self.assertFalse(namespaces_info.IsExtraProvide(token)) | |
811 | |
812 token = self._GetProvideTokens('goog.foo.bazTest') | |
813 self.assertTrue(namespaces_info.IsExtraProvide(token)) | |
814 | |
815 def testSetTestOnlyComment(self): | |
816 """Ensure a comment in setTestOnly does not cause a created namespace.""" | |
817 namespaces_info = self._GetNamespacesInfoForScript([ | |
818 'goog.setTestOnly(\'this is a comment\');' | |
819 ], ['goog']) | |
820 | |
821 self.assertEquals( | |
822 [], namespaces_info._created_namespaces, | |
823 'A comment in setTestOnly should not modify created namespaces.') | |
824 | |
825 def _GetNamespacesInfoForScript(self, script, closurized_namespaces=None): | |
826 _, namespaces_info = self._GetStartTokenAndNamespacesInfoForScript( | |
827 script, closurized_namespaces) | |
828 | |
829 return namespaces_info | |
830 | |
831 def _GetStartTokenAndNamespacesInfoForScript( | |
832 self, script, closurized_namespaces): | |
833 | |
834 token = testutil.TokenizeSource(script) | |
835 return token, self._GetInitializedNamespacesInfo( | |
836 token, closurized_namespaces, []) | |
837 | |
838 def _GetInitializedNamespacesInfo(self, token, closurized_namespaces, | |
839 ignored_extra_namespaces): | |
840 """Returns a namespaces info initialized with the given token stream.""" | |
841 namespaces_info = closurizednamespacesinfo.ClosurizedNamespacesInfo( | |
842 closurized_namespaces=closurized_namespaces, | |
843 ignored_extra_namespaces=ignored_extra_namespaces) | |
844 state_tracker = javascriptstatetracker.JavaScriptStateTracker() | |
845 | |
846 ecma_pass = ecmametadatapass.EcmaMetaDataPass() | |
847 ecma_pass.Process(token) | |
848 | |
849 state_tracker.DocFlagPass(token, error_handler=None) | |
850 | |
851 alias_pass = aliaspass.AliasPass(closurized_namespaces) | |
852 alias_pass.Process(token) | |
853 | |
854 while token: | |
855 state_tracker.HandleToken(token, state_tracker.GetLastNonSpaceToken()) | |
856 namespaces_info.ProcessToken(token, state_tracker) | |
857 state_tracker.HandleAfterToken(token) | |
858 token = token.next | |
859 | |
860 return namespaces_info | |
861 | |
862 def _GetProvideTokens(self, namespace): | |
863 """Returns a list of tokens for a goog.require of the given namespace.""" | |
864 line_text = 'goog.require(\'' + namespace + '\');\n' | |
865 return testutil.TokenizeSource([line_text]) | |
866 | |
867 def _GetRequireTokens(self, namespace): | |
868 """Returns a list of tokens for a goog.require of the given namespace.""" | |
869 line_text = 'goog.require(\'' + namespace + '\');\n' | |
870 return testutil.TokenizeSource([line_text]) | |
871 | |
872 if __name__ == '__main__': | |
873 googletest.main() | |
OLD | NEW |