blob: c0459cc80ca4c39e23c795ae8c2eff9f22124f8a [file] [log] [blame]
//===--- 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(llvm::Twine(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