xref: /llvm-project/clang-tools-extra/unittests/clang-tidy/ClangTidyTest.h (revision fc5de0af33b7fb9b9f745e70dcd351f2de4211cb)
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