def _ProcessToken(self): """Process the given token.""" token = self._token token.metadata = self._CreateMetaData() context = (self._ProcessContext() or self._context) token.metadata.context = context token.metadata.last_code = self._last_code # Determine the operator type of the token, if applicable. if token.type == TokenType.OPERATOR: token.metadata.operator_type = self._GetOperatorType(token) # Determine if there is an implied semicolon after the token. if token.type != TokenType.SEMICOLON: next_code = tokenutil.SearchExcept(token, TokenType.NON_CODE_TYPES) # A statement like if (x) does not need a semicolon after it is_implied_block = self._context == EcmaContext.IMPLIED_BLOCK is_last_code_in_line = token.IsCode() and ( not next_code or next_code.line_number != token.line_number) is_continued_identifier = (token.type == TokenType.IDENTIFIER and token.string.endswith('.')) is_continued_operator = (token.type == TokenType.OPERATOR and not token.metadata.IsUnaryPostOperator()) is_continued_dot = token.string == '.' next_code_is_operator = next_code and next_code.type == TokenType.OPERATOR next_code_is_dot = next_code and next_code.string == '.' is_end_of_block = ( token.type == TokenType.END_BLOCK and token.metadata.context.type != EcmaContext.OBJECT_LITERAL) is_multiline_string = token.type == TokenType.STRING_TEXT is_continued_var_decl = ( token.IsKeyword('var') and next_code and (next_code.type in [TokenType.IDENTIFIER, TokenType.SIMPLE_LVALUE]) and token.line_number < next_code.line_number) next_code_is_block = next_code and next_code.type == TokenType.START_BLOCK if (is_last_code_in_line and self._StatementCouldEndInContext() and not is_multiline_string and not is_end_of_block and not is_continued_var_decl and not is_continued_identifier and not is_continued_operator and not is_continued_dot and not next_code_is_dot and not next_code_is_operator and not is_implied_block and not next_code_is_block): token.metadata.is_implied_semicolon = True self._EndStatement()
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. """ last_code = tokenutil.SearchExcept(token, Type.NON_CODE_TYPES, reverse=True) if last_code.type in (Type.END_PARAMETERS, Type.END_PAREN, Type.KEYWORD ) and not last_code.IsKeyword('return'): return self.CODE else: return self.OBJECT_LITERAL
def CheckToken(self, token, state): """Checks a token, given the current parser_state, for warnings and errors. Args: token: The current token under consideration state: parser_state object that indicates the current state in the page """ # Store some convenience variables first_in_line = token.IsFirstInLine() last_in_line = token.IsLastInLine() last_non_space_token = state.GetLastNonSpaceToken() token_type = token.type # Process the line change. if not self._is_html and error_check.ShouldCheck(Rule.INDENTATION): # TODO(robbyw): Support checking indentation in HTML files. indentation_errors = self._indentation.CheckToken(token, state) for indentation_error in indentation_errors: self._HandleError(*indentation_error) if last_in_line: self._CheckLineLength(token, state) if token_type == Type.PARAMETERS: # Find missing spaces in parameter lists. if self.MISSING_PARAMETER_SPACE.search(token.string): fix_data = ', '.join( [s.strip() for s in token.string.split(',')]) self._HandleError(errors.MISSING_SPACE, 'Missing space after ","', token, position=None, fix_data=fix_data.strip()) # Find extra spaces at the beginning of parameter lists. Make sure # we aren't at the beginning of a continuing multi-line list. if not first_in_line: space_count = len(token.string) - len(token.string.lstrip()) if space_count: self._HandleError(errors.EXTRA_SPACE, 'Extra space after "("', token, position=Position(0, space_count)) elif (token_type == Type.START_BLOCK and token.metadata.context.type == Context.BLOCK): self._CheckForMissingSpaceBeforeToken(token) elif token_type == Type.END_BLOCK: # This check is for object literal end block tokens, but there is no need # to test that condition since a comma at the end of any other kind of # block is undoubtedly a parse error. last_code = token.metadata.last_code if last_code.IsOperator(','): self._HandleError( errors.COMMA_AT_END_OF_LITERAL, 'Illegal comma at end of object literal', last_code, position=Position.All(last_code.string)) if state.InFunction() and state.IsFunctionClose(): is_immediately_called = (token.next and token.next.type == Type.START_PAREN) if state.InTopLevelFunction(): # A semicolons should not be included at the end of a function # declaration. if not state.InAssignedFunction(): if not last_in_line and token.next.type == Type.SEMICOLON: self._HandleError( errors.ILLEGAL_SEMICOLON_AFTER_FUNCTION, 'Illegal semicolon after function declaration', token.next, position=Position.All(token.next.string)) # A semicolon should be included at the end of a function expression # that is not immediately called. if state.InAssignedFunction(): if not is_immediately_called and ( last_in_line or token.next.type != Type.SEMICOLON): self._HandleError( errors.MISSING_SEMICOLON_AFTER_FUNCTION, 'Missing semicolon after function assigned to a variable', token, position=Position.AtEnd(token.string)) if state.InInterfaceMethod() and last_code.type != Type.START_BLOCK: self._HandleError(errors.INTERFACE_METHOD_CANNOT_HAVE_CODE, 'Interface methods cannot contain code', last_code) elif (state.IsBlockClose() and token.next and token.next.type == Type.SEMICOLON): if ( last_code.metadata.context.parent.type != Context.OBJECT_LITERAL and last_code.metadata.context.type != Context.OBJECT_LITERAL): self._HandleError( errors.REDUNDANT_SEMICOLON, 'No semicolon is required to end a code block', token.next, position=Position.All(token.next.string)) elif token_type == Type.SEMICOLON: if token.previous and token.previous.type == Type.WHITESPACE: self._HandleError( errors.EXTRA_SPACE, 'Extra space before ";"', token.previous, position=Position.All(token.previous.string)) if token.next and token.next.line_number == token.line_number: if token.metadata.context.type != Context.FOR_GROUP_BLOCK: # TODO(robbyw): Error about no multi-statement lines. pass elif token.next.type not in ( Type.WHITESPACE, Type.SEMICOLON, Type.END_PAREN): self._HandleError( errors.MISSING_SPACE, 'Missing space after ";" in for statement', token.next, position=Position.AtBeginning()) last_code = token.metadata.last_code if last_code and last_code.type == Type.SEMICOLON: # Allow a single double semi colon in for loops for cases like: # for (;;) { }. # NOTE(user): This is not a perfect check, and will not throw an error # for cases like: for (var i = 0;; i < n; i++) {}, but then your code # probably won't work either. for_token = tokenutil.CustomSearch( last_code, lambda token: token.type == Type.KEYWORD and token.string == 'for', end_func=lambda token: token.type == Type.SEMICOLON, distance=None, reverse=True) if not for_token: self._HandleError(errors.REDUNDANT_SEMICOLON, 'Redundant semicolon', token, position=Position.All(token.string)) elif token_type == Type.START_PAREN: if token.previous and token.previous.type == Type.KEYWORD: self._HandleError(errors.MISSING_SPACE, 'Missing space before "("', token, position=Position.AtBeginning()) elif token.previous and token.previous.type == Type.WHITESPACE: before_space = token.previous.previous if ( before_space and before_space.line_number == token.line_number and before_space.type == Type.IDENTIFIER): self._HandleError( errors.EXTRA_SPACE, 'Extra space before "("', token.previous, position=Position.All(token.previous.string)) elif token_type == Type.START_BRACKET: self._HandleStartBracket(token, last_non_space_token) elif token_type in (Type.END_PAREN, Type.END_BRACKET): # Ensure there is no space before closing parentheses, except when # it's in a for statement with an omitted section, or when it's at the # beginning of a line. if (token.previous and token.previous.type == Type.WHITESPACE and not token.previous.IsFirstInLine() and not ( last_non_space_token and last_non_space_token.line_number == token.line_number and last_non_space_token.type == Type.SEMICOLON)): self._HandleError( errors.EXTRA_SPACE, 'Extra space before "%s"' % token.string, token.previous, position=Position.All(token.previous.string)) if token.type == Type.END_BRACKET: last_code = token.metadata.last_code if last_code.IsOperator(','): self._HandleError( errors.COMMA_AT_END_OF_LITERAL, 'Illegal comma at end of array literal', last_code, position=Position.All(last_code.string)) elif token_type == Type.WHITESPACE: if self.ILLEGAL_TAB.search(token.string): if token.IsFirstInLine(): if token.next: self._HandleError( errors.ILLEGAL_TAB, 'Illegal tab in whitespace before "%s"' % token.next.string, token, position=Position.All(token.string)) else: self._HandleError( errors.ILLEGAL_TAB, 'Illegal tab in whitespace', token, position=Position.All(token.string)) else: self._HandleError( errors.ILLEGAL_TAB, 'Illegal tab in whitespace after "%s"' % token.previous.string, token, position=Position.All(token.string)) # Check whitespace length if it's not the first token of the line and # if it's not immediately before a comment. if last_in_line: # Check for extra whitespace at the end of a line. self._HandleError(errors.EXTRA_SPACE, 'Extra space at end of line', token, position=Position.All(token.string)) elif not first_in_line and not token.next.IsComment(): if token.length > 1: self._HandleError( errors.EXTRA_SPACE, 'Extra space after "%s"' % token.previous.string, token, position=Position(1, len(token.string) - 1)) elif token_type == Type.OPERATOR: self._CheckOperator(token) elif token_type == Type.DOC_FLAG: flag = token.attached_object if flag.flag_type == 'bug': # TODO(robbyw): Check for exactly 1 space on the left. string = token.next.string.lstrip() string = string.split(' ', 1)[0] if not string.isdigit(): self._HandleError(errors.NO_BUG_NUMBER_AFTER_BUG_TAG, '@bug should be followed by a bug number', token) elif flag.flag_type == 'suppress': if flag.type is None: # A syntactically invalid suppress tag will get tokenized as a normal # flag, indicating an error. self._HandleError( errors.INCORRECT_SUPPRESS_SYNTAX, 'Invalid suppress syntax: should be @suppress {errortype}. ' 'Spaces matter.', token) else: for suppress_type in re.split(r'\||,', flag.type): if suppress_type not in state.GetDocFlag().SUPPRESS_TYPES: self._HandleError( errors.INVALID_SUPPRESS_TYPE, 'Invalid suppression type: %s' % suppress_type, token) elif (error_check.ShouldCheck(Rule.WELL_FORMED_AUTHOR) and flag.flag_type == 'author'): # TODO(user): In non strict mode check the author tag for as much as # it exists, though the full form checked below isn't required. string = token.next.string result = self.AUTHOR_SPEC.match(string) if not result: self._HandleError(errors.INVALID_AUTHOR_TAG_DESCRIPTION, 'Author tag line should be of the form: ' '@author [email protected] (Your Name)', token.next) else: # Check spacing between email address and name. Do this before # checking earlier spacing so positions are easier to calculate for # autofixing. num_spaces = len(result.group(2)) if num_spaces < 1: self._HandleError(errors.MISSING_SPACE, 'Missing space after email address', token.next, position=Position(result.start(2), 0)) elif num_spaces > 1: self._HandleError( errors.EXTRA_SPACE, 'Extra space after email address', token.next, position=Position(result.start(2) + 1, num_spaces - 1)) # Check for extra spaces before email address. Can't be too few, if # not at least one we wouldn't match @author tag. num_spaces = len(result.group(1)) if num_spaces > 1: self._HandleError(errors.EXTRA_SPACE, 'Extra space before email address', token.next, position=Position(1, num_spaces - 1)) elif (flag.flag_type in state.GetDocFlag().HAS_DESCRIPTION and not self._limited_doc_checks): if flag.flag_type == 'param': if flag.name is None: self._HandleError(errors.MISSING_JSDOC_PARAM_NAME, 'Missing name in @param tag', token) if not flag.description or flag.description is None: flag_name = token.type if 'name' in token.values: flag_name = '@' + token.values['name'] if flag_name not in self.JSDOC_FLAGS_DESCRIPTION_NOT_REQUIRED: self._HandleError( errors.MISSING_JSDOC_TAG_DESCRIPTION, 'Missing description in %s tag' % flag_name, token) else: self._CheckForMissingSpaceBeforeToken( flag.description_start_token) if flag.flag_type in state.GetDocFlag().HAS_TYPE: if flag.type_start_token is not None: self._CheckForMissingSpaceBeforeToken( token.attached_object.type_start_token) if flag.type and not flag.type.isspace(): self._CheckJsDocType(token) if token_type in (Type.DOC_FLAG, Type.DOC_INLINE_FLAG): if (token.values['name'] not in state.GetDocFlag().LEGAL_DOC and token.values['name'] not in FLAGS.custom_jsdoc_tags): self._HandleError( errors.INVALID_JSDOC_TAG, 'Invalid JsDoc tag: %s' % token.values['name'], token) if (error_check.ShouldCheck(Rule.NO_BRACES_AROUND_INHERIT_DOC) and token.values['name'] == 'inheritDoc' and token_type == Type.DOC_INLINE_FLAG): self._HandleError(errors.UNNECESSARY_BRACES_AROUND_INHERIT_DOC, 'Unnecessary braces around @inheritDoc', token) elif token_type == Type.SIMPLE_LVALUE: identifier = token.values['identifier'] if ((not state.InFunction() or state.InConstructor()) and state.InTopLevel() and not state.InObjectLiteralDescendant()): jsdoc = state.GetDocComment() if not state.HasDocComment(identifier): # Only test for documentation on identifiers with .s in them to # avoid checking things like simple variables. We don't require # documenting assignments to .prototype itself (bug 1880803). if (not state.InConstructor() and identifier.find('.') != -1 and not identifier.endswith('.prototype') and not self._limited_doc_checks): comment = state.GetLastComment() if not (comment and comment.lower().count( 'jsdoc inherited')): self._HandleError( errors.MISSING_MEMBER_DOCUMENTATION, "No docs found for member '%s'" % identifier, token) elif jsdoc and (not state.InConstructor() or identifier.startswith('this.')): # We are at the top level and the function/member is documented. if identifier.endswith('_') and not identifier.endswith( '__'): # Can have a private class which inherits documentation from a # public superclass. # # @inheritDoc is deprecated in favor of using @override, and they if (jsdoc.HasFlag('override') and not jsdoc.HasFlag( 'constructor') and ('accessControls' not in jsdoc.suppressions)): self._HandleError( errors.INVALID_OVERRIDE_PRIVATE, '%s should not override a private member.' % identifier, jsdoc.GetFlag('override').flag_token) if (jsdoc.HasFlag('inheritDoc') and not jsdoc.HasFlag( 'constructor') and ('accessControls' not in jsdoc.suppressions)): self._HandleError( errors.INVALID_INHERIT_DOC_PRIVATE, '%s should not inherit from a private member.' % identifier, jsdoc.GetFlag('inheritDoc').flag_token) if (not jsdoc.HasFlag('private') and ('underscore' not in jsdoc.suppressions) and not ((jsdoc.HasFlag('inheritDoc') or jsdoc.HasFlag( 'override')) and ('accessControls' in jsdoc.suppressions))): self._HandleError( errors.MISSING_PRIVATE, 'Member "%s" must have @private JsDoc.' % identifier, token) if jsdoc.HasFlag( 'private') and 'underscore' in jsdoc.suppressions: self._HandleError( errors.UNNECESSARY_SUPPRESS, '@suppress {underscore} is not necessary with @private', jsdoc.suppressions['underscore']) elif (jsdoc.HasFlag('private') and not self.InExplicitlyTypedLanguage()): # It is convention to hide public fields in some ECMA # implementations from documentation using the @private tag. self._HandleError( errors.EXTRA_PRIVATE, 'Member "%s" must not have @private JsDoc' % identifier, token) # These flags are only legal on localizable message definitions; # such variables always begin with the prefix MSG_. for f in ('desc', 'hidden', 'meaning'): if (jsdoc.HasFlag(f) and not identifier.startswith('MSG_') and identifier.find('.MSG_') == -1): self._HandleError( errors.INVALID_USE_OF_DESC_TAG, 'Member "%s" should not have @%s JsDoc' % ( identifier, f), token) # Check for illegaly assigning live objects as prototype property values. index = identifier.find('.prototype.') # Ignore anything with additional .s after the prototype. if index != -1 and identifier.find('.', index + 11) == -1: equal_operator = tokenutil.SearchExcept(token, Type.NON_CODE_TYPES) next_code = tokenutil.SearchExcept(equal_operator, Type.NON_CODE_TYPES) if next_code and ( next_code.type in ( Type.START_BRACKET, Type.START_BLOCK) or next_code.IsOperator('new')): self._HandleError( errors.ILLEGAL_PROTOTYPE_MEMBER_VALUE, 'Member %s cannot have a non-primitive value' % identifier, token) elif token_type == Type.END_PARAMETERS: # Find extra space at the end of parameter lists. We check the token # prior to the current one when it is a closing paren. if (token.previous and token.previous.type == Type.PARAMETERS and self.ENDS_WITH_SPACE.search(token.previous.string)): self._HandleError(errors.EXTRA_SPACE, 'Extra space before ")"', token.previous) jsdoc = state.GetDocComment() if state.GetFunction().is_interface: if token.previous and token.previous.type == Type.PARAMETERS: self._HandleError( errors.INTERFACE_CONSTRUCTOR_CANNOT_HAVE_PARAMS, 'Interface constructor cannot have parameters', token.previous) elif (state.InTopLevel() and jsdoc and not jsdoc.HasFlag('see') and not jsdoc.InheritsDocumentation() and not state.InObjectLiteralDescendant() and not jsdoc.IsInvalidated()): distance, edit = jsdoc.CompareParameters(state.GetParams()) if distance: params_iter = iter(state.GetParams()) docs_iter = iter(jsdoc.ordered_params) for op in edit: if op == 'I': # Insertion. # Parsing doc comments is the same for all languages # but some languages care about parameters that don't have # doc comments and some languages don't care. # Languages that don't allow variables to by typed such as # JavaScript care but languages such as ActionScript or Java # that allow variables to be typed don't care. if not self._limited_doc_checks: self.HandleMissingParameterDoc(token, params_iter.next()) elif op == 'D': # Deletion self._HandleError( errors.EXTRA_PARAMETER_DOCUMENTATION, 'Found docs for non-existing parameter: "%s"' % docs_iter.next(), token) elif op == 'S': # Substitution if not self._limited_doc_checks: self._HandleError( errors.WRONG_PARAMETER_DOCUMENTATION, 'Parameter mismatch: got "%s", expected "%s"' % (params_iter.next(), docs_iter.next()), token) else: # Equality - just advance the iterators params_iter.next() docs_iter.next() elif token_type == Type.STRING_TEXT: # If this is the first token after the start of the string, but it's at # the end of a line, we know we have a multi-line string. if token.previous.type in ( Type.SINGLE_QUOTE_STRING_START, Type.DOUBLE_QUOTE_STRING_START) and last_in_line: self._HandleError(errors.MULTI_LINE_STRING, 'Multi-line strings are not allowed', token) # This check is orthogonal to the ones above, and repeats some types, so # it is a plain if and not an elif. if token.type in Type.COMMENT_TYPES: if self.ILLEGAL_TAB.search(token.string): self._HandleError(errors.ILLEGAL_TAB, 'Illegal tab in comment "%s"' % token.string, token) trimmed = token.string.rstrip() if last_in_line and token.string != trimmed: # Check for extra whitespace at the end of a line. self._HandleError( errors.EXTRA_SPACE, 'Extra space at end of line', token, position=Position(len(trimmed), len(token.string) - len(trimmed))) # This check is also orthogonal since it is based on metadata. if token.metadata.is_implied_semicolon: self._HandleError(errors.MISSING_SEMICOLON, 'Missing semicolon at end of line', token)
def _ProcessContext(self): """Process the context at the current token. Returns: The context that should be assigned to the current token, or None if the current context after this method should be used. Raises: ParseError: When the token appears in an invalid context. """ token = self._token token_type = token.type if self._context.type in EcmaContext.BLOCK_TYPES: # Whenever we're in a block, we add a statement context. We make an # exception for switch statements since they can only contain case: and # default: and therefore don't directly contain statements. # The block we add here may be immediately removed in some cases, but # that causes no harm. parent = self._context.parent if not parent or parent.type != EcmaContext.SWITCH: self._AddContext(EcmaContext.STATEMENT) elif self._context.type == EcmaContext.ARRAY_LITERAL: self._AddContext(EcmaContext.LITERAL_ELEMENT) if token_type == TokenType.START_PAREN: if self._last_code and self._last_code.IsKeyword('for'): # for loops contain multiple statements in the group unlike while, # switch, if, etc. self._AddContext(EcmaContext.FOR_GROUP_BLOCK) else: self._AddContext(EcmaContext.GROUP) elif token_type == TokenType.END_PAREN: result = self._PopContextType(EcmaContext.GROUP, EcmaContext.FOR_GROUP_BLOCK) keyword_token = result.start_token.metadata.last_code # keyword_token will not exist if the open paren is the first line of the # file, for example if all code is wrapped in an immediately executed # annonymous function. if keyword_token and keyword_token.string in ('if', 'for', 'while'): next_code = tokenutil.SearchExcept(token, TokenType.NON_CODE_TYPES) if next_code.type != TokenType.START_BLOCK: # Check for do-while. is_do_while = False pre_keyword_token = keyword_token.metadata.last_code if (pre_keyword_token and pre_keyword_token.type == TokenType.END_BLOCK): start_block_token = pre_keyword_token.metadata.context.start_token is_do_while = start_block_token.metadata.last_code.string == 'do' # If it's not do-while, it's an implied block. if not is_do_while: self._AddContext(EcmaContext.IMPLIED_BLOCK) token.metadata.is_implied_block = True return result # else (not else if) with no open brace after it should be considered the # start of an implied block, similar to the case with if, for, and while # above. elif (token_type == TokenType.KEYWORD and token.string == 'else'): next_code = tokenutil.SearchExcept(token, TokenType.NON_CODE_TYPES) if (next_code.type != TokenType.START_BLOCK and (next_code.type != TokenType.KEYWORD or next_code.string != 'if')): self._AddContext(EcmaContext.IMPLIED_BLOCK) token.metadata.is_implied_block = True elif token_type == TokenType.START_PARAMETERS: self._AddContext(EcmaContext.PARAMETERS) elif token_type == TokenType.END_PARAMETERS: return self._PopContextType(EcmaContext.PARAMETERS) elif token_type == TokenType.START_BRACKET: if (self._last_code and self._last_code.type in TokenType.EXPRESSION_ENDER_TYPES): self._AddContext(EcmaContext.INDEX) else: self._AddContext(EcmaContext.ARRAY_LITERAL) elif token_type == TokenType.END_BRACKET: return self._PopContextType(EcmaContext.INDEX, EcmaContext.ARRAY_LITERAL) elif token_type == TokenType.START_BLOCK: if (self._last_code.type in (TokenType.END_PAREN, TokenType.END_PARAMETERS) or self._last_code.IsKeyword('else') or self._last_code.IsKeyword('do') or self._last_code.IsKeyword('try') or self._last_code.IsKeyword('finally') or (self._last_code.IsOperator(':') and self._last_code.metadata.context.type == EcmaContext.CASE_BLOCK)): # else, do, try, and finally all might have no () before {. # Also, handle the bizzare syntax case 10: {...}. self._AddContext(EcmaContext.BLOCK) else: self._AddContext(EcmaContext.OBJECT_LITERAL) elif token_type == TokenType.END_BLOCK: context = self._PopContextType(EcmaContext.BLOCK, EcmaContext.OBJECT_LITERAL) if self._context.type == EcmaContext.SWITCH: # The end of the block also means the end of the switch statement it # applies to. return self._PopContext() return context elif token.IsKeyword('switch'): self._AddContext(EcmaContext.SWITCH) elif (token_type == TokenType.KEYWORD and token.string in ('case', 'default') and self._context.type != EcmaContext.OBJECT_LITERAL): # Pop up to but not including the switch block. while self._context.parent.type != EcmaContext.SWITCH: self._PopContext() if self._context.parent is None: raise ParseError( token, 'Encountered case/default statement ' 'without switch statement') elif token.IsOperator('?'): self._AddContext(EcmaContext.TERNARY_TRUE) elif token.IsOperator(':'): if self._context.type == EcmaContext.OBJECT_LITERAL: self._AddContext(EcmaContext.LITERAL_ELEMENT) elif self._context.type == EcmaContext.TERNARY_TRUE: self._PopContext() self._AddContext(EcmaContext.TERNARY_FALSE) # Handle nested ternary statements like: # foo = bar ? baz ? 1 : 2 : 3 # When we encounter the second ":" the context is # ternary_false > ternary_true > statement > root elif (self._context.type == EcmaContext.TERNARY_FALSE and self._context.parent.type == EcmaContext.TERNARY_TRUE): self._PopContext() # Leave current ternary false context. self._PopContext() # Leave current parent ternary true self._AddContext(EcmaContext.TERNARY_FALSE) elif self._context.parent.type == EcmaContext.SWITCH: self._AddContext(EcmaContext.CASE_BLOCK) elif token.IsKeyword('var'): self._AddContext(EcmaContext.VAR) elif token.IsOperator(','): while self._context.type not in (EcmaContext.VAR, EcmaContext.ARRAY_LITERAL, EcmaContext.OBJECT_LITERAL, EcmaContext.STATEMENT, EcmaContext.PARAMETERS, EcmaContext.GROUP): self._PopContext() elif token_type == TokenType.SEMICOLON: self._EndStatement()
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): flag = self._doc_flag(token) token.attached_object = flag 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 functions outside of parens are eligible for documentation. if not self._paren_depth: 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 identifier.type in (Type.IDENTIFIER, Type.SIMPLE_LVALUE): 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 CheckToken(self, token, state): """Checks a token, given the current parser_state, for warnings and errors. Args: token: The current token under consideration state: parser_state object that indicates the current state in the page """ # For @param don't ignore record type. if (self.__ContainsRecordType(token) and token.attached_object.flag_type != 'param'): # We should bail out and not emit any warnings for this annotation. # TODO(nicksantos): Support record types for real. state.GetDocComment().Invalidate() return # Call the base class's CheckToken function. super(JavaScriptLintRules, self).CheckToken(token, state) # Store some convenience variables namespaces_info = self._namespaces_info if error_check.ShouldCheck(Rule.UNUSED_LOCAL_VARIABLES): self._CheckUnusedLocalVariables(token, state) if error_check.ShouldCheck(Rule.UNUSED_PRIVATE_MEMBERS): # Find all assignments to private members. if token.type == Type.SIMPLE_LVALUE: identifier = token.string if identifier.endswith('_') and not identifier.endswith('__'): doc_comment = state.GetDocComment() suppressed = ( doc_comment and doc_comment.HasFlag('suppress') and (doc_comment.GetFlag('suppress').type == 'underscore' or doc_comment.GetFlag('suppress').type == 'unusedPrivateMembers')) if not suppressed: # Look for static members defined on a provided namespace. if namespaces_info: namespace = namespaces_info.GetClosurizedNamespace( identifier) provided_namespaces = namespaces_info.GetProvidedNamespaces( ) else: namespace = None provided_namespaces = set() # Skip cases of this.something_.somethingElse_. regex = re.compile(r'^this\.[a-zA-Z_]+$') if namespace in provided_namespaces or regex.match( identifier): variable = identifier.split('.')[-1] self._declared_private_member_tokens[ variable] = token self._declared_private_members.add(variable) elif not identifier.endswith('__'): # Consider setting public members of private members to be a usage. for piece in identifier.split('.'): if piece.endswith('_'): self._used_private_members.add(piece) # Find all usages of private members. if token.type == Type.IDENTIFIER: for piece in token.string.split('.'): if piece.endswith('_'): self._used_private_members.add(piece) if token.type == Type.DOC_FLAG: flag = token.attached_object if flag.flag_type == 'param' and flag.name_token is not None: self._CheckForMissingSpaceBeforeToken( token.attached_object.name_token) if flag.type is not None and flag.name is not None: if error_check.ShouldCheck(Rule.VARIABLE_ARG_MARKER): # Check for variable arguments marker in type. if (flag.type.startswith('...') and flag.name != 'var_args'): self._HandleError( errors.JSDOC_MISSING_VAR_ARGS_NAME, 'Variable length argument %s must be renamed ' 'to var_args.' % flag.name, token) elif (not flag.type.startswith('...') and flag.name == 'var_args'): self._HandleError( errors.JSDOC_MISSING_VAR_ARGS_TYPE, 'Variable length argument %s type must start ' 'with \'...\'.' % flag.name, token) if error_check.ShouldCheck(Rule.OPTIONAL_TYPE_MARKER): # Check for optional marker in type. if (flag.type.endswith('=') and not flag.name.startswith('opt_')): self._HandleError( errors.JSDOC_MISSING_OPTIONAL_PREFIX, 'Optional parameter name %s must be prefixed ' 'with opt_.' % flag.name, token) elif (not flag.type.endswith('=') and flag.name.startswith('opt_')): self._HandleError( errors.JSDOC_MISSING_OPTIONAL_TYPE, 'Optional parameter %s type must end with =.' % flag.name, token) if flag.flag_type in state.GetDocFlag().HAS_TYPE: # Check for both missing type token and empty type braces '{}' # Missing suppress types are reported separately and we allow enums # and const without types. if (flag.flag_type not in ('suppress', 'enum', 'const') and (not flag.type or flag.type.isspace())): self._HandleError(errors.MISSING_JSDOC_TAG_TYPE, 'Missing type in %s tag' % token.string, token) elif flag.name_token and flag.type_end_token and tokenutil.Compare( flag.type_end_token, flag.name_token) > 0: self._HandleError( errors.OUT_OF_ORDER_JSDOC_TAG_TYPE, 'Type should be immediately after %s tag' % token.string, token) elif token.type == Type.DOUBLE_QUOTE_STRING_START: next_token = token.next while next_token.type == Type.STRING_TEXT: if javascripttokenizer.JavaScriptTokenizer.SINGLE_QUOTE.search( next_token.string): break next_token = next_token.next else: self._HandleError( errors.UNNECESSARY_DOUBLE_QUOTED_STRING, 'Single-quoted string preferred over double-quoted string.', token, position=Position.All(token.string)) elif token.type == Type.END_DOC_COMMENT: doc_comment = state.GetDocComment() # When @externs appears in a @fileoverview comment, it should trigger # the same limited doc checks as a special filename like externs.js. if doc_comment.HasFlag('fileoverview') and doc_comment.HasFlag( 'externs'): self._SetLimitedDocChecks(True) if (error_check.ShouldCheck(Rule.BLANK_LINES_AT_TOP_LEVEL) and not self._is_html and state.InTopLevel() and not state.InNonScopeBlock()): # Check if we're in a fileoverview or constructor JsDoc. is_constructor = (doc_comment.HasFlag('constructor') or doc_comment.HasFlag('interface')) # @fileoverview is an optional tag so if the dosctring is the first # token in the file treat it as a file level docstring. is_file_level_comment = (doc_comment.HasFlag('fileoverview') or not doc_comment.start_token.previous) # If the comment is not a file overview, and it does not immediately # precede some code, skip it. # NOTE: The tokenutil methods are not used here because of their # behavior at the top of a file. next_token = token.next if (not next_token or (not is_file_level_comment and next_token.type in Type.NON_CODE_TYPES)): return # Don't require extra blank lines around suppression of extra # goog.require errors. if (doc_comment.SuppressionOnly() and next_token.type == Type.IDENTIFIER and next_token.string in ['goog.provide', 'goog.require']): return # Find the start of this block (include comments above the block, unless # this is a file overview). block_start = doc_comment.start_token if not is_file_level_comment: token = block_start.previous while token and token.type in Type.COMMENT_TYPES: block_start = token token = token.previous # Count the number of blank lines before this block. blank_lines = 0 token = block_start.previous while token and token.type in [ Type.WHITESPACE, Type.BLANK_LINE ]: if token.type == Type.BLANK_LINE: # A blank line. blank_lines += 1 elif token.type == Type.WHITESPACE and not token.line.strip( ): # A line with only whitespace on it. blank_lines += 1 token = token.previous # Log errors. error_message = False expected_blank_lines = 0 # Only need blank line before file overview if it is not the beginning # of the file, e.g. copyright is first. if is_file_level_comment and blank_lines == 0 and block_start.previous: error_message = 'Should have a blank line before a file overview.' expected_blank_lines = 1 elif is_constructor and blank_lines != 3: error_message = ( 'Should have 3 blank lines before a constructor/interface.' ) expected_blank_lines = 3 elif (not is_file_level_comment and not is_constructor and blank_lines != 2): error_message = 'Should have 2 blank lines between top-level blocks.' expected_blank_lines = 2 if error_message: self._HandleError(errors.WRONG_BLANK_LINE_COUNT, error_message, block_start, position=Position.AtBeginning(), fix_data=expected_blank_lines - blank_lines) elif token.type == Type.END_BLOCK: if state.InFunction() and state.IsFunctionClose(): is_immediately_called = (token.next and token.next.type == Type.START_PAREN) function = state.GetFunction() if not self._limited_doc_checks: if (function.has_return and function.doc and not is_immediately_called and not function.doc.HasFlag('return') and not function.doc.InheritsDocumentation() and not function.doc.HasFlag('constructor')): # Check for proper documentation of return value. self._HandleError( errors.MISSING_RETURN_DOCUMENTATION, 'Missing @return JsDoc in function with non-trivial return', function.doc.end_token, position=Position.AtBeginning()) elif (not function.has_return and not function.has_throw and function.doc and function.doc.HasFlag('return') and not state.InInterfaceMethod()): return_flag = function.doc.GetFlag('return') if (return_flag.type is None or ('undefined' not in return_flag.type and 'void' not in return_flag.type and '*' not in return_flag.type)): self._HandleError( errors.UNNECESSARY_RETURN_DOCUMENTATION, 'Found @return JsDoc on function that returns nothing', return_flag.flag_token, position=Position.AtBeginning()) # b/4073735. Method in object literal definition of prototype can # safely reference 'this'. prototype_object_literal = False block_start = None previous_code = None previous_previous_code = None # Search for cases where prototype is defined as object literal. # previous_previous_code # | previous_code # | | block_start # | | | # a.b.prototype = { # c : function() { # this.d = 1; # } # } # If in object literal, find first token of block so to find previous # tokens to check above condition. if state.InObjectLiteral(): block_start = state.GetCurrentBlockStart() # If an object literal then get previous token (code type). For above # case it should be '='. if block_start: previous_code = tokenutil.SearchExcept(block_start, Type.NON_CODE_TYPES, reverse=True) # If previous token to block is '=' then get its previous token. if previous_code and previous_code.IsOperator('='): previous_previous_code = tokenutil.SearchExcept( previous_code, Type.NON_CODE_TYPES, reverse=True) # If variable/token before '=' ends with '.prototype' then its above # case of prototype defined with object literal. prototype_object_literal = ( previous_previous_code and previous_previous_code.string.endswith('.prototype')) if (function.has_this and function.doc and not function.doc.HasFlag('this') and not function.is_constructor and not function.is_interface and '.prototype.' not in function.name and not prototype_object_literal): self._HandleError( errors.MISSING_JSDOC_TAG_THIS, 'Missing @this JsDoc in function referencing "this". (' 'this usually means you are trying to reference "this" in ' 'a static function, or you have forgotten to mark a ' 'constructor with @constructor)', function.doc.end_token, position=Position.AtBeginning()) elif token.type == Type.IDENTIFIER: if token.string == 'goog.inherits' and not state.InFunction(): if state.GetLastNonSpaceToken( ).line_number == token.line_number: self._HandleError( errors.MISSING_LINE, 'Missing newline between constructor and goog.inherits', token, position=Position.AtBeginning()) extra_space = state.GetLastNonSpaceToken().next while extra_space != token: if extra_space.type == Type.BLANK_LINE: self._HandleError( errors.EXTRA_LINE, 'Extra line between constructor and goog.inherits', extra_space) extra_space = extra_space.next # TODO(robbyw): Test the last function was a constructor. # TODO(robbyw): Test correct @extends and @implements documentation. elif (token.string == 'goog.provide' and not state.InFunction() and namespaces_info is not None): namespace = tokenutil.GetStringAfterToken(token) # Report extra goog.provide statement. if not namespace or namespaces_info.IsExtraProvide(token): if not namespace: msg = 'Empty namespace in goog.provide' else: msg = 'Unnecessary goog.provide: ' + namespace # Hint to user if this is a Test namespace. if namespace.endswith('Test'): msg += ( ' *Test namespaces must be mentioned in the ' 'goog.setTestOnly() call') self._HandleError(errors.EXTRA_GOOG_PROVIDE, msg, token, position=Position.AtBeginning()) if namespaces_info.IsLastProvide(token): # Report missing provide statements after the last existing provide. missing_provides = namespaces_info.GetMissingProvides() if missing_provides: self._ReportMissingProvides( missing_provides, tokenutil.GetLastTokenInSameLine(token).next, False) # If there are no require statements, missing requires should be # reported after the last provide. if not namespaces_info.GetRequiredNamespaces(): missing_requires = namespaces_info.GetMissingRequires() if missing_requires: self._ReportMissingRequires( missing_requires, tokenutil.GetLastTokenInSameLine(token).next, True) elif (token.string == 'goog.require' and not state.InFunction() and namespaces_info is not None): namespace = tokenutil.GetStringAfterToken(token) # If there are no provide statements, missing provides should be # reported before the first require. if (namespaces_info.IsFirstRequire(token) and not namespaces_info.GetProvidedNamespaces()): missing_provides = namespaces_info.GetMissingProvides() if missing_provides: self._ReportMissingProvides( missing_provides, tokenutil.GetFirstTokenInSameLine(token), True) # Report extra goog.require statement. if not namespace or namespaces_info.IsExtraRequire(token): if not namespace: msg = 'Empty namespace in goog.require' else: msg = 'Unnecessary goog.require: ' + namespace self._HandleError(errors.EXTRA_GOOG_REQUIRE, msg, token, position=Position.AtBeginning()) # Report missing goog.require statements. if namespaces_info.IsLastRequire(token): missing_requires = namespaces_info.GetMissingRequires() if missing_requires: self._ReportMissingRequires( missing_requires, tokenutil.GetLastTokenInSameLine(token).next, False) elif token.type == Type.OPERATOR: last_in_line = token.IsLastInLine() # If the token is unary and appears to be used in a unary context # it's ok. Otherwise, if it's at the end of the line or immediately # before a comment, it's ok. # Don't report an error before a start bracket - it will be reported # by that token's space checks. if (not token.metadata.IsUnaryOperator() and not last_in_line and not token.next.IsComment() and not token.next.IsOperator(',') and token.next.type not in (Type.WHITESPACE, Type.END_PAREN, Type.END_BRACKET, Type.SEMICOLON, Type.START_BRACKET)): self._HandleError(errors.MISSING_SPACE, 'Missing space after "%s"' % token.string, token, position=Position.AtEnd(token.string)) elif token.type == Type.WHITESPACE: first_in_line = token.IsFirstInLine() last_in_line = token.IsLastInLine() # Check whitespace length if it's not the first token of the line and # if it's not immediately before a comment. if not last_in_line and not first_in_line and not token.next.IsComment( ): # Ensure there is no space after opening parentheses. if (token.previous.type in (Type.START_PAREN, Type.START_BRACKET, Type.FUNCTION_NAME) or token.next.type == Type.START_PARAMETERS): self._HandleError(errors.EXTRA_SPACE, 'Extra space after "%s"' % token.previous.string, token, position=Position.All(token.string)) elif token.type == Type.SEMICOLON: previous_token = tokenutil.SearchExcept(token, Type.NON_CODE_TYPES, reverse=True) if not previous_token: self._HandleError(errors.REDUNDANT_SEMICOLON, 'Semicolon without any statement', token, position=Position.AtEnd(token.string)) elif (previous_token.type == Type.KEYWORD and previous_token.string not in ['break', 'continue', 'return']): self._HandleError( errors.REDUNDANT_SEMICOLON, ('Semicolon after \'%s\' without any statement.' ' Looks like an error.' % previous_token.string), token, position=Position.AtEnd(token.string))
def CheckToken(self, token, state): """Checks a token for indentation errors. Args: token: The current token under consideration state: Additional information about the current tree state Returns: An error array [error code, error string, error token] if the token is improperly indented, or None if indentation is correct. """ token_type = token.type indentation_errors = [] stack = self._stack is_first = self._IsFirstNonWhitespaceTokenInLine(token) # Add tokens that could decrease indentation before checking. if token_type == Type.END_PAREN: self._PopTo(Type.START_PAREN) elif token_type == Type.END_PARAMETERS: self._PopTo(Type.START_PARAMETERS) elif token_type == Type.END_BRACKET: self._PopTo(Type.START_BRACKET) elif token_type == Type.END_BLOCK: start_token = self._PopTo(Type.START_BLOCK) # Check for required goog.scope comment. if start_token: goog_scope = tokenutil.GoogScopeOrNoneFromStartBlock( start_token.token) if goog_scope is not None: if not token.line.endswith('; // goog.scope\n'): if (token.line.find('//') > -1 and token.line.find('goog.scope') > token.line.find('//')): indentation_errors.append([ errors.MALFORMED_END_OF_SCOPE_COMMENT, ('Malformed end of goog.scope comment. Please use the ' 'exact following syntax to close the scope:\n' '}); // goog.scope'), token, Position(token.start_index, token.length) ]) else: indentation_errors.append([ errors.MISSING_END_OF_SCOPE_COMMENT, ('Missing comment for end of goog.scope which opened at line ' '%d. End the scope with:\n' '}); // goog.scope' % (start_token.line_number)), token, Position(token.start_index, token.length) ]) elif token_type == Type.KEYWORD and token.string in ('case', 'default'): self._Add(self._PopTo(Type.START_BLOCK)) elif is_first and token.string == '.': # This token should have been on the previous line, so treat it as if it # was there. info = TokenInfo(token) info.line_number = token.line_number - 1 self._Add(info) elif token_type == Type.SEMICOLON: self._PopTransient() not_binary_operator = (token_type != Type.OPERATOR or token.metadata.IsUnaryOperator()) not_dot = token.string != '.' if is_first and not_binary_operator and not_dot and token.type not in ( Type.COMMENT, Type.DOC_PREFIX, Type.STRING_TEXT): if flags.FLAGS.debug_indentation: print 'Line #%d: stack %r' % (token.line_number, stack) # Ignore lines that start in JsDoc since we don't check them properly yet. # TODO(robbyw): Support checking JsDoc indentation. # Ignore lines that start as multi-line strings since indentation is N/A. # Ignore lines that start with operators since we report that already. # Ignore lines with tabs since we report that already. expected = self._GetAllowableIndentations() actual = self._GetActualIndentation(token) # Special case comments describing else, case, and default. Allow them # to outdent to the parent block. if token_type in Type.COMMENT_TYPES: next_code = tokenutil.SearchExcept(token, Type.NON_CODE_TYPES) if next_code and next_code.type == Type.END_BLOCK: next_code = tokenutil.SearchExcept(next_code, Type.NON_CODE_TYPES) if next_code and next_code.string in ('else', 'case', 'default'): # TODO(robbyw): This almost certainly introduces false negatives. expected |= self._AddToEach(expected, -2) if actual >= 0 and actual not in expected: expected = sorted(expected) indentation_errors.append([ errors.WRONG_INDENTATION, 'Wrong indentation: expected any of {%s} but got %d' % (', '.join(['%d' % x for x in expected]), actual), token, Position(actual, expected[0]) ]) self._start_index_offset[ token.line_number] = expected[0] - actual # Add tokens that could increase indentation. if token_type == Type.START_BRACKET: self._Add( TokenInfo(token=token, is_block=token.metadata.context.type == Context.ARRAY_LITERAL)) elif token_type == Type.START_BLOCK or token.metadata.is_implied_block: self._Add(TokenInfo(token=token, is_block=True)) elif token_type in (Type.START_PAREN, Type.START_PARAMETERS): self._Add(TokenInfo(token=token, is_block=False)) elif token_type == Type.KEYWORD and token.string == 'return': self._Add(TokenInfo(token)) elif not token.IsLastInLine() and (token.IsAssignment() or token.IsOperator('?')): self._Add(TokenInfo(token=token)) # Handle implied block closes. if token.metadata.is_implied_block_close: self._PopToImpliedBlock() # Add some tokens only if they appear at the end of the line. is_last = self._IsLastCodeInLine(token) if is_last: if token_type == Type.OPERATOR: if token.string == ':': if stack and stack[-1].token.string == '?': # When a ternary : is on a different line than its '?', it doesn't # add indentation. if token.line_number == stack[-1].token.line_number: self._Add(TokenInfo(token)) elif token.metadata.context.type == Context.CASE_BLOCK: # Pop transient tokens from say, line continuations, e.g., # case x. # y: # Want to pop the transient 4 space continuation indent. self._PopTransient() # Starting the body of the case statement, which is a type of # block. self._Add(TokenInfo(token=token, is_block=True)) elif token.metadata.context.type == Context.LITERAL_ELEMENT: # When in an object literal, acts as operator indicating line # continuations. self._Add(TokenInfo(token)) pass else: # ':' might also be a statement label, no effect on indentation in # this case. pass elif token.string != ',': self._Add(TokenInfo(token)) else: # The token is a comma. if token.metadata.context.type == Context.VAR: self._Add(TokenInfo(token)) elif token.metadata.context.type != Context.PARAMETERS: self._PopTransient() elif (token.string.endswith('.') and token_type in (Type.IDENTIFIER, Type.NORMAL)): self._Add(TokenInfo(token)) elif token_type == Type.PARAMETERS and token.string.endswith(','): # Parameter lists. self._Add(TokenInfo(token)) elif token.IsKeyword('var'): self._Add(TokenInfo(token)) elif token.metadata.is_implied_semicolon: self._PopTransient() elif token.IsAssignment(): self._Add(TokenInfo(token)) return indentation_errors