summaryrefslogtreecommitdiff
path: root/tools/lint-hunks.py
blob: 6e25d93624ddb6840c65ee5f4e7c3d845c653f24 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
#!/usr/bin/python
##  Copyright (c) 2012 The WebM project authors. All Rights Reserved.
##
##  Use of this source code is governed by a BSD-style license
##  that can be found in the LICENSE file in the root of the source
##  tree. An additional intellectual property rights grant can be found
##  in the file PATENTS.  All contributing project authors may
##  be found in the AUTHORS file in the root of the source tree.
##
"""Performs style checking on each diff hunk."""
import getopt
import os
import StringIO
import subprocess
import sys

import diff


SHORT_OPTIONS = "h"
LONG_OPTIONS = ["help"]

TOPLEVEL_CMD = ["git", "rev-parse", "--show-toplevel"]
DIFF_CMD = ["git", "diff"]
DIFF_INDEX_CMD = ["git", "diff-index", "-u", "HEAD", "--"]
SHOW_CMD = ["git", "show"]
CPPLINT_FILTERS = ["-readability/casting"]


class Usage(Exception):
    pass


class SubprocessException(Exception):
    def __init__(self, args):
        msg = "Failed to execute '%s'"%(" ".join(args))
        super(SubprocessException, self).__init__(msg)


class Subprocess(subprocess.Popen):
    """Adds the notion of an expected returncode to Popen."""

    def __init__(self, args, expected_returncode=0, **kwargs):
        self._args = args
        self._expected_returncode = expected_returncode
        super(Subprocess, self).__init__(args, **kwargs)

    def communicate(self, *args, **kwargs):
        result = super(Subprocess, self).communicate(*args, **kwargs)
        if self._expected_returncode is not None:
            try:
                ok = self.returncode in self._expected_returncode
            except TypeError:
                ok = self.returncode == self._expected_returncode
            if not ok:
                raise SubprocessException(self._args)
        return result


def main(argv=None):
    if argv is None:
        argv = sys.argv
    try:
        try:
            opts, args = getopt.getopt(argv[1:], SHORT_OPTIONS, LONG_OPTIONS)
        except getopt.error, msg:
            raise Usage(msg)

        # process options
        for o, _ in opts:
            if o in ("-h", "--help"):
                print __doc__
                sys.exit(0)

        if args and len(args) > 1:
            print __doc__
            sys.exit(0)

        # Find the fully qualified path to the root of the tree
        tl = Subprocess(TOPLEVEL_CMD, stdout=subprocess.PIPE)
        tl = tl.communicate()[0].strip()

        # See if we're working on the index or not.
        if args:
            diff_cmd = DIFF_CMD + [args[0] + "^!"]
        else:
            diff_cmd = DIFF_INDEX_CMD

        # Build the command line to execute cpplint
        cpplint_cmd = [os.path.join(tl, "tools", "cpplint.py"),
                       "--filter=" + ",".join(CPPLINT_FILTERS),
                       "-"]

        # Get a list of all affected lines
        file_affected_line_map = {}
        p = Subprocess(diff_cmd, stdout=subprocess.PIPE)
        stdout = p.communicate()[0]
        for hunk in diff.ParseDiffHunks(StringIO.StringIO(stdout)):
            filename = hunk.right.filename[2:]
            if filename not in file_affected_line_map:
                file_affected_line_map[filename] = set()
            file_affected_line_map[filename].update(hunk.right.delta_line_nums)

        # Run each affected file through cpplint
        lint_failed = False
        for filename, affected_lines in file_affected_line_map.iteritems():
            if filename.split(".")[-1] not in ("c", "h", "cc"):
                continue

            if args:
                # File contents come from git
                show_cmd = SHOW_CMD + [args[0] + ":" + filename]
                show = Subprocess(show_cmd, stdout=subprocess.PIPE)
                lint = Subprocess(cpplint_cmd, expected_returncode=(0, 1),
                                  stdin=show.stdout, stderr=subprocess.PIPE)
                lint_out = lint.communicate()[1]
            else:
                # File contents come from the working tree
                lint = Subprocess(cpplint_cmd, expected_returncode=(0, 1),
                                  stdin=subprocess.PIPE, stderr=subprocess.PIPE)
                stdin = open(os.path.join(tl, filename)).read()
                lint_out = lint.communicate(stdin)[1]

            for line in lint_out.split("\n"):
                fields = line.split(":")
                if fields[0] != "-":
                    continue
                warning_line_num = int(fields[1])
                if warning_line_num in affected_lines:
                    print "%s:%d:%s"%(filename, warning_line_num,
                                      ":".join(fields[2:]))
                    lint_failed = True

        # Set exit code if any relevant lint errors seen
        if lint_failed:
            return 1

    except Usage, err:
        print >>sys.stderr, err
        print >>sys.stderr, "for help use --help"
        return 2

if __name__ == "__main__":
    sys.exit(main())