12da489cd246702bee5938545b18a6f710ed214bcJamie Gennis#!/usr/bin/env python
22da489cd246702bee5938545b18a6f710ed214bcJamie Gennis#
32da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# Copyright 2007 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 JS files for common style guide violations."""
182da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
192da489cd246702bee5938545b18a6f710ed214bcJamie Gennis__author__ = ('robbyw@google.com (Robert Walker)',
202da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              'ajp@google.com (Andy Perelson)')
212da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
222da489cd246702bee5938545b18a6f710ed214bcJamie Gennisimport gflags as flags
232da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
242da489cd246702bee5938545b18a6f710ed214bcJamie Gennisfrom closure_linter import checkerbase
252da489cd246702bee5938545b18a6f710ed214bcJamie Gennisfrom closure_linter import closurizednamespacesinfo
262da489cd246702bee5938545b18a6f710ed214bcJamie Gennisfrom closure_linter import ecmametadatapass
272da489cd246702bee5938545b18a6f710ed214bcJamie Gennisfrom closure_linter import javascriptlintrules
282da489cd246702bee5938545b18a6f710ed214bcJamie Gennisfrom closure_linter import javascriptstatetracker
292da489cd246702bee5938545b18a6f710ed214bcJamie Gennisfrom closure_linter.common import lintrunner
302da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
312da489cd246702bee5938545b18a6f710ed214bcJamie Gennisflags.DEFINE_list('limited_doc_files', ['dummy.js', 'externs.js'],
322da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                  'List of files with relaxed documentation checks. Will not '
332da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                  'report errors for missing documentation, some missing '
342da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                  'descriptions, or methods whose @return tags don\'t have a '
352da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                  'matching return statement.')
362da489cd246702bee5938545b18a6f710ed214bcJamie Gennisflags.DEFINE_list('closurized_namespaces', '',
372da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                  'Namespace prefixes, used for testing of'
382da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                  'goog.provide/require')
392da489cd246702bee5938545b18a6f710ed214bcJamie Gennisflags.DEFINE_list('ignored_extra_namespaces', '',
402da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                  'Fully qualified namespaces that should be not be reported '
412da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                  'as extra by the linter.')
422da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
432da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
442da489cd246702bee5938545b18a6f710ed214bcJamie Gennisclass JavaScriptStyleChecker(checkerbase.CheckerBase):
452da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  """Checker that applies JavaScriptLintRules."""
462da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
472da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  def __init__(self, error_handler):
482da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """Initialize an JavaScriptStyleChecker object.
492da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
502da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    Args:
512da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      error_handler: Error handler to pass all errors to.
522da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """
532da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    self._namespaces_info = None
542da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    if flags.FLAGS.closurized_namespaces:
552da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      self._namespaces_info = (
562da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          closurizednamespacesinfo.ClosurizedNamespacesInfo(
572da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              flags.FLAGS.closurized_namespaces,
582da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              flags.FLAGS.ignored_extra_namespaces))
592da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
602da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    checkerbase.CheckerBase.__init__(
612da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        self,
622da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        error_handler=error_handler,
632da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        lint_rules=javascriptlintrules.JavaScriptLintRules(
642da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            self._namespaces_info),
652da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        state_tracker=javascriptstatetracker.JavaScriptStateTracker(),
662da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        metadata_pass=ecmametadatapass.EcmaMetaDataPass(),
672da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        limited_doc_files=flags.FLAGS.limited_doc_files)
682da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
692da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  def _CheckTokens(self, token, parse_error, debug_tokens):
702da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """Checks a token stream for lint warnings/errors.
712da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
722da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    Adds a separate pass for computing dependency information based on
732da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    goog.require and goog.provide statements prior to the main linting pass.
742da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
752da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    Args:
762da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      token: The first token in the token stream.
772da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      parse_error: A ParseError if any errors occurred.
782da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      debug_tokens: Whether every token should be printed as it is encountered
792da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          during the pass.
802da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
812da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    Returns:
822da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      A boolean indicating whether the full token stream could be checked or if
832da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      checking failed prematurely.
842da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """
852da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    # To maximize the amount of errors that get reported before a parse error
862da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    # is displayed, don't run the dependency pass if a parse error exists.
872da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    if self._namespaces_info and not parse_error:
882da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      self._namespaces_info.Reset()
892da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      result = (self._ExecutePass(token, self._DependencyPass) and
902da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                self._ExecutePass(token, self._LintPass,
912da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                                  debug_tokens=debug_tokens))
922da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    else:
932da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      result = self._ExecutePass(token, self._LintPass, parse_error,
942da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                                 debug_tokens)
952da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
962da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    if not result:
972da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      return False
982da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
992da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    self._lint_rules.Finalize(self._state_tracker, self._tokenizer.mode)
1002da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1012da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    self._error_handler.FinishFile()
1022da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    return True
1032da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1042da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  def _DependencyPass(self, token):
1052da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """Processes an invidual token for dependency information.
1062da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1072da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    Used to encapsulate the logic needed to process an individual token so that
1082da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    it can be passed to _ExecutePass.
1092da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1102da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    Args:
1112da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      token: The token to process.
1122da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """
1132da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    self._namespaces_info.ProcessToken(token, self._state_tracker)
1142da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1152da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1162da489cd246702bee5938545b18a6f710ed214bcJamie Gennisclass GJsLintRunner(lintrunner.LintRunner):
1172da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  """Wrapper class to run GJsLint."""
1182da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1192da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  def Run(self, filenames, error_handler):
1202da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """Run GJsLint on the given filenames.
1212da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1222da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    Args:
1232da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      filenames: The filenames to check
1242da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      error_handler: An ErrorHandler object.
1252da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """
1262da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    checker = JavaScriptStyleChecker(error_handler)
1272da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1282da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    # Check the list of files.
1292da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    for filename in filenames:
1302da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      checker.Check(filename)
131