//===--- ClangTidyTest.h - clang-tidy ---------------------------*- C++ -*-===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #ifndef LLVM_CLANG_TOOLS_EXTRA_UNITTESTS_CLANG_TIDY_CLANGTIDYTEST_H #define LLVM_CLANG_TOOLS_EXTRA_UNITTESTS_CLANG_TIDY_CLANGTIDYTEST_H #include "ClangTidy.h" #include "ClangTidyCheck.h" #include "ClangTidyDiagnosticConsumer.h" #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/Frontend/CompilerInstance.h" #include "clang/Frontend/FrontendActions.h" #include "clang/Tooling/Core/Diagnostic.h" #include "clang/Tooling/Core/Replacement.h" #include "clang/Tooling/Refactoring.h" #include "clang/Tooling/Tooling.h" #include "llvm/Support/Path.h" #include #include namespace clang { namespace tidy { namespace test { template struct CheckFactory { static void createChecks(ClangTidyContext *Context, SmallVectorImpl> &Result) { CheckFactory::createChecks(Context, Result); CheckFactory::createChecks(Context, Result); } }; template struct CheckFactory { static void createChecks(ClangTidyContext *Context, SmallVectorImpl> &Result) { Result.emplace_back(std::make_unique( "test-check-" + std::to_string(Result.size()), Context)); } }; template class TestClangTidyAction : public ASTFrontendAction { public: TestClangTidyAction(SmallVectorImpl> &Checks, ast_matchers::MatchFinder &Finder, ClangTidyContext &Context) : Checks(Checks), Finder(Finder), Context(Context) {} private: std::unique_ptr CreateASTConsumer(CompilerInstance &Compiler, StringRef File) override { Context.setSourceManager(&Compiler.getSourceManager()); Context.setCurrentFile(File); Context.setASTContext(&Compiler.getASTContext()); Preprocessor *PP = &Compiler.getPreprocessor(); // Checks must be created here, _after_ `Context` has been initialized, so // that check constructors can access the context (for example, through // `getLangOpts()`). CheckFactory::createChecks(&Context, Checks); assert(!Checks.empty() && "No checks created"); for (auto &Check : Checks) { assert(Check.get() && "Checks can't be null"); if (!Check->isLanguageVersionSupported(Context.getLangOpts())) continue; Check->registerMatchers(&Finder); Check->registerPPCallbacks(Compiler.getSourceManager(), PP, PP); } return Finder.newASTConsumer(); } SmallVectorImpl> &Checks; ast_matchers::MatchFinder &Finder; ClangTidyContext &Context; }; template std::string runCheckOnCode(StringRef Code, std::vector *Errors = nullptr, const Twine &Filename = "input.cc", ArrayRef ExtraArgs = None, const ClangTidyOptions &ExtraOptions = ClangTidyOptions(), std::map PathsToContent = std::map()) { static_assert(sizeof...(CheckTypes) > 0, "No checks specified"); ClangTidyOptions Options = ExtraOptions; Options.Checks = "*"; ClangTidyContext Context(std::make_unique( ClangTidyGlobalOptions(), Options)); ClangTidyDiagnosticConsumer DiagConsumer(Context); DiagnosticsEngine DE(new DiagnosticIDs(), new DiagnosticOptions, &DiagConsumer, false); Context.setDiagnosticsEngine(&DE); std::vector Args(1, "clang-tidy"); Args.push_back("-fsyntax-only"); Args.push_back("-fno-delayed-template-parsing"); std::string extension( std::string(llvm::sys::path::extension(Filename.str()))); if (extension == ".m" || extension == ".mm") { Args.push_back("-fobjc-abi-version=2"); Args.push_back("-fobjc-arc"); } if (extension == ".cc" || extension == ".cpp" || extension == ".mm") { Args.push_back("-std=c++11"); } Args.push_back("-Iinclude"); Args.insert(Args.end(), ExtraArgs.begin(), ExtraArgs.end()); Args.push_back(Filename.str()); ast_matchers::MatchFinder Finder; llvm::IntrusiveRefCntPtr InMemoryFileSystem( new llvm::vfs::InMemoryFileSystem); llvm::IntrusiveRefCntPtr Files( new FileManager(FileSystemOptions(), InMemoryFileSystem)); SmallVector, sizeof...(CheckTypes)> Checks; tooling::ToolInvocation Invocation( Args, std::make_unique>(Checks, Finder, Context), Files.get()); InMemoryFileSystem->addFile(Filename, 0, llvm::MemoryBuffer::getMemBuffer(Code)); for (const auto &FileContent : PathsToContent) { InMemoryFileSystem->addFile( Twine("include/") + FileContent.first, 0, llvm::MemoryBuffer::getMemBuffer(FileContent.second)); } Invocation.setDiagnosticConsumer(&DiagConsumer); if (!Invocation.run()) { std::string ErrorText; for (const auto &Error : DiagConsumer.take()) { ErrorText += Error.Message.Message + "\n"; } llvm::report_fatal_error(ErrorText); } tooling::Replacements Fixes; std::vector Diags = DiagConsumer.take(); for (const ClangTidyError &Error : Diags) { if (const auto *ChosenFix = tooling::selectFirstFix(Error)) for (const auto &FileAndFixes : *ChosenFix) { for (const auto &Fix : FileAndFixes.second) { auto Err = Fixes.add(Fix); // FIXME: better error handling. Keep the behavior for now. if (Err) { llvm::errs() << llvm::toString(std::move(Err)) << "\n"; return ""; } } } } if (Errors) *Errors = std::move(Diags); auto Result = tooling::applyAllReplacements(Code, Fixes); if (!Result) { // FIXME: propagate the error. llvm::consumeError(Result.takeError()); return ""; } return *Result; } #define EXPECT_NO_CHANGES(Check, Code) \ EXPECT_EQ(Code, runCheckOnCode(Code)) } // namespace test } // namespace tidy } // namespace clang #endif // LLVM_CLANG_TOOLS_EXTRA_UNITTESTS_CLANG_TIDY_CLANGTIDYTEST_H