Home | History | Annotate | Download | only in closure_linter
      1 #!/usr/bin/env python
      2 #
      3 # Copyright 2007 The Closure Linter Authors. All Rights Reserved.
      4 #
      5 # Licensed under the Apache License, Version 2.0 (the "License");
      6 # you may not use this file except in compliance with the License.
      7 # You may obtain a copy of the License at
      8 #
      9 #      http://www.apache.org/licenses/LICENSE-2.0
     10 #
     11 # Unless required by applicable law or agreed to in writing, software
     12 # distributed under the License is distributed on an "AS-IS" BASIS,
     13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14 # See the License for the specific language governing permissions and
     15 # limitations under the License.
     16 
     17 """Light weight EcmaScript state tracker that reads tokens and tracks state."""
     18 
     19 __author__ = ('robbyw (at] google.com (Robert Walker)',
     20               'ajp (at] google.com (Andy Perelson)')
     21 
     22 import re
     23 
     24 from closure_linter import javascripttokenizer
     25 from closure_linter import javascripttokens
     26 from closure_linter import tokenutil
     27 from closure_linter import typeannotation
     28 
     29 # Shorthand
     30 Type = javascripttokens.JavaScriptTokenType
     31 
     32 
     33 class DocFlag(object):
     34   """Generic doc flag object.
     35 
     36   Attribute:
     37     flag_type: param, return, define, type, etc.
     38     flag_token: The flag token.
     39     type_start_token: The first token specifying the flag type,
     40       including braces.
     41     type_end_token: The last token specifying the flag type,
     42       including braces.
     43     type: The type spec string.
     44     jstype: The type spec, a TypeAnnotation instance.
     45     name_token: The token specifying the flag name.
     46     name: The flag name
     47     description_start_token: The first token in the description.
     48     description_end_token: The end token in the description.
     49     description: The description.
     50   """
     51 
     52   # Please keep these lists alphabetized.
     53 
     54   # The list of standard jsdoc tags is from
     55   STANDARD_DOC = frozenset([
     56       'author',
     57       'bug',
     58       'classTemplate',
     59       'consistentIdGenerator',
     60       'const',
     61       'constructor',
     62       'define',
     63       'deprecated',
     64       'dict',
     65       'enum',
     66       'export',
     67       'expose',
     68       'extends',
     69       'externs',
     70       'fileoverview',
     71       'idGenerator',
     72       'implements',
     73       'implicitCast',
     74       'interface',
     75       'lends',
     76       'license',
     77       'ngInject',  # This annotation is specific to AngularJS.
     78       'noalias',
     79       'nocompile',
     80       'nosideeffects',
     81       'override',
     82       'owner',
     83       'nocollapse',
     84       'package',
     85       'param',
     86       'polymerBehavior',  # This annotation is specific to Polymer.
     87       'preserve',
     88       'private',
     89       'protected',
     90       'public',
     91       'return',
     92       'see',
     93       'stableIdGenerator',
     94       'struct',
     95       'supported',
     96       'template',
     97       'this',
     98       'type',
     99       'typedef',
    100       'unrestricted',
    101       ])
    102 
    103   ANNOTATION = frozenset(['preserveTry', 'suppress'])
    104 
    105   LEGAL_DOC = STANDARD_DOC | ANNOTATION
    106 
    107   # Includes all Closure Compiler @suppress types.
    108   # Not all of these annotations are interpreted by Closure Linter.
    109   #
    110   # Specific cases:
    111   # - accessControls is supported by the compiler at the expression
    112   #   and method level to suppress warnings about private/protected
    113   #   access (method level applies to all references in the method).
    114   #   The linter mimics the compiler behavior.
    115   SUPPRESS_TYPES = frozenset([
    116       'accessControls',
    117       'ambiguousFunctionDecl',
    118       'checkDebuggerStatement',
    119       'checkRegExp',
    120       'checkStructDictInheritance',
    121       'checkTypes',
    122       'checkVars',
    123       'const',
    124       'constantProperty',
    125       'deprecated',
    126       'duplicate',
    127       'es5Strict',
    128       'externsValidation',
    129       'extraProvide',
    130       'extraRequire',
    131       'fileoverviewTags',
    132       'globalThis',
    133       'internetExplorerChecks',
    134       'invalidCasts',
    135       'missingProperties',
    136       'missingProvide',
    137       'missingRequire',
    138       'missingReturn',
    139       'nonStandardJsDocs',
    140       'reportUnknownTypes',
    141       'strictModuleDepCheck',
    142       'suspiciousCode',
    143       'tweakValidation',
    144       'typeInvalidation',
    145       'undefinedNames',
    146       'undefinedVars',
    147       'underscore',
    148       'unknownDefines',
    149       'unnecessaryCasts',
    150       'unusedPrivateMembers',
    151       'uselessCode',
    152       'visibility',
    153       'with',
    154   ])
    155 
    156   HAS_DESCRIPTION = frozenset([
    157       'define',
    158       'deprecated',
    159       'desc',
    160       'fileoverview',
    161       'license',
    162       'param',
    163       'preserve',
    164       'return',
    165       'supported',
    166   ])
    167 
    168   # Docflags whose argument should be parsed using the typeannotation parser.
    169   HAS_TYPE = frozenset([
    170       'const',
    171       'define',
    172       'enum',
    173       'export',
    174       'extends',
    175       'final',
    176       'implements',
    177       'mods',
    178       'package',
    179       'param',
    180       'private',
    181       'protected',
    182       'public',
    183       'return',
    184       'suppress',
    185       'type',
    186       'typedef',
    187   ])
    188 
    189   # Docflags for which it's ok to omit the type (flag without an argument).
    190   CAN_OMIT_TYPE = frozenset([
    191       'const',
    192       'enum',
    193       'export',
    194       'final',
    195       'package',
    196       'private',
    197       'protected',
    198       'public',
    199       'suppress',  # We'll raise a separate INCORRECT_SUPPRESS_SYNTAX instead.
    200   ])
    201 
    202   # Docflags that only take a type as an argument and should not parse a
    203   # following description.
    204   TYPE_ONLY = frozenset([
    205       'const',
    206       'enum',
    207       'extends',
    208       'implements',
    209       'package',
    210       'suppress',
    211       'type',
    212   ])
    213 
    214   HAS_NAME = frozenset(['param'])
    215 
    216   EMPTY_COMMENT_LINE = re.compile(r'^\s*\*?\s*$')
    217   EMPTY_STRING = re.compile(r'^\s*$')
    218 
    219   def __init__(self, flag_token, error_handler=None):
    220     """Creates the DocFlag object and attaches it to the given start token.
    221 
    222     Args:
    223       flag_token: The starting token of the flag.
    224       error_handler: An optional error handler for errors occurring while
    225         parsing the doctype.
    226     """
    227     self.flag_token = flag_token
    228     self.flag_type = flag_token.string.strip().lstrip('@')
    229 
    230     # Extract type, if applicable.
    231     self.type = None
    232     self.jstype = None
    233     self.type_start_token = None
    234     self.type_end_token = None
    235     if self.flag_type in self.HAS_TYPE:
    236       brace = tokenutil.SearchUntil(flag_token, [Type.DOC_START_BRACE],
    237                                     Type.FLAG_ENDING_TYPES)
    238       if brace:
    239         end_token, contents = _GetMatchingEndBraceAndContents(brace)
    240         self.type = contents
    241         self.jstype = typeannotation.Parse(brace, end_token,
    242                                            error_handler)
    243         self.type_start_token = brace
    244         self.type_end_token = end_token
    245       elif (self.flag_type in self.TYPE_ONLY and
    246             flag_token.next.type not in Type.FLAG_ENDING_TYPES and
    247             flag_token.line_number == flag_token.next.line_number):
    248         # b/10407058. If the flag is expected to be followed by a type then
    249         # search for type in same line only. If no token after flag in same
    250         # line then conclude that no type is specified.
    251         self.type_start_token = flag_token.next
    252         self.type_end_token, self.type = _GetEndTokenAndContents(
    253             self.type_start_token)
    254         if self.type is not None:
    255           self.type = self.type.strip()
    256           self.jstype = typeannotation.Parse(flag_token, self.type_end_token,
    257                                              error_handler)
    258 
    259     # Extract name, if applicable.
    260     self.name_token = None
    261     self.name = None
    262     if self.flag_type in self.HAS_NAME:
    263       # Handle bad case, name could be immediately after flag token.
    264       self.name_token = _GetNextPartialIdentifierToken(flag_token)
    265 
    266       # Handle good case, if found token is after type start, look for
    267       # a identifier (substring to cover cases like [cnt] b/4197272) after
    268       # type end, since types contain identifiers.
    269       if (self.type and self.name_token and
    270           tokenutil.Compare(self.name_token, self.type_start_token) > 0):
    271         self.name_token = _GetNextPartialIdentifierToken(self.type_end_token)
    272 
    273       if self.name_token:
    274         self.name = self.name_token.string
    275 
    276     # Extract description, if applicable.
    277     self.description_start_token = None
    278     self.description_end_token = None
    279     self.description = None
    280     if self.flag_type in self.HAS_DESCRIPTION:
    281       search_start_token = flag_token
    282       if self.name_token and self.type_end_token:
    283         if tokenutil.Compare(self.type_end_token, self.name_token) > 0:
    284           search_start_token = self.type_end_token
    285         else:
    286           search_start_token = self.name_token
    287       elif self.name_token:
    288         search_start_token = self.name_token
    289       elif self.type:
    290         search_start_token = self.type_end_token
    291 
    292       interesting_token = tokenutil.Search(search_start_token,
    293           Type.FLAG_DESCRIPTION_TYPES | Type.FLAG_ENDING_TYPES)
    294       if interesting_token.type in Type.FLAG_DESCRIPTION_TYPES:
    295         self.description_start_token = interesting_token
    296         self.description_end_token, self.description = (
    297             _GetEndTokenAndContents(interesting_token))
    298 
    299   def HasType(self):
    300     """Returns whether this flag should have a type annotation."""
    301     return self.flag_type in self.HAS_TYPE
    302 
    303   def __repr__(self):
    304     return '<Flag: %s, type:%s>' % (self.flag_type, repr(self.jstype))
    305 
    306 
    307 class DocComment(object):
    308   """JavaScript doc comment object.
    309 
    310   Attributes:
    311     ordered_params: Ordered list of parameters documented.
    312     start_token: The token that starts the doc comment.
    313     end_token: The token that ends the doc comment.
    314     suppressions: Map of suppression type to the token that added it.
    315   """
    316   def __init__(self, start_token):
    317     """Create the doc comment object.
    318 
    319     Args:
    320       start_token: The first token in the doc comment.
    321     """
    322     self.__flags = []
    323     self.start_token = start_token
    324     self.end_token = None
    325     self.suppressions = {}
    326     self.invalidated = False
    327 
    328   @property
    329   def ordered_params(self):
    330     """Gives the list of parameter names as a list of strings."""
    331     params = []
    332     for flag in self.__flags:
    333       if flag.flag_type == 'param' and flag.name:
    334         params.append(flag.name)
    335     return params
    336 
    337   def Invalidate(self):
    338     """Indicate that the JSDoc is well-formed but we had problems parsing it.
    339 
    340     This is a short-circuiting mechanism so that we don't emit false
    341     positives about well-formed doc comments just because we don't support
    342     hot new syntaxes.
    343     """
    344     self.invalidated = True
    345 
    346   def IsInvalidated(self):
    347     """Test whether Invalidate() has been called."""
    348     return self.invalidated
    349 
    350   def AddSuppression(self, token):
    351     """Add a new error suppression flag.
    352 
    353     Args:
    354       token: The suppression flag token.
    355     """
    356     flag = token and token.attached_object
    357     if flag and flag.jstype:
    358       for suppression in flag.jstype.IterIdentifiers():
    359         self.suppressions[suppression] = token
    360 
    361   def SuppressionOnly(self):
    362     """Returns whether this comment contains only suppression flags."""
    363     if not self.__flags:
    364       return False
    365 
    366     for flag in self.__flags:
    367       if flag.flag_type != 'suppress':
    368         return False
    369 
    370     return True
    371 
    372   def AddFlag(self, flag):
    373     """Add a new document flag.
    374 
    375     Args:
    376       flag: DocFlag object.
    377     """
    378     self.__flags.append(flag)
    379 
    380   def InheritsDocumentation(self):
    381     """Test if the jsdoc implies documentation inheritance.
    382 
    383     Returns:
    384         True if documentation may be pulled off the superclass.
    385     """
    386     return self.HasFlag('inheritDoc') or self.HasFlag('override')
    387 
    388   def HasFlag(self, flag_type):
    389     """Test if the given flag has been set.
    390 
    391     Args:
    392       flag_type: The type of the flag to check.
    393 
    394     Returns:
    395       True if the flag is set.
    396     """
    397     for flag in self.__flags:
    398       if flag.flag_type == flag_type:
    399         return True
    400     return False
    401 
    402   def GetFlag(self, flag_type):
    403     """Gets the last flag of the given type.
    404 
    405     Args:
    406       flag_type: The type of the flag to get.
    407 
    408     Returns:
    409       The last instance of the given flag type in this doc comment.
    410     """
    411     for flag in reversed(self.__flags):
    412       if flag.flag_type == flag_type:
    413         return flag
    414 
    415   def GetDocFlags(self):
    416     """Return the doc flags for this comment."""
    417     return list(self.__flags)
    418 
    419   def _YieldDescriptionTokens(self):
    420     for token in self.start_token:
    421 
    422       if (token is self.end_token or
    423           token.type is javascripttokens.JavaScriptTokenType.DOC_FLAG or
    424           token.type not in javascripttokens.JavaScriptTokenType.COMMENT_TYPES):
    425         return
    426 
    427       if token.type not in [
    428           javascripttokens.JavaScriptTokenType.START_DOC_COMMENT,
    429           javascripttokens.JavaScriptTokenType.END_DOC_COMMENT,
    430           javascripttokens.JavaScriptTokenType.DOC_PREFIX]:
    431         yield token
    432 
    433   @property
    434   def description(self):
    435     return tokenutil.TokensToString(
    436         self._YieldDescriptionTokens())
    437 
    438   def GetTargetIdentifier(self):
    439     """Returns the identifier (as a string) that this is a comment for.
    440 
    441     Note that this uses method uses GetIdentifierForToken to get the full
    442     identifier, even if broken up by whitespace, newlines, or comments,
    443     and thus could be longer than GetTargetToken().string.
    444 
    445     Returns:
    446       The identifier for the token this comment is for.
    447     """
    448     token = self.GetTargetToken()
    449     if token:
    450       return tokenutil.GetIdentifierForToken(token)
    451 
    452   def GetTargetToken(self):
    453     """Get this comment's target token.
    454 
    455     Returns:
    456       The token that is the target of this comment, or None if there isn't one.
    457     """
    458 
    459     # File overviews describe the file, not a token.
    460     if self.HasFlag('fileoverview'):
    461       return
    462 
    463     skip_types = frozenset([
    464         Type.WHITESPACE,
    465         Type.BLANK_LINE,
    466         Type.START_PAREN])
    467 
    468     target_types = frozenset([
    469         Type.FUNCTION_NAME,
    470         Type.IDENTIFIER,
    471         Type.SIMPLE_LVALUE])
    472 
    473     token = self.end_token.next
    474     while token:
    475       if token.type in target_types:
    476         return token
    477 
    478       # Handles the case of a comment on "var foo = ...'
    479       if token.IsKeyword('var'):
    480         next_code_token = tokenutil.CustomSearch(
    481             token,
    482             lambda t: t.type not in Type.NON_CODE_TYPES)
    483 
    484         if (next_code_token and
    485             next_code_token.IsType(Type.SIMPLE_LVALUE)):
    486           return next_code_token
    487 
    488         return
    489 
    490       # Handles the case of a comment on "function foo () {}"
    491       if token.type is Type.FUNCTION_DECLARATION:
    492         next_code_token = tokenutil.CustomSearch(
    493             token,
    494             lambda t: t.type not in Type.NON_CODE_TYPES)
    495 
    496         if next_code_token.IsType(Type.FUNCTION_NAME):
    497           return next_code_token
    498 
    499         return
    500 
    501       # Skip types will end the search.
    502       if token.type not in skip_types:
    503         return
    504 
    505       token = token.next
    506 
    507   def CompareParameters(self, params):
    508     """Computes the edit distance and list from the function params to the docs.
    509 
    510     Uses the Levenshtein edit distance algorithm, with code modified from
    511     http://en.wikibooks.org/wiki/Algorithm_implementation/Strings/Levenshtein_distance#Python
    512 
    513     Args:
    514       params: The parameter list for the function declaration.
    515 
    516     Returns:
    517       The edit distance, the edit list.
    518     """
    519     source_len, target_len = len(self.ordered_params), len(params)
    520     edit_lists = [[]]
    521     distance = [[]]
    522     for i in range(target_len+1):
    523       edit_lists[0].append(['I'] * i)
    524       distance[0].append(i)
    525 
    526     for j in range(1, source_len+1):
    527       edit_lists.append([['D'] * j])
    528       distance.append([j])
    529 
    530     for i in range(source_len):
    531       for j in range(target_len):
    532         cost = 1
    533         if self.ordered_params[i] == params[j]:
    534           cost = 0
    535 
    536         deletion = distance[i][j+1] + 1
    537         insertion = distance[i+1][j] + 1
    538         substitution = distance[i][j] + cost
    539 
    540         edit_list = None
    541         best = None
    542         if deletion <= insertion and deletion <= substitution:
    543           # Deletion is best.
    544           best = deletion
    545           edit_list = list(edit_lists[i][j+1])
    546           edit_list.append('D')
    547 
    548         elif insertion <= substitution:
    549           # Insertion is best.
    550           best = insertion
    551           edit_list = list(edit_lists[i+1][j])
    552           edit_list.append('I')
    553           edit_lists[i+1].append(edit_list)
    554 
    555         else:
    556           # Substitution is best.
    557           best = substitution
    558           edit_list = list(edit_lists[i][j])
    559           if cost:
    560             edit_list.append('S')
    561           else:
    562             edit_list.append('=')
    563 
    564         edit_lists[i+1].append(edit_list)
    565         distance[i+1].append(best)
    566 
    567     return distance[source_len][target_len], edit_lists[source_len][target_len]
    568 
    569   def __repr__(self):
    570     """Returns a string representation of this object.
    571 
    572     Returns:
    573       A string representation of this object.
    574     """
    575     return '<DocComment: %s, %s>' % (
    576         str(self.ordered_params), str(self.__flags))
    577 
    578 
    579 #
    580 # Helper methods used by DocFlag and DocComment to parse out flag information.
    581 #
    582 
    583 
    584 def _GetMatchingEndBraceAndContents(start_brace):
    585   """Returns the matching end brace and contents between the two braces.
    586 
    587   If any FLAG_ENDING_TYPE token is encountered before a matching end brace, then
    588   that token is used as the matching ending token. Contents will have all
    589   comment prefixes stripped out of them, and all comment prefixes in between the
    590   start and end tokens will be split out into separate DOC_PREFIX tokens.
    591 
    592   Args:
    593     start_brace: The DOC_START_BRACE token immediately before desired contents.
    594 
    595   Returns:
    596     The matching ending token (DOC_END_BRACE or FLAG_ENDING_TYPE) and a string
    597     of the contents between the matching tokens, minus any comment prefixes.
    598   """
    599   open_count = 1
    600   close_count = 0
    601   contents = []
    602 
    603   # We don't consider the start brace part of the type string.
    604   token = start_brace.next
    605   while open_count != close_count:
    606     if token.type == Type.DOC_START_BRACE:
    607       open_count += 1
    608     elif token.type == Type.DOC_END_BRACE:
    609       close_count += 1
    610 
    611     if token.type != Type.DOC_PREFIX:
    612       contents.append(token.string)
    613 
    614     if token.type in Type.FLAG_ENDING_TYPES:
    615       break
    616     token = token.next
    617 
    618   #Don't include the end token (end brace, end doc comment, etc.) in type.
    619   token = token.previous
    620   contents = contents[:-1]
    621 
    622   return token, ''.join(contents)
    623 
    624 
    625 def _GetNextPartialIdentifierToken(start_token):
    626   """Returns the first token having identifier as substring after a token.
    627 
    628   Searches each token after the start to see if it contains an identifier.
    629   If found, token is returned. If no identifier is found returns None.
    630   Search is abandoned when a FLAG_ENDING_TYPE token is found.
    631 
    632   Args:
    633     start_token: The token to start searching after.
    634 
    635   Returns:
    636     The token found containing identifier, None otherwise.
    637   """
    638   token = start_token.next
    639 
    640   while token and token.type not in Type.FLAG_ENDING_TYPES:
    641     match = javascripttokenizer.JavaScriptTokenizer.IDENTIFIER.search(
    642         token.string)
    643     if match is not None and token.type == Type.COMMENT:
    644       return token
    645 
    646     token = token.next
    647 
    648   return None
    649 
    650 
    651 def _GetEndTokenAndContents(start_token):
    652   """Returns last content token and all contents before FLAG_ENDING_TYPE token.
    653 
    654   Comment prefixes are split into DOC_PREFIX tokens and stripped from the
    655   returned contents.
    656 
    657   Args:
    658     start_token: The token immediately before the first content token.
    659 
    660   Returns:
    661     The last content token and a string of all contents including start and
    662     end tokens, with comment prefixes stripped.
    663   """
    664   iterator = start_token
    665   last_line = iterator.line_number
    666   last_token = None
    667   contents = ''
    668   doc_depth = 0
    669   while not iterator.type in Type.FLAG_ENDING_TYPES or doc_depth > 0:
    670     if (iterator.IsFirstInLine() and
    671         DocFlag.EMPTY_COMMENT_LINE.match(iterator.line)):
    672       # If we have a blank comment line, consider that an implicit
    673       # ending of the description. This handles a case like:
    674       #
    675       # * @return {boolean} True
    676       # *
    677       # * Note: This is a sentence.
    678       #
    679       # The note is not part of the @return description, but there was
    680       # no definitive ending token. Rather there was a line containing
    681       # only a doc comment prefix or whitespace.
    682       break
    683 
    684     # b/2983692
    685     # don't prematurely match against a @flag if inside a doc flag
    686     # need to think about what is the correct behavior for unterminated
    687     # inline doc flags
    688     if (iterator.type == Type.DOC_START_BRACE and
    689         iterator.next.type == Type.DOC_INLINE_FLAG):
    690       doc_depth += 1
    691     elif (iterator.type == Type.DOC_END_BRACE and
    692         doc_depth > 0):
    693       doc_depth -= 1
    694 
    695     if iterator.type in Type.FLAG_DESCRIPTION_TYPES:
    696       contents += iterator.string
    697       last_token = iterator
    698 
    699     iterator = iterator.next
    700     if iterator.line_number != last_line:
    701       contents += '\n'
    702       last_line = iterator.line_number
    703 
    704   end_token = last_token
    705   if DocFlag.EMPTY_STRING.match(contents):
    706     contents = None
    707   else:
    708     # Strip trailing newline.
    709     contents = contents[:-1]
    710 
    711   return end_token, contents
    712 
    713 
    714 class Function(object):
    715   """Data about a JavaScript function.
    716 
    717   Attributes:
    718     block_depth: Block depth the function began at.
    719     doc: The DocComment associated with the function.
    720     has_return: If the function has a return value.
    721     has_this: If the function references the 'this' object.
    722     is_assigned: If the function is part of an assignment.
    723     is_constructor: If the function is a constructor.
    724     name: The name of the function, whether given in the function keyword or
    725         as the lvalue the function is assigned to.
    726     start_token: First token of the function (the function' keyword token).
    727     end_token: Last token of the function (the closing '}' token).
    728     parameters: List of parameter names.
    729   """
    730 
    731   def __init__(self, block_depth, is_assigned, doc, name):
    732     self.block_depth = block_depth
    733     self.is_assigned = is_assigned
    734     self.is_constructor = doc and doc.HasFlag('constructor')
    735     self.is_interface = doc and doc.HasFlag('interface')
    736     self.has_return = False
    737     self.has_throw = False
    738     self.has_this = False
    739     self.name = name
    740     self.doc = doc
    741     self.start_token = None
    742     self.end_token = None
    743     self.parameters = None
    744 
    745 
    746 class StateTracker(object):
    747   """EcmaScript state tracker.
    748 
    749   Tracks block depth, function names, etc. within an EcmaScript token stream.
    750   """
    751 
    752   OBJECT_LITERAL = 'o'
    753   CODE = 'c'
    754 
    755   def __init__(self, doc_flag=DocFlag):
    756     """Initializes a JavaScript token stream state tracker.
    757 
    758     Args:
    759       doc_flag: An optional custom DocFlag used for validating
    760           documentation flags.
    761     """
    762     self._doc_flag = doc_flag
    763     self.Reset()
    764 
    765   def Reset(self):
    766     """Resets the state tracker to prepare for processing a new page."""
    767     self._block_depth = 0
    768     self._is_block_close = False
    769     self._paren_depth = 0
    770     self._function_stack = []
    771     self._functions_by_name = {}
    772     self._last_comment = None
    773     self._doc_comment = None
    774     self._cumulative_params = None
    775     self._block_types = []
    776     self._last_non_space_token = None
    777     self._last_line = None
    778     self._first_token = None
    779     self._documented_identifiers = set()
    780     self._variables_in_scope = []
    781 
    782   def DocFlagPass(self, start_token, error_handler):
    783     """Parses doc flags.
    784 
    785     This pass needs to be executed before the aliaspass and we don't want to do
    786     a full-blown statetracker dry run for these.
    787 
    788     Args:
    789       start_token: The token at which to start iterating
    790       error_handler: An error handler for error reporting.
    791     """
    792     if not start_token:
    793       return
    794     doc_flag_types = (Type.DOC_FLAG, Type.DOC_INLINE_FLAG)
    795     for token in start_token:
    796       if token.type in doc_flag_types:
    797         token.attached_object = self._doc_flag(token, error_handler)
    798 
    799   def InFunction(self):
    800     """Returns true if the current token is within a function.
    801 
    802     Returns:
    803       True if the current token is within a function.
    804     """
    805     return bool(self._function_stack)
    806 
    807   def InConstructor(self):
    808     """Returns true if the current token is within a constructor.
    809 
    810     Returns:
    811       True if the current token is within a constructor.
    812     """
    813     return self.InFunction() and self._function_stack[-1].is_constructor
    814 
    815   def InInterfaceMethod(self):
    816     """Returns true if the current token is within an interface method.
    817 
    818     Returns:
    819       True if the current token is within an interface method.
    820     """
    821     if self.InFunction():
    822       if self._function_stack[-1].is_interface:
    823         return True
    824       else:
    825         name = self._function_stack[-1].name
    826         prototype_index = name.find('.prototype.')
    827         if prototype_index != -1:
    828           class_function_name = name[0:prototype_index]
    829           if (class_function_name in self._functions_by_name and
    830               self._functions_by_name[class_function_name].is_interface):
    831             return True
    832 
    833     return False
    834 
    835   def InTopLevelFunction(self):
    836     """Returns true if the current token is within a top level function.
    837 
    838     Returns:
    839       True if the current token is within a top level function.
    840     """
    841     return len(self._function_stack) == 1 and self.InTopLevel()
    842 
    843   def InAssignedFunction(self):
    844     """Returns true if the current token is within a function variable.
    845 
    846     Returns:
    847       True if if the current token is within a function variable
    848     """
    849     return self.InFunction() and self._function_stack[-1].is_assigned
    850 
    851   def IsFunctionOpen(self):
    852     """Returns true if the current token is a function block open.
    853 
    854     Returns:
    855       True if the current token is a function block open.
    856     """
    857     return (self._function_stack and
    858             self._function_stack[-1].block_depth == self._block_depth - 1)
    859 
    860   def IsFunctionClose(self):
    861     """Returns true if the current token is a function block close.
    862 
    863     Returns:
    864       True if the current token is a function block close.
    865     """
    866     return (self._function_stack and
    867             self._function_stack[-1].block_depth == self._block_depth)
    868 
    869   def InBlock(self):
    870     """Returns true if the current token is within a block.
    871 
    872     Returns:
    873       True if the current token is within a block.
    874     """
    875     return bool(self._block_depth)
    876 
    877   def IsBlockClose(self):
    878     """Returns true if the current token is a block close.
    879 
    880     Returns:
    881       True if the current token is a block close.
    882     """
    883     return self._is_block_close
    884 
    885   def InObjectLiteral(self):
    886     """Returns true if the current token is within an object literal.
    887 
    888     Returns:
    889       True if the current token is within an object literal.
    890     """
    891     return self._block_depth and self._block_types[-1] == self.OBJECT_LITERAL
    892 
    893   def InObjectLiteralDescendant(self):
    894     """Returns true if the current token has an object literal ancestor.
    895 
    896     Returns:
    897       True if the current token has an object literal ancestor.
    898     """
    899     return self.OBJECT_LITERAL in self._block_types
    900 
    901   def InParentheses(self):
    902     """Returns true if the current token is within parentheses.
    903 
    904     Returns:
    905       True if the current token is within parentheses.
    906     """
    907     return bool(self._paren_depth)
    908 
    909   def ParenthesesDepth(self):
    910     """Returns the number of parens surrounding the token.
    911 
    912     Returns:
    913       The number of parenthesis surrounding the token.
    914     """
    915     return self._paren_depth
    916 
    917   def BlockDepth(self):
    918     """Returns the number of blocks in which the token is nested.
    919 
    920     Returns:
    921       The number of blocks in which the token is nested.
    922     """
    923     return self._block_depth
    924 
    925   def FunctionDepth(self):
    926     """Returns the number of functions in which the token is nested.
    927 
    928     Returns:
    929       The number of functions in which the token is nested.
    930     """
    931     return len(self._function_stack)
    932 
    933   def InTopLevel(self):
    934     """Whether we are at the top level in the class.
    935 
    936     This function call is language specific.  In some languages like
    937     JavaScript, a function is top level if it is not inside any parenthesis.
    938     In languages such as ActionScript, a function is top level if it is directly
    939     within a class.
    940     """
    941     raise TypeError('Abstract method InTopLevel not implemented')
    942 
    943   def GetBlockType(self, token):
    944     """Determine the block type given a START_BLOCK token.
    945 
    946     Code blocks come after parameters, keywords  like else, and closing parens.
    947 
    948     Args:
    949       token: The current token. Can be assumed to be type START_BLOCK.
    950     Returns:
    951       Code block type for current token.
    952     """
    953     raise TypeError('Abstract method GetBlockType not implemented')
    954 
    955   def GetParams(self):
    956     """Returns the accumulated input params as an array.
    957 
    958     In some EcmasSript languages, input params are specified like
    959     (param:Type, param2:Type2, ...)
    960     in other they are specified just as
    961     (param, param2)
    962     We handle both formats for specifying parameters here and leave
    963     it to the compilers for each language to detect compile errors.
    964     This allows more code to be reused between lint checkers for various
    965     EcmaScript languages.
    966 
    967     Returns:
    968       The accumulated input params as an array.
    969     """
    970     params = []
    971     if self._cumulative_params:
    972       params = re.compile(r'\s+').sub('', self._cumulative_params).split(',')
    973       # Strip out the type from parameters of the form name:Type.
    974       params = map(lambda param: param.split(':')[0], params)
    975 
    976     return params
    977 
    978   def GetLastComment(self):
    979     """Return the last plain comment that could be used as documentation.
    980 
    981     Returns:
    982       The last plain comment that could be used as documentation.
    983     """
    984     return self._last_comment
    985 
    986   def GetDocComment(self):
    987     """Return the most recent applicable documentation comment.
    988 
    989     Returns:
    990       The last applicable documentation comment.
    991     """
    992     return self._doc_comment
    993 
    994   def HasDocComment(self, identifier):
    995     """Returns whether the identifier has been documented yet.
    996 
    997     Args:
    998       identifier: The identifier.
    999 
   1000     Returns:
   1001       Whether the identifier has been documented yet.
   1002     """
   1003     return identifier in self._documented_identifiers
   1004 
   1005   def InDocComment(self):
   1006     """Returns whether the current token is in a doc comment.
   1007 
   1008     Returns:
   1009       Whether the current token is in a doc comment.
   1010     """
   1011     return self._doc_comment and self._doc_comment.end_token is None
   1012 
   1013   def GetDocFlag(self):
   1014     """Returns the current documentation flags.
   1015 
   1016     Returns:
   1017       The current documentation flags.
   1018     """
   1019     return self._doc_flag
   1020 
   1021   def IsTypeToken(self, t):
   1022     if self.InDocComment() and t.type not in (Type.START_DOC_COMMENT,
   1023         Type.DOC_FLAG, Type.DOC_INLINE_FLAG, Type.DOC_PREFIX):
   1024       f = tokenutil.SearchUntil(t, [Type.DOC_FLAG], [Type.START_DOC_COMMENT],
   1025                                 None, True)
   1026       if (f and f.attached_object.type_start_token is not None and
   1027           f.attached_object.type_end_token is not None):
   1028         return (tokenutil.Compare(t, f.attached_object.type_start_token) > 0 and
   1029                 tokenutil.Compare(t, f.attached_object.type_end_token) < 0)
   1030     return False
   1031 
   1032   def GetFunction(self):
   1033     """Return the function the current code block is a part of.
   1034 
   1035     Returns:
   1036       The current Function object.
   1037     """
   1038     if self._function_stack:
   1039       return self._function_stack[-1]
   1040 
   1041   def GetBlockDepth(self):
   1042     """Return the block depth.
   1043 
   1044     Returns:
   1045       The current block depth.
   1046     """
   1047     return self._block_depth
   1048 
   1049   def GetLastNonSpaceToken(self):
   1050     """Return the last non whitespace token."""
   1051     return self._last_non_space_token
   1052 
   1053   def GetLastLine(self):
   1054     """Return the last line."""
   1055     return self._last_line
   1056 
   1057   def GetFirstToken(self):
   1058     """Return the very first token in the file."""
   1059     return self._first_token
   1060 
   1061   def IsVariableInScope(self, token_string):
   1062     """Checks if string is variable in current scope.
   1063 
   1064     For given string it checks whether the string is a defined variable
   1065     (including function param) in current state.
   1066 
   1067     E.g. if variables defined (variables in current scope) is docs
   1068     then docs, docs.length etc will be considered as variable in current
   1069     scope. This will help in avoding extra goog.require for variables.
   1070 
   1071     Args:
   1072       token_string: String to check if its is a variable in current scope.
   1073 
   1074     Returns:
   1075       true if given string is a variable in current scope.
   1076     """
   1077     for variable in self._variables_in_scope:
   1078       if (token_string == variable
   1079           or token_string.startswith(variable + '.')):
   1080         return True
   1081 
   1082     return False
   1083 
   1084   def HandleToken(self, token, last_non_space_token):
   1085     """Handles the given token and updates state.
   1086 
   1087     Args:
   1088       token: The token to handle.
   1089       last_non_space_token:
   1090     """
   1091     self._is_block_close = False
   1092 
   1093     if not self._first_token:
   1094       self._first_token = token
   1095 
   1096     # Track block depth.
   1097     type = token.type
   1098     if type == Type.START_BLOCK:
   1099       self._block_depth += 1
   1100 
   1101       # Subclasses need to handle block start very differently because
   1102       # whether a block is a CODE or OBJECT_LITERAL block varies significantly
   1103       # by language.
   1104       self._block_types.append(self.GetBlockType(token))
   1105 
   1106       # When entering a function body, record its parameters.
   1107       if self.InFunction():
   1108         function = self._function_stack[-1]
   1109         if self._block_depth == function.block_depth + 1:
   1110           function.parameters = self.GetParams()
   1111 
   1112     # Track block depth.
   1113     elif type == Type.END_BLOCK:
   1114       self._is_block_close = not self.InObjectLiteral()
   1115       self._block_depth -= 1
   1116       self._block_types.pop()
   1117 
   1118     # Track parentheses depth.
   1119     elif type == Type.START_PAREN:
   1120       self._paren_depth += 1
   1121 
   1122     # Track parentheses depth.
   1123     elif type == Type.END_PAREN:
   1124       self._paren_depth -= 1
   1125 
   1126     elif type == Type.COMMENT:
   1127       self._last_comment = token.string
   1128 
   1129     elif type == Type.START_DOC_COMMENT:
   1130       self._last_comment = None
   1131       self._doc_comment = DocComment(token)
   1132 
   1133     elif type == Type.END_DOC_COMMENT:
   1134       self._doc_comment.end_token = token
   1135 
   1136     elif type in (Type.DOC_FLAG, Type.DOC_INLINE_FLAG):
   1137       # Don't overwrite flags if they were already parsed in a previous pass.
   1138       if token.attached_object is None:
   1139         flag = self._doc_flag(token)
   1140         token.attached_object = flag
   1141       else:
   1142         flag = token.attached_object
   1143       self._doc_comment.AddFlag(flag)
   1144 
   1145       if flag.flag_type == 'suppress':
   1146         self._doc_comment.AddSuppression(token)
   1147 
   1148     elif type == Type.FUNCTION_DECLARATION:
   1149       last_code = tokenutil.SearchExcept(token, Type.NON_CODE_TYPES, None,
   1150                                          True)
   1151       doc = None
   1152       # Only top-level functions are eligible for documentation.
   1153       if self.InTopLevel():
   1154         doc = self._doc_comment
   1155 
   1156       name = ''
   1157       is_assigned = last_code and (last_code.IsOperator('=') or
   1158           last_code.IsOperator('||') or last_code.IsOperator('&&') or
   1159           (last_code.IsOperator(':') and not self.InObjectLiteral()))
   1160       if is_assigned:
   1161         # TODO(robbyw): This breaks for x[2] = ...
   1162         # Must use loop to find full function name in the case of line-wrapped
   1163         # declarations (bug 1220601) like:
   1164         # my.function.foo.
   1165         #   bar = function() ...
   1166         identifier = tokenutil.Search(last_code, Type.SIMPLE_LVALUE, None, True)
   1167         while identifier and tokenutil.IsIdentifierOrDot(identifier):
   1168           name = identifier.string + name
   1169           # Traverse behind us, skipping whitespace and comments.
   1170           while True:
   1171             identifier = identifier.previous
   1172             if not identifier or not identifier.type in Type.NON_CODE_TYPES:
   1173               break
   1174 
   1175       else:
   1176         next_token = tokenutil.SearchExcept(token, Type.NON_CODE_TYPES)
   1177         while next_token and next_token.IsType(Type.FUNCTION_NAME):
   1178           name += next_token.string
   1179           next_token = tokenutil.Search(next_token, Type.FUNCTION_NAME, 2)
   1180 
   1181       function = Function(self._block_depth, is_assigned, doc, name)
   1182       function.start_token = token
   1183 
   1184       self._function_stack.append(function)
   1185       self._functions_by_name[name] = function
   1186 
   1187       # Add a delimiter in stack for scope variables to define start of
   1188       # function. This helps in popping variables of this function when
   1189       # function declaration ends.
   1190       self._variables_in_scope.append('')
   1191 
   1192     elif type == Type.START_PARAMETERS:
   1193       self._cumulative_params = ''
   1194 
   1195     elif type == Type.PARAMETERS:
   1196       self._cumulative_params += token.string
   1197       self._variables_in_scope.extend(self.GetParams())
   1198 
   1199     elif type == Type.KEYWORD and token.string == 'return':
   1200       next_token = tokenutil.SearchExcept(token, Type.NON_CODE_TYPES)
   1201       if not next_token.IsType(Type.SEMICOLON):
   1202         function = self.GetFunction()
   1203         if function:
   1204           function.has_return = True
   1205 
   1206     elif type == Type.KEYWORD and token.string == 'throw':
   1207       function = self.GetFunction()
   1208       if function:
   1209         function.has_throw = True
   1210 
   1211     elif type == Type.KEYWORD and token.string == 'var':
   1212       function = self.GetFunction()
   1213       next_token = tokenutil.Search(token, [Type.IDENTIFIER,
   1214                                             Type.SIMPLE_LVALUE])
   1215 
   1216       if next_token:
   1217         if next_token.type == Type.SIMPLE_LVALUE:
   1218           self._variables_in_scope.append(next_token.values['identifier'])
   1219         else:
   1220           self._variables_in_scope.append(next_token.string)
   1221 
   1222     elif type == Type.SIMPLE_LVALUE:
   1223       identifier = token.values['identifier']
   1224       jsdoc = self.GetDocComment()
   1225       if jsdoc:
   1226         self._documented_identifiers.add(identifier)
   1227 
   1228       self._HandleIdentifier(identifier, True)
   1229 
   1230     elif type == Type.IDENTIFIER:
   1231       self._HandleIdentifier(token.string, False)
   1232 
   1233       # Detect documented non-assignments.
   1234       next_token = tokenutil.SearchExcept(token, Type.NON_CODE_TYPES)
   1235       if next_token and next_token.IsType(Type.SEMICOLON):
   1236         if (self._last_non_space_token and
   1237             self._last_non_space_token.IsType(Type.END_DOC_COMMENT)):
   1238           self._documented_identifiers.add(token.string)
   1239 
   1240   def _HandleIdentifier(self, identifier, is_assignment):
   1241     """Process the given identifier.
   1242 
   1243     Currently checks if it references 'this' and annotates the function
   1244     accordingly.
   1245 
   1246     Args:
   1247       identifier: The identifer to process.
   1248       is_assignment: Whether the identifer is being written to.
   1249     """
   1250     if identifier == 'this' or identifier.startswith('this.'):
   1251       function = self.GetFunction()
   1252       if function:
   1253         function.has_this = True
   1254 
   1255   def HandleAfterToken(self, token):
   1256     """Handle updating state after a token has been checked.
   1257 
   1258     This function should be used for destructive state changes such as
   1259     deleting a tracked object.
   1260 
   1261     Args:
   1262       token: The token to handle.
   1263     """
   1264     type = token.type
   1265     if type == Type.SEMICOLON or type == Type.END_PAREN or (
   1266         type == Type.END_BRACKET and
   1267         self._last_non_space_token.type not in (
   1268             Type.SINGLE_QUOTE_STRING_END, Type.DOUBLE_QUOTE_STRING_END)):
   1269       # We end on any numeric array index, but keep going for string based
   1270       # array indices so that we pick up manually exported identifiers.
   1271       self._doc_comment = None
   1272       self._last_comment = None
   1273 
   1274     elif type == Type.END_BLOCK:
   1275       self._doc_comment = None
   1276       self._last_comment = None
   1277 
   1278       if self.InFunction() and self.IsFunctionClose():
   1279         # TODO(robbyw): Detect the function's name for better errors.
   1280         function = self._function_stack.pop()
   1281         function.end_token = token
   1282 
   1283         # Pop all variables till delimiter ('') those were defined in the
   1284         # function being closed so make them out of scope.
   1285         while self._variables_in_scope and self._variables_in_scope[-1]:
   1286           self._variables_in_scope.pop()
   1287 
   1288         # Pop delimiter
   1289         if self._variables_in_scope:
   1290           self._variables_in_scope.pop()
   1291 
   1292     elif type == Type.END_PARAMETERS and self._doc_comment:
   1293       self._doc_comment = None
   1294       self._last_comment = None
   1295 
   1296     if not token.IsAnyType(Type.WHITESPACE, Type.BLANK_LINE):
   1297       self._last_non_space_token = token
   1298 
   1299     self._last_line = token.line
   1300