123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458 |
- # 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"):
- 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
- # 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
- 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)
- # 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
- 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, 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)
- return (paths, options)
|