181 lines
		
	
	
		
			6.6 KiB
		
	
	
	
		
			C++
		
	
	
	
			
		
		
	
	
			181 lines
		
	
	
		
			6.6 KiB
		
	
	
	
		
			C++
		
	
	
	
| //===--- 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 <map>
 | |
| #include <memory>
 | |
| 
 | |
| namespace clang {
 | |
| namespace tidy {
 | |
| namespace test {
 | |
| 
 | |
| template <typename Check, typename... Checks> struct CheckFactory {
 | |
|   static void
 | |
|   createChecks(ClangTidyContext *Context,
 | |
|                SmallVectorImpl<std::unique_ptr<ClangTidyCheck>> &Result) {
 | |
|     CheckFactory<Check>::createChecks(Context, Result);
 | |
|     CheckFactory<Checks...>::createChecks(Context, Result);
 | |
|   }
 | |
| };
 | |
| 
 | |
| template <typename Check> struct CheckFactory<Check> {
 | |
|   static void
 | |
|   createChecks(ClangTidyContext *Context,
 | |
|                SmallVectorImpl<std::unique_ptr<ClangTidyCheck>> &Result) {
 | |
|     Result.emplace_back(std::make_unique<Check>(
 | |
|         "test-check-" + std::to_string(Result.size()), Context));
 | |
|   }
 | |
| };
 | |
| 
 | |
| template <typename... CheckTypes>
 | |
| class TestClangTidyAction : public ASTFrontendAction {
 | |
| public:
 | |
|   TestClangTidyAction(SmallVectorImpl<std::unique_ptr<ClangTidyCheck>> &Checks,
 | |
|                       ast_matchers::MatchFinder &Finder,
 | |
|                       ClangTidyContext &Context)
 | |
|       : Checks(Checks), Finder(Finder), Context(Context) {}
 | |
| 
 | |
| private:
 | |
|   std::unique_ptr<ASTConsumer> 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<CheckTypes...>::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<std::unique_ptr<ClangTidyCheck>> &Checks;
 | |
|   ast_matchers::MatchFinder &Finder;
 | |
|   ClangTidyContext &Context;
 | |
| };
 | |
| 
 | |
| template <typename... CheckTypes>
 | |
| std::string
 | |
| runCheckOnCode(StringRef Code, std::vector<ClangTidyError> *Errors = nullptr,
 | |
|                const Twine &Filename = "input.cc",
 | |
|                ArrayRef<std::string> ExtraArgs = None,
 | |
|                const ClangTidyOptions &ExtraOptions = ClangTidyOptions(),
 | |
|                std::map<StringRef, StringRef> PathsToContent =
 | |
|                    std::map<StringRef, StringRef>()) {
 | |
|   static_assert(sizeof...(CheckTypes) > 0, "No checks specified");
 | |
|   ClangTidyOptions Options = ExtraOptions;
 | |
|   Options.Checks = "*";
 | |
|   ClangTidyContext Context(std::make_unique<DefaultOptionsProvider>(
 | |
|       ClangTidyGlobalOptions(), Options));
 | |
|   ClangTidyDiagnosticConsumer DiagConsumer(Context);
 | |
|   DiagnosticsEngine DE(new DiagnosticIDs(), new DiagnosticOptions,
 | |
|                        &DiagConsumer, false);
 | |
|   Context.setDiagnosticsEngine(&DE);
 | |
| 
 | |
|   std::vector<std::string> 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<llvm::vfs::InMemoryFileSystem> InMemoryFileSystem(
 | |
|       new llvm::vfs::InMemoryFileSystem);
 | |
|   llvm::IntrusiveRefCntPtr<FileManager> Files(
 | |
|       new FileManager(FileSystemOptions(), InMemoryFileSystem));
 | |
| 
 | |
|   SmallVector<std::unique_ptr<ClangTidyCheck>, sizeof...(CheckTypes)> Checks;
 | |
|   tooling::ToolInvocation Invocation(
 | |
|       Args,
 | |
|       std::make_unique<TestClangTidyAction<CheckTypes...>>(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<ClangTidyError> 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<Check>(Code))
 | |
| 
 | |
| } // namespace test
 | |
| } // namespace tidy
 | |
| } // namespace clang
 | |
| 
 | |
| #endif // LLVM_CLANG_TOOLS_EXTRA_UNITTESTS_CLANG_TIDY_CLANGTIDYTEST_H
 |