forked from OSchip/llvm-project
250 lines
8.4 KiB
Python
Executable File
250 lines
8.4 KiB
Python
Executable File
#!/usr/bin/env python
|
|
#
|
|
#===- check_clang_tidy.py - ClangTidy Test Helper ------------*- python -*--===#
|
|
#
|
|
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
# See https://llvm.org/LICENSE.txt for license information.
|
|
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
#
|
|
#===------------------------------------------------------------------------===#
|
|
|
|
r"""
|
|
ClangTidy Test Helper
|
|
=====================
|
|
|
|
This script runs clang-tidy in fix mode and verify fixes, messages or both.
|
|
|
|
Usage:
|
|
check_clang_tidy.py [-resource-dir=<resource-dir>] \
|
|
[-assume-filename=<file-with-source-extension>] \
|
|
[-check-suffix=<comma-separated-file-check-suffixes>] \
|
|
[-check-suffixes=<comma-separated-file-check-suffixes>] \
|
|
<source-file> <check-name> <temp-file> \
|
|
-- [optional clang-tidy arguments]
|
|
|
|
Example:
|
|
// RUN: %check_clang_tidy %s llvm-include-order %t -- -- -isystem %S/Inputs
|
|
"""
|
|
|
|
import argparse
|
|
import os
|
|
import re
|
|
import subprocess
|
|
import sys
|
|
|
|
|
|
def write_file(file_name, text):
|
|
with open(file_name, 'w') as f:
|
|
f.write(text)
|
|
f.truncate()
|
|
|
|
|
|
def run_test_once(args, extra_args):
|
|
resource_dir = args.resource_dir
|
|
assume_file_name = args.assume_filename
|
|
input_file_name = args.input_file_name
|
|
check_name = args.check_name
|
|
temp_file_name = args.temp_file_name
|
|
expect_clang_tidy_error = args.expect_clang_tidy_error
|
|
std = args.std
|
|
|
|
file_name_with_extension = assume_file_name or input_file_name
|
|
_, extension = os.path.splitext(file_name_with_extension)
|
|
if extension not in ['.c', '.hpp', '.m', '.mm']:
|
|
extension = '.cpp'
|
|
temp_file_name = temp_file_name + extension
|
|
|
|
clang_tidy_extra_args = extra_args
|
|
clang_extra_args = []
|
|
if '--' in extra_args:
|
|
i = clang_tidy_extra_args.index('--')
|
|
clang_extra_args = clang_tidy_extra_args[i + 1:]
|
|
clang_tidy_extra_args = clang_tidy_extra_args[:i]
|
|
|
|
# If the test does not specify a formatting style, force "none"; otherwise
|
|
# autodetection logic can discover a ".clang-tidy" file that is not related to
|
|
# the test.
|
|
if not any(
|
|
[arg.startswith('-format-style=') for arg in clang_tidy_extra_args]):
|
|
clang_tidy_extra_args.append('-format-style=none')
|
|
|
|
if extension in ['.m', '.mm']:
|
|
clang_extra_args = ['-fobjc-abi-version=2', '-fobjc-arc', '-fblocks'] + \
|
|
clang_extra_args
|
|
|
|
if extension in ['.cpp', '.hpp', '.mm']:
|
|
clang_extra_args.append('-std=' + std)
|
|
|
|
# Tests should not rely on STL being available, and instead provide mock
|
|
# implementations of relevant APIs.
|
|
clang_extra_args.append('-nostdinc++')
|
|
|
|
if resource_dir is not None:
|
|
clang_extra_args.append('-resource-dir=%s' % resource_dir)
|
|
|
|
with open(input_file_name, 'r') as input_file:
|
|
input_text = input_file.read()
|
|
|
|
check_fixes_prefixes = []
|
|
check_messages_prefixes = []
|
|
check_notes_prefixes = []
|
|
|
|
has_check_fixes = False
|
|
has_check_messages = False
|
|
has_check_notes = False
|
|
|
|
for check in args.check_suffix:
|
|
if check and not re.match('^[A-Z0-9\-]+$', check):
|
|
sys.exit('Only A..Z, 0..9 and "-" are ' +
|
|
'allowed in check suffixes list, but "%s" was given' % (check))
|
|
|
|
file_check_suffix = ('-' + check) if check else ''
|
|
check_fixes_prefix = 'CHECK-FIXES' + file_check_suffix
|
|
check_messages_prefix = 'CHECK-MESSAGES' + file_check_suffix
|
|
check_notes_prefix = 'CHECK-NOTES' + file_check_suffix
|
|
|
|
has_check_fix = check_fixes_prefix in input_text
|
|
has_check_message = check_messages_prefix in input_text
|
|
has_check_note = check_notes_prefix in input_text
|
|
|
|
if has_check_note and has_check_message:
|
|
sys.exit('Please use either %s or %s but not both' %
|
|
(check_notes_prefix, check_messages_prefix))
|
|
|
|
if not has_check_fix and not has_check_message and not has_check_note:
|
|
sys.exit('%s, %s or %s not found in the input' %
|
|
(check_fixes_prefix, check_messages_prefix, check_notes_prefix))
|
|
|
|
has_check_fixes = has_check_fixes or has_check_fix
|
|
has_check_messages = has_check_messages or has_check_message
|
|
has_check_notes = has_check_notes or has_check_note
|
|
|
|
check_fixes_prefixes.append(check_fixes_prefix)
|
|
check_messages_prefixes.append(check_messages_prefix)
|
|
check_notes_prefixes.append(check_notes_prefix)
|
|
|
|
assert has_check_fixes or has_check_messages or has_check_notes
|
|
# Remove the contents of the CHECK lines to avoid CHECKs matching on
|
|
# themselves. We need to keep the comments to preserve line numbers while
|
|
# avoiding empty lines which could potentially trigger formatting-related
|
|
# checks.
|
|
cleaned_test = re.sub('// *CHECK-[A-Z0-9\-]*:[^\r\n]*', '//', input_text)
|
|
|
|
write_file(temp_file_name, cleaned_test)
|
|
|
|
original_file_name = temp_file_name + ".orig"
|
|
write_file(original_file_name, cleaned_test)
|
|
|
|
args = ['clang-tidy', temp_file_name, '-fix', '--checks=-*,' + check_name] + \
|
|
clang_tidy_extra_args + ['--'] + clang_extra_args
|
|
if expect_clang_tidy_error:
|
|
args.insert(0, 'not')
|
|
print('Running ' + repr(args) + '...')
|
|
try:
|
|
clang_tidy_output = \
|
|
subprocess.check_output(args, stderr=subprocess.STDOUT).decode()
|
|
except subprocess.CalledProcessError as e:
|
|
print('clang-tidy failed:\n' + e.output.decode())
|
|
raise
|
|
|
|
print('------------------------ clang-tidy output -----------------------\n' +
|
|
clang_tidy_output +
|
|
'\n------------------------------------------------------------------')
|
|
|
|
try:
|
|
diff_output = subprocess.check_output(
|
|
['diff', '-u', original_file_name, temp_file_name],
|
|
stderr=subprocess.STDOUT)
|
|
except subprocess.CalledProcessError as e:
|
|
diff_output = e.output
|
|
|
|
print('------------------------------ Fixes -----------------------------\n' +
|
|
diff_output.decode(errors='ignore') +
|
|
'\n------------------------------------------------------------------')
|
|
|
|
if has_check_fixes:
|
|
try:
|
|
subprocess.check_output(
|
|
['FileCheck', '-input-file=' + temp_file_name, input_file_name,
|
|
'-check-prefixes=' + ','.join(check_fixes_prefixes),
|
|
'-strict-whitespace'],
|
|
stderr=subprocess.STDOUT)
|
|
except subprocess.CalledProcessError as e:
|
|
print('FileCheck failed:\n' + e.output.decode())
|
|
raise
|
|
|
|
if has_check_messages:
|
|
messages_file = temp_file_name + '.msg'
|
|
write_file(messages_file, clang_tidy_output)
|
|
try:
|
|
subprocess.check_output(
|
|
['FileCheck', '-input-file=' + messages_file, input_file_name,
|
|
'-check-prefixes=' + ','.join(check_messages_prefixes),
|
|
'-implicit-check-not={{warning|error}}:'],
|
|
stderr=subprocess.STDOUT)
|
|
except subprocess.CalledProcessError as e:
|
|
print('FileCheck failed:\n' + e.output.decode())
|
|
raise
|
|
|
|
if has_check_notes:
|
|
notes_file = temp_file_name + '.notes'
|
|
filtered_output = [line for line in clang_tidy_output.splitlines()
|
|
if not "note: FIX-IT applied" in line]
|
|
write_file(notes_file, '\n'.join(filtered_output))
|
|
try:
|
|
subprocess.check_output(
|
|
['FileCheck', '-input-file=' + notes_file, input_file_name,
|
|
'-check-prefixes=' + ','.join(check_notes_prefixes),
|
|
'-implicit-check-not={{note|warning|error}}:'],
|
|
stderr=subprocess.STDOUT)
|
|
except subprocess.CalledProcessError as e:
|
|
print('FileCheck failed:\n' + e.output.decode())
|
|
raise
|
|
|
|
|
|
def expand_std(std):
|
|
if std == 'c++98-or-later':
|
|
return ['c++98', 'c++11', 'c++14', 'c++17', 'c++2a']
|
|
if std == 'c++11-or-later':
|
|
return ['c++11', 'c++14', 'c++17', 'c++2a']
|
|
if std == 'c++14-or-later':
|
|
return ['c++14', 'c++17', 'c++2a']
|
|
if std == 'c++17-or-later':
|
|
return ['c++17', 'c++2a']
|
|
if std == 'c++2a-or-later':
|
|
return ['c++2a']
|
|
return [std]
|
|
|
|
|
|
def csv(string):
|
|
return string.split(',')
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument('-expect-clang-tidy-error', action='store_true')
|
|
parser.add_argument('-resource-dir')
|
|
parser.add_argument('-assume-filename')
|
|
parser.add_argument('input_file_name')
|
|
parser.add_argument('check_name')
|
|
parser.add_argument('temp_file_name')
|
|
parser.add_argument(
|
|
'-check-suffix',
|
|
'-check-suffixes',
|
|
default=[''],
|
|
type=csv,
|
|
help='comma-separated list of FileCheck suffixes')
|
|
parser.add_argument('-std', type=csv, default=['c++11-or-later'])
|
|
|
|
args, extra_args = parser.parse_known_args()
|
|
|
|
abbreviated_stds = args.std
|
|
for abbreviated_std in abbreviated_stds:
|
|
for std in expand_std(abbreviated_std):
|
|
args.std = std
|
|
run_test_once(args, extra_args)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|