1 # Copyright (C) 2009 Google Inc. All rights reserved. 2 # Copyright (C) 2010 Chris Jerdonek (chris.jerdonek (at] 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.cmake import CMakeChecker 42 from checkers.jsonchecker import JSONChecker 43 from checkers.png import PNGChecker 44 from checkers.python import PythonChecker 45 from checkers.test_expectations import TestExpectationsChecker 46 from checkers.text import TextChecker 47 from checkers.xcodeproj import XcodeProjectFileChecker 48 from checkers.xml import XMLChecker 49 from error_handlers import DefaultStyleErrorHandler 50 from filter import FilterConfiguration 51 from optparser import ArgumentParser 52 from optparser import DefaultCommandOptionValues 53 from webkitpy.common.system.logutils import configure_logging as _configure_logging 54 55 56 _log = logging.getLogger(__name__) 57 58 59 # These are default option values for the command-line option parser. 60 _DEFAULT_MIN_CONFIDENCE = 1 61 _DEFAULT_OUTPUT_FORMAT = 'emacs' 62 63 64 # FIXME: For style categories we will never want to have, remove them. 65 # For categories for which we want to have similar functionality, 66 # modify the implementation and enable them. 67 # 68 # Throughout this module, we use "filter rule" rather than "filter" 69 # for an individual boolean filter flag like "+foo". This allows us to 70 # reserve "filter" for what one gets by collectively applying all of 71 # the filter rules. 72 # 73 # The base filter rules are the filter rules that begin the list of 74 # filter rules used to check style. For example, these rules precede 75 # any user-specified filter rules. Since by default all categories are 76 # checked, this list should normally include only rules that begin 77 # with a "-" sign. 78 _BASE_FILTER_RULES = [ 79 '-build/endif_comment', 80 '-build/include_what_you_use', # <string> for std::string 81 '-build/storage_class', # const static 82 '-legal/copyright', 83 '-readability/multiline_comment', 84 '-readability/braces', # int foo() {}; 85 '-readability/fn_size', 86 '-readability/casting', 87 '-readability/function', 88 '-runtime/arrays', # variable length array 89 '-runtime/casting', 90 '-runtime/sizeof', 91 '-runtime/explicit', # explicit 92 '-runtime/virtual', # virtual dtor 93 '-runtime/printf', 94 '-runtime/threadsafe_fn', 95 '-runtime/rtti', 96 '-whitespace/blank_line', 97 '-whitespace/end_of_line', 98 # List Python pep8 categories last. 99 # 100 # Because much of WebKit's Python code base does not abide by the 101 # PEP8 79 character limit, we ignore the 79-character-limit category 102 # pep8/E501 for now. 103 # 104 # FIXME: Consider bringing WebKit's Python code base into conformance 105 # with the 79 character limit, or some higher limit that is 106 # agreeable to the WebKit project. 107 '-pep8/E501', 108 109 # FIXME: Move the pylint rules from the pylintrc to here. This will 110 # also require us to re-work lint-webkitpy to produce the equivalent output. 111 ] 112 113 114 # The path-specific filter rules. 115 # 116 # This list is order sensitive. Only the first path substring match 117 # is used. See the FilterConfiguration documentation in filter.py 118 # for more information on this list. 119 # 120 # Each string appearing in this nested list should have at least 121 # one associated unit test assertion. These assertions are located, 122 # for example, in the test_path_rules_specifier() unit test method of 123 # checker_unittest.py. 124 _PATH_RULES_SPECIFIER = [ 125 # Files in these directories are consumers of the WebKit 126 # API and therefore do not follow the same header including 127 # discipline as WebCore. 128 129 ([# There is no clean way to avoid "yy_*" names used by flex. 130 "Source/WebCore/css/CSSParser.cpp", 131 # Qt code uses '_' in some places (such as private slots 132 # and on test xxx_data methos on tests) 133 "Source/JavaScriptCore/qt/", 134 "Source/WebKit/qt/tests/", 135 "Source/WebKit/qt/declarative/", 136 "Source/WebKit/qt/examples/"], 137 ["-readability/naming"]), 138 139 ([# The Qt APIs use Qt declaration style, it puts the * to 140 # the variable name, not to the class. 141 "Source/WebKit/qt/Api/", 142 "Source/WebKit/qt/WidgetApi/"], 143 ["-readability/naming", 144 "-whitespace/declaration"]), 145 146 ([# Qt's MiniBrowser has no config.h 147 "Tools/MiniBrowser/qt", 148 "Tools/MiniBrowser/qt/raw"], 149 ["-build/include"]), 150 151 ([# The Qt APIs use Qt/QML naming style, which includes 152 # naming parameters in h files. 153 "Source/WebKit2/UIProcess/API/qt"], 154 ["-readability/parameter_name"]), 155 156 ([# The GTK+ port uses the autotoolsconfig.h header in some C sources 157 # to serve the same purpose of config.h. 158 "Tools/GtkLauncher/main.c"], 159 ["-build/include_order"]), 160 161 ([# The GTK+ APIs use GTK+ naming style, which includes 162 # lower-cased, underscore-separated values, whitespace before 163 # parens for function calls, and always having variable names. 164 # Also, GTK+ allows the use of NULL. 165 "Source/WebCore/bindings/scripts/test/GObject", 166 "Source/WebKit/gtk/webkit/", 167 "Tools/DumpRenderTree/gtk/"], 168 ["-readability/naming", 169 "-readability/parameter_name", 170 "-readability/null", 171 "-readability/enum_casing", 172 "-whitespace/parens"]), 173 174 ([# The GTK+ API use upper case, underscore separated, words in 175 # certain types of enums (e.g. signals, properties). 176 "Source/WebKit2/UIProcess/API/gtk", 177 "Source/WebKit2/WebProcess/InjectedBundle/API/gtk"], 178 ["-readability/enum_casing"]), 179 180 ([# Header files in ForwardingHeaders have no header guards or 181 # exceptional header guards (e.g., WebCore_FWD_Debugger_h). 182 "/ForwardingHeaders/"], 183 ["-build/header_guard"]), 184 ([# assembler has lots of opcodes that use underscores, so 185 # we don't check for underscores in that directory. 186 "Source/JavaScriptCore/assembler/", 187 "Source/JavaScriptCore/jit/JIT"], 188 ["-readability/naming/underscores"]), 189 ([# JITStubs has an usual syntax which causes false alarms for a few checks. 190 "JavaScriptCore/jit/JITStubs.cpp"], 191 ["-readability/parameter_name", 192 "-whitespace/parens"]), 193 194 ([# The EFL APIs use EFL naming style, which includes 195 # both lower-cased and camel-cased, underscore-sparated 196 # values. 197 "Source/WebKit/efl/ewk/", 198 "Source/WebKit2/UIProcess/API/efl/"], 199 ["-readability/naming", 200 "-readability/parameter_name"]), 201 ([# EWebLauncher and MiniBrowser are EFL simple application. 202 # They need to use efl coding style and they don't have config.h. 203 "Tools/EWebLauncher/", 204 "Tools/MiniBrowser/efl/"], 205 ["-readability/naming", 206 "-readability/parameter_name", 207 "-whitespace/declaration", 208 "-build/include_order"]), 209 210 # WebKit2 rules: 211 # WebKit2 and certain directories have idiosyncracies. 212 ([# NPAPI has function names with underscores. 213 "Source/WebKit2/WebProcess/Plugins/Netscape"], 214 ["-readability/naming"]), 215 ([# The WebKit2 C API has names with underscores and whitespace-aligned 216 # struct members. Also, we allow unnecessary parameter names in 217 # WebKit2 APIs because we're matching CF's header style. 218 # Additionally, we use word which starts with non-capital letter 'k' 219 # for types of enums. 220 "Source/WebKit2/UIProcess/API/C/", 221 "Source/WebKit2/Shared/API/c/", 222 "Source/WebKit2/WebProcess/InjectedBundle/API/c/"], 223 ["-readability/enum_casing", 224 "-readability/naming", 225 "-readability/parameter_name", 226 "-whitespace/declaration"]), 227 ([# These files define GObjects, which implies some definitions of 228 # variables and functions containing underscores. 229 "Source/WebCore/platform/graphics/clutter/GraphicsLayerActor.cpp", 230 "Source/WebCore/platform/graphics/clutter/GraphicsLayerActor.h", 231 "Source/WebCore/platform/graphics/gstreamer/VideoSinkGStreamer1.cpp", 232 "Source/WebCore/platform/graphics/gstreamer/VideoSinkGStreamer.cpp", 233 "Source/WebCore/platform/graphics/gstreamer/WebKitWebSourceGStreamer.cpp", 234 "Source/WebCore/platform/audio/gstreamer/WebKitWebAudioSourceGStreamer.cpp", 235 "Source/WebCore/platform/network/soup/ProxyResolverSoup.cpp", 236 "Source/WebCore/platform/network/soup/ProxyResolverSoup.h"], 237 ["-readability/naming"]), 238 239 # For third-party Python code, keep only the following checks-- 240 # 241 # No tabs: to avoid having to set the SVN allow-tabs property. 242 # No trailing white space: since this is easy to correct. 243 # No carriage-return line endings: since this is easy to correct. 244 # 245 (["webkitpy/thirdparty/"], 246 ["-", 247 "+pep8/W191", # Tabs 248 "+pep8/W291", # Trailing white space 249 "+whitespace/carriage_return"]), 250 251 ([# glu's libtess is third-party code, and doesn't follow WebKit style. 252 "Source/ThirdParty/glu"], 253 ["-readability", 254 "-whitespace", 255 "-build/header_guard", 256 "-build/include_order"]), 257 258 ([# There is no way to avoid the symbols __jit_debug_register_code 259 # and __jit_debug_descriptor when integrating with gdb. 260 "Source/JavaScriptCore/jit/GDBInterface.cpp"], 261 ["-readability/naming"]), 262 263 ([# On some systems the trailing CR is causing parser failure. 264 "Source/JavaScriptCore/parser/Keywords.table"], 265 ["+whitespace/carriage_return"]), 266 267 ([# Jinja templates: files have .cpp or .h extensions, but contain 268 # template code, which can't be handled, so disable tests. 269 "Source/bindings/templates", 270 "Source/core/scripts/templates"], 271 ["-"]), 272 ] 273 274 275 _CPP_FILE_EXTENSIONS = [ 276 'c', 277 'cpp', 278 'h', 279 ] 280 281 _JSON_FILE_EXTENSION = 'json' 282 283 _PYTHON_FILE_EXTENSION = 'py' 284 285 _TEXT_FILE_EXTENSIONS = [ 286 'ac', 287 'cc', 288 'cgi', 289 'css', 290 'exp', 291 'flex', 292 'gyp', 293 'gypi', 294 'html', 295 'idl', 296 'in', 297 'js', 298 'mm', 299 'php', 300 'pl', 301 'pm', 302 'pri', 303 'pro', 304 'rb', 305 'sh', 306 'table', 307 'txt', 308 'wm', 309 'xhtml', 310 'y', 311 ] 312 313 _XCODEPROJ_FILE_EXTENSION = 'pbxproj' 314 315 _XML_FILE_EXTENSIONS = [ 316 'vcproj', 317 'vsprops', 318 ] 319 320 _PNG_FILE_EXTENSION = 'png' 321 322 _CMAKE_FILE_EXTENSION = 'cmake' 323 324 # Files to skip that are less obvious. 325 # 326 # Some files should be skipped when checking style. For example, 327 # WebKit maintains some files in Mozilla style on purpose to ease 328 # future merges. 329 _SKIPPED_FILES_WITH_WARNING = [ 330 "Source/WebKit/gtk/tests/", 331 # All WebKit*.h files in Source/WebKit2/UIProcess/API/gtk, 332 # except those ending in ...Private.h are GTK+ API headers, 333 # which differ greatly from WebKit coding style. 334 re.compile(r'Source/WebKit2/UIProcess/API/gtk/WebKit(?!.*Private\.h).*\.h$'), 335 re.compile(r'Source/WebKit2/WebProcess/InjectedBundle/API/gtk/WebKit(?!.*Private\.h).*\.h$'), 336 'Source/WebKit2/UIProcess/API/gtk/webkit2.h', 337 'Source/WebKit2/WebProcess/InjectedBundle/API/gtk/webkit-web-extension.h'] 338 339 # Files to skip that are more common or obvious. 340 # 341 # This list should be in addition to files with FileType.NONE. Files 342 # with FileType.NONE are automatically skipped without warning. 343 _SKIPPED_FILES_WITHOUT_WARNING = [ 344 "LayoutTests" + os.path.sep, 345 "Source/ThirdParty/leveldb" + os.path.sep, 346 # Prevents this being recognized as a text file. 347 "Source/WebCore/GNUmakefile.features.am.in", 348 ] 349 350 # Extensions of files which are allowed to contain carriage returns. 351 _CARRIAGE_RETURN_ALLOWED_FILE_EXTENSIONS = [ 352 'png', 353 'vcproj', 354 'vsprops', 355 ] 356 357 # The maximum number of errors to report per file, per category. 358 # If a category is not a key, then it has no maximum. 359 _MAX_REPORTS_PER_CATEGORY = { 360 "whitespace/carriage_return": 1 361 } 362 363 364 def _all_categories(): 365 """Return the set of all categories used by check-webkit-style.""" 366 # Take the union across all checkers. 367 categories = CommonCategories.union(CppChecker.categories) 368 categories = categories.union(JSONChecker.categories) 369 categories = categories.union(TestExpectationsChecker.categories) 370 categories = categories.union(PNGChecker.categories) 371 372 # FIXME: Consider adding all of the pep8 categories. Since they 373 # are not too meaningful for documentation purposes, for 374 # now we add only the categories needed for the unit tests 375 # (which validate the consistency of the configuration 376 # settings against the known categories, etc). 377 categories = categories.union(["pep8/W191", "pep8/W291", "pep8/E501"]) 378 379 return categories 380 381 382 def _check_webkit_style_defaults(): 383 """Return the default command-line options for check-webkit-style.""" 384 return DefaultCommandOptionValues(min_confidence=_DEFAULT_MIN_CONFIDENCE, 385 output_format=_DEFAULT_OUTPUT_FORMAT) 386 387 388 # This function assists in optparser not having to import from checker. 389 def check_webkit_style_parser(): 390 all_categories = _all_categories() 391 default_options = _check_webkit_style_defaults() 392 return ArgumentParser(all_categories=all_categories, 393 base_filter_rules=_BASE_FILTER_RULES, 394 default_options=default_options) 395 396 397 def check_webkit_style_configuration(options): 398 """Return a StyleProcessorConfiguration instance for check-webkit-style. 399 400 Args: 401 options: A CommandOptionValues instance. 402 403 """ 404 filter_configuration = FilterConfiguration( 405 base_rules=_BASE_FILTER_RULES, 406 path_specific=_PATH_RULES_SPECIFIER, 407 user_rules=options.filter_rules) 408 409 return StyleProcessorConfiguration(filter_configuration=filter_configuration, 410 max_reports_per_category=_MAX_REPORTS_PER_CATEGORY, 411 min_confidence=options.min_confidence, 412 output_format=options.output_format, 413 stderr_write=sys.stderr.write) 414 415 416 def _create_log_handlers(stream): 417 """Create and return a default list of logging.Handler instances. 418 419 Format WARNING messages and above to display the logging level, and 420 messages strictly below WARNING not to display it. 421 422 Args: 423 stream: See the configure_logging() docstring. 424 425 """ 426 # Handles logging.WARNING and above. 427 error_handler = logging.StreamHandler(stream) 428 error_handler.setLevel(logging.WARNING) 429 formatter = logging.Formatter("%(levelname)s: %(message)s") 430 error_handler.setFormatter(formatter) 431 432 # Create a logging.Filter instance that only accepts messages 433 # below WARNING (i.e. filters out anything WARNING or above). 434 non_error_filter = logging.Filter() 435 # The filter method accepts a logging.LogRecord instance. 436 non_error_filter.filter = lambda record: record.levelno < logging.WARNING 437 438 non_error_handler = logging.StreamHandler(stream) 439 non_error_handler.addFilter(non_error_filter) 440 formatter = logging.Formatter("%(message)s") 441 non_error_handler.setFormatter(formatter) 442 443 return [error_handler, non_error_handler] 444 445 446 def _create_debug_log_handlers(stream): 447 """Create and return a list of logging.Handler instances for debugging. 448 449 Args: 450 stream: See the configure_logging() docstring. 451 452 """ 453 handler = logging.StreamHandler(stream) 454 formatter = logging.Formatter("%(name)s: %(levelname)-8s %(message)s") 455 handler.setFormatter(formatter) 456 457 return [handler] 458 459 460 def configure_logging(stream, logger=None, is_verbose=False): 461 """Configure logging, and return the list of handlers added. 462 463 Returns: 464 A list of references to the logging handlers added to the root 465 logger. This allows the caller to later remove the handlers 466 using logger.removeHandler. This is useful primarily during unit 467 testing where the caller may want to configure logging temporarily 468 and then undo the configuring. 469 470 Args: 471 stream: A file-like object to which to log. The stream must 472 define an "encoding" data attribute, or else logging 473 raises an error. 474 logger: A logging.logger instance to configure. This parameter 475 should be used only in unit tests. Defaults to the 476 root logger. 477 is_verbose: A boolean value of whether logging should be verbose. 478 479 """ 480 # If the stream does not define an "encoding" data attribute, the 481 # logging module can throw an error like the following: 482 # 483 # Traceback (most recent call last): 484 # File "/System/Library/Frameworks/Python.framework/Versions/2.6/... 485 # lib/python2.6/logging/__init__.py", line 761, in emit 486 # self.stream.write(fs % msg.encode(self.stream.encoding)) 487 # LookupError: unknown encoding: unknown 488 if logger is None: 489 logger = logging.getLogger() 490 491 if is_verbose: 492 logging_level = logging.DEBUG 493 handlers = _create_debug_log_handlers(stream) 494 else: 495 logging_level = logging.INFO 496 handlers = _create_log_handlers(stream) 497 498 handlers = _configure_logging(logging_level=logging_level, logger=logger, 499 handlers=handlers) 500 501 return handlers 502 503 504 # Enum-like idiom 505 class FileType: 506 507 NONE = 0 # FileType.NONE evaluates to False. 508 # Alphabetize remaining types 509 # CHANGELOG = 1 510 CPP = 2 511 JSON = 3 512 PNG = 4 513 PYTHON = 5 514 TEXT = 6 515 # WATCHLIST = 7 516 XML = 8 517 XCODEPROJ = 9 518 CMAKE = 10 519 520 521 class CheckerDispatcher(object): 522 523 """Supports determining whether and how to check style, based on path.""" 524 525 def _file_extension(self, file_path): 526 """Return the file extension without the leading dot.""" 527 return os.path.splitext(file_path)[1].lstrip(".") 528 529 def _should_skip_file_path(self, file_path, skip_array_entry): 530 match = re.search("\s*png$", file_path) 531 if match: 532 return False 533 if isinstance(skip_array_entry, str): 534 if file_path.find(skip_array_entry) >= 0: 535 return True 536 elif skip_array_entry.match(file_path): 537 return True 538 return False 539 540 def should_skip_with_warning(self, file_path): 541 """Return whether the given file should be skipped with a warning.""" 542 for skipped_file in _SKIPPED_FILES_WITH_WARNING: 543 if self._should_skip_file_path(file_path, skipped_file): 544 return True 545 return False 546 547 def should_skip_without_warning(self, file_path): 548 """Return whether the given file should be skipped without a warning.""" 549 if not self._file_type(file_path): # FileType.NONE. 550 return True 551 # Since "LayoutTests" is in _SKIPPED_FILES_WITHOUT_WARNING, make 552 # an exception to prevent files like 'TestExpectations' from being skipped. 553 # 554 # FIXME: Figure out a good way to avoid having to add special logic 555 # for this special case. 556 basename = os.path.basename(file_path) 557 if basename == 'TestExpectations': 558 return False 559 for skipped_file in _SKIPPED_FILES_WITHOUT_WARNING: 560 if self._should_skip_file_path(file_path, skipped_file): 561 return True 562 return False 563 564 def should_check_and_strip_carriage_returns(self, file_path): 565 return self._file_extension(file_path) not in _CARRIAGE_RETURN_ALLOWED_FILE_EXTENSIONS 566 567 def _file_type(self, file_path): 568 """Return the file type corresponding to the given file.""" 569 file_extension = self._file_extension(file_path) 570 571 if (file_extension in _CPP_FILE_EXTENSIONS) or (file_path == '-'): 572 # FIXME: Do something about the comment below and the issue it 573 # raises since cpp_style already relies on the extension. 574 # 575 # Treat stdin as C++. Since the extension is unknown when 576 # reading from stdin, cpp_style tests should not rely on 577 # the extension. 578 return FileType.CPP 579 elif file_extension == _JSON_FILE_EXTENSION: 580 return FileType.JSON 581 elif file_extension == _PYTHON_FILE_EXTENSION: 582 return FileType.PYTHON 583 elif file_extension in _XML_FILE_EXTENSIONS: 584 return FileType.XML 585 elif file_extension == _XCODEPROJ_FILE_EXTENSION: 586 return FileType.XCODEPROJ 587 elif file_extension == _PNG_FILE_EXTENSION: 588 return FileType.PNG 589 elif ((file_extension == _CMAKE_FILE_EXTENSION) or os.path.basename(file_path) == 'CMakeLists.txt'): 590 return FileType.CMAKE 591 elif ((not file_extension and os.path.join("Tools", "Scripts") in file_path) or 592 file_extension in _TEXT_FILE_EXTENSIONS or os.path.basename(file_path) == 'TestExpectations'): 593 return FileType.TEXT 594 else: 595 return FileType.NONE 596 597 def _create_checker(self, file_type, file_path, handle_style_error, 598 min_confidence): 599 """Instantiate and return a style checker based on file type.""" 600 if file_type == FileType.NONE: 601 checker = None 602 elif file_type == FileType.CPP: 603 file_extension = self._file_extension(file_path) 604 checker = CppChecker(file_path, file_extension, 605 handle_style_error, min_confidence) 606 elif file_type == FileType.JSON: 607 checker = JSONChecker(file_path, handle_style_error) 608 elif file_type == FileType.PYTHON: 609 checker = PythonChecker(file_path, handle_style_error) 610 elif file_type == FileType.XML: 611 checker = XMLChecker(file_path, handle_style_error) 612 elif file_type == FileType.XCODEPROJ: 613 checker = XcodeProjectFileChecker(file_path, handle_style_error) 614 elif file_type == FileType.PNG: 615 checker = PNGChecker(file_path, handle_style_error) 616 elif file_type == FileType.CMAKE: 617 checker = CMakeChecker(file_path, handle_style_error) 618 elif file_type == FileType.TEXT: 619 basename = os.path.basename(file_path) 620 if basename == 'TestExpectations': 621 checker = TestExpectationsChecker(file_path, handle_style_error) 622 else: 623 checker = TextChecker(file_path, handle_style_error) 624 else: 625 raise ValueError('Invalid file type "%(file_type)s": the only valid file types ' 626 "are %(NONE)s, %(CPP)s, and %(TEXT)s." 627 % {"file_type": file_type, 628 "NONE": FileType.NONE, 629 "CPP": FileType.CPP, 630 "TEXT": FileType.TEXT}) 631 632 return checker 633 634 def dispatch(self, file_path, handle_style_error, min_confidence): 635 """Instantiate and return a style checker based on file path.""" 636 file_type = self._file_type(file_path) 637 638 checker = self._create_checker(file_type, 639 file_path, 640 handle_style_error, 641 min_confidence) 642 return checker 643 644 645 # FIXME: Remove the stderr_write attribute from this class and replace 646 # its use with calls to a logging module logger. 647 class StyleProcessorConfiguration(object): 648 649 """Stores configuration values for the StyleProcessor class. 650 651 Attributes: 652 min_confidence: An integer between 1 and 5 inclusive that is the 653 minimum confidence level of style errors to report. 654 655 max_reports_per_category: The maximum number of errors to report 656 per category, per file. 657 658 stderr_write: A function that takes a string as a parameter and 659 serves as stderr.write. 660 661 """ 662 663 def __init__(self, 664 filter_configuration, 665 max_reports_per_category, 666 min_confidence, 667 output_format, 668 stderr_write): 669 """Create a StyleProcessorConfiguration instance. 670 671 Args: 672 filter_configuration: A FilterConfiguration instance. The default 673 is the "empty" filter configuration, which 674 means that all errors should be checked. 675 676 max_reports_per_category: The maximum number of errors to report 677 per category, per file. 678 679 min_confidence: An integer between 1 and 5 inclusive that is the 680 minimum confidence level of style errors to report. 681 The default is 1, which reports all style errors. 682 683 output_format: A string that is the output format. The supported 684 output formats are "emacs" which emacs can parse 685 and "vs7" which Microsoft Visual Studio 7 can parse. 686 687 stderr_write: A function that takes a string as a parameter and 688 serves as stderr.write. 689 690 """ 691 self._filter_configuration = filter_configuration 692 self._output_format = output_format 693 694 self.max_reports_per_category = max_reports_per_category 695 self.min_confidence = min_confidence 696 self.stderr_write = stderr_write 697 698 def is_reportable(self, category, confidence_in_error, file_path): 699 """Return whether an error is reportable. 700 701 An error is reportable if both the confidence in the error is 702 at least the minimum confidence level and the current filter 703 says the category should be checked for the given path. 704 705 Args: 706 category: A string that is a style category. 707 confidence_in_error: An integer between 1 and 5 inclusive that is 708 the application's confidence in the error. 709 A higher number means greater confidence. 710 file_path: The path of the file being checked 711 712 """ 713 if confidence_in_error < self.min_confidence: 714 return False 715 716 return self._filter_configuration.should_check(category, file_path) 717 718 def write_style_error(self, 719 category, 720 confidence_in_error, 721 file_path, 722 line_number, 723 message): 724 """Write a style error to the configured stderr.""" 725 if self._output_format == 'vs7': 726 format_string = "%s(%s): %s [%s] [%d]\n" 727 else: 728 format_string = "%s:%s: %s [%s] [%d]\n" 729 730 self.stderr_write(format_string % (file_path, 731 line_number, 732 message, 733 category, 734 confidence_in_error)) 735 736 737 class ProcessorBase(object): 738 739 """The base class for processors of lists of lines.""" 740 741 def should_process(self, file_path): 742 """Return whether the file at file_path should be processed. 743 744 The TextFileReader class calls this method prior to reading in 745 the lines of a file. Use this method, for example, to prevent 746 the style checker from reading binary files into memory. 747 748 """ 749 raise NotImplementedError('Subclasses should implement.') 750 751 def process(self, lines, file_path, **kwargs): 752 """Process lines of text read from a file. 753 754 Args: 755 lines: A list of lines of text to process. 756 file_path: The path from which the lines were read. 757 **kwargs: This argument signifies that the process() method of 758 subclasses of ProcessorBase may support additional 759 keyword arguments. 760 For example, a style checker's check() method 761 may support a "reportable_lines" parameter that represents 762 the line numbers of the lines for which style errors 763 should be reported. 764 765 """ 766 raise NotImplementedError('Subclasses should implement.') 767 768 769 class StyleProcessor(ProcessorBase): 770 771 """A ProcessorBase for checking style. 772 773 Attributes: 774 error_count: An integer that is the total number of reported 775 errors for the lifetime of this instance. 776 777 """ 778 779 def __init__(self, configuration, mock_dispatcher=None, 780 mock_increment_error_count=None, 781 mock_carriage_checker_class=None): 782 """Create an instance. 783 784 Args: 785 configuration: A StyleProcessorConfiguration instance. 786 mock_dispatcher: A mock CheckerDispatcher instance. This 787 parameter is for unit testing. Defaults to a 788 CheckerDispatcher instance. 789 mock_increment_error_count: A mock error-count incrementer. 790 mock_carriage_checker_class: A mock class for checking and 791 transforming carriage returns. 792 This parameter is for unit testing. 793 Defaults to CarriageReturnChecker. 794 795 """ 796 if mock_dispatcher is None: 797 dispatcher = CheckerDispatcher() 798 else: 799 dispatcher = mock_dispatcher 800 801 if mock_increment_error_count is None: 802 # The following blank line is present to avoid flagging by pep8.py. 803 804 def increment_error_count(): 805 """Increment the total count of reported errors.""" 806 self.error_count += 1 807 else: 808 increment_error_count = mock_increment_error_count 809 810 if mock_carriage_checker_class is None: 811 # This needs to be a class rather than an instance since the 812 # process() method instantiates one using parameters. 813 carriage_checker_class = CarriageReturnChecker 814 else: 815 carriage_checker_class = mock_carriage_checker_class 816 817 self.error_count = 0 818 819 self._carriage_checker_class = carriage_checker_class 820 self._configuration = configuration 821 self._dispatcher = dispatcher 822 self._increment_error_count = increment_error_count 823 824 def should_process(self, file_path): 825 """Return whether the file should be checked for style.""" 826 if self._dispatcher.should_skip_without_warning(file_path): 827 return False 828 if self._dispatcher.should_skip_with_warning(file_path): 829 _log.warn('File exempt from style guide. Skipping: "%s"' 830 % file_path) 831 return False 832 return True 833 834 def process(self, lines, file_path, line_numbers=None): 835 """Check the given lines for style. 836 837 Arguments: 838 lines: A list of all lines in the file to check. 839 file_path: The path of the file to process. If possible, the path 840 should be relative to the source root. Otherwise, 841 path-specific logic may not behave as expected. 842 line_numbers: A list of line numbers of the lines for which 843 style errors should be reported, or None if errors 844 for all lines should be reported. When not None, this 845 list normally contains the line numbers corresponding 846 to the modified lines of a patch. 847 848 """ 849 _log.debug("Checking style: " + file_path) 850 851 style_error_handler = DefaultStyleErrorHandler( 852 configuration=self._configuration, 853 file_path=file_path, 854 increment_error_count=self._increment_error_count, 855 line_numbers=line_numbers) 856 857 carriage_checker = self._carriage_checker_class(style_error_handler) 858 859 # Check for and remove trailing carriage returns ("\r"). 860 if self._dispatcher.should_check_and_strip_carriage_returns(file_path): 861 lines = carriage_checker.check(lines) 862 863 min_confidence = self._configuration.min_confidence 864 checker = self._dispatcher.dispatch(file_path, 865 style_error_handler, 866 min_confidence) 867 868 if checker is None: 869 raise AssertionError("File should not be checked: '%s'" % file_path) 870 871 _log.debug("Using class: " + checker.__class__.__name__) 872 873 checker.check(lines) 874