aboutsummaryrefslogtreecommitdiff
path: root/tools/closure_linter-2.3.4/closure_linter/checkerbase.py
blob: 592454d64b3f5046e915e0e9cc485e7555df7225 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
#!/usr/bin/env python
#
# Copyright 2008 The Closure Linter Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS-IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Base classes for writing checkers that operate on tokens."""

__author__ = ('robbyw@google.com (Robert Walker)',
              'ajp@google.com (Andy Perelson)',
              'jacobr@google.com (Jacob Richman)')

import StringIO
import traceback

import gflags as flags
from closure_linter import ecmametadatapass
from closure_linter import errorrules
from closure_linter import errors
from closure_linter import javascripttokenizer
from closure_linter.common import error
from closure_linter.common import htmlutil

FLAGS = flags.FLAGS
flags.DEFINE_boolean('debug_tokens', False,
                     'Whether to print all tokens for debugging.')

flags.DEFINE_boolean('error_trace', False,
                     'Whether to show error exceptions.')


class LintRulesBase(object):
  """Base class for all classes defining the lint rules for a language."""

  def __init__(self):
    self.__checker = None

  def Initialize(self, checker, limited_doc_checks, is_html):
    """Initializes to prepare to check a file.

    Args:
      checker: Class to report errors to.
      limited_doc_checks: Whether doc checking is relaxed for this file.
      is_html: Whether the file is an HTML file with extracted contents.
    """
    self.__checker = checker
    self._limited_doc_checks = limited_doc_checks
    self._is_html = is_html

  def _HandleError(self, code, message, token, position=None,
                   fix_data=None):
    """Call the HandleError function for the checker we are associated with."""
    if errorrules.ShouldReportError(code):
      self.__checker.HandleError(code, message, token, position, fix_data)

  def _SetLimitedDocChecks(self, limited_doc_checks):
    """Sets whether doc checking is relaxed for this file.

    Args:
      limited_doc_checks: Whether doc checking is relaxed for this file.
    """
    self._limited_doc_checks = limited_doc_checks

  def CheckToken(self, token, parser_state):
    """Checks a token, given the current parser_state, for warnings and errors.

    Args:
      token: The current token under consideration.
      parser_state: Object that indicates the parser state in the page.

    Raises:
      TypeError: If not overridden.
    """
    raise TypeError('Abstract method CheckToken not implemented')

  def Finalize(self, parser_state, tokenizer_mode):
    """Perform all checks that need to occur after all lines are processed.

    Args:
      parser_state: State of the parser after parsing all tokens
      tokenizer_mode: Mode of the tokenizer after parsing the entire page

    Raises:
      TypeError: If not overridden.
    """
    raise TypeError('Abstract method Finalize not implemented')


class CheckerBase(object):
  """This class handles checking a LintRules object against a file."""

  def __init__(self, error_handler, lint_rules, state_tracker,
               limited_doc_files=None, metadata_pass=None):
    """Initialize a checker object.

    Args:
      error_handler: Object that handles errors.
      lint_rules: LintRules object defining lint errors given a token
        and state_tracker object.
      state_tracker: Object that tracks the current state in the token stream.
      limited_doc_files: List of filenames that are not required to have
        documentation comments.
      metadata_pass: Object that builds metadata about the token stream.
    """
    self._error_handler = error_handler
    self._lint_rules = lint_rules
    self._state_tracker = state_tracker
    self._metadata_pass = metadata_pass
    self._limited_doc_files = limited_doc_files
    self._tokenizer = javascripttokenizer.JavaScriptTokenizer()
    self._has_errors = False

  def HandleError(self, code, message, token, position=None,
                  fix_data=None):
    """Prints out the given error message including a line number.

    Args:
      code: The error code.
      message: The error to print.
      token: The token where the error occurred, or None if it was a file-wide
          issue.
      position: The position of the error, defaults to None.
      fix_data: Metadata used for fixing the error.
    """
    self._has_errors = True
    self._error_handler.HandleError(
        error.Error(code, message, token, position, fix_data))

  def HasErrors(self):
    """Returns true if the style checker has found any errors.

    Returns:
      True if the style checker has found any errors.
    """
    return self._has_errors

  def Check(self, filename, source=None):
    """Checks the file, printing warnings and errors as they are found.

    Args:
      filename: The name of the file to check.
      source: Optional. The contents of the file.  Can be either a string or
          file-like object.  If omitted, contents will be read from disk from
          the given filename.
    """

    if source is None:
      try:
        f = open(filename)
      except IOError:
        self._error_handler.HandleFile(filename, None)
        self.HandleError(errors.FILE_NOT_FOUND, 'File not found', None)
        self._error_handler.FinishFile()
        return
    else:
      if type(source) in [str, unicode]:
        f = StringIO.StringIO(source)
      else:
        f = source

    try:
      if filename.endswith('.html') or filename.endswith('.htm'):
        self.CheckLines(filename, htmlutil.GetScriptLines(f), True)
      else:
        self.CheckLines(filename, f, False)
    finally:
      f.close()

  def CheckLines(self, filename, lines_iter, is_html):
    """Checks a file, given as an iterable of lines, for warnings and errors.

    Args:
      filename: The name of the file to check.
      lines_iter: An iterator that yields one line of the file at a time.
      is_html: Whether the file being checked is an HTML file with extracted
          contents.

    Returns:
      A boolean indicating whether the full file could be checked or if checking
      failed prematurely.
    """
    limited_doc_checks = False
    if self._limited_doc_files:
      for limited_doc_filename in self._limited_doc_files:
        if filename.endswith(limited_doc_filename):
          limited_doc_checks = True
          break

    lint_rules = self._lint_rules
    lint_rules.Initialize(self, limited_doc_checks, is_html)

    token = self._tokenizer.TokenizeFile(lines_iter)

    parse_error = None
    if self._metadata_pass:
      try:
        self._metadata_pass.Reset()
        self._metadata_pass.Process(token)
      except ecmametadatapass.ParseError, caught_parse_error:
        if FLAGS.error_trace:
          traceback.print_exc()
        parse_error = caught_parse_error
      except Exception:
        print 'Internal error in %s' % filename
        traceback.print_exc()
        return False

    self._error_handler.HandleFile(filename, token)

    return self._CheckTokens(token, parse_error=parse_error,
                             debug_tokens=FLAGS.debug_tokens)

  def _CheckTokens(self, token, parse_error, debug_tokens):
    """Checks a token stream for lint warnings/errors.

    Args:
      token: The first token in the token stream to check.
      parse_error: A ParseError if any errors occurred.
      debug_tokens: Whether every token should be printed as it is encountered
          during the pass.

    Returns:
      A boolean indicating whether the full token stream could be checked or if
      checking failed prematurely.
    """
    result = self._ExecutePass(token, self._LintPass, parse_error, debug_tokens)

    if not result:
      return False

    self._lint_rules.Finalize(self._state_tracker, self._tokenizer.mode)
    self._error_handler.FinishFile()
    return True

  def _LintPass(self, token):
    """Checks an individual token for lint warnings/errors.

    Used to encapsulate the logic needed to check an individual token so that it
    can be passed to _ExecutePass.

    Args:
      token: The token to check.
    """
    self._lint_rules.CheckToken(token, self._state_tracker)

  def _ExecutePass(self, token, pass_function, parse_error=None,
                   debug_tokens=False):
    """Calls the given function for every token in the given token stream.

    As each token is passed to the given function, state is kept up to date and,
    depending on the error_trace flag, errors are either caught and reported, or
    allowed to bubble up so developers can see the full stack trace. If a parse
    error is specified, the pass will proceed as normal until the token causing
    the parse error is reached.

    Args:
      token: The first token in the token stream.
      pass_function: The function to call for each token in the token stream.
      parse_error: A ParseError if any errors occurred.
      debug_tokens: Whether every token should be printed as it is encountered
          during the pass.

    Returns:
      A boolean indicating whether the full token stream could be checked or if
      checking failed prematurely.

    Raises:
      Exception: If any error occurred while calling the given function.
    """
    self._state_tracker.Reset()
    while token:
      if debug_tokens:
        print token

      if parse_error and parse_error.token == token:
        message = ('Error parsing file at token "%s". Unable to '
                   'check the rest of file.' % token.string)
        self.HandleError(errors.FILE_DOES_NOT_PARSE, message, token)
        self._error_handler.FinishFile()
        return

      try:
        self._state_tracker.HandleToken(
            token, self._state_tracker.GetLastNonSpaceToken())
        pass_function(token)
        self._state_tracker.HandleAfterToken(token)
      except:
        if FLAGS.error_trace:
          raise
        else:
          self.HandleError(errors.FILE_DOES_NOT_PARSE,
                           ('Error parsing file at token "%s". Unable to '
                            'check the rest of file.' % token.string),
                           token)
          self._error_handler.FinishFile()
        return False
      token = token.next
    return True