clang-tools 24.0.0git
DiagnosticsTests.cpp
Go to the documentation of this file.
1//===--- DiagnosticsTests.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 "../clang-tidy/ClangTidyOptions.h"
10#include "Annotations.h"
11#include "Config.h"
12#include "Diagnostics.h"
13#include "Feature.h"
14#include "FeatureModule.h"
15#include "ParsedAST.h"
16#include "Protocol.h"
17#include "SourceCode.h"
18#include "TestFS.h"
19#include "TestIndex.h"
20#include "TestTU.h"
21#include "TidyProvider.h"
22#include "clang-tidy-config.h"
23#include "index/MemIndex.h"
24#include "index/Ref.h"
25#include "index/Relation.h"
26#include "index/Symbol.h"
27#include "support/Context.h"
28#include "support/Path.h"
29#include "clang/AST/Decl.h"
30#include "clang/Basic/Diagnostic.h"
31#include "clang/Basic/DiagnosticSema.h"
32#include "clang/Basic/LLVM.h"
33#include "clang/Basic/SourceManager.h"
34#include "clang/Basic/Specifiers.h"
35#include "llvm/ADT/ArrayRef.h"
36#include "llvm/ADT/StringRef.h"
37#include "llvm/Support/Error.h"
38#include "llvm/Support/JSON.h"
39#include "llvm/Support/ScopedPrinter.h"
40#include "llvm/Support/TargetSelect.h"
41#include "llvm/Testing/Support/SupportHelpers.h"
42#include "gmock/gmock.h"
43#include "gtest/gtest.h"
44#include <cstddef>
45#include <memory>
46#include <optional>
47#include <string>
48#include <utility>
49#include <vector>
50
51namespace clang {
52namespace clangd {
53namespace {
54
55using ::testing::_;
56using ::testing::AllOf;
57using ::testing::Contains;
58using ::testing::Each;
59using ::testing::ElementsAre;
60using ::testing::Field;
61using ::testing::IsEmpty;
62using ::testing::Not;
63using ::testing::Pair;
64using ::testing::SizeIs;
65using ::testing::UnorderedElementsAre;
66
67::testing::Matcher<const Diag &> withFix(::testing::Matcher<Fix> FixMatcher) {
68 return Field(&Diag::Fixes, ElementsAre(FixMatcher));
69}
70
71::testing::Matcher<const Diag &> withFix(::testing::Matcher<Fix> FixMatcher1,
72 ::testing::Matcher<Fix> FixMatcher2) {
73 return Field(&Diag::Fixes, UnorderedElementsAre(FixMatcher1, FixMatcher2));
74}
75
76::testing::Matcher<const Diag &> withID(unsigned ID) {
77 return Field(&Diag::ID, ID);
78}
79::testing::Matcher<const Diag &>
80withNote(::testing::Matcher<Note> NoteMatcher) {
81 return Field(&Diag::Notes, ElementsAre(NoteMatcher));
82}
83
84::testing::Matcher<const Diag &>
85withNote(::testing::Matcher<Note> NoteMatcher1,
86 ::testing::Matcher<Note> NoteMatcher2) {
87 return Field(&Diag::Notes, UnorderedElementsAre(NoteMatcher1, NoteMatcher2));
88}
89
90::testing::Matcher<const Diag &>
91withTag(::testing::Matcher<DiagnosticTag> TagMatcher) {
92 return Field(&Diag::Tags, Contains(TagMatcher));
93}
94
95MATCHER_P(hasRange, Range, "") { return arg.Range == Range; }
96
97MATCHER_P2(Diag, Range, Message,
98 "Diag at " + llvm::to_string(Range) + " = [" + Message + "]") {
99 return arg.Range == Range && arg.Message == Message;
100}
101
102MATCHER_P3(Fix, Range, Replacement, Message,
103 "Fix " + llvm::to_string(Range) + " => " +
104 ::testing::PrintToString(Replacement) + " = [" + Message + "]") {
105 return arg.Message == Message && arg.Edits.size() == 1 &&
106 arg.Edits[0].range == Range && arg.Edits[0].newText == Replacement;
107}
108
109MATCHER_P(fixMessage, Message, "") { return arg.Message == Message; }
110
111MATCHER_P(equalToLSPDiag, LSPDiag,
112 "LSP diagnostic " + llvm::to_string(LSPDiag)) {
113 if (toJSON(arg) != toJSON(LSPDiag)) {
114 *result_listener << llvm::formatv("expected:\n{0:2}\ngot\n{1:2}",
115 toJSON(LSPDiag), toJSON(arg))
116 .str();
117 return false;
118 }
119 return true;
120}
121
122MATCHER_P(diagSource, S, "") { return arg.Source == S; }
123MATCHER_P(diagName, N, "") { return arg.Name == N; }
124MATCHER_P(diagSeverity, S, "") { return arg.Severity == S; }
125
126MATCHER_P(equalToFix, Fix, "LSP fix " + llvm::to_string(Fix)) {
127 if (arg.Message != Fix.Message)
128 return false;
129 if (arg.Edits.size() != Fix.Edits.size())
130 return false;
131 for (std::size_t I = 0; I < arg.Edits.size(); ++I) {
132 if (arg.Edits[I].range != Fix.Edits[I].range ||
133 arg.Edits[I].newText != Fix.Edits[I].newText)
134 return false;
135 }
136 return true;
137}
138
139// Helper function to make tests shorter.
140Position pos(int Line, int Character) {
141 Position Res;
142 Res.line = Line;
143 Res.character = Character;
144 return Res;
145}
146
147// Normally returns the provided diagnostics matcher.
148// If clang-tidy checks are not linked in, returns a matcher for no diagnostics!
149// This is intended for tests where the diagnostics come from clang-tidy checks.
150// We don't #ifdef each individual test as it's intrusive and we want to ensure
151// that as much of the test is still compiled an run as possible.
152::testing::Matcher<std::vector<clangd::Diag>>
153ifTidyChecks(::testing::Matcher<std::vector<clangd::Diag>> M) {
154 if (!CLANGD_TIDY_CHECKS)
155 return IsEmpty();
156 return M;
157}
158
159TEST(DiagnosticsTest, DiagnosticRanges) {
160 // Check we report correct ranges, including various edge-cases.
161 Annotations Test(R"cpp(
162 // error-ok
163 #define ID(X) X
164 namespace test{};
165 void $decl[[foo]]();
166 int main() {
167 struct Container { int* begin(); int* end(); } *container;
168 for (auto i : $insertstar[[]]$range[[container]]) {
169 }
170
171 $typo[[go\
172o]]();
173 foo()$semicolon[[]]//with comments
174 $unk[[unknown]]();
175 double $type[[bar]] = "foo";
176 struct Foo { int x; }; Foo a;
177 a.$nomember[[y]];
178 test::$nomembernamespace[[test]];
179 $macro[[ID($macroarg[[fod]])]]();
180 }
181 )cpp");
182 auto TU = TestTU::withCode(Test.code());
183 EXPECT_THAT(
184 TU.build().getDiagnostics(),
185 ElementsAre(
186 // Make sure the whole token is highlighted.
187 AllOf(Diag(Test.range("range"),
188 "invalid range expression of type 'struct Container *'; "
189 "did you mean to dereference it with '*'?"),
190 withFix(Fix(Test.range("insertstar"), "*", "insert '*'"))),
191 // This range spans lines.
192 AllOf(Diag(Test.range("typo"),
193 "use of undeclared identifier 'goo'; did you mean 'foo'?"),
194 diagSource(Diag::Clang), diagName("undeclared_var_use_suggest"),
195 withFix(
196 Fix(Test.range("typo"), "foo", "change 'go\\…' to 'foo'")),
197 // This is a pretty normal range.
198 withNote(Diag(Test.range("decl"), "'foo' declared here"))),
199 // This range is zero-width and insertion. Therefore make sure we are
200 // not expanding it into other tokens. Since we are not going to
201 // replace those.
202 AllOf(Diag(Test.range("semicolon"), "expected ';' after expression"),
203 withFix(Fix(Test.range("semicolon"), ";", "insert ';'"))),
204 // This range isn't provided by clang, we expand to the token.
205 Diag(Test.range("unk"), "use of undeclared identifier 'unknown'"),
206 Diag(Test.range("type"),
207 "cannot initialize a variable of type 'double' with an lvalue "
208 "of type 'const char[4]'"),
209 Diag(Test.range("nomember"), "no member named 'y' in 'Foo'"),
210 Diag(Test.range("nomembernamespace"),
211 "no member named 'test' in namespace 'test'"),
212 AllOf(Diag(Test.range("macro"),
213 "use of undeclared identifier 'fod'; did you mean 'foo'?"),
214 withFix(Fix(Test.range("macroarg"), "foo",
215 "change 'fod' to 'foo'")))));
216}
217
218// Verify that the -Wswitch case-not-covered diagnostic range covers the
219// whole expression. This is important because the "populate-switch" tweak
220// fires for the full expression range (see tweaks/PopulateSwitchTests.cpp).
221// The quickfix flow only works end-to-end if the tweak can be triggered on
222// the diagnostic's range.
223TEST(DiagnosticsTest, WSwitch) {
224 Annotations Test(R"cpp(
225 enum A { X };
226 struct B { A a; };
227 void foo(B b) {
228 switch ([[b.a]]) {}
229 }
230 )cpp");
231 auto TU = TestTU::withCode(Test.code());
232 TU.ExtraArgs = {"-Wswitch"};
233 EXPECT_THAT(TU.build().getDiagnostics(),
234 ElementsAre(Diag(Test.range(),
235 "enumeration value 'X' not handled in switch")));
236}
237
238TEST(DiagnosticsTest, FlagsMatter) {
239 Annotations Test("[[void]] main() {} // error-ok");
240 auto TU = TestTU::withCode(Test.code());
241 EXPECT_THAT(TU.build().getDiagnostics(),
242 ElementsAre(AllOf(Diag(Test.range(), "'main' must return 'int'"),
243 withFix(Fix(Test.range(), "int",
244 "change 'void' to 'int'")))));
245 // Same code built as C gets different diagnostics.
246 TU.Filename = "Plain.c";
247 EXPECT_THAT(
248 TU.build().getDiagnostics(),
249 ElementsAre(AllOf(
250 Diag(Test.range(), "return type of 'main' is not 'int'"),
251 withFix(Fix(Test.range(), "int", "change return type to 'int'")))));
252}
253
254TEST(DiagnosticsTest, DiagnosticPreamble) {
255 Annotations Test(R"cpp(
256 #include $[["not-found.h"]] // error-ok
257 )cpp");
258
259 auto TU = TestTU::withCode(Test.code());
260 EXPECT_THAT(TU.build().getDiagnostics(),
261 ElementsAre(::testing::AllOf(
262 Diag(Test.range(), "'not-found.h' file not found"),
263 diagSource(Diag::Clang), diagName("pp_file_not_found"))));
264}
265
266TEST(DiagnosticsTest, DeduplicatedClangTidyDiagnostics) {
267 Annotations Test(R"cpp(
268 float foo = [[0.1f]];
269 )cpp");
270 auto TU = TestTU::withCode(Test.code());
271 // Enable alias clang-tidy checks, these checks emit the same diagnostics
272 // (except the check name).
273 TU.ClangTidyProvider = addTidyChecks("readability-uppercase-literal-suffix,"
274 "cert-dcl16-c");
275 // Verify that we filter out the duplicated diagnostic message.
276 EXPECT_THAT(
277 TU.build().getDiagnostics(),
278 ifTidyChecks(UnorderedElementsAre(::testing::AllOf(
279 Diag(Test.range(),
280 "floating point literal has suffix 'f', which is not uppercase"),
281 diagSource(Diag::ClangTidy)))));
282
283 Test = Annotations(R"cpp(
284 template<typename T>
285 void func(T) {
286 float f = [[0.3f]];
287 }
288 void k() {
289 func(123);
290 func(2.0);
291 }
292 )cpp");
293 TU.Code = std::string(Test.code());
294 // The check doesn't handle template instantiations which ends up emitting
295 // duplicated messages, verify that we deduplicate them.
296 EXPECT_THAT(
297 TU.build().getDiagnostics(),
298 ifTidyChecks(UnorderedElementsAre(::testing::AllOf(
299 Diag(Test.range(),
300 "floating point literal has suffix 'f', which is not uppercase"),
301 diagSource(Diag::ClangTidy)))));
302}
303
304TEST(DiagnosticsTest, ClangTidy) {
305 Annotations Test(R"cpp(
306 #include $deprecated[["assert.h"]]
307
308 #define $macrodef[[SQUARE]](X) (X)*(X)
309 int $main[[main]]() {
310 int y = 4;
311 return SQUARE($macroarg[[++]]y);
312 return $doubled[[sizeof(sizeof(int))]];
313 }
314
315 // misc-no-recursion uses a custom traversal from the TUDecl
316 void foo();
317 void $bar[[bar]]() {
318 foo();
319 }
320 void $foo[[foo]]() {
321 bar();
322 }
323 )cpp");
324 auto TU = TestTU::withCode(Test.code());
325 TU.AdditionalFiles["system/assert.h"] = ""; // Suppress "not found" error.
326 TU.ClangTidyProvider = addTidyChecks("bugprone-sizeof-expression,"
327 "bugprone-macro-repeated-side-effects,"
328 "modernize-deprecated-headers,"
329 "modernize-use-trailing-return-type,"
330 "misc-no-recursion");
331 TU.ExtraArgs.push_back("-isystem" + testPath("system"));
332 TU.ExtraArgs.push_back("-Wno-unsequenced");
333 EXPECT_THAT(
334 TU.build().getDiagnostics(),
335 ifTidyChecks(UnorderedElementsAre(
336 AllOf(Diag(Test.range("deprecated"),
337 "inclusion of deprecated C++ header 'assert.h'; consider "
338 "using 'cassert' instead"),
339 diagSource(Diag::ClangTidy),
340 diagName("modernize-deprecated-headers"),
341 withFix(Fix(Test.range("deprecated"), "<cassert>",
342 "change '\"assert.h\"' to '<cassert>'"))),
343 Diag(Test.range("doubled"),
344 "suspicious usage of 'sizeof(sizeof(...))'"),
345 AllOf(Diag(Test.range("macroarg"),
346 "side effects in the 1st macro argument 'X' are "
347 "repeated in "
348 "macro expansion"),
349 diagSource(Diag::ClangTidy),
350 diagName("bugprone-macro-repeated-side-effects"),
351 withNote(Diag(Test.range("macrodef"),
352 "macro 'SQUARE' defined here"))),
353 AllOf(Diag(Test.range("main"),
354 "use a trailing return type for this function"),
355 diagSource(Diag::ClangTidy),
356 diagName("modernize-use-trailing-return-type"),
357 // Verify there's no "[check-name]" suffix in the message.
358 withFix(fixMessage(
359 "use a trailing return type for this function"))),
360 Diag(Test.range("foo"),
361 "function 'foo' is within a recursive call chain"),
362 Diag(Test.range("bar"),
363 "function 'bar' is within a recursive call chain"))));
364}
365
366#if CLANGD_TIDY_CHECKS && CLANG_TIDY_ENABLE_QUERY_BASED_CUSTOM_CHECKS
367TEST(DiagnosticsTest, ClangTidyExperimentalCustomChecks) {
368 Annotations Test(R"cpp(
369 int x = $literal[[0]];
370 )cpp");
371 auto TU = TestTU::withCode(Test.code());
372 TU.ClangTidyProvider = [](tidy::ClangTidyOptions &Opts, llvm::StringRef) {
373 Opts.Checks = "-*,custom-integer-literal";
374 tidy::ClangTidyOptions::CustomCheckValue Check;
375 Check.Name = "integer-literal";
376 Check.Query = R"(match integerLiteral().bind("literal"))";
377 tidy::ClangTidyOptions::CustomCheckDiag Diagnostic;
378 Diagnostic.BindName = "literal";
379 Diagnostic.Message = "integer literal found";
380 Diagnostic.Level = DiagnosticIDs::Warning;
381 Check.Diags.push_back(std::move(Diagnostic));
382 Opts.CustomChecks.emplace();
383 Opts.CustomChecks->push_back(std::move(Check));
384 };
385
386 auto DiagnosticsFor = [&](bool Enabled, Config::FastCheckPolicy Policy) {
387 Config Cfg;
389 Cfg.Diagnostics.ClangTidy.FastCheckFilter = Policy;
390 WithContextValue WithCfg(Config::Key, std::move(Cfg));
391 auto AST = TU.build();
392 return std::vector<clangd::Diag>(AST.getDiagnostics());
393 };
394 auto CustomDiagnostic =
395 AllOf(Diag(Test.range("literal"), "integer literal found"),
396 diagSource(Diag::ClangTidy), diagName("custom-integer-literal"),
397 diagSeverity(DiagnosticsEngine::Warning));
398
399 EXPECT_THAT(DiagnosticsFor(false, Config::FastCheckPolicy::Loose), IsEmpty());
400 EXPECT_THAT(DiagnosticsFor(true, Config::FastCheckPolicy::Strict), IsEmpty());
401 EXPECT_THAT(DiagnosticsFor(true, Config::FastCheckPolicy::Loose),
402 ElementsAre(CustomDiagnostic));
403 EXPECT_THAT(DiagnosticsFor(true, Config::FastCheckPolicy::None),
404 ElementsAre(CustomDiagnostic));
405}
406#endif
407
408TEST(DiagnosticsTest, ClangTidyRedundantParenthesesFix) {
409 Annotations Test(R"cpp(
410 int func() {
411 return$lparen[[(]]0$rparen[[)]];
412 }
413 )cpp");
414 auto TU = TestTU::withCode(Test.code());
415 TU.ClangTidyProvider = addTidyChecks("readability-redundant-parentheses");
416
417 clangd::Fix ExpectedFix;
418 ExpectedFix.Message = "redundant parentheses around expression";
419 ExpectedFix.Edits.push_back(TextEdit{Test.range("lparen"), " "});
420 ExpectedFix.Edits.push_back(TextEdit{Test.range("rparen"), ""});
421
422 EXPECT_THAT(
423 TU.build().getDiagnostics(),
424 ifTidyChecks(ElementsAre(AllOf(
425 Diag(Test.range("lparen"), "redundant parentheses around expression"),
426 diagSource(Diag::ClangTidy),
427 diagName("readability-redundant-parentheses"),
428 withFix(equalToFix(ExpectedFix))))));
429}
430
431TEST(DiagnosticsTest, ClangTidyEOF) {
432 // clang-format off
433 Annotations Test(R"cpp(
434 [[#]]include <b.h>
435 #include "a.h")cpp");
436 // clang-format on
437 auto TU = TestTU::withCode(Test.code());
438 TU.ExtraArgs = {"-isystem."};
439 TU.AdditionalFiles["a.h"] = TU.AdditionalFiles["b.h"] = "";
440 TU.ClangTidyProvider = addTidyChecks("llvm-include-order");
441 EXPECT_THAT(
442 TU.build().getDiagnostics(),
443 ifTidyChecks(Contains(
444 AllOf(Diag(Test.range(), "#includes are not sorted properly"),
445 diagSource(Diag::ClangTidy), diagName("llvm-include-order")))));
446}
447
448TEST(DiagnosticTest, TemplatesInHeaders) {
449 // Diagnostics from templates defined in headers are placed at the expansion.
450 Annotations Main(R"cpp(
451 Derived<int> [[y]]; // error-ok
452 )cpp");
453 Annotations Header(R"cpp(
454 template <typename T>
455 struct Derived : [[T]] {};
456 )cpp");
457 TestTU TU = TestTU::withCode(Main.code());
458 TU.HeaderCode = Header.code().str();
459 EXPECT_THAT(
460 TU.build().getDiagnostics(),
461 ElementsAre(AllOf(
462 Diag(Main.range(), "in template: base specifier must name a class"),
463 withNote(Diag(Header.range(), "error occurred here"),
464 Diag(Main.range(), "in instantiation of template class "
465 "'Derived<int>' requested here")))));
466}
467
468TEST(DiagnosticTest, MakeUnique) {
469 // We usually miss diagnostics from header functions as we don't parse them.
470 // std::make_unique is an exception.
471 Annotations Main(R"cpp(
472 struct S { S(char*); };
473 auto x = std::[[make_unique]]<S>(42); // error-ok
474 )cpp");
475 TestTU TU = TestTU::withCode(Main.code());
476 TU.HeaderCode = R"cpp(
477 namespace std {
478 // These mocks aren't quite right - we omit unique_ptr for simplicity.
479 // forward is included to show its body is not needed to get the diagnostic.
480 template <typename T> T&& forward(T& t);
481 template <typename T, typename... A> T* make_unique(A&&... args) {
482 return new T(std::forward<A>(args)...);
483 }
484 }
485 )cpp";
486 EXPECT_THAT(TU.build().getDiagnostics(),
487 UnorderedElementsAre(
488 Diag(Main.range(),
489 "in template: "
490 "no matching constructor for initialization of 'S'")));
491}
492
493TEST(DiagnosticTest, CoroutineInHeader) {
494 StringRef CoroutineH = R"cpp(
495namespace std {
496template <class Ret, typename... T>
497struct coroutine_traits { using promise_type = typename Ret::promise_type; };
498
499template <class Promise = void>
500struct coroutine_handle {
501 static coroutine_handle from_address(void *) noexcept;
502 static coroutine_handle from_promise(Promise &promise);
503 constexpr void* address() const noexcept;
504};
505template <>
506struct coroutine_handle<void> {
507 template <class PromiseType>
508 coroutine_handle(coroutine_handle<PromiseType>) noexcept;
509 static coroutine_handle from_address(void *);
510 constexpr void* address() const noexcept;
511};
512
513struct awaitable {
514 bool await_ready() noexcept { return false; }
515 void await_suspend(coroutine_handle<>) noexcept {}
516 void await_resume() noexcept {}
517};
518} // namespace std
519 )cpp";
520
521 StringRef Header = R"cpp(
522#include "coroutine.h"
523template <typename T> struct [[clang::coro_return_type]] Gen {
524 struct promise_type {
525 Gen<T> get_return_object() {
526 return {};
527 }
528 std::awaitable initial_suspend();
529 std::awaitable final_suspend() noexcept;
530 void unhandled_exception();
531 void return_value(T t);
532 };
533};
534
535Gen<int> foo_coro(int b) { co_return b; }
536 )cpp";
537 Annotations Main(R"cpp(
538// error-ok
539#include "header.hpp"
540Gen<int> $[[bar_coro]](int b) { return foo_coro(b); }
541 )cpp");
542 TestTU TU = TestTU::withCode(Main.code());
543 TU.AdditionalFiles["coroutine.h"] = std::string(CoroutineH);
544 TU.AdditionalFiles["header.hpp"] = std::string(Header);
545 TU.ExtraArgs.push_back("--std=c++20");
546 EXPECT_THAT(TU.build().getDiagnostics(), ElementsAre(hasRange(Main.range())));
547}
548
549TEST(DiagnosticTest, MakeShared) {
550 // We usually miss diagnostics from header functions as we don't parse them.
551 // std::make_shared is only parsed when --parse-forwarding-functions is set
552 Annotations Main(R"cpp(
553 struct S { S(char*); };
554 auto x = std::[[make_shared]]<S>(42); // error-ok
555 )cpp");
556 TestTU TU = TestTU::withCode(Main.code());
557 TU.HeaderCode = R"cpp(
558 namespace std {
559 // These mocks aren't quite right - we omit shared_ptr for simplicity.
560 // forward is included to show its body is not needed to get the diagnostic.
561 template <typename T> T&& forward(T& t);
562 template <typename T, typename... A> T* make_shared(A&&... args) {
563 return new T(std::forward<A>(args)...);
564 }
565 }
566 )cpp";
567 TU.ParseOpts.PreambleParseForwardingFunctions = true;
568 EXPECT_THAT(TU.build().getDiagnostics(),
569 UnorderedElementsAre(
570 Diag(Main.range(),
571 "in template: "
572 "no matching constructor for initialization of 'S'")));
573}
574
575TEST(DiagnosticTest, NoMultipleDiagnosticInFlight) {
576 Annotations Main(R"cpp(
577 template <typename T> struct Foo {
578 T *begin();
579 T *end();
580 };
581 struct LabelInfo {
582 int a;
583 bool b;
584 };
585
586 void f() {
587 Foo<LabelInfo> label_info_map;
588 [[for]] (auto it = label_info_map.begin(); it != label_info_map.end(); ++it) {
589 auto S = *it;
590 }
591 }
592 )cpp");
593 TestTU TU = TestTU::withCode(Main.code());
594 TU.ClangTidyProvider = addTidyChecks("modernize-loop-convert");
595 EXPECT_THAT(
596 TU.build().getDiagnostics(),
597 ifTidyChecks(UnorderedElementsAre(::testing::AllOf(
598 Diag(Main.range(), "use range-based for loop instead"),
599 diagSource(Diag::ClangTidy), diagName("modernize-loop-convert")))));
600}
601
602TEST(DiagnosticTest, RespectsDiagnosticConfig) {
603 Annotations Main(R"cpp(
604 // error-ok
605 void x() {
606 [[unknown]]();
607 $ret[[return]] 42;
608 }
609 )cpp");
610 auto TU = TestTU::withCode(Main.code());
611 EXPECT_THAT(
612 TU.build().getDiagnostics(),
613 ElementsAre(Diag(Main.range(), "use of undeclared identifier 'unknown'"),
614 Diag(Main.range("ret"),
615 "void function 'x' should not return a value")));
616 Config Cfg;
617 Cfg.Diagnostics.Suppress.insert("return-mismatch");
618 WithContextValue WithCfg(Config::Key, std::move(Cfg));
619 EXPECT_THAT(TU.build().getDiagnostics(),
620 ElementsAre(Diag(Main.range(),
621 "use of undeclared identifier 'unknown'")));
622}
623
624TEST(DiagnosticTest, RespectsDiagnosticConfigInHeader) {
625 Annotations Header(R"cpp(
626 int x = "42"; // error-ok
627 )cpp");
628 Annotations Main(R"cpp(
629 #include "header.hpp"
630 )cpp");
631 auto TU = TestTU::withCode(Main.code());
632 TU.AdditionalFiles["header.hpp"] = std::string(Header.code());
633 Config Cfg;
634 Cfg.Diagnostics.Suppress.insert("init_conversion_failed");
635 WithContextValue WithCfg(Config::Key, std::move(Cfg));
636 EXPECT_THAT(TU.build().getDiagnostics(), IsEmpty());
637}
638
639TEST(DiagnosticTest, ClangTidySuppressionComment) {
640 Annotations Main(R"cpp(
641 int main() {
642 int i = 3;
643 double d = 8 / i; // NOLINT
644 // NOLINTNEXTLINE
645 double e = 8 / i;
646 #define BAD 8 / i
647 double f = BAD; // NOLINT
648 double g = [[8]] / i;
649 #define BAD2 BAD
650 double h = BAD2; // NOLINT
651 // NOLINTBEGIN
652 double x = BAD2;
653 double y = BAD2;
654 // NOLINTEND
655
656 // verify no crashes on unmatched nolints.
657 // NOLINTBEGIN
658 }
659 )cpp");
660 TestTU TU = TestTU::withCode(Main.code());
661 TU.ClangTidyProvider = addTidyChecks("bugprone-integer-division");
662 EXPECT_THAT(
663 TU.build().getDiagnostics(),
664 ifTidyChecks(UnorderedElementsAre(::testing::AllOf(
665 Diag(Main.range(), "result of integer division used in a floating "
666 "point context; possible loss of precision"),
667 diagSource(Diag::ClangTidy),
668 diagName("bugprone-integer-division")))));
669}
670
671TEST(DiagnosticTest, ClangTidySystemMacro) {
672 Annotations Main(R"cpp(
673 #include "user.h"
674 #include "system.h"
675 int i = 3;
676 double x = $inline[[8]] / i;
677 double y = $user[[DIVIDE_USER]](i);
678 double z = DIVIDE_SYS(i);
679 )cpp");
680
681 auto TU = TestTU::withCode(Main.code());
682 TU.AdditionalFiles["user.h"] = R"cpp(
683 #define DIVIDE_USER(Y) 8/Y
684 )cpp";
685 TU.AdditionalFiles["system.h"] = R"cpp(
686 #pragma clang system_header
687 #define DIVIDE_SYS(Y) 8/Y
688 )cpp";
689
690 TU.ClangTidyProvider = addTidyChecks("bugprone-integer-division");
691 std::string BadDivision = "result of integer division used in a floating "
692 "point context; possible loss of precision";
693
694 // Expect to see warning from user macros, but not system macros.
695 // This matches clang-tidy --system-headers=0 (the default).
696 EXPECT_THAT(TU.build().getDiagnostics(),
697 ifTidyChecks(
698 UnorderedElementsAre(Diag(Main.range("inline"), BadDivision),
699 Diag(Main.range("user"), BadDivision))));
700}
701
702TEST(DiagnosticTest, ClangTidyWarningAsError) {
703 Annotations Main(R"cpp(
704 int main() {
705 int i = 3;
706 double f = [[8]] / i; // error-ok
707 }
708 )cpp");
709 TestTU TU = TestTU::withCode(Main.code());
710 TU.ClangTidyProvider =
711 addTidyChecks("bugprone-integer-division", "bugprone-integer-division");
712 EXPECT_THAT(
713 TU.build().getDiagnostics(),
714 ifTidyChecks(UnorderedElementsAre(::testing::AllOf(
715 Diag(Main.range(), "result of integer division used in a floating "
716 "point context; possible loss of precision"),
717 diagSource(Diag::ClangTidy), diagName("bugprone-integer-division"),
718 diagSeverity(DiagnosticsEngine::Error)))));
719}
720
721TidyProvider addClangArgs(std::vector<llvm::StringRef> ExtraArgs,
722 llvm::StringRef Checks) {
723 return [ExtraArgs = std::move(ExtraArgs), Checks = Checks.str()](
724 tidy::ClangTidyOptions &Opts, llvm::StringRef) {
725 if (!Opts.ExtraArgs)
726 Opts.ExtraArgs.emplace();
727 for (llvm::StringRef Arg : ExtraArgs)
728 Opts.ExtraArgs->emplace_back(Arg);
729 if (!Checks.empty())
730 Opts.Checks = Checks;
731 };
732}
733
734TEST(DiagnosticTest, ClangTidyEnablesClangWarning) {
735 Annotations Main(R"cpp( // error-ok
736 static void [[foo]]() {}
737 )cpp");
738 TestTU TU = TestTU::withCode(Main.code());
739 // This is always emitted as a clang warning, not a clang-tidy diagnostic.
740 auto UnusedFooWarning =
741 AllOf(Diag(Main.range(), "unused function 'foo'"),
742 diagName("-Wunused-function"), diagSource(Diag::Clang),
743 diagSeverity(DiagnosticsEngine::Warning));
744
745 // Check the -Wunused warning isn't initially on.
746 EXPECT_THAT(TU.build().getDiagnostics(), IsEmpty());
747
748 // We enable warnings based on clang-tidy extra args, if the matching
749 // clang-diagnostic- is there.
750 TU.ClangTidyProvider =
751 addClangArgs({"-Wunused"}, "clang-diagnostic-unused-function");
752 EXPECT_THAT(TU.build().getDiagnostics(), ElementsAre(UnusedFooWarning));
753
754 // clang-diagnostic-* is acceptable
755 TU.ClangTidyProvider = addClangArgs({"-Wunused"}, "clang-diagnostic-*");
756 EXPECT_THAT(TU.build().getDiagnostics(), ElementsAre(UnusedFooWarning));
757 // And plain * (may turn on other checks too).
758 TU.ClangTidyProvider = addClangArgs({"-Wunused"}, "*");
759 EXPECT_THAT(TU.build().getDiagnostics(), Contains(UnusedFooWarning));
760 // And we can explicitly exclude a category too.
761 TU.ClangTidyProvider = addClangArgs(
762 {"-Wunused"}, "clang-diagnostic-*,-clang-diagnostic-unused-function");
763 EXPECT_THAT(TU.build().getDiagnostics(), IsEmpty());
764
765 // Without the exact check specified, the warnings are not enabled.
766 TU.ClangTidyProvider = addClangArgs({"-Wunused"}, "clang-diagnostic-unused");
767 EXPECT_THAT(TU.build().getDiagnostics(), IsEmpty());
768
769 // We don't respect other args.
770 TU.ClangTidyProvider = addClangArgs({"-Wunused", "-Dfoo=bar"},
771 "clang-diagnostic-unused-function");
772 EXPECT_THAT(TU.build().getDiagnostics(), ElementsAre(UnusedFooWarning))
773 << "Not unused function 'bar'!";
774
775 // -Werror doesn't apply to warnings enabled by clang-tidy extra args.
776 TU.ExtraArgs = {"-Werror"};
777 TU.ClangTidyProvider =
778 addClangArgs({"-Wunused"}, "clang-diagnostic-unused-function");
779 EXPECT_THAT(TU.build().getDiagnostics(),
780 ElementsAre(diagSeverity(DiagnosticsEngine::Warning)));
781
782 // But clang-tidy extra args won't *downgrade* errors to warnings either.
783 TU.ExtraArgs = {"-Wunused", "-Werror"};
784 TU.ClangTidyProvider =
785 addClangArgs({"-Wunused"}, "clang-diagnostic-unused-function");
786 EXPECT_THAT(TU.build().getDiagnostics(),
787 ElementsAre(diagSeverity(DiagnosticsEngine::Error)));
788
789 // FIXME: we're erroneously downgrading the whole group, this should be Error.
790 TU.ExtraArgs = {"-Wunused-function", "-Werror"};
791 TU.ClangTidyProvider =
792 addClangArgs({"-Wunused"}, "clang-diagnostic-unused-label");
793 EXPECT_THAT(TU.build().getDiagnostics(),
794 ElementsAre(diagSeverity(DiagnosticsEngine::Warning)));
795
796 // This looks silly, but it's the typical result if a warning is enabled by a
797 // high-level .clang-tidy file and disabled by a low-level one.
798 TU.ExtraArgs = {};
799 TU.ClangTidyProvider = addClangArgs({"-Wunused", "-Wno-unused"},
800 "clang-diagnostic-unused-function");
801 EXPECT_THAT(TU.build().getDiagnostics(), IsEmpty());
802
803 // Overriding only works in the proper order.
804 TU.ClangTidyProvider =
805 addClangArgs({"-Wunused"}, {"clang-diagnostic-unused-function"});
806 EXPECT_THAT(TU.build().getDiagnostics(), SizeIs(1));
807
808 // More specific vs less-specific: match clang behavior
809 TU.ClangTidyProvider = addClangArgs({"-Wunused", "-Wno-unused-function"},
810 {"clang-diagnostic-unused-function"});
811 EXPECT_THAT(TU.build().getDiagnostics(), IsEmpty());
812 TU.ClangTidyProvider = addClangArgs({"-Wunused-function", "-Wno-unused"},
813 {"clang-diagnostic-unused-function"});
814 EXPECT_THAT(TU.build().getDiagnostics(), IsEmpty());
815
816 // We do allow clang-tidy config to disable warnings from the compile
817 // command. It's unclear this is ideal, but it's hard to avoid.
818 TU.ExtraArgs = {"-Wunused"};
819 TU.ClangTidyProvider = addClangArgs({"-Wno-unused"}, {});
820 EXPECT_THAT(TU.build().getDiagnostics(), IsEmpty());
821
822 TU.ExtraArgs = {"-Wno-unused"};
823 TU.ClangTidyProvider = addClangArgs({"-Wunused"}, {"-*, clang-diagnostic-*"});
824 EXPECT_THAT(TU.build().getDiagnostics(), SizeIs(1));
825}
826
827TEST(DiagnosticTest, LongFixMessages) {
828 // We limit the size of printed code.
829 Annotations Source(R"cpp(
830 int main() {
831 // error-ok
832 int somereallyreallyreallyreallyreallyreallyreallyreallylongidentifier;
833 [[omereallyreallyreallyreallyreallyreallyreallyreallylongidentifier]]= 10;
834 }
835 )cpp");
836 TestTU TU = TestTU::withCode(Source.code());
837 EXPECT_THAT(
838 TU.build().getDiagnostics(),
839 ElementsAre(withFix(Fix(
840 Source.range(),
841 "somereallyreallyreallyreallyreallyreallyreallyreallylongidentifier",
842 "change 'omereallyreallyreallyreallyreallyreallyreallyreall…' to "
843 "'somereallyreallyreallyreallyreallyreallyreallyreal…'"))));
844 // Only show changes up to a first newline.
845 Source = Annotations(R"cpp(
846 // error-ok
847 int main() {
848 int ident;
849 [[ide\
850n]] = 10; // error-ok
851 }
852 )cpp");
853 TU.Code = std::string(Source.code());
854 EXPECT_THAT(TU.build().getDiagnostics(),
855 ElementsAre(withFix(
856 Fix(Source.range(), "ident", "change 'ide\\…' to 'ident'"))));
857}
858
859TEST(DiagnosticTest, NewLineFixMessage) {
860 Annotations Source("int a;[[]]");
861 TestTU TU = TestTU::withCode(Source.code());
862 TU.ExtraArgs = {"-Wnewline-eof"};
863 EXPECT_THAT(
864 TU.build().getDiagnostics(),
865 ElementsAre(withFix((Fix(Source.range(), "\n", "insert '\\n'")))));
866}
867
868TEST(DiagnosticTest, ClangTidySuppressionCommentTrumpsWarningAsError) {
869 Annotations Main(R"cpp(
870 int main() {
871 int i = 3;
872 double f = [[8]] / i; // NOLINT
873 }
874 )cpp");
875 TestTU TU = TestTU::withCode(Main.code());
876 TU.ClangTidyProvider =
877 addTidyChecks("bugprone-integer-division", "bugprone-integer-division");
878 EXPECT_THAT(TU.build().getDiagnostics(), UnorderedElementsAre());
879}
880
881TEST(DiagnosticTest, ClangTidyNoLiteralDataInMacroToken) {
882 Annotations Main(R"cpp(
883 #define SIGTERM 15
884 using pthread_t = int;
885 int pthread_kill(pthread_t thread, int sig);
886 int func() {
887 pthread_t thread;
888 return pthread_kill(thread, 0);
889 }
890 )cpp");
891 TestTU TU = TestTU::withCode(Main.code());
892 TU.ClangTidyProvider = addTidyChecks("bugprone-bad-signal-to-kill-thread");
893 EXPECT_THAT(TU.build().getDiagnostics(), UnorderedElementsAre()); // no-crash
894}
895
896TEST(DiagnosticTest, BadSignalToKillThreadInPreamble) {
897 Annotations Main(R"cpp(
898 #include "signal.h"
899 using pthread_t = int;
900 int pthread_kill(pthread_t thread, int sig);
901 int func() {
902 pthread_t thread;
903 return pthread_kill(thread, 15);
904 }
905 )cpp");
906 TestTU TU = TestTU::withCode(Main.code());
907 TU.HeaderFilename = "signal.h";
908 TU.HeaderCode = "#define SIGTERM 15";
909 TU.ClangTidyProvider = addTidyChecks("bugprone-bad-signal-to-kill-thread");
910 EXPECT_THAT(TU.build().getDiagnostics(),
911 ifTidyChecks(UnorderedElementsAre(
912 diagName("bugprone-bad-signal-to-kill-thread"))));
913}
914
915TEST(DiagnosticTest, ClangTidyMacroToEnumCheck) {
916 Annotations Main(R"cpp(
917 #if 1
918 auto foo();
919 #endif
920 )cpp");
921 TestTU TU = TestTU::withCode(Main.code());
922 std::vector<TidyProvider> Providers;
923 Providers.push_back(
924 addTidyChecks("cppcoreguidelines-macro-to-enum,modernize-macro-to-enum"));
925 Providers.push_back(disableUnusableChecks());
926 TU.ClangTidyProvider = combine(std::move(Providers));
927 EXPECT_THAT(TU.build().getDiagnostics(), UnorderedElementsAre()); // no-crash
928}
929
930TEST(DiagnosticTest, ElseAfterReturnRange) {
931 Annotations Main(R"cpp(
932 int foo(int cond) {
933 if (cond == 1) {
934 return 42;
935 } [[else]] if (cond == 2) {
936 return 43;
937 }
938 return 44;
939 }
940 )cpp");
941 TestTU TU = TestTU::withCode(Main.code());
942 TU.ClangTidyProvider = addTidyChecks("llvm-else-after-return");
943 EXPECT_THAT(TU.build().getDiagnostics(),
944 ifTidyChecks(ElementsAre(
945 Diag(Main.range(), "do not use 'else' after 'return'"))));
946}
947
948TEST(DiagnosticTest, ClangTidySelfContainedDiags) {
949 Annotations Main(R"cpp($MathHeader[[]]
950 struct Foo{
951 int A, B;
952 Foo()$Fix[[]] {
953 $A[[A = 1;]]
954 $B[[B = 1;]]
955 }
956 };
957 void InitVariables() {
958 float $C[[C]]$CFix[[]];
959 double $D[[D]]$DFix[[]];
960 }
961 )cpp");
962 TestTU TU = TestTU::withCode(Main.code());
963 TU.ClangTidyProvider =
964 addTidyChecks("cppcoreguidelines-prefer-member-initializer,"
965 "cppcoreguidelines-init-variables");
966 clangd::Fix ExpectedAFix;
967 ExpectedAFix.Message =
968 "'A' should be initialized in a member initializer of the constructor";
969 ExpectedAFix.Edits.push_back(TextEdit{Main.range("Fix"), " : A(1)"});
970 ExpectedAFix.Edits.push_back(TextEdit{Main.range("A"), ""});
971
972 // When invoking clang-tidy normally, this code would produce `, B(1)` as the
973 // fix the `B` member, as it would think its already included the ` : ` from
974 // the previous `A` fix.
975 clangd::Fix ExpectedBFix;
976 ExpectedBFix.Message =
977 "'B' should be initialized in a member initializer of the constructor";
978 ExpectedBFix.Edits.push_back(TextEdit{Main.range("Fix"), " : B(1)"});
979 ExpectedBFix.Edits.push_back(TextEdit{Main.range("B"), ""});
980
981 clangd::Fix ExpectedCFix;
982 ExpectedCFix.Message = "variable 'C' is not initialized";
983 ExpectedCFix.Edits.push_back(
984 TextEdit{Main.range("MathHeader"), "#include <math.h>\n\n"});
985 ExpectedCFix.Edits.push_back(TextEdit{Main.range("CFix"), " = NAN"});
986
987 // Again in clang-tidy only the include directive would be emitted for the
988 // first warning. However we need the include attaching for both warnings.
989 clangd::Fix ExpectedDFix;
990 ExpectedDFix.Message = "variable 'D' is not initialized";
991 ExpectedDFix.Edits.push_back(
992 TextEdit{Main.range("MathHeader"), "#include <math.h>\n\n"});
993 ExpectedDFix.Edits.push_back(TextEdit{Main.range("DFix"), " = NAN"});
994 EXPECT_THAT(
995 TU.build().getDiagnostics(),
996 ifTidyChecks(UnorderedElementsAre(
997 AllOf(Diag(Main.range("A"), "'A' should be initialized in a member "
998 "initializer of the constructor"),
999 withFix(equalToFix(ExpectedAFix))),
1000 AllOf(Diag(Main.range("B"), "'B' should be initialized in a member "
1001 "initializer of the constructor"),
1002 withFix(equalToFix(ExpectedBFix))),
1003 AllOf(Diag(Main.range("C"), "variable 'C' is not initialized"),
1004 withFix(equalToFix(ExpectedCFix))),
1005 AllOf(Diag(Main.range("D"), "variable 'D' is not initialized"),
1006 withFix(equalToFix(ExpectedDFix))))));
1007}
1008
1009TEST(DiagnosticTest, ClangTidySelfContainedDiagsFormatting) {
1010 Annotations Main(R"cpp(
1011 class Interface {
1012 public:
1013 virtual void Reset1() = 0;
1014 virtual void Reset2() = 0;
1015 };
1016 class A : public Interface {
1017 // This will be marked by clangd to use override instead of virtual
1018 $virtual1[[virtual ]]void $Reset1[[Reset1]]()$override1[[]];
1019 $virtual2[[virtual ]]/**/void $Reset2[[Reset2]]()$override2[[]];
1020 };
1021 )cpp");
1022 TestTU TU = TestTU::withCode(Main.code());
1023 TU.ClangTidyProvider =
1024 addTidyChecks("cppcoreguidelines-explicit-virtual-functions,");
1025 clangd::Fix const ExpectedFix1{
1026 "prefer using 'override' or (rarely) 'final' "
1027 "instead of 'virtual'",
1028 {TextEdit{Main.range("virtual1"), ""},
1029 TextEdit{Main.range("override1"), " override"}},
1030 {}};
1031 clangd::Fix const ExpectedFix2{
1032 "prefer using 'override' or (rarely) 'final' "
1033 "instead of 'virtual'",
1034 {TextEdit{Main.range("virtual2"), ""},
1035 TextEdit{Main.range("override2"), " override"}},
1036 {}};
1037 // Note that in the Fix we expect the "virtual" keyword and the following
1038 // whitespace to be deleted
1039 EXPECT_THAT(TU.build().getDiagnostics(),
1040 ifTidyChecks(UnorderedElementsAre(
1041 AllOf(Diag(Main.range("Reset1"),
1042 "prefer using 'override' or (rarely) 'final' "
1043 "instead of 'virtual'"),
1044 withFix(equalToFix(ExpectedFix1))),
1045 AllOf(Diag(Main.range("Reset2"),
1046 "prefer using 'override' or (rarely) 'final' "
1047 "instead of 'virtual'"),
1048 withFix(equalToFix(ExpectedFix2))))));
1049}
1050
1051TEST(DiagnosticsTest, ClangTidyCallingIntoPreprocessor) {
1052 std::string Main = R"cpp(
1053 extern "C" {
1054 #include "b.h"
1055 }
1056 )cpp";
1057 std::string Header = R"cpp(
1058 #define EXTERN extern
1059 EXTERN int waldo();
1060 )cpp";
1061 auto TU = TestTU::withCode(Main);
1062 TU.AdditionalFiles["b.h"] = Header;
1063 TU.ClangTidyProvider = addTidyChecks("modernize-use-trailing-return-type");
1064 // Check that no assertion failures occur during the build
1065 TU.build();
1066}
1067
1068TEST(DiagnosticsTest, Preprocessor) {
1069 // This looks like a preamble, but there's an #else in the middle!
1070 // Check that:
1071 // - the #else doesn't generate diagnostics (we had this bug)
1072 // - we get diagnostics from the taken branch
1073 // - we get no diagnostics from the not taken branch
1074 Annotations Test(R"cpp(
1075 #ifndef FOO
1076 #define FOO
1077 int a = [[b]]; // error-ok
1078 #else
1079 int x = y;
1080 #endif
1081 )cpp");
1082 EXPECT_THAT(
1083 TestTU::withCode(Test.code()).build().getDiagnostics(),
1084 ElementsAre(Diag(Test.range(), "use of undeclared identifier 'b'")));
1085}
1086
1087TEST(DiagnosticsTest, IgnoreVerify) {
1088 auto TU = TestTU::withCode(R"cpp(
1089 int a; // expected-error {{}}
1090 )cpp");
1091 TU.ExtraArgs.push_back("-Xclang");
1092 TU.ExtraArgs.push_back("-verify");
1093 EXPECT_THAT(TU.build().getDiagnostics(), IsEmpty());
1094}
1095
1096TEST(DiagnosticTest, IgnoreBEFilelistOptions) {
1097 auto TU = TestTU::withCode("");
1098 TU.ExtraArgs.push_back("-Xclang");
1099 for (const auto *DisableOption :
1100 {"-fsanitize-ignorelist=null", "-fprofile-list=null",
1101 "-fxray-always-instrument=null", "-fxray-never-instrument=null",
1102 "-fxray-attr-list=null"}) {
1103 TU.ExtraArgs.push_back(DisableOption);
1104 EXPECT_THAT(TU.build().getDiagnostics(), IsEmpty());
1105 TU.ExtraArgs.pop_back();
1106 }
1107}
1108
1109// Recursive main-file include is diagnosed, and doesn't crash.
1110TEST(DiagnosticsTest, RecursivePreamble) {
1111 auto TU = TestTU::withCode(R"cpp(
1112 #include "foo.h" // error-ok
1113 int symbol;
1114 )cpp");
1115 TU.Filename = "foo.h";
1116 EXPECT_THAT(TU.build().getDiagnostics(),
1117 ElementsAre(diagName("pp_including_mainfile_in_preamble")));
1118 EXPECT_THAT(TU.build().getLocalTopLevelDecls(), SizeIs(1));
1119}
1120
1121// Recursive main-file include with #pragma once guard is OK.
1122TEST(DiagnosticsTest, RecursivePreamblePragmaOnce) {
1123 auto TU = TestTU::withCode(R"cpp(
1124 #pragma once
1125 #include "foo.h"
1126 int symbol;
1127 )cpp");
1128 TU.Filename = "foo.h";
1129 EXPECT_THAT(TU.build().getDiagnostics(),
1130 Not(Contains(diagName("pp_including_mainfile_in_preamble"))));
1131 EXPECT_THAT(TU.build().getLocalTopLevelDecls(), SizeIs(1));
1132}
1133
1134// Recursive main-file include with #ifndef guard should be OK.
1135// However, it's not yet recognized (incomplete at end of preamble).
1136TEST(DiagnosticsTest, RecursivePreambleIfndefGuard) {
1137 auto TU = TestTU::withCode(R"cpp(
1138 #ifndef FOO
1139 #define FOO
1140 #include "foo.h" // error-ok
1141 int symbol;
1142 #endif
1143 )cpp");
1144 TU.Filename = "foo.h";
1145 // FIXME: should be no errors here.
1146 EXPECT_THAT(TU.build().getDiagnostics(),
1147 ElementsAre(diagName("pp_including_mainfile_in_preamble")));
1148 EXPECT_THAT(TU.build().getLocalTopLevelDecls(), SizeIs(1));
1149}
1150
1151TEST(DiagnosticsTest, PreambleWithPragmaAssumeNonnull) {
1152 auto TU = TestTU::withCode(R"cpp(
1153#pragma clang assume_nonnull begin
1154void foo(int *x);
1155#pragma clang assume_nonnull end
1156)cpp");
1157 auto AST = TU.build();
1158 EXPECT_THAT(AST.getDiagnostics(), IsEmpty());
1159 const auto *X = cast<FunctionDecl>(findDecl(AST, "foo")).getParamDecl(0);
1160 ASSERT_TRUE(X->getOriginalType()->getNullability() ==
1161 NullabilityKind::NonNull);
1162}
1163
1164TEST(DiagnosticsTest, PreamblePragmaDiagnosticPushPop) {
1165 auto TU = TestTU::withCode(R"cpp(
1166#pragma clang diagnostic push
1167int main() {
1168 return 0;
1169}
1170#pragma clang diagnostic pop
1171)cpp");
1172 auto AST = TU.build();
1173 EXPECT_THAT(AST.getDiagnostics(), IsEmpty());
1174}
1175
1176TEST(DiagnosticsTest, PreambleHeaderWithBadPragmaAssumeNonnull) {
1177 Annotations Header(R"cpp(
1178#pragma clang assume_nonnull begin // error-ok
1179void foo(int *X);
1180)cpp");
1181 auto TU = TestTU::withCode(R"cpp(
1182#include "foo.h" // unterminated assume_nonnull should not affect bar.
1183void bar(int *Y);
1184)cpp");
1185 TU.AdditionalFiles = {{"foo.h", std::string(Header.code())}};
1186 auto AST = TU.build();
1187 EXPECT_THAT(AST.getDiagnostics(),
1188 ElementsAre(diagName("pp_eof_in_assume_nonnull")));
1189 const auto *X = cast<FunctionDecl>(findDecl(AST, "foo")).getParamDecl(0);
1190 ASSERT_TRUE(X->getOriginalType()->getNullability() ==
1191 NullabilityKind::NonNull);
1192 const auto *Y = cast<FunctionDecl>(findDecl(AST, "bar")).getParamDecl(0);
1193 ASSERT_FALSE(Y->getOriginalType()->getNullability());
1194}
1195
1196TEST(DiagnosticsTest, InsideMacros) {
1197 Annotations Test(R"cpp(
1198 #define TEN 10
1199 #define RET(x) return x + 10
1200
1201 int* foo() {
1202 RET($foo[[0]]); // error-ok
1203 }
1204 int* bar() {
1205 return $bar[[TEN]];
1206 }
1207 )cpp");
1208 EXPECT_THAT(TestTU::withCode(Test.code()).build().getDiagnostics(),
1209 ElementsAre(Diag(Test.range("foo"),
1210 "cannot initialize return object of type "
1211 "'int *' with an rvalue of type 'int'"),
1212 Diag(Test.range("bar"),
1213 "cannot initialize return object of type "
1214 "'int *' with an rvalue of type 'int'")));
1215}
1216
1217TEST(DiagnosticsTest, NoFixItInMacro) {
1218 Annotations Test(R"cpp(
1219 #define Define(name) void name() {}
1220
1221 [[Define]](main) // error-ok
1222 )cpp");
1223 auto TU = TestTU::withCode(Test.code());
1224 EXPECT_THAT(TU.build().getDiagnostics(),
1225 ElementsAre(AllOf(Diag(Test.range(), "'main' must return 'int'"),
1226 Not(withFix(_)))));
1227}
1228
1229TEST(DiagnosticsTest, PragmaSystemHeader) {
1230 Annotations Test("#pragma clang [[system_header]]\n");
1231 auto TU = TestTU::withCode(Test.code());
1232 EXPECT_THAT(
1233 TU.build().getDiagnostics(),
1234 ElementsAre(AllOf(
1235 Diag(Test.range(), "#pragma system_header ignored in main file"))));
1236 TU.Filename = "TestTU.h";
1237 EXPECT_THAT(TU.build().getDiagnostics(), IsEmpty());
1238}
1239
1240TEST(ClangdTest, MSAsm) {
1241 // Parsing MS assembly tries to use the target MCAsmInfo, which we don't link.
1242 // We used to crash here. Now clang emits a diagnostic, which we filter out.
1243 llvm::InitializeAllTargetInfos(); // As in ClangdMain
1244 auto TU = TestTU::withCode("void fn() { __asm { cmp cl,64 } }");
1245 TU.ExtraArgs = {"-fms-extensions"};
1246 EXPECT_THAT(TU.build().getDiagnostics(), IsEmpty());
1247}
1248
1249TEST(DiagnosticsTest, ToLSP) {
1250 URIForFile MainFile =
1251 URIForFile::canonicalize(testPath("foo/bar/main.cpp"), "");
1253 URIForFile::canonicalize(testPath("foo/bar/header.h"), "");
1254
1255 clangd::Diag D;
1256 D.ID = clang::diag::err_undeclared_var_use;
1258 D.Name = "undeclared_var_use";
1259 D.Source = clangd::Diag::Clang;
1260 D.Message = "something terrible happened";
1261 D.Range = {pos(1, 2), pos(3, 4)};
1262 D.InsideMainFile = true;
1263 D.Severity = DiagnosticsEngine::Error;
1264 D.File = "foo/bar/main.cpp";
1265 D.AbsFile = std::string(MainFile.file());
1266 D.OpaqueData["test"] = "bar";
1267
1268 clangd::Note NoteInMain;
1269 NoteInMain.Message = "declared somewhere in the main file";
1270 NoteInMain.Range = {pos(5, 6), pos(7, 8)};
1271 NoteInMain.Severity = DiagnosticsEngine::Remark;
1272 NoteInMain.File = "../foo/bar/main.cpp";
1273 NoteInMain.InsideMainFile = true;
1274 NoteInMain.AbsFile = std::string(MainFile.file());
1275
1276 D.Notes.push_back(NoteInMain);
1277
1278 clangd::Note NoteInHeader;
1279 NoteInHeader.Message = "declared somewhere in the header file";
1280 NoteInHeader.Range = {pos(9, 10), pos(11, 12)};
1281 NoteInHeader.Severity = DiagnosticsEngine::Note;
1282 NoteInHeader.File = "../foo/baz/header.h";
1283 NoteInHeader.InsideMainFile = false;
1284 NoteInHeader.AbsFile = std::string(HeaderFile.file());
1285 D.Notes.push_back(NoteInHeader);
1286
1287 clangd::Fix F;
1288 F.Message = "do something";
1289 D.Fixes.push_back(F);
1290
1291 // Diagnostics should turn into these:
1292 clangd::Diagnostic MainLSP;
1293 MainLSP.range = D.Range;
1294 MainLSP.severity = getSeverity(DiagnosticsEngine::Error);
1295 MainLSP.code = "undeclared_var_use";
1296 MainLSP.source = "clang";
1297 MainLSP.message =
1298 R"(Something terrible happened (fix available)
1299
1300main.cpp:6:7: remark: declared somewhere in the main file
1301
1302../foo/baz/header.h:10:11:
1303note: declared somewhere in the header file)";
1304 MainLSP.tags = {DiagnosticTag::Unnecessary};
1305 MainLSP.data = D.OpaqueData;
1306
1307 clangd::Diagnostic NoteInMainLSP;
1308 NoteInMainLSP.range = NoteInMain.Range;
1309 NoteInMainLSP.severity = getSeverity(DiagnosticsEngine::Remark);
1310 NoteInMainLSP.message = R"(Declared somewhere in the main file
1311
1312main.cpp:2:3: error: something terrible happened)";
1313
1315 // Transform diagnostics and check the results.
1316 std::vector<std::pair<clangd::Diagnostic, std::vector<clangd::Fix>>> LSPDiags;
1317 toLSPDiags(D, MainFile, Opts,
1318 [&](clangd::Diagnostic LSPDiag, ArrayRef<clangd::Fix> Fixes) {
1319 LSPDiags.push_back(
1320 {std::move(LSPDiag),
1321 std::vector<clangd::Fix>(Fixes.begin(), Fixes.end())});
1322 });
1323
1324 EXPECT_THAT(
1325 LSPDiags,
1326 ElementsAre(Pair(equalToLSPDiag(MainLSP), ElementsAre(equalToFix(F))),
1327 Pair(equalToLSPDiag(NoteInMainLSP), IsEmpty())));
1328 EXPECT_EQ(LSPDiags[0].first.code, "undeclared_var_use");
1329 EXPECT_EQ(LSPDiags[0].first.source, "clang");
1330 EXPECT_EQ(LSPDiags[1].first.code, "");
1331 EXPECT_EQ(LSPDiags[1].first.source, "");
1332
1333 // Same thing, but don't flatten notes into the main list.
1334 LSPDiags.clear();
1335 Opts.EmitRelatedLocations = true;
1336 toLSPDiags(D, MainFile, Opts,
1337 [&](clangd::Diagnostic LSPDiag, ArrayRef<clangd::Fix> Fixes) {
1338 LSPDiags.push_back(
1339 {std::move(LSPDiag),
1340 std::vector<clangd::Fix>(Fixes.begin(), Fixes.end())});
1341 });
1342 MainLSP.message = "Something terrible happened (fix available)";
1343 DiagnosticRelatedInformation NoteInMainDRI;
1344 NoteInMainDRI.message = "Declared somewhere in the main file";
1345 NoteInMainDRI.location.range = NoteInMain.Range;
1346 NoteInMainDRI.location.uri = MainFile;
1347 MainLSP.relatedInformation = {NoteInMainDRI};
1348 DiagnosticRelatedInformation NoteInHeaderDRI;
1349 NoteInHeaderDRI.message = "Declared somewhere in the header file";
1350 NoteInHeaderDRI.location.range = NoteInHeader.Range;
1351 NoteInHeaderDRI.location.uri = HeaderFile;
1352 MainLSP.relatedInformation = {NoteInMainDRI, NoteInHeaderDRI};
1353 EXPECT_THAT(LSPDiags, ElementsAre(Pair(equalToLSPDiag(MainLSP),
1354 ElementsAre(equalToFix(F)))));
1355}
1356
1357struct SymbolWithHeader {
1358 std::string QName;
1359 std::string DeclaringFile;
1360 std::string IncludeHeader;
1361};
1362
1363std::unique_ptr<SymbolIndex>
1364buildIndexWithSymbol(llvm::ArrayRef<SymbolWithHeader> Syms) {
1366 for (const auto &S : Syms) {
1367 Symbol Sym = cls(S.QName);
1369 Sym.CanonicalDeclaration.FileURI = S.DeclaringFile.c_str();
1370 Sym.Definition.FileURI = S.DeclaringFile.c_str();
1371 Sym.IncludeHeaders.emplace_back(S.IncludeHeader, 1, Symbol::Include);
1372 Slab.insert(Sym);
1373 }
1374 return MemIndex::build(std::move(Slab).build(), RefSlab(), RelationSlab());
1375}
1376
1377TEST(IncludeFixerTest, IncompleteType) {
1378 auto TU = TestTU::withHeaderCode("namespace ns { class X; } ns::X *x;");
1379 TU.ExtraArgs.push_back("-std=c++20");
1380 auto Index = buildIndexWithSymbol(
1381 {SymbolWithHeader{"ns::X", "unittest:///x.h", "\"x.h\""}});
1382 TU.ExternalIndex = Index.get();
1383
1384 std::vector<std::pair<llvm::StringRef, llvm::StringRef>> Tests{
1385 {"incomplete_nested_name_spec", "[[ns::X::]]Nested n;"},
1386 {"incomplete_base_class", "class Y : [[ns::X]] {};"},
1387 {"incomplete_member_access", "auto i = x[[->]]f();"},
1388 {"incomplete_type", "auto& [[[]]m] = *x;"},
1389 {"init_incomplete_type",
1390 "struct C { static int f(ns::X&); }; int i = C::f([[{]]});"},
1391 {"bad_cast_incomplete", "auto a = [[static_cast]]<ns::X>(0);"},
1392 {"template_nontype_parm_incomplete", "template <ns::X [[foo]]> int a;"},
1393 {"typecheck_decl_incomplete_type", "ns::X [[var]];"},
1394 {"typecheck_incomplete_tag", "auto i = [[(*x)]]->f();"},
1395 {"typecheck_nonviable_condition_incomplete",
1396 "struct A { operator ns::X(); } a; const ns::X &[[b]] = a;"},
1397 {"invalid_incomplete_type_use", "auto var = [[ns::X()]];"},
1398 {"sizeof_alignof_incomplete_or_sizeless_type",
1399 "auto s = [[sizeof]](ns::X);"},
1400 {"for_range_incomplete_type", "void foo() { for (auto i : [[*]]x ) {} }"},
1401 {"func_def_incomplete_result", "ns::X [[func]] () {}"},
1402 {"field_incomplete_or_sizeless", "class M { ns::X [[member]]; };"},
1403 {"array_incomplete_or_sizeless_type", "auto s = [[(ns::X[]){}]];"},
1404 {"call_incomplete_return", "ns::X f(); auto fp = &f; auto z = [[fp()]];"},
1405 {"call_function_incomplete_return", "ns::X foo(); auto a = [[foo()]];"},
1406 {"call_incomplete_argument", "int m(ns::X); int i = m([[*x]]);"},
1407 {"switch_incomplete_class_type", "void a() { [[switch]](*x) {} }"},
1408 {"delete_incomplete_class_type", "void f() { [[delete]] *x; }"},
1409 {"-Wdelete-incomplete", "void f() { [[delete]] x; }"},
1410 {"dereference_incomplete_type",
1411 R"cpp(void f() { asm("" : "=r"([[*]]x)::); })cpp"},
1412 };
1413 for (auto Case : Tests) {
1414 Annotations Main(Case.second);
1415 TU.Code = Main.code().str() + "\n // error-ok";
1416 EXPECT_THAT(
1417 TU.build().getDiagnostics(),
1418 ElementsAre(AllOf(diagName(Case.first), hasRange(Main.range()),
1419 withFix(Fix(Range{}, "#include \"x.h\"\n",
1420 "Include \"x.h\" for symbol ns::X")))))
1421 << Case.second;
1422 }
1423}
1424
1425TEST(IncludeFixerTest, IncompleteEnum) {
1426 Symbol Sym = enm("X");
1428 Sym.CanonicalDeclaration.FileURI = Sym.Definition.FileURI = "unittest:///x.h";
1429 Sym.IncludeHeaders.emplace_back("\"x.h\"", 1, Symbol::Include);
1431 Slab.insert(Sym);
1432 auto Index =
1433 MemIndex::build(std::move(Slab).build(), RefSlab(), RelationSlab());
1434
1435 TestTU TU;
1436 TU.ExternalIndex = Index.get();
1437 TU.ExtraArgs.push_back("-std=c++20");
1438 TU.ExtraArgs.push_back("-fno-ms-compatibility"); // else incomplete enum is OK
1439
1440 std::vector<std::pair<llvm::StringRef, llvm::StringRef>> Tests{
1441 {"incomplete_enum", "enum class X : int; using enum [[X]];"},
1442 {"underlying_type_of_incomplete_enum",
1443 "[[__underlying_type]](enum X) i;"},
1444 };
1445 for (auto Case : Tests) {
1446 Annotations Main(Case.second);
1447 TU.Code = Main.code().str() + "\n // error-ok";
1448 EXPECT_THAT(TU.build().getDiagnostics(),
1449 Contains(AllOf(diagName(Case.first), hasRange(Main.range()),
1450 withFix(Fix(Range{}, "#include \"x.h\"\n",
1451 "Include \"x.h\" for symbol X")))))
1452 << Case.second;
1453 }
1454}
1455
1456TEST(IncludeFixerTest, NoSuggestIncludeWhenNoDefinitionInHeader) {
1457 Annotations Test(R"cpp(// error-ok
1458$insert[[]]namespace ns {
1459 class X;
1460}
1461class Y : $base[[public ns::X]] {};
1462int main() {
1463 ns::X *x;
1464 x$access[[->]]f();
1465}
1466 )cpp");
1467 auto TU = TestTU::withCode(Test.code());
1468 Symbol Sym = cls("ns::X");
1470 Sym.CanonicalDeclaration.FileURI = "unittest:///x.h";
1471 Sym.Definition.FileURI = "unittest:///x.cc";
1472 Sym.IncludeHeaders.emplace_back("\"x.h\"", 1, Symbol::Include);
1473
1475 Slab.insert(Sym);
1476 auto Index =
1477 MemIndex::build(std::move(Slab).build(), RefSlab(), RelationSlab());
1478 TU.ExternalIndex = Index.get();
1479
1480 EXPECT_THAT(TU.build().getDiagnostics(),
1481 UnorderedElementsAre(
1482 Diag(Test.range("base"), "base class has incomplete type"),
1483 Diag(Test.range("access"),
1484 "member access into incomplete type 'ns::X'")));
1485}
1486
1487TEST(IncludeFixerTest, Typo) {
1488 Annotations Test(R"cpp(// error-ok
1489$insert[[]]namespace ns {
1490void foo() {
1491 $unqualified1[[X]] x;
1492 // No fix if the unresolved type is used as specifier. (ns::)X::Nested will be
1493 // considered the unresolved type.
1494 $unqualified2[[X]]::Nested n;
1495}
1496struct S : $base[[X]] {};
1497}
1498void bar() {
1499 ns::$qualified1[[X]] x; // ns:: is valid.
1500 ns::$qualified2[[X]](); // Error: no member in namespace
1501
1502 ::$global[[Global]] glob;
1503}
1504using Type = ns::$template[[Foo]]<int>;
1505 )cpp");
1506 auto TU = TestTU::withCode(Test.code());
1507 auto Index = buildIndexWithSymbol(
1508 {SymbolWithHeader{"ns::X", "unittest:///x.h", "\"x.h\""},
1509 SymbolWithHeader{"Global", "unittest:///global.h", "\"global.h\""},
1510 SymbolWithHeader{"ns::Foo", "unittest:///foo.h", "\"foo.h\""}});
1511 TU.ExternalIndex = Index.get();
1512
1513 EXPECT_THAT(
1514 TU.build().getDiagnostics(),
1515 UnorderedElementsAre(
1516 AllOf(Diag(Test.range("unqualified1"), "unknown type name 'X'"),
1517 diagName("unknown_typename"),
1518 withFix(Fix(Test.range("insert"), "#include \"x.h\"\n",
1519 "Include \"x.h\" for symbol ns::X"))),
1520 Diag(Test.range("unqualified2"), "use of undeclared identifier 'X'"),
1521 AllOf(Diag(Test.range("qualified1"),
1522 "no type named 'X' in namespace 'ns'"),
1523 diagName("typename_nested_not_found"),
1524 withFix(Fix(Test.range("insert"), "#include \"x.h\"\n",
1525 "Include \"x.h\" for symbol ns::X"))),
1526 AllOf(Diag(Test.range("qualified2"),
1527 "no member named 'X' in namespace 'ns'"),
1528 diagName("no_member"),
1529 withFix(Fix(Test.range("insert"), "#include \"x.h\"\n",
1530 "Include \"x.h\" for symbol ns::X"))),
1531 AllOf(Diag(Test.range("global"),
1532 "no type named 'Global' in the global namespace"),
1533 diagName("typename_nested_not_found"),
1534 withFix(Fix(Test.range("insert"), "#include \"global.h\"\n",
1535 "Include \"global.h\" for symbol Global"))),
1536 AllOf(Diag(Test.range("template"),
1537 "no template named 'Foo' in namespace 'ns'"),
1538 diagName("no_member_template"),
1539 withFix(Fix(Test.range("insert"), "#include \"foo.h\"\n",
1540 "Include \"foo.h\" for symbol ns::Foo"))),
1541 AllOf(Diag(Test.range("base"), "expected class name"),
1542 diagName("expected_class_name"),
1543 withFix(Fix(Test.range("insert"), "#include \"x.h\"\n",
1544 "Include \"x.h\" for symbol ns::X")))));
1545}
1546
1547TEST(IncludeFixerTest, TypoInMacro) {
1548 auto TU = TestTU::withCode(R"cpp(// error-ok
1549#define ID(T) T
1550X a1;
1551ID(X a2);
1552ns::X a3;
1553ID(ns::X a4);
1554namespace ns{};
1555ns::X a5;
1556ID(ns::X a6);
1557)cpp");
1558 auto Index = buildIndexWithSymbol(
1559 {SymbolWithHeader{"X", "unittest:///x.h", "\"x.h\""},
1560 SymbolWithHeader{"ns::X", "unittest:///ns.h", "\"x.h\""}});
1561 TU.ExternalIndex = Index.get();
1562 // FIXME: -fms-compatibility (which is default on windows) breaks the
1563 // ns::X cases when the namespace is undeclared. Find out why!
1564 TU.ExtraArgs = {"-fno-ms-compatibility"};
1565 EXPECT_THAT(TU.build().getDiagnostics(), Each(withFix(_)));
1566}
1567
1568TEST(IncludeFixerTest, MultipleMatchedSymbols) {
1569 Annotations Test(R"cpp(// error-ok
1570$insert[[]]namespace na {
1571namespace nb {
1572void foo() {
1573 $unqualified[[X]] x;
1574}
1575}
1576}
1577 )cpp");
1578 auto TU = TestTU::withCode(Test.code());
1579 auto Index = buildIndexWithSymbol(
1580 {SymbolWithHeader{"na::X", "unittest:///a.h", "\"a.h\""},
1581 SymbolWithHeader{"na::nb::X", "unittest:///b.h", "\"b.h\""}});
1582 TU.ExternalIndex = Index.get();
1583
1584 EXPECT_THAT(TU.build().getDiagnostics(),
1585 UnorderedElementsAre(AllOf(
1586 Diag(Test.range("unqualified"), "unknown type name 'X'"),
1587 diagName("unknown_typename"),
1588 withFix(Fix(Test.range("insert"), "#include \"a.h\"\n",
1589 "Include \"a.h\" for symbol na::X"),
1590 Fix(Test.range("insert"), "#include \"b.h\"\n",
1591 "Include \"b.h\" for symbol na::nb::X")))));
1592}
1593
1594TEST(IncludeFixerTest, NoCrashMemberAccess) {
1595 Annotations Test(R"cpp(// error-ok
1596 struct X { int xyz; };
1597 void g() { X x; x.$[[xy]]; }
1598 )cpp");
1599 auto TU = TestTU::withCode(Test.code());
1600 auto Index = buildIndexWithSymbol(
1601 SymbolWithHeader{"na::X", "unittest:///a.h", "\"a.h\""});
1602 TU.ExternalIndex = Index.get();
1603
1604 EXPECT_THAT(
1605 TU.build().getDiagnostics(),
1606 UnorderedElementsAre(Diag(Test.range(), "no member named 'xy' in 'X'")));
1607}
1608
1609TEST(IncludeFixerTest, NoCrashOnQualifiedConversionFunctionName) {
1610 auto TU = TestTU::withCode(R"cpp(// error-ok
1611namespace std {}
1612void f() { operator new[](0, operator::align_val_t{}); }
1613 )cpp");
1614 TU.ExtraArgs.push_back("-std=c++17");
1615 auto Index = buildIndexWithSymbol(
1616 SymbolWithHeader{"std::align_val_t", "unittest:///new.h", "<new>"});
1617 TU.ExternalIndex = Index.get();
1618
1619 EXPECT_THAT(TU.build().getDiagnostics(), Not(IsEmpty()));
1620}
1621
1622TEST(IncludeFixerTest, FixConversionFunctionTargetType) {
1623 Annotations Test(R"cpp(// error-ok
1624$insert[[]]struct Wrapper {
1625 template <typename T>
1626 operator T() const { return T(); }
1627};
1628
1629void f() {
1630 Wrapper W;
1631 auto V = W.operator::$target[[Something]]();
1632}
1633 )cpp");
1634 auto TU = TestTU::withCode(Test.code());
1635 auto Index = buildIndexWithSymbol(
1636 SymbolWithHeader{"Something", "unittest:///test.h", "\"test.h\""});
1637 TU.ExternalIndex = Index.get();
1638
1639 EXPECT_THAT(TU.build().getDiagnostics(),
1640 Contains(Field(
1641 &Diag::Fixes,
1642 Contains(Fix(Test.range("insert"), "#include \"test.h\"\n",
1643 "Include \"test.h\" for symbol Something")))));
1644}
1645
1646TEST(IncludeFixerTest, UseCachedIndexResults) {
1647 // As index results for the identical request are cached, more than 5 fixes
1648 // are generated.
1649 Annotations Test(R"cpp(// error-ok
1650$insert[[]]void foo() {
1651 $x1[[X]] x;
1652 $x2[[X]] x;
1653 $x3[[X]] x;
1654 $x4[[X]] x;
1655 $x5[[X]] x;
1656 $x6[[X]] x;
1657 $x7[[X]] x;
1658}
1659
1660class X;
1661void bar(X *x) {
1662 x$a1[[->]]f();
1663 x$a2[[->]]f();
1664 x$a3[[->]]f();
1665 x$a4[[->]]f();
1666 x$a5[[->]]f();
1667 x$a6[[->]]f();
1668 x$a7[[->]]f();
1669}
1670 )cpp");
1671 auto TU = TestTU::withCode(Test.code());
1672 auto Index =
1673 buildIndexWithSymbol(SymbolWithHeader{"X", "unittest:///a.h", "\"a.h\""});
1674 TU.ExternalIndex = Index.get();
1675
1676 auto Parsed = TU.build();
1677 for (const auto &D : Parsed.getDiagnostics()) {
1678 if (D.Fixes.size() != 1) {
1679 ADD_FAILURE() << "D.Fixes.size() != 1";
1680 continue;
1681 }
1682 EXPECT_EQ(D.Fixes[0].Message, std::string("Include \"a.h\" for symbol X"));
1683 }
1684}
1685
1686TEST(IncludeFixerTest, UnresolvedNameAsSpecifier) {
1687 Annotations Test(R"cpp(// error-ok
1688$insert[[]]namespace ns {
1689}
1690void g() { ns::$[[scope]]::X_Y(); }
1691 )cpp");
1692 TestTU TU;
1693 TU.Code = std::string(Test.code());
1694 // FIXME: Figure out why this is needed and remove it, PR43662.
1695 TU.ExtraArgs.push_back("-fno-ms-compatibility");
1696 auto Index = buildIndexWithSymbol(
1697 SymbolWithHeader{"ns::scope::X_Y", "unittest:///x.h", "\"x.h\""});
1698 TU.ExternalIndex = Index.get();
1699
1700 EXPECT_THAT(
1701 TU.build().getDiagnostics(),
1702 UnorderedElementsAre(
1703 AllOf(Diag(Test.range(), "no member named 'scope' in namespace 'ns'"),
1704 diagName("no_member"),
1705 withFix(Fix(Test.range("insert"), "#include \"x.h\"\n",
1706 "Include \"x.h\" for symbol ns::scope::X_Y")))));
1707}
1708
1709TEST(IncludeFixerTest, UnresolvedSpecifierWithSemaCorrection) {
1710 Annotations Test(R"cpp(// error-ok
1711$insert[[]]namespace clang {
1712void f() {
1713 // "clangd::" will be corrected to "clang::" by Sema.
1714 $q1[[clangd]]::$x[[X]] x;
1715 $q2[[clangd]]::$ns[[ns]]::Y y;
1716}
1717}
1718 )cpp");
1719 TestTU TU;
1720 TU.Code = std::string(Test.code());
1721 // FIXME: Figure out why this is needed and remove it, PR43662.
1722 TU.ExtraArgs.push_back("-fno-ms-compatibility");
1723 auto Index = buildIndexWithSymbol(
1724 {SymbolWithHeader{"clang::clangd::X", "unittest:///x.h", "\"x.h\""},
1725 SymbolWithHeader{"clang::clangd::ns::Y", "unittest:///y.h", "\"y.h\""}});
1726 TU.ExternalIndex = Index.get();
1727
1728 EXPECT_THAT(
1729 TU.build().getDiagnostics(),
1730 UnorderedElementsAre(
1731 AllOf(Diag(Test.range("q1"), "use of undeclared identifier 'clangd'; "
1732 "did you mean 'clang'?"),
1733 diagName("undeclared_var_use_suggest"),
1734 withFix(_, // change clangd to clang
1735 Fix(Test.range("insert"), "#include \"x.h\"\n",
1736 "Include \"x.h\" for symbol clang::clangd::X"))),
1737 AllOf(Diag(Test.range("x"), "no type named 'X' in namespace 'clang'"),
1738 diagName("typename_nested_not_found"),
1739 withFix(Fix(Test.range("insert"), "#include \"x.h\"\n",
1740 "Include \"x.h\" for symbol clang::clangd::X"))),
1741 AllOf(
1742 Diag(Test.range("q2"), "use of undeclared identifier 'clangd'; "
1743 "did you mean 'clang'?"),
1744 diagName("undeclared_var_use_suggest"),
1745 withFix(_, // change clangd to clang
1746 Fix(Test.range("insert"), "#include \"y.h\"\n",
1747 "Include \"y.h\" for symbol clang::clangd::ns::Y"))),
1748 AllOf(Diag(Test.range("ns"),
1749 "no member named 'ns' in namespace 'clang'"),
1750 diagName("no_member"),
1751 withFix(
1752 Fix(Test.range("insert"), "#include \"y.h\"\n",
1753 "Include \"y.h\" for symbol clang::clangd::ns::Y")))));
1754}
1755
1756TEST(IncludeFixerTest, SpecifiedScopeIsNamespaceAlias) {
1757 Annotations Test(R"cpp(// error-ok
1758$insert[[]]namespace a {}
1759namespace b = a;
1760namespace c {
1761 b::$[[X]] x;
1762}
1763 )cpp");
1764 auto TU = TestTU::withCode(Test.code());
1765 auto Index = buildIndexWithSymbol(
1766 SymbolWithHeader{"a::X", "unittest:///x.h", "\"x.h\""});
1767 TU.ExternalIndex = Index.get();
1768
1769 EXPECT_THAT(TU.build().getDiagnostics(),
1770 UnorderedElementsAre(AllOf(
1771 Diag(Test.range(), "no type named 'X' in namespace 'a'"),
1772 diagName("typename_nested_not_found"),
1773 withFix(Fix(Test.range("insert"), "#include \"x.h\"\n",
1774 "Include \"x.h\" for symbol a::X")))));
1775}
1776
1777TEST(IncludeFixerTest, NoCrashOnTemplateInstantiations) {
1778 Annotations Test(R"cpp(
1779 template <typename T> struct Templ {
1780 template <typename U>
1781 typename U::type operator=(const U &);
1782 };
1783
1784 struct A {
1785 Templ<char> s;
1786 A() { [[a]]; /*error-ok*/ } // crash if we compute scopes lazily.
1787 };
1788 )cpp");
1789
1790 auto TU = TestTU::withCode(Test.code());
1791 auto Index = buildIndexWithSymbol({});
1792 TU.ExternalIndex = Index.get();
1793
1794 EXPECT_THAT(
1795 TU.build().getDiagnostics(),
1796 ElementsAre(Diag(Test.range(), "use of undeclared identifier 'a'")));
1797}
1798
1799TEST(IncludeFixerTest, HeaderNamedInDiag) {
1800 Annotations Test(R"cpp(
1801 $insert[[]]int main() {
1802 [[printf]]("");
1803 }
1804 )cpp");
1805 auto TU = TestTU::withCode(Test.code());
1806 TU.ExtraArgs = {"-xc", "-std=c99",
1807 "-Wno-error=implicit-function-declaration"};
1808 auto Index = buildIndexWithSymbol({});
1809 TU.ExternalIndex = Index.get();
1810
1811 EXPECT_THAT(
1812 TU.build().getDiagnostics(),
1813 ElementsAre(AllOf(
1814 Diag(Test.range(), "call to undeclared library function 'printf' "
1815 "with type 'int (const char *, ...)'; ISO C99 "
1816 "and later do not support implicit function "
1817 "declarations"),
1818 withFix(Fix(Test.range("insert"), "#include <stdio.h>\n",
1819 "Include <stdio.h> for symbol printf")))));
1820
1821 TU.ExtraArgs = {"-xc", "-std=c89"};
1822 EXPECT_THAT(
1823 TU.build().getDiagnostics(),
1824 ElementsAre(AllOf(
1825 Diag(Test.range(), "implicitly declaring library function 'printf' "
1826 "with type 'int (const char *, ...)'"),
1827 withFix(Fix(Test.range("insert"), "#include <stdio.h>\n",
1828 "Include <stdio.h> for symbol printf")))));
1829}
1830
1831TEST(IncludeFixerTest, CImplicitFunctionDecl) {
1832 Annotations Test("void x() { [[foo]](); }");
1833 auto TU = TestTU::withCode(Test.code());
1834 TU.Filename = "test.c";
1835 TU.ExtraArgs = {"-std=c99", "-Wno-error=implicit-function-declaration"};
1836
1837 Symbol Sym = func("foo");
1839 Sym.CanonicalDeclaration.FileURI = "unittest:///foo.h";
1840 Sym.IncludeHeaders.emplace_back("\"foo.h\"", 1, Symbol::Include);
1841
1843 Slab.insert(Sym);
1844 auto Index =
1845 MemIndex::build(std::move(Slab).build(), RefSlab(), RelationSlab());
1846 TU.ExternalIndex = Index.get();
1847
1848 EXPECT_THAT(
1849 TU.build().getDiagnostics(),
1850 ElementsAre(AllOf(
1851 Diag(Test.range(),
1852 "call to undeclared function 'foo'; ISO C99 and later do not "
1853 "support implicit function declarations"),
1854 withFix(Fix(Range{}, "#include \"foo.h\"\n",
1855 "Include \"foo.h\" for symbol foo")))));
1856
1857 TU.ExtraArgs = {"-std=c89", "-Wall"};
1858 EXPECT_THAT(TU.build().getDiagnostics(),
1859 ElementsAre(AllOf(
1860 Diag(Test.range(), "implicit declaration of function 'foo'"),
1861 withFix(Fix(Range{}, "#include \"foo.h\"\n",
1862 "Include \"foo.h\" for symbol foo")))));
1863}
1864
1865TEST(DiagsInHeaders, DiagInsideHeader) {
1866 Annotations Main(R"cpp(
1867 #include [["a.h"]]
1868 void foo() {})cpp");
1869 Annotations Header("[[no_type_spec]]; // error-ok");
1870 TestTU TU = TestTU::withCode(Main.code());
1871 TU.AdditionalFiles = {{"a.h", std::string(Header.code())}};
1872 EXPECT_THAT(TU.build().getDiagnostics(),
1873 UnorderedElementsAre(AllOf(
1874 Diag(Main.range(), "in included file: a type specifier is "
1875 "required for all declarations"),
1876 withNote(Diag(Header.range(), "error occurred here")))));
1877}
1878
1879TEST(DiagsInHeaders, DiagInTransitiveInclude) {
1880 Annotations Main(R"cpp(
1881 #include [["a.h"]]
1882 void foo() {})cpp");
1883 TestTU TU = TestTU::withCode(Main.code());
1884 TU.AdditionalFiles = {{"a.h", "#include \"b.h\""},
1885 {"b.h", "no_type_spec; // error-ok"}};
1886 EXPECT_THAT(TU.build().getDiagnostics(),
1887 UnorderedElementsAre(Diag(Main.range(),
1888 "in included file: a type specifier is "
1889 "required for all declarations")));
1890}
1891
1892TEST(DiagsInHeaders, DiagInMultipleHeaders) {
1893 Annotations Main(R"cpp(
1894 #include $a[["a.h"]]
1895 #include $b[["b.h"]]
1896 void foo() {})cpp");
1897 TestTU TU = TestTU::withCode(Main.code());
1898 TU.AdditionalFiles = {{"a.h", "no_type_spec; // error-ok"},
1899 {"b.h", "no_type_spec; // error-ok"}};
1900 EXPECT_THAT(TU.build().getDiagnostics(),
1901 UnorderedElementsAre(
1902 Diag(Main.range("a"), "in included file: a type specifier is "
1903 "required for all declarations"),
1904 Diag(Main.range("b"), "in included file: a type specifier is "
1905 "required for all declarations")));
1906}
1907
1908TEST(DiagsInHeaders, PreferExpansionLocation) {
1909 Annotations Main(R"cpp(
1910 #include [["a.h"]]
1911 #include "b.h"
1912 void foo() {})cpp");
1913 TestTU TU = TestTU::withCode(Main.code());
1914 TU.AdditionalFiles = {
1915 {"a.h", "#include \"b.h\"\n"},
1916 {"b.h", "#ifndef X\n#define X\nno_type_spec; // error-ok\n#endif"}};
1917 EXPECT_THAT(TU.build().getDiagnostics(),
1918 Contains(Diag(Main.range(), "in included file: a type specifier "
1919 "is required for all declarations")));
1920}
1921
1922TEST(DiagsInHeaders, PreferExpansionLocationMacros) {
1923 Annotations Main(R"cpp(
1924 #define X
1925 #include "a.h"
1926 #undef X
1927 #include [["b.h"]]
1928 void foo() {})cpp");
1929 TestTU TU = TestTU::withCode(Main.code());
1930 TU.AdditionalFiles = {
1931 {"a.h", "#include \"c.h\"\n"},
1932 {"b.h", "#include \"c.h\"\n"},
1933 {"c.h", "#ifndef X\n#define X\nno_type_spec; // error-ok\n#endif"}};
1934 EXPECT_THAT(TU.build().getDiagnostics(),
1935 UnorderedElementsAre(Diag(Main.range(),
1936 "in included file: a type specifier is "
1937 "required for all declarations")));
1938}
1939
1940TEST(DiagsInHeaders, LimitDiagsOutsideMainFile) {
1941 Annotations Main(R"cpp(
1942 #include [["a.h"]]
1943 #include "b.h"
1944 void foo() {})cpp");
1945 TestTU TU = TestTU::withCode(Main.code());
1946 TU.AdditionalFiles = {{"a.h", "#include \"c.h\"\n"},
1947 {"b.h", "#include \"c.h\"\n"},
1948 {"c.h", R"cpp(
1949 #ifndef X
1950 #define X
1951 no_type_spec_0; // error-ok
1952 no_type_spec_1;
1953 no_type_spec_2;
1954 no_type_spec_3;
1955 no_type_spec_4;
1956 no_type_spec_5;
1957 no_type_spec_6;
1958 no_type_spec_7;
1959 no_type_spec_8;
1960 no_type_spec_9;
1961 no_type_spec_10;
1962 #endif)cpp"}};
1963 EXPECT_THAT(TU.build().getDiagnostics(),
1964 UnorderedElementsAre(Diag(Main.range(),
1965 "in included file: a type specifier is "
1966 "required for all declarations")));
1967}
1968
1969TEST(DiagsInHeaders, OnlyErrorOrFatal) {
1970 Annotations Main(R"cpp(
1971 #include [["a.h"]]
1972 void foo() {})cpp");
1973 Annotations Header(R"cpp(
1974 [[no_type_spec]]; // error-ok
1975 int x = 5/0;)cpp");
1976 TestTU TU = TestTU::withCode(Main.code());
1977 TU.AdditionalFiles = {{"a.h", std::string(Header.code())}};
1978 EXPECT_THAT(TU.build().getDiagnostics(),
1979 UnorderedElementsAre(AllOf(
1980 Diag(Main.range(), "in included file: a type specifier is "
1981 "required for all declarations"),
1982 withNote(Diag(Header.range(), "error occurred here")))));
1983}
1984
1985TEST(DiagsInHeaders, OnlyDefaultErrorOrFatal) {
1986 Annotations Main(R"cpp(
1987 #include [["a.h"]] // get unused "foo" warning when building preamble.
1988 )cpp");
1989 Annotations Header(R"cpp(
1990 namespace { void foo() {} }
1991 void func() {foo();} ;)cpp");
1992 TestTU TU = TestTU::withCode(Main.code());
1993 TU.AdditionalFiles = {{"a.h", std::string(Header.code())}};
1994 // promote warnings to errors.
1995 TU.ExtraArgs = {"-Werror", "-Wunused"};
1996 EXPECT_THAT(TU.build().getDiagnostics(), IsEmpty());
1997}
1998
1999TEST(DiagsInHeaders, FromNonWrittenSources) {
2000 Annotations Main(R"cpp(
2001 #include [["a.h"]]
2002 void foo() {})cpp");
2003 Annotations Header(R"cpp(
2004 int x = 5/0;
2005 int b = [[FOO]]; // error-ok)cpp");
2006 TestTU TU = TestTU::withCode(Main.code());
2007 TU.AdditionalFiles = {{"a.h", std::string(Header.code())}};
2008 TU.ExtraArgs = {"-DFOO=NOOO"};
2009 EXPECT_THAT(TU.build().getDiagnostics(),
2010 UnorderedElementsAre(AllOf(
2011 Diag(Main.range(),
2012 "in included file: use of undeclared identifier 'NOOO'"),
2013 withNote(Diag(Header.range(), "error occurred here")))));
2014}
2015
2016TEST(DiagsInHeaders, ErrorFromMacroExpansion) {
2017 Annotations Main(R"cpp(
2018 void bar() {
2019 int fo; // error-ok
2020 #include [["a.h"]]
2021 })cpp");
2022 Annotations Header(R"cpp(
2023 #define X foo
2024 X;)cpp");
2025 TestTU TU = TestTU::withCode(Main.code());
2026 TU.AdditionalFiles = {{"a.h", std::string(Header.code())}};
2027 EXPECT_THAT(TU.build().getDiagnostics(),
2028 UnorderedElementsAre(
2029 Diag(Main.range(), "in included file: use of undeclared "
2030 "identifier 'foo'; did you mean 'fo'?")));
2031}
2032
2033TEST(DiagsInHeaders, ErrorFromMacroArgument) {
2034 Annotations Main(R"cpp(
2035 void bar() {
2036 int fo; // error-ok
2037 #include [["a.h"]]
2038 })cpp");
2039 Annotations Header(R"cpp(
2040 #define X(arg) arg
2041 X(foo);)cpp");
2042 TestTU TU = TestTU::withCode(Main.code());
2043 TU.AdditionalFiles = {{"a.h", std::string(Header.code())}};
2044 EXPECT_THAT(TU.build().getDiagnostics(),
2045 UnorderedElementsAre(
2046 Diag(Main.range(), "in included file: use of undeclared "
2047 "identifier 'foo'; did you mean 'fo'?")));
2048}
2049
2050TEST(IgnoreDiags, FromNonWrittenInclude) {
2051 TestTU TU;
2052 TU.ExtraArgs.push_back("--include=a.h");
2053 TU.AdditionalFiles = {{"a.h", "void main();"}};
2054 // The diagnostic "main must return int" is from the header, we don't attempt
2055 // to render it in the main file as there is no written location there.
2056 EXPECT_THAT(TU.build().getDiagnostics(), UnorderedElementsAre());
2057}
2058
2059TEST(ToLSPDiag, RangeIsInMain) {
2061 clangd::Diag D;
2062 D.Range = {pos(1, 2), pos(3, 4)};
2063 D.Notes.emplace_back();
2064 Note &N = D.Notes.back();
2065 N.Range = {pos(2, 3), pos(3, 4)};
2066
2067 D.InsideMainFile = true;
2068 N.InsideMainFile = false;
2069 toLSPDiags(D, {}, Opts,
2070 [&](clangd::Diagnostic LSPDiag, ArrayRef<clangd::Fix>) {
2071 EXPECT_EQ(LSPDiag.range, D.Range);
2072 });
2073
2074 D.InsideMainFile = false;
2075 N.InsideMainFile = true;
2076 toLSPDiags(D, {}, Opts,
2077 [&](clangd::Diagnostic LSPDiag, ArrayRef<clangd::Fix>) {
2078 EXPECT_EQ(LSPDiag.range, N.Range);
2079 });
2080}
2081
2082TEST(ParsedASTTest, ModuleSawDiag) {
2083 TestTU TU;
2084
2085 auto AST = TU.build();
2086 #if 0
2087 EXPECT_THAT(AST.getDiagnostics(),
2088 testing::Contains(Diag(Code.range(), KDiagMsg.str())));
2089 #endif
2090}
2091
2092TEST(Preamble, EndsOnNonEmptyLine) {
2093 TestTU TU;
2094 TU.ExtraArgs = {"-Wnewline-eof"};
2095
2096 {
2097 TU.Code = "#define FOO\n void bar();\n";
2098 auto AST = TU.build();
2099 EXPECT_THAT(AST.getDiagnostics(), IsEmpty());
2100 }
2101 {
2102 Annotations Code("#define FOO[[]]");
2103 TU.Code = Code.code().str();
2104 auto AST = TU.build();
2105 EXPECT_THAT(
2106 AST.getDiagnostics(),
2107 testing::Contains(Diag(Code.range(), "no newline at end of file")));
2108 }
2109}
2110
2111TEST(Diagnostics, Tags) {
2112 TestTU TU;
2113 TU.ExtraArgs = {"-Wunused", "-Wdeprecated"};
2114 Annotations Test(R"cpp(
2115 void bar() __attribute__((deprecated));
2116 void foo() {
2117 int $unused[[x]];
2118 $deprecated[[bar]]();
2119 })cpp");
2120 TU.Code = Test.code().str();
2121 EXPECT_THAT(TU.build().getDiagnostics(),
2122 UnorderedElementsAre(
2123 AllOf(Diag(Test.range("unused"), "unused variable 'x'"),
2125 AllOf(Diag(Test.range("deprecated"), "'bar' is deprecated"),
2126 withTag(DiagnosticTag::Deprecated))));
2127
2128 Test = Annotations(R"cpp(
2129 $typedef[[typedef int INT]];
2130 )cpp");
2131 TU.Code = Test.code();
2132 TU.ClangTidyProvider = addTidyChecks("modernize-use-using");
2133 EXPECT_THAT(
2134 TU.build().getDiagnostics(),
2135 ifTidyChecks(UnorderedElementsAre(
2136 AllOf(Diag(Test.range("typedef"), "use 'using' instead of 'typedef'"),
2137 withTag(DiagnosticTag::Deprecated)))));
2138}
2139
2140TEST(Diagnostics, TidyDiagsArentAffectedFromWerror) {
2141 TestTU TU;
2142 TU.ExtraArgs = {"-Werror"};
2143 Annotations Test(R"cpp($typedef[[typedef int INT]]; // error-ok)cpp");
2144 TU.Code = Test.code().str();
2145 TU.ClangTidyProvider = addTidyChecks("modernize-use-using");
2146 EXPECT_THAT(
2147 TU.build().getDiagnostics(),
2148 ifTidyChecks(UnorderedElementsAre(
2149 AllOf(Diag(Test.range("typedef"), "use 'using' instead of 'typedef'"),
2150 // Make sure severity for clang-tidy finding isn't bumped to
2151 // error due to Werror in compile flags.
2152 diagSeverity(DiagnosticsEngine::Warning)))));
2153
2154 TU.ClangTidyProvider =
2155 addTidyChecks("modernize-use-using", /*WarningsAsErrors=*/"modernize-*");
2156 EXPECT_THAT(
2157 TU.build().getDiagnostics(),
2158 ifTidyChecks(UnorderedElementsAre(
2159 AllOf(Diag(Test.range("typedef"), "use 'using' instead of 'typedef'"),
2160 // Unless bumped explicitly with WarnAsError.
2161 diagSeverity(DiagnosticsEngine::Error)))));
2162}
2163
2164TEST(Diagnostics, DeprecatedDiagsAreHints) {
2166 std::optional<clangd::Diagnostic> Diag;
2167 clangd::Diag D;
2168 D.Range = {pos(1, 2), pos(3, 4)};
2169 D.InsideMainFile = true;
2170
2171 // Downgrade warnings with deprecated tags to remark.
2172 D.Tags = {Deprecated};
2173 D.Severity = DiagnosticsEngine::Warning;
2174 toLSPDiags(D, {}, Opts,
2175 [&](clangd::Diagnostic LSPDiag, ArrayRef<clangd::Fix>) {
2176 Diag = std::move(LSPDiag);
2177 });
2178 EXPECT_EQ(Diag->severity, getSeverity(DiagnosticsEngine::Remark));
2179 Diag.reset();
2180
2181 // Preserve errors.
2182 D.Severity = DiagnosticsEngine::Error;
2183 toLSPDiags(D, {}, Opts,
2184 [&](clangd::Diagnostic LSPDiag, ArrayRef<clangd::Fix>) {
2185 Diag = std::move(LSPDiag);
2186 });
2187 EXPECT_EQ(Diag->severity, getSeverity(DiagnosticsEngine::Error));
2188 Diag.reset();
2189
2190 // No-op without tag.
2191 D.Tags = {};
2192 D.Severity = DiagnosticsEngine::Warning;
2193 toLSPDiags(D, {}, Opts,
2194 [&](clangd::Diagnostic LSPDiag, ArrayRef<clangd::Fix>) {
2195 Diag = std::move(LSPDiag);
2196 });
2197 EXPECT_EQ(Diag->severity, getSeverity(DiagnosticsEngine::Warning));
2198}
2199
2200TEST(DiagnosticsTest, IncludeCleaner) {
2201 Annotations Test(R"cpp(
2202$fix[[ $diag[[#include "unused.h"]]
2203]]
2204 #include "used.h"
2205
2206 #include "ignore.h"
2207
2208 #include <system_header.h>
2209
2210 void foo() {
2211 used();
2212 }
2213 )cpp");
2214 TestTU TU;
2215 TU.Code = Test.code().str();
2216 TU.AdditionalFiles["unused.h"] = R"cpp(
2217 #pragma once
2218 void unused() {}
2219 )cpp";
2220 TU.AdditionalFiles["used.h"] = R"cpp(
2221 #pragma once
2222 void used() {}
2223 )cpp";
2224 TU.AdditionalFiles["ignore.h"] = R"cpp(
2225 #pragma once
2226 void ignore() {}
2227 )cpp";
2228 TU.AdditionalFiles["system/system_header.h"] = "";
2229 TU.ExtraArgs = {"-isystem" + testPath("system")};
2230 Config Cfg;
2232 // Set filtering.
2233 Cfg.Diagnostics.Includes.IgnoreHeader.emplace_back(
2234 [](llvm::StringRef Header) { return Header.ends_with("ignore.h"); });
2235 WithContextValue WithCfg(Config::Key, std::move(Cfg));
2236 auto AST = TU.build();
2237 EXPECT_THAT(
2238 AST.getDiagnostics(),
2239 Contains(AllOf(
2240 Diag(Test.range("diag"),
2241 "included header unused.h is not used directly"),
2242 withTag(DiagnosticTag::Unnecessary), diagSource(Diag::Clangd),
2243 withFix(Fix(Test.range("fix"), "", "remove #include directive")))));
2244 auto &Diag = AST.getDiagnostics().front();
2246 llvm::ValueIs(Not(IsEmpty())));
2247 Cfg.Diagnostics.SuppressAll = true;
2248 WithContextValue SuppressAllWithCfg(Config::Key, std::move(Cfg));
2249 EXPECT_THAT(TU.build().getDiagnostics(), IsEmpty());
2250 Cfg.Diagnostics.SuppressAll = false;
2251 Cfg.Diagnostics.Suppress = {"unused-includes"};
2252 WithContextValue SuppressFilterWithCfg(Config::Key, std::move(Cfg));
2253 EXPECT_THAT(TU.build().getDiagnostics(), IsEmpty());
2254}
2255
2256TEST(DiagnosticsTest, FixItFromHeader) {
2257 llvm::StringLiteral Header(R"cpp(
2258 void foo(int *);
2259 void foo(int *, int);)cpp");
2260 Annotations Source(R"cpp(
2261 /*error-ok*/
2262 void bar() {
2263 int x;
2264 $diag[[foo]]($fix[[]]x, 1);
2265 })cpp");
2266 TestTU TU;
2267 TU.Code = Source.code().str();
2268 TU.HeaderCode = Header.str();
2269 EXPECT_THAT(
2270 TU.build().getDiagnostics(),
2271 UnorderedElementsAre(AllOf(
2272 Diag(Source.range("diag"), "no matching function for call to 'foo'"),
2273 withFix(Fix(Source.range("fix"), "&",
2274 "candidate function not viable: no known conversion from "
2275 "'int' to 'int *' for 1st argument; take the address of "
2276 "the argument with &")))));
2277}
2278
2279TEST(DiagnosticsTest, UnusedInHeader) {
2280 // Clang diagnoses unused static inline functions outside headers.
2281 auto TU = TestTU::withCode("static inline void foo(void) {}");
2282 TU.ExtraArgs.push_back("-Wunused-function");
2283 TU.Filename = "test.c";
2284 EXPECT_THAT(TU.build().getDiagnostics(),
2285 ElementsAre(withID(diag::warn_unused_function)));
2286 // Sema should recognize a *.h file open in clangd as a header.
2287 // https://github.com/clangd/vscode-clangd/issues/360
2288 TU.Filename = "test.h";
2289 EXPECT_THAT(TU.build().getDiagnostics(), IsEmpty());
2290}
2291
2292TEST(DiagnosticsTest, DontSuppressSubcategories) {
2293 Annotations Source(R"cpp(
2294 /*error-ok*/
2295 void bar(int x) {
2296 switch(x) {
2297 default:
2298 break;
2299 break;
2300 }
2301 })cpp");
2302 TestTU TU;
2303 TU.ExtraArgs.push_back("-Wunreachable-code-aggressive");
2304 TU.Code = Source.code().str();
2305 Config Cfg;
2306 // This shouldn't suppress subcategory unreachable-break.
2307 Cfg.Diagnostics.Suppress = {"unreachable-code"};
2308 WithContextValue SuppressFilterWithCfg(Config::Key, std::move(Cfg));
2309 EXPECT_THAT(TU.build().getDiagnostics(),
2310 ElementsAre(diagName("-Wunreachable-code-break")));
2311}
2312
2313// Simulate a diagnostic with two removal fixits.
2314TEST(DiagnosticsTest, TokenMergeFixit) {
2315 Annotations Test(R"cpp(
2316 int test() {
2317 return$lparen[[(]]0$rparen[[)]];
2318 }
2319 )cpp");
2320
2321 SourceManagerForFile SM("TestTU.cpp", Test.code());
2322 DiagnosticsEngine &DE = SM.get().getDiagnostics();
2323 StoreDiags DiagConsumer;
2324 DE.setClient(&DiagConsumer, /*ShouldOwnClient=*/false);
2325
2326 LangOptions LangOpts;
2327 DiagConsumer.BeginSourceFile(LangOpts, /*PP=*/nullptr);
2328 {
2329 auto Loc = [&](Position P) {
2330 return llvm::cantFail(sourceLocationInMainFile(SM.get(), P));
2331 };
2332 SourceLocation LParenBegin = Loc(Test.range("lparen").start);
2333 SourceLocation LParenEnd = Loc(Test.range("lparen").end);
2334 SourceLocation RParenBegin = Loc(Test.range("rparen").start);
2335 SourceLocation RParenEnd = Loc(Test.range("rparen").end);
2336
2337 unsigned ID = DE.getCustomDiagID(DiagnosticsEngine::Warning,
2338 "redundant parentheses around expression");
2339 DiagnosticBuilder DB = DE.Report(LParenBegin, ID);
2340 DB.AddFixItHint(FixItHint::CreateRemoval(
2341 CharSourceRange::getCharRange(LParenBegin, LParenEnd)));
2342 DB.AddFixItHint(FixItHint::CreateRemoval(
2343 CharSourceRange::getCharRange(RParenBegin, RParenEnd)));
2344 }
2345 DiagConsumer.EndSourceFile();
2346
2347 auto Diags = DiagConsumer.take();
2348
2349 ASSERT_EQ(Diags.size(), 1u);
2350 ASSERT_EQ(Diags[0].Fixes.size(), 1u);
2351 const auto &Fix = Diags[0].Fixes[0];
2352
2353 ASSERT_EQ(Fix.Edits.size(), 2u);
2354 EXPECT_EQ(Fix.Edits[0].range, Test.range("lparen"));
2355 EXPECT_EQ(Fix.Edits[0].newText, " ");
2356 EXPECT_EQ(Fix.Edits[1].range, Test.range("rparen"));
2357 EXPECT_EQ(Fix.Edits[1].newText, "");
2358}
2359
2360} // namespace
2361} // namespace clangd
2362} // namespace clang
static cl::opt< bool > Fix("fix", desc(R"( Apply suggested fixes. Without -fix-errors clang-tidy will bail out if any compilation errors were found. )"), cl::init(false), cl::cat(ClangTidyCategory))
static cl::opt< std::string > Checks("checks", desc(R"( Comma-separated list of globs with optional '-' prefix. Globs are processed in order of appearance in the list. Globs without '-' prefix add checks with matching names to the set, globs with the '-' prefix remove checks with matching names from the set of enabled checks. This option's value is appended to the value of the 'Checks' option in .clang-tidy file, if any. )"), cl::init(""), cl::cat(ClangTidyCategory))
Same as llvm::Annotations, but adjusts functions to LSP-specific types for positions and ranges.
Definition Annotations.h:23
static std::unique_ptr< SymbolIndex > build(SymbolSlab Symbols, RefSlab Refs, RelationSlab Relations)
Builds an index from slabs. The index takes ownership of the data.
Definition MemIndex.cpp:18
An efficient structure of storing large set of symbol references in memory.
Definition Ref.h:111
StoreDiags collects the diagnostics that can later be reported by clangd.
SymbolSlab::Builder is a mutable container that can 'freeze' to SymbolSlab.
Definition Symbol.h:238
void insert(const Symbol &S)
Adds a symbol, overwriting any existing one with the same ID.
Definition Symbol.cpp:52
WithContextValue extends Context::current() with a single value.
Definition Context.h:200
FIXME: Skip testing on windows temporarily due to the different escaping code mode.
Definition AST.cpp:44
llvm::unique_function< void(tidy::ClangTidyOptions &, llvm::StringRef) const > TidyProvider
A factory to modify a tidy::ClangTidyOptions.
Symbol func(llvm::StringRef Name)
Definition TestIndex.cpp:62
const NamedDecl & findDecl(ParsedAST &AST, llvm::StringRef QName)
Definition TestTU.cpp:220
Symbol cls(llvm::StringRef Name)
Definition TestIndex.cpp:66
TidyProvider combine(std::vector< TidyProvider > Providers)
void toLSPDiags(const Diag &D, const URIForFile &File, const ClangdDiagnosticOptions &Opts, llvm::function_ref< void(clangd::Diagnostic, llvm::ArrayRef< Fix >)> OutFn)
Conversion to LSP diagnostics.
MATCHER_P2(hasFlag, Flag, Path, "")
MATCHER_P(named, N, "")
std::string testPath(PathRef File, llvm::sys::path::Style Style)
Definition TestFS.cpp:94
static URISchemeRegistry::Add< TestScheme > X(TestScheme::Scheme, "Test schema")
llvm::json::Value toJSON(const FuzzyFindRequest &Request)
Definition Index.cpp:45
TidyProvider addTidyChecks(llvm::StringRef Checks, llvm::StringRef WarningsAsErrors)
Provider the enables a specific set of checks and warnings as errors.
TEST(BackgroundQueueTest, Priority)
Symbol enm(llvm::StringRef Name)
Definition TestIndex.cpp:70
llvm::Expected< SourceLocation > sourceLocationInMainFile(const SourceManager &SM, Position P)
Return the file location, corresponding to P.
TidyProvider disableUnusableChecks(llvm::ArrayRef< std::string > ExtraBadChecks)
Provider that will disable checks known to not work with clangd.
int getSeverity(DiagnosticsEngine::Level L)
Convert from clang diagnostic level to LSP severity.
@ Deprecated
Deprecated or obsolete code.
Definition Protocol.h:936
@ Unnecessary
Unused or unnecessary code.
Definition Protocol.h:932
std::optional< std::string > getDiagnosticDocURI(Diag::DiagSource Source, unsigned ID, llvm::StringRef Name)
Returns a URI providing more information about a particular diagnostic.
cppcoreguidelines::ProBoundsAvoidUncheckedContainerAccessCheck P
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
Settings that express user/project preferences and control clangd behavior.
Definition Config.h:45
static clangd::Key< Config > Key
Context key which can be used to set the current Config.
Definition Config.h:49
@ Strict
Diagnose missing and unused includes.
Definition Config.h:99
struct clang::clangd::Config::@343034053122374337352226322054223376344037116252 Diagnostics
Controls warnings and errors when parsing code.
llvm::StringSet Suppress
Definition Config.h:106
IncludesPolicy UnusedIncludes
Definition Config.h:117
struct clang::clangd::Config::@343034053122374337352226322054223376344037116252::@107156241027253143221327255130274177352007274355 ClangTidy
Configures what clang-tidy checks to run and options to use with them.
bool ExperimentalCustomChecks
Definition Config.h:114
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.
llvm::SmallVector< DiagnosticTag, 1 > Tags
enum clang::clangd::Diag::DiagSource Source
std::vector< Note > Notes
Elaborate on the problem, usually pointing to a related piece of code.
Represents a related message and source code location for a diagnostic.
Definition Protocol.h:919
std::string message
The message of this related diagnostic information.
Definition Protocol.h:923
Represents a single fix-it that editor can apply to fix the error.
Definition Diagnostics.h:81
std::string Message
Message for the fix-it.
Definition Diagnostics.h:83
llvm::SmallVector< TextEdit, 1 > Edits
TextEdits from clang's fix-its. Must be non-empty.
Definition Diagnostics.h:85
Represents a header file to be include'd.
Definition Headers.h:42
Represents a note for the diagnostic.
Definition Diagnostics.h:95
int line
Line position in a document (zero-based).
Definition Protocol.h:159
Ensure we have enough bits to represent all SymbolTag values.
Definition Symbol.h:49
@ IndexedForCodeCompletion
Whether or not this symbol is meant to be used for the code completion.
Definition Symbol.h:155
@ Include
#include "header.h"
Definition Symbol.h:107
std::vector< std::string > ExtraArgs
Definition TestTU.h:60
std::string Code
Definition TestTU.h:49
static TestTU withHeaderCode(llvm::StringRef HeaderCode)
Definition TestTU.h:42
static TestTU withCode(llvm::StringRef Code)
Definition TestTU.h:36
const SymbolIndex * ExternalIndex
Definition TestTU.h:67
static URIForFile canonicalize(llvm::StringRef AbsPath, llvm::StringRef TUPath)
Canonicalizes AbsPath via URI.
Definition Protocol.cpp:46