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
26namespace clang {
27namespace tidy {
28namespace test {
29
30template <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
39template <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(val: Result.size()), Context));
45 }
46};
47
48template <typename... CheckTypes>
49class TestClangTidyAction : public ASTFrontendAction {
50public:
51 TestClangTidyAction(SmallVectorImpl<std::unique_ptr<ClangTidyCheck>> &Checks,
52 ast_matchers::MatchFinder &Finder,
53 ClangTidyContext &Context)
54 : Checks(Checks), Finder(Finder), Context(Context) {}
55
56private:
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(LangOpts: Context.getLangOpts()))
73 continue;
74 Check->registerMatchers(Finder: &Finder);
75 Check->registerPPCallbacks(SM: Compiler.getSourceManager(), PP, ModuleExpanderPP: 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
85template <typename... CheckTypes>
86std::string
87runCheckOnCode(StringRef Code, std::vector<ClangTidyError> *Errors = nullptr,
88 const Twine &Filename = "input.cc",
89 ArrayRef<std::string> ExtraArgs = std::nullopt,
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 args: ClangTidyGlobalOptions(), args&: 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(x: "-fsyntax-only");
105 Args.push_back(x: "-fno-delayed-template-parsing");
106 std::string extension(
107 std::string(llvm::sys::path::extension(path: Filename.str())));
108 if (extension == ".m" || extension == ".mm") {
109 Args.push_back(x: "-fobjc-abi-version=2");
110 Args.push_back(x: "-fobjc-arc");
111 }
112 if (extension == ".cc" || extension == ".cpp" || extension == ".mm") {
113 Args.push_back(x: "-std=c++20");
114 }
115 Args.push_back(x: "-Iinclude");
116 Args.insert(position: Args.end(), first: ExtraArgs.begin(), last: ExtraArgs.end());
117 Args.push_back(x: 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(Path: Filename, ModificationTime: 0,
132 Buffer: llvm::MemoryBuffer::getMemBuffer(InputData: Code));
133 for (const auto &FileContent : PathsToContent) {
134 InMemoryFileSystem->addFile(
135 Path: Twine("include/") + FileContent.first, ModificationTime: 0,
136 Buffer: llvm::MemoryBuffer::getMemBuffer(InputData: 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(reason: 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(D: Error))
151 for (const auto &FileAndFixes : *ChosenFix) {
152 for (const auto &Fix : FileAndFixes.second) {
153 auto Err = Fixes.add(R: Fix);
154 // FIXME: better error handling. Keep the behavior for now.
155 if (Err) {
156 llvm::errs() << llvm::toString(E: std::move(Err)) << "\n";
157 return "";
158 }
159 }
160 }
161 }
162 if (Errors)
163 *Errors = std::move(Diags);
164 auto Result = tooling::applyAllReplacements(Code, Replaces: Fixes);
165 if (!Result) {
166 // FIXME: propagate the error.
167 llvm::consumeError(Err: 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

source code of clang-tools-extra/unittests/clang-tidy/ClangTidyTest.h