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