- add sources.
[platform/framework/web/crosswalk.git] / src / third_party / closure_linter / closure_linter / statetracker.py
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@google.com (Robert Walker)',
20               'ajp@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
28 # Shorthand
29 Type = javascripttokens.JavaScriptTokenType
30
31
32 class DocFlag(object):
33   """Generic doc flag object.
34
35   Attribute:
36     flag_type: param, return, define, type, etc.
37     flag_token: The flag token.
38     type_start_token: The first token specifying the flag type,
39       including braces.
40     type_end_token: The last token specifying the flag type,
41       including braces.
42     type: The type spec.
43     name_token: The token specifying the flag name.
44     name: The flag name
45     description_start_token: The first token in the description.
46     description_end_token: The end token in the description.
47     description: The description.
48   """
49
50   # Please keep these lists alphabetized.
51
52   # The list of standard jsdoc tags is from
53   STANDARD_DOC = frozenset([
54       'author',
55       'bug',
56       'const',
57       'constructor',
58       'define',
59       'deprecated',
60       'enum',
61       'export',
62       'extends',
63       'externs',
64       'fileoverview',
65       'implements',
66       'implicitCast',
67       'interface',
68       'lends',
69       'license',
70       'noalias',
71       'nocompile',
72       'nosideeffects',
73       'override',
74       'owner',
75       'param',
76       'preserve',
77       'private',
78       'return',
79       'see',
80       'supported',
81       'template',
82       'this',
83       'type',
84       'typedef',
85       ])
86
87   ANNOTATION = frozenset(['preserveTry', 'suppress'])
88
89   LEGAL_DOC = STANDARD_DOC | ANNOTATION
90
91   # Includes all Closure Compiler @suppress types.
92   # Not all of these annotations are interpreted by Closure Linter.
93   #
94   # Specific cases:
95   # - accessControls is supported by the compiler at the expression
96   #   and method level to suppress warnings about private/protected
97   #   access (method level applies to all references in the method).
98   #   The linter mimics the compiler behavior.
99   SUPPRESS_TYPES = frozenset([
100       'accessControls',
101       'ambiguousFunctionDecl',
102       'checkRegExp',
103       'checkTypes',
104       'checkVars',
105       'const',
106       'constantProperty',
107       'deprecated',
108       'duplicate',
109       'es5Strict',
110       'externsValidation',
111       'extraProvide',
112       'extraRequire',
113       'fileoverviewTags',
114       'globalThis',
115       'internetExplorerChecks',
116       'invalidCasts',
117       'missingProperties',
118       'missingProvide',
119       'missingRequire',
120       'nonStandardJsDocs',
121       'strictModuleDepCheck',
122       'tweakValidation',
123       'typeInvalidation',
124       'undefinedNames',
125       'undefinedVars',
126       'underscore',
127       'unknownDefines',
128       'uselessCode',
129       'visibility',
130       'with'])
131
132   HAS_DESCRIPTION = frozenset([
133     'define', 'deprecated', 'desc', 'fileoverview', 'license', 'param',
134     'preserve', 'return', 'supported'])
135
136   HAS_TYPE = frozenset([
137       'define', 'enum', 'extends', 'implements', 'param', 'return', 'type',
138       'suppress'])
139
140   TYPE_ONLY = frozenset(['enum', 'extends', 'implements',  'suppress', 'type'])
141
142   HAS_NAME = frozenset(['param'])
143
144   EMPTY_COMMENT_LINE = re.compile(r'^\s*\*?\s*$')
145   EMPTY_STRING = re.compile(r'^\s*$')
146
147   def __init__(self, flag_token):
148     """Creates the DocFlag object and attaches it to the given start token.
149
150     Args:
151       flag_token: The starting token of the flag.
152     """
153     self.flag_token = flag_token
154     self.flag_type = flag_token.string.strip().lstrip('@')
155
156     # Extract type, if applicable.
157     self.type = None
158     self.type_start_token = None
159     self.type_end_token = None
160     if self.flag_type in self.HAS_TYPE:
161       brace = tokenutil.SearchUntil(flag_token, [Type.DOC_START_BRACE],
162                                     Type.FLAG_ENDING_TYPES)
163       if brace:
164         end_token, contents = _GetMatchingEndBraceAndContents(brace)
165         self.type = contents
166         self.type_start_token = brace
167         self.type_end_token = end_token
168       elif (self.flag_type in self.TYPE_ONLY and
169           flag_token.next.type not in Type.FLAG_ENDING_TYPES):
170         self.type_start_token = flag_token.next
171         self.type_end_token, self.type = _GetEndTokenAndContents(
172             self.type_start_token)
173         if self.type is not None:
174           self.type = self.type.strip()
175
176     # Extract name, if applicable.
177     self.name_token = None
178     self.name = None
179     if self.flag_type in self.HAS_NAME:
180       # Handle bad case, name could be immediately after flag token.
181       self.name_token = _GetNextIdentifierToken(flag_token)
182
183       # Handle good case, if found token is after type start, look for
184       # identifier after type end, since types contain identifiers.
185       if (self.type and self.name_token and
186           tokenutil.Compare(self.name_token, self.type_start_token) > 0):
187         self.name_token = _GetNextIdentifierToken(self.type_end_token)
188
189       if self.name_token:
190         self.name = self.name_token.string
191
192     # Extract description, if applicable.
193     self.description_start_token = None
194     self.description_end_token = None
195     self.description = None
196     if self.flag_type in self.HAS_DESCRIPTION:
197       search_start_token = flag_token
198       if self.name_token and self.type_end_token:
199         if tokenutil.Compare(self.type_end_token, self.name_token) > 0:
200           search_start_token = self.type_end_token
201         else:
202           search_start_token = self.name_token
203       elif self.name_token:
204         search_start_token = self.name_token
205       elif self.type:
206         search_start_token = self.type_end_token
207
208       interesting_token = tokenutil.Search(search_start_token,
209           Type.FLAG_DESCRIPTION_TYPES | Type.FLAG_ENDING_TYPES)
210       if interesting_token.type in Type.FLAG_DESCRIPTION_TYPES:
211         self.description_start_token = interesting_token
212         self.description_end_token, self.description = (
213             _GetEndTokenAndContents(interesting_token))
214
215
216 class DocComment(object):
217   """JavaScript doc comment object.
218
219   Attributes:
220     ordered_params: Ordered list of parameters documented.
221     start_token: The token that starts the doc comment.
222     end_token: The token that ends the doc comment.
223     suppressions: Map of suppression type to the token that added it.
224   """
225   def __init__(self, start_token):
226     """Create the doc comment object.
227
228     Args:
229       start_token: The first token in the doc comment.
230     """
231     self.__params = {}
232     self.ordered_params = []
233     self.__flags = {}
234     self.start_token = start_token
235     self.end_token = None
236     self.suppressions = {}
237     self.invalidated = False
238
239   def Invalidate(self):
240     """Indicate that the JSDoc is well-formed but we had problems parsing it.
241
242     This is a short-circuiting mechanism so that we don't emit false
243     positives about well-formed doc comments just because we don't support
244     hot new syntaxes.
245     """
246     self.invalidated = True
247
248   def IsInvalidated(self):
249     """Test whether Invalidate() has been called."""
250     return self.invalidated
251
252   def AddParam(self, name, param_type):
253     """Add a new documented parameter.
254
255     Args:
256       name: The name of the parameter to document.
257       param_type: The parameter's declared JavaScript type.
258     """
259     self.ordered_params.append(name)
260     self.__params[name] = param_type
261
262   def AddSuppression(self, token):
263     """Add a new error suppression flag.
264
265     Args:
266       token: The suppression flag token.
267     """
268     #TODO(user): Error if no braces
269     brace = tokenutil.SearchUntil(token, [Type.DOC_START_BRACE],
270                                   [Type.DOC_FLAG])
271     if brace:
272       end_token, contents = _GetMatchingEndBraceAndContents(brace)
273       for suppression in contents.split('|'):
274         self.suppressions[suppression] = token
275
276   def SuppressionOnly(self):
277     """Returns whether this comment contains only suppression flags."""
278     for flag_type in self.__flags.keys():
279       if flag_type != 'suppress':
280         return False
281     return True
282
283   def AddFlag(self, flag):
284     """Add a new document flag.
285
286     Args:
287       flag: DocFlag object.
288     """
289     self.__flags[flag.flag_type] = flag
290
291   def InheritsDocumentation(self):
292     """Test if the jsdoc implies documentation inheritance.
293
294     Returns:
295         True if documentation may be pulled off the superclass.
296     """
297     return self.HasFlag('inheritDoc') or self.HasFlag('override')
298
299   def HasFlag(self, flag_type):
300     """Test if the given flag has been set.
301
302     Args:
303       flag_type: The type of the flag to check.
304
305     Returns:
306       True if the flag is set.
307     """
308     return flag_type in self.__flags
309
310   def GetFlag(self, flag_type):
311     """Gets the last flag of the given type.
312
313     Args:
314       flag_type: The type of the flag to get.
315
316     Returns:
317       The last instance of the given flag type in this doc comment.
318     """
319     return self.__flags[flag_type]
320
321   def CompareParameters(self, params):
322     """Computes the edit distance and list from the function params to the docs.
323
324     Uses the Levenshtein edit distance algorithm, with code modified from
325     http://en.wikibooks.org/wiki/Algorithm_implementation/Strings/Levenshtein_distance#Python
326
327     Args:
328       params: The parameter list for the function declaration.
329
330     Returns:
331       The edit distance, the edit list.
332     """
333     source_len, target_len = len(self.ordered_params), len(params)
334     edit_lists = [[]]
335     distance = [[]]
336     for i in range(target_len+1):
337       edit_lists[0].append(['I'] * i)
338       distance[0].append(i)
339
340     for j in range(1, source_len+1):
341       edit_lists.append([['D'] * j])
342       distance.append([j])
343
344     for i in range(source_len):
345       for j in range(target_len):
346         cost = 1
347         if self.ordered_params[i] == params[j]:
348           cost = 0
349
350         deletion = distance[i][j+1] + 1
351         insertion = distance[i+1][j] + 1
352         substitution = distance[i][j] + cost
353
354         edit_list = None
355         best = None
356         if deletion <= insertion and deletion <= substitution:
357           # Deletion is best.
358           best = deletion
359           edit_list = list(edit_lists[i][j+1])
360           edit_list.append('D')
361
362         elif insertion <= substitution:
363           # Insertion is best.
364           best = insertion
365           edit_list = list(edit_lists[i+1][j])
366           edit_list.append('I')
367           edit_lists[i+1].append(edit_list)
368
369         else:
370           # Substitution is best.
371           best = substitution
372           edit_list = list(edit_lists[i][j])
373           if cost:
374             edit_list.append('S')
375           else:
376             edit_list.append('=')
377
378         edit_lists[i+1].append(edit_list)
379         distance[i+1].append(best)
380
381     return distance[source_len][target_len], edit_lists[source_len][target_len]
382
383   def __repr__(self):
384     """Returns a string representation of this object.
385
386     Returns:
387       A string representation of this object.
388     """
389     return '<DocComment: %s, %s>' % (str(self.__params), str(self.__flags))
390
391
392 #
393 # Helper methods used by DocFlag and DocComment to parse out flag information.
394 #
395
396
397 def _GetMatchingEndBraceAndContents(start_brace):
398   """Returns the matching end brace and contents between the two braces.
399
400   If any FLAG_ENDING_TYPE token is encountered before a matching end brace, then
401   that token is used as the matching ending token. Contents will have all
402   comment prefixes stripped out of them, and all comment prefixes in between the
403   start and end tokens will be split out into separate DOC_PREFIX tokens.
404
405   Args:
406     start_brace: The DOC_START_BRACE token immediately before desired contents.
407
408   Returns:
409     The matching ending token (DOC_END_BRACE or FLAG_ENDING_TYPE) and a string
410     of the contents between the matching tokens, minus any comment prefixes.
411   """
412   open_count = 1
413   close_count = 0
414   contents = []
415
416   # We don't consider the start brace part of the type string.
417   token = start_brace.next
418   while open_count != close_count:
419     if token.type == Type.DOC_START_BRACE:
420       open_count += 1
421     elif token.type == Type.DOC_END_BRACE:
422       close_count += 1
423
424     if token.type != Type.DOC_PREFIX:
425       contents.append(token.string)
426
427     if token.type in Type.FLAG_ENDING_TYPES:
428       break
429     token = token.next
430
431   #Don't include the end token (end brace, end doc comment, etc.) in type.
432   token = token.previous
433   contents = contents[:-1]
434
435   return token, ''.join(contents)
436
437
438 def _GetNextIdentifierToken(start_token):
439   """Searches for and returns the first identifier at the beginning of a token.
440
441   Searches each token after the start to see if it starts with an identifier.
442   If found, will split the token into at most 3 piecies: leading whitespace,
443   identifier, rest of token, returning the identifier token. If no identifier is
444   found returns None and changes no tokens. Search is abandoned when a
445   FLAG_ENDING_TYPE token is found.
446
447   Args:
448     start_token: The token to start searching after.
449
450   Returns:
451     The identifier token is found, None otherwise.
452   """
453   token = start_token.next
454
455   while token and not token.type in Type.FLAG_ENDING_TYPES:
456     match = javascripttokenizer.JavaScriptTokenizer.IDENTIFIER.match(
457         token.string)
458     if (match is not None and token.type == Type.COMMENT and
459         len(token.string) == len(match.group(0))):
460       return token
461
462     token = token.next
463
464   return None
465
466
467 def _GetEndTokenAndContents(start_token):
468   """Returns last content token and all contents before FLAG_ENDING_TYPE token.
469
470   Comment prefixes are split into DOC_PREFIX tokens and stripped from the
471   returned contents.
472
473   Args:
474     start_token: The token immediately before the first content token.
475
476   Returns:
477     The last content token and a string of all contents including start and
478     end tokens, with comment prefixes stripped.
479   """
480   iterator = start_token
481   last_line = iterator.line_number
482   last_token = None
483   contents = ''
484   doc_depth = 0
485   while not iterator.type in Type.FLAG_ENDING_TYPES or doc_depth > 0:
486     if (iterator.IsFirstInLine() and
487         DocFlag.EMPTY_COMMENT_LINE.match(iterator.line)):
488       # If we have a blank comment line, consider that an implicit
489       # ending of the description. This handles a case like:
490       #
491       # * @return {boolean} True
492       # *
493       # * Note: This is a sentence.
494       #
495       # The note is not part of the @return description, but there was
496       # no definitive ending token. Rather there was a line containing
497       # only a doc comment prefix or whitespace.
498       break
499
500     # b/2983692
501     # don't prematurely match against a @flag if inside a doc flag
502     # need to think about what is the correct behavior for unterminated
503     # inline doc flags
504     if (iterator.type == Type.DOC_START_BRACE and
505         iterator.next.type == Type.DOC_INLINE_FLAG):
506       doc_depth += 1
507     elif (iterator.type == Type.DOC_END_BRACE and
508         doc_depth > 0):
509       doc_depth -= 1
510
511     if iterator.type in Type.FLAG_DESCRIPTION_TYPES:
512       contents += iterator.string
513       last_token = iterator
514
515     iterator = iterator.next
516     if iterator.line_number != last_line:
517       contents += '\n'
518       last_line = iterator.line_number
519
520   end_token = last_token
521   if DocFlag.EMPTY_STRING.match(contents):
522     contents = None
523   else:
524     # Strip trailing newline.
525     contents = contents[:-1]
526
527   return end_token, contents
528
529
530 class Function(object):
531   """Data about a JavaScript function.
532
533   Attributes:
534     block_depth: Block depth the function began at.
535     doc: The DocComment associated with the function.
536     has_return: If the function has a return value.
537     has_this: If the function references the 'this' object.
538     is_assigned: If the function is part of an assignment.
539     is_constructor: If the function is a constructor.
540     name: The name of the function, whether given in the function keyword or
541         as the lvalue the function is assigned to.
542   """
543
544   def __init__(self, block_depth, is_assigned, doc, name):
545     self.block_depth = block_depth
546     self.is_assigned = is_assigned
547     self.is_constructor = doc and doc.HasFlag('constructor')
548     self.is_interface = doc and doc.HasFlag('interface')
549     self.has_return = False
550     self.has_throw = False
551     self.has_this = False
552     self.name = name
553     self.doc = doc
554
555
556 class StateTracker(object):
557   """EcmaScript state tracker.
558
559   Tracks block depth, function names, etc. within an EcmaScript token stream.
560   """
561
562   OBJECT_LITERAL = 'o'
563   CODE = 'c'
564
565   def __init__(self, doc_flag=DocFlag):
566     """Initializes a JavaScript token stream state tracker.
567
568     Args:
569       doc_flag: An optional custom DocFlag used for validating
570           documentation flags.
571     """
572     self._doc_flag = doc_flag
573     self.Reset()
574
575   def Reset(self):
576     """Resets the state tracker to prepare for processing a new page."""
577     self._block_depth = 0
578     self._is_block_close = False
579     self._paren_depth = 0
580     self._functions = []
581     self._functions_by_name = {}
582     self._last_comment = None
583     self._doc_comment = None
584     self._cumulative_params = None
585     self._block_types = []
586     self._last_non_space_token = None
587     self._last_line = None
588     self._first_token = None
589     self._documented_identifiers = set()
590
591   def InFunction(self):
592     """Returns true if the current token is within a function.
593
594     Returns:
595       True if the current token is within a function.
596     """
597     return bool(self._functions)
598
599   def InConstructor(self):
600     """Returns true if the current token is within a constructor.
601
602     Returns:
603       True if the current token is within a constructor.
604     """
605     return self.InFunction() and self._functions[-1].is_constructor
606
607   def InInterfaceMethod(self):
608     """Returns true if the current token is within an interface method.
609
610     Returns:
611       True if the current token is within an interface method.
612     """
613     if self.InFunction():
614       if self._functions[-1].is_interface:
615         return True
616       else:
617         name = self._functions[-1].name
618         prototype_index = name.find('.prototype.')
619         if prototype_index != -1:
620           class_function_name = name[0:prototype_index]
621           if (class_function_name in self._functions_by_name and
622               self._functions_by_name[class_function_name].is_interface):
623             return True
624
625     return False
626
627   def InTopLevelFunction(self):
628     """Returns true if the current token is within a top level function.
629
630     Returns:
631       True if the current token is within a top level function.
632     """
633     return len(self._functions) == 1 and self.InTopLevel()
634
635   def InAssignedFunction(self):
636     """Returns true if the current token is within a function variable.
637
638     Returns:
639       True if if the current token is within a function variable
640     """
641     return self.InFunction() and self._functions[-1].is_assigned
642
643   def IsFunctionOpen(self):
644     """Returns true if the current token is a function block open.
645
646     Returns:
647       True if the current token is a function block open.
648     """
649     return (self._functions and
650             self._functions[-1].block_depth == self._block_depth - 1)
651
652   def IsFunctionClose(self):
653     """Returns true if the current token is a function block close.
654
655     Returns:
656       True if the current token is a function block close.
657     """
658     return (self._functions and
659             self._functions[-1].block_depth == self._block_depth)
660
661   def InBlock(self):
662     """Returns true if the current token is within a block.
663
664     Returns:
665       True if the current token is within a block.
666     """
667     return bool(self._block_depth)
668
669   def IsBlockClose(self):
670     """Returns true if the current token is a block close.
671
672     Returns:
673       True if the current token is a block close.
674     """
675     return self._is_block_close
676
677   def InObjectLiteral(self):
678     """Returns true if the current token is within an object literal.
679
680     Returns:
681       True if the current token is within an object literal.
682     """
683     return self._block_depth and self._block_types[-1] == self.OBJECT_LITERAL
684
685   def InObjectLiteralDescendant(self):
686     """Returns true if the current token has an object literal ancestor.
687
688     Returns:
689       True if the current token has an object literal ancestor.
690     """
691     return self.OBJECT_LITERAL in self._block_types
692
693   def InParentheses(self):
694     """Returns true if the current token is within parentheses.
695
696     Returns:
697       True if the current token is within parentheses.
698     """
699     return bool(self._paren_depth)
700
701   def InTopLevel(self):
702     """Whether we are at the top level in the class.
703
704     This function call is language specific.  In some languages like
705     JavaScript, a function is top level if it is not inside any parenthesis.
706     In languages such as ActionScript, a function is top level if it is directly
707     within a class.
708     """
709     raise TypeError('Abstract method InTopLevel not implemented')
710
711   def GetBlockType(self, token):
712     """Determine the block type given a START_BLOCK token.
713
714     Code blocks come after parameters, keywords  like else, and closing parens.
715
716     Args:
717       token: The current token. Can be assumed to be type START_BLOCK.
718     Returns:
719       Code block type for current token.
720     """
721     raise TypeError('Abstract method GetBlockType not implemented')
722
723   def GetParams(self):
724     """Returns the accumulated input params as an array.
725
726     In some EcmasSript languages, input params are specified like
727     (param:Type, param2:Type2, ...)
728     in other they are specified just as
729     (param, param2)
730     We handle both formats for specifying parameters here and leave
731     it to the compilers for each language to detect compile errors.
732     This allows more code to be reused between lint checkers for various
733     EcmaScript languages.
734
735     Returns:
736       The accumulated input params as an array.
737     """
738     params = []
739     if self._cumulative_params:
740       params = re.compile(r'\s+').sub('', self._cumulative_params).split(',')
741       # Strip out the type from parameters of the form name:Type.
742       params = map(lambda param: param.split(':')[0], params)
743
744     return params
745
746   def GetLastComment(self):
747     """Return the last plain comment that could be used as documentation.
748
749     Returns:
750       The last plain comment that could be used as documentation.
751     """
752     return self._last_comment
753
754   def GetDocComment(self):
755     """Return the most recent applicable documentation comment.
756
757     Returns:
758       The last applicable documentation comment.
759     """
760     return self._doc_comment
761
762   def HasDocComment(self, identifier):
763     """Returns whether the identifier has been documented yet.
764
765     Args:
766       identifier: The identifier.
767
768     Returns:
769       Whether the identifier has been documented yet.
770     """
771     return identifier in self._documented_identifiers
772
773   def InDocComment(self):
774     """Returns whether the current token is in a doc comment.
775
776     Returns:
777       Whether the current token is in a doc comment.
778     """
779     return self._doc_comment and self._doc_comment.end_token is None
780
781   def GetDocFlag(self):
782     """Returns the current documentation flags.
783
784     Returns:
785       The current documentation flags.
786     """
787     return self._doc_flag
788
789   def IsTypeToken(self, t):
790     if self.InDocComment() and t.type not in (Type.START_DOC_COMMENT,
791         Type.DOC_FLAG, Type.DOC_INLINE_FLAG, Type.DOC_PREFIX):
792       f = tokenutil.SearchUntil(t, [Type.DOC_FLAG], [Type.START_DOC_COMMENT],
793                                 None, True)
794       if f and f.attached_object.type_start_token is not None:
795         return (tokenutil.Compare(t, f.attached_object.type_start_token) > 0 and
796                 tokenutil.Compare(t, f.attached_object.type_end_token) < 0)
797     return False
798
799   def GetFunction(self):
800     """Return the function the current code block is a part of.
801
802     Returns:
803       The current Function object.
804     """
805     if self._functions:
806       return self._functions[-1]
807
808   def GetBlockDepth(self):
809     """Return the block depth.
810
811     Returns:
812       The current block depth.
813     """
814     return self._block_depth
815
816   def GetLastNonSpaceToken(self):
817     """Return the last non whitespace token."""
818     return self._last_non_space_token
819
820   def GetLastLine(self):
821     """Return the last line."""
822     return self._last_line
823
824   def GetFirstToken(self):
825     """Return the very first token in the file."""
826     return self._first_token
827
828   def HandleToken(self, token, last_non_space_token):
829     """Handles the given token and updates state.
830
831     Args:
832       token: The token to handle.
833       last_non_space_token:
834     """
835     self._is_block_close = False
836
837     if not self._first_token:
838       self._first_token = token
839
840     # Track block depth.
841     type = token.type
842     if type == Type.START_BLOCK:
843       self._block_depth += 1
844
845       # Subclasses need to handle block start very differently because
846       # whether a block is a CODE or OBJECT_LITERAL block varies significantly
847       # by language.
848       self._block_types.append(self.GetBlockType(token))
849
850     # Track block depth.
851     elif type == Type.END_BLOCK:
852       self._is_block_close = not self.InObjectLiteral()
853       self._block_depth -= 1
854       self._block_types.pop()
855
856     # Track parentheses depth.
857     elif type == Type.START_PAREN:
858       self._paren_depth += 1
859
860     # Track parentheses depth.
861     elif type == Type.END_PAREN:
862       self._paren_depth -= 1
863
864     elif type == Type.COMMENT:
865       self._last_comment = token.string
866
867     elif type == Type.START_DOC_COMMENT:
868       self._last_comment = None
869       self._doc_comment = DocComment(token)
870
871     elif type == Type.END_DOC_COMMENT:
872       self._doc_comment.end_token = token
873
874     elif type in (Type.DOC_FLAG, Type.DOC_INLINE_FLAG):
875       flag = self._doc_flag(token)
876       token.attached_object = flag
877       self._doc_comment.AddFlag(flag)
878
879       if flag.flag_type == 'param' and flag.name:
880         self._doc_comment.AddParam(flag.name, flag.type)
881       elif flag.flag_type == 'suppress':
882         self._doc_comment.AddSuppression(token)
883
884     elif type == Type.FUNCTION_DECLARATION:
885       last_code = tokenutil.SearchExcept(token, Type.NON_CODE_TYPES, None,
886                                          True)
887       doc = None
888       # Only functions outside of parens are eligible for documentation.
889       if not self._paren_depth:
890         doc = self._doc_comment
891
892       name = ''
893       is_assigned = last_code and (last_code.IsOperator('=') or
894           last_code.IsOperator('||') or last_code.IsOperator('&&') or
895           (last_code.IsOperator(':') and not self.InObjectLiteral()))
896       if is_assigned:
897         # TODO(robbyw): This breaks for x[2] = ...
898         # Must use loop to find full function name in the case of line-wrapped
899         # declarations (bug 1220601) like:
900         # my.function.foo.
901         #   bar = function() ...
902         identifier = tokenutil.Search(last_code, Type.SIMPLE_LVALUE, None, True)
903         while identifier and identifier.type in (
904             Type.IDENTIFIER, Type.SIMPLE_LVALUE):
905           name = identifier.string + name
906           # Traverse behind us, skipping whitespace and comments.
907           while True:
908             identifier = identifier.previous
909             if not identifier or not identifier.type in Type.NON_CODE_TYPES:
910               break
911
912       else:
913         next_token = tokenutil.SearchExcept(token, Type.NON_CODE_TYPES)
914         while next_token and next_token.IsType(Type.FUNCTION_NAME):
915           name += next_token.string
916           next_token = tokenutil.Search(next_token, Type.FUNCTION_NAME, 2)
917
918       function = Function(self._block_depth, is_assigned, doc, name)
919       self._functions.append(function)
920       self._functions_by_name[name] = function
921
922     elif type == Type.START_PARAMETERS:
923       self._cumulative_params = ''
924
925     elif type == Type.PARAMETERS:
926       self._cumulative_params += token.string
927
928     elif type == Type.KEYWORD and token.string == 'return':
929       next_token = tokenutil.SearchExcept(token, Type.NON_CODE_TYPES)
930       if not next_token.IsType(Type.SEMICOLON):
931         function = self.GetFunction()
932         if function:
933           function.has_return = True
934
935     elif type == Type.KEYWORD and token.string == 'throw':
936       function = self.GetFunction()
937       if function:
938         function.has_throw = True
939
940     elif type == Type.SIMPLE_LVALUE:
941       identifier = token.values['identifier']
942       jsdoc = self.GetDocComment()
943       if jsdoc:
944         self._documented_identifiers.add(identifier)
945
946       self._HandleIdentifier(identifier, True)
947
948     elif type == Type.IDENTIFIER:
949       self._HandleIdentifier(token.string, False)
950
951       # Detect documented non-assignments.
952       next_token = tokenutil.SearchExcept(token, Type.NON_CODE_TYPES)
953       if next_token.IsType(Type.SEMICOLON):
954         if (self._last_non_space_token and
955             self._last_non_space_token.IsType(Type.END_DOC_COMMENT)):
956           self._documented_identifiers.add(token.string)
957
958   def _HandleIdentifier(self, identifier, is_assignment):
959     """Process the given identifier.
960
961     Currently checks if it references 'this' and annotates the function
962     accordingly.
963
964     Args:
965       identifier: The identifer to process.
966       is_assignment: Whether the identifer is being written to.
967     """
968     if identifier == 'this' or identifier.startswith('this.'):
969       function = self.GetFunction()
970       if function:
971         function.has_this = True
972
973
974   def HandleAfterToken(self, token):
975     """Handle updating state after a token has been checked.
976
977     This function should be used for destructive state changes such as
978     deleting a tracked object.
979
980     Args:
981       token: The token to handle.
982     """
983     type = token.type
984     if type == Type.SEMICOLON or type == Type.END_PAREN or (
985         type == Type.END_BRACKET and
986         self._last_non_space_token.type not in (
987             Type.SINGLE_QUOTE_STRING_END, Type.DOUBLE_QUOTE_STRING_END)):
988       # We end on any numeric array index, but keep going for string based
989       # array indices so that we pick up manually exported identifiers.
990       self._doc_comment = None
991       self._last_comment = None
992
993     elif type == Type.END_BLOCK:
994       self._doc_comment = None
995       self._last_comment = None
996
997       if self.InFunction() and self.IsFunctionClose():
998         # TODO(robbyw): Detect the function's name for better errors.
999         self._functions.pop()
1000
1001     elif type == Type.END_PARAMETERS and self._doc_comment:
1002       self._doc_comment = None
1003       self._last_comment = None
1004
1005     if not token.IsAnyType(Type.WHITESPACE, Type.BLANK_LINE):
1006       self._last_non_space_token = token
1007
1008     self._last_line = token.line