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)"""Supports checking WebKit style in Python files."""
245c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
258abfc5808a4e34d6e03867af8bc440dee641886fTorne (Richard Coles)import os
26926b001d589ce2f10facb93dd4b87578ea35a855Torne (Richard Coles)import re
277757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdochimport sys
287757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
29926b001d589ce2f10facb93dd4b87578ea35a855Torne (Richard Coles)from StringIO import StringIO
30926b001d589ce2f10facb93dd4b87578ea35a855Torne (Richard Coles)
31926b001d589ce2f10facb93dd4b87578ea35a855Torne (Richard Coles)from webkitpy.common.system.filesystem import FileSystem
327757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdochfrom webkitpy.common.system.executive import Executive
33926b001d589ce2f10facb93dd4b87578ea35a855Torne (Richard Coles)from webkitpy.common.webkit_finder import WebKitFinder
340019e4eead4d990e4304c54a9028aca9122fb256Ben Murdochfrom webkitpy.thirdparty import pep8
355c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
365c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
375c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)class PythonChecker(object):
385c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    """Processes text lines for checking style."""
395c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    def __init__(self, file_path, handle_style_error):
405c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        self._file_path = file_path
415c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        self._handle_style_error = handle_style_error
425c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
435c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    def check(self, lines):
44926b001d589ce2f10facb93dd4b87578ea35a855Torne (Richard Coles)        self._check_pep8(lines)
45926b001d589ce2f10facb93dd4b87578ea35a855Torne (Richard Coles)        self._check_pylint(lines)
46926b001d589ce2f10facb93dd4b87578ea35a855Torne (Richard Coles)
47926b001d589ce2f10facb93dd4b87578ea35a855Torne (Richard Coles)    def _check_pep8(self, lines):
485c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        # Initialize pep8.options, which is necessary for
495c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        # Checker.check_all() to execute.
505c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        pep8.process_options(arglist=[self._file_path])
515c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
52926b001d589ce2f10facb93dd4b87578ea35a855Torne (Richard Coles)        pep8_checker = pep8.Checker(self._file_path)
535c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
545c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        def _pep8_handle_error(line_number, offset, text, check):
555c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)            # FIXME: Incorporate the character offset into the error output.
565c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)            #        This will require updating the error handler __call__
575c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)            #        signature to include an optional "offset" parameter.
585c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)            pep8_code = text[:4]
595c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)            pep8_message = text[5:]
605c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
615c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)            category = "pep8/" + pep8_code
625c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
635c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)            self._handle_style_error(line_number, category, 5, pep8_message)
645c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
65926b001d589ce2f10facb93dd4b87578ea35a855Torne (Richard Coles)        pep8_checker.report_error = _pep8_handle_error
66926b001d589ce2f10facb93dd4b87578ea35a855Torne (Richard Coles)        pep8_errors = pep8_checker.check_all()
67926b001d589ce2f10facb93dd4b87578ea35a855Torne (Richard Coles)
68926b001d589ce2f10facb93dd4b87578ea35a855Torne (Richard Coles)    def _check_pylint(self, lines):
697757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        output = self._run_pylint(self._file_path)
707757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        errors = self._parse_pylint_output(output)
717757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        for line_number, category, message in errors:
727757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            self._handle_style_error(line_number, category, 5, message)
73926b001d589ce2f10facb93dd4b87578ea35a855Torne (Richard Coles)
747757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def _run_pylint(self, path):
757757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        wkf = WebKitFinder(FileSystem())
767757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        executive = Executive()
778abfc5808a4e34d6e03867af8bc440dee641886fTorne (Richard Coles)        env = os.environ.copy()
7851b2906e11752df6c18351cf520e30522d3b53a1Torne (Richard Coles)        env['PYTHONPATH'] = ('%s%s%s%s%s' % (wkf.path_from_webkit_base('Tools', 'Scripts'),
7951b2906e11752df6c18351cf520e30522d3b53a1Torne (Richard Coles)                                         os.pathsep,
8051b2906e11752df6c18351cf520e30522d3b53a1Torne (Richard Coles)                                         wkf.path_from_webkit_base('Source', 'build', 'scripts'),
818abfc5808a4e34d6e03867af8bc440dee641886fTorne (Richard Coles)                                         os.pathsep,
828abfc5808a4e34d6e03867af8bc440dee641886fTorne (Richard Coles)                                         wkf.path_from_webkit_base('Tools', 'Scripts', 'webkitpy', 'thirdparty')))
837757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        return executive.run_command([sys.executable, wkf.path_from_depot_tools_base('pylint.py'),
847757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                                      '--output-format=parseable',
857757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                                      '--errors-only',
867757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                                      '--rcfile=' + wkf.path_from_webkit_base('Tools', 'Scripts', 'webkitpy', 'pylintrc'),
877757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                                      path],
888abfc5808a4e34d6e03867af8bc440dee641886fTorne (Richard Coles)                                     env=env,
898abfc5808a4e34d6e03867af8bc440dee641886fTorne (Richard Coles)                                     error_handler=executive.ignore_error)
907757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
917757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def _parse_pylint_output(self, output):
927757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        # We filter out these messages because they are bugs in pylint that produce false positives.
937757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        # FIXME: Does it make sense to combine these rules with the rules in style/checker.py somehow?
947757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        FALSE_POSITIVES = [
957757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            # possibly http://www.logilab.org/ticket/98613 ?
967757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            "Instance of 'Popen' has no 'poll' member",
977757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            "Instance of 'Popen' has no 'returncode' member",
987757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            "Instance of 'Popen' has no 'stdin' member",
997757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            "Instance of 'Popen' has no 'stdout' member",
1007757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            "Instance of 'Popen' has no 'stderr' member",
1017757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            "Instance of 'Popen' has no 'wait' member",
1027757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        ]
103926b001d589ce2f10facb93dd4b87578ea35a855Torne (Richard Coles)
104926b001d589ce2f10facb93dd4b87578ea35a855Torne (Richard Coles)        lint_regex = re.compile('([^:]+):([^:]+): \[([^]]+)\] (.*)')
1057757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        errors = []
1067757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        for line in output.splitlines():
1077757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            if any(msg in line for msg in FALSE_POSITIVES):
1087757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                continue
1097757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
1107757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            match_obj = lint_regex.match(line)
1117757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            if not match_obj:
1127757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                continue
1137757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
114926b001d589ce2f10facb93dd4b87578ea35a855Torne (Richard Coles)            line_number = int(match_obj.group(2))
115926b001d589ce2f10facb93dd4b87578ea35a855Torne (Richard Coles)            category_and_method = match_obj.group(3).split(', ')
116926b001d589ce2f10facb93dd4b87578ea35a855Torne (Richard Coles)            category = 'pylint/' + (category_and_method[0])
117926b001d589ce2f10facb93dd4b87578ea35a855Torne (Richard Coles)            if len(category_and_method) > 1:
118926b001d589ce2f10facb93dd4b87578ea35a855Torne (Richard Coles)                message = '[%s] %s' % (category_and_method[1], match_obj.group(4))
119926b001d589ce2f10facb93dd4b87578ea35a855Torne (Richard Coles)            else:
120926b001d589ce2f10facb93dd4b87578ea35a855Torne (Richard Coles)                message = match_obj.group(4)
1217757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            errors.append((line_number, category, message))
1227757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        return errors
123