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. 59def write_header( 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. 142def write_implementation( 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. 203def adapt_module( 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. 270def add_release_notes( 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. 334def write_test( 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 775 write_header( 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() 796