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"
11#include "Feature.h"
12#include "FeatureModule.h"
13#include "Selection.h"
14#include "TestTU.h"
15#include "TidyProvider.h"
16#include "refactor/Tweak.h"
17#include "support/Logger.h"
18#include "clang/AST/Decl.h"
19#include "clang/Basic/DiagnosticFrontend.h"
20#include "clang/Frontend/CompilerInstance.h"
21#include "clang/Frontend/FrontendOptions.h"
22#include "clang/Frontend/MultiplexConsumer.h"
23#include "clang/Lex/PPCallbacks.h"
24#include "clang/Lex/Preprocessor.h"
25#include "llvm/Support/Error.h"
26#include "gmock/gmock.h"
27#include "gtest/gtest.h"
28#include <functional>
29#include <memory>
30
31namespace clang {
32namespace clangd {
33namespace {
34
35struct TestModule final : FeatureModule {
36 struct Listener final : ASTListener {
37 Listener(TestModule &Module) : Module(Module) {}
38
39 void beforeBeginSourceFile(CompilerInstance &CI) override {
40 if (Module.BeforeBeginSourceFile)
41 Module.BeforeBeginSourceFile(CI);
42 }
43 void beforePPCallbacks(CompilerInstance &CI) override {
44 if (Module.BeforePPCallbacks)
45 Module.BeforePPCallbacks(CI);
46 }
47 void beforeExecute(CompilerInstance &CI) override {
48 if (Module.BeforeExecute)
49 Module.BeforeExecute(CI);
50 }
51 void afterExecute(CompilerInstance &CI) override {
52 if (Module.AfterExecute)
53 Module.AfterExecute(CI);
54 }
55 void sawDiagnostic(const clang::Diagnostic &Info,
56 clangd::Diag &Diag) override {
57 if (Module.SawDiagnostic)
58 Module.SawDiagnostic(Info, Diag);
59 }
60 void finalizeDiagnostic(clangd::Diag &Diag) override {
61 if (Module.FinalizeDiagnostic)
62 Module.FinalizeDiagnostic(Diag);
63 }
64
65 private:
66 TestModule &Module;
67 };
68
69 std::unique_ptr<ASTListener> astListeners() override {
70 return std::make_unique<Listener>(*this);
71 }
72
73 std::function<void(CompilerInstance &)> BeforeBeginSourceFile;
74 std::function<void(CompilerInstance &)> BeforePPCallbacks;
75 std::function<void(CompilerInstance &)> BeforeExecute;
76 std::function<void(CompilerInstance &)> AfterExecute;
77 std::function<void(const clang::Diagnostic &, clangd::Diag &)> SawDiagnostic;
78 std::function<void(clangd::Diag &)> FinalizeDiagnostic;
79};
80
81TEST(FeatureModulesTest, ContributesTweak) {
82 static constexpr const char *TweakID = "ModuleTweak";
83 struct TweakContributingModule final : public FeatureModule {
84 struct ModuleTweak final : public Tweak {
85 const char *id() const override { return TweakID; }
86 bool prepare(const Selection &Sel) override { return true; }
87 Expected<Effect> apply(const Selection &Sel) override {
88 return error("not implemented");
89 }
90 std::string title() const override { return id(); }
91 llvm::StringLiteral kind() const override {
92 return llvm::StringLiteral("");
93 };
94 };
95
96 void contributeTweaks(std::vector<std::unique_ptr<Tweak>> &Out) override {
97 Out.emplace_back(new ModuleTweak);
98 }
99 };
100
102 Set.add(std::make_unique<TweakContributingModule>());
103
104 auto AST = TestTU::withCode("").build();
105 auto Tree =
106 SelectionTree::createRight(AST.getASTContext(), AST.getTokens(), 0, 0);
107 auto Actual = prepareTweak(
108 TweakID, Tweak::Selection(nullptr, AST, 0, 0, std::move(Tree), nullptr),
109 &Set);
110 ASSERT_TRUE(bool(Actual));
111 EXPECT_EQ(Actual->get()->id(), TweakID);
112}
113
114TEST(FeatureModulesTest, SuppressDiags) {
115 struct DiagModifierModule final : public FeatureModule {
116 struct Listener : public FeatureModule::ASTListener {
117 void sawDiagnostic(const clang::Diagnostic &Info,
118 clangd::Diag &Diag) override {
119 Diag.Severity = DiagnosticsEngine::Ignored;
120 }
121 };
122 std::unique_ptr<ASTListener> astListeners() override {
123 return std::make_unique<Listener>();
124 };
125 };
127 FMS.add(std::make_unique<DiagModifierModule>());
128
129 Annotations Code("[[test]]; /* error-ok */");
130 TestTU TU;
131 TU.Code = Code.code().str();
132
133 {
134 auto AST = TU.build();
135 EXPECT_THAT(AST.getDiagnostics(), testing::Not(testing::IsEmpty()));
136 }
137
138 TU.FeatureModules = &FMS;
139 {
140 auto AST = TU.build();
141 EXPECT_THAT(AST.getDiagnostics(), testing::IsEmpty());
142 }
143}
144
145TEST(FeatureModulesTest, BeforeBeginSourceFile) {
146 std::vector<frontend::ActionKind> Builds;
147 auto Module = std::make_unique<TestModule>();
148 Module->BeforeBeginSourceFile = [&](CompilerInstance &CI) {
149 Builds.push_back(CI.getFrontendOpts().ProgramAction);
150 };
151 FeatureModuleSet Modules;
152 Modules.add(std::move(Module));
153 auto TU = TestTU::withCode(R"cpp(
154 #include "header.h"
155 HeaderType value;
156 )cpp");
157 TU.AdditionalFiles["header.h"] = "struct HeaderType {};";
158 TU.FeatureModules = &Modules;
159 EXPECT_THAT(TU.build().getDiagnostics(), testing::IsEmpty());
160 // The preamble is built from header.h, but only the main-file build calls
161 // this hook.
162 EXPECT_THAT(Builds, testing::ElementsAre(frontend::ParseSyntaxOnly));
163}
164
165TEST(FeatureModulesTest, BeforeBeginSourceFileDiagnostics) {
166 unsigned SeenDiagnostics = 0;
167 auto Module = std::make_unique<TestModule>();
168 Module->BeforeBeginSourceFile = [](CompilerInstance &CI) {
169 // The newline warning is emitted while BeginSourceFile initializes macros,
170 // so beforePPCallbacks and beforeExecute would be too late to promote it.
171 CI.getDiagnostics().setSeverity(
172 diag::warn_fe_macro_contains_embedded_newline, diag::Severity::Error,
173 SourceLocation());
174 };
175 Module->SawDiagnostic = [&](const clang::Diagnostic &Info, clangd::Diag &) {
176 if (Info.getID() == diag::warn_fe_macro_contains_embedded_newline)
177 ++SeenDiagnostics;
178 };
179 FeatureModuleSet Modules;
180 Modules.add(std::move(Module));
181
182 auto TU = TestTU::withCode("int value;");
183 TU.ExtraArgs = {"-DMACRO=first\nsecond"};
184 TU.FeatureModules = &Modules;
185 auto AST = TU.build();
186 // clangd filters out this location-less diagnostic even when promoted, so
187 // check Clang's error count to verify that it was emitted as an error.
188 EXPECT_EQ(AST.getPreprocessor().getDiagnostics().getNumErrors(), 1u);
189 // StoreDiags filters it out before sawDiagnostic: it has no source location
190 // and is a warning by default, despite being promoted to an error here.
191 EXPECT_EQ(SeenDiagnostics, 0u);
192}
193
194TEST(FeatureModulesTest, BeforePPCallbacks) {
195 struct IncludeRecorder : public PPCallbacks {
196 IncludeRecorder(std::vector<std::string> &Includes) : Includes(Includes) {}
197
198 void InclusionDirective(SourceLocation, const Token &, StringRef FileName,
199 bool, CharSourceRange, OptionalFileEntryRef,
200 StringRef, StringRef, const clang::Module *, bool,
201 SrcMgr::CharacteristicKind) override {
202 Includes.push_back(FileName.str());
203 }
204
205 private:
206 std::vector<std::string> &Includes;
207 };
208 std::vector<std::string> Includes;
209 auto Module = std::make_unique<TestModule>();
210 Module->BeforePPCallbacks = [&Includes](CompilerInstance &CI) {
211 // The preamble build processes the main file's initial directives,
212 // including #include "header.h", and the included header's contents. The
213 // main-file build reuses that preamble and skips those directives.
214 // ReplayPreamble synthesizes InclusionDirective callbacks for the saved
215 // direct includes. Register only during the main-file build to observe this
216 // replay, rather than the original include during preamble construction.
217 if (CI.getFrontendOpts().ProgramAction == frontend::ParseSyntaxOnly)
218 CI.getPreprocessor().addPPCallbacks(
219 std::make_unique<IncludeRecorder>(Includes));
220 };
222 FMS.add(std::move(Module));
223
224 TestTU TU = TestTU::withCode(R"cpp(
225 #include "header.h"
226 void mainFileFunc(); // Ends the preamble; parsed during the main-file build.
227 )cpp");
228 TU.AdditionalFiles["header.h"] = "";
229 TU.FeatureModules = &FMS;
230 TU.build();
231 EXPECT_THAT(Includes, testing::ElementsAre("header.h"));
232}
233
234TEST(FeatureModulesTest, BeforeExecute) {
235 auto Module = std::make_unique<TestModule>();
236 Module->BeforeExecute = [](CompilerInstance &CI) {
237 CI.getPreprocessor().SetSuppressIncludeNotFoundError(true);
238 };
240 FMS.add(std::move(Module));
241
242 TestTU TU = TestTU::withCode(R"cpp(
243 /*error-ok*/
244 #include "not_found.h"
245
246 void foo() {
247 #include "not_found_not_preamble.h"
248 }
249 )cpp");
250
251 {
252 auto AST = TU.build();
253 EXPECT_THAT(AST.getDiagnostics(), testing::Not(testing::IsEmpty()));
254 }
255
256 TU.FeatureModules = &FMS;
257 {
258 auto AST = TU.build();
259 EXPECT_THAT(AST.getDiagnostics(), testing::IsEmpty());
260 }
261}
262
263TEST(FeatureModulesTest, AfterExecute) {
264 std::vector<std::string> DeclNames;
265 auto Module = std::make_unique<TestModule>();
266 Module->AfterExecute = [&DeclNames](CompilerInstance &CI) {
267 for (Decl *D : CI.getASTContext().getTraversalScope())
268 if (const auto *ND = llvm::dyn_cast<NamedDecl>(D))
269 DeclNames.push_back(ND->getNameAsString());
270 };
272 FMS.add(std::move(Module));
273
274 TestTU TU = TestTU::withCode(R"cpp(
275 #include "header.h"
276 void mainFileFunc();
277 )cpp");
278 TU.AdditionalFiles["header.h"] = "void headerFunc();";
279 TU.FeatureModules = &FMS;
280 TU.build();
281
282 // afterExecute runs once clangd has restricted the traversal scope, so the
283 // declaration from the header is intentionally not visible here.
284 EXPECT_THAT(DeclNames, testing::ElementsAre("mainFileFunc"));
285}
286
287TEST(FeatureModulesTest, FinalizeDiagnostic) {
288 unsigned Notes = 0;
289 unsigned Fixes = 0;
290 auto Module = std::make_unique<TestModule>();
291 Module->FinalizeDiagnostic = [&](clangd::Diag &Diag) {
292 if (Diag.Message.find("undeclared identifier 'fooo'") == std::string::npos)
293 return;
294 Notes = Diag.Notes.size();
295 Fixes = Diag.Fixes.size();
296 };
298 FMS.add(std::move(Module));
299
300 TestTU TU = TestTU::withCode(R"cpp(
301 void foo();
302 void bar() { fooo(); } // error-ok
303 )cpp");
304 TU.FeatureModules = &FMS;
305 EXPECT_THAT(TU.build().getDiagnostics(), testing::SizeIs(1));
306 EXPECT_EQ(Notes, 1u);
307 EXPECT_EQ(Fixes, 1u);
308}
309
310TEST(FeatureModulesTest, ClangTidyTemporaryProvider) {
311 if (!CLANGD_TIDY_CHECKS)
312 GTEST_SKIP() << "Requires clang-tidy checks";
313 FeatureModuleSet Modules;
314 Modules.add(std::make_unique<ClangTidyFeatureModule>(
315 addTidyChecks("modernize-use-nullptr")));
316 auto TU = TestTU::withCode("int *p = 0;");
317 TU.FeatureModules = &Modules;
318 auto *Tidy = Modules.get<ClangTidyFeatureModule>();
319 auto Provider = std::make_shared<const TidyProvider>(addTidyChecks("-*"));
320 // Check-mode timing temporarily overrides tidy's configuration, then
321 // restores the original provider for subsequent builds.
322 Tidy->swapProvider(Provider);
323 EXPECT_THAT(TU.build().getDiagnostics(), testing::IsEmpty());
324 Tidy->swapProvider(Provider);
325 auto AST = TU.build();
326 ASSERT_THAT(AST.getDiagnostics(), testing::SizeIs(1));
327 EXPECT_EQ(AST.getDiagnostics().front().Name, "modernize-use-nullptr");
328}
329
330TEST(FeatureModulesTest, ClangTidyDoesNotReinitializeConsumer) {
331 if (!CLANGD_TIDY_CHECKS)
332 GTEST_SKIP() << "Requires clang-tidy checks";
333 class InitializationGuard final : public MultiplexConsumer {
334 public:
335 using MultiplexConsumer::MultiplexConsumer;
336 void Initialize(ASTContext &Ctx) override {
337 EXPECT_FALSE(Initialized);
338 Initialized = true;
339 MultiplexConsumer::Initialize(Ctx);
340 }
341
342 private:
343 bool Initialized = false;
344 };
345 auto Module = std::make_unique<TestModule>();
346 Module->BeforePPCallbacks = [](CompilerInstance &CI) {
347 if (CI.getFrontendOpts().ProgramAction == frontend::ParseSyntaxOnly)
348 CI.setASTConsumer(
349 std::make_unique<InitializationGuard>(CI.takeASTConsumer()));
350 };
351 FeatureModuleSet Modules;
352 Modules.add(std::move(Module));
353 auto TU = TestTU::withCode("int *p = 0;");
354 TU.FeatureModules = &Modules;
355 Modules.add(std::make_unique<ClangTidyFeatureModule>(
356 addTidyChecks("modernize-use-nullptr")));
357 // Running tidy must not reinitialize the existing AST consumer.
358 EXPECT_THAT(TU.build().getDiagnostics(), testing::SizeIs(1));
359}
360
361} // namespace
362} // namespace clangd
363} // namespace clang
Same as llvm::Annotations, but adjusts functions to LSP-specific types for positions and ranges.
Definition Annotations.h:23
Runs clang-tidy checks as part of clangd's AST build.
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
TidyProvider addTidyChecks(llvm::StringRef Checks, llvm::StringRef WarningsAsErrors)
Provider the enables a specific set of checks and warnings as errors.
TEST(BackgroundQueueTest, Priority)
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
A top-level diagnostic that may have Notes and Fixes.
Definition Diagnostics.h:95
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:114
static TestTU withCode(llvm::StringRef Code)
Definition TestTU.h:36
Input to prepare and apply tweaks.
Definition Tweak.h:49