OLD | NEW |
| (Empty) |
1 #!/usr/bin/env python | |
2 """Unit tests for the typeannotation module.""" | |
3 | |
4 | |
5 | |
6 | |
7 import unittest as googletest | |
8 | |
9 from closure_linter import testutil | |
10 from closure_linter.common import erroraccumulator | |
11 | |
12 CRAZY_TYPE = ('Array.<!function(new:X,{a:null},...(c|d)):' | |
13 'function(...(Object.<string>))>') | |
14 | |
15 | |
16 class TypeErrorException(Exception): | |
17 """Exception for TypeErrors.""" | |
18 | |
19 def __init__(self, errors): | |
20 super(TypeErrorException, self).__init__() | |
21 self.errors = errors | |
22 | |
23 | |
24 class TypeParserTest(googletest.TestCase): | |
25 """Tests for typeannotation parsing.""" | |
26 | |
27 def _ParseComment(self, script): | |
28 """Parse a script that contains one comment and return it.""" | |
29 accumulator = erroraccumulator.ErrorAccumulator() | |
30 _, comments = testutil.ParseFunctionsAndComments(script, accumulator) | |
31 if accumulator.GetErrors(): | |
32 raise TypeErrorException(accumulator.GetErrors()) | |
33 self.assertEquals(1, len(comments)) | |
34 return comments[0] | |
35 | |
36 def _ParseType(self, type_str): | |
37 """Creates a comment to parse and returns the parsed type.""" | |
38 comment = self._ParseComment('/** @type {%s} **/' % type_str) | |
39 return comment.GetDocFlags()[0].jstype | |
40 | |
41 def assertProperReconstruction(self, type_str, matching_str=None): | |
42 """Parses the type and asserts the its repr matches the type. | |
43 | |
44 If matching_str is specified, it will assert that the repr matches this | |
45 string instead. | |
46 | |
47 Args: | |
48 type_str: The type string to parse. | |
49 matching_str: A string the __repr__ of the parsed type should match. | |
50 Returns: | |
51 The parsed js_type. | |
52 """ | |
53 parsed_type = self._ParseType(type_str) | |
54 # Use listEqual assertion to more easily identify the difference | |
55 self.assertListEqual(list(matching_str or type_str), | |
56 list(repr(parsed_type))) | |
57 self.assertEquals(matching_str or type_str, repr(parsed_type)) | |
58 | |
59 # Newlines will be inserted by the file writer. | |
60 self.assertEquals(type_str.replace('\n', ''), parsed_type.ToString()) | |
61 return parsed_type | |
62 | |
63 def assertNullable(self, type_str, nullable=True): | |
64 parsed_type = self.assertProperReconstruction(type_str) | |
65 self.assertEquals(nullable, parsed_type.GetNullability(), | |
66 '"%s" should %sbe nullable' % | |
67 (type_str, 'not ' if nullable else '')) | |
68 | |
69 def assertNotNullable(self, type_str): | |
70 return self.assertNullable(type_str, nullable=False) | |
71 | |
72 def testReconstruction(self): | |
73 self.assertProperReconstruction('*') | |
74 self.assertProperReconstruction('number') | |
75 self.assertProperReconstruction('(((number)))') | |
76 self.assertProperReconstruction('!number') | |
77 self.assertProperReconstruction('?!number') | |
78 self.assertProperReconstruction('number=') | |
79 self.assertProperReconstruction('number=!?', '?!number=') | |
80 self.assertProperReconstruction('number|?string') | |
81 self.assertProperReconstruction('(number|string)') | |
82 self.assertProperReconstruction('?(number|string)') | |
83 self.assertProperReconstruction('Object.<number,string>') | |
84 self.assertProperReconstruction('function(new:Object)') | |
85 self.assertProperReconstruction('function(new:Object):number') | |
86 self.assertProperReconstruction('function(new:Object,Element):number') | |
87 self.assertProperReconstruction('function(this:T,...)') | |
88 self.assertProperReconstruction('{a:?number}') | |
89 self.assertProperReconstruction('{a:?number,b:(number|string)}') | |
90 self.assertProperReconstruction('{c:{nested_element:*}|undefined}') | |
91 self.assertProperReconstruction('{handleEvent:function(?):?}') | |
92 self.assertProperReconstruction('function():?|null') | |
93 self.assertProperReconstruction('null|function():?|bar') | |
94 | |
95 def testOptargs(self): | |
96 self.assertProperReconstruction('number=') | |
97 self.assertProperReconstruction('number|string=') | |
98 self.assertProperReconstruction('(number|string)=') | |
99 self.assertProperReconstruction('(number|string=)') | |
100 self.assertProperReconstruction('(number=|string)') | |
101 self.assertProperReconstruction('function(...):number=') | |
102 | |
103 def testIndepth(self): | |
104 # Do an deeper check of the crazy identifier | |
105 crazy = self.assertProperReconstruction(CRAZY_TYPE) | |
106 self.assertEquals('Array.', crazy.identifier) | |
107 self.assertEquals(1, len(crazy.sub_types)) | |
108 func1 = crazy.sub_types[0] | |
109 func2 = func1.return_type | |
110 self.assertEquals('function', func1.identifier) | |
111 self.assertEquals('function', func2.identifier) | |
112 self.assertEquals(3, len(func1.sub_types)) | |
113 self.assertEquals(1, len(func2.sub_types)) | |
114 self.assertEquals('Object.', func2.sub_types[0].sub_types[0].identifier) | |
115 | |
116 def testIterIdentifiers(self): | |
117 nested_identifiers = self._ParseType('(a|{b:(c|function(new:d):e)})') | |
118 for identifier in ('a', 'b', 'c', 'd', 'e'): | |
119 self.assertIn(identifier, nested_identifiers.IterIdentifiers()) | |
120 | |
121 def testIsEmpty(self): | |
122 self.assertTrue(self._ParseType('').IsEmpty()) | |
123 self.assertFalse(self._ParseType('?').IsEmpty()) | |
124 self.assertFalse(self._ParseType('!').IsEmpty()) | |
125 self.assertFalse(self._ParseType('<?>').IsEmpty()) | |
126 | |
127 def testIsConstructor(self): | |
128 self.assertFalse(self._ParseType('').IsConstructor()) | |
129 self.assertFalse(self._ParseType('Array.<number>').IsConstructor()) | |
130 self.assertTrue(self._ParseType('function(new:T)').IsConstructor()) | |
131 | |
132 def testIsVarArgsType(self): | |
133 self.assertTrue(self._ParseType('...number').IsVarArgsType()) | |
134 self.assertTrue(self._ParseType('...Object|Array').IsVarArgsType()) | |
135 self.assertTrue(self._ParseType('...(Object|Array)').IsVarArgsType()) | |
136 self.assertFalse(self._ParseType('Object|...Array').IsVarArgsType()) | |
137 self.assertFalse(self._ParseType('(...Object|Array)').IsVarArgsType()) | |
138 | |
139 def testIsUnknownType(self): | |
140 self.assertTrue(self._ParseType('?').IsUnknownType()) | |
141 self.assertTrue(self._ParseType('Foo.<?>').sub_types[0].IsUnknownType()) | |
142 self.assertFalse(self._ParseType('?|!').IsUnknownType()) | |
143 self.assertTrue(self._ParseType('?|!').sub_types[0].IsUnknownType()) | |
144 self.assertFalse(self._ParseType('!').IsUnknownType()) | |
145 | |
146 long_type = 'function():?|{handleEvent:function(?=):?,sample:?}|?=' | |
147 record = self._ParseType(long_type) | |
148 # First check that there's not just one type with 3 return types, but three | |
149 # top-level types. | |
150 self.assertEquals(3, len(record.sub_types)) | |
151 | |
152 # Now extract all unknown type instances and verify that they really are. | |
153 handle_event, sample = record.sub_types[1].sub_types | |
154 for i, sub_type in enumerate([ | |
155 record.sub_types[0].return_type, | |
156 handle_event.return_type, | |
157 handle_event.sub_types[0], | |
158 sample, | |
159 record.sub_types[2]]): | |
160 self.assertTrue(sub_type.IsUnknownType(), | |
161 'Type %d should be the unknown type: %s\n%s' % ( | |
162 i, sub_type.tokens, record.Dump())) | |
163 | |
164 def testTypedefNames(self): | |
165 easy = self._ParseType('{a}') | |
166 self.assertTrue(easy.record_type) | |
167 | |
168 easy = self.assertProperReconstruction('{a}', '{a:}').sub_types[0] | |
169 self.assertEquals('a', easy.key_type.identifier) | |
170 self.assertEquals('', easy.identifier) | |
171 | |
172 easy = self.assertProperReconstruction('{a:b}').sub_types[0] | |
173 self.assertEquals('a', easy.key_type.identifier) | |
174 self.assertEquals('b', easy.identifier) | |
175 | |
176 def assertTypeError(self, type_str): | |
177 """Asserts that parsing the given type raises a linter error.""" | |
178 self.assertRaises(TypeErrorException, self._ParseType, type_str) | |
179 | |
180 def testParseBadTypes(self): | |
181 """Tests that several errors in types don't break the parser.""" | |
182 self.assertTypeError('<') | |
183 self.assertTypeError('>') | |
184 self.assertTypeError('Foo.<Bar') | |
185 self.assertTypeError('Foo.Bar>=') | |
186 self.assertTypeError('Foo.<Bar>>=') | |
187 self.assertTypeError('(') | |
188 self.assertTypeError(')') | |
189 self.assertTypeError('Foo.<Bar)>') | |
190 self._ParseType(':') | |
191 self._ParseType(':foo') | |
192 self.assertTypeError(':)foo') | |
193 self.assertTypeError('(a|{b:(c|function(new:d):e') | |
194 | |
195 def testNullable(self): | |
196 self.assertNullable('null') | |
197 self.assertNullable('Object') | |
198 self.assertNullable('?string') | |
199 self.assertNullable('?number') | |
200 | |
201 self.assertNotNullable('string') | |
202 self.assertNotNullable('number') | |
203 self.assertNotNullable('boolean') | |
204 self.assertNotNullable('function(Object)') | |
205 self.assertNotNullable('function(Object):Object') | |
206 self.assertNotNullable('function(?Object):?Object') | |
207 self.assertNotNullable('!Object') | |
208 | |
209 self.assertNotNullable('boolean|string') | |
210 self.assertNotNullable('(boolean|string)') | |
211 | |
212 self.assertNullable('(boolean|string|null)') | |
213 self.assertNullable('(?boolean)') | |
214 self.assertNullable('?(boolean)') | |
215 | |
216 self.assertNullable('(boolean|Object)') | |
217 self.assertNotNullable('(boolean|(string|{a:}))') | |
218 | |
219 def testSpaces(self): | |
220 """Tests that spaces don't change the outcome.""" | |
221 type_str = (' A < b | ( c | ? ! d e f ) > | ' | |
222 'function ( x : . . . ) : { y : z = } ') | |
223 two_spaces = type_str.replace(' ', ' ') | |
224 no_spaces = type_str.replace(' ', '') | |
225 newlines = type_str.replace(' ', '\n * ') | |
226 self.assertProperReconstruction(no_spaces) | |
227 self.assertProperReconstruction(type_str, no_spaces) | |
228 self.assertProperReconstruction(two_spaces, no_spaces) | |
229 self.assertProperReconstruction(newlines, no_spaces) | |
230 | |
231 if __name__ == '__main__': | |
232 googletest.main() | |
233 | |
OLD | NEW |