Index: clang-tidy/tool/run-clang-tidy.py =================================================================== --- clang-tidy/tool/run-clang-tidy.py +++ clang-tidy/tool/run-clang-tidy.py @@ -7,7 +7,6 @@ # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception # #===------------------------------------------------------------------------===# -# FIXME: Integrate with clang-tidy-diff.py """ Parallel clang-tidy runner @@ -60,6 +59,7 @@ else: import queue as queue + def find_compilation_database(path): """Adjusts the directory until a compilation database is found.""" result = './' @@ -77,7 +77,7 @@ return os.path.normpath(os.path.join(directory, f)) -def get_tidy_invocation(f, clang_tidy_binary, checks, tmpdir, build_path, +def get_tidy_invocation(f, lines, clang_tidy_binary, checks, tmpdir, build_path, header_filter, extra_arg, extra_arg_before, quiet, config): """Gets a command line for clang-tidy.""" @@ -87,6 +87,12 @@ else: # Show warnings in all in-project headers by default. start.append('-header-filter=^' + build_path + '/.*') + + if lines is not None: + line_json = json.dumps([{"name": f, "lines": lines}], separators=(',', ':')) + # Run clang-tidy on files containing changes. + start.append('-line-filter=' + line_json) + if checks: start.append('-checks=' + checks) if tmpdir is not None: @@ -159,8 +165,9 @@ def run_tidy(args, tmpdir, build_path, queue, lock, failed_files): """Takes filenames out of queue and runs clang-tidy on them.""" while True: - name = queue.get() - invocation = get_tidy_invocation(name, args.clang_tidy_binary, args.checks, + (name, lines) = queue.get() + invocation = get_tidy_invocation(name, lines, + args.clang_tidy_binary, args.checks, tmpdir, build_path, args.header_filter, args.extra_arg, args.extra_arg_before, args.quiet, args.config) @@ -176,11 +183,42 @@ queue.task_done() +def get_changed_lines(prefix_len): + iregex = '^%s$' % r'.*\.(cpp|cc|c\+\+|cxx|c|cl|h|hpp|m|mm|inc)' + lines_by_file = {} + filename = None + for line in sys.stdin: + match = re.search('^\+\+\+\ \"?(.*?/){%s}([^ \t\n\"]*)' % prefix_len, line) + if match: + filename = match.group(2) + if filename is None: + continue + + if not re.match(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]) + + return lines_by_file + + def main(): parser = argparse.ArgumentParser(description='Runs clang-tidy over all files ' 'in a compilation database. Requires ' 'clang-tidy and clang-apply-replacements in ' '$PATH.') + parser.add_argument('--diff', default=None, const=2, nargs='?', type=int, + help='check only the diff read from stdin.' + ' Strip the smallest prefix containing DIFF[=2] slashes') parser.add_argument('-clang-tidy-binary', metavar='PATH', default='clang-tidy', help='path to clang-tidy binary') @@ -249,11 +287,6 @@ print("Unable to run clang-tidy.", file=sys.stderr) sys.exit(1) - # Load the database and extract all files. - database = json.load(open(os.path.join(build_path, db_path))) - files = [make_absolute(entry['file'], entry['directory']) - for entry in database] - max_task = args.j if max_task == 0: max_task = multiprocessing.cpu_count() @@ -265,6 +298,16 @@ # Build up a big regexy filter from all command line arguments. file_name_re = re.compile('|'.join(args.files)) + files = None + changed_lines = None + if args.diff is not None: + changed_lines = get_changed_lines(args.diff) + files = [(k, v) for k,v in changed_lines.items() if file_name_re.search(k)] + else: + # Load the database and extract affected files. + database = json.load(open(os.path.join(build_path, db_path))) + all_files = (make_absolute(entry['file'], entry['directory']) for entry in database) + files = [(x, None) for x in all_files if file_name_re.search(x)] return_code = 0 try: @@ -281,8 +324,7 @@ # Fill the queue with files. for name in files: - if file_name_re.search(name): - task_queue.put(name) + task_queue.put(name) # Wait for all threads to be done. task_queue.join() Index: test/clang-tidy/run-clang-tidy-diff.cpp =================================================================== --- /dev/null +++ test/clang-tidy/run-clang-tidy-diff.cpp @@ -0,0 +1,27 @@ +// REQUIRES: shell +// RUN: mkdir -p "%t" +// RUN: cd "%t" +// RUN: sed 's/placeholder_for_f/f/' %s > diff_to.cpp +// RUN: echo '[{"directory": "%t", "command": "clang++ -o test.o -std=c++11 diff_to.cpp", "file": "diff_to.cpp"}]' > compile_commands.json +// RUN: clang-tidy -checks=-*,modernize-use-override diff_to.cpp -- -std=c++11 | FileCheck -check-prefix=CHECK-SANITY %s +// RUN: not diff -U0 %s diff_to.cpp | %run_clang_tidy --diff=0 -checks=-*,modernize-use-override 2>&1 | FileCheck %s +// RUN: not diff -U0 %s diff_to.cpp | %run_clang_tidy --diff=0 -checks=-*,modernize-use-override -quiet 2>&1 | FileCheck -check-prefix=CHECK-QUIET %s +// RUN: not diff -U0 %s diff_to.cpp | %run_clang_tidy --diff=0 -checks=-*,modernize-use-override 2>&1 | FileCheck -check-prefix=CHECK %s +struct A { + virtual void f() {} + virtual void g() {} +}; +// CHECK-NOT: warning: +// CHECK-QUIET-NOT: warning: +struct B : public A { + void placeholder_for_f() {} +// CHECK-SANITY: [[@LINE-1]]:8: warning: annotate this +// CHECK: [[@LINE-2]]:8: warning: annotate this +// CHECK-QUIET: [[@LINE-3]]:8: warning: annotate this + void g() {} +// CHECK-SANITY: [[@LINE-1]]:8: warning: annotate this +// CHECK-NOT: warning: +// CHECK-QUIET-NOT: warning: +}; +// CHECK-SANITY-NOT: Suppressed +// CHECK-QUIET-NOT: Suppressed