| # Copyright (C) 2010 Chris Jerdonek (cjerdonek@webkit.org) |
| # |
| # Redistribution and use in source and binary forms, with or without |
| # modification, are permitted provided that the following conditions |
| # are met: |
| # 1. Redistributions of source code must retain the above copyright |
| # notice, this list of conditions and the following disclaimer. |
| # 2. Redistributions in binary form must reproduce the above copyright |
| # notice, this list of conditions and the following disclaimer in the |
| # documentation and/or other materials provided with the distribution. |
| # |
| # THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND ANY |
| # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
| # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| # DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY |
| # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
| # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
| # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON |
| # ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
| # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| |
| """Supports the parsing of command-line options for check-webkit-style.""" |
| |
| 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__) |
| |
| _USAGE = """usage: %prog [--help] [options] [path1] [path2] ... |
| |
| Overview: |
| Check coding style according to WebKit style guidelines: |
| |
| http://webkit.org/coding/coding-style.html |
| |
| 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. |
| |
| 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. |
| |
| 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. |
| |
| 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. |
| |
| 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". |
| |
| Examples: --filter=-whitespace,+whitespace/braces |
| --filter=-whitespace,-runtime/printf,+runtime/printf_format |
| --filter=-,+build/include_what_you_use |
| |
| 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). |
| |
| 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. |
| |
| 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. |
| |
| 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. |
| |
| 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.""" |
| |
| _EPILOG = ("This script can miss errors and does not substitute for " |
| "code review.") |
| |
| |
| # This class should not have knowledge of the flag key names. |
| class DefaultCommandOptionValues(object): |
| |
| """Stores the default check-webkit-style command-line options. |
| |
| Attributes: |
| output_format: A string that is the default output format. |
| min_confidence: An integer that is the default minimum confidence level. |
| |
| """ |
| |
| def __init__(self, min_confidence, output_format): |
| self.min_confidence = min_confidence |
| self.output_format = output_format |
| |
| |
| # 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: |
| 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 |
| path-specific rules and so take precedence over |
| the base filter rules, etc. |
| |
| 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. |
| |
| """ |
| def __init__(self, |
| filter_rules=None, |
| git_commit=None, |
| diff_files=None, |
| is_verbose=False, |
| min_confidence=1, |
| output_format="emacs", |
| commit_queue=False, |
| git_index=False): |
| 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) |
| |
| self.filter_rules = filter_rules |
| self.git_commit = git_commit |
| self.diff_files = diff_files |
| self.is_verbose = is_verbose |
| self.min_confidence = min_confidence |
| self.output_format = output_format |
| self.commit_queue = commit_queue |
| self.git_index = git_index |
| |
| # Useful for unit testing. |
| def __eq__(self, other): |
| """Return whether this instance is equal to another.""" |
| if self.filter_rules != other.filter_rules: |
| return False |
| if self.git_commit != other.git_commit: |
| return False |
| if self.diff_files != other.diff_files: |
| return False |
| if self.is_verbose != other.is_verbose: |
| return False |
| if self.min_confidence != other.min_confidence: |
| return False |
| if self.output_format != other.output_format: |
| return False |
| if self.git_index != other.git_index: |
| return False |
| |
| return True |
| |
| # Useful for unit testing. |
| def __ne__(self, other): |
| # Python does not automatically deduce this from __eq__(). |
| return not self.__eq__(other) |
| |
| |
| class ArgumentPrinter(object): |
| |
| """Supports the printing of check-webkit-style command arguments.""" |
| |
| def _flag_pair_to_string(self, flag_key, flag_value): |
| return '--%(key)s=%(val)s' % {'key': flag_key, 'val': flag_value} |
| |
| def to_flag_string(self, options): |
| """Return a flag string of the given CommandOptionValues instance. |
| |
| This method orders the flag values alphabetically by the flag key. |
| |
| Args: |
| options: A CommandOptionValues instance. |
| |
| """ |
| flags = {} |
| flags['min-confidence'] = options.min_confidence |
| flags['output'] = options.output_format |
| # Only include the filter flag if user-provided rules are present. |
| filter_rules = options.filter_rules |
| if filter_rules: |
| flags['filter'] = ",".join(filter_rules) |
| if options.git_commit: |
| flags['git-commit'] = options.git_commit |
| if options.diff_files: |
| flags['diff_files'] = options.diff_files |
| |
| flag_string = '' |
| # Alphabetizing lets us unit test this method. |
| for key in sorted(flags.keys()): |
| flag_string += self._flag_pair_to_string(key, flags[key]) + ' ' |
| |
| return flag_string.strip() |
| |
| |
| class ArgumentParser(object): |
| |
| # FIXME: Move the documentation of the attributes to the __init__ |
| # docstring after making the attributes internal. |
| """Supports the parsing of check-webkit-style command arguments. |
| |
| Attributes: |
| create_usage: A function that accepts a DefaultCommandOptionValues |
| instance and returns a string of usage instructions. |
| Defaults to the function that generates the usage |
| string for check-webkit-style. |
| default_options: A DefaultCommandOptionValues instance that provides |
| the default values for options not explicitly |
| provided by the user. |
| stderr_write: A function that takes a string as a parameter and |
| serves as stderr.write. Defaults to sys.stderr.write. |
| This parameter should be specified only for unit tests. |
| |
| """ |
| |
| def __init__(self, |
| all_categories, |
| default_options, |
| base_filter_rules=None, |
| mock_stderr=None, |
| usage=None): |
| """Create an ArgumentParser instance. |
| |
| Args: |
| all_categories: The set of all available style categories. |
| default_options: See the corresponding attribute in the class |
| docstring. |
| Keyword Args: |
| base_filter_rules: The list of filter rules at the beginning of |
| the list of rules used to check style. This |
| list has the least precedence when checking |
| style and precedes any user-provided rules. |
| The class uses this parameter only for display |
| purposes to the user. Defaults to the empty list. |
| create_usage: See the documentation of the corresponding |
| attribute in the class docstring. |
| stderr_write: See the documentation of the corresponding |
| attribute in the class docstring. |
| |
| """ |
| if base_filter_rules is None: |
| base_filter_rules = [] |
| 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.default_options = default_options |
| 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_commit_help = ("check all changes in the given commit. " |
| "Use 'commit_id..' to check all changes after commmit_id") |
| parser.add_option("-g", "--git-diff", "--git-commit", |
| metavar="COMMIT", dest="git_commit", help=git_commit_help,) |
| |
| diff_files_help = "diff the files passed on the command line rather than checking the style of every line" |
| parser.add_option("--diff-files", action="store_true", dest="diff_files", default=False, help=diff_files_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) |
| |
| commit_queue_help = "force commit queue to check contributors.json change" |
| parser.add_option("--commit-queue", action="store_true", dest="commit_queue", default=False, help=commit_queue_help) |
| parser.add_option("--git-index", action="store_true", dest="git_index", default=False, help="Scan staged files only") |
| |
| # 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 file=stderr: print_help(file=file) |
| |
| 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: |
| _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.""" |
| self.stderr_write('\nAll categories:\n') |
| for category in sorted(self._all_categories): |
| self.stderr_write(' ' + category + '\n') |
| |
| self.stderr_write('\nDefault filter rules**:\n') |
| for filter_rule in sorted(self._base_filter_rules): |
| self.stderr_write(' ' + filter_rule + '\n') |
| self.stderr_write('\n**The command always evaluates the above rules, ' |
| 'and before any --filter flag.\n\n') |
| |
| sys.exit(0) |
| |
| def _parse_filter_flag(self, flag_value): |
| """Parse the --filter flag, and return a list of filter rules. |
| |
| Args: |
| flag_value: A string of comma-separated filter rules, for |
| example "-whitespace,+whitespace/indent". |
| |
| """ |
| filters = [] |
| for uncleaned_filter in flag_value.split(','): |
| filter = uncleaned_filter.strip() |
| if not filter: |
| continue |
| filters.append(filter) |
| return filters |
| |
| 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:]. |
| |
| Returns: |
| A tuple of (paths, options) |
| |
| paths: The list of paths to check. |
| options: A CommandOptionValues instance. |
| |
| """ |
| (options, paths) = self._parser.parse_args(args=args) |
| |
| filter_value = options.filter_value |
| git_commit = options.git_commit |
| diff_files = options.diff_files |
| is_verbose = options.is_verbose |
| min_confidence = options.min_confidence |
| output_format = options.output_format |
| commit_queue = options.commit_queue |
| git_index = options.git_index |
| |
| 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. |
| |
| 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 = [] |
| |
| try: |
| validate_filter_rules(filter_rules, self._all_categories) |
| except ValueError as err: |
| self._parse_error(err) |
| |
| options = CommandOptionValues(filter_rules=filter_rules, |
| git_commit=git_commit, |
| diff_files=diff_files, |
| is_verbose=is_verbose, |
| min_confidence=min_confidence, |
| output_format=output_format, |
| commit_queue=commit_queue, |
| git_index=git_index) |
| |
| return (paths, options) |