1 //===--- ClangTidyTest.h - clang-tidy ---------------------------*- C++ -*-===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 9 #ifndef LLVM_CLANG_TOOLS_EXTRA_UNITTESTS_CLANG_TIDY_CLANGTIDYTEST_H 10 #define LLVM_CLANG_TOOLS_EXTRA_UNITTESTS_CLANG_TIDY_CLANGTIDYTEST_H 11 12 #include "ClangTidy.h" 13 #include "ClangTidyCheck.h" 14 #include "ClangTidyDiagnosticConsumer.h" 15 #include "clang/ASTMatchers/ASTMatchFinder.h" 16 #include "clang/Frontend/CompilerInstance.h" 17 #include "clang/Frontend/FrontendActions.h" 18 #include "clang/Tooling/Core/Diagnostic.h" 19 #include "clang/Tooling/Core/Replacement.h" 20 #include "clang/Tooling/Refactoring.h" 21 #include "clang/Tooling/Tooling.h" 22 #include "llvm/Support/Path.h" 23 #include <map> 24 #include <memory> 25 26 namespace clang { 27 namespace tidy { 28 namespace test { 29 30 template <typename Check, typename... Checks> struct CheckFactory { 31 static void 32 createChecks(ClangTidyContext *Context, 33 SmallVectorImpl<std::unique_ptr<ClangTidyCheck>> &Result) { 34 CheckFactory<Check>::createChecks(Context, Result); 35 CheckFactory<Checks...>::createChecks(Context, Result); 36 } 37 }; 38 39 template <typename Check> struct CheckFactory<Check> { 40 static void 41 createChecks(ClangTidyContext *Context, 42 SmallVectorImpl<std::unique_ptr<ClangTidyCheck>> &Result) { 43 Result.emplace_back(std::make_unique<Check>( 44 "test-check-" + std::to_string(Result.size()), Context)); 45 } 46 }; 47 48 template <typename... CheckTypes> 49 class TestClangTidyAction : public ASTFrontendAction { 50 public: 51 TestClangTidyAction(SmallVectorImpl<std::unique_ptr<ClangTidyCheck>> &Checks, 52 ast_matchers::MatchFinder &Finder, 53 ClangTidyContext &Context) 54 : Checks(Checks), Finder(Finder), Context(Context) {} 55 56 private: 57 std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &Compiler, 58 StringRef File) override { 59 Context.setSourceManager(&Compiler.getSourceManager()); 60 Context.setCurrentFile(File); 61 Context.setASTContext(&Compiler.getASTContext()); 62 63 Preprocessor *PP = &Compiler.getPreprocessor(); 64 65 // Checks must be created here, _after_ `Context` has been initialized, so 66 // that check constructors can access the context (for example, through 67 // `getLangOpts()`). 68 CheckFactory<CheckTypes...>::createChecks(&Context, Checks); 69 assert(!Checks.empty() && "No checks created"); 70 for (auto &Check : Checks) { 71 assert(Check.get() && "Checks can't be null"); 72 if (!Check->isLanguageVersionSupported(Context.getLangOpts())) 73 continue; 74 Check->registerMatchers(&Finder); 75 Check->registerPPCallbacks(Compiler.getSourceManager(), PP, PP); 76 } 77 return Finder.newASTConsumer(); 78 } 79 80 SmallVectorImpl<std::unique_ptr<ClangTidyCheck>> &Checks; 81 ast_matchers::MatchFinder &Finder; 82 ClangTidyContext &Context; 83 }; 84 85 template <typename... CheckTypes> 86 std::string 87 runCheckOnCode(StringRef Code, std::vector<ClangTidyError> *Errors = nullptr, 88 const Twine &Filename = "input.cc", 89 ArrayRef<std::string> ExtraArgs = {}, 90 const ClangTidyOptions &ExtraOptions = ClangTidyOptions(), 91 std::map<StringRef, StringRef> PathsToContent = 92 std::map<StringRef, StringRef>()) { 93 static_assert(sizeof...(CheckTypes) > 0, "No checks specified"); 94 ClangTidyOptions Options = ExtraOptions; 95 Options.Checks = "*"; 96 ClangTidyContext Context(std::make_unique<DefaultOptionsProvider>( 97 ClangTidyGlobalOptions(), Options)); 98 ClangTidyDiagnosticConsumer DiagConsumer(Context); 99 DiagnosticsEngine DE(new DiagnosticIDs(), new DiagnosticOptions, 100 &DiagConsumer, false); 101 Context.setDiagnosticsEngine(&DE); 102 103 std::vector<std::string> Args(1, "clang-tidy"); 104 Args.push_back("-fsyntax-only"); 105 Args.push_back("-fno-delayed-template-parsing"); 106 std::string extension( 107 std::string(llvm::sys::path::extension(Filename.str()))); 108 if (extension == ".m" || extension == ".mm") { 109 Args.push_back("-fobjc-abi-version=2"); 110 Args.push_back("-fobjc-arc"); 111 } 112 if (extension == ".cc" || extension == ".cpp" || extension == ".mm") { 113 Args.push_back("-std=c++20"); 114 } 115 Args.push_back("-Iinclude"); 116 Args.insert(Args.end(), ExtraArgs.begin(), ExtraArgs.end()); 117 Args.push_back(Filename.str()); 118 119 ast_matchers::MatchFinder Finder; 120 llvm::IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> InMemoryFileSystem( 121 new llvm::vfs::InMemoryFileSystem); 122 llvm::IntrusiveRefCntPtr<FileManager> Files( 123 new FileManager(FileSystemOptions(), InMemoryFileSystem)); 124 125 SmallVector<std::unique_ptr<ClangTidyCheck>, sizeof...(CheckTypes)> Checks; 126 tooling::ToolInvocation Invocation( 127 Args, 128 std::make_unique<TestClangTidyAction<CheckTypes...>>(Checks, Finder, 129 Context), 130 Files.get()); 131 InMemoryFileSystem->addFile(Filename, 0, 132 llvm::MemoryBuffer::getMemBuffer(Code)); 133 for (const auto &FileContent : PathsToContent) { 134 InMemoryFileSystem->addFile( 135 Twine("include/") + FileContent.first, 0, 136 llvm::MemoryBuffer::getMemBuffer(FileContent.second)); 137 } 138 Invocation.setDiagnosticConsumer(&DiagConsumer); 139 if (!Invocation.run()) { 140 std::string ErrorText; 141 for (const auto &Error : DiagConsumer.take()) { 142 ErrorText += Error.Message.Message + "\n"; 143 } 144 llvm::report_fatal_error(llvm::Twine(ErrorText)); 145 } 146 147 tooling::Replacements Fixes; 148 std::vector<ClangTidyError> Diags = DiagConsumer.take(); 149 for (const ClangTidyError &Error : Diags) { 150 if (const auto *ChosenFix = tooling::selectFirstFix(Error)) 151 for (const auto &FileAndFixes : *ChosenFix) { 152 for (const auto &Fix : FileAndFixes.second) { 153 auto Err = Fixes.add(Fix); 154 // FIXME: better error handling. Keep the behavior for now. 155 if (Err) { 156 llvm::errs() << llvm::toString(std::move(Err)) << "\n"; 157 return ""; 158 } 159 } 160 } 161 } 162 if (Errors) 163 *Errors = std::move(Diags); 164 auto Result = tooling::applyAllReplacements(Code, Fixes); 165 if (!Result) { 166 // FIXME: propagate the error. 167 llvm::consumeError(Result.takeError()); 168 return ""; 169 } 170 return *Result; 171 } 172 173 #define EXPECT_NO_CHANGES(Check, Code) \ 174 EXPECT_EQ(Code, runCheckOnCode<Check>(Code)) 175 176 } // namespace test 177 } // namespace tidy 178 } // namespace clang 179 180 #endif // LLVM_CLANG_TOOLS_EXTRA_UNITTESTS_CLANG_TIDY_CLANGTIDYTEST_H 181