[DFAJumpThreading] Remove incoming StartBlock from all phis when unfolding select...
[llvm-project.git] / clang-tools-extra / test / clang-tidy / check_clang_tidy.py
blob53ffca0bad8d06fd92d0e9c5663ecc8acc464208
1 #!/usr/bin/env python3
3 # ===- check_clang_tidy.py - ClangTidy Test Helper ------------*- python -*--===#
5 # Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
6 # See https://llvm.org/LICENSE.txt for license information.
7 # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
9 # ===------------------------------------------------------------------------===#
11 r"""
12 ClangTidy Test Helper
13 =====================
15 This script runs clang-tidy in fix mode and verify fixes, messages or both.
17 Usage:
18 check_clang_tidy.py [-resource-dir=<resource-dir>] \
19 [-assume-filename=<file-with-source-extension>] \
20 [-check-suffix=<comma-separated-file-check-suffixes>] \
21 [-check-suffixes=<comma-separated-file-check-suffixes>] \
22 [-std=c++(98|11|14|17|20)[-or-later]] \
23 <source-file> <check-name> <temp-file> \
24 -- [optional clang-tidy arguments]
26 Example:
27 // RUN: %check_clang_tidy %s llvm-include-order %t -- -- -isystem %S/Inputs
29 Notes:
30 -std=c++(98|11|14|17|20)-or-later:
31 This flag will cause multiple runs within the same check_clang_tidy
32 execution. Make sure you don't have shared state across these runs.
33 """
35 import argparse
36 import os
37 import re
38 import subprocess
39 import sys
42 def write_file(file_name, text):
43 with open(file_name, "w", encoding="utf-8") as f:
44 f.write(text)
45 f.truncate()
48 def try_run(args, raise_error=True):
49 try:
50 process_output = subprocess.check_output(args, stderr=subprocess.STDOUT).decode(
51 errors="ignore"
53 except subprocess.CalledProcessError as e:
54 process_output = e.output.decode(errors="ignore")
55 print("%s failed:\n%s" % (" ".join(args), process_output))
56 if raise_error:
57 raise
58 return process_output
61 # This class represents the appearance of a message prefix in a file.
62 class MessagePrefix:
63 def __init__(self, label):
64 self.has_message = False
65 self.prefixes = []
66 self.label = label
68 def check(self, file_check_suffix, input_text):
69 self.prefix = self.label + file_check_suffix
70 self.has_message = self.prefix in input_text
71 if self.has_message:
72 self.prefixes.append(self.prefix)
73 return self.has_message
76 class CheckRunner:
77 def __init__(self, args, extra_args):
78 self.resource_dir = args.resource_dir
79 self.assume_file_name = args.assume_filename
80 self.input_file_name = args.input_file_name
81 self.check_name = args.check_name
82 self.temp_file_name = args.temp_file_name
83 self.original_file_name = self.temp_file_name + ".orig"
84 self.expect_clang_tidy_error = args.expect_clang_tidy_error
85 self.std = args.std
86 self.check_suffix = args.check_suffix
87 self.input_text = ""
88 self.has_check_fixes = False
89 self.has_check_messages = False
90 self.has_check_notes = False
91 self.fixes = MessagePrefix("CHECK-FIXES")
92 self.messages = MessagePrefix("CHECK-MESSAGES")
93 self.notes = MessagePrefix("CHECK-NOTES")
95 file_name_with_extension = self.assume_file_name or self.input_file_name
96 _, extension = os.path.splitext(file_name_with_extension)
97 if extension not in [".c", ".hpp", ".m", ".mm"]:
98 extension = ".cpp"
99 self.temp_file_name = self.temp_file_name + extension
101 self.clang_extra_args = []
102 self.clang_tidy_extra_args = extra_args
103 if "--" in extra_args:
104 i = self.clang_tidy_extra_args.index("--")
105 self.clang_extra_args = self.clang_tidy_extra_args[i + 1 :]
106 self.clang_tidy_extra_args = self.clang_tidy_extra_args[:i]
108 # If the test does not specify a config style, force an empty one; otherwise
109 # auto-detection logic can discover a ".clang-tidy" file that is not related to
110 # the test.
111 if not any(
112 [re.match("^-?-config(-file)?=", arg) for arg in self.clang_tidy_extra_args]
114 self.clang_tidy_extra_args.append("--config={}")
116 if extension in [".m", ".mm"]:
117 self.clang_extra_args = [
118 "-fobjc-abi-version=2",
119 "-fobjc-arc",
120 "-fblocks",
121 ] + self.clang_extra_args
123 if extension in [".cpp", ".hpp", ".mm"]:
124 self.clang_extra_args.append("-std=" + self.std)
126 # Tests should not rely on STL being available, and instead provide mock
127 # implementations of relevant APIs.
128 self.clang_extra_args.append("-nostdinc++")
130 if self.resource_dir is not None:
131 self.clang_extra_args.append("-resource-dir=%s" % self.resource_dir)
133 def read_input(self):
134 with open(self.input_file_name, "r", encoding="utf-8") as input_file:
135 self.input_text = input_file.read()
137 def get_prefixes(self):
138 for suffix in self.check_suffix:
139 if suffix and not re.match("^[A-Z0-9\\-]+$", suffix):
140 sys.exit(
141 'Only A..Z, 0..9 and "-" are allowed in check suffixes list,'
142 + ' but "%s" was given' % suffix
145 file_check_suffix = ("-" + suffix) if suffix else ""
147 has_check_fix = self.fixes.check(file_check_suffix, self.input_text)
148 self.has_check_fixes = self.has_check_fixes or has_check_fix
150 has_check_message = self.messages.check(file_check_suffix, self.input_text)
151 self.has_check_messages = self.has_check_messages or has_check_message
153 has_check_note = self.notes.check(file_check_suffix, self.input_text)
154 self.has_check_notes = self.has_check_notes or has_check_note
156 if has_check_note and has_check_message:
157 sys.exit(
158 "Please use either %s or %s but not both"
159 % (self.notes.prefix, self.messages.prefix)
162 if not has_check_fix and not has_check_message and not has_check_note:
163 sys.exit(
164 "%s, %s or %s not found in the input"
165 % (self.fixes.prefix, self.messages.prefix, self.notes.prefix)
168 assert self.has_check_fixes or self.has_check_messages or self.has_check_notes
170 def prepare_test_inputs(self):
171 # Remove the contents of the CHECK lines to avoid CHECKs matching on
172 # themselves. We need to keep the comments to preserve line numbers while
173 # avoiding empty lines which could potentially trigger formatting-related
174 # checks.
175 cleaned_test = re.sub("// *CHECK-[A-Z0-9\\-]*:[^\r\n]*", "//", self.input_text)
176 write_file(self.temp_file_name, cleaned_test)
177 write_file(self.original_file_name, cleaned_test)
179 def run_clang_tidy(self):
180 args = (
182 "clang-tidy",
183 self.temp_file_name,
184 "-fix",
185 "--checks=-*," + self.check_name,
187 + self.clang_tidy_extra_args
188 + ["--"]
189 + self.clang_extra_args
191 if self.expect_clang_tidy_error:
192 args.insert(0, "not")
193 print("Running " + repr(args) + "...")
194 clang_tidy_output = try_run(args)
195 print("------------------------ clang-tidy output -----------------------")
196 print(
197 clang_tidy_output.encode(sys.stdout.encoding, errors="replace").decode(
198 sys.stdout.encoding
201 print("------------------------------------------------------------------")
203 diff_output = try_run(
204 ["diff", "-u", self.original_file_name, self.temp_file_name], False
206 print("------------------------------ Fixes -----------------------------")
207 print(diff_output)
208 print("------------------------------------------------------------------")
209 return clang_tidy_output
211 def check_fixes(self):
212 if self.has_check_fixes:
213 try_run(
215 "FileCheck",
216 "-input-file=" + self.temp_file_name,
217 self.input_file_name,
218 "-check-prefixes=" + ",".join(self.fixes.prefixes),
219 "-strict-whitespace",
223 def check_messages(self, clang_tidy_output):
224 if self.has_check_messages:
225 messages_file = self.temp_file_name + ".msg"
226 write_file(messages_file, clang_tidy_output)
227 try_run(
229 "FileCheck",
230 "-input-file=" + messages_file,
231 self.input_file_name,
232 "-check-prefixes=" + ",".join(self.messages.prefixes),
233 "-implicit-check-not={{warning|error}}:",
237 def check_notes(self, clang_tidy_output):
238 if self.has_check_notes:
239 notes_file = self.temp_file_name + ".notes"
240 filtered_output = [
241 line
242 for line in clang_tidy_output.splitlines()
243 if not ("note: FIX-IT applied" in line)
245 write_file(notes_file, "\n".join(filtered_output))
246 try_run(
248 "FileCheck",
249 "-input-file=" + notes_file,
250 self.input_file_name,
251 "-check-prefixes=" + ",".join(self.notes.prefixes),
252 "-implicit-check-not={{note|warning|error}}:",
256 def run(self):
257 self.read_input()
258 self.get_prefixes()
259 self.prepare_test_inputs()
260 clang_tidy_output = self.run_clang_tidy()
261 self.check_fixes()
262 self.check_messages(clang_tidy_output)
263 self.check_notes(clang_tidy_output)
266 def expand_std(std):
267 if std == "c++98-or-later":
268 return ["c++98", "c++11", "c++14", "c++17", "c++20", "c++23", "c++2c"]
269 if std == "c++11-or-later":
270 return ["c++11", "c++14", "c++17", "c++20", "c++23", "c++2c"]
271 if std == "c++14-or-later":
272 return ["c++14", "c++17", "c++20", "c++23", "c++2c"]
273 if std == "c++17-or-later":
274 return ["c++17", "c++20", "c++23", "c++2c"]
275 if std == "c++20-or-later":
276 return ["c++20", "c++23", "c++2c"]
277 if std == "c++23-or-later":
278 return ["c++23", "c++2c"]
279 return [std]
282 def csv(string):
283 return string.split(",")
286 def parse_arguments():
287 parser = argparse.ArgumentParser()
288 parser.add_argument("-expect-clang-tidy-error", action="store_true")
289 parser.add_argument("-resource-dir")
290 parser.add_argument("-assume-filename")
291 parser.add_argument("input_file_name")
292 parser.add_argument("check_name")
293 parser.add_argument("temp_file_name")
294 parser.add_argument(
295 "-check-suffix",
296 "-check-suffixes",
297 default=[""],
298 type=csv,
299 help="comma-separated list of FileCheck suffixes",
301 parser.add_argument("-std", type=csv, default=["c++11-or-later"])
302 return parser.parse_known_args()
305 def main():
306 args, extra_args = parse_arguments()
308 abbreviated_stds = args.std
309 for abbreviated_std in abbreviated_stds:
310 for std in expand_std(abbreviated_std):
311 args.std = std
312 CheckRunner(args, extra_args).run()
315 if __name__ == "__main__":
316 main()