| 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
|
| index 52e86a900e28a0533e78afe50b3460e1bbb64f75..4df205c2c2e69f0d31575e230fac043eab9e02ac 100755
|
| --- a/third_party/closure_linter/closure_linter/statetracker.py
|
| +++ b/third_party/closure_linter/closure_linter/statetracker.py
|
| @@ -53,35 +53,47 @@ class DocFlag(object):
|
| 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',
|
| + 'package',
|
| 'param',
|
| 'preserve',
|
| 'private',
|
| + 'protected',
|
| + 'public',
|
| 'return',
|
| 'see',
|
| + 'stableIdGenerator',
|
| + 'struct',
|
| 'supported',
|
| 'template',
|
| 'this',
|
| 'type',
|
| 'typedef',
|
| + 'unrestricted',
|
| ])
|
|
|
| ANNOTATION = frozenset(['preserveTry', 'suppress'])
|
| @@ -100,6 +112,7 @@ class DocFlag(object):
|
| 'accessControls',
|
| 'ambiguousFunctionDecl',
|
| 'checkRegExp',
|
| + 'checkStructDictInheritance',
|
| 'checkTypes',
|
| 'checkVars',
|
| 'const',
|
| @@ -117,6 +130,7 @@ class DocFlag(object):
|
| 'missingProperties',
|
| 'missingProvide',
|
| 'missingRequire',
|
| + 'missingReturn',
|
| 'nonStandardJsDocs',
|
| 'strictModuleDepCheck',
|
| 'tweakValidation',
|
| @@ -125,19 +139,25 @@ class DocFlag(object):
|
| 'undefinedVars',
|
| 'underscore',
|
| 'unknownDefines',
|
| + 'unnecessaryCasts',
|
| + 'unusedPrivateMembers',
|
| 'uselessCode',
|
| 'visibility',
|
| 'with'])
|
|
|
| HAS_DESCRIPTION = frozenset([
|
| - 'define', 'deprecated', 'desc', 'fileoverview', 'license', 'param',
|
| - 'preserve', 'return', 'supported'])
|
| + 'define', 'deprecated', 'desc', 'fileoverview', 'license', 'param',
|
| + 'preserve', 'return', 'supported'])
|
|
|
| HAS_TYPE = frozenset([
|
| 'define', 'enum', 'extends', 'implements', 'param', 'return', 'type',
|
| - 'suppress'])
|
| + 'suppress', 'const', 'package', 'private', 'protected', 'public'])
|
|
|
| - TYPE_ONLY = frozenset(['enum', 'extends', 'implements', 'suppress', 'type'])
|
| + CAN_OMIT_TYPE = frozenset(['enum', 'const', 'package', 'private',
|
| + 'protected', 'public'])
|
| +
|
| + TYPE_ONLY = frozenset(['enum', 'extends', 'implements', 'suppress', 'type',
|
| + 'const', 'package', 'private', 'protected', 'public'])
|
|
|
| HAS_NAME = frozenset(['param'])
|
|
|
| @@ -166,7 +186,11 @@ class DocFlag(object):
|
| 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):
|
| + 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)
|
| @@ -178,13 +202,14 @@ class DocFlag(object):
|
| self.name = None
|
| if self.flag_type in self.HAS_NAME:
|
| # Handle bad case, name could be immediately after flag token.
|
| - self.name_token = _GetNextIdentifierToken(flag_token)
|
| + self.name_token = _GetNextPartialIdentifierToken(flag_token)
|
|
|
| # Handle good case, if found token is after type start, look for
|
| - # identifier after type end, since types contain identifiers.
|
| + # 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 = _GetNextIdentifierToken(self.type_end_token)
|
| + self.name_token = _GetNextPartialIdentifierToken(self.type_end_token)
|
|
|
| if self.name_token:
|
| self.name = self.name_token.string
|
| @@ -228,14 +253,21 @@ class DocComment(object):
|
| Args:
|
| start_token: The first token in the doc comment.
|
| """
|
| - self.__params = {}
|
| - self.ordered_params = []
|
| - self.__flags = {}
|
| + 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.
|
|
|
| @@ -249,16 +281,6 @@ class DocComment(object):
|
| """Test whether Invalidate() has been called."""
|
| return self.invalidated
|
|
|
| - def AddParam(self, name, param_type):
|
| - """Add a new documented parameter.
|
| -
|
| - Args:
|
| - name: The name of the parameter to document.
|
| - param_type: The parameter's declared JavaScript type.
|
| - """
|
| - self.ordered_params.append(name)
|
| - self.__params[name] = param_type
|
| -
|
| def AddSuppression(self, token):
|
| """Add a new error suppression flag.
|
|
|
| @@ -275,9 +297,13 @@ class DocComment(object):
|
|
|
| def SuppressionOnly(self):
|
| """Returns whether this comment contains only suppression flags."""
|
| - for flag_type in self.__flags.keys():
|
| - if flag_type != 'suppress':
|
| + if not self.__flags:
|
| + return False
|
| +
|
| + for flag in self.__flags:
|
| + if flag.flag_type != 'suppress':
|
| return False
|
| +
|
| return True
|
|
|
| def AddFlag(self, flag):
|
| @@ -286,7 +312,7 @@ class DocComment(object):
|
| Args:
|
| flag: DocFlag object.
|
| """
|
| - self.__flags[flag.flag_type] = flag
|
| + self.__flags.append(flag)
|
|
|
| def InheritsDocumentation(self):
|
| """Test if the jsdoc implies documentation inheritance.
|
| @@ -305,7 +331,10 @@ class DocComment(object):
|
| Returns:
|
| True if the flag is set.
|
| """
|
| - return flag_type in self.__flags
|
| + 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.
|
| @@ -316,7 +345,101 @@ class DocComment(object):
|
| Returns:
|
| The last instance of the given flag type in this doc comment.
|
| """
|
| - return self.__flags[flag_type]
|
| + 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.
|
| @@ -386,7 +509,8 @@ class DocComment(object):
|
| Returns:
|
| A string representation of this object.
|
| """
|
| - return '<DocComment: %s, %s>' % (str(self.__params), str(self.__flags))
|
| + return '<DocComment: %s, %s>' % (
|
| + str(self.ordered_params), str(self.__flags))
|
|
|
|
|
| #
|
| @@ -435,28 +559,25 @@ def _GetMatchingEndBraceAndContents(start_brace):
|
| return token, ''.join(contents)
|
|
|
|
|
| -def _GetNextIdentifierToken(start_token):
|
| - """Searches for and returns the first identifier at the beginning of a token.
|
| +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 starts with an identifier.
|
| - If found, will split the token into at most 3 piecies: leading whitespace,
|
| - identifier, rest of token, returning the identifier token. If no identifier is
|
| - found returns None and changes no tokens. Search is abandoned when a
|
| - FLAG_ENDING_TYPE token is found.
|
| + 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 identifier token is found, None otherwise.
|
| + The token found containing identifier, None otherwise.
|
| """
|
| token = start_token.next
|
|
|
| - while token and not token.type in Type.FLAG_ENDING_TYPES:
|
| - match = javascripttokenizer.JavaScriptTokenizer.IDENTIFIER.match(
|
| + 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 and
|
| - len(token.string) == len(match.group(0))):
|
| + if match is not None and token.type == Type.COMMENT:
|
| return token
|
|
|
| token = token.next
|
| @@ -539,6 +660,9 @@ class Function(object):
|
| 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):
|
| @@ -551,6 +675,9 @@ class Function(object):
|
| self.has_this = False
|
| self.name = name
|
| self.doc = doc
|
| + self.start_token = None
|
| + self.end_token = None
|
| + self.parameters = None
|
|
|
|
|
| class StateTracker(object):
|
| @@ -577,7 +704,7 @@ class StateTracker(object):
|
| self._block_depth = 0
|
| self._is_block_close = False
|
| self._paren_depth = 0
|
| - self._functions = []
|
| + self._function_stack = []
|
| self._functions_by_name = {}
|
| self._last_comment = None
|
| self._doc_comment = None
|
| @@ -587,6 +714,7 @@ class StateTracker(object):
|
| self._last_line = None
|
| self._first_token = None
|
| self._documented_identifiers = set()
|
| + self._variables_in_scope = []
|
|
|
| def InFunction(self):
|
| """Returns true if the current token is within a function.
|
| @@ -594,7 +722,7 @@ class StateTracker(object):
|
| Returns:
|
| True if the current token is within a function.
|
| """
|
| - return bool(self._functions)
|
| + return bool(self._function_stack)
|
|
|
| def InConstructor(self):
|
| """Returns true if the current token is within a constructor.
|
| @@ -602,7 +730,7 @@ class StateTracker(object):
|
| Returns:
|
| True if the current token is within a constructor.
|
| """
|
| - return self.InFunction() and self._functions[-1].is_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.
|
| @@ -611,10 +739,10 @@ class StateTracker(object):
|
| True if the current token is within an interface method.
|
| """
|
| if self.InFunction():
|
| - if self._functions[-1].is_interface:
|
| + if self._function_stack[-1].is_interface:
|
| return True
|
| else:
|
| - name = self._functions[-1].name
|
| + name = self._function_stack[-1].name
|
| prototype_index = name.find('.prototype.')
|
| if prototype_index != -1:
|
| class_function_name = name[0:prototype_index]
|
| @@ -630,7 +758,7 @@ class StateTracker(object):
|
| Returns:
|
| True if the current token is within a top level function.
|
| """
|
| - return len(self._functions) == 1 and self.InTopLevel()
|
| + return len(self._function_stack) == 1 and self.InTopLevel()
|
|
|
| def InAssignedFunction(self):
|
| """Returns true if the current token is within a function variable.
|
| @@ -638,7 +766,7 @@ class StateTracker(object):
|
| Returns:
|
| True if if the current token is within a function variable
|
| """
|
| - return self.InFunction() and self._functions[-1].is_assigned
|
| + return self.InFunction() and self._function_stack[-1].is_assigned
|
|
|
| def IsFunctionOpen(self):
|
| """Returns true if the current token is a function block open.
|
| @@ -646,8 +774,8 @@ class StateTracker(object):
|
| Returns:
|
| True if the current token is a function block open.
|
| """
|
| - return (self._functions and
|
| - self._functions[-1].block_depth == self._block_depth - 1)
|
| + 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.
|
| @@ -655,8 +783,8 @@ class StateTracker(object):
|
| Returns:
|
| True if the current token is a function block close.
|
| """
|
| - return (self._functions and
|
| - self._functions[-1].block_depth == self._block_depth)
|
| + 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.
|
| @@ -698,6 +826,30 @@ class StateTracker(object):
|
| """
|
| 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.
|
|
|
| @@ -791,7 +943,8 @@ class StateTracker(object):
|
| 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:
|
| + 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
|
| @@ -802,8 +955,8 @@ class StateTracker(object):
|
| Returns:
|
| The current Function object.
|
| """
|
| - if self._functions:
|
| - return self._functions[-1]
|
| + if self._function_stack:
|
| + return self._function_stack[-1]
|
|
|
| def GetBlockDepth(self):
|
| """Return the block depth.
|
| @@ -825,6 +978,29 @@ class StateTracker(object):
|
| """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.
|
|
|
| @@ -847,6 +1023,12 @@ class StateTracker(object):
|
| # 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()
|
| @@ -876,9 +1058,7 @@ class StateTracker(object):
|
| token.attached_object = flag
|
| self._doc_comment.AddFlag(flag)
|
|
|
| - if flag.flag_type == 'param' and flag.name:
|
| - self._doc_comment.AddParam(flag.name, flag.type)
|
| - elif flag.flag_type == 'suppress':
|
| + if flag.flag_type == 'suppress':
|
| self._doc_comment.AddSuppression(token)
|
|
|
| elif type == Type.FUNCTION_DECLARATION:
|
| @@ -916,14 +1096,22 @@ class StateTracker(object):
|
| next_token = tokenutil.Search(next_token, Type.FUNCTION_NAME, 2)
|
|
|
| function = Function(self._block_depth, is_assigned, doc, name)
|
| - self._functions.append(function)
|
| + 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)
|
| @@ -937,6 +1125,17 @@ class StateTracker(object):
|
| 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()
|
| @@ -950,7 +1149,7 @@ class StateTracker(object):
|
|
|
| # Detect documented non-assignments.
|
| next_token = tokenutil.SearchExcept(token, Type.NON_CODE_TYPES)
|
| - if next_token.IsType(Type.SEMICOLON):
|
| + 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)
|
| @@ -970,7 +1169,6 @@ class StateTracker(object):
|
| if function:
|
| function.has_this = True
|
|
|
| -
|
| def HandleAfterToken(self, token):
|
| """Handle updating state after a token has been checked.
|
|
|
| @@ -996,7 +1194,17 @@ class StateTracker(object):
|
|
|
| if self.InFunction() and self.IsFunctionClose():
|
| # TODO(robbyw): Detect the function's name for better errors.
|
| - self._functions.pop()
|
| + 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
|
|
|