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