Index: third_party/closure_linter/closure_linter/statetracker.py |
diff --git a/third_party/closure_linter/closure_linter/statetracker.py b/third_party/closure_linter/closure_linter/statetracker.py |
index ba25dfc5cf916de33d99ca14cc4c86b75bf088f3..5730facb87dc7ea2d89b423167e5f1a799884b02 100755 |
--- a/third_party/closure_linter/closure_linter/statetracker.py |
+++ b/third_party/closure_linter/closure_linter/statetracker.py |
@@ -24,6 +24,7 @@ import re |
from closure_linter import javascripttokenizer |
from closure_linter import javascripttokens |
from closure_linter import tokenutil |
+from closure_linter import typeannotation |
# Shorthand |
Type = javascripttokens.JavaScriptTokenType |
@@ -39,7 +40,8 @@ class DocFlag(object): |
including braces. |
type_end_token: The last token specifying the flag type, |
including braces. |
- type: The type spec. |
+ type: The type spec string. |
+ jstype: The type spec, a TypeAnnotation instance. |
name_token: The token specifying the flag name. |
name: The flag name |
description_start_token: The first token in the description. |
@@ -78,8 +80,10 @@ class DocFlag(object): |
'nosideeffects', |
'override', |
'owner', |
+ 'nocollapse', |
'package', |
'param', |
+ 'polymerBehavior', # This annotation is specific to Polymer. |
'preserve', |
'private', |
'protected', |
@@ -111,6 +115,7 @@ class DocFlag(object): |
SUPPRESS_TYPES = frozenset([ |
'accessControls', |
'ambiguousFunctionDecl', |
+ 'checkDebuggerStatement', |
'checkRegExp', |
'checkStructDictInheritance', |
'checkTypes', |
@@ -132,6 +137,7 @@ class DocFlag(object): |
'missingRequire', |
'missingReturn', |
'nonStandardJsDocs', |
+ 'reportUnknownTypes', |
'strictModuleDepCheck', |
'suspiciousCode', |
'tweakValidation', |
@@ -144,38 +150,86 @@ class DocFlag(object): |
'unusedPrivateMembers', |
'uselessCode', |
'visibility', |
- 'with']) |
+ 'with', |
+ ]) |
HAS_DESCRIPTION = frozenset([ |
- 'define', 'deprecated', 'desc', 'fileoverview', 'license', 'param', |
- 'preserve', 'return', 'supported']) |
+ 'define', |
+ 'deprecated', |
+ 'desc', |
+ 'fileoverview', |
+ 'license', |
+ 'param', |
+ 'preserve', |
+ 'return', |
+ 'supported', |
+ ]) |
+ # Docflags whose argument should be parsed using the typeannotation parser. |
HAS_TYPE = frozenset([ |
- 'define', 'enum', 'extends', 'implements', 'param', 'return', 'type', |
- 'suppress', 'const', 'package', 'private', 'protected', 'public']) |
+ 'const', |
+ 'define', |
+ 'enum', |
+ 'export', |
+ 'extends', |
+ 'final', |
+ 'implements', |
+ 'mods', |
+ 'package', |
+ 'param', |
+ 'private', |
+ 'protected', |
+ 'public', |
+ 'return', |
+ 'suppress', |
+ 'type', |
+ 'typedef', |
+ ]) |
- CAN_OMIT_TYPE = frozenset(['enum', 'const', 'package', 'private', |
- 'protected', 'public']) |
+ # Docflags for which it's ok to omit the type (flag without an argument). |
+ CAN_OMIT_TYPE = frozenset([ |
+ 'const', |
+ 'enum', |
+ 'export', |
+ 'final', |
+ 'package', |
+ 'private', |
+ 'protected', |
+ 'public', |
+ 'suppress', # We'll raise a separate INCORRECT_SUPPRESS_SYNTAX instead. |
+ ]) |
- TYPE_ONLY = frozenset(['enum', 'extends', 'implements', 'suppress', 'type', |
- 'const', 'package', 'private', 'protected', 'public']) |
+ # Docflags that only take a type as an argument and should not parse a |
+ # following description. |
+ TYPE_ONLY = frozenset([ |
+ 'const', |
+ 'enum', |
+ 'extends', |
+ 'implements', |
+ 'package', |
+ 'suppress', |
+ 'type', |
+ ]) |
HAS_NAME = frozenset(['param']) |
EMPTY_COMMENT_LINE = re.compile(r'^\s*\*?\s*$') |
EMPTY_STRING = re.compile(r'^\s*$') |
- def __init__(self, flag_token): |
+ def __init__(self, flag_token, error_handler=None): |
"""Creates the DocFlag object and attaches it to the given start token. |
Args: |
flag_token: The starting token of the flag. |
+ error_handler: An optional error handler for errors occurring while |
+ parsing the doctype. |
""" |
self.flag_token = flag_token |
self.flag_type = flag_token.string.strip().lstrip('@') |
# Extract type, if applicable. |
self.type = None |
+ self.jstype = None |
self.type_start_token = None |
self.type_end_token = None |
if self.flag_type in self.HAS_TYPE: |
@@ -184,6 +238,8 @@ class DocFlag(object): |
if brace: |
end_token, contents = _GetMatchingEndBraceAndContents(brace) |
self.type = contents |
+ self.jstype = typeannotation.Parse(brace, end_token, |
+ error_handler) |
self.type_start_token = brace |
self.type_end_token = end_token |
elif (self.flag_type in self.TYPE_ONLY and |
@@ -197,6 +253,8 @@ class DocFlag(object): |
self.type_start_token) |
if self.type is not None: |
self.type = self.type.strip() |
+ self.jstype = typeannotation.Parse(flag_token, self.type_end_token, |
+ error_handler) |
# Extract name, if applicable. |
self.name_token = None |
@@ -238,6 +296,13 @@ class DocFlag(object): |
self.description_end_token, self.description = ( |
_GetEndTokenAndContents(interesting_token)) |
+ def HasType(self): |
+ """Returns whether this flag should have a type annotation.""" |
+ return self.flag_type in self.HAS_TYPE |
+ |
+ def __repr__(self): |
+ return '<Flag: %s, type:%s>' % (self.flag_type, repr(self.jstype)) |
+ |
class DocComment(object): |
"""JavaScript doc comment object. |
@@ -288,12 +353,9 @@ class DocComment(object): |
Args: |
token: The suppression flag token. |
""" |
- #TODO(user): Error if no braces |
- brace = tokenutil.SearchUntil(token, [Type.DOC_START_BRACE], |
- [Type.DOC_FLAG]) |
- if brace: |
- end_token, contents = _GetMatchingEndBraceAndContents(brace) |
- for suppression in contents.split('|'): |
+ flag = token and token.attached_object |
+ if flag and flag.jstype: |
+ for suppression in flag.jstype.IterIdentifiers(): |
self.suppressions[suppression] = token |
def SuppressionOnly(self): |
@@ -717,6 +779,23 @@ class StateTracker(object): |
self._documented_identifiers = set() |
self._variables_in_scope = [] |
+ def DocFlagPass(self, start_token, error_handler): |
+ """Parses doc flags. |
+ |
+ This pass needs to be executed before the aliaspass and we don't want to do |
+ a full-blown statetracker dry run for these. |
+ |
+ Args: |
+ start_token: The token at which to start iterating |
+ error_handler: An error handler for error reporting. |
+ """ |
+ if not start_token: |
+ return |
+ doc_flag_types = (Type.DOC_FLAG, Type.DOC_INLINE_FLAG) |
+ for token in start_token: |
+ if token.type in doc_flag_types: |
+ token.attached_object = self._doc_flag(token, error_handler) |
+ |
def InFunction(self): |
"""Returns true if the current token is within a function. |
@@ -1055,8 +1134,12 @@ class StateTracker(object): |
self._doc_comment.end_token = token |
elif type in (Type.DOC_FLAG, Type.DOC_INLINE_FLAG): |
- flag = self._doc_flag(token) |
- token.attached_object = flag |
+ # Don't overwrite flags if they were already parsed in a previous pass. |
+ if token.attached_object is None: |
+ flag = self._doc_flag(token) |
+ token.attached_object = flag |
+ else: |
+ flag = token.attached_object |
self._doc_comment.AddFlag(flag) |
if flag.flag_type == 'suppress': |
@@ -1066,8 +1149,8 @@ class StateTracker(object): |
last_code = tokenutil.SearchExcept(token, Type.NON_CODE_TYPES, None, |
True) |
doc = None |
- # Only functions outside of parens are eligible for documentation. |
- if not self._paren_depth: |
+ # Only top-level functions are eligible for documentation. |
+ if self.InTopLevel(): |
doc = self._doc_comment |
name = '' |
@@ -1081,8 +1164,7 @@ class StateTracker(object): |
# my.function.foo. |
# bar = function() ... |
identifier = tokenutil.Search(last_code, Type.SIMPLE_LVALUE, None, True) |
- while identifier and identifier.type in ( |
- Type.IDENTIFIER, Type.SIMPLE_LVALUE): |
+ while identifier and tokenutil.IsIdentifierOrDot(identifier): |
name = identifier.string + name |
# Traverse behind us, skipping whitespace and comments. |
while True: |
@@ -1183,7 +1265,8 @@ class StateTracker(object): |
if type == Type.SEMICOLON or type == Type.END_PAREN or ( |
type == Type.END_BRACKET and |
self._last_non_space_token.type not in ( |
- Type.SINGLE_QUOTE_STRING_END, Type.DOUBLE_QUOTE_STRING_END)): |
+ Type.SINGLE_QUOTE_STRING_END, Type.DOUBLE_QUOTE_STRING_END, |
+ Type.TEMPLATE_STRING_END)): |
# We end on any numeric array index, but keep going for string based |
# array indices so that we pick up manually exported identifiers. |
self._doc_comment = None |