diff options
Diffstat (limited to 'WebKitTools/Scripts/webkitpy/style/optparser.py')
-rw-r--r-- | WebKitTools/Scripts/webkitpy/style/optparser.py | 399 |
1 files changed, 216 insertions, 183 deletions
diff --git a/WebKitTools/Scripts/webkitpy/style/optparser.py b/WebKitTools/Scripts/webkitpy/style/optparser.py index 4137c8b..576c16a 100644 --- a/WebKitTools/Scripts/webkitpy/style/optparser.py +++ b/WebKitTools/Scripts/webkitpy/style/optparser.py @@ -22,84 +22,83 @@ """Supports the parsing of command-line options for check-webkit-style.""" -import getopt +import logging +from optparse import OptionParser import os.path import sys from filter import validate_filter_rules # This module should not import anything from checker.py. +_log = logging.getLogger(__name__) -def _create_usage(default_options): - """Return the usage string to display for command help. +_USAGE = """usage: %prog [--help] [options] [path1] [path2] ... - Args: - default_options: A DefaultCommandOptionValues instance. +Overview: + Check coding style according to WebKit style guidelines: - """ - usage = """ -Syntax: %(program_name)s [--verbose=#] [--git-commit=<SingleCommit>] [--output=vs7] - [--filter=-x,+y,...] [file] ... - - The style guidelines this tries to follow are here: - http://webkit.org/coding/coding-style.html + http://webkit.org/coding/coding-style.html - Every style error is given a confidence score from 1-5, with 5 meaning - we are certain of the problem, and 1 meaning it could be a legitimate - construct. This can miss some errors and does not substitute for - code review. + Path arguments can be files and directories. If neither a git commit nor + paths are passed, then all changes in your source control working directory + are checked. - To prevent specific lines from being linted, add a '// NOLINT' comment to the - end of the line. +Style errors: + This script assigns to every style error a confidence score from 1-5 and + a category name. A confidence score of 5 means the error is certainly + a problem, and 1 means it could be fine. - Linted extensions are .cpp, .c and .h. Other file types are ignored. + Category names appear in error messages in brackets, for example + [whitespace/indent]. See the options section below for an option that + displays all available categories and which are reported by default. - The file parameter is optional and accepts multiple files. Leaving - out the file parameter applies the check to all files considered changed - by your source control management system. +Filters: + Use filters to configure what errors to report. Filters are specified using + a comma-separated list of boolean filter rules. The script reports errors + in a category if the category passes the filter, as described below. - Flags: + All categories start out passing. Boolean filter rules are then evaluated + from left to right, with later rules taking precedence. For example, the + rule "+foo" passes any category that starts with "foo", and "-foo" fails + any such category. The filter input "-whitespace,+whitespace/braces" fails + the category "whitespace/tab" and passes "whitespace/braces". - verbose=# - A number 1-5 that restricts output to errors with a confidence - score at or above this value. In particular, the value 1 displays - all errors. The default is %(default_verbosity)s. + Examples: --filter=-whitespace,+whitespace/braces + --filter=-whitespace,-runtime/printf,+runtime/printf_format + --filter=-,+build/include_what_you_use - git-commit=<SingleCommit> - Checks the style of everything from the given commit to the local tree. +Paths: + Certain style-checking behavior depends on the paths relative to + the WebKit source root of the files being checked. For example, + certain types of errors may be handled differently for files in + WebKit/gtk/webkit/ (e.g. by suppressing "readability/naming" errors + for files in this directory). - output=vs7 - The output format, which may be one of - emacs : to ease emacs parsing - vs7 : compatible with Visual Studio - Defaults to "%(default_output_format)s". Other formats are unsupported. + Consequently, if the path relative to the source root cannot be + determined for a file being checked, then style checking may not + work correctly for that file. This can occur, for example, if no + WebKit checkout can be found, or if the source root can be detected, + but one of the files being checked lies outside the source tree. - filter=-x,+y,... - A comma-separated list of boolean filter rules used to filter - which categories of style guidelines to check. The script checks - a category if the category passes the filter rules, as follows. + If a WebKit checkout can be detected and all files being checked + are in the source tree, then all paths will automatically be + converted to paths relative to the source root prior to checking. + This is also useful for display purposes. - Any webkit category starts out passing. All filter rules are then - evaluated left to right, with later rules taking precedence. For - example, the rule "+foo" passes any category that starts with "foo", - and "-foo" fails any such category. The filter input "-whitespace, - +whitespace/braces" fails the category "whitespace/tab" and passes - "whitespace/braces". + Currently, this command can detect the source root only if the + command is run from within a WebKit checkout (i.e. if the current + working directory is below the root of a checkout). In particular, + it is not recommended to run this script from a directory outside + a checkout. - Examples: --filter=-whitespace,+whitespace/braces - --filter=-whitespace,-runtime/printf,+runtime/printf_format - --filter=-,+build/include_what_you_use + Running this script from a top-level WebKit source directory and + checking only files in the source tree will ensure that all style + checking behaves correctly -- whether or not a checkout can be + detected. This is because all file paths will already be relative + to the source root and so will not need to be converted.""" - Category names appear in error messages in brackets, for example - [whitespace/indent]. To see a list of all categories available to - %(program_name)s, along with which are enabled by default, pass - the empty filter as follows: - --filter= -""" % {'program_name': os.path.basename(sys.argv[0]), - 'default_verbosity': default_options.verbosity, - 'default_output_format': default_options.output_format} - - return usage +_EPILOG = ("This script can miss errors and does not substitute for " + "code review.") # This class should not have knowledge of the flag key names. @@ -109,26 +108,22 @@ class DefaultCommandOptionValues(object): Attributes: output_format: A string that is the default output format. - verbosity: An integer that is the default verbosity level. + min_confidence: An integer that is the default minimum confidence level. """ - def __init__(self, output_format, verbosity): + def __init__(self, min_confidence, output_format): + self.min_confidence = min_confidence self.output_format = output_format - self.verbosity = verbosity -# FIXME: Eliminate support for "extra_flag_values". -# # This class should not have knowledge of the flag key names. class CommandOptionValues(object): """Stores the option values passed by the user via the command line. Attributes: - extra_flag_values: A string-string dictionary of all flag key-value - pairs that are not otherwise represented by this - class. The default is the empty dictionary. + is_verbose: A boolean value of whether verbose logging is enabled. filter_rules: The list of filter rules provided by the user. These rules are appended to the base rules and @@ -138,54 +133,52 @@ class CommandOptionValues(object): git_commit: A string representing the git commit to check. The default is None. + min_confidence: An integer between 1 and 5 inclusive that is the + minimum confidence level of style errors to report. + The default is 1, which reports all errors. + output_format: A string that is the output format. The supported output formats are "emacs" which emacs can parse and "vs7" which Microsoft Visual Studio 7 can parse. - verbosity: An integer between 1-5 inclusive that restricts output - to errors with a confidence score at or above this value. - The default is 1, which reports all errors. - """ def __init__(self, - extra_flag_values=None, filter_rules=None, git_commit=None, - output_format="emacs", - verbosity=1): - if extra_flag_values is None: - extra_flag_values = {} + is_verbose=False, + min_confidence=1, + output_format="emacs"): if filter_rules is None: filter_rules = [] + if (min_confidence < 1) or (min_confidence > 5): + raise ValueError('Invalid "min_confidence" parameter: value ' + "must be an integer between 1 and 5 inclusive. " + 'Value given: "%s".' % min_confidence) + if output_format not in ("emacs", "vs7"): raise ValueError('Invalid "output_format" parameter: ' 'value must be "emacs" or "vs7". ' 'Value given: "%s".' % output_format) - if (verbosity < 1) or (verbosity > 5): - raise ValueError('Invalid "verbosity" parameter: ' - "value must be an integer between 1-5 inclusive. " - 'Value given: "%s".' % verbosity) - - self.extra_flag_values = extra_flag_values self.filter_rules = filter_rules self.git_commit = git_commit + self.is_verbose = is_verbose + self.min_confidence = min_confidence self.output_format = output_format - self.verbosity = verbosity # Useful for unit testing. def __eq__(self, other): """Return whether this instance is equal to another.""" - if self.extra_flag_values != other.extra_flag_values: - return False if self.filter_rules != other.filter_rules: return False if self.git_commit != other.git_commit: return False - if self.output_format != other.output_format: + if self.is_verbose != other.is_verbose: return False - if self.verbosity != other.verbosity: + if self.min_confidence != other.min_confidence: + return False + if self.output_format != other.output_format: return False return True @@ -212,10 +205,9 @@ class ArgumentPrinter(object): options: A CommandOptionValues instance. """ - flags = options.extra_flag_values.copy() - + flags = {} + flags['min-confidence'] = options.min_confidence flags['output'] = options.output_format - flags['verbose'] = options.verbosity # Only include the filter flag if user-provided rules are present. filter_rules = options.filter_rules if filter_rules: @@ -231,7 +223,6 @@ class ArgumentPrinter(object): return flag_string.strip() -# FIXME: Replace the use of getopt.getopt() with optparse.OptionParser. class ArgumentParser(object): # FIXME: Move the documentation of the attributes to the __init__ @@ -256,8 +247,8 @@ class ArgumentParser(object): all_categories, default_options, base_filter_rules=None, - create_usage=None, - stderr_write=None): + mock_stderr=None, + usage=None): """Create an ArgumentParser instance. Args: @@ -279,31 +270,96 @@ class ArgumentParser(object): """ if base_filter_rules is None: base_filter_rules = [] - if create_usage is None: - create_usage = _create_usage - if stderr_write is None: - stderr_write = sys.stderr.write + stderr = sys.stderr if mock_stderr is None else mock_stderr + if usage is None: + usage = _USAGE self._all_categories = all_categories self._base_filter_rules = base_filter_rules + # FIXME: Rename these to reflect that they are internal. - self.create_usage = create_usage self.default_options = default_options - self.stderr_write = stderr_write - - def _exit_with_usage(self, error_message=''): - """Exit and print a usage string with an optional error message. - - Args: - error_message: A string that is an error message to print. - - """ - usage = self.create_usage(self.default_options) - self.stderr_write(usage) + self.stderr_write = stderr.write + + self._parser = self._create_option_parser(stderr=stderr, + usage=usage, + default_min_confidence=self.default_options.min_confidence, + default_output_format=self.default_options.output_format) + + def _create_option_parser(self, stderr, usage, + default_min_confidence, default_output_format): + # Since the epilog string is short, it is not necessary to replace + # the epilog string with a mock epilog string when testing. + # For this reason, we use _EPILOG directly rather than passing it + # as an argument like we do for the usage string. + parser = OptionParser(usage=usage, epilog=_EPILOG) + + filter_help = ('set a filter to control what categories of style ' + 'errors to report. Specify a filter using a comma-' + 'delimited list of boolean filter rules, for example ' + '"--filter -whitespace,+whitespace/braces". To display ' + 'all categories and which are enabled by default, pass ' + """no value (e.g. '-f ""' or '--filter=').""") + parser.add_option("-f", "--filter-rules", metavar="RULES", + dest="filter_value", help=filter_help) + + git_help = "check all changes after the given git commit." + parser.add_option("-g", "--git-commit", "--git-diff", "--git-since", + metavar="COMMIT", dest="git_since", help=git_help,) + + min_confidence_help = ("set the minimum confidence of style errors " + "to report. Can be an integer 1-5, with 1 " + "displaying all errors. Defaults to %default.") + parser.add_option("-m", "--min-confidence", metavar="INT", + type="int", dest="min_confidence", + default=default_min_confidence, + help=min_confidence_help) + + output_format_help = ('set the output format, which can be "emacs" ' + 'or "vs7" (for Visual Studio). ' + 'Defaults to "%default".') + parser.add_option("-o", "--output-format", metavar="FORMAT", + choices=["emacs", "vs7"], + dest="output_format", default=default_output_format, + help=output_format_help) + + verbose_help = "enable verbose logging." + parser.add_option("-v", "--verbose", dest="is_verbose", default=False, + action="store_true", help=verbose_help) + + # Override OptionParser's error() method so that option help will + # also display when an error occurs. Normally, just the usage + # string displays and not option help. + parser.error = self._parse_error + + # Override OptionParser's print_help() method so that help output + # does not render to the screen while running unit tests. + print_help = parser.print_help + parser.print_help = lambda: print_help(file=stderr) + + return parser + + def _parse_error(self, error_message): + """Print the help string and an error message, and exit.""" + # The method format_help() includes both the usage string and + # the flag options. + help = self._parser.format_help() + # Separate help from the error message with a single blank line. + self.stderr_write(help + "\n") if error_message: - sys.exit('\nFATAL ERROR: ' + error_message) - else: - sys.exit(1) + _log.error(error_message) + + # Since we are using this method to replace/override the Python + # module optparse's OptionParser.error() method, we match its + # behavior and exit with status code 2. + # + # As additional background, Python documentation says-- + # + # "Unix programs generally use 2 for command line syntax errors + # and 1 for all other kind of errors." + # + # (from http://docs.python.org/library/sys.html#sys.exit ) + sys.exit(2) def _exit_with_categories(self): """Exit and print the style categories and default filter rules.""" @@ -335,90 +391,67 @@ class ArgumentParser(object): filters.append(filter) return filters - def parse(self, args, extra_flags=None): + def parse(self, args): """Parse the command line arguments to check-webkit-style. Args: args: A list of command-line arguments as returned by sys.argv[1:]. - extra_flags: A list of flags whose values we want to extract, but - are not supported by the CommandOptionValues class. - An example flag "new_flag=". This defaults to the - empty list. Returns: - A tuple of (filenames, options) + A tuple of (paths, options) - filenames: The list of filenames to check. + paths: The list of paths to check. options: A CommandOptionValues instance. """ - if extra_flags is None: - extra_flags = [] - - output_format = self.default_options.output_format - verbosity = self.default_options.verbosity + (options, paths) = self._parser.parse_args(args=args) + + filter_value = options.filter_value + git_commit = options.git_since + is_verbose = options.is_verbose + min_confidence = options.min_confidence + output_format = options.output_format + + if filter_value is not None and not filter_value: + # Then the user explicitly passed no filter, for + # example "-f ''" or "--filter=". + self._exit_with_categories() + + # Validate user-provided values. + + if paths and git_commit: + self._parse_error('You cannot provide both paths and a git ' + 'commit at the same time.') + + # FIXME: Add unit tests. + if git_commit and '..' in git_commit: + # FIXME: If the range is a "...", the code should find the common + # ancestor and start there. See git diff --help for how + # "..." usually works. + self._parse_error('invalid --git-commit option: option does ' + 'not support ranges "..": %s' % git_commit) + + min_confidence = int(min_confidence) + if (min_confidence < 1) or (min_confidence > 5): + self._parse_error('option --min-confidence: invalid integer: ' + '%s: value must be between 1 and 5' + % min_confidence) + + if filter_value: + filter_rules = self._parse_filter_flag(filter_value) + else: + filter_rules = [] - # The flags already supported by the CommandOptionValues class. - flags = ['help', 'output=', 'verbose=', 'filter=', 'git-commit='] + try: + validate_filter_rules(filter_rules, self._all_categories) + except ValueError, err: + self._parse_error(err) - for extra_flag in extra_flags: - if extra_flag in flags: - raise ValueError('Flag \'%(extra_flag)s is duplicated ' - 'or already supported.' % - {'extra_flag': extra_flag}) - flags.append(extra_flag) + options = CommandOptionValues(filter_rules=filter_rules, + git_commit=git_commit, + is_verbose=is_verbose, + min_confidence=min_confidence, + output_format=output_format) - try: - (opts, filenames) = getopt.getopt(args, '', flags) - except getopt.GetoptError: - # FIXME: Settle on an error handling approach: come up - # with a consistent guideline as to when and whether - # a ValueError should be raised versus calling - # sys.exit when needing to interrupt execution. - self._exit_with_usage('Invalid arguments.') - - extra_flag_values = {} - git_commit = None - filter_rules = [] - - for (opt, val) in opts: - if opt == '--help': - self._exit_with_usage() - elif opt == '--output': - output_format = val - elif opt == '--verbose': - verbosity = val - elif opt == '--git-commit': - git_commit = val - elif opt == '--filter': - if not val: - self._exit_with_categories() - filter_rules = self._parse_filter_flag(val) - else: - extra_flag_values[opt] = val - - # Check validity of resulting values. - if filenames and (git_commit != None): - self._exit_with_usage('It is not possible to check files and a ' - 'specific commit at the same time.') - - if output_format not in ('emacs', 'vs7'): - raise ValueError('Invalid --output value "%s": The only ' - 'allowed output formats are emacs and vs7.' % - output_format) - - validate_filter_rules(filter_rules, self._all_categories) - - verbosity = int(verbosity) - if (verbosity < 1) or (verbosity > 5): - raise ValueError('Invalid --verbose value %s: value must ' - 'be between 1-5.' % verbosity) - - options = CommandOptionValues(extra_flag_values=extra_flag_values, - filter_rules=filter_rules, - git_commit=git_commit, - output_format=output_format, - verbosity=verbosity) - - return (filenames, options) + return (paths, options) |