1#!/usr/bin/env python
2#
3# Copyright 2008 The Closure Linter Authors. All Rights Reserved.
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9#      http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS-IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17"""Base classes for writing checkers that operate on tokens."""
18
19# Allow non-Google copyright
20# pylint: disable=g-bad-file-header
21
22__author__ = ('robbyw@google.com (Robert Walker)',
23              'ajp@google.com (Andy Perelson)',
24              'jacobr@google.com (Jacob Richman)')
25
26from closure_linter import errorrules
27from closure_linter.common import error
28
29
30class LintRulesBase(object):
31  """Base class for all classes defining the lint rules for a language."""
32
33  def __init__(self):
34    self.__checker = None
35
36  def Initialize(self, checker, limited_doc_checks, is_html):
37    """Initializes to prepare to check a file.
38
39    Args:
40      checker: Class to report errors to.
41      limited_doc_checks: Whether doc checking is relaxed for this file.
42      is_html: Whether the file is an HTML file with extracted contents.
43    """
44    self.__checker = checker
45    self._limited_doc_checks = limited_doc_checks
46    self._is_html = is_html
47
48  def _HandleError(self, code, message, token, position=None,
49                   fix_data=None):
50    """Call the HandleError function for the checker we are associated with."""
51    if errorrules.ShouldReportError(code):
52      self.__checker.HandleError(code, message, token, position, fix_data)
53
54  def _SetLimitedDocChecks(self, limited_doc_checks):
55    """Sets whether doc checking is relaxed for this file.
56
57    Args:
58      limited_doc_checks: Whether doc checking is relaxed for this file.
59    """
60    self._limited_doc_checks = limited_doc_checks
61
62  def CheckToken(self, token, parser_state):
63    """Checks a token, given the current parser_state, for warnings and errors.
64
65    Args:
66      token: The current token under consideration.
67      parser_state: Object that indicates the parser state in the page.
68
69    Raises:
70      TypeError: If not overridden.
71    """
72    raise TypeError('Abstract method CheckToken not implemented')
73
74  def Finalize(self, parser_state):
75    """Perform all checks that need to occur after all lines are processed.
76
77    Args:
78      parser_state: State of the parser after parsing all tokens
79
80    Raises:
81      TypeError: If not overridden.
82    """
83    raise TypeError('Abstract method Finalize not implemented')
84
85
86class CheckerBase(object):
87  """This class handles checking a LintRules object against a file."""
88
89  def __init__(self, error_handler, lint_rules, state_tracker):
90    """Initialize a checker object.
91
92    Args:
93      error_handler: Object that handles errors.
94      lint_rules: LintRules object defining lint errors given a token
95        and state_tracker object.
96      state_tracker: Object that tracks the current state in the token stream.
97
98    """
99    self._error_handler = error_handler
100    self._lint_rules = lint_rules
101    self._state_tracker = state_tracker
102
103    self._has_errors = False
104
105  def HandleError(self, code, message, token, position=None,
106                  fix_data=None):
107    """Prints out the given error message including a line number.
108
109    Args:
110      code: The error code.
111      message: The error to print.
112      token: The token where the error occurred, or None if it was a file-wide
113          issue.
114      position: The position of the error, defaults to None.
115      fix_data: Metadata used for fixing the error.
116    """
117    self._has_errors = True
118    self._error_handler.HandleError(
119        error.Error(code, message, token, position, fix_data))
120
121  def HasErrors(self):
122    """Returns true if the style checker has found any errors.
123
124    Returns:
125      True if the style checker has found any errors.
126    """
127    return self._has_errors
128
129  def Check(self, start_token, limited_doc_checks=False, is_html=False,
130            stop_token=None):
131    """Checks a token stream, reporting errors to the error reporter.
132
133    Args:
134      start_token: First token in token stream.
135      limited_doc_checks: Whether doc checking is relaxed for this file.
136      is_html: Whether the file being checked is an HTML file with extracted
137          contents.
138      stop_token: If given, check should stop at this token.
139    """
140
141    self._lint_rules.Initialize(self, limited_doc_checks, is_html)
142    self._ExecutePass(start_token, self._LintPass, stop_token=stop_token)
143    self._lint_rules.Finalize(self._state_tracker)
144
145  def _LintPass(self, token):
146    """Checks an individual token for lint warnings/errors.
147
148    Used to encapsulate the logic needed to check an individual token so that it
149    can be passed to _ExecutePass.
150
151    Args:
152      token: The token to check.
153    """
154    self._lint_rules.CheckToken(token, self._state_tracker)
155
156  def _ExecutePass(self, token, pass_function, stop_token=None):
157    """Calls the given function for every token in the given token stream.
158
159    As each token is passed to the given function, state is kept up to date and,
160    depending on the error_trace flag, errors are either caught and reported, or
161    allowed to bubble up so developers can see the full stack trace. If a parse
162    error is specified, the pass will proceed as normal until the token causing
163    the parse error is reached.
164
165    Args:
166      token: The first token in the token stream.
167      pass_function: The function to call for each token in the token stream.
168      stop_token: The last token to check (if given).
169
170    Raises:
171      Exception: If any error occurred while calling the given function.
172    """
173
174    self._state_tracker.Reset()
175    while token:
176      # When we are looking at a token and decided to delete the whole line, we
177      # will delete all of them in the "HandleToken()" below.  So the current
178      # token and subsequent ones may already be deleted here.  The way we
179      # delete a token does not wipe out the previous and next pointers of the
180      # deleted token.  So we need to check the token itself to make sure it is
181      # not deleted.
182      if not token.is_deleted:
183        # End the pass at the stop token
184        if stop_token and token is stop_token:
185          return
186
187        self._state_tracker.HandleToken(
188            token, self._state_tracker.GetLastNonSpaceToken())
189        pass_function(token)
190        self._state_tracker.HandleAfterToken(token)
191
192      token = token.next
193