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