aboutsummaryrefslogtreecommitdiff
path: root/tools/closure_linter-2.3.4/closure_linter/javascriptlintrules.py
diff options
context:
space:
mode:
Diffstat (limited to 'tools/closure_linter-2.3.4/closure_linter/javascriptlintrules.py')
-rwxr-xr-xtools/closure_linter-2.3.4/closure_linter/javascriptlintrules.py487
1 files changed, 487 insertions, 0 deletions
diff --git a/tools/closure_linter-2.3.4/closure_linter/javascriptlintrules.py b/tools/closure_linter-2.3.4/closure_linter/javascriptlintrules.py
new file mode 100755
index 0000000..05dd3ee
--- /dev/null
+++ b/tools/closure_linter-2.3.4/closure_linter/javascriptlintrules.py
@@ -0,0 +1,487 @@
+#!/usr/bin/env python
+#
+# Copyright 2011 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.
+
+"""Methods for checking JS files for common style guide violations.
+
+These style guide violations should only apply to JavaScript and not an Ecma
+scripting languages.
+"""
+
+__author__ = ('robbyw@google.com (Robert Walker)',
+ 'ajp@google.com (Andy Perelson)',
+ 'jacobr@google.com (Jacob Richman)')
+
+import re
+from closure_linter import ecmalintrules
+from closure_linter import error_check
+from closure_linter import errors
+from closure_linter import javascripttokenizer
+from closure_linter import javascripttokens
+from closure_linter import requireprovidesorter
+from closure_linter import tokenutil
+from closure_linter.common import error
+from closure_linter.common import position
+
+# Shorthand
+Error = error.Error
+Position = position.Position
+Rule = error_check.Rule
+Type = javascripttokens.JavaScriptTokenType
+
+
+class JavaScriptLintRules(ecmalintrules.EcmaScriptLintRules):
+ """JavaScript lint rules that catch JavaScript specific style errors."""
+
+ def __init__(self, namespaces_info):
+ """Initializes a JavaScriptLintRules instance."""
+ ecmalintrules.EcmaScriptLintRules.__init__(self)
+ self._namespaces_info = namespaces_info
+
+ def HandleMissingParameterDoc(self, token, param_name):
+ """Handle errors associated with a parameter missing a param tag."""
+ self._HandleError(errors.MISSING_PARAMETER_DOCUMENTATION,
+ 'Missing docs for parameter: "%s"' % param_name, token)
+
+ def __ContainsRecordType(self, token):
+ """Check whether the given token contains a record type.
+
+ Args:
+ token: The token being checked
+
+ Returns:
+ True if the token contains a record type, False otherwise.
+ """
+ # If we see more than one left-brace in the string of an annotation token,
+ # then there's a record type in there.
+ return (
+ token and token.type == Type.DOC_FLAG and
+ token.attached_object.type is not None and
+ token.attached_object.type.find('{') != token.string.rfind('{'))
+
+ 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
+ """
+ if self.__ContainsRecordType(token):
+ # We should bail out and not emit any warnings for this annotation.
+ # TODO(nicksantos): Support record types for real.
+ state.GetDocComment().Invalidate()
+ return
+
+ # Call the base class's CheckToken function.
+ super(JavaScriptLintRules, self).CheckToken(token, state)
+
+ # Store some convenience variables
+ namespaces_info = self._namespaces_info
+
+ if 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 (error_check.ShouldCheck(Rule.OPTIONAL_TYPE_MARKER) and
+ flag.type is not None and flag.name is not None):
+ # Check for optional marker in type.
+ if (flag.type.endswith('=') and
+ not flag.name.startswith('opt_')):
+ self._HandleError(errors.JSDOC_MISSING_OPTIONAL_PREFIX,
+ 'Optional parameter name %s must be prefixed '
+ 'with opt_.' % flag.name,
+ token)
+ elif (not flag.type.endswith('=') and
+ flag.name.startswith('opt_')):
+ self._HandleError(errors.JSDOC_MISSING_OPTIONAL_TYPE,
+ 'Optional parameter %s type must end with =.' %
+ flag.name,
+ token)
+
+ if flag.flag_type in state.GetDocFlag().HAS_TYPE:
+ # Check for both missing type token and empty type braces '{}'
+ # Missing suppress types are reported separately and we allow enums
+ # without types.
+ if (flag.flag_type not in ('suppress', 'enum') and
+ (not flag.type or flag.type.isspace())):
+ self._HandleError(errors.MISSING_JSDOC_TAG_TYPE,
+ 'Missing type in %s tag' % token.string, token)
+
+ elif flag.name_token and flag.type_end_token and tokenutil.Compare(
+ flag.type_end_token, flag.name_token) > 0:
+ self._HandleError(
+ errors.OUT_OF_ORDER_JSDOC_TAG_TYPE,
+ 'Type should be immediately after %s tag' % token.string,
+ token)
+
+ elif token.type == Type.DOUBLE_QUOTE_STRING_START:
+ next_token = token.next
+ while next_token.type == Type.STRING_TEXT:
+ if javascripttokenizer.JavaScriptTokenizer.SINGLE_QUOTE.search(
+ next_token.string):
+ break
+ next_token = next_token.next
+ else:
+ self._HandleError(
+ errors.UNNECESSARY_DOUBLE_QUOTED_STRING,
+ 'Single-quoted string preferred over double-quoted string.',
+ token,
+ Position.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.InBlock()):
+
+ # Check if we're in a fileoverview or constructor JsDoc.
+ is_constructor = (
+ doc_comment.HasFlag('constructor') or
+ doc_comment.HasFlag('interface'))
+ is_file_overview = doc_comment.HasFlag('fileoverview')
+
+ # 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_overview 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_overview:
+ 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
+
+ if is_file_overview and blank_lines == 0:
+ 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_overview 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.AtBeginning(),
+ 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.AtBeginning())
+ elif (not function.has_return and
+ not function.has_throw and
+ function.doc and
+ function.doc.HasFlag('return') and
+ not state.InInterfaceMethod()):
+ return_flag = function.doc.GetFlag('return')
+ if (return_flag.type is None or (
+ 'undefined' not in return_flag.type and
+ 'void' not in return_flag.type and
+ '*' not in return_flag.type)):
+ self._HandleError(
+ errors.UNNECESSARY_RETURN_DOCUMENTATION,
+ 'Found @return JsDoc on function that returns nothing',
+ return_flag.flag_token, Position.AtBeginning())
+
+ if state.InFunction() and state.IsFunctionClose():
+ is_immediately_called = (token.next and
+ token.next.type == Type.START_PAREN)
+ 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):
+ 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.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.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.Search(token, Type.STRING_TEXT).string
+
+ # Report extra goog.provide statement.
+ if namespaces_info.IsExtraProvide(token):
+ self._HandleError(
+ errors.EXTRA_GOOG_PROVIDE,
+ 'Unnecessary goog.provide: ' + namespace,
+ token, position=Position.AtBeginning())
+
+ if namespaces_info.IsLastProvide(token):
+ # Report missing provide statements after the last existing provide.
+ missing_provides = namespaces_info.GetMissingProvides()
+ if missing_provides:
+ self._ReportMissingProvides(
+ missing_provides,
+ tokenutil.GetLastTokenInSameLine(token).next,
+ False)
+
+ # If there are no require statements, missing requires should be
+ # reported after the last provide.
+ if not namespaces_info.GetRequiredNamespaces():
+ missing_requires = namespaces_info.GetMissingRequires()
+ if missing_requires:
+ self._ReportMissingRequires(
+ missing_requires,
+ tokenutil.GetLastTokenInSameLine(token).next,
+ True)
+
+ elif (token.string == 'goog.require' and
+ not state.InFunction() and
+ namespaces_info is not None):
+ namespace = tokenutil.Search(token, Type.STRING_TEXT).string
+
+ # 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 namespaces_info.IsExtraRequire(token):
+ self._HandleError(
+ errors.EXTRA_GOOG_REQUIRE,
+ 'Unnecessary goog.require: ' + namespace,
+ token, position=Position.AtBeginning())
+
+ # Report missing goog.require statements.
+ if namespaces_info.IsLastRequire(token):
+ missing_requires = namespaces_info.GetMissingRequires()
+ if missing_requires:
+ self._ReportMissingRequires(
+ missing_requires,
+ tokenutil.GetLastTokenInSameLine(token).next,
+ False)
+
+ elif token.type == Type.OPERATOR:
+ last_in_line = token.IsLastInLine()
+ # If the token is unary and appears to be used in a unary context
+ # it's ok. Otherwise, if it's at the end of the line or immediately
+ # before a comment, it's ok.
+ # Don't report an error before a start bracket - it will be reported
+ # by that token's space checks.
+ if (not token.metadata.IsUnaryOperator() and not last_in_line
+ and not token.next.IsComment()
+ and not token.next.IsOperator(',')
+ and not token.next.type 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.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.All(token.string))
+
+ def _ReportMissingProvides(self, missing_provides, token, need_blank_line):
+ """Reports missing provide statements to the error handler.
+
+ Args:
+ missing_provides: A list of strings where each string is a namespace that
+ should be provided, but is not.
+ token: The token where the error was detected (also where the new provides
+ will be inserted.
+ need_blank_line: Whether a blank line needs to be inserted after the new
+ provides are inserted. May be True, False, or None, where None
+ indicates that the insert location is unknown.
+ """
+ self._HandleError(
+ errors.MISSING_GOOG_PROVIDE,
+ 'Missing the following goog.provide statements:\n' +
+ '\n'.join(map(lambda x: 'goog.provide(\'%s\');' % x,
+ sorted(missing_provides))),
+ token, position=Position.AtBeginning(),
+ fix_data=(missing_provides, need_blank_line))
+
+ def _ReportMissingRequires(self, missing_requires, token, need_blank_line):
+ """Reports missing require statements to the error handler.
+
+ Args:
+ missing_requires: A list of strings where each string is a namespace that
+ should be required, but is not.
+ token: The token where the error was detected (also where the new requires
+ will be inserted.
+ need_blank_line: Whether a blank line needs to be inserted before the new
+ requires are inserted. May be True, False, or None, where None
+ indicates that the insert location is unknown.
+ """
+ self._HandleError(
+ errors.MISSING_GOOG_REQUIRE,
+ 'Missing the following goog.require statements:\n' +
+ '\n'.join(map(lambda x: 'goog.require(\'%s\');' % x,
+ sorted(missing_requires))),
+ token, position=Position.AtBeginning(),
+ fix_data=(missing_requires, need_blank_line))
+
+ def Finalize(self, state, tokenizer_mode):
+ """Perform all checks that need to occur after all lines are processed."""
+ # Call the base class's Finalize function.
+ super(JavaScriptLintRules, self).Finalize(state, tokenizer_mode)
+
+ namespaces_info = self._namespaces_info
+ if namespaces_info is not None:
+ # If there are no provide or require statements, missing provides and
+ # requires should be reported on line 1.
+ if (not namespaces_info.GetProvidedNamespaces() and
+ not namespaces_info.GetRequiredNamespaces()):
+ missing_provides = namespaces_info.GetMissingProvides()
+ if missing_provides:
+ self._ReportMissingProvides(
+ missing_provides, state.GetFirstToken(), None)
+
+ missing_requires = namespaces_info.GetMissingRequires()
+ if missing_requires:
+ self._ReportMissingRequires(
+ missing_requires, state.GetFirstToken(), None)
+
+ self._CheckSortedRequiresProvides(state.GetFirstToken())
+
+ def _CheckSortedRequiresProvides(self, token):
+ """Checks that all goog.require and goog.provide statements are sorted.
+
+ Note that this method needs to be run after missing statements are added to
+ preserve alphabetical order.
+
+ Args:
+ token: The first token in the token stream.
+ """
+ sorter = requireprovidesorter.RequireProvideSorter()
+ provides_result = sorter.CheckProvides(token)
+ if provides_result:
+ self._HandleError(
+ errors.GOOG_PROVIDES_NOT_ALPHABETIZED,
+ 'goog.provide classes must be alphabetized. The correct code is:\n' +
+ '\n'.join(
+ map(lambda x: 'goog.provide(\'%s\');' % x, provides_result[1])),
+ provides_result[0],
+ position=Position.AtBeginning(),
+ fix_data=provides_result[0])
+
+ requires_result = sorter.CheckRequires(token)
+ if requires_result:
+ self._HandleError(
+ errors.GOOG_REQUIRES_NOT_ALPHABETIZED,
+ 'goog.require classes must be alphabetized. The correct code is:\n' +
+ '\n'.join(
+ map(lambda x: 'goog.require(\'%s\');' % x, requires_result[1])),
+ requires_result[0],
+ position=Position.AtBeginning(),
+ fix_data=requires_result[0])
+
+ def GetLongLineExceptions(self):
+ """Gets a list of regexps for lines which can be longer than the limit."""
+ return [
+ re.compile('goog\.require\(.+\);?\s*$'),
+ re.compile('goog\.provide\(.+\);?\s*$')
+ ]