|  | #!/usr/bin/env python | 
|  | # | 
|  | #===- clang-tidy-diff.py - ClangTidy Diff Checker ------------*- python -*--===# | 
|  | # | 
|  | #                     The LLVM Compiler Infrastructure | 
|  | # | 
|  | # This file is distributed under the University of Illinois Open Source | 
|  | # License. See LICENSE.TXT for details. | 
|  | # | 
|  | #===------------------------------------------------------------------------===# | 
|  |  | 
|  | r""" | 
|  | ClangTidy Diff Checker | 
|  | ====================== | 
|  |  | 
|  | This script reads input from a unified diff, runs clang-tidy on all changed | 
|  | files and outputs clang-tidy warnings in changed lines only. This is useful to | 
|  | detect clang-tidy regressions in the lines touched by a specific patch. | 
|  | Example usage for git/svn users: | 
|  |  | 
|  | git diff -U0 HEAD^ | clang-tidy-diff.py -p1 | 
|  | svn diff --diff-cmd=diff -x-U0 | \ | 
|  | clang-tidy-diff.py -fix -checks=-*,modernize-use-override | 
|  |  | 
|  | """ | 
|  |  | 
|  | import argparse | 
|  | import json | 
|  | import re | 
|  | import subprocess | 
|  | import sys | 
|  |  | 
|  |  | 
|  | def main(): | 
|  | parser = argparse.ArgumentParser(description= | 
|  | 'Run clang-tidy against changed files, and ' | 
|  | 'output diagnostics only for modified ' | 
|  | 'lines.') | 
|  | parser.add_argument('-clang-tidy-binary', metavar='PATH', | 
|  | default='clang-tidy', | 
|  | help='path to clang-tidy binary') | 
|  | parser.add_argument('-p', metavar='NUM', default=0, | 
|  | help='strip the smallest prefix containing P slashes') | 
|  | parser.add_argument('-regex', metavar='PATTERN', default=None, | 
|  | help='custom pattern selecting file paths to check ' | 
|  | '(case sensitive, overrides -iregex)') | 
|  | parser.add_argument('-iregex', metavar='PATTERN', default= | 
|  | r'.*\.(cpp|cc|c\+\+|cxx|c|cl|h|hpp|m|mm|inc)', | 
|  | help='custom pattern selecting file paths to check ' | 
|  | '(case insensitive, overridden by -regex)') | 
|  |  | 
|  | parser.add_argument('-fix', action='store_true', default=False, | 
|  | help='apply suggested fixes') | 
|  | parser.add_argument('-checks', | 
|  | help='checks filter, when not specified, use clang-tidy ' | 
|  | 'default', | 
|  | default='') | 
|  | parser.add_argument('-path', dest='build_path', | 
|  | help='Path used to read a compile command database.') | 
|  | parser.add_argument('-extra-arg', dest='extra_arg', | 
|  | action='append', default=[], | 
|  | help='Additional argument to append to the compiler ' | 
|  | 'command line.') | 
|  | parser.add_argument('-extra-arg-before', dest='extra_arg_before', | 
|  | action='append', default=[], | 
|  | help='Additional argument to prepend to the compiler ' | 
|  | 'command line.') | 
|  | parser.add_argument('-quiet', action='store_true', default=False, | 
|  | help='Run clang-tidy in quiet mode') | 
|  | clang_tidy_args = [] | 
|  | argv = sys.argv[1:] | 
|  | if '--' in argv: | 
|  | clang_tidy_args.extend(argv[argv.index('--'):]) | 
|  | argv = argv[:argv.index('--')] | 
|  |  | 
|  | args = parser.parse_args(argv) | 
|  |  | 
|  | # Extract changed lines for each file. | 
|  | filename = None | 
|  | lines_by_file = {} | 
|  | for line in sys.stdin: | 
|  | match = re.search('^\+\+\+\ \"?(.*?/){%s}([^ \t\n\"]*)' % args.p, line) | 
|  | if match: | 
|  | filename = match.group(2) | 
|  | if filename == None: | 
|  | continue | 
|  |  | 
|  | if args.regex is not None: | 
|  | if not re.match('^%s$' % args.regex, filename): | 
|  | continue | 
|  | else: | 
|  | if not re.match('^%s$' % args.iregex, filename, re.IGNORECASE): | 
|  | continue | 
|  |  | 
|  | match = re.search('^@@.*\+(\d+)(,(\d+))?', line) | 
|  | if match: | 
|  | start_line = int(match.group(1)) | 
|  | line_count = 1 | 
|  | if match.group(3): | 
|  | line_count = int(match.group(3)) | 
|  | if line_count == 0: | 
|  | continue | 
|  | end_line = start_line + line_count - 1; | 
|  | lines_by_file.setdefault(filename, []).append([start_line, end_line]) | 
|  |  | 
|  | if len(lines_by_file) == 0: | 
|  | print("No relevant changes found.") | 
|  | sys.exit(0) | 
|  |  | 
|  | line_filter_json = json.dumps( | 
|  | [{"name" : name, "lines" : lines_by_file[name]} for name in lines_by_file], | 
|  | separators = (',', ':')) | 
|  |  | 
|  | quote = ""; | 
|  | if sys.platform == 'win32': | 
|  | line_filter_json=re.sub(r'"', r'"""', line_filter_json) | 
|  | else: | 
|  | quote = "'"; | 
|  |  | 
|  | # Run clang-tidy on files containing changes. | 
|  | command = [args.clang_tidy_binary] | 
|  | command.append('-line-filter=' + quote + line_filter_json + quote) | 
|  | if args.fix: | 
|  | command.append('-fix') | 
|  | if args.checks != '': | 
|  | command.append('-checks=' + quote + args.checks + quote) | 
|  | if args.quiet: | 
|  | command.append('-quiet') | 
|  | if args.build_path is not None: | 
|  | command.append('-p=%s' % args.build_path) | 
|  | command.extend(lines_by_file.keys()) | 
|  | for arg in args.extra_arg: | 
|  | command.append('-extra-arg=%s' % arg) | 
|  | for arg in args.extra_arg_before: | 
|  | command.append('-extra-arg-before=%s' % arg) | 
|  | command.extend(clang_tidy_args) | 
|  |  | 
|  | sys.exit(subprocess.call(' '.join(command), shell=True)) | 
|  |  | 
|  | if __name__ == '__main__': | 
|  | main() |