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