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

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

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

Powered by Google App Engine
This is Rietveld 408576698