blob: cec3376e12dfbf15bb9e0b77a397e1ab26ed5b6a [file] [log] [blame]
Jamie Gennis2da489c2012-09-19 18:06:29 -07001#!/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 closurizednamespacesinfo
23from closure_linter import javascriptstatetracker
24from closure_linter import javascripttokenizer
25from closure_linter import javascripttokens
26from closure_linter import tokenutil
27
28# pylint: disable-msg=C6409
29TokenType = javascripttokens.JavaScriptTokenType
30
31
32class ClosurizedNamespacesInfoTest(googletest.TestCase):
33 """Tests for ClosurizedNamespacesInfo."""
34
35 _test_cases = {
36 'goog.global.anything': None,
37 'package.CONSTANT': 'package',
38 'package.methodName': 'package',
39 'package.subpackage.methodName': 'package.subpackage',
40 'package.subpackage.methodName.apply': 'package.subpackage',
41 'package.ClassName.something': 'package.ClassName',
42 'package.ClassName.Enum.VALUE.methodName': 'package.ClassName',
43 'package.ClassName.CONSTANT': 'package.ClassName',
44 'package.namespace.CONSTANT.methodName': 'package.namespace',
45 'package.ClassName.inherits': 'package.ClassName',
46 'package.ClassName.apply': 'package.ClassName',
47 'package.ClassName.methodName.apply': 'package.ClassName',
48 'package.ClassName.methodName.call': 'package.ClassName',
49 'package.ClassName.prototype.methodName': 'package.ClassName',
50 'package.ClassName.privateMethod_': 'package.ClassName',
51 'package.className.privateProperty_': 'package.className',
52 'package.className.privateProperty_.methodName': 'package.className',
53 'package.ClassName.PrivateEnum_': 'package.ClassName',
54 'package.ClassName.prototype.methodName.apply': 'package.ClassName',
55 'package.ClassName.property.subProperty': 'package.ClassName',
56 'package.className.prototype.something.somethingElse': 'package.className'
57 }
58
59 _tokenizer = javascripttokenizer.JavaScriptTokenizer()
60
61 def testGetClosurizedNamespace(self):
62 """Tests that the correct namespace is returned for various identifiers."""
63 namespaces_info = closurizednamespacesinfo.ClosurizedNamespacesInfo(
64 closurized_namespaces=['package'], ignored_extra_namespaces=[])
65 for identifier, expected_namespace in self._test_cases.items():
66 actual_namespace = namespaces_info.GetClosurizedNamespace(identifier)
67 self.assertEqual(
68 expected_namespace,
69 actual_namespace,
70 'expected namespace "' + str(expected_namespace) +
71 '" for identifier "' + str(identifier) + '" but was "' +
72 str(actual_namespace) + '"')
73
74 def testIgnoredExtraNamespaces(self):
75 """Tests that ignored_extra_namespaces are ignored."""
76 token = self._GetRequireTokens('package.Something')
77 namespaces_info = closurizednamespacesinfo.ClosurizedNamespacesInfo(
78 closurized_namespaces=['package'],
79 ignored_extra_namespaces=['package.Something'])
80
81 self.assertFalse(namespaces_info.IsExtraRequire(token),
82 'Should be valid since it is in ignored namespaces.')
83
84 namespaces_info = closurizednamespacesinfo.ClosurizedNamespacesInfo(
85 ['package'], [])
86
87 self.assertTrue(namespaces_info.IsExtraRequire(token),
88 'Should be invalid since it is not in ignored namespaces.')
89
90 def testIsExtraProvide_created(self):
91 """Tests that provides for created namespaces are not extra."""
92 input_lines = [
93 'goog.provide(\'package.Foo\');',
94 'package.Foo = function() {};'
95 ]
96 token = self._tokenizer.TokenizeFile(input_lines)
97 namespaces_info = self._GetInitializedNamespacesInfo(token, ['package'], [])
98
99 self.assertFalse(namespaces_info.IsExtraProvide(token),
100 'Should not be extra since it is created.')
101
102 def testIsExtraProvide_createdIdentifier(self):
103 """Tests that provides for created identifiers are not extra."""
104 input_lines = [
105 'goog.provide(\'package.Foo.methodName\');',
106 'package.Foo.methodName = function() {};'
107 ]
108 token = self._tokenizer.TokenizeFile(input_lines)
109 namespaces_info = self._GetInitializedNamespacesInfo(token, ['package'], [])
110
111 self.assertFalse(namespaces_info.IsExtraProvide(token),
112 'Should not be extra since it is created.')
113
114 def testIsExtraProvide_notCreated(self):
115 """Tests that provides for non-created namespaces are extra."""
116 input_lines = ['goog.provide(\'package.Foo\');']
117 token = self._tokenizer.TokenizeFile(input_lines)
118 namespaces_info = self._GetInitializedNamespacesInfo(token, ['package'], [])
119
120 self.assertTrue(namespaces_info.IsExtraProvide(token),
121 'Should be extra since it is not created.')
122
123 def testIsExtraProvide_duplicate(self):
124 """Tests that providing a namespace twice makes the second one extra."""
125 input_lines = [
126 'goog.provide(\'package.Foo\');',
127 'goog.provide(\'package.Foo\');',
128 'package.Foo = function() {};'
129 ]
130 token = self._tokenizer.TokenizeFile(input_lines)
131 namespaces_info = self._GetInitializedNamespacesInfo(token, ['package'], [])
132
133 # Advance to the second goog.provide token.
134 token = tokenutil.Search(token.next, TokenType.IDENTIFIER)
135
136 self.assertTrue(namespaces_info.IsExtraProvide(token),
137 'Should be extra since it is already provided.')
138
139 def testIsExtraProvide_notClosurized(self):
140 """Tests that provides of non-closurized namespaces are not extra."""
141 input_lines = ['goog.provide(\'notclosurized.Foo\');']
142 token = self._tokenizer.TokenizeFile(input_lines)
143 namespaces_info = self._GetInitializedNamespacesInfo(token, ['package'], [])
144
145 self.assertFalse(namespaces_info.IsExtraProvide(token),
146 'Should not be extra since it is not closurized.')
147
148 def testIsExtraRequire_used(self):
149 """Tests that requires for used namespaces are not extra."""
150 input_lines = [
151 'goog.require(\'package.Foo\');',
152 'var x = package.Foo.methodName();'
153 ]
154 token = self._tokenizer.TokenizeFile(input_lines)
155 namespaces_info = self._GetInitializedNamespacesInfo(token, ['package'], [])
156
157 self.assertFalse(namespaces_info.IsExtraRequire(token),
158 'Should not be extra since it is used.')
159
160 def testIsExtraRequire_usedIdentifier(self):
161 """Tests that requires for used methods on classes are extra."""
162 input_lines = [
163 'goog.require(\'package.Foo.methodName\');',
164 'var x = package.Foo.methodName();'
165 ]
166 token = self._tokenizer.TokenizeFile(input_lines)
167 namespaces_info = self._GetInitializedNamespacesInfo(token, ['package'], [])
168
169 self.assertTrue(namespaces_info.IsExtraRequire(token),
170 'Should require the package, not the method specifically.')
171
172 def testIsExtraRequire_notUsed(self):
173 """Tests that requires for unused namespaces are extra."""
174 input_lines = ['goog.require(\'package.Foo\');']
175 token = self._tokenizer.TokenizeFile(input_lines)
176 namespaces_info = self._GetInitializedNamespacesInfo(token, ['package'], [])
177
178 self.assertTrue(namespaces_info.IsExtraRequire(token),
179 'Should be extra since it is not used.')
180
181 def testIsExtraRequire_notClosurized(self):
182 """Tests that requires of non-closurized namespaces are not extra."""
183 input_lines = ['goog.require(\'notclosurized.Foo\');']
184 token = self._tokenizer.TokenizeFile(input_lines)
185 namespaces_info = self._GetInitializedNamespacesInfo(token, ['package'], [])
186
187 self.assertFalse(namespaces_info.IsExtraRequire(token),
188 'Should not be extra since it is not closurized.')
189
190 def testIsExtraRequire_objectOnClass(self):
191 """Tests that requiring an object on a class is extra."""
192 input_lines = [
193 'goog.require(\'package.Foo.Enum\');',
194 'var x = package.Foo.Enum.VALUE1;',
195 ]
196 token = self._tokenizer.TokenizeFile(input_lines)
197 namespaces_info = self._GetInitializedNamespacesInfo(token, ['package'], [])
198
199 self.assertTrue(namespaces_info.IsExtraRequire(token),
200 'The whole class, not the object, should be required.');
201
202 def testIsExtraRequire_constantOnClass(self):
203 """Tests that requiring a constant on a class is extra."""
204 input_lines = [
205 'goog.require(\'package.Foo.CONSTANT\');',
206 'var x = package.Foo.CONSTANT',
207 ]
208 token = self._tokenizer.TokenizeFile(input_lines)
209 namespaces_info = self._GetInitializedNamespacesInfo(token, ['package'], [])
210
211 self.assertTrue(namespaces_info.IsExtraRequire(token),
212 'The class, not the constant, should be required.');
213
214 def testIsExtraRequire_constantNotOnClass(self):
215 """Tests that requiring a constant not on a class is OK."""
216 input_lines = [
217 'goog.require(\'package.subpackage.CONSTANT\');',
218 'var x = package.subpackage.CONSTANT',
219 ]
220 token = self._tokenizer.TokenizeFile(input_lines)
221 namespaces_info = self._GetInitializedNamespacesInfo(token, ['package'], [])
222
223 self.assertFalse(namespaces_info.IsExtraRequire(token),
224 'Constants can be required except on classes.');
225
226 def testIsExtraRequire_methodNotOnClass(self):
227 """Tests that requiring a method not on a class is OK."""
228 input_lines = [
229 'goog.require(\'package.subpackage.method\');',
230 'var x = package.subpackage.method()',
231 ]
232 token = self._tokenizer.TokenizeFile(input_lines)
233 namespaces_info = self._GetInitializedNamespacesInfo(token, ['package'], [])
234
235 self.assertFalse(namespaces_info.IsExtraRequire(token),
236 'Methods can be required except on classes.');
237
238 def testIsExtraRequire_defaults(self):
239 """Tests that there are no warnings about extra requires for test utils"""
240 input_lines = ['goog.require(\'goog.testing.jsunit\');']
241 token = self._tokenizer.TokenizeFile(input_lines)
242 namespaces_info = self._GetInitializedNamespacesInfo(token, ['goog'], [])
243
244 self.assertFalse(namespaces_info.IsExtraRequire(token),
245 'Should not be extra since it is for testing.')
246
247 def testGetMissingProvides_provided(self):
248 """Tests that provided functions don't cause a missing provide."""
249 input_lines = [
250 'goog.provide(\'package.Foo\');',
251 'package.Foo = function() {};'
252 ]
253 token = self._tokenizer.TokenizeFile(input_lines)
254 namespaces_info = self._GetInitializedNamespacesInfo(token, ['package'], [])
255
256 self.assertEquals(0, len(namespaces_info.GetMissingProvides()))
257
258 def testGetMissingProvides_providedIdentifier(self):
259 """Tests that provided identifiers don't cause a missing provide."""
260 input_lines = [
261 'goog.provide(\'package.Foo.methodName\');',
262 'package.Foo.methodName = function() {};'
263 ]
264 token = self._tokenizer.TokenizeFile(input_lines)
265 namespaces_info = self._GetInitializedNamespacesInfo(token, ['package'], [])
266
267 self.assertEquals(0, len(namespaces_info.GetMissingProvides()))
268
269 def testGetMissingProvides_providedParentIdentifier(self):
270 """Tests that provided identifiers on a class don't cause a missing provide
271 on objects attached to that class."""
272 input_lines = [
273 'goog.provide(\'package.foo.ClassName\');',
274 'package.foo.ClassName.methodName = function() {};',
275 'package.foo.ClassName.ObjectName = 1;',
276 ]
277 token = self._tokenizer.TokenizeFile(input_lines)
278 namespaces_info = self._GetInitializedNamespacesInfo(token, ['package'], [])
279
280 self.assertEquals(0, len(namespaces_info.GetMissingProvides()))
281
282 def testGetMissingProvides_unprovided(self):
283 """Tests that unprovided functions cause a missing provide."""
284 input_lines = ['package.Foo = function() {};']
285 token = self._tokenizer.TokenizeFile(input_lines)
286 namespaces_info = self._GetInitializedNamespacesInfo(token, ['package'], [])
287
288 self.assertEquals(1, len(namespaces_info.GetMissingProvides()))
289 self.assertTrue('package.Foo' in namespaces_info.GetMissingProvides())
290
291 def testGetMissingProvides_privatefunction(self):
292 """Tests that unprovided private functions don't cause a missing provide."""
293 input_lines = ['package.Foo_ = function() {};']
294 token = self._tokenizer.TokenizeFile(input_lines)
295 namespaces_info = self._GetInitializedNamespacesInfo(token, ['package'], [])
296
297 self.assertEquals(0, len(namespaces_info.GetMissingProvides()))
298
299 def testGetMissingProvides_required(self):
300 """Tests that required namespaces don't cause a missing provide."""
301 input_lines = [
302 'goog.require(\'package.Foo\');',
303 'package.Foo.methodName = function() {};'
304 ]
305 token = self._tokenizer.TokenizeFile(input_lines)
306 namespaces_info = self._GetInitializedNamespacesInfo(token, ['package'], [])
307
308 self.assertEquals(0, len(namespaces_info.GetMissingProvides()))
309
310 def testGetMissingRequires_required(self):
311 """Tests that required namespaces don't cause a missing require."""
312 input_lines = [
313 'goog.require(\'package.Foo\');',
314 'package.Foo();'
315 ]
316 token = self._tokenizer.TokenizeFile(input_lines)
317 namespaces_info = self._GetInitializedNamespacesInfo(token, ['package'], [])
318
319 self.assertEquals(0, len(namespaces_info.GetMissingProvides()))
320
321 def testGetMissingRequires_requiredIdentifier(self):
322 """Tests that required namespaces satisfy identifiers on that namespace."""
323 input_lines = [
324 'goog.require(\'package.Foo\');',
325 'package.Foo.methodName();'
326 ]
327 token = self._tokenizer.TokenizeFile(input_lines)
328 namespaces_info = self._GetInitializedNamespacesInfo(token, ['package'], [])
329
330 self.assertEquals(0, len(namespaces_info.GetMissingProvides()))
331
332 def testGetMissingRequires_requiredParentClass(self):
333 """Tests that requiring a parent class of an object is sufficient to prevent
334 a missing require on that object."""
335 input_lines = [
336 'goog.require(\'package.Foo\');',
337 'package.Foo.methodName();',
338 'package.Foo.methodName(package.Foo.ObjectName);'
339 ]
340 token = self._tokenizer.TokenizeFile(input_lines)
341 namespaces_info = self._GetInitializedNamespacesInfo(token, ['package'], [])
342
343 self.assertEquals(0, len(namespaces_info.GetMissingRequires()))
344
345 def testGetMissingRequires_unrequired(self):
346 """Tests that unrequired namespaces cause a missing require."""
347 input_lines = ['package.Foo();']
348 token = self._tokenizer.TokenizeFile(input_lines)
349 namespaces_info = self._GetInitializedNamespacesInfo(token, ['package'], [])
350
351 self.assertEquals(1, len(namespaces_info.GetMissingRequires()))
352 self.assertTrue('package.Foo' in namespaces_info.GetMissingRequires())
353
354 def testGetMissingRequires_provided(self):
355 """Tests that provided namespaces satisfy identifiers on that namespace."""
356 input_lines = [
357 'goog.provide(\'package.Foo\');',
358 'package.Foo.methodName();'
359 ]
360 token = self._tokenizer.TokenizeFile(input_lines)
361 namespaces_info = self._GetInitializedNamespacesInfo(token, ['package'], [])
362
363 self.assertEquals(0, len(namespaces_info.GetMissingRequires()))
364
365 def testGetMissingRequires_created(self):
366 """Tests that created namespaces do not satisfy usage of an identifier."""
367 input_lines = [
368 'package.Foo = function();',
369 'package.Foo.methodName();'
370 ]
371 token = self._tokenizer.TokenizeFile(input_lines)
372 namespaces_info = self._GetInitializedNamespacesInfo(token, ['package'], [])
373
374 self.assertEquals(1, len(namespaces_info.GetMissingRequires()))
375 self.assertTrue('package.Foo' in namespaces_info.GetMissingRequires())
376
377 def testGetMissingRequires_createdIdentifier(self):
378 """Tests that created identifiers satisfy usage of the identifier."""
379 input_lines = [
380 'package.Foo.methodName = function();',
381 'package.Foo.methodName();'
382 ]
383 token = self._tokenizer.TokenizeFile(input_lines)
384 namespaces_info = self._GetInitializedNamespacesInfo(token, ['package'], [])
385
386 self.assertEquals(0, len(namespaces_info.GetMissingRequires()))
387
388 def testGetMissingRequires_objectOnClass(self):
389 """Tests that we should require a class, not the object on the class."""
390 input_lines = [
391 'goog.require(\'package.Foo.Enum\');',
392 'var x = package.Foo.Enum.VALUE1;',
393 ]
394 token = self._tokenizer.TokenizeFile(input_lines)
395 namespaces_info = self._GetInitializedNamespacesInfo(token, ['package'], [])
396
397 self.assertEquals(1, len(namespaces_info.GetMissingRequires()),
398 'The whole class, not the object, should be required.');
399
400 def testIsFirstProvide(self):
401 """Tests operation of the isFirstProvide method."""
402 input_lines = [
403 'goog.provide(\'package.Foo\');',
404 'package.Foo.methodName();'
405 ]
406 token = self._tokenizer.TokenizeFile(input_lines)
407 namespaces_info = self._GetInitializedNamespacesInfo(token, ['package'], [])
408
409 self.assertTrue(namespaces_info.IsFirstProvide(token))
410
411 def testGetWholeIdentifierString(self):
412 """Tests that created identifiers satisfy usage of the identifier."""
413 input_lines = [
414 'package.Foo.',
415 ' veryLong.',
416 ' identifier;'
417 ]
418 token = self._tokenizer.TokenizeFile(input_lines)
419 namespaces_info = closurizednamespacesinfo.ClosurizedNamespacesInfo([], [])
420
421 self.assertEquals('package.Foo.veryLong.identifier',
422 namespaces_info._GetWholeIdentifierString(token))
423 self.assertEquals(None,
424 namespaces_info._GetWholeIdentifierString(token.next))
425
426 def _GetInitializedNamespacesInfo(self, token, closurized_namespaces,
427 ignored_extra_namespaces):
428 """Returns a namespaces info initialized with the given token stream."""
429 namespaces_info = closurizednamespacesinfo.ClosurizedNamespacesInfo(
430 closurized_namespaces=closurized_namespaces,
431 ignored_extra_namespaces=ignored_extra_namespaces)
432 state_tracker = javascriptstatetracker.JavaScriptStateTracker()
433
434 while token:
435 namespaces_info.ProcessToken(token, state_tracker)
436 token = token.next
437
438 return namespaces_info
439
440 def _GetProvideTokens(self, namespace):
441 """Returns a list of tokens for a goog.require of the given namespace."""
442 line_text = 'goog.require(\'' + namespace + '\');\n'
443 return javascripttokenizer.JavaScriptTokenizer().TokenizeFile([line_text])
444
445 def _GetRequireTokens(self, namespace):
446 """Returns a list of tokens for a goog.require of the given namespace."""
447 line_text = 'goog.require(\'' + namespace + '\');\n'
448 return javascripttokenizer.JavaScriptTokenizer().TokenizeFile([line_text])
449
450if __name__ == '__main__':
451 googletest.main()