1 # Copyright (C) 2009 Google Inc. All rights reserved. 2 # Copyright (C) 2010 Chris Jerdonek (chris.jerdonek (at] gmail.com) 3 # 4 # Redistribution and use in source and binary forms, with or without 5 # modification, are permitted provided that the following conditions are 6 # met: 7 # 8 # * Redistributions of source code must retain the above copyright 9 # notice, this list of conditions and the following disclaimer. 10 # * Redistributions in binary form must reproduce the above 11 # copyright notice, this list of conditions and the following disclaimer 12 # in the documentation and/or other materials provided with the 13 # distribution. 14 # * Neither the name of Google Inc. nor the names of its 15 # contributors may be used to endorse or promote products derived from 16 # this software without specific prior written permission. 17 # 18 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 30 """Front end of some style-checker modules.""" 31 32 import codecs 33 import getopt 34 import os.path 35 import sys 36 37 from .. style_references import parse_patch 38 from error_handlers import DefaultStyleErrorHandler 39 from error_handlers import PatchStyleErrorHandler 40 from filter import validate_filter_rules 41 from filter import FilterConfiguration 42 from processors.common import check_no_carriage_return 43 from processors.common import categories as CommonCategories 44 from processors.cpp import CppProcessor 45 from processors.text import TextProcessor 46 47 48 # These defaults are used by check-webkit-style. 49 WEBKIT_DEFAULT_VERBOSITY = 1 50 WEBKIT_DEFAULT_OUTPUT_FORMAT = 'emacs' 51 52 53 # FIXME: For style categories we will never want to have, remove them. 54 # For categories for which we want to have similar functionality, 55 # modify the implementation and enable them. 56 # 57 # Throughout this module, we use "filter rule" rather than "filter" 58 # for an individual boolean filter flag like "+foo". This allows us to 59 # reserve "filter" for what one gets by collectively applying all of 60 # the filter rules. 61 # 62 # The _WEBKIT_FILTER_RULES are prepended to any user-specified filter 63 # rules. Since by default all errors are on, only include rules that 64 # begin with a - sign. 65 WEBKIT_DEFAULT_FILTER_RULES = [ 66 '-build/endif_comment', 67 '-build/include_what_you_use', # <string> for std::string 68 '-build/storage_class', # const static 69 '-legal/copyright', 70 '-readability/multiline_comment', 71 '-readability/braces', # int foo() {}; 72 '-readability/fn_size', 73 '-readability/casting', 74 '-readability/function', 75 '-runtime/arrays', # variable length array 76 '-runtime/casting', 77 '-runtime/sizeof', 78 '-runtime/explicit', # explicit 79 '-runtime/virtual', # virtual dtor 80 '-runtime/printf', 81 '-runtime/threadsafe_fn', 82 '-runtime/rtti', 83 '-whitespace/blank_line', 84 '-whitespace/end_of_line', 85 '-whitespace/labels', 86 ] 87 88 89 # FIXME: Change the second value of each tuple from a tuple to a list, 90 # and alter the filter code so it accepts lists instead. (The 91 # filter code will need to convert incoming values from a list 92 # to a tuple prior to caching). This will make this 93 # configuration setting a bit simpler since tuples have an 94 # unusual syntax case. 95 # 96 # The path-specific filter rules. 97 # 98 # This list is order sensitive. Only the first path substring match 99 # is used. See the FilterConfiguration documentation in filter.py 100 # for more information on this list. 101 _PATH_RULES_SPECIFIER = [ 102 # Files in these directories are consumers of the WebKit 103 # API and therefore do not follow the same header including 104 # discipline as WebCore. 105 (["WebKitTools/WebKitAPITest/", 106 "WebKit/qt/QGVLauncher/"], 107 ("-build/include", 108 "-readability/streams")), 109 ([# The GTK+ APIs use GTK+ naming style, which includes 110 # lower-cased, underscore-separated values. 111 "WebKit/gtk/webkit/", 112 # There is no clean way to avoid "yy_*" names used by flex. 113 "WebCore/css/CSSParser.cpp", 114 # There is no clean way to avoid "xxx_data" methods inside 115 # Qt's autotests since they are called automatically by the 116 # QtTest module. 117 "WebKit/qt/tests/", 118 "JavaScriptCore/qt/tests"], 119 ("-readability/naming",)), 120 # These are test file patterns. 121 (["_test.cpp", 122 "_unittest.cpp", 123 "_regtest.cpp"], 124 ("-readability/streams", # Many unit tests use cout. 125 "-runtime/rtti")), 126 ] 127 128 129 # Some files should be skipped when checking style. For example, 130 # WebKit maintains some files in Mozilla style on purpose to ease 131 # future merges. 132 # 133 # Include a warning for skipped files that are less obvious. 134 SKIPPED_FILES_WITH_WARNING = [ 135 # The Qt API and tests do not follow WebKit style. 136 # They follow Qt style. :) 137 "gtk2drawing.c", # WebCore/platform/gtk/gtk2drawing.c 138 "gtk2drawing.h", # WebCore/platform/gtk/gtk2drawing.h 139 "JavaScriptCore/qt/api/", 140 "WebKit/gtk/tests/", 141 "WebKit/qt/Api/", 142 "WebKit/qt/tests/", 143 ] 144 145 146 # Don't include a warning for skipped files that are more common 147 # and more obvious. 148 SKIPPED_FILES_WITHOUT_WARNING = [ 149 "LayoutTests/" 150 ] 151 152 153 # The maximum number of errors to report per file, per category. 154 # If a category is not a key, then it has no maximum. 155 MAX_REPORTS_PER_CATEGORY = { 156 "whitespace/carriage_return": 1 157 } 158 159 160 def style_categories(): 161 """Return the set of all categories used by check-webkit-style.""" 162 # Take the union across all processors. 163 return CommonCategories.union(CppProcessor.categories) 164 165 166 def webkit_argument_defaults(): 167 """Return the DefaultArguments instance for use by check-webkit-style.""" 168 return ArgumentDefaults(WEBKIT_DEFAULT_OUTPUT_FORMAT, 169 WEBKIT_DEFAULT_VERBOSITY, 170 WEBKIT_DEFAULT_FILTER_RULES) 171 172 173 def _create_usage(defaults): 174 """Return the usage string to display for command help. 175 176 Args: 177 defaults: An ArgumentDefaults instance. 178 179 """ 180 usage = """ 181 Syntax: %(program_name)s [--verbose=#] [--git-commit=<SingleCommit>] [--output=vs7] 182 [--filter=-x,+y,...] [file] ... 183 184 The style guidelines this tries to follow are here: 185 http://webkit.org/coding/coding-style.html 186 187 Every style error is given a confidence score from 1-5, with 5 meaning 188 we are certain of the problem, and 1 meaning it could be a legitimate 189 construct. This can miss some errors and does not substitute for 190 code review. 191 192 To prevent specific lines from being linted, add a '// NOLINT' comment to the 193 end of the line. 194 195 Linted extensions are .cpp, .c and .h. Other file types are ignored. 196 197 The file parameter is optional and accepts multiple files. Leaving 198 out the file parameter applies the check to all files considered changed 199 by your source control management system. 200 201 Flags: 202 203 verbose=# 204 A number 1-5 that restricts output to errors with a confidence 205 score at or above this value. In particular, the value 1 displays 206 all errors. The default is %(default_verbosity)s. 207 208 git-commit=<SingleCommit> 209 Checks the style of everything from the given commit to the local tree. 210 211 output=vs7 212 The output format, which may be one of 213 emacs : to ease emacs parsing 214 vs7 : compatible with Visual Studio 215 Defaults to "%(default_output_format)s". Other formats are unsupported. 216 217 filter=-x,+y,... 218 A comma-separated list of boolean filter rules used to filter 219 which categories of style guidelines to check. The script checks 220 a category if the category passes the filter rules, as follows. 221 222 Any webkit category starts out passing. All filter rules are then 223 evaluated left to right, with later rules taking precedence. For 224 example, the rule "+foo" passes any category that starts with "foo", 225 and "-foo" fails any such category. The filter input "-whitespace, 226 +whitespace/braces" fails the category "whitespace/tab" and passes 227 "whitespace/braces". 228 229 Examples: --filter=-whitespace,+whitespace/braces 230 --filter=-whitespace,-runtime/printf,+runtime/printf_format 231 --filter=-,+build/include_what_you_use 232 233 Category names appear in error messages in brackets, for example 234 [whitespace/indent]. To see a list of all categories available to 235 %(program_name)s, along with which are enabled by default, pass 236 the empty filter as follows: 237 --filter= 238 """ % {'program_name': os.path.basename(sys.argv[0]), 239 'default_verbosity': defaults.verbosity, 240 'default_output_format': defaults.output_format} 241 242 return usage 243 244 245 # FIXME: Eliminate support for "extra_flag_values". 246 # 247 # FIXME: Remove everything from ProcessorOptions except for the 248 # information that can be passed via the command line, and 249 # rename to something like CheckWebKitStyleOptions. This 250 # includes, but is not limited to, removing the 251 # max_reports_per_error attribute and the is_reportable() 252 # method. See also the FIXME below to create a new class 253 # called something like CheckerConfiguration. 254 # 255 # This class should not have knowledge of the flag key names. 256 class ProcessorOptions(object): 257 258 """A container to store options passed via the command line. 259 260 Attributes: 261 extra_flag_values: A string-string dictionary of all flag key-value 262 pairs that are not otherwise represented by this 263 class. The default is the empty dictionary. 264 265 filter_configuration: A FilterConfiguration instance. The default 266 is the "empty" filter configuration, which 267 means that all errors should be checked. 268 269 git_commit: A string representing the git commit to check. 270 The default is None. 271 272 max_reports_per_error: The maximum number of errors to report 273 per file, per category. 274 275 output_format: A string that is the output format. The supported 276 output formats are "emacs" which emacs can parse 277 and "vs7" which Microsoft Visual Studio 7 can parse. 278 279 verbosity: An integer between 1-5 inclusive that restricts output 280 to errors with a confidence score at or above this value. 281 The default is 1, which reports all errors. 282 283 """ 284 def __init__(self, 285 extra_flag_values=None, 286 filter_configuration = None, 287 git_commit=None, 288 max_reports_per_category=None, 289 output_format="emacs", 290 verbosity=1): 291 if extra_flag_values is None: 292 extra_flag_values = {} 293 if filter_configuration is None: 294 filter_configuration = FilterConfiguration() 295 if max_reports_per_category is None: 296 max_reports_per_category = {} 297 298 if output_format not in ("emacs", "vs7"): 299 raise ValueError('Invalid "output_format" parameter: ' 300 'value must be "emacs" or "vs7". ' 301 'Value given: "%s".' % output_format) 302 303 if (verbosity < 1) or (verbosity > 5): 304 raise ValueError('Invalid "verbosity" parameter: ' 305 "value must be an integer between 1-5 inclusive. " 306 'Value given: "%s".' % verbosity) 307 308 self.extra_flag_values = extra_flag_values 309 self.filter_configuration = filter_configuration 310 self.git_commit = git_commit 311 self.max_reports_per_category = max_reports_per_category 312 self.output_format = output_format 313 self.verbosity = verbosity 314 315 # Useful for unit testing. 316 def __eq__(self, other): 317 """Return whether this ProcessorOptions instance is equal to another.""" 318 if self.extra_flag_values != other.extra_flag_values: 319 return False 320 if self.filter_configuration != other.filter_configuration: 321 return False 322 if self.git_commit != other.git_commit: 323 return False 324 if self.max_reports_per_category != other.max_reports_per_category: 325 return False 326 if self.output_format != other.output_format: 327 return False 328 if self.verbosity != other.verbosity: 329 return False 330 331 return True 332 333 # Useful for unit testing. 334 def __ne__(self, other): 335 # Python does not automatically deduce this from __eq__(). 336 return not self.__eq__(other) 337 338 def is_reportable(self, category, confidence_in_error, path): 339 """Return whether an error is reportable. 340 341 An error is reportable if the confidence in the error 342 is at least the current verbosity level, and if the current 343 filter says that the category should be checked for the 344 given path. 345 346 Args: 347 category: A string that is a style category. 348 confidence_in_error: An integer between 1 and 5, inclusive, that 349 represents the application's confidence in 350 the error. A higher number signifies greater 351 confidence. 352 path: The path of the file being checked 353 354 """ 355 if confidence_in_error < self.verbosity: 356 return False 357 358 return self.filter_configuration.should_check(category, path) 359 360 361 # This class should not have knowledge of the flag key names. 362 class ArgumentDefaults(object): 363 364 """A container to store default argument values. 365 366 Attributes: 367 output_format: A string that is the default output format. 368 verbosity: An integer that is the default verbosity level. 369 base_filter_rules: A list of strings that are boolean filter rules 370 to prepend to any user-specified rules. 371 372 """ 373 374 def __init__(self, default_output_format, default_verbosity, 375 default_base_filter_rules): 376 self.output_format = default_output_format 377 self.verbosity = default_verbosity 378 self.base_filter_rules = default_base_filter_rules 379 380 381 class ArgumentPrinter(object): 382 383 """Supports the printing of check-webkit-style command arguments.""" 384 385 def _flag_pair_to_string(self, flag_key, flag_value): 386 return '--%(key)s=%(val)s' % {'key': flag_key, 'val': flag_value } 387 388 def to_flag_string(self, options): 389 """Return a flag string yielding the given ProcessorOptions instance. 390 391 This method orders the flag values alphabetically by the flag key. 392 393 Args: 394 options: A ProcessorOptions instance. 395 396 """ 397 flags = options.extra_flag_values.copy() 398 399 flags['output'] = options.output_format 400 flags['verbose'] = options.verbosity 401 # Only include the filter flag if user-provided rules are present. 402 user_rules = options.filter_configuration.user_rules 403 if user_rules: 404 flags['filter'] = ",".join(user_rules) 405 if options.git_commit: 406 flags['git-commit'] = options.git_commit 407 408 flag_string = '' 409 # Alphabetizing lets us unit test this method. 410 for key in sorted(flags.keys()): 411 flag_string += self._flag_pair_to_string(key, flags[key]) + ' ' 412 413 return flag_string.strip() 414 415 416 class ArgumentParser(object): 417 418 """Supports the parsing of check-webkit-style command arguments. 419 420 Attributes: 421 defaults: An ArgumentDefaults instance. 422 create_usage: A function that accepts an ArgumentDefaults instance 423 and returns a string of usage instructions. 424 This defaults to the function used to generate the 425 usage string for check-webkit-style. 426 doc_print: A function that accepts a string parameter and that is 427 called to display help messages. This defaults to 428 sys.stderr.write(). 429 430 """ 431 432 def __init__(self, argument_defaults, create_usage=None, doc_print=None): 433 if create_usage is None: 434 create_usage = _create_usage 435 if doc_print is None: 436 doc_print = sys.stderr.write 437 438 self.defaults = argument_defaults 439 self.create_usage = create_usage 440 self.doc_print = doc_print 441 442 def _exit_with_usage(self, error_message=''): 443 """Exit and print a usage string with an optional error message. 444 445 Args: 446 error_message: A string that is an error message to print. 447 448 """ 449 usage = self.create_usage(self.defaults) 450 self.doc_print(usage) 451 if error_message: 452 sys.exit('\nFATAL ERROR: ' + error_message) 453 else: 454 sys.exit(1) 455 456 def _exit_with_categories(self): 457 """Exit and print the style categories and default filter rules.""" 458 self.doc_print('\nAll categories:\n') 459 categories = style_categories() 460 for category in sorted(categories): 461 self.doc_print(' ' + category + '\n') 462 463 self.doc_print('\nDefault filter rules**:\n') 464 for filter_rule in sorted(self.defaults.base_filter_rules): 465 self.doc_print(' ' + filter_rule + '\n') 466 self.doc_print('\n**The command always evaluates the above rules, ' 467 'and before any --filter flag.\n\n') 468 469 sys.exit(0) 470 471 def _parse_filter_flag(self, flag_value): 472 """Parse the --filter flag, and return a list of filter rules. 473 474 Args: 475 flag_value: A string of comma-separated filter rules, for 476 example "-whitespace,+whitespace/indent". 477 478 """ 479 filters = [] 480 for uncleaned_filter in flag_value.split(','): 481 filter = uncleaned_filter.strip() 482 if not filter: 483 continue 484 filters.append(filter) 485 return filters 486 487 def parse(self, args, extra_flags=None): 488 """Parse the command line arguments to check-webkit-style. 489 490 Args: 491 args: A list of command-line arguments as returned by sys.argv[1:]. 492 extra_flags: A list of flags whose values we want to extract, but 493 are not supported by the ProcessorOptions class. 494 An example flag "new_flag=". This defaults to the 495 empty list. 496 497 Returns: 498 A tuple of (filenames, options) 499 500 filenames: The list of filenames to check. 501 options: A ProcessorOptions instance. 502 503 """ 504 if extra_flags is None: 505 extra_flags = [] 506 507 output_format = self.defaults.output_format 508 verbosity = self.defaults.verbosity 509 base_rules = self.defaults.base_filter_rules 510 511 # The flags already supported by the ProcessorOptions class. 512 flags = ['help', 'output=', 'verbose=', 'filter=', 'git-commit='] 513 514 for extra_flag in extra_flags: 515 if extra_flag in flags: 516 raise ValueError('Flag \'%(extra_flag)s is duplicated ' 517 'or already supported.' % 518 {'extra_flag': extra_flag}) 519 flags.append(extra_flag) 520 521 try: 522 (opts, filenames) = getopt.getopt(args, '', flags) 523 except getopt.GetoptError: 524 # FIXME: Settle on an error handling approach: come up 525 # with a consistent guideline as to when and whether 526 # a ValueError should be raised versus calling 527 # sys.exit when needing to interrupt execution. 528 self._exit_with_usage('Invalid arguments.') 529 530 extra_flag_values = {} 531 git_commit = None 532 user_rules = [] 533 534 for (opt, val) in opts: 535 if opt == '--help': 536 self._exit_with_usage() 537 elif opt == '--output': 538 output_format = val 539 elif opt == '--verbose': 540 verbosity = val 541 elif opt == '--git-commit': 542 git_commit = val 543 elif opt == '--filter': 544 if not val: 545 self._exit_with_categories() 546 # Prepend the defaults. 547 user_rules = self._parse_filter_flag(val) 548 else: 549 extra_flag_values[opt] = val 550 551 # Check validity of resulting values. 552 if filenames and (git_commit != None): 553 self._exit_with_usage('It is not possible to check files and a ' 554 'specific commit at the same time.') 555 556 if output_format not in ('emacs', 'vs7'): 557 raise ValueError('Invalid --output value "%s": The only ' 558 'allowed output formats are emacs and vs7.' % 559 output_format) 560 561 all_categories = style_categories() 562 validate_filter_rules(user_rules, all_categories) 563 564 verbosity = int(verbosity) 565 if (verbosity < 1) or (verbosity > 5): 566 raise ValueError('Invalid --verbose value %s: value must ' 567 'be between 1-5.' % verbosity) 568 569 filter_configuration = FilterConfiguration(base_rules=base_rules, 570 path_specific=_PATH_RULES_SPECIFIER, 571 user_rules=user_rules) 572 573 options = ProcessorOptions(extra_flag_values=extra_flag_values, 574 filter_configuration=filter_configuration, 575 git_commit=git_commit, 576 max_reports_per_category=MAX_REPORTS_PER_CATEGORY, 577 output_format=output_format, 578 verbosity=verbosity) 579 580 return (filenames, options) 581 582 583 # Enum-like idiom 584 class FileType: 585 586 NONE = 1 587 # Alphabetize remaining types 588 CPP = 2 589 TEXT = 3 590 591 592 class ProcessorDispatcher(object): 593 594 """Supports determining whether and how to check style, based on path.""" 595 596 cpp_file_extensions = ( 597 'c', 598 'cpp', 599 'h', 600 ) 601 602 text_file_extensions = ( 603 'css', 604 'html', 605 'idl', 606 'js', 607 'mm', 608 'php', 609 'pm', 610 'py', 611 'txt', 612 ) 613 614 def _file_extension(self, file_path): 615 """Return the file extension without the leading dot.""" 616 return os.path.splitext(file_path)[1].lstrip(".") 617 618 def should_skip_with_warning(self, file_path): 619 """Return whether the given file should be skipped with a warning.""" 620 for skipped_file in SKIPPED_FILES_WITH_WARNING: 621 if file_path.find(skipped_file) >= 0: 622 return True 623 return False 624 625 def should_skip_without_warning(self, file_path): 626 """Return whether the given file should be skipped without a warning.""" 627 for skipped_file in SKIPPED_FILES_WITHOUT_WARNING: 628 if file_path.find(skipped_file) >= 0: 629 return True 630 return False 631 632 def _file_type(self, file_path): 633 """Return the file type corresponding to the given file.""" 634 file_extension = self._file_extension(file_path) 635 636 if (file_extension in self.cpp_file_extensions) or (file_path == '-'): 637 # FIXME: Do something about the comment below and the issue it 638 # raises since cpp_style already relies on the extension. 639 # 640 # Treat stdin as C++. Since the extension is unknown when 641 # reading from stdin, cpp_style tests should not rely on 642 # the extension. 643 return FileType.CPP 644 elif ("ChangeLog" in file_path 645 or "WebKitTools/Scripts/" in file_path 646 or file_extension in self.text_file_extensions): 647 return FileType.TEXT 648 else: 649 return FileType.NONE 650 651 def _create_processor(self, file_type, file_path, handle_style_error, verbosity): 652 """Instantiate and return a style processor based on file type.""" 653 if file_type == FileType.NONE: 654 processor = None 655 elif file_type == FileType.CPP: 656 file_extension = self._file_extension(file_path) 657 processor = CppProcessor(file_path, file_extension, handle_style_error, verbosity) 658 elif file_type == FileType.TEXT: 659 processor = TextProcessor(file_path, handle_style_error) 660 else: 661 raise ValueError('Invalid file type "%(file_type)s": the only valid file types ' 662 "are %(NONE)s, %(CPP)s, and %(TEXT)s." 663 % {"file_type": file_type, 664 "NONE": FileType.NONE, 665 "CPP": FileType.CPP, 666 "TEXT": FileType.TEXT}) 667 668 return processor 669 670 def dispatch_processor(self, file_path, handle_style_error, verbosity): 671 """Instantiate and return a style processor based on file path.""" 672 file_type = self._file_type(file_path) 673 674 processor = self._create_processor(file_type, 675 file_path, 676 handle_style_error, 677 verbosity) 678 return processor 679 680 681 # FIXME: When creating the new CheckWebKitStyleOptions class as 682 # described in a FIXME above, add a new class here called 683 # something like CheckerConfiguration. The class should contain 684 # attributes for options needed to process a file. This includes 685 # a subset of the CheckWebKitStyleOptions attributes, a 686 # FilterConfiguration attribute, an stderr_write attribute, a 687 # max_reports_per_category attribute, etc. It can also include 688 # the is_reportable() method. The StyleChecker should accept 689 # an instance of this class rather than a ProcessorOptions 690 # instance. 691 692 693 class StyleChecker(object): 694 695 """Supports checking style in files and patches. 696 697 Attributes: 698 error_count: An integer that is the total number of reported 699 errors for the lifetime of this StyleChecker 700 instance. 701 options: A ProcessorOptions instance that controls the behavior 702 of style checking. 703 704 """ 705 706 def __init__(self, options, stderr_write=None): 707 """Create a StyleChecker instance. 708 709 Args: 710 options: See options attribute. 711 stderr_write: A function that takes a string as a parameter 712 and that is called when a style error occurs. 713 Defaults to sys.stderr.write. This should be 714 used only for unit tests. 715 716 """ 717 if stderr_write is None: 718 stderr_write = sys.stderr.write 719 720 self._stderr_write = stderr_write 721 self.error_count = 0 722 self.options = options 723 724 def _increment_error_count(self): 725 """Increment the total count of reported errors.""" 726 self.error_count += 1 727 728 def _process_file(self, processor, file_path, handle_style_error): 729 """Process the file using the given processor.""" 730 try: 731 # Support the UNIX convention of using "-" for stdin. Note that 732 # we are not opening the file with universal newline support 733 # (which codecs doesn't support anyway), so the resulting lines do 734 # contain trailing '\r' characters if we are reading a file that 735 # has CRLF endings. 736 # If after the split a trailing '\r' is present, it is removed 737 # below. If it is not expected to be present (i.e. os.linesep != 738 # '\r\n' as in Windows), a warning is issued below if this file 739 # is processed. 740 if file_path == '-': 741 file = codecs.StreamReaderWriter(sys.stdin, 742 codecs.getreader('utf8'), 743 codecs.getwriter('utf8'), 744 'replace') 745 else: 746 file = codecs.open(file_path, 'r', 'utf8', 'replace') 747 748 contents = file.read() 749 750 except IOError: 751 self._stderr_write("Skipping input '%s': Can't open for reading\n" % file_path) 752 return 753 754 lines = contents.split("\n") 755 756 for line_number in range(len(lines)): 757 # FIXME: We should probably use the SVN "eol-style" property 758 # or a white list to decide whether or not to do 759 # the carriage-return check. Originally, we did the 760 # check only if (os.linesep != '\r\n'). 761 # 762 # FIXME: As a minor optimization, we can have 763 # check_no_carriage_return() return whether 764 # the line ends with "\r". 765 check_no_carriage_return(lines[line_number], line_number, 766 handle_style_error) 767 if lines[line_number].endswith("\r"): 768 lines[line_number] = lines[line_number].rstrip("\r") 769 770 processor.process(lines) 771 772 def check_file(self, file_path, handle_style_error=None, process_file=None): 773 """Check style in the given file. 774 775 Args: 776 file_path: A string that is the path of the file to process. 777 handle_style_error: The function to call when a style error 778 occurs. This parameter is meant for internal 779 use within this class. Defaults to a 780 DefaultStyleErrorHandler instance. 781 process_file: The function to call to process the file. This 782 parameter should be used only for unit tests. 783 Defaults to the file processing method of this class. 784 785 """ 786 if handle_style_error is None: 787 handle_style_error = DefaultStyleErrorHandler(file_path, 788 self.options, 789 self._increment_error_count, 790 self._stderr_write) 791 if process_file is None: 792 process_file = self._process_file 793 794 dispatcher = ProcessorDispatcher() 795 796 if dispatcher.should_skip_without_warning(file_path): 797 return 798 if dispatcher.should_skip_with_warning(file_path): 799 self._stderr_write('Ignoring "%s": this file is exempt from the ' 800 "style guide.\n" % file_path) 801 return 802 803 verbosity = self.options.verbosity 804 processor = dispatcher.dispatch_processor(file_path, 805 handle_style_error, 806 verbosity) 807 if processor is None: 808 return 809 810 process_file(processor, file_path, handle_style_error) 811 812 def check_patch(self, patch_string): 813 """Check style in the given patch. 814 815 Args: 816 patch_string: A string that is a patch string. 817 818 """ 819 patch_files = parse_patch(patch_string) 820 for file_path, diff in patch_files.iteritems(): 821 style_error_handler = PatchStyleErrorHandler(diff, 822 file_path, 823 self.options, 824 self._increment_error_count, 825 self._stderr_write) 826 827 self.check_file(file_path, style_error_handler) 828 829