• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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