Index: third_party/closure_linter/closure_linter/statetracker.py |
diff --git a/third_party/closure_linter/closure_linter/statetracker.py b/third_party/closure_linter/closure_linter/statetracker.py |
deleted file mode 100755 |
index 5730facb87dc7ea2d89b423167e5f1a799884b02..0000000000000000000000000000000000000000 |
--- a/third_party/closure_linter/closure_linter/statetracker.py |
+++ /dev/null |
@@ -1,1300 +0,0 @@ |
-#!/usr/bin/env python |
-# |
-# Copyright 2007 The Closure Linter Authors. All Rights Reserved. |
-# |
-# Licensed under the Apache License, Version 2.0 (the "License"); |
-# you may not use this file except in compliance with the License. |
-# You may obtain a copy of the License at |
-# |
-# http://www.apache.org/licenses/LICENSE-2.0 |
-# |
-# Unless required by applicable law or agreed to in writing, software |
-# distributed under the License is distributed on an "AS-IS" BASIS, |
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
-# See the License for the specific language governing permissions and |
-# limitations under the License. |
- |
-"""Light weight EcmaScript state tracker that reads tokens and tracks state.""" |
- |
-__author__ = ('robbyw@google.com (Robert Walker)', |
- 'ajp@google.com (Andy Perelson)') |
- |
-import re |
- |
-from closure_linter import javascripttokenizer |
-from closure_linter import javascripttokens |
-from closure_linter import tokenutil |
-from closure_linter import typeannotation |
- |
-# Shorthand |
-Type = javascripttokens.JavaScriptTokenType |
- |
- |
-class DocFlag(object): |
- """Generic doc flag object. |
- |
- Attribute: |
- flag_type: param, return, define, type, etc. |
- flag_token: The flag token. |
- type_start_token: The first token specifying the flag type, |
- including braces. |
- type_end_token: The last token specifying the flag type, |
- including braces. |
- type: The type spec string. |
- jstype: The type spec, a TypeAnnotation instance. |
- name_token: The token specifying the flag name. |
- name: The flag name |
- description_start_token: The first token in the description. |
- description_end_token: The end token in the description. |
- description: The description. |
- """ |
- |
- # Please keep these lists alphabetized. |
- |
- # The list of standard jsdoc tags is from |
- STANDARD_DOC = frozenset([ |
- 'author', |
- 'bug', |
- 'classTemplate', |
- 'consistentIdGenerator', |
- 'const', |
- 'constructor', |
- 'define', |
- 'deprecated', |
- 'dict', |
- 'enum', |
- 'export', |
- 'expose', |
- 'extends', |
- 'externs', |
- 'fileoverview', |
- 'idGenerator', |
- 'implements', |
- 'implicitCast', |
- 'interface', |
- 'lends', |
- 'license', |
- 'ngInject', # This annotation is specific to AngularJS. |
- 'noalias', |
- 'nocompile', |
- 'nosideeffects', |
- 'override', |
- 'owner', |
- 'nocollapse', |
- 'package', |
- 'param', |
- 'polymerBehavior', # This annotation is specific to Polymer. |
- 'preserve', |
- 'private', |
- 'protected', |
- 'public', |
- 'return', |
- 'see', |
- 'stableIdGenerator', |
- 'struct', |
- 'supported', |
- 'template', |
- 'this', |
- 'type', |
- 'typedef', |
- 'unrestricted', |
- ]) |
- |
- ANNOTATION = frozenset(['preserveTry', 'suppress']) |
- |
- LEGAL_DOC = STANDARD_DOC | ANNOTATION |
- |
- # Includes all Closure Compiler @suppress types. |
- # Not all of these annotations are interpreted by Closure Linter. |
- # |
- # Specific cases: |
- # - accessControls is supported by the compiler at the expression |
- # and method level to suppress warnings about private/protected |
- # access (method level applies to all references in the method). |
- # The linter mimics the compiler behavior. |
- SUPPRESS_TYPES = frozenset([ |
- 'accessControls', |
- 'ambiguousFunctionDecl', |
- 'checkDebuggerStatement', |
- 'checkRegExp', |
- 'checkStructDictInheritance', |
- 'checkTypes', |
- 'checkVars', |
- 'const', |
- 'constantProperty', |
- 'deprecated', |
- 'duplicate', |
- 'es5Strict', |
- 'externsValidation', |
- 'extraProvide', |
- 'extraRequire', |
- 'fileoverviewTags', |
- 'globalThis', |
- 'internetExplorerChecks', |
- 'invalidCasts', |
- 'missingProperties', |
- 'missingProvide', |
- 'missingRequire', |
- 'missingReturn', |
- 'nonStandardJsDocs', |
- 'reportUnknownTypes', |
- 'strictModuleDepCheck', |
- 'suspiciousCode', |
- 'tweakValidation', |
- 'typeInvalidation', |
- 'undefinedNames', |
- 'undefinedVars', |
- 'underscore', |
- 'unknownDefines', |
- 'unnecessaryCasts', |
- 'unusedPrivateMembers', |
- 'uselessCode', |
- 'visibility', |
- 'with', |
- ]) |
- |
- HAS_DESCRIPTION = frozenset([ |
- 'define', |
- 'deprecated', |
- 'desc', |
- 'fileoverview', |
- 'license', |
- 'param', |
- 'preserve', |
- 'return', |
- 'supported', |
- ]) |
- |
- # Docflags whose argument should be parsed using the typeannotation parser. |
- HAS_TYPE = frozenset([ |
- 'const', |
- 'define', |
- 'enum', |
- 'export', |
- 'extends', |
- 'final', |
- 'implements', |
- 'mods', |
- 'package', |
- 'param', |
- 'private', |
- 'protected', |
- 'public', |
- 'return', |
- 'suppress', |
- 'type', |
- 'typedef', |
- ]) |
- |
- # Docflags for which it's ok to omit the type (flag without an argument). |
- CAN_OMIT_TYPE = frozenset([ |
- 'const', |
- 'enum', |
- 'export', |
- 'final', |
- 'package', |
- 'private', |
- 'protected', |
- 'public', |
- 'suppress', # We'll raise a separate INCORRECT_SUPPRESS_SYNTAX instead. |
- ]) |
- |
- # Docflags that only take a type as an argument and should not parse a |
- # following description. |
- TYPE_ONLY = frozenset([ |
- 'const', |
- 'enum', |
- 'extends', |
- 'implements', |
- 'package', |
- 'suppress', |
- 'type', |
- ]) |
- |
- HAS_NAME = frozenset(['param']) |
- |
- EMPTY_COMMENT_LINE = re.compile(r'^\s*\*?\s*$') |
- EMPTY_STRING = re.compile(r'^\s*$') |
- |
- def __init__(self, flag_token, error_handler=None): |
- """Creates the DocFlag object and attaches it to the given start token. |
- |
- Args: |
- flag_token: The starting token of the flag. |
- error_handler: An optional error handler for errors occurring while |
- parsing the doctype. |
- """ |
- self.flag_token = flag_token |
- self.flag_type = flag_token.string.strip().lstrip('@') |
- |
- # Extract type, if applicable. |
- self.type = None |
- self.jstype = None |
- self.type_start_token = None |
- self.type_end_token = None |
- if self.flag_type in self.HAS_TYPE: |
- brace = tokenutil.SearchUntil(flag_token, [Type.DOC_START_BRACE], |
- Type.FLAG_ENDING_TYPES) |
- if brace: |
- end_token, contents = _GetMatchingEndBraceAndContents(brace) |
- self.type = contents |
- self.jstype = typeannotation.Parse(brace, end_token, |
- error_handler) |
- self.type_start_token = brace |
- self.type_end_token = end_token |
- elif (self.flag_type in self.TYPE_ONLY and |
- flag_token.next.type not in Type.FLAG_ENDING_TYPES and |
- flag_token.line_number == flag_token.next.line_number): |
- # b/10407058. If the flag is expected to be followed by a type then |
- # search for type in same line only. If no token after flag in same |
- # line then conclude that no type is specified. |
- self.type_start_token = flag_token.next |
- self.type_end_token, self.type = _GetEndTokenAndContents( |
- self.type_start_token) |
- if self.type is not None: |
- self.type = self.type.strip() |
- self.jstype = typeannotation.Parse(flag_token, self.type_end_token, |
- error_handler) |
- |
- # Extract name, if applicable. |
- self.name_token = None |
- self.name = None |
- if self.flag_type in self.HAS_NAME: |
- # Handle bad case, name could be immediately after flag token. |
- self.name_token = _GetNextPartialIdentifierToken(flag_token) |
- |
- # Handle good case, if found token is after type start, look for |
- # a identifier (substring to cover cases like [cnt] b/4197272) after |
- # type end, since types contain identifiers. |
- if (self.type and self.name_token and |
- tokenutil.Compare(self.name_token, self.type_start_token) > 0): |
- self.name_token = _GetNextPartialIdentifierToken(self.type_end_token) |
- |
- if self.name_token: |
- self.name = self.name_token.string |
- |
- # Extract description, if applicable. |
- self.description_start_token = None |
- self.description_end_token = None |
- self.description = None |
- if self.flag_type in self.HAS_DESCRIPTION: |
- search_start_token = flag_token |
- if self.name_token and self.type_end_token: |
- if tokenutil.Compare(self.type_end_token, self.name_token) > 0: |
- search_start_token = self.type_end_token |
- else: |
- search_start_token = self.name_token |
- elif self.name_token: |
- search_start_token = self.name_token |
- elif self.type: |
- search_start_token = self.type_end_token |
- |
- interesting_token = tokenutil.Search(search_start_token, |
- Type.FLAG_DESCRIPTION_TYPES | Type.FLAG_ENDING_TYPES) |
- if interesting_token.type in Type.FLAG_DESCRIPTION_TYPES: |
- self.description_start_token = interesting_token |
- self.description_end_token, self.description = ( |
- _GetEndTokenAndContents(interesting_token)) |
- |
- def HasType(self): |
- """Returns whether this flag should have a type annotation.""" |
- return self.flag_type in self.HAS_TYPE |
- |
- def __repr__(self): |
- return '<Flag: %s, type:%s>' % (self.flag_type, repr(self.jstype)) |
- |
- |
-class DocComment(object): |
- """JavaScript doc comment object. |
- |
- Attributes: |
- ordered_params: Ordered list of parameters documented. |
- start_token: The token that starts the doc comment. |
- end_token: The token that ends the doc comment. |
- suppressions: Map of suppression type to the token that added it. |
- """ |
- def __init__(self, start_token): |
- """Create the doc comment object. |
- |
- Args: |
- start_token: The first token in the doc comment. |
- """ |
- self.__flags = [] |
- self.start_token = start_token |
- self.end_token = None |
- self.suppressions = {} |
- self.invalidated = False |
- |
- @property |
- def ordered_params(self): |
- """Gives the list of parameter names as a list of strings.""" |
- params = [] |
- for flag in self.__flags: |
- if flag.flag_type == 'param' and flag.name: |
- params.append(flag.name) |
- return params |
- |
- def Invalidate(self): |
- """Indicate that the JSDoc is well-formed but we had problems parsing it. |
- |
- This is a short-circuiting mechanism so that we don't emit false |
- positives about well-formed doc comments just because we don't support |
- hot new syntaxes. |
- """ |
- self.invalidated = True |
- |
- def IsInvalidated(self): |
- """Test whether Invalidate() has been called.""" |
- return self.invalidated |
- |
- def AddSuppression(self, token): |
- """Add a new error suppression flag. |
- |
- Args: |
- token: The suppression flag token. |
- """ |
- flag = token and token.attached_object |
- if flag and flag.jstype: |
- for suppression in flag.jstype.IterIdentifiers(): |
- self.suppressions[suppression] = token |
- |
- def SuppressionOnly(self): |
- """Returns whether this comment contains only suppression flags.""" |
- if not self.__flags: |
- return False |
- |
- for flag in self.__flags: |
- if flag.flag_type != 'suppress': |
- return False |
- |
- return True |
- |
- def AddFlag(self, flag): |
- """Add a new document flag. |
- |
- Args: |
- flag: DocFlag object. |
- """ |
- self.__flags.append(flag) |
- |
- def InheritsDocumentation(self): |
- """Test if the jsdoc implies documentation inheritance. |
- |
- Returns: |
- True if documentation may be pulled off the superclass. |
- """ |
- return self.HasFlag('inheritDoc') or self.HasFlag('override') |
- |
- def HasFlag(self, flag_type): |
- """Test if the given flag has been set. |
- |
- Args: |
- flag_type: The type of the flag to check. |
- |
- Returns: |
- True if the flag is set. |
- """ |
- for flag in self.__flags: |
- if flag.flag_type == flag_type: |
- return True |
- return False |
- |
- def GetFlag(self, flag_type): |
- """Gets the last flag of the given type. |
- |
- Args: |
- flag_type: The type of the flag to get. |
- |
- Returns: |
- The last instance of the given flag type in this doc comment. |
- """ |
- for flag in reversed(self.__flags): |
- if flag.flag_type == flag_type: |
- return flag |
- |
- def GetDocFlags(self): |
- """Return the doc flags for this comment.""" |
- return list(self.__flags) |
- |
- def _YieldDescriptionTokens(self): |
- for token in self.start_token: |
- |
- if (token is self.end_token or |
- token.type is javascripttokens.JavaScriptTokenType.DOC_FLAG or |
- token.type not in javascripttokens.JavaScriptTokenType.COMMENT_TYPES): |
- return |
- |
- if token.type not in [ |
- javascripttokens.JavaScriptTokenType.START_DOC_COMMENT, |
- javascripttokens.JavaScriptTokenType.END_DOC_COMMENT, |
- javascripttokens.JavaScriptTokenType.DOC_PREFIX]: |
- yield token |
- |
- @property |
- def description(self): |
- return tokenutil.TokensToString( |
- self._YieldDescriptionTokens()) |
- |
- def GetTargetIdentifier(self): |
- """Returns the identifier (as a string) that this is a comment for. |
- |
- Note that this uses method uses GetIdentifierForToken to get the full |
- identifier, even if broken up by whitespace, newlines, or comments, |
- and thus could be longer than GetTargetToken().string. |
- |
- Returns: |
- The identifier for the token this comment is for. |
- """ |
- token = self.GetTargetToken() |
- if token: |
- return tokenutil.GetIdentifierForToken(token) |
- |
- def GetTargetToken(self): |
- """Get this comment's target token. |
- |
- Returns: |
- The token that is the target of this comment, or None if there isn't one. |
- """ |
- |
- # File overviews describe the file, not a token. |
- if self.HasFlag('fileoverview'): |
- return |
- |
- skip_types = frozenset([ |
- Type.WHITESPACE, |
- Type.BLANK_LINE, |
- Type.START_PAREN]) |
- |
- target_types = frozenset([ |
- Type.FUNCTION_NAME, |
- Type.IDENTIFIER, |
- Type.SIMPLE_LVALUE]) |
- |
- token = self.end_token.next |
- while token: |
- if token.type in target_types: |
- return token |
- |
- # Handles the case of a comment on "var foo = ...' |
- if token.IsKeyword('var'): |
- next_code_token = tokenutil.CustomSearch( |
- token, |
- lambda t: t.type not in Type.NON_CODE_TYPES) |
- |
- if (next_code_token and |
- next_code_token.IsType(Type.SIMPLE_LVALUE)): |
- return next_code_token |
- |
- return |
- |
- # Handles the case of a comment on "function foo () {}" |
- if token.type is Type.FUNCTION_DECLARATION: |
- next_code_token = tokenutil.CustomSearch( |
- token, |
- lambda t: t.type not in Type.NON_CODE_TYPES) |
- |
- if next_code_token.IsType(Type.FUNCTION_NAME): |
- return next_code_token |
- |
- return |
- |
- # Skip types will end the search. |
- if token.type not in skip_types: |
- return |
- |
- token = token.next |
- |
- def CompareParameters(self, params): |
- """Computes the edit distance and list from the function params to the docs. |
- |
- Uses the Levenshtein edit distance algorithm, with code modified from |
- http://en.wikibooks.org/wiki/Algorithm_implementation/Strings/Levenshtein_distance#Python |
- |
- Args: |
- params: The parameter list for the function declaration. |
- |
- Returns: |
- The edit distance, the edit list. |
- """ |
- source_len, target_len = len(self.ordered_params), len(params) |
- edit_lists = [[]] |
- distance = [[]] |
- for i in range(target_len+1): |
- edit_lists[0].append(['I'] * i) |
- distance[0].append(i) |
- |
- for j in range(1, source_len+1): |
- edit_lists.append([['D'] * j]) |
- distance.append([j]) |
- |
- for i in range(source_len): |
- for j in range(target_len): |
- cost = 1 |
- if self.ordered_params[i] == params[j]: |
- cost = 0 |
- |
- deletion = distance[i][j+1] + 1 |
- insertion = distance[i+1][j] + 1 |
- substitution = distance[i][j] + cost |
- |
- edit_list = None |
- best = None |
- if deletion <= insertion and deletion <= substitution: |
- # Deletion is best. |
- best = deletion |
- edit_list = list(edit_lists[i][j+1]) |
- edit_list.append('D') |
- |
- elif insertion <= substitution: |
- # Insertion is best. |
- best = insertion |
- edit_list = list(edit_lists[i+1][j]) |
- edit_list.append('I') |
- edit_lists[i+1].append(edit_list) |
- |
- else: |
- # Substitution is best. |
- best = substitution |
- edit_list = list(edit_lists[i][j]) |
- if cost: |
- edit_list.append('S') |
- else: |
- edit_list.append('=') |
- |
- edit_lists[i+1].append(edit_list) |
- distance[i+1].append(best) |
- |
- return distance[source_len][target_len], edit_lists[source_len][target_len] |
- |
- def __repr__(self): |
- """Returns a string representation of this object. |
- |
- Returns: |
- A string representation of this object. |
- """ |
- return '<DocComment: %s, %s>' % ( |
- str(self.ordered_params), str(self.__flags)) |
- |
- |
-# |
-# Helper methods used by DocFlag and DocComment to parse out flag information. |
-# |
- |
- |
-def _GetMatchingEndBraceAndContents(start_brace): |
- """Returns the matching end brace and contents between the two braces. |
- |
- If any FLAG_ENDING_TYPE token is encountered before a matching end brace, then |
- that token is used as the matching ending token. Contents will have all |
- comment prefixes stripped out of them, and all comment prefixes in between the |
- start and end tokens will be split out into separate DOC_PREFIX tokens. |
- |
- Args: |
- start_brace: The DOC_START_BRACE token immediately before desired contents. |
- |
- Returns: |
- The matching ending token (DOC_END_BRACE or FLAG_ENDING_TYPE) and a string |
- of the contents between the matching tokens, minus any comment prefixes. |
- """ |
- open_count = 1 |
- close_count = 0 |
- contents = [] |
- |
- # We don't consider the start brace part of the type string. |
- token = start_brace.next |
- while open_count != close_count: |
- if token.type == Type.DOC_START_BRACE: |
- open_count += 1 |
- elif token.type == Type.DOC_END_BRACE: |
- close_count += 1 |
- |
- if token.type != Type.DOC_PREFIX: |
- contents.append(token.string) |
- |
- if token.type in Type.FLAG_ENDING_TYPES: |
- break |
- token = token.next |
- |
- #Don't include the end token (end brace, end doc comment, etc.) in type. |
- token = token.previous |
- contents = contents[:-1] |
- |
- return token, ''.join(contents) |
- |
- |
-def _GetNextPartialIdentifierToken(start_token): |
- """Returns the first token having identifier as substring after a token. |
- |
- Searches each token after the start to see if it contains an identifier. |
- If found, token is returned. If no identifier is found returns None. |
- Search is abandoned when a FLAG_ENDING_TYPE token is found. |
- |
- Args: |
- start_token: The token to start searching after. |
- |
- Returns: |
- The token found containing identifier, None otherwise. |
- """ |
- token = start_token.next |
- |
- while token and token.type not in Type.FLAG_ENDING_TYPES: |
- match = javascripttokenizer.JavaScriptTokenizer.IDENTIFIER.search( |
- token.string) |
- if match is not None and token.type == Type.COMMENT: |
- return token |
- |
- token = token.next |
- |
- return None |
- |
- |
-def _GetEndTokenAndContents(start_token): |
- """Returns last content token and all contents before FLAG_ENDING_TYPE token. |
- |
- Comment prefixes are split into DOC_PREFIX tokens and stripped from the |
- returned contents. |
- |
- Args: |
- start_token: The token immediately before the first content token. |
- |
- Returns: |
- The last content token and a string of all contents including start and |
- end tokens, with comment prefixes stripped. |
- """ |
- iterator = start_token |
- last_line = iterator.line_number |
- last_token = None |
- contents = '' |
- doc_depth = 0 |
- while not iterator.type in Type.FLAG_ENDING_TYPES or doc_depth > 0: |
- if (iterator.IsFirstInLine() and |
- DocFlag.EMPTY_COMMENT_LINE.match(iterator.line)): |
- # If we have a blank comment line, consider that an implicit |
- # ending of the description. This handles a case like: |
- # |
- # * @return {boolean} True |
- # * |
- # * Note: This is a sentence. |
- # |
- # The note is not part of the @return description, but there was |
- # no definitive ending token. Rather there was a line containing |
- # only a doc comment prefix or whitespace. |
- break |
- |
- # b/2983692 |
- # don't prematurely match against a @flag if inside a doc flag |
- # need to think about what is the correct behavior for unterminated |
- # inline doc flags |
- if (iterator.type == Type.DOC_START_BRACE and |
- iterator.next.type == Type.DOC_INLINE_FLAG): |
- doc_depth += 1 |
- elif (iterator.type == Type.DOC_END_BRACE and |
- doc_depth > 0): |
- doc_depth -= 1 |
- |
- if iterator.type in Type.FLAG_DESCRIPTION_TYPES: |
- contents += iterator.string |
- last_token = iterator |
- |
- iterator = iterator.next |
- if iterator.line_number != last_line: |
- contents += '\n' |
- last_line = iterator.line_number |
- |
- end_token = last_token |
- if DocFlag.EMPTY_STRING.match(contents): |
- contents = None |
- else: |
- # Strip trailing newline. |
- contents = contents[:-1] |
- |
- return end_token, contents |
- |
- |
-class Function(object): |
- """Data about a JavaScript function. |
- |
- Attributes: |
- block_depth: Block depth the function began at. |
- doc: The DocComment associated with the function. |
- has_return: If the function has a return value. |
- has_this: If the function references the 'this' object. |
- is_assigned: If the function is part of an assignment. |
- is_constructor: If the function is a constructor. |
- name: The name of the function, whether given in the function keyword or |
- as the lvalue the function is assigned to. |
- start_token: First token of the function (the function' keyword token). |
- end_token: Last token of the function (the closing '}' token). |
- parameters: List of parameter names. |
- """ |
- |
- def __init__(self, block_depth, is_assigned, doc, name): |
- self.block_depth = block_depth |
- self.is_assigned = is_assigned |
- self.is_constructor = doc and doc.HasFlag('constructor') |
- self.is_interface = doc and doc.HasFlag('interface') |
- self.has_return = False |
- self.has_throw = False |
- self.has_this = False |
- self.name = name |
- self.doc = doc |
- self.start_token = None |
- self.end_token = None |
- self.parameters = None |
- |
- |
-class StateTracker(object): |
- """EcmaScript state tracker. |
- |
- Tracks block depth, function names, etc. within an EcmaScript token stream. |
- """ |
- |
- OBJECT_LITERAL = 'o' |
- CODE = 'c' |
- |
- def __init__(self, doc_flag=DocFlag): |
- """Initializes a JavaScript token stream state tracker. |
- |
- Args: |
- doc_flag: An optional custom DocFlag used for validating |
- documentation flags. |
- """ |
- self._doc_flag = doc_flag |
- self.Reset() |
- |
- def Reset(self): |
- """Resets the state tracker to prepare for processing a new page.""" |
- self._block_depth = 0 |
- self._is_block_close = False |
- self._paren_depth = 0 |
- self._function_stack = [] |
- self._functions_by_name = {} |
- self._last_comment = None |
- self._doc_comment = None |
- self._cumulative_params = None |
- self._block_types = [] |
- self._last_non_space_token = None |
- self._last_line = None |
- self._first_token = None |
- self._documented_identifiers = set() |
- self._variables_in_scope = [] |
- |
- def DocFlagPass(self, start_token, error_handler): |
- """Parses doc flags. |
- |
- This pass needs to be executed before the aliaspass and we don't want to do |
- a full-blown statetracker dry run for these. |
- |
- Args: |
- start_token: The token at which to start iterating |
- error_handler: An error handler for error reporting. |
- """ |
- if not start_token: |
- return |
- doc_flag_types = (Type.DOC_FLAG, Type.DOC_INLINE_FLAG) |
- for token in start_token: |
- if token.type in doc_flag_types: |
- token.attached_object = self._doc_flag(token, error_handler) |
- |
- def InFunction(self): |
- """Returns true if the current token is within a function. |
- |
- Returns: |
- True if the current token is within a function. |
- """ |
- return bool(self._function_stack) |
- |
- def InConstructor(self): |
- """Returns true if the current token is within a constructor. |
- |
- Returns: |
- True if the current token is within a constructor. |
- """ |
- return self.InFunction() and self._function_stack[-1].is_constructor |
- |
- def InInterfaceMethod(self): |
- """Returns true if the current token is within an interface method. |
- |
- Returns: |
- True if the current token is within an interface method. |
- """ |
- if self.InFunction(): |
- if self._function_stack[-1].is_interface: |
- return True |
- else: |
- name = self._function_stack[-1].name |
- prototype_index = name.find('.prototype.') |
- if prototype_index != -1: |
- class_function_name = name[0:prototype_index] |
- if (class_function_name in self._functions_by_name and |
- self._functions_by_name[class_function_name].is_interface): |
- return True |
- |
- return False |
- |
- def InTopLevelFunction(self): |
- """Returns true if the current token is within a top level function. |
- |
- Returns: |
- True if the current token is within a top level function. |
- """ |
- return len(self._function_stack) == 1 and self.InTopLevel() |
- |
- def InAssignedFunction(self): |
- """Returns true if the current token is within a function variable. |
- |
- Returns: |
- True if if the current token is within a function variable |
- """ |
- return self.InFunction() and self._function_stack[-1].is_assigned |
- |
- def IsFunctionOpen(self): |
- """Returns true if the current token is a function block open. |
- |
- Returns: |
- True if the current token is a function block open. |
- """ |
- return (self._function_stack and |
- self._function_stack[-1].block_depth == self._block_depth - 1) |
- |
- def IsFunctionClose(self): |
- """Returns true if the current token is a function block close. |
- |
- Returns: |
- True if the current token is a function block close. |
- """ |
- return (self._function_stack and |
- self._function_stack[-1].block_depth == self._block_depth) |
- |
- def InBlock(self): |
- """Returns true if the current token is within a block. |
- |
- Returns: |
- True if the current token is within a block. |
- """ |
- return bool(self._block_depth) |
- |
- def IsBlockClose(self): |
- """Returns true if the current token is a block close. |
- |
- Returns: |
- True if the current token is a block close. |
- """ |
- return self._is_block_close |
- |
- def InObjectLiteral(self): |
- """Returns true if the current token is within an object literal. |
- |
- Returns: |
- True if the current token is within an object literal. |
- """ |
- return self._block_depth and self._block_types[-1] == self.OBJECT_LITERAL |
- |
- def InObjectLiteralDescendant(self): |
- """Returns true if the current token has an object literal ancestor. |
- |
- Returns: |
- True if the current token has an object literal ancestor. |
- """ |
- return self.OBJECT_LITERAL in self._block_types |
- |
- def InParentheses(self): |
- """Returns true if the current token is within parentheses. |
- |
- Returns: |
- True if the current token is within parentheses. |
- """ |
- return bool(self._paren_depth) |
- |
- def ParenthesesDepth(self): |
- """Returns the number of parens surrounding the token. |
- |
- Returns: |
- The number of parenthesis surrounding the token. |
- """ |
- return self._paren_depth |
- |
- def BlockDepth(self): |
- """Returns the number of blocks in which the token is nested. |
- |
- Returns: |
- The number of blocks in which the token is nested. |
- """ |
- return self._block_depth |
- |
- def FunctionDepth(self): |
- """Returns the number of functions in which the token is nested. |
- |
- Returns: |
- The number of functions in which the token is nested. |
- """ |
- return len(self._function_stack) |
- |
- def InTopLevel(self): |
- """Whether we are at the top level in the class. |
- |
- This function call is language specific. In some languages like |
- JavaScript, a function is top level if it is not inside any parenthesis. |
- In languages such as ActionScript, a function is top level if it is directly |
- within a class. |
- """ |
- raise TypeError('Abstract method InTopLevel not implemented') |
- |
- def GetBlockType(self, token): |
- """Determine the block type given a START_BLOCK token. |
- |
- Code blocks come after parameters, keywords like else, and closing parens. |
- |
- Args: |
- token: The current token. Can be assumed to be type START_BLOCK. |
- Returns: |
- Code block type for current token. |
- """ |
- raise TypeError('Abstract method GetBlockType not implemented') |
- |
- def GetParams(self): |
- """Returns the accumulated input params as an array. |
- |
- In some EcmasSript languages, input params are specified like |
- (param:Type, param2:Type2, ...) |
- in other they are specified just as |
- (param, param2) |
- We handle both formats for specifying parameters here and leave |
- it to the compilers for each language to detect compile errors. |
- This allows more code to be reused between lint checkers for various |
- EcmaScript languages. |
- |
- Returns: |
- The accumulated input params as an array. |
- """ |
- params = [] |
- if self._cumulative_params: |
- params = re.compile(r'\s+').sub('', self._cumulative_params).split(',') |
- # Strip out the type from parameters of the form name:Type. |
- params = map(lambda param: param.split(':')[0], params) |
- |
- return params |
- |
- def GetLastComment(self): |
- """Return the last plain comment that could be used as documentation. |
- |
- Returns: |
- The last plain comment that could be used as documentation. |
- """ |
- return self._last_comment |
- |
- def GetDocComment(self): |
- """Return the most recent applicable documentation comment. |
- |
- Returns: |
- The last applicable documentation comment. |
- """ |
- return self._doc_comment |
- |
- def HasDocComment(self, identifier): |
- """Returns whether the identifier has been documented yet. |
- |
- Args: |
- identifier: The identifier. |
- |
- Returns: |
- Whether the identifier has been documented yet. |
- """ |
- return identifier in self._documented_identifiers |
- |
- def InDocComment(self): |
- """Returns whether the current token is in a doc comment. |
- |
- Returns: |
- Whether the current token is in a doc comment. |
- """ |
- return self._doc_comment and self._doc_comment.end_token is None |
- |
- def GetDocFlag(self): |
- """Returns the current documentation flags. |
- |
- Returns: |
- The current documentation flags. |
- """ |
- return self._doc_flag |
- |
- def IsTypeToken(self, t): |
- if self.InDocComment() and t.type not in (Type.START_DOC_COMMENT, |
- Type.DOC_FLAG, Type.DOC_INLINE_FLAG, Type.DOC_PREFIX): |
- f = tokenutil.SearchUntil(t, [Type.DOC_FLAG], [Type.START_DOC_COMMENT], |
- None, True) |
- if (f and f.attached_object.type_start_token is not None and |
- f.attached_object.type_end_token is not None): |
- return (tokenutil.Compare(t, f.attached_object.type_start_token) > 0 and |
- tokenutil.Compare(t, f.attached_object.type_end_token) < 0) |
- return False |
- |
- def GetFunction(self): |
- """Return the function the current code block is a part of. |
- |
- Returns: |
- The current Function object. |
- """ |
- if self._function_stack: |
- return self._function_stack[-1] |
- |
- def GetBlockDepth(self): |
- """Return the block depth. |
- |
- Returns: |
- The current block depth. |
- """ |
- return self._block_depth |
- |
- def GetLastNonSpaceToken(self): |
- """Return the last non whitespace token.""" |
- return self._last_non_space_token |
- |
- def GetLastLine(self): |
- """Return the last line.""" |
- return self._last_line |
- |
- def GetFirstToken(self): |
- """Return the very first token in the file.""" |
- return self._first_token |
- |
- def IsVariableInScope(self, token_string): |
- """Checks if string is variable in current scope. |
- |
- For given string it checks whether the string is a defined variable |
- (including function param) in current state. |
- |
- E.g. if variables defined (variables in current scope) is docs |
- then docs, docs.length etc will be considered as variable in current |
- scope. This will help in avoding extra goog.require for variables. |
- |
- Args: |
- token_string: String to check if its is a variable in current scope. |
- |
- Returns: |
- true if given string is a variable in current scope. |
- """ |
- for variable in self._variables_in_scope: |
- if (token_string == variable |
- or token_string.startswith(variable + '.')): |
- return True |
- |
- return False |
- |
- def HandleToken(self, token, last_non_space_token): |
- """Handles the given token and updates state. |
- |
- Args: |
- token: The token to handle. |
- last_non_space_token: |
- """ |
- self._is_block_close = False |
- |
- if not self._first_token: |
- self._first_token = token |
- |
- # Track block depth. |
- type = token.type |
- if type == Type.START_BLOCK: |
- self._block_depth += 1 |
- |
- # Subclasses need to handle block start very differently because |
- # whether a block is a CODE or OBJECT_LITERAL block varies significantly |
- # by language. |
- self._block_types.append(self.GetBlockType(token)) |
- |
- # When entering a function body, record its parameters. |
- if self.InFunction(): |
- function = self._function_stack[-1] |
- if self._block_depth == function.block_depth + 1: |
- function.parameters = self.GetParams() |
- |
- # Track block depth. |
- elif type == Type.END_BLOCK: |
- self._is_block_close = not self.InObjectLiteral() |
- self._block_depth -= 1 |
- self._block_types.pop() |
- |
- # Track parentheses depth. |
- elif type == Type.START_PAREN: |
- self._paren_depth += 1 |
- |
- # Track parentheses depth. |
- elif type == Type.END_PAREN: |
- self._paren_depth -= 1 |
- |
- elif type == Type.COMMENT: |
- self._last_comment = token.string |
- |
- elif type == Type.START_DOC_COMMENT: |
- self._last_comment = None |
- self._doc_comment = DocComment(token) |
- |
- elif type == Type.END_DOC_COMMENT: |
- self._doc_comment.end_token = token |
- |
- elif type in (Type.DOC_FLAG, Type.DOC_INLINE_FLAG): |
- # Don't overwrite flags if they were already parsed in a previous pass. |
- if token.attached_object is None: |
- flag = self._doc_flag(token) |
- token.attached_object = flag |
- else: |
- flag = token.attached_object |
- self._doc_comment.AddFlag(flag) |
- |
- if flag.flag_type == 'suppress': |
- self._doc_comment.AddSuppression(token) |
- |
- elif type == Type.FUNCTION_DECLARATION: |
- last_code = tokenutil.SearchExcept(token, Type.NON_CODE_TYPES, None, |
- True) |
- doc = None |
- # Only top-level functions are eligible for documentation. |
- if self.InTopLevel(): |
- doc = self._doc_comment |
- |
- name = '' |
- is_assigned = last_code and (last_code.IsOperator('=') or |
- last_code.IsOperator('||') or last_code.IsOperator('&&') or |
- (last_code.IsOperator(':') and not self.InObjectLiteral())) |
- if is_assigned: |
- # TODO(robbyw): This breaks for x[2] = ... |
- # Must use loop to find full function name in the case of line-wrapped |
- # declarations (bug 1220601) like: |
- # my.function.foo. |
- # bar = function() ... |
- identifier = tokenutil.Search(last_code, Type.SIMPLE_LVALUE, None, True) |
- while identifier and tokenutil.IsIdentifierOrDot(identifier): |
- name = identifier.string + name |
- # Traverse behind us, skipping whitespace and comments. |
- while True: |
- identifier = identifier.previous |
- if not identifier or not identifier.type in Type.NON_CODE_TYPES: |
- break |
- |
- else: |
- next_token = tokenutil.SearchExcept(token, Type.NON_CODE_TYPES) |
- while next_token and next_token.IsType(Type.FUNCTION_NAME): |
- name += next_token.string |
- next_token = tokenutil.Search(next_token, Type.FUNCTION_NAME, 2) |
- |
- function = Function(self._block_depth, is_assigned, doc, name) |
- function.start_token = token |
- |
- self._function_stack.append(function) |
- self._functions_by_name[name] = function |
- |
- # Add a delimiter in stack for scope variables to define start of |
- # function. This helps in popping variables of this function when |
- # function declaration ends. |
- self._variables_in_scope.append('') |
- |
- elif type == Type.START_PARAMETERS: |
- self._cumulative_params = '' |
- |
- elif type == Type.PARAMETERS: |
- self._cumulative_params += token.string |
- self._variables_in_scope.extend(self.GetParams()) |
- |
- elif type == Type.KEYWORD and token.string == 'return': |
- next_token = tokenutil.SearchExcept(token, Type.NON_CODE_TYPES) |
- if not next_token.IsType(Type.SEMICOLON): |
- function = self.GetFunction() |
- if function: |
- function.has_return = True |
- |
- elif type == Type.KEYWORD and token.string == 'throw': |
- function = self.GetFunction() |
- if function: |
- function.has_throw = True |
- |
- elif type == Type.KEYWORD and token.string == 'var': |
- function = self.GetFunction() |
- next_token = tokenutil.Search(token, [Type.IDENTIFIER, |
- Type.SIMPLE_LVALUE]) |
- |
- if next_token: |
- if next_token.type == Type.SIMPLE_LVALUE: |
- self._variables_in_scope.append(next_token.values['identifier']) |
- else: |
- self._variables_in_scope.append(next_token.string) |
- |
- elif type == Type.SIMPLE_LVALUE: |
- identifier = token.values['identifier'] |
- jsdoc = self.GetDocComment() |
- if jsdoc: |
- self._documented_identifiers.add(identifier) |
- |
- self._HandleIdentifier(identifier, True) |
- |
- elif type == Type.IDENTIFIER: |
- self._HandleIdentifier(token.string, False) |
- |
- # Detect documented non-assignments. |
- next_token = tokenutil.SearchExcept(token, Type.NON_CODE_TYPES) |
- if next_token and next_token.IsType(Type.SEMICOLON): |
- if (self._last_non_space_token and |
- self._last_non_space_token.IsType(Type.END_DOC_COMMENT)): |
- self._documented_identifiers.add(token.string) |
- |
- def _HandleIdentifier(self, identifier, is_assignment): |
- """Process the given identifier. |
- |
- Currently checks if it references 'this' and annotates the function |
- accordingly. |
- |
- Args: |
- identifier: The identifer to process. |
- is_assignment: Whether the identifer is being written to. |
- """ |
- if identifier == 'this' or identifier.startswith('this.'): |
- function = self.GetFunction() |
- if function: |
- function.has_this = True |
- |
- def HandleAfterToken(self, token): |
- """Handle updating state after a token has been checked. |
- |
- This function should be used for destructive state changes such as |
- deleting a tracked object. |
- |
- Args: |
- token: The token to handle. |
- """ |
- type = token.type |
- if type == Type.SEMICOLON or type == Type.END_PAREN or ( |
- type == Type.END_BRACKET and |
- self._last_non_space_token.type not in ( |
- Type.SINGLE_QUOTE_STRING_END, Type.DOUBLE_QUOTE_STRING_END, |
- Type.TEMPLATE_STRING_END)): |
- # We end on any numeric array index, but keep going for string based |
- # array indices so that we pick up manually exported identifiers. |
- self._doc_comment = None |
- self._last_comment = None |
- |
- elif type == Type.END_BLOCK: |
- self._doc_comment = None |
- self._last_comment = None |
- |
- if self.InFunction() and self.IsFunctionClose(): |
- # TODO(robbyw): Detect the function's name for better errors. |
- function = self._function_stack.pop() |
- function.end_token = token |
- |
- # Pop all variables till delimiter ('') those were defined in the |
- # function being closed so make them out of scope. |
- while self._variables_in_scope and self._variables_in_scope[-1]: |
- self._variables_in_scope.pop() |
- |
- # Pop delimiter |
- if self._variables_in_scope: |
- self._variables_in_scope.pop() |
- |
- elif type == Type.END_PARAMETERS and self._doc_comment: |
- self._doc_comment = None |
- self._last_comment = None |
- |
- if not token.IsAnyType(Type.WHITESPACE, Type.BLANK_LINE): |
- self._last_non_space_token = token |
- |
- self._last_line = token.line |