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 21import unittest as googletest 22from closure_linter import aliaspass 23from closure_linter import closurizednamespacesinfo 24from closure_linter import ecmametadatapass 25from closure_linter import javascriptstatetracker 26from closure_linter import javascripttokens 27from closure_linter import testutil 28from closure_linter import tokenutil 29 30# pylint: disable=g-bad-name 31TokenType = javascripttokens.JavaScriptTokenType 32 33 34def _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 39class 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 872if __name__ == '__main__': 873 googletest.main() 874