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 _CheckUnusedLocalVariables(self, token, state): """Checks for unused local variables in function blocks. Args: token: The token to check. state: The state tracker. """ # We don't use state.InFunction because that disregards scope functions. in_function = state.FunctionDepth() > 0 if token.type == Type.SIMPLE_LVALUE or token.type == Type.IDENTIFIER: if in_function: identifier = token.string # Check whether the previous token was var. previous_code_token = tokenutil.CustomSearch( token, lambda t: t.type not in Type.NON_CODE_TYPES, reverse=True) if previous_code_token and previous_code_token.IsKeyword( 'var'): # Add local variable declaration to the top of the unused locals # stack. self._unused_local_variables_by_scope[-1][ identifier] = token elif token.type == Type.IDENTIFIER: # This covers most cases where the variable is used as an identifier. self._MarkLocalVariableUsed(token.string) elif token.type == Type.SIMPLE_LVALUE and '.' in identifier: # This covers cases where a value is assigned to a property of the # variable. self._MarkLocalVariableUsed(token.string) elif token.type == Type.START_BLOCK: if in_function and state.IsFunctionOpen(): # Push a new map onto the stack self._unused_local_variables_by_scope.append({}) elif token.type == Type.END_BLOCK: if state.IsFunctionClose(): # Pop the stack and report any remaining locals as unused. unused_local_variables = self._unused_local_variables_by_scope.pop( ) for unused_token in unused_local_variables.values(): self._HandleError( errors.UNUSED_LOCAL_VARIABLE, 'Unused local variable: %s.' % unused_token.string, unused_token) elif token.type == Type.DOC_FLAG: # Flags that use aliased symbols should be counted. flag = token.attached_object js_type = flag and flag.jstype if flag and flag.flag_type in state.GetDocFlag( ).HAS_TYPE and js_type: self._MarkAliasUsed(js_type)
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: last_code = token.metadata.last_code if FLAGS.check_trailing_comma: 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(): 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 or used by a dot operator. if (state.InAssignedFunction() and token.next and token.next.type != Type.SEMICOLON): next_token = tokenutil.GetNextCodeToken(token) is_immediately_used = next_token and ( next_token.type == Type.START_PAREN or tokenutil.IsDot(next_token)) if not is_immediately_used: 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: # Ensure that opening parentheses have a space before any keyword # that is not being invoked like a member function. if (token.previous and token.previous.type == Type.KEYWORD and (not token.previous.metadata or not token.previous.metadata.last_code or not token.previous.metadata.last_code.string or token.previous.metadata.last_code.string[-1:] != '.')): 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 # Ensure that there is no extra space before a function invocation, # even if the function being invoked happens to be a keyword. if (before_space and before_space.line_number == token.line_number and before_space.type == Type.IDENTIFIER or (before_space.type == Type.KEYWORD and before_space.metadata and before_space.metadata.last_code and before_space.metadata.last_code.string and before_space.metadata.last_code.string[-1:] == '.')): 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. last_code = token.metadata.last_code if FLAGS.check_trailing_comma and last_code.IsOperator(','): if token_type in [Type.END_BRACKET, Type.END_PAREN]: self._HandleError( errors.COMMA_AT_END_OF_LITERAL, 'Illegal comma at end of array literal', last_code, position=Position.All(last_code.string)) 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)) 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 flag.jstype.IterIdentifiers(): 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.HasType(): if flag.type_start_token is not None: self._CheckForMissingSpaceBeforeToken( token.attached_object.type_start_token) if flag.jstype and not flag.jstype.IsEmpty(): self._CheckJsDocType(token, flag.jstype) if error_check.ShouldCheck(Rule.BRACES_AROUND_TYPE) and ( flag.type_start_token.type != Type.DOC_START_BRACE or flag.type_end_token.type != Type.DOC_END_BRACE): self._HandleError( errors.MISSING_BRACES_AROUND_TYPE, 'Type must always be surrounded by curly braces.', 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_. if not identifier.startswith('MSG_') and '.MSG_' not in identifier: for f in ('desc', 'hidden', 'meaning'): if jsdoc.HasFlag(f): self._HandleError( errors.INVALID_USE_OF_DESC_TAG, 'Member "%s" does not start with MSG_ and thus ' '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 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() type = token.type # Process the line change. if not self._is_html and FLAGS.strict: # 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 type == Type.PARAMETERS: # Find missing spaces in parameter lists. if self.MISSING_PARAMETER_SPACE.search(token.string): self._HandleError(errors.MISSING_SPACE, 'Missing space after ","', token) # 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(0, space_count)) elif (type == Type.START_BLOCK and token.metadata.context.type == Context.BLOCK): self._CheckForMissingSpaceBeforeToken(token) elif 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.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(): # When the function was top-level and not immediately called, check # that it's terminated by a semi-colon. if state.InAssignedFunction(): if not is_immediately_called and ( last_in_line or not token.next.type == Type.SEMICOLON): self._HandleError( errors.MISSING_SEMICOLON_AFTER_FUNCTION, 'Missing semicolon after function assigned to a variable', token, Position.AtEnd(token.string)) else: 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.All(token.next.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): self._HandleError( errors.REDUNDANT_SEMICOLON, 'No semicolon is required to end a code block', token.next, Position.All(token.next.string)) elif type == Type.SEMICOLON: if token.previous and token.previous.type == Type.WHITESPACE: self._HandleError(errors.EXTRA_SPACE, 'Extra space before ";"', token.previous, 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.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.All(token.string)) elif type == Type.START_PAREN: if token.previous and token.previous.type == Type.KEYWORD: self._HandleError(errors.MISSING_SPACE, 'Missing space before "("', token, 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.All(token.previous.string)) elif type == Type.START_BRACKET: if (not first_in_line and token.previous.type == Type.WHITESPACE and last_non_space_token and last_non_space_token.type in Type.EXPRESSION_ENDER_TYPES): self._HandleError(errors.EXTRA_SPACE, 'Extra space before "["', token.previous, Position.All(token.previous.string)) # If the [ token is the first token in a line we shouldn't complain # about a missing space before [. This is because some Ecma script # languages allow syntax like: # [Annotation] # class MyClass {...} # So we don't want to blindly warn about missing spaces before [. # In the the future, when rules for computing exactly how many spaces # lines should be indented are added, then we can return errors for # [ tokens that are improperly indented. # For example: # var someVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongVariableName = # [a,b,c]; # should trigger a proper indentation warning message as [ is not indented # by four spaces. elif (not first_in_line and token.previous and not token.previous.type in ([Type.WHITESPACE, Type.START_PAREN, Type.START_BRACKET] + Type.EXPRESSION_ENDER_TYPES)): self._HandleError(errors.MISSING_SPACE, 'Missing space before "["', token, Position.AtBeginning()) elif 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.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.All(last_code.string)) elif type == Type.WHITESPACE: if self.ILLEGAL_TAB.search(token.string): if token.IsFirstInLine(): self._HandleError( errors.ILLEGAL_TAB, 'Illegal tab in whitespace before "%s"' % token.next.string, token, Position.All(token.string)) else: self._HandleError( errors.ILLEGAL_TAB, 'Illegal tab in whitespace after "%s"' % token.previous.string, token, 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.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(1, len(token.string) - 1)) elif type == Type.OPERATOR: last_code = token.metadata.last_code if not self._ExpectSpaceBeforeOperator(token): if (token.previous and token.previous.type == Type.WHITESPACE and last_code and last_code.type in (Type.NORMAL, Type.IDENTIFIER)): self._HandleError(errors.EXTRA_SPACE, 'Extra space before "%s"' % token.string, token.previous, Position.All(token.previous.string)) elif (token.previous and not token.previous.IsComment() and token.previous.type in Type.EXPRESSION_ENDER_TYPES): self._HandleError(errors.MISSING_SPACE, 'Missing space before "%s"' % token.string, token, Position.AtBeginning()) # Check that binary operators are not used to start lines. if ((not last_code or last_code.line_number != token.line_number) and not token.metadata.IsUnaryOperator()): self._HandleError( errors.LINE_STARTS_WITH_OPERATOR, 'Binary operator should go on previous line "%s"' % token.string, token) elif 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) elif flag.type not in state.GetDocFlag().SUPPRESS_TYPES: self._HandleError( errors.INVALID_SUPPRESS_TYPE, 'Invalid suppression type: %s' % flag.type, token) elif FLAGS.strict 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(result.start(2), 0)) elif num_spaces > 1: self._HandleError( errors.EXTRA_SPACE, 'Extra space after email address', token.next, 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(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'] self._HandleError( errors.MISSING_JSDOC_TAG_DESCRIPTION, 'Missing description in %s tag' % flag_name, token) else: self._CheckForMissingSpaceBeforeToken( flag.description_start_token) # We want punctuation to be inside of any tags ending a description, # so strip tags before checking description. See bug 1127192. Note # that depending on how lines break, the real description end token # may consist only of stripped html and the effective end token can # be different. end_token = flag.description_end_token end_string = htmlutil.StripTags(end_token.string).strip() while (end_string == '' and not end_token.type in Type.FLAG_ENDING_TYPES): end_token = end_token.previous if end_token.type in Type.FLAG_DESCRIPTION_TYPES: end_string = htmlutil.StripTags( end_token.string).rstrip() if not (end_string.endswith('.') or end_string.endswith('?') or end_string.endswith('!')): # Find the position for the missing punctuation, inside of any html # tags. desc_str = end_token.string.rstrip() while desc_str.endswith('>'): start_tag_index = desc_str.rfind('<') if start_tag_index < 0: break desc_str = desc_str[:start_tag_index].rstrip() end_position = Position(len(desc_str), 0) self._HandleError( errors. JSDOC_TAG_DESCRIPTION_ENDS_WITH_INVALID_CHARACTER, ('%s descriptions must end with valid punctuation such as a ' 'period.' % token.string), end_token, end_position) 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 flag.type != '' and not flag.type.isspace(): self._CheckJsDocType(token) if 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 (FLAGS.strict and token.values['name'] == 'inheritDoc' and type == Type.DOC_INLINE_FLAG): self._HandleError(errors.UNNECESSARY_BRACES_AROUND_INHERIT_DOC, 'Unnecessary braces around @inheritDoc', token) elif type == Type.SIMPLE_LVALUE: identifier = token.values['identifier'] if ((not state.InFunction() or state.InConstructor()) and not state.InParentheses() 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('__'): if jsdoc.HasFlag('override'): self._HandleError( errors.INVALID_OVERRIDE_PRIVATE, '%s should not override a private member.' % identifier, jsdoc.GetFlag('override').flag_token) # Can have a private class which inherits documentation from a # public superclass. if jsdoc.HasFlag('inheritDoc') and not jsdoc.HasFlag( 'constructor'): 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 not ('underscore' 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'): self._HandleError( errors.EXTRA_PRIVATE, 'Member "%s" must not have @private JsDoc' % identifier, token) if ((jsdoc.HasFlag('desc') or jsdoc.HasFlag('hidden')) and not identifier.startswith('MSG_') and identifier.find('.MSG_') == -1): # TODO(user): Update error message to show the actual invalid # tag, either @desc or @hidden. self._HandleError( errors.INVALID_USE_OF_DESC_TAG, 'Member "%s" should not have @desc JsDoc' % identifier, 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 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 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(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)