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 |