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

Unified Diff: third_party/closure_linter/closure_linter/typeannotation.py

Issue 2328693002: Updated linter with upstream release (2.3.19) (Closed)
Patch Set: Created 4 years, 3 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 side-by-side diff with in-line comments
Download patch
Index: third_party/closure_linter/closure_linter/typeannotation.py
diff --git a/third_party/closure_linter/closure_linter/typeannotation.py b/third_party/closure_linter/closure_linter/typeannotation.py
new file mode 100644
index 0000000000000000000000000000000000000000..e8e270de241b968c5eabed5c1394499d21fe3400
--- /dev/null
+++ b/third_party/closure_linter/closure_linter/typeannotation.py
@@ -0,0 +1,410 @@
+#!/usr/bin/env python
+#*-* coding: utf-8
+"""Closure typeannotation parsing and utilities."""
+
+
+
+from closure_linter import errors
+from closure_linter import javascripttokens
+from closure_linter.common import error
+
+# Shorthand
+TYPE = javascripttokens.JavaScriptTokenType
+
+
+class TypeAnnotation(object):
+ """Represents a structured view of a closure type annotation.
+
+ Attribute:
+ identifier: The name of the type.
+ key_type: The name part before a colon.
+ sub_types: The list of sub_types used e.g. for Array.<…>
+ or_null: The '?' annotation
+ not_null: The '!' annotation
+ type_group: If this a a grouping (a|b), but does not include function(a).
+ return_type: The return type of a function definition.
+ alias: The actual type set by closurizednamespaceinfo if the identifier uses
+ an alias to shorten the name.
+ tokens: An ordered list of tokens used for this type. May contain
+ TypeAnnotation instances for sub_types, key_type or return_type.
+ """
+
+ IMPLICIT_TYPE_GROUP = 2
+
+ NULLABILITY_UNKNOWN = 2
+
+ FUNCTION_TYPE = 'function'
+ NULL_TYPE = 'null'
+ VAR_ARGS_TYPE = '...'
+
+ # Frequently used known non-nullable types.
+ NON_NULLABLE = frozenset([
+ 'boolean', FUNCTION_TYPE, 'number', 'string', 'undefined'])
+ # Frequently used known nullable types.
+ NULLABLE_TYPE_WHITELIST = frozenset([
+ 'Array', 'Document', 'Element', 'Function', 'Node', 'NodeList',
+ 'Object'])
+
+ def __init__(self):
+ self.identifier = ''
+ self.sub_types = []
+ self.or_null = False
+ self.not_null = False
+ self.type_group = False
+ self.alias = None
+ self.key_type = None
+ self.record_type = False
+ self.opt_arg = False
+ self.return_type = None
+ self.tokens = []
+
+ def IsFunction(self):
+ """Determines whether this is a function definition."""
+ return self.identifier == TypeAnnotation.FUNCTION_TYPE
+
+ def IsConstructor(self):
+ """Determines whether this is a function definition for a constructor."""
+ key_type = self.sub_types and self.sub_types[0].key_type
+ return self.IsFunction() and key_type.identifier == 'new'
+
+ def IsRecordType(self):
+ """Returns True if this type is a record type."""
+ return (self.record_type or
+ any(t.IsRecordType() for t in self.sub_types))
+
+ def IsVarArgsType(self):
+ """Determines if the type is a var_args type, i.e. starts with '...'."""
+ return self.identifier.startswith(TypeAnnotation.VAR_ARGS_TYPE) or (
+ self.type_group == TypeAnnotation.IMPLICIT_TYPE_GROUP and
+ self.sub_types[0].identifier.startswith(TypeAnnotation.VAR_ARGS_TYPE))
+
+ def IsEmpty(self):
+ """Returns True if the type is empty."""
+ return not self.tokens
+
+ def IsUnknownType(self):
+ """Returns True if this is the unknown type {?}."""
+ return (self.or_null
+ and not self.identifier
+ and not self.sub_types
+ and not self.return_type)
+
+ def Append(self, item):
+ """Adds a sub_type to this type and finalizes it.
+
+ Args:
+ item: The TypeAnnotation item to append.
+ """
+ # item is a TypeAnnotation instance, so pylint: disable=protected-access
+ self.sub_types.append(item._Finalize(self))
+
+ def __repr__(self):
+ """Reconstructs the type definition."""
+ append = ''
+ if self.sub_types:
+ separator = (',' if not self.type_group else '|')
+ if self.IsFunction():
+ surround = '(%s)'
+ else:
+ surround = {False: '{%s}' if self.record_type else '<%s>',
+ True: '(%s)',
+ TypeAnnotation.IMPLICIT_TYPE_GROUP: '%s'}[self.type_group]
+ append = surround % separator.join(repr(t) for t in self.sub_types)
+ if self.return_type:
+ append += ':%s' % repr(self.return_type)
+ append += '=' if self.opt_arg else ''
+ prefix = '' + ('?' if self.or_null else '') + ('!' if self.not_null else '')
+ keyword = '%s:' % repr(self.key_type) if self.key_type else ''
+ return keyword + prefix + '%s' % (self.alias or self.identifier) + append
+
+ def ToString(self):
+ """Concats the type's tokens to form a string again."""
+ ret = []
+ for token in self.tokens:
+ if not isinstance(token, TypeAnnotation):
+ ret.append(token.string)
+ else:
+ ret.append(token.ToString())
+ return ''.join(ret)
+
+ def Dump(self, indent=''):
+ """Dumps this type's structure for debugging purposes."""
+ result = []
+ for t in self.tokens:
+ if isinstance(t, TypeAnnotation):
+ result.append(indent + str(t) + ' =>\n' + t.Dump(indent + ' '))
+ else:
+ result.append(indent + str(t))
+ return '\n'.join(result)
+
+ def IterIdentifiers(self):
+ """Iterates over all identifiers in this type and its subtypes."""
+ if self.identifier:
+ yield self.identifier
+ for subtype in self.IterTypes():
+ for identifier in subtype.IterIdentifiers():
+ yield identifier
+
+ def IterTypeGroup(self):
+ """Iterates over all types in the type group including self.
+
+ Yields:
+ If this is a implicit or manual type-group: all sub_types.
+ Otherwise: self
+ E.g. for @type {Foo.<Bar>} this will yield only Foo.<Bar>,
+ for @type {Foo|(Bar|Sample)} this will yield Foo, Bar and Sample.
+
+ """
+ if self.type_group:
+ for sub_type in self.sub_types:
+ for sub_type in sub_type.IterTypeGroup():
+ yield sub_type
+ else:
+ yield self
+
+ def IterTypes(self):
+ """Iterates over each subtype as well as return and key types."""
+ if self.return_type:
+ yield self.return_type
+
+ if self.key_type:
+ yield self.key_type
+
+ for sub_type in self.sub_types:
+ yield sub_type
+
+ def GetNullability(self, modifiers=True):
+ """Computes whether the type may be null.
+
+ Args:
+ modifiers: Whether the modifiers ? and ! should be considered in the
+ evaluation.
+ Returns:
+ True if the type allows null, False if the type is strictly non nullable
+ and NULLABILITY_UNKNOWN if the nullability cannot be determined.
+ """
+
+ # Explicitly marked nullable types or 'null' are nullable.
+ if ((modifiers and self.or_null) or
+ self.identifier == TypeAnnotation.NULL_TYPE):
+ return True
+
+ # Explicitly marked non-nullable types or non-nullable base types:
+ if ((modifiers and self.not_null) or self.record_type
+ or self.identifier in TypeAnnotation.NON_NULLABLE):
+ return False
+
+ # A type group is nullable if any of its elements are nullable.
+ if self.type_group:
+ maybe_nullable = False
+ for sub_type in self.sub_types:
+ nullability = sub_type.GetNullability()
+ if nullability == self.NULLABILITY_UNKNOWN:
+ maybe_nullable = nullability
+ elif nullability:
+ return True
+ return maybe_nullable
+
+ # Whitelisted types are nullable.
+ if self.identifier.rstrip('.') in TypeAnnotation.NULLABLE_TYPE_WHITELIST:
+ return True
+
+ # All other types are unknown (most should be nullable, but
+ # enums are not and typedefs might not be).
+ return TypeAnnotation.NULLABILITY_UNKNOWN
+
+ def WillAlwaysBeNullable(self):
+ """Computes whether the ! flag is illegal for this type.
+
+ This is the case if this type or any of the subtypes is marked as
+ explicitly nullable.
+
+ Returns:
+ True if the ! flag would be illegal.
+ """
+ if self.or_null or self.identifier == TypeAnnotation.NULL_TYPE:
+ return True
+
+ if self.type_group:
+ return any(t.WillAlwaysBeNullable() for t in self.sub_types)
+
+ return False
+
+ def _Finalize(self, parent):
+ """Fixes some parsing issues once the TypeAnnotation is complete."""
+
+ # Normalize functions whose definition ended up in the key type because
+ # they defined a return type after a colon.
+ if (self.key_type and
+ self.key_type.identifier == TypeAnnotation.FUNCTION_TYPE):
+ current = self.key_type
+ current.return_type = self
+ self.key_type = None
+ # opt_arg never refers to the return type but to the function itself.
+ current.opt_arg = self.opt_arg
+ self.opt_arg = False
+ return current
+
+ # If a typedef just specified the key, it will not end up in the key type.
+ if parent.record_type and not self.key_type:
+ current = TypeAnnotation()
+ current.key_type = self
+ current.tokens.append(self)
+ return current
+ return self
+
+ def FirstToken(self):
+ """Returns the first token used in this type or any of its subtypes."""
+ first = self.tokens[0]
+ return first.FirstToken() if isinstance(first, TypeAnnotation) else first
+
+
+def Parse(token, token_end, error_handler):
+ """Parses a type annotation and returns a TypeAnnotation object."""
+ return TypeAnnotationParser(error_handler).Parse(token.next, token_end)
+
+
+class TypeAnnotationParser(object):
+ """A parser for type annotations constructing the TypeAnnotation object."""
+
+ def __init__(self, error_handler):
+ self._stack = []
+ self._error_handler = error_handler
+ self._closing_error = False
+
+ def Parse(self, token, token_end):
+ """Parses a type annotation and returns a TypeAnnotation object."""
+ root = TypeAnnotation()
+ self._stack.append(root)
+ current = TypeAnnotation()
+ root.tokens.append(current)
+
+ while token and token != token_end:
+ if token.type in (TYPE.DOC_TYPE_START_BLOCK, TYPE.DOC_START_BRACE):
+ if token.string == '(':
+ if current.identifier and current.identifier not in [
+ TypeAnnotation.FUNCTION_TYPE, TypeAnnotation.VAR_ARGS_TYPE]:
+ self.Error(token,
+ 'Invalid identifier for (): "%s"' % current.identifier)
+ current.type_group = (
+ current.identifier != TypeAnnotation.FUNCTION_TYPE)
+ elif token.string == '{':
+ current.record_type = True
+ current.tokens.append(token)
+ self._stack.append(current)
+ current = TypeAnnotation()
+ self._stack[-1].tokens.append(current)
+
+ elif token.type in (TYPE.DOC_TYPE_END_BLOCK, TYPE.DOC_END_BRACE):
+ prev = self._stack.pop()
+ prev.Append(current)
+ current = prev
+
+ # If an implicit type group was created, close it as well.
+ if prev.type_group == TypeAnnotation.IMPLICIT_TYPE_GROUP:
+ prev = self._stack.pop()
+ prev.Append(current)
+ current = prev
+ current.tokens.append(token)
+
+ elif token.type == TYPE.DOC_TYPE_MODIFIER:
+ if token.string == '!':
+ current.tokens.append(token)
+ current.not_null = True
+ elif token.string == '?':
+ current.tokens.append(token)
+ current.or_null = True
+ elif token.string == ':':
+ current.tokens.append(token)
+ prev = current
+ current = TypeAnnotation()
+ prev.tokens.append(current)
+ current.key_type = prev
+ elif token.string == '=':
+ # For implicit type groups the '=' refers to the parent.
+ try:
+ if self._stack[-1].type_group == TypeAnnotation.IMPLICIT_TYPE_GROUP:
+ self._stack[-1].tokens.append(token)
+ self._stack[-1].opt_arg = True
+ else:
+ current.tokens.append(token)
+ current.opt_arg = True
+ except IndexError:
+ self.ClosingError(token)
+ elif token.string == '|':
+ # If a type group has explicitly been opened, do a normal append.
+ # Otherwise we have to open the type group and move the current
+ # type into it, before appending
+ if not self._stack[-1].type_group:
+ type_group = TypeAnnotation()
+ if (current.key_type and
+ current.key_type.identifier != TypeAnnotation.FUNCTION_TYPE):
+ type_group.key_type = current.key_type
+ current.key_type = None
+ type_group.type_group = TypeAnnotation.IMPLICIT_TYPE_GROUP
+ # Fix the token order
+ prev = self._stack[-1].tokens.pop()
+ self._stack[-1].tokens.append(type_group)
+ type_group.tokens.append(prev)
+ self._stack.append(type_group)
+ self._stack[-1].tokens.append(token)
+ self.Append(current, error_token=token)
+ current = TypeAnnotation()
+ self._stack[-1].tokens.append(current)
+ elif token.string == ',':
+ self.Append(current, error_token=token)
+ current = TypeAnnotation()
+ self._stack[-1].tokens.append(token)
+ self._stack[-1].tokens.append(current)
+ else:
+ current.tokens.append(token)
+ self.Error(token, 'Invalid token')
+
+ elif token.type == TYPE.COMMENT:
+ current.tokens.append(token)
+ current.identifier += token.string.strip()
+
+ elif token.type in [TYPE.DOC_PREFIX, TYPE.WHITESPACE]:
+ current.tokens.append(token)
+
+ else:
+ current.tokens.append(token)
+ self.Error(token, 'Unexpected token')
+
+ token = token.next
+
+ self.Append(current, error_token=token)
+ try:
+ ret = self._stack.pop()
+ except IndexError:
+ self.ClosingError(token)
+ # The type is screwed up, but let's return something.
+ return current
+
+ if self._stack and (len(self._stack) != 1 or
+ ret.type_group != TypeAnnotation.IMPLICIT_TYPE_GROUP):
+ self.Error(token, 'Too many opening items.')
+
+ return ret if len(ret.sub_types) > 1 else ret.sub_types[0]
+
+ def Append(self, type_obj, error_token):
+ """Appends a new TypeAnnotation object to the current parent."""
+ if self._stack:
+ self._stack[-1].Append(type_obj)
+ else:
+ self.ClosingError(error_token)
+
+ def ClosingError(self, token):
+ """Reports an error about too many closing items, but only once."""
+ if not self._closing_error:
+ self._closing_error = True
+ self.Error(token, 'Too many closing items.')
+
+ def Error(self, token, message):
+ """Calls the error_handler to post an error message."""
+ if self._error_handler:
+ self._error_handler.HandleError(error.Error(
+ errors.JSDOC_DOES_NOT_PARSE,
+ 'Error parsing jsdoc type at token "%s" (column: %d): %s' %
+ (token.string, token.start_index, message), token))
+

Powered by Google App Engine
This is Rietveld 408576698