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