clang-tools 23.0.0git
add_new_check.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2#
3# ===-----------------------------------------------------------------------===#
4#
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
8#
9# ===-----------------------------------------------------------------------===#
10
11import argparse
12import itertools
13import os
14import re
15import sys
16import textwrap
17from operator import methodcaller
18from typing import Optional, Tuple, Match
19
20
21# Adapts the module's CMakelist file. Returns 'True' if it could add a new
22# entry and 'False' if the entry already existed.
23def adapt_cmake(module_path: str, check_name_camel: str) -> bool:
24 filename = os.path.join(module_path, "CMakeLists.txt")
25
26 # The documentation files are encoded using UTF-8, however on Windows the
27 # default encoding might be different (e.g. CP-1252). To make sure UTF-8 is
28 # always used, use `open(filename, mode, encoding='utf8')` for reading and
29 # writing files here and elsewhere.
30 with open(filename, "r", encoding="utf8") as f:
31 lines = f.readlines()
32
33 cpp_file = f"{check_name_camel}.cpp"
34
35 # Figure out whether this check already exists.
36 for line in lines:
37 if line.strip() == cpp_file:
38 return False
39
40 print(f"Updating {filename}...")
41 with open(filename, "w", encoding="utf8", newline="\n") as f:
42 cpp_found = False
43 file_added = False
44 for line in lines:
45 cpp_line = line.strip().endswith(".cpp")
46 if (not file_added) and (cpp_line or cpp_found):
47 cpp_found = True
48 if (line.strip() > cpp_file) or (not cpp_line):
49 f.write(f" {cpp_file}\n")
50 file_added = True
51 f.write(line)
52
53 return True
54
55
56# Adds a header for the new check.
58 module_path: str,
59 module: str,
60 namespace: str,
61 check_name: str,
62 check_name_camel: str,
63 description: str,
64 lang_restrict: str,
65) -> None:
66 wrapped_desc = "\n".join(
67 textwrap.wrap(
68 description, width=80, initial_indent="/// ", subsequent_indent="/// "
69 )
70 )
71 if lang_restrict:
72 override_supported = """
73 bool isLanguageVersionSupported(const LangOptions &LangOpts) const override {
74 return %s;
75 }""" % (
76 lang_restrict % {"lang": "LangOpts"}
77 )
78 else:
79 override_supported = ""
80 filename = f"{os.path.join(module_path, check_name_camel)}.h"
81 print(f"Creating {filename}...")
82 with open(filename, "w", encoding="utf8", newline="\n") as f:
83 header_guard = (
84 f"LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_{module.upper()}_"
85 f"{check_name_camel.upper()}_H"
86 )
87 f.write(
88 """\
89//===----------------------------------------------------------------------===//
90//
91// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
92// See https://llvm.org/LICENSE.txt for license information.
93// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
94//
95//===----------------------------------------------------------------------===//
96
97#ifndef %(header_guard)s
98#define %(header_guard)s
99
100#include "../ClangTidyCheck.h"
101
102namespace clang::tidy::%(namespace)s {
103
104%(description)s
105///
106/// For the user-facing documentation see:
107/// https://clang.llvm.org/extra/clang-tidy/checks/%(module)s/%(check_name)s.html
108class %(check_name_camel)s : public ClangTidyCheck {
109public:
110 %(check_name_camel)s(StringRef Name, ClangTidyContext *Context)
111 : ClangTidyCheck(Name, Context) {}
112 void registerMatchers(ast_matchers::MatchFinder *Finder) override;
113 void check(const ast_matchers::MatchFinder::MatchResult &Result) override;%(override_supported)s
114};
115
116} // namespace clang::tidy::%(namespace)s
117
118#endif // %(header_guard)s
119"""
120 % {
121 "header_guard": header_guard,
122 "check_name_camel": check_name_camel,
123 "check_name": check_name,
124 "module": module,
125 "namespace": namespace,
126 "description": wrapped_desc,
127 "override_supported": override_supported,
128 }
129 )
130
131
132# Adds the implementation of the new check.
134 module_path: str, module: str, namespace: str, check_name_camel: str
135) -> None:
136 filename = f"{os.path.join(module_path, check_name_camel)}.cpp"
137 print(f"Creating {filename}...")
138 with open(filename, "w", encoding="utf8", newline="\n") as f:
139 f.write(
140 """\
141//===----------------------------------------------------------------------===//
142//
143// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
144// See https://llvm.org/LICENSE.txt for license information.
145// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
146//
147//===----------------------------------------------------------------------===//
148
149#include "%(check_name)s.h"
150#include "clang/ASTMatchers/ASTMatchFinder.h"
151
152using namespace clang::ast_matchers;
153
154namespace clang::tidy::%(namespace)s {
155
156void %(check_name)s::registerMatchers(MatchFinder *Finder) {
157 // FIXME: Add matchers.
158 Finder->addMatcher(functionDecl().bind("x"), this);
159}
160
161void %(check_name)s::check(const MatchFinder::MatchResult &Result) {
162 // FIXME: Add callback implementation.
163 const auto *MatchedDecl = Result.Nodes.getNodeAs<FunctionDecl>("x");
164 if (!MatchedDecl->getIdentifier() || MatchedDecl->getName().starts_with("awesome_"))
165 return;
166 diag(MatchedDecl->getLocation(), "function %%0 is insufficiently awesome")
167 << MatchedDecl
168 << FixItHint::CreateInsertion(MatchedDecl->getLocation(), "awesome_");
169 diag(MatchedDecl->getLocation(), "insert 'awesome'", DiagnosticIDs::Note);
170}
171
172} // namespace clang::tidy::%(namespace)s
173"""
174 % {"check_name": check_name_camel, "namespace": namespace}
175 )
176
177
178# Returns the source filename that implements the module.
179def get_module_filename(module_path: str, module: str) -> str:
180 modulecpp = list(
181 filter(
182 lambda p: p.lower() == f"{module.lower()}tidymodule.cpp",
183 os.listdir(module_path),
184 )
185 )[0]
186 return os.path.join(module_path, modulecpp)
187
188
189# Modifies the module to include the new check.
191 module_path: str, module: str, check_name: str, check_name_camel: str
192) -> None:
193 filename = get_module_filename(module_path, module)
194 with open(filename, "r", encoding="utf8") as f:
195 lines = f.readlines()
196
197 print(f"Updating {filename}...")
198 with open(filename, "w", encoding="utf8", newline="\n") as f:
199 header_added = False
200 header_found = False
201 check_added = False
202 check_fq_name = f"{module}-{check_name}"
203 check_decl = (
204 f" CheckFactories.registerCheck<{check_name_camel}>(\n"
205 f' "{check_fq_name}");\n'
206 )
207
208 lines_iter = iter(lines)
209 try:
210 while True:
211 line = next(lines_iter)
212 if not header_added:
213 if match := re.search('#include "(.*)"', line):
214 header_found = True
215 if match.group(1) > check_name_camel:
216 header_added = True
217 f.write(f'#include "{check_name_camel}.h"\n')
218 elif header_found:
219 header_added = True
220 f.write(f'#include "{check_name_camel}.h"\n')
221
222 if not check_added:
223 if line.strip() == "}":
224 check_added = True
225 f.write(check_decl)
226 else:
227 prev_line = None
228 if match := re.search(
229 r'registerCheck<(.*)> *\‍( *(?:"([^"]*)")?', line
230 ):
231 current_check_name = match.group(2)
232 if current_check_name is None:
233 # If we didn't find the check name on this line, look on the
234 # next one.
235 prev_line = line
236 line = next(lines_iter)
237 match = re.search(' *"([^"]*)"', line)
238 if match:
239 current_check_name = match.group(1)
240 assert current_check_name
241 if current_check_name > check_fq_name:
242 check_added = True
243 f.write(check_decl)
244 if prev_line:
245 f.write(prev_line)
246 f.write(line)
247 except StopIteration:
248 pass
249
250
251# Adds a release notes entry.
253 module_path: str, module: str, check_name: str, description: str
254) -> None:
255 wrapped_desc = "\n".join(
256 textwrap.wrap(
257 description, width=80, initial_indent=" ", subsequent_indent=" "
258 )
259 )
260 check_name_dashes = f"{module}-{check_name}"
261 filename = os.path.normpath(
262 os.path.join(module_path, "../../docs/ReleaseNotes.rst")
263 )
264 with open(filename, "r", encoding="utf8") as f:
265 lines = f.readlines()
266
267 lineMatcher = re.compile("New checks")
268 nextSectionMatcher = re.compile("New check aliases")
269 checkMatcher = re.compile("- New :doc:`(.*)")
270
271 print(f"Updating {filename}...")
272 with open(filename, "w", encoding="utf8", newline="\n") as f:
273 note_added = False
274 header_found = False
275 add_note_here = False
276
277 for line in lines:
278 if not note_added:
279 if match_check := checkMatcher.match(line):
280 last_check = match_check.group(1)
281 if last_check > check_name_dashes:
282 add_note_here = True
283
284 if nextSectionMatcher.match(line):
285 add_note_here = True
286
287 if lineMatcher.match(line):
288 header_found = True
289 f.write(line)
290 continue
291
292 if line.startswith("^^^^"):
293 f.write(line)
294 continue
295
296 if header_found and add_note_here:
297 if not line.startswith("^^^^"):
298 f.write(
299 f"""- New :doc:`{check_name_dashes}
300 <clang-tidy/checks/{module}/{check_name}>` check.
301
302{wrapped_desc}
303
304"""
305 )
306 note_added = True
307
308 f.write(line)
309
310
311# Adds a test for the check.
313 module_path: str,
314 module: str,
315 check_name: str,
316 test_extension: str,
317 test_standard: Optional[str],
318) -> None:
319 test_standard = f"-std={test_standard}-or-later " if test_standard else ""
320 check_name_dashes = f"{module}-{check_name}"
321 filename = os.path.normpath(
322 os.path.join(
323 module_path,
324 "..",
325 "..",
326 "test",
327 "clang-tidy",
328 "checkers",
329 module,
330 f"{check_name}.{test_extension}",
331 )
332 )
333 print(f"Creating {filename}...")
334 with open(filename, "w", encoding="utf8", newline="\n") as f:
335 f.write(
336 """\
337// RUN: %%check_clang_tidy %(standard)s%%s %(check_name_dashes)s %%t
338
339// FIXME: Add something that triggers the check here.
340void f();
341// CHECK-MESSAGES: :[[@LINE-1]]:6: warning: function 'f' is insufficiently awesome [%(check_name_dashes)s]
342
343// FIXME: Verify the applied fix.
344// * Make the CHECK patterns specific enough and try to make verified lines
345// unique to avoid incorrect matches.
346// * Use {{}} for regular expressions.
347// CHECK-FIXES: {{^}}void awesome_f();{{$}}
348
349// FIXME: Add something that doesn't trigger the check here.
350void awesome_f2();
351"""
352 % {"check_name_dashes": check_name_dashes, "standard": test_standard}
353 )
354
355
356def get_actual_filename(dirname: str, filename: str) -> str:
357 if not os.path.isdir(dirname):
358 return ""
359 name = os.path.join(dirname, filename)
360 if os.path.isfile(name):
361 return name
362 caselessname = filename.lower()
363 for file in os.listdir(dirname):
364 if file.lower() == caselessname:
365 return os.path.join(dirname, file)
366 return ""
367
368
369# Recreates the list of checks in the docs/clang-tidy/checks directory.
370def update_checks_list(clang_tidy_path: str) -> None:
371 docs_dir = os.path.join(clang_tidy_path, "../docs/clang-tidy/checks")
372 filename = os.path.normpath(os.path.join(docs_dir, "list.rst"))
373 # Read the content of the current list.rst file
374 with open(filename, "r", encoding="utf8") as f:
375 lines = f.readlines()
376 # Get all existing docs
377 doc_files = []
378 for subdir in filter(
379 lambda s: os.path.isdir(os.path.join(docs_dir, s)), os.listdir(docs_dir)
380 ):
381 for file in filter(
382 methodcaller("endswith", ".rst"), os.listdir(os.path.join(docs_dir, subdir))
383 ):
384 doc_files.append((subdir, file))
385 doc_files.sort()
386
387 # We couldn't find the source file from the check name, so try to find the
388 # class name that corresponds to the check in the module file.
389 def filename_from_module(module_name: str, check_name: str) -> str:
390 module_path = os.path.join(clang_tidy_path, module_name)
391 if not os.path.isdir(module_path):
392 return ""
393 module_file = get_module_filename(module_path, module_name)
394 if not os.path.isfile(module_file):
395 return ""
396 with open(module_file, "r") as f:
397 code = f.read()
398 full_check_name = f"{module_name}-{check_name}"
399 if (name_pos := code.find(f'"{full_check_name}"')) == -1:
400 return ""
401 if (stmt_end_pos := code.find(";", name_pos)) == -1:
402 return ""
403 if (stmt_start_pos := code.rfind(";", 0, name_pos)) == -1 and (
404 stmt_start_pos := code.rfind("{", 0, name_pos)
405 ) == -1:
406 return ""
407 stmt = code[stmt_start_pos + 1 : stmt_end_pos]
408 matches = re.search(r'registerCheck<([^>:]*)>\‍(\s*"([^"]*)"\s*\‍)', stmt)
409 if matches and matches[2] == full_check_name:
410 class_name = matches[1]
411 if "::" in class_name:
412 parts = class_name.split("::")
413 class_name = parts[-1]
414 class_path = os.path.join(
415 clang_tidy_path, module_name, "..", *parts[0:-1]
416 )
417 else:
418 class_path = os.path.join(clang_tidy_path, module_name)
419 return get_actual_filename(class_path, f"{class_name}.cpp")
420
421 return ""
422
423 # Examine code looking for a c'tor definition to get the base class name.
424 def get_base_class(code: str, check_file: str) -> str:
425 check_class_name = os.path.splitext(os.path.basename(check_file))[0]
426 ctor_pattern = rf"{check_class_name}\‍([^:]*\‍)\s*:\s*([A-Z][A-Za-z0-9]*Check)\‍("
427 matches = re.search(rf"\s+{check_class_name}::{ctor_pattern}", code)
428
429 # The constructor might be inline in the header.
430 if not matches:
431 header_file = f"{os.path.splitext(check_file)[0]}.h"
432 if not os.path.isfile(header_file):
433 return ""
434 with open(header_file, encoding="utf8") as f:
435 code = f.read()
436 matches = re.search(rf" {ctor_pattern}", code)
437
438 if matches and matches[1] != "ClangTidyCheck":
439 return matches[1]
440 return ""
441
442 # Some simple heuristics to figure out if a check has an autofix or not.
443 def has_fixits(code: str) -> bool:
444 for needle in [
445 "FixItHint",
446 "ReplacementText",
447 "fixit",
448 "FixIt",
449 "TransformerClangTidyCheck",
450 ]:
451 if needle in code:
452 return True
453 return False
454
455 # Try to figure out of the check supports fixits.
456 def has_auto_fix(check_name: str) -> str:
457 dirname, _, check_name = check_name.partition("-")
458
459 check_file = get_actual_filename(
460 os.path.join(clang_tidy_path, dirname),
461 f"{get_camel_check_name(check_name)}.cpp",
462 )
463 if not os.path.isfile(check_file):
464 # Some older checks don't end with 'Check.cpp'
465 check_file = get_actual_filename(
466 os.path.join(clang_tidy_path, dirname),
467 f"{get_camel_name(check_name)}.cpp",
468 )
469 if not os.path.isfile(check_file):
470 # Some checks aren't in a file based on the check name.
471 check_file = filename_from_module(dirname, check_name)
472 if not (check_file and os.path.isfile(check_file)):
473 return ""
474
475 with open(check_file, encoding="utf8") as f:
476 code = f.read()
477 if has_fixits(code):
478 return ' "Yes"'
479
480 if base_class := get_base_class(code, check_file):
481 base_file = os.path.join(clang_tidy_path, dirname, f"{base_class}.cpp")
482 if os.path.isfile(base_file):
483 with open(base_file, encoding="utf8") as f:
484 code = f.read()
485 if has_fixits(code):
486 return ' "Yes"'
487
488 return ""
489
490 def detect_alias_target(check_name: str, content: str) -> Optional[str]:
491 """Return the :doc: target for non-redirect alias pages.
492
493 This recognizes pages that keep their own documentation content, but
494 whose paragraph explicitly states that the current check is an
495 alias of another check.
496 """
497 paragraphs = [
498 re.sub(r"\s+", " ", paragraph.strip())
499 for paragraph in re.split(r"\n\s*\n", content)
500 if paragraph.strip()
501 ]
502
503 self_alias = re.compile(
504 r"^This check is an alias(?: of check| for)\b",
505 re.IGNORECASE,
506 )
507 named_alias = re.compile(
508 rf"^The\s+`?{re.escape(check_name)}(?:\s+check)?`?"
509 rf"(?:\s+check)?\s+is\s+an\s+alias,?\s+please\s+see\b",
510 re.IGNORECASE,
511 )
512
513 for paragraph in paragraphs:
514 if self_alias.search(paragraph) or named_alias.search(paragraph):
515 if match := re.search(r":doc:`[^`<]+?<([^>]+)>`", paragraph):
516 return match.group(1)
517 if match := re.search(r"`[^`<]+?<(.+?)\.html(?:#[^>]+)?>`_", paragraph):
518 return match.group(1)
519 return None
520
521 def process_doc(doc_file: Tuple[str, str]) -> Tuple[str, Optional[str]]:
522 check_name = f"{doc_file[0]}-{doc_file[1].replace('.rst', '')}"
523
524 with open(os.path.join(docs_dir, *doc_file), "r", encoding="utf8") as doc:
525 content = doc.read()
526
527 if match := re.search(".*:orphan:.*", content):
528 # Orphan page, don't list it.
529 return "", None
530
531 return check_name, detect_alias_target(check_name, content)
532
533 def format_link(doc_file: Tuple[str, str]) -> str:
534 check_name, match = process_doc(doc_file)
535 if not match and check_name and not check_name.startswith("clang-analyzer-"):
536 return (
537 f" :doc:`{check_name} <{doc_file[0]}/{doc_file[1].replace('.rst', '')}>`,"
538 f"{has_auto_fix(check_name)}\n"
539 )
540 else:
541 return ""
542
543 def format_link_alias(doc_file: Tuple[str, str]) -> str:
544 check_name, match = process_doc(doc_file)
545 is_clang_analyzer = check_name.startswith("clang-analyzer-")
546 if not check_name or (not match and not is_clang_analyzer):
547 return ""
548
549 module = doc_file[0]
550 check_file = doc_file[1].replace(".rst", "")
551 if is_clang_analyzer:
552 title = f"Clang Static Analyzer {check_file}"
553 # Clang Static Analyzer aliases still need the external redirect
554 # target so list.rst can link to the upstream analyzer docs.
555 with open(os.path.join(docs_dir, *doc_file), "r", encoding="utf8") as doc:
556 content = doc.read()
557 redirect = re.search(
558 r".*:http-equiv=refresh: \d+;URL=(.*).html(.*)", content
559 )
560 # Preserve the anchor in checkers.html from group 2.
561 target = (
562 "" if not redirect else f"{redirect.group(1)}.html{redirect.group(2)}"
563 )
564 autofix = ""
565 ref_begin = ""
566 ref_end = "_"
567 else:
568 # Match neighbour or current-directory doc targets.
569 redirect_parts = re.search(r"^(?:\.\./([^/]+)/)?([^/]+)$", match)
570 assert redirect_parts
571 redirect_module = redirect_parts[1] or module
572 title = f"{redirect_module}-{redirect_parts[2]}"
573 target = f"{redirect_module}/{redirect_parts[2]}"
574 autofix = has_auto_fix(title)
575 ref_begin = ":doc:"
576 ref_end = ""
577
578 if target:
579 # The checker is just a redirect.
580 return (
581 f" :doc:`{check_name} <{module}/{check_file}>`, "
582 f"{ref_begin}`{title} <{target}>`{ref_end},{autofix}\n"
583 )
584
585 # The checker is just a alias without redirect.
586 return f" :doc:`{check_name} <{module}/{check_file}>`, {title},{autofix}\n"
587
588 print(f"Updating {filename}...")
589 with open(filename, "w", encoding="utf8", newline="\n") as f:
590 for line in lines:
591 f.write(line)
592 if line.strip() == ".. csv-table::":
593 # We dump the checkers
594 f.write(' :header: "Name", "Offers fixes"\n\n')
595 f.writelines(map(format_link, doc_files))
596 # and the aliases
597 f.write("\nCheck aliases\n-------------\n\n")
598 f.write(".. csv-table::\n")
599 f.write(' :header: "Name", "Redirect", "Offers fixes"\n\n')
600 f.writelines(map(format_link_alias, doc_files))
601 break
602
603
604# Adds a documentation for the check.
605def write_docs(module_path: str, module: str, check_name: str) -> None:
606 check_name_dashes = f"{module}-{check_name}"
607 filename = os.path.normpath(
608 os.path.join(
609 module_path, "../../docs/clang-tidy/checks/", module, f"{check_name}.rst"
610 )
611 )
612 print(f"Creating {filename}...")
613 with open(filename, "w", encoding="utf8", newline="\n") as f:
614 f.write(
615 """.. title:: clang-tidy - %(check_name_dashes)s
616
617%(check_name_dashes)s
618%(underline)s
619
620FIXME: Describe what patterns does the check detect and why. Give examples.
621"""
622 % {
623 "check_name_dashes": check_name_dashes,
624 "underline": "=" * len(check_name_dashes),
625 }
626 )
627
628
629def get_camel_name(check_name: str) -> str:
630 return "".join(map(lambda elem: elem.capitalize(), check_name.split("-")))
631
632
633def get_camel_check_name(check_name: str) -> str:
634 return f"{get_camel_name(check_name)}Check"
635
636
637def main() -> None:
638 language_to_extension = {
639 "c": "c",
640 "c++": "cpp",
641 "objc": "m",
642 "objc++": "mm",
643 }
644 cpp_language_to_requirements = {
645 "c++98": "CPlusPlus",
646 "c++11": "CPlusPlus11",
647 "c++14": "CPlusPlus14",
648 "c++17": "CPlusPlus17",
649 "c++20": "CPlusPlus20",
650 "c++23": "CPlusPlus23",
651 "c++26": "CPlusPlus26",
652 }
653 c_language_to_requirements = {
654 "c99": None,
655 "c11": "C11",
656 "c17": "C17",
657 "c23": "C23",
658 "c27": "C2Y",
659 }
660 parser = argparse.ArgumentParser()
661 parser.add_argument(
662 "--update-docs",
663 action="store_true",
664 help="just update the list of documentation files, then exit",
665 )
666 parser.add_argument(
667 "--language",
668 help="language to use for new check (defaults to c++)",
669 choices=language_to_extension.keys(),
670 default=None,
671 metavar="LANG",
672 )
673 parser.add_argument(
674 "--description",
675 "-d",
676 help="short description of what the check does",
677 default="FIXME: Write a short description",
678 type=str,
679 )
680 parser.add_argument(
681 "--standard",
682 help="Specify a specific version of the language",
683 choices=list(
684 itertools.chain(
685 cpp_language_to_requirements.keys(), c_language_to_requirements.keys()
686 )
687 ),
688 default=None,
689 )
690 parser.add_argument(
691 "module",
692 nargs="?",
693 help="module directory under which to place the new tidy check (e.g., misc)",
694 )
695 parser.add_argument(
696 "check", nargs="?", help="name of new tidy check to add (e.g. foo-do-the-stuff)"
697 )
698 args = parser.parse_args()
699
700 if args.update_docs:
701 update_checks_list(os.path.dirname(sys.argv[0]))
702 return
703
704 if not args.module or not args.check:
705 print("Module and check must be specified.")
706 parser.print_usage()
707 return
708
709 module = args.module
710 check_name = args.check
711 check_name_camel = get_camel_check_name(check_name)
712 if check_name.startswith(module):
713 print(
714 f'Check name "{check_name}" must not start with the module "{module}". Exiting.'
715 )
716 return
717 clang_tidy_path = os.path.dirname(sys.argv[0])
718 module_path = os.path.join(clang_tidy_path, module)
719
720 if not adapt_cmake(module_path, check_name_camel):
721 return
722
723 # Map module names to namespace names that don't conflict with widely used top-level namespaces.
724 if module == "llvm":
725 namespace = f"{module}_check"
726 else:
727 namespace = module
728
729 description = args.description
730 if not description.endswith("."):
731 description += "."
732
733 language = args.language
734
735 if args.standard:
736 if args.standard in cpp_language_to_requirements:
737 if language and language != "c++":
738 raise ValueError("C++ standard chosen when language is not C++")
739 language = "c++"
740 elif args.standard in c_language_to_requirements:
741 if language and language != "c":
742 raise ValueError("C standard chosen when language is not C")
743 language = "c"
744
745 if not language:
746 language = "c++"
747
748 language_restrict = None
749
750 if language == "c":
751 language_restrict = "!%(lang)s.CPlusPlus"
752 if extra := c_language_to_requirements.get(args.standard, None):
753 language_restrict += f" && %(lang)s.{extra}"
754 elif language == "c++":
755 language_restrict = (
756 f"%(lang)s.{cpp_language_to_requirements.get(args.standard, 'CPlusPlus')}"
757 )
758 elif language in ["objc", "objc++"]:
759 language_restrict = "%(lang)s.ObjC"
760 else:
761 raise ValueError(f"Unsupported language '{language}' was specified")
762
764 module_path,
765 module,
766 namespace,
767 check_name,
768 check_name_camel,
769 description,
770 language_restrict,
771 )
772 write_implementation(module_path, module, namespace, check_name_camel)
773 adapt_module(module_path, module, check_name, check_name_camel)
774 add_release_notes(module_path, module, check_name, description)
775 test_extension = language_to_extension[language]
776 write_test(module_path, module, check_name, test_extension, args.standard)
777 write_docs(module_path, module, check_name)
778 update_checks_list(clang_tidy_path)
779 print("Done. Now it's your turn!")
780
781
782if __name__ == "__main__":
783 main()
None write_header(str module_path, str module, str namespace, str check_name, str check_name_camel, str description, str lang_restrict)
str get_module_filename(str module_path, str module)
None update_checks_list(str clang_tidy_path)
None adapt_module(str module_path, str module, str check_name, str check_name_camel)
str get_camel_name(str check_name)
None write_docs(str module_path, str module, str check_name)
None write_implementation(str module_path, str module, str namespace, str check_name_camel)
str get_actual_filename(str dirname, str filename)
None write_test(str module_path, str module, str check_name, str test_extension, Optional[str] test_standard)
bool adapt_cmake(str module_path, str check_name_camel)
str get_camel_check_name(str check_name)
None add_release_notes(str module_path, str module, str check_name, str description)