15c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)# Copyright (C) 2010 Chris Jerdonek (cjerdonek@webkit.org)
25c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#
35c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)# Redistribution and use in source and binary forms, with or without
45c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)# modification, are permitted provided that the following conditions
55c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)# are met:
65c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)# 1.  Redistributions of source code must retain the above copyright
75c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#     notice, this list of conditions and the following disclaimer.
85c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)# 2.  Redistributions in binary form must reproduce the above copyright
95c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#     notice, this list of conditions and the following disclaimer in the
105c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#     documentation and/or other materials provided with the distribution.
115c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#
125c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)# THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND
135c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
145c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
155c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)# DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR
165c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
175c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
185c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
195c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
205c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
215c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
225c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
235c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)import codecs
245c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)import logging
255c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)import sys
265c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
275c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)import webkitpy.style.checker as checker
285c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)from webkitpy.style.patchreader import PatchReader
295c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)from webkitpy.style.checker import StyleProcessor
305c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)from webkitpy.style.filereader import TextFileReader
315c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)from webkitpy.common.host import Host
325c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
335c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
345c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)_log = logging.getLogger(__name__)
355c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
365c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
375c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)def change_directory(filesystem, checkout_root, paths):
385c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    """Change the working directory to the WebKit checkout root, if possible.
395c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
405c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    If every path in the paths parameter is below the checkout root (or if
415c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    the paths parameter is empty or None), this method changes the current
425c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    working directory to the checkout root and converts the paths parameter
435c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    as described below.
445c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        This allows the paths being checked to be displayed relative to the
455c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    checkout root, and for path-specific style checks to work as expected.
465c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    Path-specific checks include whether files should be skipped, whether
475c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    custom style rules should apply to certain files, etc.
485c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
495c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    Returns:
505c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)      paths: A copy of the paths parameter -- possibly converted, as follows.
515c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)             If this method changed the current working directory to the
525c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)             checkout root, then the list is the paths parameter converted to
535c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)             normalized paths relative to the checkout root.
545c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
555c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    Args:
565c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)      paths: A list of paths to the files that should be checked for style.
575c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)             This argument can be None or the empty list if a git commit
585c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)             or all changes under the checkout root should be checked.
595c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)      checkout_root: The path to the root of the WebKit checkout.
605c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
615c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    """
625c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    if paths is not None:
635c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        paths = list(paths)
645c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
655c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    if paths:
665c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        # Then try converting all of the paths to paths relative to
675c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        # the checkout root.
685c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        rel_paths = []
695c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        for path in paths:
705c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)            rel_path = filesystem.relpath(path, checkout_root)
715c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)            if rel_path.startswith(filesystem.pardir):
725c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)                # Then the path is not below the checkout root.  Since all
735c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)                # paths should be interpreted relative to the same root,
745c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)                # do not interpret any of the paths as relative to the
755c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)                # checkout root.  Interpret all of them relative to the
765c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)                # current working directory, and do not change the current
775c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)                # working directory.
785c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)                _log.warn(
795c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)"""Path-dependent style checks may not work correctly:
805c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
815c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)  One of the given paths is outside the WebKit checkout of the current
825c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)  working directory:
835c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
845c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    Path: %s
855c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    Checkout root: %s
865c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
875c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)  Pass only files below the checkout root to ensure correct results.
885c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)  See the help documentation for more info.
895c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)"""
905c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)                          % (path, checkout_root))
915c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
925c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)                return paths
935c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)            rel_paths.append(rel_path)
945c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        # If we got here, the conversion was successful.
955c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        paths = rel_paths
965c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
975c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    _log.debug("Changing to checkout root: " + checkout_root)
985c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    filesystem.chdir(checkout_root)
995c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
1005c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    return paths
1015c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
1025c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
1035c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)class CheckWebKitStyle(object):
1045c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    def _engage_awesome_stderr_hacks(self):
1055c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        # Change stderr to write with replacement characters so we don't die
1065c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        # if we try to print something containing non-ASCII characters.
1075c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        stderr = codecs.StreamReaderWriter(sys.stderr,
1085c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)                                           codecs.getreader('utf8'),
1095c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)                                           codecs.getwriter('utf8'),
1105c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)                                           'replace')
1115c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        # Setting an "encoding" attribute on the stream is necessary to
1125c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        # prevent the logging module from raising an error.  See
1135c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        # the checker.configure_logging() function for more information.
1145c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        stderr.encoding = "UTF-8"
1155c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
1165c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        # FIXME: Change webkitpy.style so that we do not need to overwrite
1175c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        #        the global sys.stderr.  This involves updating the code to
1185c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        #        accept a stream parameter where necessary, and not calling
1195c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        #        sys.stderr explicitly anywhere.
1205c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        sys.stderr = stderr
1215c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        return stderr
1225c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
1235c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    def main(self):
1245c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        args = sys.argv[1:]
1255c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
1265c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        host = Host()
1275c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        host.initialize_scm()
1285c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
1295c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        stderr = self._engage_awesome_stderr_hacks()
1305c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
1315c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        # Checking for the verbose flag before calling check_webkit_style_parser()
1325c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        # lets us enable verbose logging earlier.
1335c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        is_verbose = "-v" in args or "--verbose" in args
1345c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
1355c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        checker.configure_logging(stream=stderr, is_verbose=is_verbose)
1365c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        _log.debug("Verbose logging enabled.")
1375c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
1385c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        parser = checker.check_webkit_style_parser()
1395c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        (paths, options) = parser.parse(args)
1405c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
1415c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        configuration = checker.check_webkit_style_configuration(options)
1425c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
1435c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        paths = change_directory(host.filesystem, checkout_root=host.scm().checkout_root, paths=paths)
1445c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
1455c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        style_processor = StyleProcessor(configuration)
1465c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        file_reader = TextFileReader(host.filesystem, style_processor)
1475c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
1485c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        if paths and not options.diff_files:
1495c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)            file_reader.process_paths(paths)
1505c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        else:
1515c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)            changed_files = paths if options.diff_files else None
1525c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)            patch = host.scm().create_patch(options.git_commit, changed_files=changed_files)
1535c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)            patch_checker = PatchReader(file_reader)
1545c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)            patch_checker.check(patch)
1555c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
1565c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        error_count = style_processor.error_count
1575c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        file_count = file_reader.file_count
1585c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        delete_only_file_count = file_reader.delete_only_file_count
1595c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
1605c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        _log.info("Total errors found: %d in %d files" % (error_count, file_count))
1615c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        # We fail when style errors are found or there are no checked files.
1625c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        return error_count > 0 or (file_count == 0 and delete_only_file_count == 0)
163