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 |