12da489cd246702bee5938545b18a6f710ed214bcJamie Gennis#!/usr/bin/env python
22da489cd246702bee5938545b18a6f710ed214bcJamie Gennis#
32da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# Copyright 2008 The Closure Linter Authors. All Rights Reserved.
42da489cd246702bee5938545b18a6f710ed214bcJamie Gennis#
52da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# Licensed under the Apache License, Version 2.0 (the "License");
62da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# you may not use this file except in compliance with the License.
72da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# You may obtain a copy of the License at
82da489cd246702bee5938545b18a6f710ed214bcJamie Gennis#
92da489cd246702bee5938545b18a6f710ed214bcJamie Gennis#      http://www.apache.org/licenses/LICENSE-2.0
102da489cd246702bee5938545b18a6f710ed214bcJamie Gennis#
112da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# Unless required by applicable law or agreed to in writing, software
122da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# distributed under the License is distributed on an "AS-IS" BASIS,
132da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
142da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# See the License for the specific language governing permissions and
152da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# limitations under the License.
162da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
172da489cd246702bee5938545b18a6f710ed214bcJamie Gennis"""Core methods for checking EcmaScript files for common style guide violations.
182da489cd246702bee5938545b18a6f710ed214bcJamie Gennis"""
192da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
202da489cd246702bee5938545b18a6f710ed214bcJamie Gennis__author__ = ('robbyw@google.com (Robert Walker)',
212da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              'ajp@google.com (Andy Perelson)',
222da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              'jacobr@google.com (Jacob Richman)')
232da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
242da489cd246702bee5938545b18a6f710ed214bcJamie Gennisimport re
252da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
262da489cd246702bee5938545b18a6f710ed214bcJamie Gennisfrom closure_linter import checkerbase
272da489cd246702bee5938545b18a6f710ed214bcJamie Gennisfrom closure_linter import ecmametadatapass
282da489cd246702bee5938545b18a6f710ed214bcJamie Gennisfrom closure_linter import error_check
292da489cd246702bee5938545b18a6f710ed214bcJamie Gennisfrom closure_linter import errors
302da489cd246702bee5938545b18a6f710ed214bcJamie Gennisfrom closure_linter import indentation
312da489cd246702bee5938545b18a6f710ed214bcJamie Gennisfrom closure_linter import javascripttokens
322da489cd246702bee5938545b18a6f710ed214bcJamie Gennisfrom closure_linter import javascripttokenizer
332da489cd246702bee5938545b18a6f710ed214bcJamie Gennisfrom closure_linter import statetracker
342da489cd246702bee5938545b18a6f710ed214bcJamie Gennisfrom closure_linter import tokenutil
352da489cd246702bee5938545b18a6f710ed214bcJamie Gennisfrom closure_linter.common import error
362da489cd246702bee5938545b18a6f710ed214bcJamie Gennisfrom closure_linter.common import htmlutil
372da489cd246702bee5938545b18a6f710ed214bcJamie Gennisfrom closure_linter.common import lintrunner
382da489cd246702bee5938545b18a6f710ed214bcJamie Gennisfrom closure_linter.common import position
392da489cd246702bee5938545b18a6f710ed214bcJamie Gennisfrom closure_linter.common import tokens
402da489cd246702bee5938545b18a6f710ed214bcJamie Gennisimport gflags as flags
412da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
422da489cd246702bee5938545b18a6f710ed214bcJamie GennisFLAGS = flags.FLAGS
432da489cd246702bee5938545b18a6f710ed214bcJamie Gennisflags.DEFINE_list('custom_jsdoc_tags', '', 'Extra jsdoc tags to allow')
442da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
452da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# TODO(robbyw): Check for extra parens on return statements
462da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# TODO(robbyw): Check for 0px in strings
472da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# TODO(robbyw): Ensure inline jsDoc is in {}
482da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# TODO(robbyw): Check for valid JS types in parameter docs
492da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
502da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# Shorthand
512da489cd246702bee5938545b18a6f710ed214bcJamie GennisContext = ecmametadatapass.EcmaContext
522da489cd246702bee5938545b18a6f710ed214bcJamie GennisError = error.Error
532da489cd246702bee5938545b18a6f710ed214bcJamie GennisModes = javascripttokenizer.JavaScriptModes
542da489cd246702bee5938545b18a6f710ed214bcJamie GennisPosition = position.Position
552da489cd246702bee5938545b18a6f710ed214bcJamie GennisRule = error_check.Rule
562da489cd246702bee5938545b18a6f710ed214bcJamie GennisType = javascripttokens.JavaScriptTokenType
572da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
582da489cd246702bee5938545b18a6f710ed214bcJamie Gennisclass EcmaScriptLintRules(checkerbase.LintRulesBase):
592da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  """EmcaScript lint style checking rules.
602da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
612da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  Can be used to find common style errors in JavaScript, ActionScript and other
622da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  Ecma like scripting languages.  Style checkers for Ecma scripting languages
632da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  should inherit from this style checker.
642da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  Please do not add any state to EcmaScriptLintRules or to any subclasses.
652da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
662da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  All state should be added to the StateTracker subclass used for a particular
672da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  language.
682da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  """
692da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
702da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  # Static constants.
712da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  MAX_LINE_LENGTH = 80
722da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
732da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  MISSING_PARAMETER_SPACE = re.compile(r',\S')
742da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
752da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  EXTRA_SPACE = re.compile('(\(\s|\s\))')
762da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
772da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  ENDS_WITH_SPACE = re.compile('\s$')
782da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
792da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  ILLEGAL_TAB = re.compile(r'\t')
802da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
812da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  # Regex used to split up complex types to check for invalid use of ? and |.
822da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  TYPE_SPLIT = re.compile(r'[,<>()]')
832da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
842da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  # Regex for form of author lines after the @author tag.
852da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  AUTHOR_SPEC = re.compile(r'(\s*)[^\s]+@[^(\s]+(\s*)\(.+\)')
862da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
872da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  # Acceptable tokens to remove for line too long testing.
882da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  LONG_LINE_IGNORE = frozenset(['*', '//', '@see'] +
892da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      ['@%s' % tag for tag in statetracker.DocFlag.HAS_TYPE])
902da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
912da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  def __init__(self):
922da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """Initialize this lint rule object."""
932da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    checkerbase.LintRulesBase.__init__(self)
942da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
952da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  def Initialize(self, checker, limited_doc_checks, is_html):
962da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """Initialize this lint rule object before parsing a new file."""
972da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    checkerbase.LintRulesBase.Initialize(self, checker, limited_doc_checks,
982da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                                         is_html)
992da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    self._indentation = indentation.IndentationRules()
1002da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1012da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  def HandleMissingParameterDoc(self, token, param_name):
1022da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """Handle errors associated with a parameter missing a @param tag."""
1032da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    raise TypeError('Abstract method HandleMissingParameterDoc not implemented')
1042da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1052da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  def _CheckLineLength(self, last_token, state):
1062da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """Checks whether the line is too long.
1072da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1082da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    Args:
1092da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      last_token: The last token in the line.
1102da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """
1112da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    # Start from the last token so that we have the flag object attached to
1122da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    # and DOC_FLAG tokens.
1132da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    line_number = last_token.line_number
1142da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    token = last_token
1152da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1162da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    # Build a representation of the string where spaces indicate potential
1172da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    # line-break locations.
1182da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    line = []
1192da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    while token and token.line_number == line_number:
1202da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      if state.IsTypeToken(token):
1212da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        line.insert(0, 'x' * len(token.string))
1222da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      elif token.type in (Type.IDENTIFIER, Type.NORMAL):
1232da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        # Dots are acceptable places to wrap.
1242da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        line.insert(0, token.string.replace('.', ' '))
1252da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      else:
1262da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        line.insert(0, token.string)
1272da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      token = token.previous
1282da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1292da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    line = ''.join(line)
1302da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    line = line.rstrip('\n\r\f')
1312da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    try:
1322da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      length = len(unicode(line, 'utf-8'))
1332da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    except:
1342da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      # Unknown encoding. The line length may be wrong, as was originally the
1352da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      # case for utf-8 (see bug 1735846). For now just accept the default
1362da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      # length, but as we find problems we can either add test for other
1372da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      # possible encodings or return without an error to protect against
1382da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      # false positives at the cost of more false negatives.
1392da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      length = len(line)
1402da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1412da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    if length > self.MAX_LINE_LENGTH:
1422da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1432da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      # If the line matches one of the exceptions, then it's ok.
1442da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      for long_line_regexp in self.GetLongLineExceptions():
1452da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        if long_line_regexp.match(last_token.line):
1462da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          return
1472da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1482da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      # If the line consists of only one "word", or multiple words but all
1492da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      # except one are ignoreable, then it's ok.
1502da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      parts = set(line.split())
1512da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1522da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      # We allow two "words" (type and name) when the line contains @param
1532da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      max = 1
1542da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      if '@param' in parts:
1552da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        max = 2
1562da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1572da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      # Custom tags like @requires may have url like descriptions, so ignore
1582da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      # the tag, similar to how we handle @see.
1592da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      custom_tags = set(['@%s' % f for f in FLAGS.custom_jsdoc_tags])
1602da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      if (len(parts.difference(self.LONG_LINE_IGNORE | custom_tags)) > max):
1612da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        self._HandleError(errors.LINE_TOO_LONG,
1622da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            'Line too long (%d characters).' % len(line), last_token)
1632da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1642da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  def _CheckJsDocType(self, token):
1652da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """Checks the given type for style errors.
1662da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1672da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    Args:
1682da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      token: The DOC_FLAG token for the flag whose type to check.
1692da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """
1702da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    flag = token.attached_object
1712da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    type = flag.type
1722da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    if type and type is not None and not type.isspace():
1732da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      pieces = self.TYPE_SPLIT.split(type)
1742da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      if len(pieces) == 1 and type.count('|') == 1 and (
1752da489cd246702bee5938545b18a6f710ed214bcJamie Gennis           type.endswith('|null') or type.startswith('null|')):
1762da489cd246702bee5938545b18a6f710ed214bcJamie Gennis         self._HandleError(errors.JSDOC_PREFER_QUESTION_TO_PIPE_NULL,
1772da489cd246702bee5938545b18a6f710ed214bcJamie Gennis             'Prefer "?Type" to "Type|null": "%s"' % type, token)
1782da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1792da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      for p in pieces:
1802da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        if p.count('|') and p.count('?'):
1812da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          # TODO(robbyw): We should do actual parsing of JsDoc types.  As is,
1822da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          # this won't report an error for {number|Array.<string>?}, etc.
1832da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          self._HandleError(errors.JSDOC_ILLEGAL_QUESTION_WITH_PIPE,
1842da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              'JsDoc types cannot contain both "?" and "|": "%s"' % p, token)
1852da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1862da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      if error_check.ShouldCheck(Rule.BRACES_AROUND_TYPE) and (
1872da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          flag.type_start_token.type != Type.DOC_START_BRACE or
1882da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          flag.type_end_token.type != Type.DOC_END_BRACE):
1892da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        self._HandleError(errors.MISSING_BRACES_AROUND_TYPE,
1902da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            'Type must always be surrounded by curly braces.', token)
1912da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1922da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  def _CheckForMissingSpaceBeforeToken(self, token):
1932da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """Checks for a missing space at the beginning of a token.
1942da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1952da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    Reports a MISSING_SPACE error if the token does not begin with a space or
1962da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    the previous token doesn't end with a space and the previous token is on the
1972da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    same line as the token.
1982da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1992da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    Args:
2002da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      token: The token being checked
2012da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """
2022da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    # TODO(user): Check if too many spaces?
2032da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    if (len(token.string) == len(token.string.lstrip()) and
2042da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        token.previous and token.line_number == token.previous.line_number and
2052da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        len(token.previous.string) - len(token.previous.string.rstrip()) == 0):
2062da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      self._HandleError(
2072da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          errors.MISSING_SPACE,
2082da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          'Missing space before "%s"' % token.string,
2092da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          token,
2102da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          Position.AtBeginning())
2112da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
2122da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  def _ExpectSpaceBeforeOperator(self, token):
2132da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """Returns whether a space should appear before the given operator token.
2142da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
2152da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    Args:
2162da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      token: The operator token.
2172da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
2182da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    Returns:
2192da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      Whether there should be a space before the token.
2202da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """
2212da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    if token.string == ',' or token.metadata.IsUnaryPostOperator():
2222da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      return False
2232da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
2242da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    # Colons should appear in labels, object literals, the case of a switch
2252da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    # statement, and ternary operator. Only want a space in the case of the
2262da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    # ternary operator.
2272da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    if (token.string == ':' and
2282da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        token.metadata.context.type in (Context.LITERAL_ELEMENT,
2292da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                                        Context.CASE_BLOCK,
2302da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                                        Context.STATEMENT)):
2312da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      return False
2322da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
2332da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    if token.metadata.IsUnaryOperator() and token.IsFirstInLine():
2342da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      return False
2352da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
2362da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    return True
2372da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
2382da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  def CheckToken(self, token, state):
2392da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """Checks a token, given the current parser_state, for warnings and errors.
2402da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
2412da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    Args:
2422da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      token: The current token under consideration
2432da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      state: parser_state object that indicates the current state in the page
2442da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """
2452da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    # Store some convenience variables
2462da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    first_in_line = token.IsFirstInLine()
2472da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    last_in_line = token.IsLastInLine()
2482da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    last_non_space_token = state.GetLastNonSpaceToken()
2492da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
2502da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    type = token.type
2512da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
2522da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    # Process the line change.
2532da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    if not self._is_html and error_check.ShouldCheck(Rule.INDENTATION):
2542da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      # TODO(robbyw): Support checking indentation in HTML files.
2552da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      indentation_errors = self._indentation.CheckToken(token, state)
2562da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      for indentation_error in indentation_errors:
2572da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        self._HandleError(*indentation_error)
2582da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
2592da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    if last_in_line:
2602da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      self._CheckLineLength(token, state)
2612da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
2622da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    if type == Type.PARAMETERS:
2632da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      # Find missing spaces in parameter lists.
2642da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      if self.MISSING_PARAMETER_SPACE.search(token.string):
2652da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        self._HandleError(errors.MISSING_SPACE, 'Missing space after ","',
2662da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            token)
2672da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
2682da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      # Find extra spaces at the beginning of parameter lists.  Make sure
2692da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      # we aren't at the beginning of a continuing multi-line list.
2702da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      if not first_in_line:
2712da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        space_count = len(token.string) - len(token.string.lstrip())
2722da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        if space_count:
2732da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          self._HandleError(errors.EXTRA_SPACE, 'Extra space after "("',
2742da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              token, Position(0, space_count))
2752da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
2762da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    elif (type == Type.START_BLOCK and
2772da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          token.metadata.context.type == Context.BLOCK):
2782da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      self._CheckForMissingSpaceBeforeToken(token)
2792da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
2802da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    elif type == Type.END_BLOCK:
2812da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      # This check is for object literal end block tokens, but there is no need
2822da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      # to test that condition since a comma at the end of any other kind of
2832da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      # block is undoubtedly a parse error.
2842da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      last_code = token.metadata.last_code
2852da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      if last_code.IsOperator(','):
2862da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        self._HandleError(errors.COMMA_AT_END_OF_LITERAL,
2872da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            'Illegal comma at end of object literal', last_code,
2882da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            Position.All(last_code.string))
2892da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
2902da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      if state.InFunction() and state.IsFunctionClose():
2912da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        is_immediately_called = (token.next and
2922da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                                 token.next.type == Type.START_PAREN)
2932da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        if state.InTopLevelFunction():
2942da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          # When the function was top-level and not immediately called, check
2952da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          # that it's terminated by a semi-colon.
2962da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          if state.InAssignedFunction():
2972da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            if not is_immediately_called and (last_in_line or
2982da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                not token.next.type == Type.SEMICOLON):
2992da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              self._HandleError(errors.MISSING_SEMICOLON_AFTER_FUNCTION,
3002da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                  'Missing semicolon after function assigned to a variable',
3012da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                  token, Position.AtEnd(token.string))
3022da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          else:
3032da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            if not last_in_line and token.next.type == Type.SEMICOLON:
3042da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              self._HandleError(errors.ILLEGAL_SEMICOLON_AFTER_FUNCTION,
3052da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                  'Illegal semicolon after function declaration',
3062da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                  token.next, Position.All(token.next.string))
3072da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
3082da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        if (state.InInterfaceMethod() and last_code.type != Type.START_BLOCK):
3092da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          self._HandleError(errors.INTERFACE_METHOD_CANNOT_HAVE_CODE,
3102da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              'Interface methods cannot contain code', last_code)
3112da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
3122da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      elif (state.IsBlockClose() and
3132da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            token.next and token.next.type == Type.SEMICOLON):
3142da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        self._HandleError(errors.REDUNDANT_SEMICOLON,
3152da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            'No semicolon is required to end a code block',
3162da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            token.next, Position.All(token.next.string))
3172da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
3182da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    elif type == Type.SEMICOLON:
3192da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      if token.previous and token.previous.type == Type.WHITESPACE:
3202da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        self._HandleError(errors.EXTRA_SPACE, 'Extra space before ";"',
3212da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            token.previous, Position.All(token.previous.string))
3222da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
3232da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      if token.next and token.next.line_number == token.line_number:
3242da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        if token.metadata.context.type != Context.FOR_GROUP_BLOCK:
3252da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          # TODO(robbyw): Error about no multi-statement lines.
3262da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          pass
3272da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
3282da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        elif token.next.type not in (
3292da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            Type.WHITESPACE, Type.SEMICOLON, Type.END_PAREN):
3302da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          self._HandleError(errors.MISSING_SPACE,
3312da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              'Missing space after ";" in for statement',
3322da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              token.next,
3332da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              Position.AtBeginning())
3342da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
3352da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      last_code = token.metadata.last_code
3362da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      if last_code and last_code.type == Type.SEMICOLON:
3372da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        # Allow a single double semi colon in for loops for cases like:
3382da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        # for (;;) { }.
3392da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        # NOTE(user): This is not a perfect check, and will not throw an error
3402da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        # for cases like: for (var i = 0;; i < n; i++) {}, but then your code
3412da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        # probably won't work either.
3422da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        for_token = tokenutil.CustomSearch(last_code,
3432da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            lambda token: token.type == Type.KEYWORD and token.string == 'for',
3442da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            end_func=lambda token: token.type == Type.SEMICOLON,
3452da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            distance=None,
3462da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            reverse=True)
3472da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
3482da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        if not for_token:
3492da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          self._HandleError(errors.REDUNDANT_SEMICOLON, 'Redundant semicolon',
3502da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              token, Position.All(token.string))
3512da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
3522da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    elif type == Type.START_PAREN:
3532da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      if token.previous and token.previous.type == Type.KEYWORD:
3542da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        self._HandleError(errors.MISSING_SPACE, 'Missing space before "("',
3552da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            token, Position.AtBeginning())
3562da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      elif token.previous and token.previous.type == Type.WHITESPACE:
3572da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        before_space = token.previous.previous
3582da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        if (before_space and before_space.line_number == token.line_number and
3592da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            before_space.type == Type.IDENTIFIER):
3602da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          self._HandleError(errors.EXTRA_SPACE, 'Extra space before "("',
3612da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              token.previous, Position.All(token.previous.string))
3622da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
3632da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    elif type == Type.START_BRACKET:
3642da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      self._HandleStartBracket(token, last_non_space_token)
3652da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    elif type in (Type.END_PAREN, Type.END_BRACKET):
3662da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      # Ensure there is no space before closing parentheses, except when
3672da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      # it's in a for statement with an omitted section, or when it's at the
3682da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      # beginning of a line.
3692da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      if (token.previous and token.previous.type == Type.WHITESPACE and
3702da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          not token.previous.IsFirstInLine() and
3712da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          not (last_non_space_token and last_non_space_token.line_number ==
3722da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                   token.line_number and
3732da489cd246702bee5938545b18a6f710ed214bcJamie Gennis               last_non_space_token.type == Type.SEMICOLON)):
3742da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        self._HandleError(errors.EXTRA_SPACE, 'Extra space before "%s"' %
3752da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            token.string, token.previous, Position.All(token.previous.string))
3762da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
3772da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      if token.type == Type.END_BRACKET:
3782da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        last_code = token.metadata.last_code
3792da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        if last_code.IsOperator(','):
3802da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          self._HandleError(errors.COMMA_AT_END_OF_LITERAL,
3812da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              'Illegal comma at end of array literal', last_code,
3822da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              Position.All(last_code.string))
3832da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
3842da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    elif type == Type.WHITESPACE:
3852da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      if self.ILLEGAL_TAB.search(token.string):
3862da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        if token.IsFirstInLine():
3872da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          if token.next:
3882da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            self._HandleError(errors.ILLEGAL_TAB,
3892da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                'Illegal tab in whitespace before "%s"' % token.next.string,
3902da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                token, Position.All(token.string))
3912da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          else:
3922da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            self._HandleError(errors.ILLEGAL_TAB,
3932da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                'Illegal tab in whitespace',
3942da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                token, Position.All(token.string))
3952da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        else:
3962da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          self._HandleError(errors.ILLEGAL_TAB,
3972da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              'Illegal tab in whitespace after "%s"' % token.previous.string,
3982da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              token, Position.All(token.string))
3992da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
4002da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      # Check whitespace length if it's not the first token of the line and
4012da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      # if it's not immediately before a comment.
4022da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      if last_in_line:
4032da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        # Check for extra whitespace at the end of a line.
4042da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        self._HandleError(errors.EXTRA_SPACE, 'Extra space at end of line',
4052da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            token, Position.All(token.string))
4062da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      elif not first_in_line and not token.next.IsComment():
4072da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        if token.length > 1:
4082da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          self._HandleError(errors.EXTRA_SPACE, 'Extra space after "%s"' %
4092da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              token.previous.string, token,
4102da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              Position(1, len(token.string) - 1))
4112da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
4122da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    elif type == Type.OPERATOR:
4132da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      last_code = token.metadata.last_code
4142da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
4152da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      if not self._ExpectSpaceBeforeOperator(token):
4162da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        if (token.previous and token.previous.type == Type.WHITESPACE and
4172da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            last_code and last_code.type in (Type.NORMAL, Type.IDENTIFIER)):
4182da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          self._HandleError(errors.EXTRA_SPACE,
4192da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              'Extra space before "%s"' % token.string, token.previous,
4202da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              Position.All(token.previous.string))
4212da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
4222da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      elif (token.previous and
4232da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            not token.previous.IsComment() and
4242da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            token.previous.type in Type.EXPRESSION_ENDER_TYPES):
4252da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        self._HandleError(errors.MISSING_SPACE,
4262da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                          'Missing space before "%s"' % token.string, token,
4272da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                          Position.AtBeginning())
4282da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
4292da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      # Check that binary operators are not used to start lines.
4302da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      if ((not last_code or last_code.line_number != token.line_number) and
4312da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          not token.metadata.IsUnaryOperator()):
4322da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        self._HandleError(errors.LINE_STARTS_WITH_OPERATOR,
4332da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            'Binary operator should go on previous line "%s"' % token.string,
4342da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            token)
4352da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
4362da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    elif type == Type.DOC_FLAG:
4372da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      flag = token.attached_object
4382da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
4392da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      if flag.flag_type == 'bug':
4402da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        # TODO(robbyw): Check for exactly 1 space on the left.
4412da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        string = token.next.string.lstrip()
4422da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        string = string.split(' ', 1)[0]
4432da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
4442da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        if not string.isdigit():
4452da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          self._HandleError(errors.NO_BUG_NUMBER_AFTER_BUG_TAG,
4462da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              '@bug should be followed by a bug number', token)
4472da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
4482da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      elif flag.flag_type == 'suppress':
4492da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        if flag.type is None:
4502da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          # A syntactically invalid suppress tag will get tokenized as a normal
4512da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          # flag, indicating an error.
4522da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          self._HandleError(errors.INCORRECT_SUPPRESS_SYNTAX,
4532da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              'Invalid suppress syntax: should be @suppress {errortype}. '
4542da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              'Spaces matter.', token)
4552da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        else:
4562da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          for suppress_type in flag.type.split('|'):
4572da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            if suppress_type not in state.GetDocFlag().SUPPRESS_TYPES:
4582da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              self._HandleError(errors.INVALID_SUPPRESS_TYPE,
4592da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                'Invalid suppression type: %s' % suppress_type,
4602da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                token)
4612da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
4622da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      elif (error_check.ShouldCheck(Rule.WELL_FORMED_AUTHOR) and
4632da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            flag.flag_type == 'author'):
4642da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        # TODO(user): In non strict mode check the author tag for as much as
4652da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        # it exists, though the full form checked below isn't required.
4662da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        string = token.next.string
4672da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        result = self.AUTHOR_SPEC.match(string)
4682da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        if not result:
4692da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          self._HandleError(errors.INVALID_AUTHOR_TAG_DESCRIPTION,
4702da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                            'Author tag line should be of the form: '
4712da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                            '@author foo@somewhere.com (Your Name)',
4722da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                            token.next)
4732da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        else:
4742da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          # Check spacing between email address and name. Do this before
4752da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          # checking earlier spacing so positions are easier to calculate for
4762da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          # autofixing.
4772da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          num_spaces = len(result.group(2))
4782da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          if num_spaces < 1:
4792da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            self._HandleError(errors.MISSING_SPACE,
4802da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                              'Missing space after email address',
4812da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                              token.next, Position(result.start(2), 0))
4822da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          elif num_spaces > 1:
4832da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            self._HandleError(errors.EXTRA_SPACE,
4842da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                              'Extra space after email address',
4852da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                              token.next,
4862da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                              Position(result.start(2) + 1, num_spaces - 1))
4872da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
4882da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          # Check for extra spaces before email address. Can't be too few, if
4892da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          # not at least one we wouldn't match @author tag.
4902da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          num_spaces = len(result.group(1))
4912da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          if num_spaces > 1:
4922da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            self._HandleError(errors.EXTRA_SPACE,
4932da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                              'Extra space before email address',
4942da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                              token.next, Position(1, num_spaces - 1))
4952da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
4962da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      elif (flag.flag_type in state.GetDocFlag().HAS_DESCRIPTION and
4972da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            not self._limited_doc_checks):
4982da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        if flag.flag_type == 'param':
4992da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          if flag.name is None:
5002da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            self._HandleError(errors.MISSING_JSDOC_PARAM_NAME,
5012da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                'Missing name in @param tag', token)
5022da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
5032da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        if not flag.description or flag.description is None:
5042da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          flag_name = token.type
5052da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          if 'name' in token.values:
5062da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            flag_name = '@' + token.values['name']
5072da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          self._HandleError(errors.MISSING_JSDOC_TAG_DESCRIPTION,
5082da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              'Missing description in %s tag' % flag_name, token)
5092da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        else:
5102da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          self._CheckForMissingSpaceBeforeToken(flag.description_start_token)
5112da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
5122da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          # We want punctuation to be inside of any tags ending a description,
5132da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          # so strip tags before checking description. See bug 1127192. Note
5142da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          # that depending on how lines break, the real description end token
5152da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          # may consist only of stripped html and the effective end token can
5162da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          # be different.
5172da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          end_token = flag.description_end_token
5182da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          end_string = htmlutil.StripTags(end_token.string).strip()
5192da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          while (end_string == '' and not
5202da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                 end_token.type in Type.FLAG_ENDING_TYPES):
5212da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            end_token = end_token.previous
5222da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            if end_token.type in Type.FLAG_DESCRIPTION_TYPES:
5232da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              end_string = htmlutil.StripTags(end_token.string).rstrip()
5242da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
5252da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          if not (end_string.endswith('.') or end_string.endswith('?') or
5262da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              end_string.endswith('!')):
5272da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            # Find the position for the missing punctuation, inside of any html
5282da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            # tags.
5292da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            desc_str = end_token.string.rstrip()
5302da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            while desc_str.endswith('>'):
5312da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              start_tag_index = desc_str.rfind('<')
5322da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              if start_tag_index < 0:
5332da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                break
5342da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              desc_str = desc_str[:start_tag_index].rstrip()
5352da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            end_position = Position(len(desc_str), 0)
5362da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
5372da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            self._HandleError(
5382da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                errors.JSDOC_TAG_DESCRIPTION_ENDS_WITH_INVALID_CHARACTER,
5392da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                ('%s descriptions must end with valid punctuation such as a '
5402da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                 'period.' % token.string),
5412da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                end_token, end_position)
5422da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
5432da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      if flag.flag_type in state.GetDocFlag().HAS_TYPE:
5442da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        if flag.type_start_token is not None:
5452da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          self._CheckForMissingSpaceBeforeToken(
5462da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              token.attached_object.type_start_token)
5472da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
5482da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        if flag.type and flag.type != '' and not flag.type.isspace():
5492da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          self._CheckJsDocType(token)
5502da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
5512da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    if type in (Type.DOC_FLAG, Type.DOC_INLINE_FLAG):
5522da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        if (token.values['name'] not in state.GetDocFlag().LEGAL_DOC and
5532da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            token.values['name'] not in FLAGS.custom_jsdoc_tags):
5542da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          self._HandleError(errors.INVALID_JSDOC_TAG,
5552da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              'Invalid JsDoc tag: %s' % token.values['name'], token)
5562da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
5572da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        if (error_check.ShouldCheck(Rule.NO_BRACES_AROUND_INHERIT_DOC) and
5582da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            token.values['name'] == 'inheritDoc' and
5592da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            type == Type.DOC_INLINE_FLAG):
5602da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          self._HandleError(errors.UNNECESSARY_BRACES_AROUND_INHERIT_DOC,
5612da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              'Unnecessary braces around @inheritDoc',
5622da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              token)
5632da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
5642da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    elif type == Type.SIMPLE_LVALUE:
5652da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      identifier = token.values['identifier']
5662da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
5672da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      if ((not state.InFunction() or state.InConstructor()) and
5682da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          not state.InParentheses() and not state.InObjectLiteralDescendant()):
5692da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        jsdoc = state.GetDocComment()
5702da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        if not state.HasDocComment(identifier):
5712da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          # Only test for documentation on identifiers with .s in them to
5722da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          # avoid checking things like simple variables. We don't require
5732da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          # documenting assignments to .prototype itself (bug 1880803).
5742da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          if (not state.InConstructor() and
5752da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              identifier.find('.') != -1 and not
5762da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              identifier.endswith('.prototype') and not
5772da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              self._limited_doc_checks):
5782da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            comment = state.GetLastComment()
5792da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            if not (comment and comment.lower().count('jsdoc inherited')):
5802da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              self._HandleError(errors.MISSING_MEMBER_DOCUMENTATION,
5812da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                  "No docs found for member '%s'" % identifier,
5822da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                  token);
5832da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        elif jsdoc and (not state.InConstructor() or
5842da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                        identifier.startswith('this.')):
5852da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          # We are at the top level and the function/member is documented.
5862da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          if identifier.endswith('_') and not identifier.endswith('__'):
5872da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            # Can have a private class which inherits documentation from a
5882da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            # public superclass.
5892da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            #
5902da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            # @inheritDoc is deprecated in favor of using @override, and they
5912da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            if (jsdoc.HasFlag('override') and not jsdoc.HasFlag('constructor')
5922da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                and not ('accessControls' in jsdoc.suppressions)):
5932da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              self._HandleError(errors.INVALID_OVERRIDE_PRIVATE,
5942da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                  '%s should not override a private member.' % identifier,
5952da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                  jsdoc.GetFlag('override').flag_token)
5962da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            if (jsdoc.HasFlag('inheritDoc') and not jsdoc.HasFlag('constructor')
5972da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                and not ('accessControls' in jsdoc.suppressions)):
5982da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              self._HandleError(errors.INVALID_INHERIT_DOC_PRIVATE,
5992da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                  '%s should not inherit from a private member.' % identifier,
6002da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                  jsdoc.GetFlag('inheritDoc').flag_token)
6012da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            if (not jsdoc.HasFlag('private') and
6022da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                not ('underscore' in jsdoc.suppressions) and not
6032da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                ((jsdoc.HasFlag('inheritDoc') or jsdoc.HasFlag('override')) and
6042da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                 ('accessControls' in jsdoc.suppressions))):
6052da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              self._HandleError(errors.MISSING_PRIVATE,
6062da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                  'Member "%s" must have @private JsDoc.' %
6072da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                  identifier, token)
6082da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            if jsdoc.HasFlag('private') and 'underscore' in jsdoc.suppressions:
6092da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              self._HandleError(errors.UNNECESSARY_SUPPRESS,
6102da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                  '@suppress {underscore} is not necessary with @private',
6112da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                  jsdoc.suppressions['underscore'])
6122da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          elif (jsdoc.HasFlag('private') and
6132da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                not self.InExplicitlyTypedLanguage()):
6142da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            # It is convention to hide public fields in some ECMA
6152da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            # implementations from documentation using the @private tag.
6162da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            self._HandleError(errors.EXTRA_PRIVATE,
6172da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                'Member "%s" must not have @private JsDoc' %
6182da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                identifier, token)
6192da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
6202da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          # These flags are only legal on localizable message definitions;
6212da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          # such variables always begin with the prefix MSG_.
6222da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          for f in ('desc', 'hidden', 'meaning'):
6232da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            if (jsdoc.HasFlag(f)
6242da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              and not identifier.startswith('MSG_')
6252da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              and identifier.find('.MSG_') == -1):
6262da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              self._HandleError(errors.INVALID_USE_OF_DESC_TAG,
6272da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                  'Member "%s" should not have @%s JsDoc' % (identifier, f),
6282da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                  token)
6292da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
6302da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      # Check for illegaly assigning live objects as prototype property values.
6312da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      index = identifier.find('.prototype.')
6322da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      # Ignore anything with additional .s after the prototype.
6332da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      if index != -1 and identifier.find('.', index + 11) == -1:
6342da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        equal_operator = tokenutil.SearchExcept(token, Type.NON_CODE_TYPES)
6352da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        next_code = tokenutil.SearchExcept(equal_operator, Type.NON_CODE_TYPES)
6362da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        if next_code and (
6372da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            next_code.type in (Type.START_BRACKET, Type.START_BLOCK) or
6382da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            next_code.IsOperator('new')):
6392da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          self._HandleError(errors.ILLEGAL_PROTOTYPE_MEMBER_VALUE,
6402da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              'Member %s cannot have a non-primitive value' % identifier,
6412da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              token)
6422da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
6432da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    elif type == Type.END_PARAMETERS:
6442da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      # Find extra space at the end of parameter lists.  We check the token
6452da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      # prior to the current one when it is a closing paren.
6462da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      if (token.previous and token.previous.type == Type.PARAMETERS
6472da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          and self.ENDS_WITH_SPACE.search(token.previous.string)):
6482da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        self._HandleError(errors.EXTRA_SPACE, 'Extra space before ")"',
6492da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            token.previous)
6502da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
6512da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      jsdoc = state.GetDocComment()
6522da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      if state.GetFunction().is_interface:
6532da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        if token.previous and token.previous.type == Type.PARAMETERS:
6542da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          self._HandleError(errors.INTERFACE_CONSTRUCTOR_CANNOT_HAVE_PARAMS,
6552da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              'Interface constructor cannot have parameters',
6562da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              token.previous)
6572da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      elif (state.InTopLevel() and jsdoc and not jsdoc.HasFlag('see')
6582da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          and not jsdoc.InheritsDocumentation()
6592da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          and not state.InObjectLiteralDescendant() and not
6602da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          jsdoc.IsInvalidated()):
6612da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        distance, edit = jsdoc.CompareParameters(state.GetParams())
6622da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        if distance:
6632da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          params_iter = iter(state.GetParams())
6642da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          docs_iter = iter(jsdoc.ordered_params)
6652da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
6662da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          for op in edit:
6672da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            if op == 'I':
6682da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              # Insertion.
6692da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              # Parsing doc comments is the same for all languages
6702da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              # but some languages care about parameters that don't have
6712da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              # doc comments and some languages don't care.
6722da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              # Languages that don't allow variables to by typed such as
6732da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              # JavaScript care but languages such as ActionScript or Java
6742da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              # that allow variables to be typed don't care.
6752da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              if not self._limited_doc_checks:
6762da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                self.HandleMissingParameterDoc(token, params_iter.next())
6772da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
6782da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            elif op == 'D':
6792da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              # Deletion
6802da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              self._HandleError(errors.EXTRA_PARAMETER_DOCUMENTATION,
6812da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                  'Found docs for non-existing parameter: "%s"' %
6822da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                  docs_iter.next(), token)
6832da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            elif op == 'S':
6842da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              # Substitution
6852da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              if not self._limited_doc_checks:
6862da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                self._HandleError(errors.WRONG_PARAMETER_DOCUMENTATION,
6872da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                    'Parameter mismatch: got "%s", expected "%s"' %
6882da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                    (params_iter.next(), docs_iter.next()), token)
6892da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
6902da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            else:
6912da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              # Equality - just advance the iterators
6922da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              params_iter.next()
6932da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              docs_iter.next()
6942da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
6952da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    elif type == Type.STRING_TEXT:
6962da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      # If this is the first token after the start of the string, but it's at
6972da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      # the end of a line, we know we have a multi-line string.
6982da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      if token.previous.type in (Type.SINGLE_QUOTE_STRING_START,
6992da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          Type.DOUBLE_QUOTE_STRING_START) and last_in_line:
7002da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        self._HandleError(errors.MULTI_LINE_STRING,
7012da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            'Multi-line strings are not allowed', token)
7022da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
7032da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
7042da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    # This check is orthogonal to the ones above, and repeats some types, so
7052da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    # it is a plain if and not an elif.
7062da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    if token.type in Type.COMMENT_TYPES:
7072da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      if self.ILLEGAL_TAB.search(token.string):
7082da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        self._HandleError(errors.ILLEGAL_TAB,
7092da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            'Illegal tab in comment "%s"' % token.string, token)
7102da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
7112da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      trimmed = token.string.rstrip()
7122da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      if last_in_line and token.string != trimmed:
7132da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        # Check for extra whitespace at the end of a line.
7142da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        self._HandleError(errors.EXTRA_SPACE, 'Extra space at end of line',
7152da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            token, Position(len(trimmed), len(token.string) - len(trimmed)))
7162da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
7172da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    # This check is also orthogonal since it is based on metadata.
7182da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    if token.metadata.is_implied_semicolon:
7192da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      self._HandleError(errors.MISSING_SEMICOLON,
7202da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          'Missing semicolon at end of line', token)
7212da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
7222da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  def _HandleStartBracket(self, token, last_non_space_token):
7232da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """Handles a token that is an open bracket.
7242da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
7252da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    Args:
7262da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      token: The token to handle.
7272da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      last_non_space_token: The last token that was not a space.
7282da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """
7292da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    if (not token.IsFirstInLine() and token.previous.type == Type.WHITESPACE and
7302da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        last_non_space_token and
7312da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        last_non_space_token.type in Type.EXPRESSION_ENDER_TYPES):
7322da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      self._HandleError(errors.EXTRA_SPACE, 'Extra space before "["',
7332da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                        token.previous, Position.All(token.previous.string))
7342da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    # If the [ token is the first token in a line we shouldn't complain
7352da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    # about a missing space before [.  This is because some Ecma script
7362da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    # languages allow syntax like:
7372da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    # [Annotation]
7382da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    # class MyClass {...}
7392da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    # So we don't want to blindly warn about missing spaces before [.
7402da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    # In the the future, when rules for computing exactly how many spaces
7412da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    # lines should be indented are added, then we can return errors for
7422da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    # [ tokens that are improperly indented.
7432da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    # For example:
7442da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    # var someVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongVariableName =
7452da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    # [a,b,c];
7462da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    # should trigger a proper indentation warning message as [ is not indented
7472da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    # by four spaces.
7482da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    elif (not token.IsFirstInLine() and token.previous and
7492da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          not token.previous.type in (
7502da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              [Type.WHITESPACE, Type.START_PAREN, Type.START_BRACKET] +
7512da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              Type.EXPRESSION_ENDER_TYPES)):
7522da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      self._HandleError(errors.MISSING_SPACE, 'Missing space before "["',
7532da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                        token, Position.AtBeginning())
7542da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
7552da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  def Finalize(self, state, tokenizer_mode):
7562da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    last_non_space_token = state.GetLastNonSpaceToken()
7572da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    # Check last line for ending with newline.
7582da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    if state.GetLastLine() and not (state.GetLastLine().isspace() or
7592da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        state.GetLastLine().rstrip('\n\r\f') != state.GetLastLine()):
7602da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      self._HandleError(
7612da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          errors.FILE_MISSING_NEWLINE,
7622da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          'File does not end with new line.  (%s)' % state.GetLastLine(),
7632da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          last_non_space_token)
7642da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
7652da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    # Check that the mode is not mid comment, argument list, etc.
7662da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    if not tokenizer_mode == Modes.TEXT_MODE:
7672da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      self._HandleError(
7682da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          errors.FILE_IN_BLOCK,
7692da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          'File ended in mode "%s".' % tokenizer_mode,
7702da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          last_non_space_token)
7712da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
7722da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    try:
7732da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      self._indentation.Finalize()
7742da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    except Exception, e:
7752da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      self._HandleError(
7762da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          errors.FILE_DOES_NOT_PARSE,
7772da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          str(e),
7782da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          last_non_space_token)
7792da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
7802da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  def GetLongLineExceptions(self):
7812da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """Gets a list of regexps for lines which can be longer than the limit."""
7822da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    return []
7832da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
7842da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  def InExplicitlyTypedLanguage(self):
7852da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """Returns whether this ecma implementation is explicitly typed."""
7862da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    return False
787