Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(382)

Side by Side Diff: third_party/closure_linter/closure_linter/typeannotation.py

Issue 2592193002: Remove closure_linter from Chrome (Closed)
Patch Set: Created 3 years, 12 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(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
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698