| OLD | NEW |
| (Empty) |
| 1 # Copyright (C) 2009 Google Inc. All rights reserved. | |
| 2 # Copyright (C) 2010 Chris Jerdonek (chris.jerdonek@gmail.com) | |
| 3 # Copyright (C) 2010 ProFUSION embedded systems | |
| 4 # | |
| 5 # Redistribution and use in source and binary forms, with or without | |
| 6 # modification, are permitted provided that the following conditions are | |
| 7 # met: | |
| 8 # | |
| 9 # * Redistributions of source code must retain the above copyright | |
| 10 # notice, this list of conditions and the following disclaimer. | |
| 11 # * Redistributions in binary form must reproduce the above | |
| 12 # copyright notice, this list of conditions and the following disclaimer | |
| 13 # in the documentation and/or other materials provided with the | |
| 14 # distribution. | |
| 15 # * Neither the name of Google Inc. nor the names of its | |
| 16 # contributors may be used to endorse or promote products derived from | |
| 17 # this software without specific prior written permission. | |
| 18 # | |
| 19 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 20 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 21 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 22 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 23 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 24 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 25 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 26 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 27 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 28 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 29 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 30 | |
| 31 """Front end of some style-checker modules.""" | |
| 32 | |
| 33 import logging | |
| 34 import os.path | |
| 35 import re | |
| 36 import sys | |
| 37 | |
| 38 from checkers.common import categories as CommonCategories | |
| 39 from checkers.common import CarriageReturnChecker | |
| 40 from checkers.cpp import CppChecker | |
| 41 from checkers.jsonchecker import JSONChecker | |
| 42 from checkers.png import PNGChecker | |
| 43 from checkers.python import PythonChecker | |
| 44 from checkers.test_expectations import TestExpectationsChecker | |
| 45 from checkers.text import TextChecker | |
| 46 from checkers.xcodeproj import XcodeProjectFileChecker | |
| 47 from checkers.xml import XMLChecker | |
| 48 from error_handlers import DefaultStyleErrorHandler | |
| 49 from filter import FilterConfiguration | |
| 50 from optparser import ArgumentParser | |
| 51 from optparser import DefaultCommandOptionValues | |
| 52 from webkitpy.common.system.logutils import configure_logging as _configure_logg
ing | |
| 53 | |
| 54 | |
| 55 _log = logging.getLogger(__name__) | |
| 56 | |
| 57 | |
| 58 # These are default option values for the command-line option parser. | |
| 59 _DEFAULT_MIN_CONFIDENCE = 1 | |
| 60 _DEFAULT_OUTPUT_FORMAT = 'emacs' | |
| 61 | |
| 62 | |
| 63 # FIXME: For style categories we will never want to have, remove them. | |
| 64 # For categories for which we want to have similar functionality, | |
| 65 # modify the implementation and enable them. | |
| 66 # | |
| 67 # Throughout this module, we use "filter rule" rather than "filter" | |
| 68 # for an individual boolean filter flag like "+foo". This allows us to | |
| 69 # reserve "filter" for what one gets by collectively applying all of | |
| 70 # the filter rules. | |
| 71 # | |
| 72 # The base filter rules are the filter rules that begin the list of | |
| 73 # filter rules used to check style. For example, these rules precede | |
| 74 # any user-specified filter rules. Since by default all categories are | |
| 75 # checked, this list should normally include only rules that begin | |
| 76 # with a "-" sign. | |
| 77 _BASE_FILTER_RULES = [ | |
| 78 '-build/endif_comment', | |
| 79 '-build/include_what_you_use', # <string> for std::string | |
| 80 '-build/storage_class', # const static | |
| 81 '-legal/copyright', | |
| 82 '-readability/multiline_comment', | |
| 83 '-readability/braces', # int foo() {}; | |
| 84 '-readability/fn_size', | |
| 85 '-readability/casting', | |
| 86 '-readability/function', | |
| 87 '-runtime/arrays', # variable length array | |
| 88 '-runtime/casting', | |
| 89 '-runtime/sizeof', | |
| 90 '-runtime/explicit', # explicit | |
| 91 '-runtime/virtual', # virtual dtor | |
| 92 '-runtime/printf', | |
| 93 '-runtime/threadsafe_fn', | |
| 94 '-runtime/rtti', | |
| 95 '-whitespace/blank_line', | |
| 96 '-whitespace/end_of_line', | |
| 97 # List Python pep8 categories last. | |
| 98 # | |
| 99 # Because much of WebKit's Python code base does not abide by the | |
| 100 # PEP8 79 character limit, we ignore the 79-character-limit category | |
| 101 # pep8/E501 for now. | |
| 102 # | |
| 103 # FIXME: Consider bringing WebKit's Python code base into conformance | |
| 104 # with the 79 character limit, or some higher limit that is | |
| 105 # agreeable to the WebKit project. | |
| 106 '-pep8/E501', | |
| 107 | |
| 108 # FIXME: Move the pylint rules from the pylintrc to here. This will | |
| 109 # also require us to re-work lint-webkitpy to produce the equivalent output. | |
| 110 ] | |
| 111 | |
| 112 | |
| 113 # The path-specific filter rules. | |
| 114 # | |
| 115 # This list is order sensitive. Only the first path substring match | |
| 116 # is used. See the FilterConfiguration documentation in filter.py | |
| 117 # for more information on this list. | |
| 118 # | |
| 119 # Each string appearing in this nested list should have at least | |
| 120 # one associated unit test assertion. These assertions are located, | |
| 121 # for example, in the test_path_rules_specifier() unit test method of | |
| 122 # checker_unittest.py. | |
| 123 _PATH_RULES_SPECIFIER = [ | |
| 124 # Files in these directories are consumers of the WebKit | |
| 125 # API and therefore do not follow the same header including | |
| 126 # discipline as WebCore. | |
| 127 | |
| 128 ([# There is no clean way to avoid "yy_*" names used by flex. | |
| 129 "Source/core/css/CSSParser-in.cpp"], | |
| 130 ["-readability/naming"]), | |
| 131 | |
| 132 # For third-party Python code, keep only the following checks-- | |
| 133 # | |
| 134 # No tabs: to avoid having to set the SVN allow-tabs property. | |
| 135 # No trailing white space: since this is easy to correct. | |
| 136 # No carriage-return line endings: since this is easy to correct. | |
| 137 # | |
| 138 (["webkitpy/thirdparty/"], | |
| 139 ["-", | |
| 140 "+pep8/W191", # Tabs | |
| 141 "+pep8/W291", # Trailing white space | |
| 142 "+whitespace/carriage_return"]), | |
| 143 | |
| 144 ([# Jinja templates: files have .cpp or .h extensions, but contain | |
| 145 # template code, which can't be handled, so disable tests. | |
| 146 "Source/bindings/templates", | |
| 147 "Source/build/scripts/templates"], | |
| 148 ["-"]), | |
| 149 | |
| 150 ([# IDL compiler reference output | |
| 151 # Conforming to style significantly increases the complexity of the code | |
| 152 # generator and decreases *its* readability, which is of more concern | |
| 153 # than style of the machine-generated code itself. | |
| 154 "Source/bindings/tests/results"], | |
| 155 ["-"]), | |
| 156 ] | |
| 157 | |
| 158 | |
| 159 _CPP_FILE_EXTENSIONS = [ | |
| 160 'c', | |
| 161 'cpp', | |
| 162 'h', | |
| 163 ] | |
| 164 | |
| 165 _JSON_FILE_EXTENSION = 'json' | |
| 166 | |
| 167 _PYTHON_FILE_EXTENSION = 'py' | |
| 168 | |
| 169 _TEXT_FILE_EXTENSIONS = [ | |
| 170 'cc', | |
| 171 'cgi', | |
| 172 'css', | |
| 173 'gyp', | |
| 174 'gypi', | |
| 175 'html', | |
| 176 'idl', | |
| 177 'in', | |
| 178 'js', | |
| 179 'mm', | |
| 180 'php', | |
| 181 'pl', | |
| 182 'pm', | |
| 183 'rb', | |
| 184 'sh', | |
| 185 'txt', | |
| 186 'xhtml', | |
| 187 'y', | |
| 188 ] | |
| 189 | |
| 190 _XCODEPROJ_FILE_EXTENSION = 'pbxproj' | |
| 191 | |
| 192 _XML_FILE_EXTENSIONS = [ | |
| 193 'vcproj', | |
| 194 'vsprops', | |
| 195 ] | |
| 196 | |
| 197 _PNG_FILE_EXTENSION = 'png' | |
| 198 | |
| 199 # Files to skip that are less obvious. | |
| 200 # | |
| 201 # Some files should be skipped when checking style. For example, | |
| 202 # WebKit maintains some files in Mozilla style on purpose to ease | |
| 203 # future merges. | |
| 204 _SKIPPED_FILES_WITH_WARNING = [ | |
| 205 "Source/WebKit/gtk/tests/", | |
| 206 # All WebKit*.h files in Source/WebKit2/UIProcess/API/gtk, | |
| 207 # except those ending in ...Private.h are GTK+ API headers, | |
| 208 # which differ greatly from WebKit coding style. | |
| 209 re.compile(r'Source/WebKit2/UIProcess/API/gtk/WebKit(?!.*Private\.h).*\.h$')
, | |
| 210 re.compile(r'Source/WebKit2/WebProcess/InjectedBundle/API/gtk/WebKit(?!.*Pri
vate\.h).*\.h$'), | |
| 211 'Source/WebKit2/UIProcess/API/gtk/webkit2.h', | |
| 212 'Source/WebKit2/WebProcess/InjectedBundle/API/gtk/webkit-web-extension.h'] | |
| 213 | |
| 214 # Files to skip that are more common or obvious. | |
| 215 # | |
| 216 # This list should be in addition to files with FileType.NONE. Files | |
| 217 # with FileType.NONE are automatically skipped without warning. | |
| 218 _SKIPPED_FILES_WITHOUT_WARNING = [ | |
| 219 "tests" + os.path.sep, | |
| 220 "Source/ThirdParty/leveldb" + os.path.sep, | |
| 221 # Prevents this being recognized as a text file. | |
| 222 "Source/WebCore/GNUmakefile.features.am.in", | |
| 223 ] | |
| 224 | |
| 225 # Extensions of files which are allowed to contain carriage returns. | |
| 226 _CARRIAGE_RETURN_ALLOWED_FILE_EXTENSIONS = [ | |
| 227 'png', | |
| 228 'vcproj', | |
| 229 'vsprops', | |
| 230 ] | |
| 231 | |
| 232 # The maximum number of errors to report per file, per category. | |
| 233 # If a category is not a key, then it has no maximum. | |
| 234 _MAX_REPORTS_PER_CATEGORY = { | |
| 235 "whitespace/carriage_return": 1 | |
| 236 } | |
| 237 | |
| 238 | |
| 239 def _all_categories(): | |
| 240 """Return the set of all categories used by check-webkit-style.""" | |
| 241 # Take the union across all checkers. | |
| 242 categories = CommonCategories.union(CppChecker.categories) | |
| 243 categories = categories.union(JSONChecker.categories) | |
| 244 categories = categories.union(TestExpectationsChecker.categories) | |
| 245 categories = categories.union(PNGChecker.categories) | |
| 246 | |
| 247 # FIXME: Consider adding all of the pep8 categories. Since they | |
| 248 # are not too meaningful for documentation purposes, for | |
| 249 # now we add only the categories needed for the unit tests | |
| 250 # (which validate the consistency of the configuration | |
| 251 # settings against the known categories, etc). | |
| 252 categories = categories.union(["pep8/W191", "pep8/W291", "pep8/E501"]) | |
| 253 | |
| 254 return categories | |
| 255 | |
| 256 | |
| 257 def _check_webkit_style_defaults(): | |
| 258 """Return the default command-line options for check-webkit-style.""" | |
| 259 return DefaultCommandOptionValues(min_confidence=_DEFAULT_MIN_CONFIDENCE, | |
| 260 output_format=_DEFAULT_OUTPUT_FORMAT) | |
| 261 | |
| 262 | |
| 263 # This function assists in optparser not having to import from checker. | |
| 264 def check_webkit_style_parser(): | |
| 265 all_categories = _all_categories() | |
| 266 default_options = _check_webkit_style_defaults() | |
| 267 return ArgumentParser(all_categories=all_categories, | |
| 268 base_filter_rules=_BASE_FILTER_RULES, | |
| 269 default_options=default_options) | |
| 270 | |
| 271 | |
| 272 def check_webkit_style_configuration(options): | |
| 273 """Return a StyleProcessorConfiguration instance for check-webkit-style. | |
| 274 | |
| 275 Args: | |
| 276 options: A CommandOptionValues instance. | |
| 277 | |
| 278 """ | |
| 279 filter_configuration = FilterConfiguration( | |
| 280 base_rules=_BASE_FILTER_RULES, | |
| 281 path_specific=_PATH_RULES_SPECIFIER, | |
| 282 user_rules=options.filter_rules) | |
| 283 | |
| 284 return StyleProcessorConfiguration(filter_configuration=filter_configuration
, | |
| 285 max_reports_per_category=_MAX_REPORTS_PER_CATEGORY, | |
| 286 min_confidence=options.min_confidence, | |
| 287 output_format=options.output_format, | |
| 288 stderr_write=sys.stderr.write) | |
| 289 | |
| 290 | |
| 291 def _create_log_handlers(stream): | |
| 292 """Create and return a default list of logging.Handler instances. | |
| 293 | |
| 294 Format WARNING messages and above to display the logging level, and | |
| 295 messages strictly below WARNING not to display it. | |
| 296 | |
| 297 Args: | |
| 298 stream: See the configure_logging() docstring. | |
| 299 | |
| 300 """ | |
| 301 # Handles logging.WARNING and above. | |
| 302 error_handler = logging.StreamHandler(stream) | |
| 303 error_handler.setLevel(logging.WARNING) | |
| 304 formatter = logging.Formatter("%(levelname)s: %(message)s") | |
| 305 error_handler.setFormatter(formatter) | |
| 306 | |
| 307 # Create a logging.Filter instance that only accepts messages | |
| 308 # below WARNING (i.e. filters out anything WARNING or above). | |
| 309 non_error_filter = logging.Filter() | |
| 310 # The filter method accepts a logging.LogRecord instance. | |
| 311 non_error_filter.filter = lambda record: record.levelno < logging.WARNING | |
| 312 | |
| 313 non_error_handler = logging.StreamHandler(stream) | |
| 314 non_error_handler.addFilter(non_error_filter) | |
| 315 formatter = logging.Formatter("%(message)s") | |
| 316 non_error_handler.setFormatter(formatter) | |
| 317 | |
| 318 return [error_handler, non_error_handler] | |
| 319 | |
| 320 | |
| 321 def _create_debug_log_handlers(stream): | |
| 322 """Create and return a list of logging.Handler instances for debugging. | |
| 323 | |
| 324 Args: | |
| 325 stream: See the configure_logging() docstring. | |
| 326 | |
| 327 """ | |
| 328 handler = logging.StreamHandler(stream) | |
| 329 formatter = logging.Formatter("%(name)s: %(levelname)-8s %(message)s") | |
| 330 handler.setFormatter(formatter) | |
| 331 | |
| 332 return [handler] | |
| 333 | |
| 334 | |
| 335 def configure_logging(stream, logger=None, is_verbose=False): | |
| 336 """Configure logging, and return the list of handlers added. | |
| 337 | |
| 338 Returns: | |
| 339 A list of references to the logging handlers added to the root | |
| 340 logger. This allows the caller to later remove the handlers | |
| 341 using logger.removeHandler. This is useful primarily during unit | |
| 342 testing where the caller may want to configure logging temporarily | |
| 343 and then undo the configuring. | |
| 344 | |
| 345 Args: | |
| 346 stream: A file-like object to which to log. The stream must | |
| 347 define an "encoding" data attribute, or else logging | |
| 348 raises an error. | |
| 349 logger: A logging.logger instance to configure. This parameter | |
| 350 should be used only in unit tests. Defaults to the | |
| 351 root logger. | |
| 352 is_verbose: A boolean value of whether logging should be verbose. | |
| 353 | |
| 354 """ | |
| 355 # If the stream does not define an "encoding" data attribute, the | |
| 356 # logging module can throw an error like the following: | |
| 357 # | |
| 358 # Traceback (most recent call last): | |
| 359 # File "/System/Library/Frameworks/Python.framework/Versions/2.6/... | |
| 360 # lib/python2.6/logging/__init__.py", line 761, in emit | |
| 361 # self.stream.write(fs % msg.encode(self.stream.encoding)) | |
| 362 # LookupError: unknown encoding: unknown | |
| 363 if logger is None: | |
| 364 logger = logging.getLogger() | |
| 365 | |
| 366 if is_verbose: | |
| 367 logging_level = logging.DEBUG | |
| 368 handlers = _create_debug_log_handlers(stream) | |
| 369 else: | |
| 370 logging_level = logging.INFO | |
| 371 handlers = _create_log_handlers(stream) | |
| 372 | |
| 373 handlers = _configure_logging(logging_level=logging_level, logger=logger, | |
| 374 handlers=handlers) | |
| 375 | |
| 376 return handlers | |
| 377 | |
| 378 | |
| 379 # Enum-like idiom | |
| 380 class FileType: | |
| 381 | |
| 382 NONE = 0 # FileType.NONE evaluates to False. | |
| 383 # Alphabetize remaining types | |
| 384 # CHANGELOG = 1 | |
| 385 CPP = 2 | |
| 386 JSON = 3 | |
| 387 PNG = 4 | |
| 388 PYTHON = 5 | |
| 389 TEXT = 6 | |
| 390 # WATCHLIST = 7 | |
| 391 XML = 8 | |
| 392 XCODEPROJ = 9 | |
| 393 | |
| 394 | |
| 395 class CheckerDispatcher(object): | |
| 396 | |
| 397 """Supports determining whether and how to check style, based on path.""" | |
| 398 | |
| 399 def _file_extension(self, file_path): | |
| 400 """Return the file extension without the leading dot.""" | |
| 401 return os.path.splitext(file_path)[1].lstrip(".") | |
| 402 | |
| 403 def _should_skip_file_path(self, file_path, skip_array_entry): | |
| 404 match = re.search("\s*png$", file_path) | |
| 405 if match: | |
| 406 return False | |
| 407 if isinstance(skip_array_entry, str): | |
| 408 if file_path.find(skip_array_entry) >= 0: | |
| 409 return True | |
| 410 elif skip_array_entry.match(file_path): | |
| 411 return True | |
| 412 return False | |
| 413 | |
| 414 def should_skip_with_warning(self, file_path): | |
| 415 """Return whether the given file should be skipped with a warning.""" | |
| 416 for skipped_file in _SKIPPED_FILES_WITH_WARNING: | |
| 417 if self._should_skip_file_path(file_path, skipped_file): | |
| 418 return True | |
| 419 return False | |
| 420 | |
| 421 def should_skip_without_warning(self, file_path): | |
| 422 """Return whether the given file should be skipped without a warning.""" | |
| 423 if not self._file_type(file_path): # FileType.NONE. | |
| 424 return True | |
| 425 # Since "tests" is in _SKIPPED_FILES_WITHOUT_WARNING, make | |
| 426 # an exception to prevent files like 'TestExpectations' from being skipp
ed. | |
| 427 # | |
| 428 # FIXME: Figure out a good way to avoid having to add special logic | |
| 429 # for this special case. | |
| 430 basename = os.path.basename(file_path) | |
| 431 if basename == 'TestExpectations': | |
| 432 return False | |
| 433 for skipped_file in _SKIPPED_FILES_WITHOUT_WARNING: | |
| 434 if self._should_skip_file_path(file_path, skipped_file): | |
| 435 return True | |
| 436 return False | |
| 437 | |
| 438 def should_check_and_strip_carriage_returns(self, file_path): | |
| 439 return self._file_extension(file_path) not in _CARRIAGE_RETURN_ALLOWED_F
ILE_EXTENSIONS | |
| 440 | |
| 441 def _file_type(self, file_path): | |
| 442 """Return the file type corresponding to the given file.""" | |
| 443 file_extension = self._file_extension(file_path) | |
| 444 | |
| 445 if (file_extension in _CPP_FILE_EXTENSIONS) or (file_path == '-'): | |
| 446 # FIXME: Do something about the comment below and the issue it | |
| 447 # raises since cpp_style already relies on the extension. | |
| 448 # | |
| 449 # Treat stdin as C++. Since the extension is unknown when | |
| 450 # reading from stdin, cpp_style tests should not rely on | |
| 451 # the extension. | |
| 452 return FileType.CPP | |
| 453 elif file_extension == _JSON_FILE_EXTENSION: | |
| 454 return FileType.JSON | |
| 455 elif file_extension == _PYTHON_FILE_EXTENSION: | |
| 456 return FileType.PYTHON | |
| 457 elif file_extension in _XML_FILE_EXTENSIONS: | |
| 458 return FileType.XML | |
| 459 elif file_extension == _XCODEPROJ_FILE_EXTENSION: | |
| 460 return FileType.XCODEPROJ | |
| 461 elif file_extension == _PNG_FILE_EXTENSION: | |
| 462 return FileType.PNG | |
| 463 elif ((not file_extension and os.path.join("Tools", "Scripts") in file_p
ath) or | |
| 464 file_extension in _TEXT_FILE_EXTENSIONS or os.path.basename(file_p
ath) == 'TestExpectations'): | |
| 465 return FileType.TEXT | |
| 466 else: | |
| 467 return FileType.NONE | |
| 468 | |
| 469 def _create_checker(self, file_type, file_path, handle_style_error, | |
| 470 min_confidence): | |
| 471 """Instantiate and return a style checker based on file type.""" | |
| 472 if file_type == FileType.NONE: | |
| 473 checker = None | |
| 474 elif file_type == FileType.CPP: | |
| 475 file_extension = self._file_extension(file_path) | |
| 476 checker = CppChecker(file_path, file_extension, | |
| 477 handle_style_error, min_confidence) | |
| 478 elif file_type == FileType.JSON: | |
| 479 checker = JSONChecker(file_path, handle_style_error) | |
| 480 elif file_type == FileType.PYTHON: | |
| 481 checker = PythonChecker(file_path, handle_style_error) | |
| 482 elif file_type == FileType.XML: | |
| 483 checker = XMLChecker(file_path, handle_style_error) | |
| 484 elif file_type == FileType.XCODEPROJ: | |
| 485 checker = XcodeProjectFileChecker(file_path, handle_style_error) | |
| 486 elif file_type == FileType.PNG: | |
| 487 checker = PNGChecker(file_path, handle_style_error) | |
| 488 elif file_type == FileType.TEXT: | |
| 489 basename = os.path.basename(file_path) | |
| 490 if basename == 'TestExpectations': | |
| 491 checker = TestExpectationsChecker(file_path, handle_style_error) | |
| 492 else: | |
| 493 checker = TextChecker(file_path, handle_style_error) | |
| 494 else: | |
| 495 raise ValueError('Invalid file type "%(file_type)s": the only valid
file types ' | |
| 496 "are %(NONE)s, %(CPP)s, and %(TEXT)s." | |
| 497 % {"file_type": file_type, | |
| 498 "NONE": FileType.NONE, | |
| 499 "CPP": FileType.CPP, | |
| 500 "TEXT": FileType.TEXT}) | |
| 501 | |
| 502 return checker | |
| 503 | |
| 504 def dispatch(self, file_path, handle_style_error, min_confidence): | |
| 505 """Instantiate and return a style checker based on file path.""" | |
| 506 file_type = self._file_type(file_path) | |
| 507 | |
| 508 checker = self._create_checker(file_type, | |
| 509 file_path, | |
| 510 handle_style_error, | |
| 511 min_confidence) | |
| 512 return checker | |
| 513 | |
| 514 | |
| 515 # FIXME: Remove the stderr_write attribute from this class and replace | |
| 516 # its use with calls to a logging module logger. | |
| 517 class StyleProcessorConfiguration(object): | |
| 518 | |
| 519 """Stores configuration values for the StyleProcessor class. | |
| 520 | |
| 521 Attributes: | |
| 522 min_confidence: An integer between 1 and 5 inclusive that is the | |
| 523 minimum confidence level of style errors to report. | |
| 524 | |
| 525 max_reports_per_category: The maximum number of errors to report | |
| 526 per category, per file. | |
| 527 | |
| 528 stderr_write: A function that takes a string as a parameter and | |
| 529 serves as stderr.write. | |
| 530 | |
| 531 """ | |
| 532 | |
| 533 def __init__(self, | |
| 534 filter_configuration, | |
| 535 max_reports_per_category, | |
| 536 min_confidence, | |
| 537 output_format, | |
| 538 stderr_write): | |
| 539 """Create a StyleProcessorConfiguration instance. | |
| 540 | |
| 541 Args: | |
| 542 filter_configuration: A FilterConfiguration instance. The default | |
| 543 is the "empty" filter configuration, which | |
| 544 means that all errors should be checked. | |
| 545 | |
| 546 max_reports_per_category: The maximum number of errors to report | |
| 547 per category, per file. | |
| 548 | |
| 549 min_confidence: An integer between 1 and 5 inclusive that is the | |
| 550 minimum confidence level of style errors to report. | |
| 551 The default is 1, which reports all style errors. | |
| 552 | |
| 553 output_format: A string that is the output format. The supported | |
| 554 output formats are "emacs" which emacs can parse | |
| 555 and "vs7" which Microsoft Visual Studio 7 can parse. | |
| 556 | |
| 557 stderr_write: A function that takes a string as a parameter and | |
| 558 serves as stderr.write. | |
| 559 | |
| 560 """ | |
| 561 self._filter_configuration = filter_configuration | |
| 562 self._output_format = output_format | |
| 563 | |
| 564 self.max_reports_per_category = max_reports_per_category | |
| 565 self.min_confidence = min_confidence | |
| 566 self.stderr_write = stderr_write | |
| 567 | |
| 568 def is_reportable(self, category, confidence_in_error, file_path): | |
| 569 """Return whether an error is reportable. | |
| 570 | |
| 571 An error is reportable if both the confidence in the error is | |
| 572 at least the minimum confidence level and the current filter | |
| 573 says the category should be checked for the given path. | |
| 574 | |
| 575 Args: | |
| 576 category: A string that is a style category. | |
| 577 confidence_in_error: An integer between 1 and 5 inclusive that is | |
| 578 the application's confidence in the error. | |
| 579 A higher number means greater confidence. | |
| 580 file_path: The path of the file being checked | |
| 581 | |
| 582 """ | |
| 583 if confidence_in_error < self.min_confidence: | |
| 584 return False | |
| 585 | |
| 586 return self._filter_configuration.should_check(category, file_path) | |
| 587 | |
| 588 def write_style_error(self, | |
| 589 category, | |
| 590 confidence_in_error, | |
| 591 file_path, | |
| 592 line_number, | |
| 593 message): | |
| 594 """Write a style error to the configured stderr.""" | |
| 595 if self._output_format == 'vs7': | |
| 596 format_string = "%s(%s): %s [%s] [%d]\n" | |
| 597 else: | |
| 598 format_string = "%s:%s: %s [%s] [%d]\n" | |
| 599 | |
| 600 self.stderr_write(format_string % (file_path, | |
| 601 line_number, | |
| 602 message, | |
| 603 category, | |
| 604 confidence_in_error)) | |
| 605 | |
| 606 | |
| 607 class ProcessorBase(object): | |
| 608 | |
| 609 """The base class for processors of lists of lines.""" | |
| 610 | |
| 611 def should_process(self, file_path): | |
| 612 """Return whether the file at file_path should be processed. | |
| 613 | |
| 614 The TextFileReader class calls this method prior to reading in | |
| 615 the lines of a file. Use this method, for example, to prevent | |
| 616 the style checker from reading binary files into memory. | |
| 617 | |
| 618 """ | |
| 619 raise NotImplementedError('Subclasses should implement.') | |
| 620 | |
| 621 def process(self, lines, file_path, **kwargs): | |
| 622 """Process lines of text read from a file. | |
| 623 | |
| 624 Args: | |
| 625 lines: A list of lines of text to process. | |
| 626 file_path: The path from which the lines were read. | |
| 627 **kwargs: This argument signifies that the process() method of | |
| 628 subclasses of ProcessorBase may support additional | |
| 629 keyword arguments. | |
| 630 For example, a style checker's check() method | |
| 631 may support a "reportable_lines" parameter that represents | |
| 632 the line numbers of the lines for which style errors | |
| 633 should be reported. | |
| 634 | |
| 635 """ | |
| 636 raise NotImplementedError('Subclasses should implement.') | |
| 637 | |
| 638 | |
| 639 class StyleProcessor(ProcessorBase): | |
| 640 | |
| 641 """A ProcessorBase for checking style. | |
| 642 | |
| 643 Attributes: | |
| 644 error_count: An integer that is the total number of reported | |
| 645 errors for the lifetime of this instance. | |
| 646 | |
| 647 """ | |
| 648 | |
| 649 def __init__(self, configuration, mock_dispatcher=None, | |
| 650 mock_increment_error_count=None, | |
| 651 mock_carriage_checker_class=None): | |
| 652 """Create an instance. | |
| 653 | |
| 654 Args: | |
| 655 configuration: A StyleProcessorConfiguration instance. | |
| 656 mock_dispatcher: A mock CheckerDispatcher instance. This | |
| 657 parameter is for unit testing. Defaults to a | |
| 658 CheckerDispatcher instance. | |
| 659 mock_increment_error_count: A mock error-count incrementer. | |
| 660 mock_carriage_checker_class: A mock class for checking and | |
| 661 transforming carriage returns. | |
| 662 This parameter is for unit testing. | |
| 663 Defaults to CarriageReturnChecker. | |
| 664 | |
| 665 """ | |
| 666 if mock_dispatcher is None: | |
| 667 dispatcher = CheckerDispatcher() | |
| 668 else: | |
| 669 dispatcher = mock_dispatcher | |
| 670 | |
| 671 if mock_increment_error_count is None: | |
| 672 # The following blank line is present to avoid flagging by pep8.py. | |
| 673 | |
| 674 def increment_error_count(): | |
| 675 """Increment the total count of reported errors.""" | |
| 676 self.error_count += 1 | |
| 677 else: | |
| 678 increment_error_count = mock_increment_error_count | |
| 679 | |
| 680 if mock_carriage_checker_class is None: | |
| 681 # This needs to be a class rather than an instance since the | |
| 682 # process() method instantiates one using parameters. | |
| 683 carriage_checker_class = CarriageReturnChecker | |
| 684 else: | |
| 685 carriage_checker_class = mock_carriage_checker_class | |
| 686 | |
| 687 self.error_count = 0 | |
| 688 | |
| 689 self._carriage_checker_class = carriage_checker_class | |
| 690 self._configuration = configuration | |
| 691 self._dispatcher = dispatcher | |
| 692 self._increment_error_count = increment_error_count | |
| 693 | |
| 694 def should_process(self, file_path): | |
| 695 """Return whether the file should be checked for style.""" | |
| 696 if self._dispatcher.should_skip_without_warning(file_path): | |
| 697 return False | |
| 698 if self._dispatcher.should_skip_with_warning(file_path): | |
| 699 _log.warn('File exempt from style guide. Skipping: "%s"' | |
| 700 % file_path) | |
| 701 return False | |
| 702 return True | |
| 703 | |
| 704 def process(self, lines, file_path, line_numbers=None): | |
| 705 """Check the given lines for style. | |
| 706 | |
| 707 Arguments: | |
| 708 lines: A list of all lines in the file to check. | |
| 709 file_path: The path of the file to process. If possible, the path | |
| 710 should be relative to the source root. Otherwise, | |
| 711 path-specific logic may not behave as expected. | |
| 712 line_numbers: A list of line numbers of the lines for which | |
| 713 style errors should be reported, or None if errors | |
| 714 for all lines should be reported. When not None, this | |
| 715 list normally contains the line numbers corresponding | |
| 716 to the modified lines of a patch. | |
| 717 | |
| 718 """ | |
| 719 _log.debug("Checking style: " + file_path) | |
| 720 | |
| 721 style_error_handler = DefaultStyleErrorHandler( | |
| 722 configuration=self._configuration, | |
| 723 file_path=file_path, | |
| 724 increment_error_count=self._increment_error_count, | |
| 725 line_numbers=line_numbers) | |
| 726 | |
| 727 carriage_checker = self._carriage_checker_class(style_error_handler) | |
| 728 | |
| 729 # Check for and remove trailing carriage returns ("\r"). | |
| 730 if self._dispatcher.should_check_and_strip_carriage_returns(file_path): | |
| 731 lines = carriage_checker.check(lines) | |
| 732 | |
| 733 min_confidence = self._configuration.min_confidence | |
| 734 checker = self._dispatcher.dispatch(file_path, | |
| 735 style_error_handler, | |
| 736 min_confidence) | |
| 737 | |
| 738 if checker is None: | |
| 739 raise AssertionError("File should not be checked: '%s'" % file_path) | |
| 740 | |
| 741 _log.debug("Using class: " + checker.__class__.__name__) | |
| 742 | |
| 743 checker.check(lines) | |
| OLD | NEW |