12da489cd246702bee5938545b18a6f710ed214bcJamie Gennis#!/usr/bin/env python
22da489cd246702bee5938545b18a6f710ed214bcJamie Gennis#
32da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# Copyright 2011 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"""Methods for checking JS files for common style guide violations.
182da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
192da489cd246702bee5938545b18a6f710ed214bcJamie GennisThese style guide violations should only apply to JavaScript and not an Ecma
202da489cd246702bee5938545b18a6f710ed214bcJamie Gennisscripting languages.
212da489cd246702bee5938545b18a6f710ed214bcJamie Gennis"""
222da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
232da489cd246702bee5938545b18a6f710ed214bcJamie Gennis__author__ = ('robbyw@google.com (Robert Walker)',
242da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              'ajp@google.com (Andy Perelson)',
252da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              'jacobr@google.com (Jacob Richman)')
262da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
272da489cd246702bee5938545b18a6f710ed214bcJamie Gennisimport re
282da489cd246702bee5938545b18a6f710ed214bcJamie Gennisfrom sets import Set
292da489cd246702bee5938545b18a6f710ed214bcJamie Gennisfrom closure_linter import ecmalintrules
302da489cd246702bee5938545b18a6f710ed214bcJamie Gennisfrom closure_linter import error_check
312da489cd246702bee5938545b18a6f710ed214bcJamie Gennisfrom closure_linter import errors
322da489cd246702bee5938545b18a6f710ed214bcJamie Gennisfrom closure_linter import javascripttokenizer
332da489cd246702bee5938545b18a6f710ed214bcJamie Gennisfrom closure_linter import javascripttokens
342da489cd246702bee5938545b18a6f710ed214bcJamie Gennisfrom closure_linter import requireprovidesorter
352da489cd246702bee5938545b18a6f710ed214bcJamie Gennisfrom closure_linter import tokenutil
362da489cd246702bee5938545b18a6f710ed214bcJamie Gennisfrom closure_linter.common import error
372da489cd246702bee5938545b18a6f710ed214bcJamie Gennisfrom closure_linter.common import position
382da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
392da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# Shorthand
402da489cd246702bee5938545b18a6f710ed214bcJamie GennisError = error.Error
412da489cd246702bee5938545b18a6f710ed214bcJamie GennisPosition = position.Position
422da489cd246702bee5938545b18a6f710ed214bcJamie GennisRule = error_check.Rule
432da489cd246702bee5938545b18a6f710ed214bcJamie GennisType = javascripttokens.JavaScriptTokenType
442da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
452da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
462da489cd246702bee5938545b18a6f710ed214bcJamie Gennisclass JavaScriptLintRules(ecmalintrules.EcmaScriptLintRules):
472da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  """JavaScript lint rules that catch JavaScript specific style errors."""
482da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
492da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  def __init__(self, namespaces_info):
502da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """Initializes a JavaScriptLintRules instance."""
512da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    ecmalintrules.EcmaScriptLintRules.__init__(self)
522da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    self._namespaces_info = namespaces_info
532da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    self._declared_private_member_tokens = {}
542da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    self._declared_private_members = Set()
552da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    self._used_private_members = Set()
562da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
572da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  def HandleMissingParameterDoc(self, token, param_name):
582da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """Handle errors associated with a parameter missing a param tag."""
592da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    self._HandleError(errors.MISSING_PARAMETER_DOCUMENTATION,
602da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                      'Missing docs for parameter: "%s"' % param_name, token)
612da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
622da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  def __ContainsRecordType(self, token):
632da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """Check whether the given token contains a record type.
642da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
652da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    Args:
662da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      token: The token being checked
672da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
682da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    Returns:
692da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      True if the token contains a record type, False otherwise.
702da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """
712da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    # If we see more than one left-brace in the string of an annotation token,
722da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    # then there's a record type in there.
732da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    return (
742da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        token and token.type == Type.DOC_FLAG and
752da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        token.attached_object.type is not None and
762da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        token.attached_object.type.find('{') != token.string.rfind('{'))
772da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
782da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  def CheckToken(self, token, state):
792da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """Checks a token, given the current parser_state, for warnings and errors.
802da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
812da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    Args:
822da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      token: The current token under consideration
832da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      state: parser_state object that indicates the current state in the page
842da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """
852da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    if self.__ContainsRecordType(token):
862da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      # We should bail out and not emit any warnings for this annotation.
872da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      # TODO(nicksantos): Support record types for real.
882da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      state.GetDocComment().Invalidate()
892da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      return
902da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
912da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    # Call the base class's CheckToken function.
922da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    super(JavaScriptLintRules, self).CheckToken(token, state)
932da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
942da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    # Store some convenience variables
952da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    namespaces_info = self._namespaces_info
962da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
972da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    if error_check.ShouldCheck(Rule.UNUSED_PRIVATE_MEMBERS):
982da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      # Find all assignments to private members.
992da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      if token.type == Type.SIMPLE_LVALUE:
1002da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        identifier = token.string
1012da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        if identifier.endswith('_') and not identifier.endswith('__'):
1022da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          doc_comment = state.GetDocComment()
1032da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          suppressed = (doc_comment and doc_comment.HasFlag('suppress') and
1042da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                        doc_comment.GetFlag('suppress').type == 'underscore')
1052da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          if not suppressed:
1062da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            # Look for static members defined on a provided namespace.
1072da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            namespace = namespaces_info.GetClosurizedNamespace(identifier)
1082da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            provided_namespaces = namespaces_info.GetProvidedNamespaces()
1092da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1102da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            # Skip cases of this.something_.somethingElse_.
1112da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            regex = re.compile('^this\.[a-zA-Z_]+$')
1122da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            if namespace in provided_namespaces or regex.match(identifier):
1132da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              variable = identifier.split('.')[-1]
1142da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              self._declared_private_member_tokens[variable] = token
1152da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              self._declared_private_members.add(variable)
1162da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        elif not identifier.endswith('__'):
1172da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          # Consider setting public members of private members to be a usage.
1182da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          for piece in identifier.split('.'):
1192da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            if piece.endswith('_'):
1202da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              self._used_private_members.add(piece)
1212da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1222da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      # Find all usages of private members.
1232da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      if token.type == Type.IDENTIFIER:
1242da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        for piece in token.string.split('.'):
1252da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          if piece.endswith('_'):
1262da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            self._used_private_members.add(piece)
1272da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1282da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    if token.type == Type.DOC_FLAG:
1292da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      flag = token.attached_object
1302da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1312da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      if flag.flag_type == 'param' and flag.name_token is not None:
1322da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        self._CheckForMissingSpaceBeforeToken(
1332da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            token.attached_object.name_token)
1342da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1352da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        if (error_check.ShouldCheck(Rule.OPTIONAL_TYPE_MARKER) and
1362da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            flag.type is not None and flag.name is not None):
1372da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          # Check for optional marker in type.
1382da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          if (flag.type.endswith('=') and
1392da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              not flag.name.startswith('opt_')):
1402da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            self._HandleError(errors.JSDOC_MISSING_OPTIONAL_PREFIX,
1412da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                              'Optional parameter name %s must be prefixed '
1422da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                              'with opt_.' % flag.name,
1432da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                              token)
1442da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          elif (not flag.type.endswith('=') and
1452da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                flag.name.startswith('opt_')):
1462da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            self._HandleError(errors.JSDOC_MISSING_OPTIONAL_TYPE,
1472da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                              'Optional parameter %s type must end with =.' %
1482da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                              flag.name,
1492da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                              token)
1502da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1512da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      if flag.flag_type in state.GetDocFlag().HAS_TYPE:
1522da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        # Check for both missing type token and empty type braces '{}'
1532da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        # Missing suppress types are reported separately and we allow enums
1542da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        # without types.
1552da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        if (flag.flag_type not in ('suppress', 'enum') and
1562da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            (not flag.type or flag.type.isspace())):
1572da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          self._HandleError(errors.MISSING_JSDOC_TAG_TYPE,
1582da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                            'Missing type in %s tag' % token.string, token)
1592da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1602da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        elif flag.name_token and flag.type_end_token and tokenutil.Compare(
1612da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            flag.type_end_token, flag.name_token) > 0:
1622da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          self._HandleError(
1632da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              errors.OUT_OF_ORDER_JSDOC_TAG_TYPE,
1642da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              'Type should be immediately after %s tag' % token.string,
1652da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              token)
1662da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1672da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    elif token.type == Type.DOUBLE_QUOTE_STRING_START:
1682da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      next_token = token.next
1692da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      while next_token.type == Type.STRING_TEXT:
1702da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        if javascripttokenizer.JavaScriptTokenizer.SINGLE_QUOTE.search(
1712da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            next_token.string):
1722da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          break
1732da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        next_token = next_token.next
1742da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      else:
1752da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        self._HandleError(
1762da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            errors.UNNECESSARY_DOUBLE_QUOTED_STRING,
1772da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            'Single-quoted string preferred over double-quoted string.',
1782da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            token,
1792da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            Position.All(token.string))
1802da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1812da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    elif token.type == Type.END_DOC_COMMENT:
1822da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      doc_comment = state.GetDocComment()
1832da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1842da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      # When @externs appears in a @fileoverview comment, it should trigger
1852da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      # the same limited doc checks as a special filename like externs.js.
1862da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      if doc_comment.HasFlag('fileoverview') and doc_comment.HasFlag('externs'):
1872da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        self._SetLimitedDocChecks(True)
1882da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1892da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      if (error_check.ShouldCheck(Rule.BLANK_LINES_AT_TOP_LEVEL) and
1902da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          not self._is_html and state.InTopLevel() and not state.InBlock()):
1912da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1922da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        # Check if we're in a fileoverview or constructor JsDoc.
1932da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        is_constructor = (
1942da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            doc_comment.HasFlag('constructor') or
1952da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            doc_comment.HasFlag('interface'))
1962da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        is_file_overview = doc_comment.HasFlag('fileoverview')
1972da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1982da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        # If the comment is not a file overview, and it does not immediately
1992da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        # precede some code, skip it.
2002da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        # NOTE: The tokenutil methods are not used here because of their
2012da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        # behavior at the top of a file.
2022da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        next_token = token.next
2032da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        if (not next_token or
2042da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            (not is_file_overview and next_token.type in Type.NON_CODE_TYPES)):
2052da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          return
2062da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
2072da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        # Don't require extra blank lines around suppression of extra
2082da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        # goog.require errors.
2092da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        if (doc_comment.SuppressionOnly() and
2102da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            next_token.type == Type.IDENTIFIER and
2112da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            next_token.string in ['goog.provide', 'goog.require']):
2122da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          return
2132da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
2142da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        # Find the start of this block (include comments above the block, unless
2152da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        # this is a file overview).
2162da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        block_start = doc_comment.start_token
2172da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        if not is_file_overview:
2182da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          token = block_start.previous
2192da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          while token and token.type in Type.COMMENT_TYPES:
2202da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            block_start = token
2212da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            token = token.previous
2222da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
2232da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        # Count the number of blank lines before this block.
2242da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        blank_lines = 0
2252da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        token = block_start.previous
2262da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        while token and token.type in [Type.WHITESPACE, Type.BLANK_LINE]:
2272da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          if token.type == Type.BLANK_LINE:
2282da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            # A blank line.
2292da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            blank_lines += 1
2302da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          elif token.type == Type.WHITESPACE and not token.line.strip():
2312da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            # A line with only whitespace on it.
2322da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            blank_lines += 1
2332da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          token = token.previous
2342da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
2352da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        # Log errors.
2362da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        error_message = False
2372da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        expected_blank_lines = 0
2382da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
2392da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        if is_file_overview and blank_lines == 0:
2402da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          error_message = 'Should have a blank line before a file overview.'
2412da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          expected_blank_lines = 1
2422da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        elif is_constructor and blank_lines != 3:
2432da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          error_message = (
2442da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              'Should have 3 blank lines before a constructor/interface.')
2452da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          expected_blank_lines = 3
2462da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        elif not is_file_overview and not is_constructor and blank_lines != 2:
2472da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          error_message = 'Should have 2 blank lines between top-level blocks.'
2482da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          expected_blank_lines = 2
2492da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
2502da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        if error_message:
2512da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          self._HandleError(
2522da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              errors.WRONG_BLANK_LINE_COUNT, error_message,
2532da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              block_start, Position.AtBeginning(),
2542da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              expected_blank_lines - blank_lines)
2552da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
2562da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    elif token.type == Type.END_BLOCK:
2572da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      if state.InFunction() and state.IsFunctionClose():
2582da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        is_immediately_called = (token.next and
2592da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                                 token.next.type == Type.START_PAREN)
2602da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
2612da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        function = state.GetFunction()
2622da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        if not self._limited_doc_checks:
2632da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          if (function.has_return and function.doc and
2642da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              not is_immediately_called and
2652da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              not function.doc.HasFlag('return') and
2662da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              not function.doc.InheritsDocumentation() and
2672da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              not function.doc.HasFlag('constructor')):
2682da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            # Check for proper documentation of return value.
2692da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            self._HandleError(
2702da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                errors.MISSING_RETURN_DOCUMENTATION,
2712da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                'Missing @return JsDoc in function with non-trivial return',
2722da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                function.doc.end_token, Position.AtBeginning())
2732da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          elif (not function.has_return and
2742da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                not function.has_throw and
2752da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                function.doc and
2762da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                function.doc.HasFlag('return') and
2772da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                not state.InInterfaceMethod()):
2782da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            return_flag = function.doc.GetFlag('return')
2792da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            if (return_flag.type is None or (
2802da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                'undefined' not in return_flag.type and
2812da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                'void' not in return_flag.type and
2822da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                '*' not in return_flag.type)):
2832da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              self._HandleError(
2842da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                  errors.UNNECESSARY_RETURN_DOCUMENTATION,
2852da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                  'Found @return JsDoc on function that returns nothing',
2862da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                  return_flag.flag_token, Position.AtBeginning())
2872da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
2882da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      if state.InFunction() and state.IsFunctionClose():
2892da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        is_immediately_called = (token.next and
2902da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                                 token.next.type == Type.START_PAREN)
2912da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        if (function.has_this and function.doc and
2922da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            not function.doc.HasFlag('this') and
2932da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            not function.is_constructor and
2942da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            not function.is_interface and
2952da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            '.prototype.' not in function.name):
2962da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          self._HandleError(
2972da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              errors.MISSING_JSDOC_TAG_THIS,
2982da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              'Missing @this JsDoc in function referencing "this". ('
2992da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              'this usually means you are trying to reference "this" in '
3002da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              'a static function, or you have forgotten to mark a '
3012da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              'constructor with @constructor)',
3022da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              function.doc.end_token, Position.AtBeginning())
3032da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
3042da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    elif token.type == Type.IDENTIFIER:
3052da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      if token.string == 'goog.inherits' and not state.InFunction():
3062da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        if state.GetLastNonSpaceToken().line_number == token.line_number:
3072da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          self._HandleError(
3082da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              errors.MISSING_LINE,
3092da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              'Missing newline between constructor and goog.inherits',
3102da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              token,
3112da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              Position.AtBeginning())
3122da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
3132da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        extra_space = state.GetLastNonSpaceToken().next
3142da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        while extra_space != token:
3152da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          if extra_space.type == Type.BLANK_LINE:
3162da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            self._HandleError(
3172da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                errors.EXTRA_LINE,
3182da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                'Extra line between constructor and goog.inherits',
3192da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                extra_space)
3202da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          extra_space = extra_space.next
3212da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
3222da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        # TODO(robbyw): Test the last function was a constructor.
3232da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        # TODO(robbyw): Test correct @extends and @implements documentation.
3242da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
3252da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      elif (token.string == 'goog.provide' and
3262da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            not state.InFunction() and
3272da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            namespaces_info is not None):
3282da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        namespace = tokenutil.Search(token, Type.STRING_TEXT).string
3292da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
3302da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        # Report extra goog.provide statement.
3312da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        if namespaces_info.IsExtraProvide(token):
3322da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          self._HandleError(
3332da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              errors.EXTRA_GOOG_PROVIDE,
3342da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              'Unnecessary goog.provide: ' + namespace,
3352da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              token, position=Position.AtBeginning())
3362da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
3372da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        if namespaces_info.IsLastProvide(token):
3382da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          # Report missing provide statements after the last existing provide.
3392da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          missing_provides = namespaces_info.GetMissingProvides()
3402da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          if missing_provides:
3412da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            self._ReportMissingProvides(
3422da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                missing_provides,
3432da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                tokenutil.GetLastTokenInSameLine(token).next,
3442da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                False)
3452da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
3462da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          # If there are no require statements, missing requires should be
3472da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          # reported after the last provide.
3482da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          if not namespaces_info.GetRequiredNamespaces():
3492da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            missing_requires = namespaces_info.GetMissingRequires()
3502da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            if missing_requires:
3512da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              self._ReportMissingRequires(
3522da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                  missing_requires,
3532da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                  tokenutil.GetLastTokenInSameLine(token).next,
3542da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                  True)
3552da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
3562da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      elif (token.string == 'goog.require' and
3572da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            not state.InFunction() and
3582da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            namespaces_info is not None):
3592da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        namespace = tokenutil.Search(token, Type.STRING_TEXT).string
3602da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
3612da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        # If there are no provide statements, missing provides should be
3622da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        # reported before the first require.
3632da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        if (namespaces_info.IsFirstRequire(token) and
3642da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            not namespaces_info.GetProvidedNamespaces()):
3652da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          missing_provides = namespaces_info.GetMissingProvides()
3662da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          if missing_provides:
3672da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            self._ReportMissingProvides(
3682da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                missing_provides,
3692da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                tokenutil.GetFirstTokenInSameLine(token),
3702da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                True)
3712da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
3722da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        # Report extra goog.require statement.
3732da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        if namespaces_info.IsExtraRequire(token):
3742da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          self._HandleError(
3752da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              errors.EXTRA_GOOG_REQUIRE,
3762da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              'Unnecessary goog.require: ' + namespace,
3772da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              token, position=Position.AtBeginning())
3782da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
3792da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        # Report missing goog.require statements.
3802da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        if namespaces_info.IsLastRequire(token):
3812da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          missing_requires = namespaces_info.GetMissingRequires()
3822da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          if missing_requires:
3832da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            self._ReportMissingRequires(
3842da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                missing_requires,
3852da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                tokenutil.GetLastTokenInSameLine(token).next,
3862da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                False)
3872da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
3882da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    elif token.type == Type.OPERATOR:
3892da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      last_in_line = token.IsLastInLine()
3902da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      # If the token is unary and appears to be used in a unary context
3912da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      # it's ok.  Otherwise, if it's at the end of the line or immediately
3922da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      # before a comment, it's ok.
3932da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      # Don't report an error before a start bracket - it will be reported
3942da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      # by that token's space checks.
3952da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      if (not token.metadata.IsUnaryOperator() and not last_in_line
3962da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          and not token.next.IsComment()
3972da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          and not token.next.IsOperator(',')
3982da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          and not token.next.type in (Type.WHITESPACE, Type.END_PAREN,
3992da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                                      Type.END_BRACKET, Type.SEMICOLON,
4002da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                                      Type.START_BRACKET)):
4012da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        self._HandleError(
4022da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            errors.MISSING_SPACE,
4032da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            'Missing space after "%s"' % token.string,
4042da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            token,
4052da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            Position.AtEnd(token.string))
4062da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    elif token.type == Type.WHITESPACE:
4072da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      first_in_line = token.IsFirstInLine()
4082da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      last_in_line = token.IsLastInLine()
4092da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      # Check whitespace length if it's not the first token of the line and
4102da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      # if it's not immediately before a comment.
4112da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      if not last_in_line and not first_in_line and not token.next.IsComment():
4122da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        # Ensure there is no space after opening parentheses.
4132da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        if (token.previous.type in (Type.START_PAREN, Type.START_BRACKET,
4142da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                                    Type.FUNCTION_NAME)
4152da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            or token.next.type == Type.START_PARAMETERS):
4162da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          self._HandleError(
4172da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              errors.EXTRA_SPACE,
4182da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              'Extra space after "%s"' % token.previous.string,
4192da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              token,
4202da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              Position.All(token.string))
4212da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
4222da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  def _ReportMissingProvides(self, missing_provides, token, need_blank_line):
4232da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """Reports missing provide statements to the error handler.
4242da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
4252da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    Args:
4262da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      missing_provides: A list of strings where each string is a namespace that
4272da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          should be provided, but is not.
4282da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      token: The token where the error was detected (also where the new provides
4292da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          will be inserted.
4302da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      need_blank_line: Whether a blank line needs to be inserted after the new
4312da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          provides are inserted. May be True, False, or None, where None
4322da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          indicates that the insert location is unknown.
4332da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """
4342da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    self._HandleError(
4352da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        errors.MISSING_GOOG_PROVIDE,
4362da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        'Missing the following goog.provide statements:\n' +
4372da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        '\n'.join(map(lambda x: 'goog.provide(\'%s\');' % x,
4382da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                      sorted(missing_provides))),
4392da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        token, position=Position.AtBeginning(),
4402da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        fix_data=(missing_provides, need_blank_line))
4412da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
4422da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  def _ReportMissingRequires(self, missing_requires, token, need_blank_line):
4432da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """Reports missing require statements to the error handler.
4442da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
4452da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    Args:
4462da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      missing_requires: A list of strings where each string is a namespace that
4472da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          should be required, but is not.
4482da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      token: The token where the error was detected (also where the new requires
4492da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          will be inserted.
4502da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      need_blank_line: Whether a blank line needs to be inserted before the new
4512da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          requires are inserted. May be True, False, or None, where None
4522da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          indicates that the insert location is unknown.
4532da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """
4542da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    self._HandleError(
4552da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        errors.MISSING_GOOG_REQUIRE,
4562da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        'Missing the following goog.require statements:\n' +
4572da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        '\n'.join(map(lambda x: 'goog.require(\'%s\');' % x,
4582da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                      sorted(missing_requires))),
4592da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        token, position=Position.AtBeginning(),
4602da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        fix_data=(missing_requires, need_blank_line))
4612da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
4622da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  def Finalize(self, state, tokenizer_mode):
4632da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """Perform all checks that need to occur after all lines are processed."""
4642da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    # Call the base class's Finalize function.
4652da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    super(JavaScriptLintRules, self).Finalize(state, tokenizer_mode)
4662da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
4672da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    if error_check.ShouldCheck(Rule.UNUSED_PRIVATE_MEMBERS):
4682da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      # Report an error for any declared private member that was never used.
4692da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      unused_private_members = (self._declared_private_members -
4702da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                                self._used_private_members)
4712da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
4722da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      for variable in unused_private_members:
4732da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        token = self._declared_private_member_tokens[variable]
4742da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        self._HandleError(errors.UNUSED_PRIVATE_MEMBER,
4752da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                          'Unused private member: %s.' % token.string,
4762da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                          token)
4772da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
4782da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      # Clear state to prepare for the next file.
4792da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      self._declared_private_member_tokens = {}
4802da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      self._declared_private_members = Set()
4812da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      self._used_private_members = Set()
4822da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
4832da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    namespaces_info = self._namespaces_info
4842da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    if namespaces_info is not None:
4852da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      # If there are no provide or require statements, missing provides and
4862da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      # requires should be reported on line 1.
4872da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      if (not namespaces_info.GetProvidedNamespaces() and
4882da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          not namespaces_info.GetRequiredNamespaces()):
4892da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        missing_provides = namespaces_info.GetMissingProvides()
4902da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        if missing_provides:
4912da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          self._ReportMissingProvides(
4922da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              missing_provides, state.GetFirstToken(), None)
4932da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
4942da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        missing_requires = namespaces_info.GetMissingRequires()
4952da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        if missing_requires:
4962da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          self._ReportMissingRequires(
4972da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              missing_requires, state.GetFirstToken(), None)
4982da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
4992da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    self._CheckSortedRequiresProvides(state.GetFirstToken())
5002da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
5012da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  def _CheckSortedRequiresProvides(self, token):
5022da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """Checks that all goog.require and goog.provide statements are sorted.
5032da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
5042da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    Note that this method needs to be run after missing statements are added to
5052da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    preserve alphabetical order.
5062da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
5072da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    Args:
5082da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      token: The first token in the token stream.
5092da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """
5102da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    sorter = requireprovidesorter.RequireProvideSorter()
5112da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    provides_result = sorter.CheckProvides(token)
5122da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    if provides_result:
5132da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      self._HandleError(
5142da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          errors.GOOG_PROVIDES_NOT_ALPHABETIZED,
5152da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          'goog.provide classes must be alphabetized.  The correct code is:\n' +
5162da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          '\n'.join(
5172da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              map(lambda x: 'goog.provide(\'%s\');' % x, provides_result[1])),
5182da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          provides_result[0],
5192da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          position=Position.AtBeginning(),
5202da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          fix_data=provides_result[0])
5212da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
5222da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    requires_result = sorter.CheckRequires(token)
5232da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    if requires_result:
5242da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      self._HandleError(
5252da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          errors.GOOG_REQUIRES_NOT_ALPHABETIZED,
5262da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          'goog.require classes must be alphabetized.  The correct code is:\n' +
5272da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          '\n'.join(
5282da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              map(lambda x: 'goog.require(\'%s\');' % x, requires_result[1])),
5292da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          requires_result[0],
5302da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          position=Position.AtBeginning(),
5312da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          fix_data=requires_result[0])
5322da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
5332da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  def GetLongLineExceptions(self):
5342da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """Gets a list of regexps for lines which can be longer than the limit."""
5352da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    return [
53666a37686207944273ced825e0e8b6b6375f8c3deJamie Gennis        re.compile('.*// @suppress longLineCheck$'),
5372da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        re.compile('goog\.require\(.+\);?\s*$'),
5382da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        re.compile('goog\.provide\(.+\);?\s*$')
5392da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        ]
540