def _ExpectSpaceBeforeOperator(self, token): """Returns whether a space should appear before the given operator token. Args: token: The operator token. Returns: Whether there should be a space before the token. """ if token.string == ',' or token.metadata.IsUnaryPostOperator(): return False if tokenutil.IsDot(token): return False # Colons should appear in labels, object literals, the case of a switch # statement, and ternary operator. Only want a space in the case of the # ternary operator. if self._IsLabel(token): return False if token.metadata.IsUnaryOperator() and token.IsFirstInLine(): return False return True
def _CheckOperator(self, token): """Checks an operator for spacing and line style. Args: token: The operator token. """ 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) and last_code.line_number == token.line_number): self._HandleError( errors.EXTRA_SPACE, 'Extra space before "%s"' % token.string, token.previous, position=Position.All(token.previous.string)) elif (token.previous and not token.previous.IsComment() and not tokenutil.IsDot(token) and token.previous.type in Type.EXPRESSION_ENDER_TYPES): self._HandleError(errors.MISSING_SPACE, 'Missing space before "%s"' % token.string, token, position=Position.AtBeginning()) # Check wrapping of operators. next_code = tokenutil.GetNextCodeToken(token) is_dot = tokenutil.IsDot(token) wrapped_before = last_code and last_code.line_number != token.line_number wrapped_after = next_code and next_code.line_number != token.line_number if FLAGS.dot_on_next_line and is_dot and wrapped_after: self._HandleError( errors.LINE_ENDS_WITH_DOT, '"." must go on the following line', token) if (not is_dot and wrapped_before and not token.metadata.IsUnaryOperator()): self._HandleError( errors.LINE_STARTS_WITH_OPERATOR, 'Binary operator must go on previous line "%s"' % token.string, 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() 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 """ # 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 ( 'underscore' in doc_comment.suppressions or 'unusedPrivateMembers' in doc_comment.suppressions) 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.jstype.IsVarArgsType() 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.jstype.IsVarArgsType() 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.jstype.opt_arg 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.jstype.opt_arg 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, # const, private, public and protected without types. if (flag.flag_type not in state.GetDocFlag().CAN_OMIT_TYPE and (not flag.jstype or flag.jstype.IsEmpty())): 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()): flag = function.doc.GetFlag('return') valid_no_return_names = ['undefined', 'void', '*'] invalid_return = flag.jstype is None or not any( sub_type.identifier in valid_no_return_names for sub_type in flag.jstype.IterTypeGroup()) if invalid_return: self._HandleError( errors.UNNECESSARY_RETURN_DOCUMENTATION, 'Found @return JsDoc on function that returns nothing', 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, illegal_alias_statements = ( namespaces_info.GetMissingRequires()) if missing_requires: self._ReportMissingRequires( missing_requires, tokenutil.GetLastTokenInSameLine(token).next, True) if illegal_alias_statements: self._ReportIllegalAliasStatement(illegal_alias_statements) 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, illegal_alias_statements = ( namespaces_info.GetMissingRequires()) if missing_requires: self._ReportMissingRequires( missing_requires, tokenutil.GetLastTokenInSameLine(token).next, False) if illegal_alias_statements: self._ReportIllegalAliasStatement(illegal_alias_statements) 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 not tokenutil.IsDot(token) 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))