| OLD | NEW |
| 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 Loading... |
| 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 Loading... |
| 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 Loading... |
| 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 Loading... |
| 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 Loading... |
| 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 Loading... |
| 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 Loading... |
| 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 Loading... |
| 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 |
| OLD | NEW |