clang-tools 24.0.0git
FeatureModulesTests.cpp
Go to the documentation of this file.
1//===--- FeatureModulesTests.cpp -------------------------------*- 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#include "Annotations.h"
10#include "FeatureModule.h"
11#include "Selection.h"
12#include "TestTU.h"
13#include "refactor/Tweak.h"
14#include "support/Logger.h"
15#include "clang/AST/Decl.h"
16#include "clang/Basic/DiagnosticFrontend.h"
17#include "clang/Frontend/CompilerInstance.h"
18#include "clang/Frontend/FrontendOptions.h"
19#include "clang/Lex/PPCallbacks.h"
20#include "clang/Lex/Preprocessor.h"
21#include "llvm/Support/Error.h"
22#include "gmock/gmock.h"
23#include "gtest/gtest.h"
24#include <functional>
25#include <memory>
26
27namespace clang {
28namespace clangd {
29namespace {
30
31struct TestModule final : FeatureModule {
32 struct Listener final : ASTListener {
33 Listener(TestModule &Module) : Module(Module) {}
34
35 void beforeBeginSourceFile(CompilerInstance &CI) override {
36 if (Module.BeforeBeginSourceFile)
37 Module.BeforeBeginSourceFile(CI);
38 }
39 void beforePPCallbacks(CompilerInstance &CI) override {
40 if (Module.BeforePPCallbacks)
41 Module.BeforePPCallbacks(CI);
42 }
43 void beforeExecute(CompilerInstance &CI) override {
44 if (Module.BeforeExecute)
45 Module.BeforeExecute(CI);
46 }
47 void afterExecute(CompilerInstance &CI) override {
48 if (Module.AfterExecute)
49 Module.AfterExecute(CI);
50 }
51 void sawDiagnostic(const clang::Diagnostic &Info,
52 clangd::Diag &Diag) override {
53 if (Module.SawDiagnostic)
54 Module.SawDiagnostic(Info, Diag);
55 }
56 void finalizeDiagnostic(clangd::Diag &Diag) override {
57 if (Module.FinalizeDiagnostic)
58 Module.FinalizeDiagnostic(Diag);
59 }
60
61 private:
62 TestModule &Module;
63 };
64
65 std::unique_ptr<ASTListener> astListeners() override {
66 return std::make_unique<Listener>(*this);
67 }
68
69 std::function<void(CompilerInstance &)> BeforeBeginSourceFile;
70 std::function<void(CompilerInstance &)> BeforePPCallbacks;
71 std::function<void(CompilerInstance &)> BeforeExecute;
72 std::function<void(CompilerInstance &)> AfterExecute;
73 std::function<void(const clang::Diagnostic &, clangd::Diag &)> SawDiagnostic;
74 std::function<void(clangd::Diag &)> FinalizeDiagnostic;
75};
76
77TEST(FeatureModulesTest, ContributesTweak) {
78 static constexpr const char *TweakID = "ModuleTweak";
79 struct TweakContributingModule final : public FeatureModule {
80 struct ModuleTweak final : public Tweak {
81 const char *id() const override { return TweakID; }
82 bool prepare(const Selection &Sel) override { return true; }
83 Expected<Effect> apply(const Selection &Sel) override {
84 return error("not implemented");
85 }
86 std::string title() const override { return id(); }
87 llvm::StringLiteral kind() const override {
88 return llvm::StringLiteral("");
89 };
90 };
91
92 void contributeTweaks(std::vector<std::unique_ptr<Tweak>> &Out) override {
93 Out.emplace_back(new ModuleTweak);
94 }
95 };
96
98 Set.add(std::make_unique<TweakContributingModule>());
99
100 auto AST = TestTU::withCode("").build();
101 auto Tree =
102 SelectionTree::createRight(AST.getASTContext(), AST.getTokens(), 0, 0);
103 auto Actual = prepareTweak(
104 TweakID, Tweak::Selection(nullptr, AST, 0, 0, std::move(Tree), nullptr),
105 &Set);
106 ASSERT_TRUE(bool(Actual));
107 EXPECT_EQ(Actual->get()->id(), TweakID);
108}
109
110TEST(FeatureModulesTest, SuppressDiags) {
111 struct DiagModifierModule final : public FeatureModule {
112 struct Listener : public FeatureModule::ASTListener {
113 void sawDiagnostic(const clang::Diagnostic &Info,
114 clangd::Diag &Diag) override {
115 Diag.Severity = DiagnosticsEngine::Ignored;
116 }
117 };
118 std::unique_ptr<ASTListener> astListeners() override {
119 return std::make_unique<Listener>();
120 };
121 };
123 FMS.add(std::make_unique<DiagModifierModule>());
124
125 Annotations Code("[[test]]; /* error-ok */");
126 TestTU TU;
127 TU.Code = Code.code().str();
128
129 {
130 auto AST = TU.build();
131 EXPECT_THAT(AST.getDiagnostics(), testing::Not(testing::IsEmpty()));
132 }
133
134 TU.FeatureModules = &FMS;
135 {
136 auto AST = TU.build();
137 EXPECT_THAT(AST.getDiagnostics(), testing::IsEmpty());
138 }
139}
140
141TEST(FeatureModulesTest, BeforeBeginSourceFile) {
142 std::vector<frontend::ActionKind> Builds;
143 auto Module = std::make_unique<TestModule>();
144 Module->BeforeBeginSourceFile = [&](CompilerInstance &CI) {
145 Builds.push_back(CI.getFrontendOpts().ProgramAction);
146 };
147 FeatureModuleSet Modules;
148 Modules.add(std::move(Module));
149 auto TU = TestTU::withCode(R"cpp(
150 #include "header.h"
151 HeaderType value;
152 )cpp");
153 TU.AdditionalFiles["header.h"] = "struct HeaderType {};";
154 TU.FeatureModules = &Modules;
155 EXPECT_THAT(TU.build().getDiagnostics(), testing::IsEmpty());
156 // The preamble is built from header.h, but only the main-file build calls
157 // this hook.
158 EXPECT_THAT(Builds, testing::ElementsAre(frontend::ParseSyntaxOnly));
159}
160
161TEST(FeatureModulesTest, BeforeBeginSourceFileDiagnostics) {
162 unsigned SeenDiagnostics = 0;
163 auto Module = std::make_unique<TestModule>();
164 Module->BeforeBeginSourceFile = [](CompilerInstance &CI) {
165 // The newline warning is emitted while BeginSourceFile initializes macros,
166 // so beforePPCallbacks and beforeExecute would be too late to promote it.
167 CI.getDiagnostics().setSeverity(
168 diag::warn_fe_macro_contains_embedded_newline, diag::Severity::Error,
169 SourceLocation());
170 };
171 Module->SawDiagnostic = [&](const clang::Diagnostic &Info, clangd::Diag &) {
172 if (Info.getID() == diag::warn_fe_macro_contains_embedded_newline)
173 ++SeenDiagnostics;
174 };
175 FeatureModuleSet Modules;
176 Modules.add(std::move(Module));
177
178 auto TU = TestTU::withCode("int value;");
179 TU.ExtraArgs = {"-DMACRO=first\nsecond"};
180 TU.FeatureModules = &Modules;
181 auto AST = TU.build();
182 // clangd filters out this location-less diagnostic even when promoted, so
183 // check Clang's error count to verify that it was emitted as an error.
184 EXPECT_EQ(AST.getPreprocessor().getDiagnostics().getNumErrors(), 1u);
185 // StoreDiags filters it out before sawDiagnostic: it has no source location
186 // and is a warning by default, despite being promoted to an error here.
187 EXPECT_EQ(SeenDiagnostics, 0u);
188}
189
190TEST(FeatureModulesTest, BeforePPCallbacks) {
191 struct IncludeRecorder : public PPCallbacks {
192 IncludeRecorder(std::vector<std::string> &Includes) : Includes(Includes) {}
193
194 void InclusionDirective(SourceLocation, const Token &, StringRef FileName,
195 bool, CharSourceRange, OptionalFileEntryRef,
196 StringRef, StringRef, const clang::Module *, bool,
197 SrcMgr::CharacteristicKind) override {
198 Includes.push_back(FileName.str());
199 }
200
201 private:
202 std::vector<std::string> &Includes;
203 };
204 std::vector<std::string> Includes;
205 auto Module = std::make_unique<TestModule>();
206 Module->BeforePPCallbacks = [&Includes](CompilerInstance &CI) {
207 // The preamble build processes the main file's initial directives,
208 // including #include "header.h", and the included header's contents. The
209 // main-file build reuses that preamble and skips those directives.
210 // ReplayPreamble synthesizes InclusionDirective callbacks for the saved
211 // direct includes. Register only during the main-file build to observe this
212 // replay, rather than the original include during preamble construction.
213 if (CI.getFrontendOpts().ProgramAction == frontend::ParseSyntaxOnly)
214 CI.getPreprocessor().addPPCallbacks(
215 std::make_unique<IncludeRecorder>(Includes));
216 };
218 FMS.add(std::move(Module));
219
220 TestTU TU = TestTU::withCode(R"cpp(
221 #include "header.h"
222 void mainFileFunc(); // Ends the preamble; parsed during the main-file build.
223 )cpp");
224 TU.AdditionalFiles["header.h"] = "";
225 TU.FeatureModules = &FMS;
226 TU.build();
227 EXPECT_THAT(Includes, testing::ElementsAre("header.h"));
228}
229
230TEST(FeatureModulesTest, BeforeExecute) {
231 auto Module = std::make_unique<TestModule>();
232 Module->BeforeExecute = [](CompilerInstance &CI) {
233 CI.getPreprocessor().SetSuppressIncludeNotFoundError(true);
234 };
236 FMS.add(std::move(Module));
237
238 TestTU TU = TestTU::withCode(R"cpp(
239 /*error-ok*/
240 #include "not_found.h"
241
242 void foo() {
243 #include "not_found_not_preamble.h"
244 }
245 )cpp");
246
247 {
248 auto AST = TU.build();
249 EXPECT_THAT(AST.getDiagnostics(), testing::Not(testing::IsEmpty()));
250 }
251
252 TU.FeatureModules = &FMS;
253 {
254 auto AST = TU.build();
255 EXPECT_THAT(AST.getDiagnostics(), testing::IsEmpty());
256 }
257}
258
259TEST(FeatureModulesTest, AfterExecute) {
260 std::vector<std::string> DeclNames;
261 auto Module = std::make_unique<TestModule>();
262 Module->AfterExecute = [&DeclNames](CompilerInstance &CI) {
263 for (Decl *D : CI.getASTContext().getTraversalScope())
264 if (const auto *ND = llvm::dyn_cast<NamedDecl>(D))
265 DeclNames.push_back(ND->getNameAsString());
266 };
268 FMS.add(std::move(Module));
269
270 TestTU TU = TestTU::withCode(R"cpp(
271 #include "header.h"
272 void mainFileFunc();
273 )cpp");
274 TU.AdditionalFiles["header.h"] = "void headerFunc();";
275 TU.FeatureModules = &FMS;
276 TU.build();
277
278 // afterExecute runs once clangd has restricted the traversal scope, so the
279 // declaration from the header is intentionally not visible here.
280 EXPECT_THAT(DeclNames, testing::ElementsAre("mainFileFunc"));
281}
282
283TEST(FeatureModulesTest, FinalizeDiagnostic) {
284 unsigned Notes = 0;
285 unsigned Fixes = 0;
286 auto Module = std::make_unique<TestModule>();
287 Module->FinalizeDiagnostic = [&](clangd::Diag &Diag) {
288 if (Diag.Message.find("undeclared identifier 'fooo'") == std::string::npos)
289 return;
290 Notes = Diag.Notes.size();
291 Fixes = Diag.Fixes.size();
292 };
294 FMS.add(std::move(Module));
295
296 TestTU TU = TestTU::withCode(R"cpp(
297 void foo();
298 void bar() { fooo(); } // error-ok
299 )cpp");
300 TU.FeatureModules = &FMS;
301 EXPECT_THAT(TU.build().getDiagnostics(), testing::SizeIs(1));
302 EXPECT_EQ(Notes, 1u);
303 EXPECT_EQ(Fixes, 1u);
304}
305
306} // namespace
307} // namespace clangd
308} // namespace clang
Same as llvm::Annotations, but adjusts functions to LSP-specific types for positions and ranges.
Definition Annotations.h:23
A FeatureModuleSet is a collection of feature modules installed in clangd.
void add(std::unique_ptr< FeatureModule > M)
A FeatureModule contributes a vertical feature to clangd.
static SelectionTree createRight(ASTContext &AST, const syntax::TokenBuffer &Tokens, unsigned Begin, unsigned End)
llvm::Error error(std::error_code, std::string &&)
Definition Logger.cpp:80
FIXME: Skip testing on windows temporarily due to the different escaping code mode.
Definition AST.cpp:44
@ Info
An information message.
Definition Protocol.h:755
llvm::Expected< std::unique_ptr< Tweak > > prepareTweak(StringRef ID, const Tweak::Selection &S, const FeatureModuleSet *Modules)
Definition Tweak.cpp:91
TEST(BackgroundQueueTest, Priority)
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
A top-level diagnostic that may have Notes and Fixes.
Definition Diagnostics.h:98
std::vector< Fix > Fixes
Alternative fixes for this diagnostic, one should be chosen.
std::vector< Note > Notes
Elaborate on the problem, usually pointing to a related piece of code.
std::string Code
Definition TestTU.h:49
ParsedAST build() const
Definition TestTU.cpp:115
static TestTU withCode(llvm::StringRef Code)
Definition TestTU.h:36
Input to prepare and apply tweaks.
Definition Tweak.h:49