1 //===--- ClangTidyTest.h - clang-tidy ---------------------------*- C++ -*-===//
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
7 //===----------------------------------------------------------------------===//
9 #ifndef LLVM_CLANG_TOOLS_EXTRA_UNITTESTS_CLANG_TIDY_CLANGTIDYTEST_H
10 #define LLVM_CLANG_TOOLS_EXTRA_UNITTESTS_CLANG_TIDY_CLANGTIDYTEST_H
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"
30 template <typename Check
, typename
... Checks
> struct CheckFactory
{
32 createChecks(ClangTidyContext
*Context
,
33 SmallVectorImpl
<std::unique_ptr
<ClangTidyCheck
>> &Result
) {
34 CheckFactory
<Check
>::createChecks(Context
, Result
);
35 CheckFactory
<Checks
...>::createChecks(Context
, Result
);
39 template <typename Check
> struct CheckFactory
<Check
> {
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
));
48 template <typename
... CheckTypes
>
49 class TestClangTidyAction
: public ASTFrontendAction
{
51 TestClangTidyAction(SmallVectorImpl
<std::unique_ptr
<ClangTidyCheck
>> &Checks
,
52 ast_matchers::MatchFinder
&Finder
,
53 ClangTidyContext
&Context
)
54 : Checks(Checks
), Finder(Finder
), Context(Context
) {}
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());
63 Preprocessor
*PP
= &Compiler
.getPreprocessor();
65 // Checks must be created here, _after_ `Context` has been initialized, so
66 // that check constructors can access the context (for example, through
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()))
74 Check
->registerMatchers(&Finder
);
75 Check
->registerPPCallbacks(Compiler
.getSourceManager(), PP
, PP
);
77 return Finder
.newASTConsumer();
80 SmallVectorImpl
<std::unique_ptr
<ClangTidyCheck
>> &Checks
;
81 ast_matchers::MatchFinder
&Finder
;
82 ClangTidyContext
&Context
;
85 template <typename
... CheckTypes
>
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
;
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
);
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");
112 if (extension
== ".cc" || extension
== ".cpp" || extension
== ".mm") {
113 Args
.push_back("-std=c++20");
115 Args
.push_back("-Iinclude");
116 Args
.insert(Args
.end(), ExtraArgs
.begin(), ExtraArgs
.end());
117 Args
.push_back(Filename
.str());
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
));
125 SmallVector
<std::unique_ptr
<ClangTidyCheck
>, sizeof...(CheckTypes
)> Checks
;
126 tooling::ToolInvocation
Invocation(
128 std::make_unique
<TestClangTidyAction
<CheckTypes
...>>(Checks
, Finder
,
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
));
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";
144 llvm::report_fatal_error(llvm::Twine(ErrorText
));
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.
156 llvm::errs() << llvm::toString(std::move(Err
)) << "\n";
163 *Errors
= std::move(Diags
);
164 auto Result
= tooling::applyAllReplacements(Code
, Fixes
);
166 // FIXME: propagate the error.
167 llvm::consumeError(Result
.takeError());
173 #define EXPECT_NO_CHANGES(Check, Code) \
174 EXPECT_EQ(Code, runCheckOnCode<Check>(Code))
180 #endif // LLVM_CLANG_TOOLS_EXTRA_UNITTESTS_CLANG_TIDY_CLANGTIDYTEST_H