| OLD | NEW |
| (Empty) |
| 1 # Copyright (C) 2010 Chris Jerdonek (cjerdonek@webkit.org) | |
| 2 # | |
| 3 # Redistribution and use in source and binary forms, with or without | |
| 4 # modification, are permitted provided that the following conditions | |
| 5 # are met: | |
| 6 # 1. Redistributions of source code must retain the above copyright | |
| 7 # notice, this list of conditions and the following disclaimer. | |
| 8 # 2. Redistributions in binary form must reproduce the above copyright | |
| 9 # notice, this list of conditions and the following disclaimer in the | |
| 10 # documentation and/or other materials provided with the distribution. | |
| 11 # | |
| 12 # THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND ANY | |
| 13 # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | |
| 14 # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |
| 15 # DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY | |
| 16 # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | |
| 17 # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | |
| 18 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON | |
| 19 # ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 20 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | |
| 21 # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 22 | |
| 23 """Supports the parsing of command-line options for check-webkit-style.""" | |
| 24 | |
| 25 import logging | |
| 26 from optparse import OptionParser | |
| 27 import os.path | |
| 28 import sys | |
| 29 | |
| 30 from filter import validate_filter_rules | |
| 31 # This module should not import anything from checker.py. | |
| 32 | |
| 33 _log = logging.getLogger(__name__) | |
| 34 | |
| 35 _USAGE = """usage: %prog [--help] [options] [path1] [path2] ... | |
| 36 | |
| 37 Overview: | |
| 38 Check coding style according to WebKit style guidelines: | |
| 39 | |
| 40 http://webkit.org/coding/coding-style.html | |
| 41 | |
| 42 Path arguments can be files and directories. If neither a git commit nor | |
| 43 paths are passed, then all changes in your source control working directory | |
| 44 are checked. | |
| 45 | |
| 46 Style errors: | |
| 47 This script assigns to every style error a confidence score from 1-5 and | |
| 48 a category name. A confidence score of 5 means the error is certainly | |
| 49 a problem, and 1 means it could be fine. | |
| 50 | |
| 51 Category names appear in error messages in brackets, for example | |
| 52 [whitespace/indent]. See the options section below for an option that | |
| 53 displays all available categories and which are reported by default. | |
| 54 | |
| 55 Filters: | |
| 56 Use filters to configure what errors to report. Filters are specified using | |
| 57 a comma-separated list of boolean filter rules. The script reports errors | |
| 58 in a category if the category passes the filter, as described below. | |
| 59 | |
| 60 All categories start out passing. Boolean filter rules are then evaluated | |
| 61 from left to right, with later rules taking precedence. For example, the | |
| 62 rule "+foo" passes any category that starts with "foo", and "-foo" fails | |
| 63 any such category. The filter input "-whitespace,+whitespace/braces" fails | |
| 64 the category "whitespace/tab" and passes "whitespace/braces". | |
| 65 | |
| 66 Examples: --filter=-whitespace,+whitespace/braces | |
| 67 --filter=-whitespace,-runtime/printf,+runtime/printf_format | |
| 68 --filter=-,+build/include_what_you_use | |
| 69 | |
| 70 Paths: | |
| 71 Certain style-checking behavior depends on the paths relative to | |
| 72 the WebKit source root of the files being checked. For example, | |
| 73 certain types of errors may be handled differently for files in | |
| 74 WebKit/gtk/webkit/ (e.g. by suppressing "readability/naming" errors | |
| 75 for files in this directory). | |
| 76 | |
| 77 Consequently, if the path relative to the source root cannot be | |
| 78 determined for a file being checked, then style checking may not | |
| 79 work correctly for that file. This can occur, for example, if no | |
| 80 WebKit checkout can be found, or if the source root can be detected, | |
| 81 but one of the files being checked lies outside the source tree. | |
| 82 | |
| 83 If a WebKit checkout can be detected and all files being checked | |
| 84 are in the source tree, then all paths will automatically be | |
| 85 converted to paths relative to the source root prior to checking. | |
| 86 This is also useful for display purposes. | |
| 87 | |
| 88 Currently, this command can detect the source root only if the | |
| 89 command is run from within a WebKit checkout (i.e. if the current | |
| 90 working directory is below the root of a checkout). In particular, | |
| 91 it is not recommended to run this script from a directory outside | |
| 92 a checkout. | |
| 93 | |
| 94 Running this script from a top-level WebKit source directory and | |
| 95 checking only files in the source tree will ensure that all style | |
| 96 checking behaves correctly -- whether or not a checkout can be | |
| 97 detected. This is because all file paths will already be relative | |
| 98 to the source root and so will not need to be converted.""" | |
| 99 | |
| 100 _EPILOG = ("This script can miss errors and does not substitute for " | |
| 101 "code review.") | |
| 102 | |
| 103 | |
| 104 # This class should not have knowledge of the flag key names. | |
| 105 class DefaultCommandOptionValues(object): | |
| 106 | |
| 107 """Stores the default check-webkit-style command-line options. | |
| 108 | |
| 109 Attributes: | |
| 110 output_format: A string that is the default output format. | |
| 111 min_confidence: An integer that is the default minimum confidence level. | |
| 112 | |
| 113 """ | |
| 114 | |
| 115 def __init__(self, min_confidence, output_format): | |
| 116 self.min_confidence = min_confidence | |
| 117 self.output_format = output_format | |
| 118 | |
| 119 | |
| 120 # This class should not have knowledge of the flag key names. | |
| 121 class CommandOptionValues(object): | |
| 122 | |
| 123 """Stores the option values passed by the user via the command line. | |
| 124 | |
| 125 Attributes: | |
| 126 is_verbose: A boolean value of whether verbose logging is enabled. | |
| 127 | |
| 128 filter_rules: The list of filter rules provided by the user. | |
| 129 These rules are appended to the base rules and | |
| 130 path-specific rules and so take precedence over | |
| 131 the base filter rules, etc. | |
| 132 | |
| 133 git_commit: A string representing the git commit to check. | |
| 134 The default is None. | |
| 135 | |
| 136 min_confidence: An integer between 1 and 5 inclusive that is the | |
| 137 minimum confidence level of style errors to report. | |
| 138 The default is 1, which reports all errors. | |
| 139 | |
| 140 output_format: A string that is the output format. The supported | |
| 141 output formats are "emacs" which emacs can parse | |
| 142 and "vs7" which Microsoft Visual Studio 7 can parse. | |
| 143 | |
| 144 """ | |
| 145 def __init__(self, | |
| 146 filter_rules=None, | |
| 147 git_commit=None, | |
| 148 diff_files=None, | |
| 149 is_verbose=False, | |
| 150 min_confidence=1, | |
| 151 output_format="emacs"): | |
| 152 if filter_rules is None: | |
| 153 filter_rules = [] | |
| 154 | |
| 155 if (min_confidence < 1) or (min_confidence > 5): | |
| 156 raise ValueError('Invalid "min_confidence" parameter: value ' | |
| 157 "must be an integer between 1 and 5 inclusive. " | |
| 158 'Value given: "%s".' % min_confidence) | |
| 159 | |
| 160 if output_format not in ("emacs", "vs7"): | |
| 161 raise ValueError('Invalid "output_format" parameter: ' | |
| 162 'value must be "emacs" or "vs7". ' | |
| 163 'Value given: "%s".' % output_format) | |
| 164 | |
| 165 self.filter_rules = filter_rules | |
| 166 self.git_commit = git_commit | |
| 167 self.diff_files = diff_files | |
| 168 self.is_verbose = is_verbose | |
| 169 self.min_confidence = min_confidence | |
| 170 self.output_format = output_format | |
| 171 | |
| 172 # Useful for unit testing. | |
| 173 def __eq__(self, other): | |
| 174 """Return whether this instance is equal to another.""" | |
| 175 if self.filter_rules != other.filter_rules: | |
| 176 return False | |
| 177 if self.git_commit != other.git_commit: | |
| 178 return False | |
| 179 if self.diff_files != other.diff_files: | |
| 180 return False | |
| 181 if self.is_verbose != other.is_verbose: | |
| 182 return False | |
| 183 if self.min_confidence != other.min_confidence: | |
| 184 return False | |
| 185 if self.output_format != other.output_format: | |
| 186 return False | |
| 187 | |
| 188 return True | |
| 189 | |
| 190 # Useful for unit testing. | |
| 191 def __ne__(self, other): | |
| 192 # Python does not automatically deduce this from __eq__(). | |
| 193 return not self.__eq__(other) | |
| 194 | |
| 195 | |
| 196 class ArgumentPrinter(object): | |
| 197 | |
| 198 """Supports the printing of check-webkit-style command arguments.""" | |
| 199 | |
| 200 def _flag_pair_to_string(self, flag_key, flag_value): | |
| 201 return '--%(key)s=%(val)s' % {'key': flag_key, 'val': flag_value } | |
| 202 | |
| 203 def to_flag_string(self, options): | |
| 204 """Return a flag string of the given CommandOptionValues instance. | |
| 205 | |
| 206 This method orders the flag values alphabetically by the flag key. | |
| 207 | |
| 208 Args: | |
| 209 options: A CommandOptionValues instance. | |
| 210 | |
| 211 """ | |
| 212 flags = {} | |
| 213 flags['min-confidence'] = options.min_confidence | |
| 214 flags['output'] = options.output_format | |
| 215 # Only include the filter flag if user-provided rules are present. | |
| 216 filter_rules = options.filter_rules | |
| 217 if filter_rules: | |
| 218 flags['filter'] = ",".join(filter_rules) | |
| 219 if options.git_commit: | |
| 220 flags['git-commit'] = options.git_commit | |
| 221 if options.diff_files: | |
| 222 flags['diff_files'] = options.diff_files | |
| 223 | |
| 224 flag_string = '' | |
| 225 # Alphabetizing lets us unit test this method. | |
| 226 for key in sorted(flags.keys()): | |
| 227 flag_string += self._flag_pair_to_string(key, flags[key]) + ' ' | |
| 228 | |
| 229 return flag_string.strip() | |
| 230 | |
| 231 | |
| 232 class ArgumentParser(object): | |
| 233 | |
| 234 # FIXME: Move the documentation of the attributes to the __init__ | |
| 235 # docstring after making the attributes internal. | |
| 236 """Supports the parsing of check-webkit-style command arguments. | |
| 237 | |
| 238 Attributes: | |
| 239 create_usage: A function that accepts a DefaultCommandOptionValues | |
| 240 instance and returns a string of usage instructions. | |
| 241 Defaults to the function that generates the usage | |
| 242 string for check-webkit-style. | |
| 243 default_options: A DefaultCommandOptionValues instance that provides | |
| 244 the default values for options not explicitly | |
| 245 provided by the user. | |
| 246 stderr_write: A function that takes a string as a parameter and | |
| 247 serves as stderr.write. Defaults to sys.stderr.write. | |
| 248 This parameter should be specified only for unit tests. | |
| 249 | |
| 250 """ | |
| 251 | |
| 252 def __init__(self, | |
| 253 all_categories, | |
| 254 default_options, | |
| 255 base_filter_rules=None, | |
| 256 mock_stderr=None, | |
| 257 usage=None): | |
| 258 """Create an ArgumentParser instance. | |
| 259 | |
| 260 Args: | |
| 261 all_categories: The set of all available style categories. | |
| 262 default_options: See the corresponding attribute in the class | |
| 263 docstring. | |
| 264 Keyword Args: | |
| 265 base_filter_rules: The list of filter rules at the beginning of | |
| 266 the list of rules used to check style. This | |
| 267 list has the least precedence when checking | |
| 268 style and precedes any user-provided rules. | |
| 269 The class uses this parameter only for display | |
| 270 purposes to the user. Defaults to the empty list. | |
| 271 create_usage: See the documentation of the corresponding | |
| 272 attribute in the class docstring. | |
| 273 stderr_write: See the documentation of the corresponding | |
| 274 attribute in the class docstring. | |
| 275 | |
| 276 """ | |
| 277 if base_filter_rules is None: | |
| 278 base_filter_rules = [] | |
| 279 stderr = sys.stderr if mock_stderr is None else mock_stderr | |
| 280 if usage is None: | |
| 281 usage = _USAGE | |
| 282 | |
| 283 self._all_categories = all_categories | |
| 284 self._base_filter_rules = base_filter_rules | |
| 285 | |
| 286 # FIXME: Rename these to reflect that they are internal. | |
| 287 self.default_options = default_options | |
| 288 self.stderr_write = stderr.write | |
| 289 | |
| 290 self._parser = self._create_option_parser(stderr=stderr, | |
| 291 usage=usage, | |
| 292 default_min_confidence=self.default_options.min_confidence, | |
| 293 default_output_format=self.default_options.output_format) | |
| 294 | |
| 295 def _create_option_parser(self, stderr, usage, | |
| 296 default_min_confidence, default_output_format): | |
| 297 # Since the epilog string is short, it is not necessary to replace | |
| 298 # the epilog string with a mock epilog string when testing. | |
| 299 # For this reason, we use _EPILOG directly rather than passing it | |
| 300 # as an argument like we do for the usage string. | |
| 301 parser = OptionParser(usage=usage, epilog=_EPILOG) | |
| 302 | |
| 303 filter_help = ('set a filter to control what categories of style ' | |
| 304 'errors to report. Specify a filter using a comma-' | |
| 305 'delimited list of boolean filter rules, for example ' | |
| 306 '"--filter -whitespace,+whitespace/braces". To display ' | |
| 307 'all categories and which are enabled by default, pass ' | |
| 308 """no value (e.g. '-f ""' or '--filter=').""") | |
| 309 parser.add_option("-f", "--filter-rules", metavar="RULES", | |
| 310 dest="filter_value", help=filter_help) | |
| 311 | |
| 312 git_commit_help = ("check all changes in the given commit. " | |
| 313 "Use 'commit_id..' to check all changes after commmit
_id") | |
| 314 parser.add_option("-g", "--git-diff", "--git-commit", | |
| 315 metavar="COMMIT", dest="git_commit", help=git_commit_h
elp,) | |
| 316 | |
| 317 diff_files_help = "diff the files passed on the command line rather than
checking the style of every line" | |
| 318 parser.add_option("--diff-files", action="store_true", dest="diff_files"
, default=False, help=diff_files_help) | |
| 319 | |
| 320 min_confidence_help = ("set the minimum confidence of style errors " | |
| 321 "to report. Can be an integer 1-5, with 1 " | |
| 322 "displaying all errors. Defaults to %default.") | |
| 323 parser.add_option("-m", "--min-confidence", metavar="INT", | |
| 324 type="int", dest="min_confidence", | |
| 325 default=default_min_confidence, | |
| 326 help=min_confidence_help) | |
| 327 | |
| 328 output_format_help = ('set the output format, which can be "emacs" ' | |
| 329 'or "vs7" (for Visual Studio). ' | |
| 330 'Defaults to "%default".') | |
| 331 parser.add_option("-o", "--output-format", metavar="FORMAT", | |
| 332 choices=["emacs", "vs7"], | |
| 333 dest="output_format", default=default_output_format, | |
| 334 help=output_format_help) | |
| 335 | |
| 336 verbose_help = "enable verbose logging." | |
| 337 parser.add_option("-v", "--verbose", dest="is_verbose", default=False, | |
| 338 action="store_true", help=verbose_help) | |
| 339 | |
| 340 # Override OptionParser's error() method so that option help will | |
| 341 # also display when an error occurs. Normally, just the usage | |
| 342 # string displays and not option help. | |
| 343 parser.error = self._parse_error | |
| 344 | |
| 345 # Override OptionParser's print_help() method so that help output | |
| 346 # does not render to the screen while running unit tests. | |
| 347 print_help = parser.print_help | |
| 348 parser.print_help = lambda file=stderr: print_help(file=file) | |
| 349 | |
| 350 return parser | |
| 351 | |
| 352 def _parse_error(self, error_message): | |
| 353 """Print the help string and an error message, and exit.""" | |
| 354 # The method format_help() includes both the usage string and | |
| 355 # the flag options. | |
| 356 help = self._parser.format_help() | |
| 357 # Separate help from the error message with a single blank line. | |
| 358 self.stderr_write(help + "\n") | |
| 359 if error_message: | |
| 360 _log.error(error_message) | |
| 361 | |
| 362 # Since we are using this method to replace/override the Python | |
| 363 # module optparse's OptionParser.error() method, we match its | |
| 364 # behavior and exit with status code 2. | |
| 365 # | |
| 366 # As additional background, Python documentation says-- | |
| 367 # | |
| 368 # "Unix programs generally use 2 for command line syntax errors | |
| 369 # and 1 for all other kind of errors." | |
| 370 # | |
| 371 # (from http://docs.python.org/library/sys.html#sys.exit ) | |
| 372 sys.exit(2) | |
| 373 | |
| 374 def _exit_with_categories(self): | |
| 375 """Exit and print the style categories and default filter rules.""" | |
| 376 self.stderr_write('\nAll categories:\n') | |
| 377 for category in sorted(self._all_categories): | |
| 378 self.stderr_write(' ' + category + '\n') | |
| 379 | |
| 380 self.stderr_write('\nDefault filter rules**:\n') | |
| 381 for filter_rule in sorted(self._base_filter_rules): | |
| 382 self.stderr_write(' ' + filter_rule + '\n') | |
| 383 self.stderr_write('\n**The command always evaluates the above rules, ' | |
| 384 'and before any --filter flag.\n\n') | |
| 385 | |
| 386 sys.exit(0) | |
| 387 | |
| 388 def _parse_filter_flag(self, flag_value): | |
| 389 """Parse the --filter flag, and return a list of filter rules. | |
| 390 | |
| 391 Args: | |
| 392 flag_value: A string of comma-separated filter rules, for | |
| 393 example "-whitespace,+whitespace/indent". | |
| 394 | |
| 395 """ | |
| 396 filters = [] | |
| 397 for uncleaned_filter in flag_value.split(','): | |
| 398 filter = uncleaned_filter.strip() | |
| 399 if not filter: | |
| 400 continue | |
| 401 filters.append(filter) | |
| 402 return filters | |
| 403 | |
| 404 def parse(self, args): | |
| 405 """Parse the command line arguments to check-webkit-style. | |
| 406 | |
| 407 Args: | |
| 408 args: A list of command-line arguments as returned by sys.argv[1:]. | |
| 409 | |
| 410 Returns: | |
| 411 A tuple of (paths, options) | |
| 412 | |
| 413 paths: The list of paths to check. | |
| 414 options: A CommandOptionValues instance. | |
| 415 | |
| 416 """ | |
| 417 (options, paths) = self._parser.parse_args(args=args) | |
| 418 | |
| 419 filter_value = options.filter_value | |
| 420 git_commit = options.git_commit | |
| 421 diff_files = options.diff_files | |
| 422 is_verbose = options.is_verbose | |
| 423 min_confidence = options.min_confidence | |
| 424 output_format = options.output_format | |
| 425 | |
| 426 if filter_value is not None and not filter_value: | |
| 427 # Then the user explicitly passed no filter, for | |
| 428 # example "-f ''" or "--filter=". | |
| 429 self._exit_with_categories() | |
| 430 | |
| 431 # Validate user-provided values. | |
| 432 | |
| 433 min_confidence = int(min_confidence) | |
| 434 if (min_confidence < 1) or (min_confidence > 5): | |
| 435 self._parse_error('option --min-confidence: invalid integer: ' | |
| 436 '%s: value must be between 1 and 5' | |
| 437 % min_confidence) | |
| 438 | |
| 439 if filter_value: | |
| 440 filter_rules = self._parse_filter_flag(filter_value) | |
| 441 else: | |
| 442 filter_rules = [] | |
| 443 | |
| 444 try: | |
| 445 validate_filter_rules(filter_rules, self._all_categories) | |
| 446 except ValueError, err: | |
| 447 self._parse_error(err) | |
| 448 | |
| 449 options = CommandOptionValues(filter_rules=filter_rules, | |
| 450 git_commit=git_commit, | |
| 451 diff_files=diff_files, | |
| 452 is_verbose=is_verbose, | |
| 453 min_confidence=min_confidence, | |
| 454 output_format=output_format) | |
| 455 | |
| 456 return (paths, options) | |
| 457 | |
| OLD | NEW |