| OLD | NEW |
| (Empty) |
| 1 #!/usr/bin/env python | |
| 2 #*-* coding: utf-8 | |
| 3 """Closure typeannotation parsing and utilities.""" | |
| 4 | |
| 5 | |
| 6 | |
| 7 from closure_linter import errors | |
| 8 from closure_linter import javascripttokens | |
| 9 from closure_linter.common import error | |
| 10 | |
| 11 # Shorthand | |
| 12 TYPE = javascripttokens.JavaScriptTokenType | |
| 13 | |
| 14 | |
| 15 class TypeAnnotation(object): | |
| 16 """Represents a structured view of a closure type annotation. | |
| 17 | |
| 18 Attribute: | |
| 19 identifier: The name of the type. | |
| 20 key_type: The name part before a colon. | |
| 21 sub_types: The list of sub_types used e.g. for Array.<…> | |
| 22 or_null: The '?' annotation | |
| 23 not_null: The '!' annotation | |
| 24 type_group: If this a a grouping (a|b), but does not include function(a). | |
| 25 return_type: The return type of a function definition. | |
| 26 alias: The actual type set by closurizednamespaceinfo if the identifier uses | |
| 27 an alias to shorten the name. | |
| 28 tokens: An ordered list of tokens used for this type. May contain | |
| 29 TypeAnnotation instances for sub_types, key_type or return_type. | |
| 30 """ | |
| 31 | |
| 32 IMPLICIT_TYPE_GROUP = 2 | |
| 33 | |
| 34 NULLABILITY_UNKNOWN = 2 | |
| 35 | |
| 36 FUNCTION_TYPE = 'function' | |
| 37 NULL_TYPE = 'null' | |
| 38 VAR_ARGS_TYPE = '...' | |
| 39 | |
| 40 # Frequently used known non-nullable types. | |
| 41 NON_NULLABLE = frozenset([ | |
| 42 'boolean', FUNCTION_TYPE, 'number', 'string', 'undefined']) | |
| 43 # Frequently used known nullable types. | |
| 44 NULLABLE_TYPE_WHITELIST = frozenset([ | |
| 45 'Array', 'Document', 'Element', 'Function', 'Node', 'NodeList', | |
| 46 'Object']) | |
| 47 | |
| 48 def __init__(self): | |
| 49 self.identifier = '' | |
| 50 self.sub_types = [] | |
| 51 self.or_null = False | |
| 52 self.not_null = False | |
| 53 self.type_group = False | |
| 54 self.alias = None | |
| 55 self.key_type = None | |
| 56 self.record_type = False | |
| 57 self.opt_arg = False | |
| 58 self.return_type = None | |
| 59 self.tokens = [] | |
| 60 | |
| 61 def IsFunction(self): | |
| 62 """Determines whether this is a function definition.""" | |
| 63 return self.identifier == TypeAnnotation.FUNCTION_TYPE | |
| 64 | |
| 65 def IsConstructor(self): | |
| 66 """Determines whether this is a function definition for a constructor.""" | |
| 67 key_type = self.sub_types and self.sub_types[0].key_type | |
| 68 return self.IsFunction() and key_type.identifier == 'new' | |
| 69 | |
| 70 def IsRecordType(self): | |
| 71 """Returns True if this type is a record type.""" | |
| 72 return (self.record_type or | |
| 73 any(t.IsRecordType() for t in self.sub_types)) | |
| 74 | |
| 75 def IsVarArgsType(self): | |
| 76 """Determines if the type is a var_args type, i.e. starts with '...'.""" | |
| 77 return self.identifier.startswith(TypeAnnotation.VAR_ARGS_TYPE) or ( | |
| 78 self.type_group == TypeAnnotation.IMPLICIT_TYPE_GROUP and | |
| 79 self.sub_types[0].identifier.startswith(TypeAnnotation.VAR_ARGS_TYPE)) | |
| 80 | |
| 81 def IsEmpty(self): | |
| 82 """Returns True if the type is empty.""" | |
| 83 return not self.tokens | |
| 84 | |
| 85 def IsUnknownType(self): | |
| 86 """Returns True if this is the unknown type {?}.""" | |
| 87 return (self.or_null | |
| 88 and not self.identifier | |
| 89 and not self.sub_types | |
| 90 and not self.return_type) | |
| 91 | |
| 92 def Append(self, item): | |
| 93 """Adds a sub_type to this type and finalizes it. | |
| 94 | |
| 95 Args: | |
| 96 item: The TypeAnnotation item to append. | |
| 97 """ | |
| 98 # item is a TypeAnnotation instance, so pylint: disable=protected-access | |
| 99 self.sub_types.append(item._Finalize(self)) | |
| 100 | |
| 101 def __repr__(self): | |
| 102 """Reconstructs the type definition.""" | |
| 103 append = '' | |
| 104 if self.sub_types: | |
| 105 separator = (',' if not self.type_group else '|') | |
| 106 if self.IsFunction(): | |
| 107 surround = '(%s)' | |
| 108 else: | |
| 109 surround = {False: '{%s}' if self.record_type else '<%s>', | |
| 110 True: '(%s)', | |
| 111 TypeAnnotation.IMPLICIT_TYPE_GROUP: '%s'}[self.type_group] | |
| 112 append = surround % separator.join(repr(t) for t in self.sub_types) | |
| 113 if self.return_type: | |
| 114 append += ':%s' % repr(self.return_type) | |
| 115 append += '=' if self.opt_arg else '' | |
| 116 prefix = '' + ('?' if self.or_null else '') + ('!' if self.not_null else '') | |
| 117 keyword = '%s:' % repr(self.key_type) if self.key_type else '' | |
| 118 return keyword + prefix + '%s' % (self.alias or self.identifier) + append | |
| 119 | |
| 120 def ToString(self): | |
| 121 """Concats the type's tokens to form a string again.""" | |
| 122 ret = [] | |
| 123 for token in self.tokens: | |
| 124 if not isinstance(token, TypeAnnotation): | |
| 125 ret.append(token.string) | |
| 126 else: | |
| 127 ret.append(token.ToString()) | |
| 128 return ''.join(ret) | |
| 129 | |
| 130 def Dump(self, indent=''): | |
| 131 """Dumps this type's structure for debugging purposes.""" | |
| 132 result = [] | |
| 133 for t in self.tokens: | |
| 134 if isinstance(t, TypeAnnotation): | |
| 135 result.append(indent + str(t) + ' =>\n' + t.Dump(indent + ' ')) | |
| 136 else: | |
| 137 result.append(indent + str(t)) | |
| 138 return '\n'.join(result) | |
| 139 | |
| 140 def IterIdentifiers(self): | |
| 141 """Iterates over all identifiers in this type and its subtypes.""" | |
| 142 if self.identifier: | |
| 143 yield self.identifier | |
| 144 for subtype in self.IterTypes(): | |
| 145 for identifier in subtype.IterIdentifiers(): | |
| 146 yield identifier | |
| 147 | |
| 148 def IterTypeGroup(self): | |
| 149 """Iterates over all types in the type group including self. | |
| 150 | |
| 151 Yields: | |
| 152 If this is a implicit or manual type-group: all sub_types. | |
| 153 Otherwise: self | |
| 154 E.g. for @type {Foo.<Bar>} this will yield only Foo.<Bar>, | |
| 155 for @type {Foo|(Bar|Sample)} this will yield Foo, Bar and Sample. | |
| 156 | |
| 157 """ | |
| 158 if self.type_group: | |
| 159 for sub_type in self.sub_types: | |
| 160 for sub_type in sub_type.IterTypeGroup(): | |
| 161 yield sub_type | |
| 162 else: | |
| 163 yield self | |
| 164 | |
| 165 def IterTypes(self): | |
| 166 """Iterates over each subtype as well as return and key types.""" | |
| 167 if self.return_type: | |
| 168 yield self.return_type | |
| 169 | |
| 170 if self.key_type: | |
| 171 yield self.key_type | |
| 172 | |
| 173 for sub_type in self.sub_types: | |
| 174 yield sub_type | |
| 175 | |
| 176 def GetNullability(self, modifiers=True): | |
| 177 """Computes whether the type may be null. | |
| 178 | |
| 179 Args: | |
| 180 modifiers: Whether the modifiers ? and ! should be considered in the | |
| 181 evaluation. | |
| 182 Returns: | |
| 183 True if the type allows null, False if the type is strictly non nullable | |
| 184 and NULLABILITY_UNKNOWN if the nullability cannot be determined. | |
| 185 """ | |
| 186 | |
| 187 # Explicitly marked nullable types or 'null' are nullable. | |
| 188 if ((modifiers and self.or_null) or | |
| 189 self.identifier == TypeAnnotation.NULL_TYPE): | |
| 190 return True | |
| 191 | |
| 192 # Explicitly marked non-nullable types or non-nullable base types: | |
| 193 if ((modifiers and self.not_null) or self.record_type | |
| 194 or self.identifier in TypeAnnotation.NON_NULLABLE): | |
| 195 return False | |
| 196 | |
| 197 # A type group is nullable if any of its elements are nullable. | |
| 198 if self.type_group: | |
| 199 maybe_nullable = False | |
| 200 for sub_type in self.sub_types: | |
| 201 nullability = sub_type.GetNullability() | |
| 202 if nullability == self.NULLABILITY_UNKNOWN: | |
| 203 maybe_nullable = nullability | |
| 204 elif nullability: | |
| 205 return True | |
| 206 return maybe_nullable | |
| 207 | |
| 208 # Whitelisted types are nullable. | |
| 209 if self.identifier.rstrip('.') in TypeAnnotation.NULLABLE_TYPE_WHITELIST: | |
| 210 return True | |
| 211 | |
| 212 # All other types are unknown (most should be nullable, but | |
| 213 # enums are not and typedefs might not be). | |
| 214 return TypeAnnotation.NULLABILITY_UNKNOWN | |
| 215 | |
| 216 def WillAlwaysBeNullable(self): | |
| 217 """Computes whether the ! flag is illegal for this type. | |
| 218 | |
| 219 This is the case if this type or any of the subtypes is marked as | |
| 220 explicitly nullable. | |
| 221 | |
| 222 Returns: | |
| 223 True if the ! flag would be illegal. | |
| 224 """ | |
| 225 if self.or_null or self.identifier == TypeAnnotation.NULL_TYPE: | |
| 226 return True | |
| 227 | |
| 228 if self.type_group: | |
| 229 return any(t.WillAlwaysBeNullable() for t in self.sub_types) | |
| 230 | |
| 231 return False | |
| 232 | |
| 233 def _Finalize(self, parent): | |
| 234 """Fixes some parsing issues once the TypeAnnotation is complete.""" | |
| 235 | |
| 236 # Normalize functions whose definition ended up in the key type because | |
| 237 # they defined a return type after a colon. | |
| 238 if (self.key_type and | |
| 239 self.key_type.identifier == TypeAnnotation.FUNCTION_TYPE): | |
| 240 current = self.key_type | |
| 241 current.return_type = self | |
| 242 self.key_type = None | |
| 243 # opt_arg never refers to the return type but to the function itself. | |
| 244 current.opt_arg = self.opt_arg | |
| 245 self.opt_arg = False | |
| 246 return current | |
| 247 | |
| 248 # If a typedef just specified the key, it will not end up in the key type. | |
| 249 if parent.record_type and not self.key_type: | |
| 250 current = TypeAnnotation() | |
| 251 current.key_type = self | |
| 252 current.tokens.append(self) | |
| 253 return current | |
| 254 return self | |
| 255 | |
| 256 def FirstToken(self): | |
| 257 """Returns the first token used in this type or any of its subtypes.""" | |
| 258 first = self.tokens[0] | |
| 259 return first.FirstToken() if isinstance(first, TypeAnnotation) else first | |
| 260 | |
| 261 | |
| 262 def Parse(token, token_end, error_handler): | |
| 263 """Parses a type annotation and returns a TypeAnnotation object.""" | |
| 264 return TypeAnnotationParser(error_handler).Parse(token.next, token_end) | |
| 265 | |
| 266 | |
| 267 class TypeAnnotationParser(object): | |
| 268 """A parser for type annotations constructing the TypeAnnotation object.""" | |
| 269 | |
| 270 def __init__(self, error_handler): | |
| 271 self._stack = [] | |
| 272 self._error_handler = error_handler | |
| 273 self._closing_error = False | |
| 274 | |
| 275 def Parse(self, token, token_end): | |
| 276 """Parses a type annotation and returns a TypeAnnotation object.""" | |
| 277 root = TypeAnnotation() | |
| 278 self._stack.append(root) | |
| 279 current = TypeAnnotation() | |
| 280 root.tokens.append(current) | |
| 281 | |
| 282 while token and token != token_end: | |
| 283 if token.type in (TYPE.DOC_TYPE_START_BLOCK, TYPE.DOC_START_BRACE): | |
| 284 if token.string == '(': | |
| 285 if current.identifier and current.identifier not in [ | |
| 286 TypeAnnotation.FUNCTION_TYPE, TypeAnnotation.VAR_ARGS_TYPE]: | |
| 287 self.Error(token, | |
| 288 'Invalid identifier for (): "%s"' % current.identifier) | |
| 289 current.type_group = ( | |
| 290 current.identifier != TypeAnnotation.FUNCTION_TYPE) | |
| 291 elif token.string == '{': | |
| 292 current.record_type = True | |
| 293 current.tokens.append(token) | |
| 294 self._stack.append(current) | |
| 295 current = TypeAnnotation() | |
| 296 self._stack[-1].tokens.append(current) | |
| 297 | |
| 298 elif token.type in (TYPE.DOC_TYPE_END_BLOCK, TYPE.DOC_END_BRACE): | |
| 299 prev = self._stack.pop() | |
| 300 prev.Append(current) | |
| 301 current = prev | |
| 302 | |
| 303 # If an implicit type group was created, close it as well. | |
| 304 if prev.type_group == TypeAnnotation.IMPLICIT_TYPE_GROUP: | |
| 305 prev = self._stack.pop() | |
| 306 prev.Append(current) | |
| 307 current = prev | |
| 308 current.tokens.append(token) | |
| 309 | |
| 310 elif token.type == TYPE.DOC_TYPE_MODIFIER: | |
| 311 if token.string == '!': | |
| 312 current.tokens.append(token) | |
| 313 current.not_null = True | |
| 314 elif token.string == '?': | |
| 315 current.tokens.append(token) | |
| 316 current.or_null = True | |
| 317 elif token.string == ':': | |
| 318 current.tokens.append(token) | |
| 319 prev = current | |
| 320 current = TypeAnnotation() | |
| 321 prev.tokens.append(current) | |
| 322 current.key_type = prev | |
| 323 elif token.string == '=': | |
| 324 # For implicit type groups the '=' refers to the parent. | |
| 325 try: | |
| 326 if self._stack[-1].type_group == TypeAnnotation.IMPLICIT_TYPE_GROUP: | |
| 327 self._stack[-1].tokens.append(token) | |
| 328 self._stack[-1].opt_arg = True | |
| 329 else: | |
| 330 current.tokens.append(token) | |
| 331 current.opt_arg = True | |
| 332 except IndexError: | |
| 333 self.ClosingError(token) | |
| 334 elif token.string == '|': | |
| 335 # If a type group has explicitly been opened, do a normal append. | |
| 336 # Otherwise we have to open the type group and move the current | |
| 337 # type into it, before appending | |
| 338 if not self._stack[-1].type_group: | |
| 339 type_group = TypeAnnotation() | |
| 340 if (current.key_type and | |
| 341 current.key_type.identifier != TypeAnnotation.FUNCTION_TYPE): | |
| 342 type_group.key_type = current.key_type | |
| 343 current.key_type = None | |
| 344 type_group.type_group = TypeAnnotation.IMPLICIT_TYPE_GROUP | |
| 345 # Fix the token order | |
| 346 prev = self._stack[-1].tokens.pop() | |
| 347 self._stack[-1].tokens.append(type_group) | |
| 348 type_group.tokens.append(prev) | |
| 349 self._stack.append(type_group) | |
| 350 self._stack[-1].tokens.append(token) | |
| 351 self.Append(current, error_token=token) | |
| 352 current = TypeAnnotation() | |
| 353 self._stack[-1].tokens.append(current) | |
| 354 elif token.string == ',': | |
| 355 self.Append(current, error_token=token) | |
| 356 current = TypeAnnotation() | |
| 357 self._stack[-1].tokens.append(token) | |
| 358 self._stack[-1].tokens.append(current) | |
| 359 else: | |
| 360 current.tokens.append(token) | |
| 361 self.Error(token, 'Invalid token') | |
| 362 | |
| 363 elif token.type == TYPE.COMMENT: | |
| 364 current.tokens.append(token) | |
| 365 current.identifier += token.string.strip() | |
| 366 | |
| 367 elif token.type in [TYPE.DOC_PREFIX, TYPE.WHITESPACE]: | |
| 368 current.tokens.append(token) | |
| 369 | |
| 370 else: | |
| 371 current.tokens.append(token) | |
| 372 self.Error(token, 'Unexpected token') | |
| 373 | |
| 374 token = token.next | |
| 375 | |
| 376 self.Append(current, error_token=token) | |
| 377 try: | |
| 378 ret = self._stack.pop() | |
| 379 except IndexError: | |
| 380 self.ClosingError(token) | |
| 381 # The type is screwed up, but let's return something. | |
| 382 return current | |
| 383 | |
| 384 if self._stack and (len(self._stack) != 1 or | |
| 385 ret.type_group != TypeAnnotation.IMPLICIT_TYPE_GROUP): | |
| 386 self.Error(token, 'Too many opening items.') | |
| 387 | |
| 388 return ret if len(ret.sub_types) > 1 else ret.sub_types[0] | |
| 389 | |
| 390 def Append(self, type_obj, error_token): | |
| 391 """Appends a new TypeAnnotation object to the current parent.""" | |
| 392 if self._stack: | |
| 393 self._stack[-1].Append(type_obj) | |
| 394 else: | |
| 395 self.ClosingError(error_token) | |
| 396 | |
| 397 def ClosingError(self, token): | |
| 398 """Reports an error about too many closing items, but only once.""" | |
| 399 if not self._closing_error: | |
| 400 self._closing_error = True | |
| 401 self.Error(token, 'Too many closing items.') | |
| 402 | |
| 403 def Error(self, token, message): | |
| 404 """Calls the error_handler to post an error message.""" | |
| 405 if self._error_handler: | |
| 406 self._error_handler.HandleError(error.Error( | |
| 407 errors.JSDOC_DOES_NOT_PARSE, | |
| 408 'Error parsing jsdoc type at token "%s" (column: %d): %s' % | |
| 409 (token.string, token.start_index, message), token)) | |
| 410 | |
| OLD | NEW |