| 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 |