aboutsummaryrefslogtreecommitdiff
path: root/tools/closure_linter-2.3.4/closure_linter/ecmalintrules.py
diff options
context:
space:
mode:
Diffstat (limited to 'tools/closure_linter-2.3.4/closure_linter/ecmalintrules.py')
-rwxr-xr-xtools/closure_linter-2.3.4/closure_linter/ecmalintrules.py786
1 files changed, 786 insertions, 0 deletions
diff --git a/tools/closure_linter-2.3.4/closure_linter/ecmalintrules.py b/tools/closure_linter-2.3.4/closure_linter/ecmalintrules.py
new file mode 100755
index 0000000..1187f51
--- /dev/null
+++ b/tools/closure_linter-2.3.4/closure_linter/ecmalintrules.py
@@ -0,0 +1,786 @@
+#!/usr/bin/env python
+#
+# Copyright 2008 The Closure Linter Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS-IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Core methods for checking EcmaScript files for common style guide violations.
+"""
+
+__author__ = ('robbyw@google.com (Robert Walker)',
+ 'ajp@google.com (Andy Perelson)',
+ 'jacobr@google.com (Jacob Richman)')
+
+import re
+
+from closure_linter import checkerbase
+from closure_linter import ecmametadatapass
+from closure_linter import error_check
+from closure_linter import errors
+from closure_linter import indentation
+from closure_linter import javascripttokens
+from closure_linter import javascripttokenizer
+from closure_linter import statetracker
+from closure_linter import tokenutil
+from closure_linter.common import error
+from closure_linter.common import htmlutil
+from closure_linter.common import lintrunner
+from closure_linter.common import position
+from closure_linter.common import tokens
+import gflags as flags
+
+FLAGS = flags.FLAGS
+flags.DEFINE_list('custom_jsdoc_tags', '', 'Extra jsdoc tags to allow')
+
+# TODO(robbyw): Check for extra parens on return statements
+# TODO(robbyw): Check for 0px in strings
+# TODO(robbyw): Ensure inline jsDoc is in {}
+# TODO(robbyw): Check for valid JS types in parameter docs
+
+# Shorthand
+Context = ecmametadatapass.EcmaContext
+Error = error.Error
+Modes = javascripttokenizer.JavaScriptModes
+Position = position.Position
+Rule = error_check.Rule
+Type = javascripttokens.JavaScriptTokenType
+
+class EcmaScriptLintRules(checkerbase.LintRulesBase):
+ """EmcaScript lint style checking rules.
+
+ Can be used to find common style errors in JavaScript, ActionScript and other
+ Ecma like scripting languages. Style checkers for Ecma scripting languages
+ should inherit from this style checker.
+ Please do not add any state to EcmaScriptLintRules or to any subclasses.
+
+ All state should be added to the StateTracker subclass used for a particular
+ language.
+ """
+
+ # Static constants.
+ MAX_LINE_LENGTH = 80
+
+ MISSING_PARAMETER_SPACE = re.compile(r',\S')
+
+ EXTRA_SPACE = re.compile('(\(\s|\s\))')
+
+ ENDS_WITH_SPACE = re.compile('\s$')
+
+ ILLEGAL_TAB = re.compile(r'\t')
+
+ # Regex used to split up complex types to check for invalid use of ? and |.
+ TYPE_SPLIT = re.compile(r'[,<>()]')
+
+ # Regex for form of author lines after the @author tag.
+ AUTHOR_SPEC = re.compile(r'(\s*)[^\s]+@[^(\s]+(\s*)\(.+\)')
+
+ # Acceptable tokens to remove for line too long testing.
+ LONG_LINE_IGNORE = frozenset(['*', '//', '@see'] +
+ ['@%s' % tag for tag in statetracker.DocFlag.HAS_TYPE])
+
+ def __init__(self):
+ """Initialize this lint rule object."""
+ checkerbase.LintRulesBase.__init__(self)
+
+ def Initialize(self, checker, limited_doc_checks, is_html):
+ """Initialize this lint rule object before parsing a new file."""
+ checkerbase.LintRulesBase.Initialize(self, checker, limited_doc_checks,
+ is_html)
+ self._indentation = indentation.IndentationRules()
+
+ def HandleMissingParameterDoc(self, token, param_name):
+ """Handle errors associated with a parameter missing a @param tag."""
+ raise TypeError('Abstract method HandleMissingParameterDoc not implemented')
+
+ def _CheckLineLength(self, last_token, state):
+ """Checks whether the line is too long.
+
+ Args:
+ last_token: The last token in the line.
+ """
+ # Start from the last token so that we have the flag object attached to
+ # and DOC_FLAG tokens.
+ line_number = last_token.line_number
+ token = last_token
+
+ # Build a representation of the string where spaces indicate potential
+ # line-break locations.
+ line = []
+ while token and token.line_number == line_number:
+ if state.IsTypeToken(token):
+ line.insert(0, 'x' * len(token.string))
+ elif token.type in (Type.IDENTIFIER, Type.NORMAL):
+ # Dots are acceptable places to wrap.
+ line.insert(0, token.string.replace('.', ' '))
+ else:
+ line.insert(0, token.string)
+ token = token.previous
+
+ line = ''.join(line)
+ line = line.rstrip('\n\r\f')
+ try:
+ length = len(unicode(line, 'utf-8'))
+ except:
+ # Unknown encoding. The line length may be wrong, as was originally the
+ # case for utf-8 (see bug 1735846). For now just accept the default
+ # length, but as we find problems we can either add test for other
+ # possible encodings or return without an error to protect against
+ # false positives at the cost of more false negatives.
+ length = len(line)
+
+ if length > self.MAX_LINE_LENGTH:
+
+ # If the line matches one of the exceptions, then it's ok.
+ for long_line_regexp in self.GetLongLineExceptions():
+ if long_line_regexp.match(last_token.line):
+ return
+
+ # If the line consists of only one "word", or multiple words but all
+ # except one are ignoreable, then it's ok.
+ parts = set(line.split())
+
+ # We allow two "words" (type and name) when the line contains @param
+ max = 1
+ if '@param' in parts:
+ max = 2
+
+ # Custom tags like @requires may have url like descriptions, so ignore
+ # the tag, similar to how we handle @see.
+ custom_tags = set(['@%s' % f for f in FLAGS.custom_jsdoc_tags])
+ if (len(parts.difference(self.LONG_LINE_IGNORE | custom_tags)) > max):
+ self._HandleError(errors.LINE_TOO_LONG,
+ 'Line too long (%d characters).' % len(line), last_token)
+
+ def _CheckJsDocType(self, token):
+ """Checks the given type for style errors.
+
+ Args:
+ token: The DOC_FLAG token for the flag whose type to check.
+ """
+ flag = token.attached_object
+ type = flag.type
+ if type and type is not None and not type.isspace():
+ pieces = self.TYPE_SPLIT.split(type)
+ if len(pieces) == 1 and type.count('|') == 1 and (
+ type.endswith('|null') or type.startswith('null|')):
+ self._HandleError(errors.JSDOC_PREFER_QUESTION_TO_PIPE_NULL,
+ 'Prefer "?Type" to "Type|null": "%s"' % type, token)
+
+ for p in pieces:
+ if p.count('|') and p.count('?'):
+ # TODO(robbyw): We should do actual parsing of JsDoc types. As is,
+ # this won't report an error for {number|Array.<string>?}, etc.
+ self._HandleError(errors.JSDOC_ILLEGAL_QUESTION_WITH_PIPE,
+ 'JsDoc types cannot contain both "?" and "|": "%s"' % p, token)
+
+ 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)
+
+ def _CheckForMissingSpaceBeforeToken(self, token):
+ """Checks for a missing space at the beginning of a token.
+
+ Reports a MISSING_SPACE error if the token does not begin with a space or
+ the previous token doesn't end with a space and the previous token is on the
+ same line as the token.
+
+ Args:
+ token: The token being checked
+ """
+ # TODO(user): Check if too many spaces?
+ if (len(token.string) == len(token.string.lstrip()) and
+ token.previous and token.line_number == token.previous.line_number and
+ len(token.previous.string) - len(token.previous.string.rstrip()) == 0):
+ self._HandleError(
+ errors.MISSING_SPACE,
+ 'Missing space before "%s"' % token.string,
+ token,
+ Position.AtBeginning())
+
+ 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
+
+ # 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 (token.string == ':' and
+ token.metadata.context.type in (Context.LITERAL_ELEMENT,
+ Context.CASE_BLOCK,
+ Context.STATEMENT)):
+ return False
+
+ if token.metadata.IsUnaryOperator() and token.IsFirstInLine():
+ return False
+
+ return True
+
+ 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 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 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:
+ self._HandleStartBracket(token, last_non_space_token)
+ 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():
+ if token.next:
+ 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',
+ 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)
+ else:
+ for suppress_type in flag.type.split('|'):
+ 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 foo@somewhere.com (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 (error_check.ShouldCheck(Rule.NO_BRACES_AROUND_INHERIT_DOC) 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('__'):
+ # 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 not ('accessControls' 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 not ('accessControls' 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
+ not ('underscore' 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 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)
+
+ def _HandleStartBracket(self, token, last_non_space_token):
+ """Handles a token that is an open bracket.
+
+ Args:
+ token: The token to handle.
+ last_non_space_token: The last token that was not a space.
+ """
+ if (not token.IsFirstInLine() 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 token.IsFirstInLine() 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())
+
+ def Finalize(self, state, tokenizer_mode):
+ last_non_space_token = state.GetLastNonSpaceToken()
+ # Check last line for ending with newline.
+ if state.GetLastLine() and not (state.GetLastLine().isspace() or
+ state.GetLastLine().rstrip('\n\r\f') != state.GetLastLine()):
+ self._HandleError(
+ errors.FILE_MISSING_NEWLINE,
+ 'File does not end with new line. (%s)' % state.GetLastLine(),
+ last_non_space_token)
+
+ # Check that the mode is not mid comment, argument list, etc.
+ if not tokenizer_mode == Modes.TEXT_MODE:
+ self._HandleError(
+ errors.FILE_IN_BLOCK,
+ 'File ended in mode "%s".' % tokenizer_mode,
+ last_non_space_token)
+
+ try:
+ self._indentation.Finalize()
+ except Exception, e:
+ self._HandleError(
+ errors.FILE_DOES_NOT_PARSE,
+ str(e),
+ last_non_space_token)
+
+ def GetLongLineExceptions(self):
+ """Gets a list of regexps for lines which can be longer than the limit."""
+ return []
+
+ def InExplicitlyTypedLanguage(self):
+ """Returns whether this ecma implementation is explicitly typed."""
+ return False