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