Index: third_party/closure_linter/closure_linter/ecmalintrules.py |
diff --git a/third_party/closure_linter/closure_linter/ecmalintrules.py b/third_party/closure_linter/closure_linter/ecmalintrules.py |
index 1187f514e7326a5baa674082fe9b65679dec1137..6ed2e97f5715e006441b7ad0360ca2e095ae895d 100755 |
--- a/third_party/closure_linter/closure_linter/ecmalintrules.py |
+++ b/third_party/closure_linter/closure_linter/ecmalintrules.py |
@@ -23,21 +23,21 @@ __author__ = ('robbyw@google.com (Robert Walker)', |
import re |
+import gflags as flags |
+ |
from closure_linter import checkerbase |
from closure_linter import ecmametadatapass |
from closure_linter import error_check |
+from closure_linter import errorrules |
from closure_linter import errors |
from closure_linter import indentation |
-from closure_linter import javascripttokens |
from closure_linter import javascripttokenizer |
+from closure_linter import javascripttokens |
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') |
@@ -55,6 +55,7 @@ Position = position.Position |
Rule = error_check.Rule |
Type = javascripttokens.JavaScriptTokenType |
+ |
class EcmaScriptLintRules(checkerbase.LintRulesBase): |
"""EmcaScript lint style checking rules. |
@@ -67,14 +68,15 @@ class EcmaScriptLintRules(checkerbase.LintRulesBase): |
language. |
""" |
- # Static constants. |
- MAX_LINE_LENGTH = 80 |
+ # It will be initialized in constructor so the flags are initialized. |
+ max_line_length = -1 |
+ # Static constants. |
MISSING_PARAMETER_SPACE = re.compile(r',\S') |
- EXTRA_SPACE = re.compile('(\(\s|\s\))') |
+ EXTRA_SPACE = re.compile(r'(\(\s|\s\))') |
- ENDS_WITH_SPACE = re.compile('\s$') |
+ ENDS_WITH_SPACE = re.compile(r'\s$') |
ILLEGAL_TAB = re.compile(r'\t') |
@@ -85,12 +87,18 @@ class EcmaScriptLintRules(checkerbase.LintRulesBase): |
AUTHOR_SPEC = re.compile(r'(\s*)[^\s]+@[^(\s]+(\s*)\(.+\)') |
# Acceptable tokens to remove for line too long testing. |
- LONG_LINE_IGNORE = frozenset(['*', '//', '@see'] + |
+ LONG_LINE_IGNORE = frozenset( |
+ ['*', '//', '@see'] + |
['@%s' % tag for tag in statetracker.DocFlag.HAS_TYPE]) |
+ JSDOC_FLAGS_DESCRIPTION_NOT_REQUIRED = frozenset([ |
+ '@param', '@return', '@returns']) |
+ |
def __init__(self): |
"""Initialize this lint rule object.""" |
checkerbase.LintRulesBase.__init__(self) |
+ if EcmaScriptLintRules.max_line_length == -1: |
+ EcmaScriptLintRules.max_line_length = errorrules.GetMaxLineLength() |
def Initialize(self, checker, limited_doc_checks, is_html): |
"""Initialize this lint rule object before parsing a new file.""" |
@@ -107,6 +115,7 @@ class EcmaScriptLintRules(checkerbase.LintRulesBase): |
Args: |
last_token: The last token in the line. |
+ state: parser_state object that indicates the current state in the page |
""" |
# Start from the last token so that we have the flag object attached to |
# and DOC_FLAG tokens. |
@@ -130,7 +139,7 @@ class EcmaScriptLintRules(checkerbase.LintRulesBase): |
line = line.rstrip('\n\r\f') |
try: |
length = len(unicode(line, 'utf-8')) |
- except: |
+ except (LookupError, UnicodeDecodeError): |
# 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 |
@@ -138,7 +147,7 @@ class EcmaScriptLintRules(checkerbase.LintRulesBase): |
# false positives at the cost of more false negatives. |
length = len(line) |
- if length > self.MAX_LINE_LENGTH: |
+ if length > EcmaScriptLintRules.max_line_length: |
# If the line matches one of the exceptions, then it's ok. |
for long_line_regexp in self.GetLongLineExceptions(): |
@@ -150,15 +159,17 @@ class EcmaScriptLintRules(checkerbase.LintRulesBase): |
parts = set(line.split()) |
# We allow two "words" (type and name) when the line contains @param |
- max = 1 |
+ max_parts = 1 |
if '@param' in parts: |
- max = 2 |
+ max_parts = 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, |
+ if (len(parts.difference(self.LONG_LINE_IGNORE | custom_tags)) |
+ > max_parts): |
+ self._HandleError( |
+ errors.LINE_TOO_LONG, |
'Line too long (%d characters).' % len(line), last_token) |
def _CheckJsDocType(self, token): |
@@ -168,25 +179,23 @@ class EcmaScriptLintRules(checkerbase.LintRulesBase): |
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) |
+ flag_type = flag.type |
+ if flag_type and flag_type is not None and not flag_type.isspace(): |
+ pieces = self.TYPE_SPLIT.split(flag_type) |
+ if len(pieces) == 1 and flag_type.count('|') == 1 and ( |
+ flag_type.endswith('|null') or flag_type.startswith('null|')): |
+ self._HandleError( |
+ errors.JSDOC_PREFER_QUESTION_TO_PIPE_NULL, |
+ 'Prefer "?Type" to "Type|null": "%s"' % flag_type, token) |
+ |
+ # TODO(user): We should do actual parsing of JsDoc types to report an |
+ # error for wrong usage of '?' and '|' e.g. {?number|string|null} etc. |
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, |
+ self._HandleError( |
+ errors.MISSING_BRACES_AROUND_TYPE, |
'Type must always be surrounded by curly braces.', token) |
def _CheckForMissingSpaceBeforeToken(self, token): |
@@ -207,7 +216,37 @@ class EcmaScriptLintRules(checkerbase.LintRulesBase): |
errors.MISSING_SPACE, |
'Missing space before "%s"' % token.string, |
token, |
- Position.AtBeginning()) |
+ position=Position.AtBeginning()) |
+ |
+ 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)): |
+ 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 |
+ token.previous.type in Type.EXPRESSION_ENDER_TYPES): |
+ self._HandleError(errors.MISSING_SPACE, |
+ 'Missing space before "%s"' % token.string, token, |
+ position=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) |
def _ExpectSpaceBeforeOperator(self, token): |
"""Returns whether a space should appear before the given operator token. |
@@ -247,7 +286,7 @@ class EcmaScriptLintRules(checkerbase.LintRulesBase): |
last_in_line = token.IsLastInLine() |
last_non_space_token = state.GetLastNonSpaceToken() |
- type = token.type |
+ token_type = token.type |
# Process the line change. |
if not self._is_html and error_check.ShouldCheck(Rule.INDENTATION): |
@@ -259,11 +298,12 @@ class EcmaScriptLintRules(checkerbase.LintRulesBase): |
if last_in_line: |
self._CheckLineLength(token, state) |
- if type == Type.PARAMETERS: |
+ 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) |
+ 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. |
@@ -271,54 +311,64 @@ class EcmaScriptLintRules(checkerbase.LintRulesBase): |
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)) |
+ token, position=Position(0, space_count)) |
- elif (type == Type.START_BLOCK and |
+ elif (token_type == Type.START_BLOCK and |
token.metadata.context.type == Context.BLOCK): |
self._CheckForMissingSpaceBeforeToken(token) |
- elif type == Type.END_BLOCK: |
+ elif token_type == Type.END_BLOCK: |
# This check is for object literal end block tokens, but there is no need |
# to test that condition since a comma at the end of any other kind of |
# block is undoubtedly a parse error. |
last_code = token.metadata.last_code |
if last_code.IsOperator(','): |
- self._HandleError(errors.COMMA_AT_END_OF_LITERAL, |
+ self._HandleError( |
+ errors.COMMA_AT_END_OF_LITERAL, |
'Illegal comma at end of object literal', last_code, |
- Position.All(last_code.string)) |
+ position=Position.All(last_code.string)) |
if state.InFunction() and state.IsFunctionClose(): |
is_immediately_called = (token.next and |
token.next.type == Type.START_PAREN) |
if state.InTopLevelFunction(): |
- # 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: |
+ # 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, |
+ self._HandleError( |
+ errors.ILLEGAL_SEMICOLON_AFTER_FUNCTION, |
'Illegal semicolon after function declaration', |
- token.next, Position.All(token.next.string)) |
+ token.next, position=Position.All(token.next.string)) |
- if (state.InInterfaceMethod() and last_code.type != Type.START_BLOCK): |
+ # A semicolon should be included at the end of a function expression |
+ # that is not immediately called. |
+ if state.InAssignedFunction(): |
+ if not is_immediately_called and ( |
+ last_in_line or token.next.type != Type.SEMICOLON): |
+ self._HandleError( |
+ errors.MISSING_SEMICOLON_AFTER_FUNCTION, |
+ 'Missing semicolon after function assigned to a variable', |
+ token, position=Position.AtEnd(token.string)) |
+ |
+ if state.InInterfaceMethod() and last_code.type != Type.START_BLOCK: |
self._HandleError(errors.INTERFACE_METHOD_CANNOT_HAVE_CODE, |
- 'Interface methods cannot contain code', last_code) |
+ '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 (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.All(token.previous.string)) |
+ 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: |
@@ -327,10 +377,11 @@ class EcmaScriptLintRules(checkerbase.LintRulesBase): |
elif token.next.type not in ( |
Type.WHITESPACE, Type.SEMICOLON, Type.END_PAREN): |
- self._HandleError(errors.MISSING_SPACE, |
+ self._HandleError( |
+ errors.MISSING_SPACE, |
'Missing space after ";" in for statement', |
token.next, |
- Position.AtBeginning()) |
+ position=Position.AtBeginning()) |
last_code = token.metadata.last_code |
if last_code and last_code.type == Type.SEMICOLON: |
@@ -339,7 +390,8 @@ class EcmaScriptLintRules(checkerbase.LintRulesBase): |
# 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, |
+ 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, |
@@ -347,93 +399,79 @@ class EcmaScriptLintRules(checkerbase.LintRulesBase): |
if not for_token: |
self._HandleError(errors.REDUNDANT_SEMICOLON, 'Redundant semicolon', |
- token, Position.All(token.string)) |
+ token, position=Position.All(token.string)) |
- elif type == Type.START_PAREN: |
+ elif token_type == Type.START_PAREN: |
if token.previous and token.previous.type == Type.KEYWORD: |
self._HandleError(errors.MISSING_SPACE, 'Missing space before "("', |
- token, Position.AtBeginning()) |
+ token, position=Position.AtBeginning()) |
elif token.previous and token.previous.type == Type.WHITESPACE: |
before_space = token.previous.previous |
if (before_space and before_space.line_number == token.line_number and |
before_space.type == Type.IDENTIFIER): |
- self._HandleError(errors.EXTRA_SPACE, 'Extra space before "("', |
- token.previous, Position.All(token.previous.string)) |
+ self._HandleError( |
+ errors.EXTRA_SPACE, 'Extra space before "("', |
+ token.previous, position=Position.All(token.previous.string)) |
- elif type == Type.START_BRACKET: |
+ elif token_type == Type.START_BRACKET: |
self._HandleStartBracket(token, last_non_space_token) |
- elif type in (Type.END_PAREN, Type.END_BRACKET): |
+ elif token_type in (Type.END_PAREN, Type.END_BRACKET): |
# Ensure there is no space before closing parentheses, except when |
# it's in a for statement with an omitted section, or when it's at the |
# beginning of a line. |
if (token.previous and token.previous.type == Type.WHITESPACE and |
not token.previous.IsFirstInLine() and |
not (last_non_space_token and last_non_space_token.line_number == |
- token.line_number and |
+ 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)) |
+ self._HandleError( |
+ errors.EXTRA_SPACE, 'Extra space before "%s"' % |
+ token.string, token.previous, |
+ position=Position.All(token.previous.string)) |
if token.type == Type.END_BRACKET: |
last_code = token.metadata.last_code |
if last_code.IsOperator(','): |
- self._HandleError(errors.COMMA_AT_END_OF_LITERAL, |
+ self._HandleError( |
+ errors.COMMA_AT_END_OF_LITERAL, |
'Illegal comma at end of array literal', last_code, |
- Position.All(last_code.string)) |
+ position=Position.All(last_code.string)) |
- elif type == Type.WHITESPACE: |
+ elif token_type == Type.WHITESPACE: |
if self.ILLEGAL_TAB.search(token.string): |
if token.IsFirstInLine(): |
if token.next: |
- self._HandleError(errors.ILLEGAL_TAB, |
+ self._HandleError( |
+ errors.ILLEGAL_TAB, |
'Illegal tab in whitespace before "%s"' % token.next.string, |
- token, Position.All(token.string)) |
+ token, position=Position.All(token.string)) |
else: |
- self._HandleError(errors.ILLEGAL_TAB, |
+ self._HandleError( |
+ errors.ILLEGAL_TAB, |
'Illegal tab in whitespace', |
- token, Position.All(token.string)) |
+ token, position=Position.All(token.string)) |
else: |
- self._HandleError(errors.ILLEGAL_TAB, |
+ self._HandleError( |
+ errors.ILLEGAL_TAB, |
'Illegal tab in whitespace after "%s"' % token.previous.string, |
- token, Position.All(token.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.All(token.string)) |
+ 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"' % |
+ self._HandleError( |
+ errors.EXTRA_SPACE, 'Extra space after "%s"' % |
token.previous.string, token, |
- Position(1, len(token.string) - 1)) |
+ position=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: |
+ elif token_type == Type.OPERATOR: |
+ self._CheckOperator(token) |
+ elif token_type == Type.DOC_FLAG: |
flag = token.attached_object |
if flag.flag_type == 'bug': |
@@ -443,21 +481,22 @@ class EcmaScriptLintRules(checkerbase.LintRulesBase): |
if not string.isdigit(): |
self._HandleError(errors.NO_BUG_NUMBER_AFTER_BUG_TAG, |
- '@bug should be followed by a bug number', token) |
+ '@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, |
+ self._HandleError( |
+ errors.INCORRECT_SUPPRESS_SYNTAX, |
'Invalid suppress syntax: should be @suppress {errortype}. ' |
'Spaces matter.', token) |
else: |
- for suppress_type in flag.type.split('|'): |
+ for suppress_type in re.split(r'\||,', flag.type): |
if suppress_type not in state.GetDocFlag().SUPPRESS_TYPES: |
- self._HandleError(errors.INVALID_SUPPRESS_TYPE, |
- 'Invalid suppression type: %s' % suppress_type, |
- token) |
+ 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'): |
@@ -478,12 +517,12 @@ class EcmaScriptLintRules(checkerbase.LintRulesBase): |
if num_spaces < 1: |
self._HandleError(errors.MISSING_SPACE, |
'Missing space after email address', |
- token.next, Position(result.start(2), 0)) |
+ 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(result.start(2) + 1, 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. |
@@ -491,81 +530,54 @@ class EcmaScriptLintRules(checkerbase.LintRulesBase): |
if num_spaces > 1: |
self._HandleError(errors.EXTRA_SPACE, |
'Extra space before email address', |
- token.next, Position(1, num_spaces - 1)) |
+ 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) |
+ '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) |
+ if flag_name not in self.JSDOC_FLAGS_DESCRIPTION_NOT_REQUIRED: |
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) |
+ errors.MISSING_JSDOC_TAG_DESCRIPTION, |
+ 'Missing description in %s tag' % flag_name, token) |
+ else: |
+ self._CheckForMissingSpaceBeforeToken(flag.description_start_token) |
if flag.flag_type in state.GetDocFlag().HAS_TYPE: |
if flag.type_start_token is not None: |
self._CheckForMissingSpaceBeforeToken( |
token.attached_object.type_start_token) |
- if flag.type and flag.type != '' and not flag.type.isspace(): |
+ if 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: |
+ 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 |
- not state.InParentheses() and not state.InObjectLiteralDescendant()): |
+ 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 |
@@ -577,9 +589,10 @@ class EcmaScriptLintRules(checkerbase.LintRulesBase): |
self._limited_doc_checks): |
comment = state.GetLastComment() |
if not (comment and comment.lower().count('jsdoc inherited')): |
- self._HandleError(errors.MISSING_MEMBER_DOCUMENTATION, |
+ self._HandleError( |
+ errors.MISSING_MEMBER_DOCUMENTATION, |
"No docs found for member '%s'" % identifier, |
- token); |
+ token) |
elif jsdoc and (not state.InConstructor() or |
identifier.startswith('this.')): |
# We are at the top level and the function/member is documented. |
@@ -589,31 +602,36 @@ class EcmaScriptLintRules(checkerbase.LintRulesBase): |
# |
# @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, |
+ 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 not ('accessControls' in jsdoc.suppressions)): |
- self._HandleError(errors.INVALID_INHERIT_DOC_PRIVATE, |
+ 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 |
- not ('underscore' in jsdoc.suppressions) and not |
+ ('underscore' not in jsdoc.suppressions) and not |
((jsdoc.HasFlag('inheritDoc') or jsdoc.HasFlag('override')) and |
('accessControls' in jsdoc.suppressions))): |
- self._HandleError(errors.MISSING_PRIVATE, |
+ 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, |
+ 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, |
+ self._HandleError( |
+ errors.EXTRA_PRIVATE, |
'Member "%s" must not have @private JsDoc' % |
identifier, token) |
@@ -621,9 +639,10 @@ class EcmaScriptLintRules(checkerbase.LintRulesBase): |
# 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, |
+ 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) |
@@ -636,28 +655,30 @@ class EcmaScriptLintRules(checkerbase.LintRulesBase): |
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, |
+ self._HandleError( |
+ errors.ILLEGAL_PROTOTYPE_MEMBER_VALUE, |
'Member %s cannot have a non-primitive value' % identifier, |
token) |
- elif type == Type.END_PARAMETERS: |
+ 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) |
+ 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, |
+ 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()): |
+ 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()) |
@@ -678,12 +699,13 @@ class EcmaScriptLintRules(checkerbase.LintRulesBase): |
elif op == 'D': |
# Deletion |
self._HandleError(errors.EXTRA_PARAMETER_DOCUMENTATION, |
- 'Found docs for non-existing parameter: "%s"' % |
- docs_iter.next(), token) |
+ '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, |
+ self._HandleError( |
+ errors.WRONG_PARAMETER_DOCUMENTATION, |
'Parameter mismatch: got "%s", expected "%s"' % |
(params_iter.next(), docs_iter.next()), token) |
@@ -692,32 +714,33 @@ class EcmaScriptLintRules(checkerbase.LintRulesBase): |
params_iter.next() |
docs_iter.next() |
- elif type == Type.STRING_TEXT: |
+ 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, |
+ 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) |
- |
+ '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) |
+ '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))) |
+ 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) |
+ 'Missing semicolon at end of line', token) |
def _HandleStartBracket(self, token, last_non_space_token): |
"""Handles a token that is an open bracket. |
@@ -729,8 +752,9 @@ class EcmaScriptLintRules(checkerbase.LintRulesBase): |
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)) |
+ self._HandleError( |
+ errors.EXTRA_SPACE, 'Extra space before "["', |
+ token.previous, position=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: |
@@ -746,29 +770,31 @@ class EcmaScriptLintRules(checkerbase.LintRulesBase): |
# 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 ( |
+ token.previous.type not in ( |
[Type.WHITESPACE, Type.START_PAREN, Type.START_BRACKET] + |
Type.EXPRESSION_ENDER_TYPES)): |
self._HandleError(errors.MISSING_SPACE, 'Missing space before "["', |
- token, Position.AtBeginning()) |
+ token, position=Position.AtBeginning()) |
+ |
+ def Finalize(self, state): |
+ """Perform all checks that need to occur after all lines are processed. |
- def Finalize(self, state, tokenizer_mode): |
+ Args: |
+ state: State of the parser after parsing all tokens |
+ |
+ Raises: |
+ TypeError: If not overridden. |
+ """ |
last_non_space_token = state.GetLastNonSpaceToken() |
# Check last line for ending with newline. |
- if state.GetLastLine() and not (state.GetLastLine().isspace() or |
+ 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: |
@@ -778,7 +804,11 @@ class EcmaScriptLintRules(checkerbase.LintRulesBase): |
last_non_space_token) |
def GetLongLineExceptions(self): |
- """Gets a list of regexps for lines which can be longer than the limit.""" |
+ """Gets a list of regexps for lines which can be longer than the limit. |
+ |
+ Returns: |
+ A list of regexps, used as matches (rather than searches). |
+ """ |
return [] |
def InExplicitlyTypedLanguage(self): |