1 //===--- DiagnosticsTests.cpp ------------------------------------*- C++-*-===//
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
7 //===----------------------------------------------------------------------===//
9 #include "../clang-tidy/ClangTidyOptions.h"
10 #include "Annotations.h"
12 #include "Diagnostics.h"
14 #include "FeatureModule.h"
15 #include "ParsedAST.h"
18 #include "TestIndex.h"
20 #include "TidyProvider.h"
21 #include "index/MemIndex.h"
22 #include "index/Ref.h"
23 #include "index/Relation.h"
24 #include "index/Symbol.h"
25 #include "support/Context.h"
26 #include "support/Path.h"
27 #include "clang/AST/Decl.h"
28 #include "clang/Basic/Diagnostic.h"
29 #include "clang/Basic/DiagnosticSema.h"
30 #include "clang/Basic/LLVM.h"
31 #include "clang/Basic/Specifiers.h"
32 #include "llvm/ADT/ArrayRef.h"
33 #include "llvm/ADT/StringRef.h"
34 #include "llvm/Support/JSON.h"
35 #include "llvm/Support/ScopedPrinter.h"
36 #include "llvm/Support/TargetSelect.h"
37 #include "llvm/Testing/Support/SupportHelpers.h"
38 #include "gmock/gmock.h"
39 #include "gtest/gtest.h"
52 using ::testing::AllOf
;
53 using ::testing::Contains
;
54 using ::testing::Each
;
55 using ::testing::ElementsAre
;
56 using ::testing::Field
;
57 using ::testing::IsEmpty
;
59 using ::testing::Pair
;
60 using ::testing::SizeIs
;
61 using ::testing::UnorderedElementsAre
;
63 ::testing::Matcher
<const Diag
&> withFix(::testing::Matcher
<Fix
> FixMatcher
) {
64 return Field(&Diag::Fixes
, ElementsAre(FixMatcher
));
67 ::testing::Matcher
<const Diag
&> withFix(::testing::Matcher
<Fix
> FixMatcher1
,
68 ::testing::Matcher
<Fix
> FixMatcher2
) {
69 return Field(&Diag::Fixes
, UnorderedElementsAre(FixMatcher1
, FixMatcher2
));
72 ::testing::Matcher
<const Diag
&> withID(unsigned ID
) {
73 return Field(&Diag::ID
, ID
);
75 ::testing::Matcher
<const Diag
&>
76 withNote(::testing::Matcher
<Note
> NoteMatcher
) {
77 return Field(&Diag::Notes
, ElementsAre(NoteMatcher
));
80 ::testing::Matcher
<const Diag
&>
81 withNote(::testing::Matcher
<Note
> NoteMatcher1
,
82 ::testing::Matcher
<Note
> NoteMatcher2
) {
83 return Field(&Diag::Notes
, UnorderedElementsAre(NoteMatcher1
, NoteMatcher2
));
86 ::testing::Matcher
<const Diag
&>
87 withTag(::testing::Matcher
<DiagnosticTag
> TagMatcher
) {
88 return Field(&Diag::Tags
, Contains(TagMatcher
));
91 MATCHER_P(hasRange
, Range
, "") { return arg
.Range
== Range
; }
93 MATCHER_P2(Diag
, Range
, Message
,
94 "Diag at " + llvm::to_string(Range
) + " = [" + Message
+ "]") {
95 return arg
.Range
== Range
&& arg
.Message
== Message
;
98 MATCHER_P3(Fix
, Range
, Replacement
, Message
,
99 "Fix " + llvm::to_string(Range
) + " => " +
100 ::testing::PrintToString(Replacement
) + " = [" + Message
+ "]") {
101 return arg
.Message
== Message
&& arg
.Edits
.size() == 1 &&
102 arg
.Edits
[0].range
== Range
&& arg
.Edits
[0].newText
== Replacement
;
105 MATCHER_P(fixMessage
, Message
, "") { return arg
.Message
== Message
; }
107 MATCHER_P(equalToLSPDiag
, LSPDiag
,
108 "LSP diagnostic " + llvm::to_string(LSPDiag
)) {
109 if (toJSON(arg
) != toJSON(LSPDiag
)) {
110 *result_listener
<< llvm::formatv("expected:\n{0:2}\ngot\n{1:2}",
111 toJSON(LSPDiag
), toJSON(arg
))
118 MATCHER_P(diagSource
, S
, "") { return arg
.Source
== S
; }
119 MATCHER_P(diagName
, N
, "") { return arg
.Name
== N
; }
120 MATCHER_P(diagSeverity
, S
, "") { return arg
.Severity
== S
; }
122 MATCHER_P(equalToFix
, Fix
, "LSP fix " + llvm::to_string(Fix
)) {
123 if (arg
.Message
!= Fix
.Message
)
125 if (arg
.Edits
.size() != Fix
.Edits
.size())
127 for (std::size_t I
= 0; I
< arg
.Edits
.size(); ++I
) {
128 if (arg
.Edits
[I
].range
!= Fix
.Edits
[I
].range
||
129 arg
.Edits
[I
].newText
!= Fix
.Edits
[I
].newText
)
135 // Helper function to make tests shorter.
136 Position
pos(int Line
, int Character
) {
139 Res
.character
= Character
;
143 // Normally returns the provided diagnostics matcher.
144 // If clang-tidy checks are not linked in, returns a matcher for no diagnostics!
145 // This is intended for tests where the diagnostics come from clang-tidy checks.
146 // We don't #ifdef each individual test as it's intrusive and we want to ensure
147 // that as much of the test is still compiled an run as possible.
148 ::testing::Matcher
<std::vector
<clangd::Diag
>>
149 ifTidyChecks(::testing::Matcher
<std::vector
<clangd::Diag
>> M
) {
150 if (!CLANGD_TIDY_CHECKS
)
155 TEST(DiagnosticsTest
, DiagnosticRanges
) {
156 // Check we report correct ranges, including various edge-cases.
157 Annotations
Test(R
"cpp(
163 struct Container { int* begin(); int* end(); } *container;
164 for (auto i : $insertstar[[]]$range[[container]]) {
169 foo()$semicolon[[]]//with comments
171 double $type[[bar]] = "foo
";
172 struct Foo { int x; }; Foo a;
174 test::$nomembernamespace[[test]];
175 $macro[[ID($macroarg[[fod]])]]();
178 auto TU
= TestTU::withCode(Test
.code());
180 TU
.build().getDiagnostics(),
182 // Make sure the whole token is highlighted.
183 AllOf(Diag(Test
.range("range"),
184 "invalid range expression of type 'struct Container *'; "
185 "did you mean to dereference it with '*'?"),
186 withFix(Fix(Test
.range("insertstar"), "*", "insert '*'"))),
187 // This range spans lines.
188 AllOf(Diag(Test
.range("typo"),
189 "use of undeclared identifier 'goo'; did you mean 'foo'?"),
190 diagSource(Diag::Clang
), diagName("undeclared_var_use_suggest"),
192 Fix(Test
.range("typo"), "foo", "change 'go\\…' to 'foo'")),
193 // This is a pretty normal range.
194 withNote(Diag(Test
.range("decl"), "'foo' declared here"))),
195 // This range is zero-width and insertion. Therefore make sure we are
196 // not expanding it into other tokens. Since we are not going to
198 AllOf(Diag(Test
.range("semicolon"), "expected ';' after expression"),
199 withFix(Fix(Test
.range("semicolon"), ";", "insert ';'"))),
200 // This range isn't provided by clang, we expand to the token.
201 Diag(Test
.range("unk"), "use of undeclared identifier 'unknown'"),
202 Diag(Test
.range("type"),
203 "cannot initialize a variable of type 'double' with an lvalue "
204 "of type 'const char[4]'"),
205 Diag(Test
.range("nomember"), "no member named 'y' in 'Foo'"),
206 Diag(Test
.range("nomembernamespace"),
207 "no member named 'test' in namespace 'test'"),
208 AllOf(Diag(Test
.range("macro"),
209 "use of undeclared identifier 'fod'; did you mean 'foo'?"),
210 withFix(Fix(Test
.range("macroarg"), "foo",
211 "change 'fod' to 'foo'")))));
214 // Verify that the -Wswitch case-not-covered diagnostic range covers the
215 // whole expression. This is important because the "populate-switch" tweak
216 // fires for the full expression range (see tweaks/PopulateSwitchTests.cpp).
217 // The quickfix flow only works end-to-end if the tweak can be triggered on
218 // the diagnostic's range.
219 TEST(DiagnosticsTest
, WSwitch
) {
220 Annotations
Test(R
"cpp(
227 auto TU
= TestTU::withCode(Test
.code());
228 TU
.ExtraArgs
= {"-Wswitch"};
229 EXPECT_THAT(TU
.build().getDiagnostics(),
230 ElementsAre(Diag(Test
.range(),
231 "enumeration value 'X' not handled in switch")));
234 TEST(DiagnosticsTest
, FlagsMatter
) {
235 Annotations
Test("[[void]] main() {} // error-ok");
236 auto TU
= TestTU::withCode(Test
.code());
237 EXPECT_THAT(TU
.build().getDiagnostics(),
238 ElementsAre(AllOf(Diag(Test
.range(), "'main' must return 'int'"),
239 withFix(Fix(Test
.range(), "int",
240 "change 'void' to 'int'")))));
241 // Same code built as C gets different diagnostics.
242 TU
.Filename
= "Plain.c";
244 TU
.build().getDiagnostics(),
246 Diag(Test
.range(), "return type of 'main' is not 'int'"),
247 withFix(Fix(Test
.range(), "int", "change return type to 'int'")))));
250 TEST(DiagnosticsTest
, DiagnosticPreamble
) {
251 Annotations
Test(R
"cpp(
252 #include $[["not-found
.h
"]] // error-ok
255 auto TU
= TestTU::withCode(Test
.code());
256 EXPECT_THAT(TU
.build().getDiagnostics(),
257 ElementsAre(::testing::AllOf(
258 Diag(Test
.range(), "'not-found.h' file not found"),
259 diagSource(Diag::Clang
), diagName("pp_file_not_found"))));
262 TEST(DiagnosticsTest
, DeduplicatedClangTidyDiagnostics
) {
263 Annotations
Test(R
"cpp(
264 float foo = [[0.1f]];
266 auto TU
= TestTU::withCode(Test
.code());
267 // Enable alias clang-tidy checks, these check emits the same diagnostics
268 // (except the check name).
269 TU
.ClangTidyProvider
= addTidyChecks("readability-uppercase-literal-suffix,"
270 "hicpp-uppercase-literal-suffix");
271 // Verify that we filter out the duplicated diagnostic message.
273 TU
.build().getDiagnostics(),
274 ifTidyChecks(UnorderedElementsAre(::testing::AllOf(
276 "floating point literal has suffix 'f', which is not uppercase"),
277 diagSource(Diag::ClangTidy
)))));
279 Test
= Annotations(R
"cpp(
289 TU
.Code
= std::string(Test
.code());
290 // The check doesn't handle template instantiations which ends up emitting
291 // duplicated messages, verify that we deduplicate them.
293 TU
.build().getDiagnostics(),
294 ifTidyChecks(UnorderedElementsAre(::testing::AllOf(
296 "floating point literal has suffix 'f', which is not uppercase"),
297 diagSource(Diag::ClangTidy
)))));
300 TEST(DiagnosticsTest
, ClangTidy
) {
301 Annotations
Test(R
"cpp(
302 #include $deprecated[["assert.h
"]]
304 #define $macrodef[[SQUARE]](X) (X)*(X)
305 int $main[[main]]() {
307 return SQUARE($macroarg[[++]]y);
308 return $doubled[[sizeof(sizeof(int))]];
311 // misc-no-recursion uses a custom traversal from the TUDecl
320 auto TU
= TestTU::withCode(Test
.code());
321 TU
.HeaderFilename
= "assert.h"; // Suppress "not found" error.
322 TU
.ClangTidyProvider
= addTidyChecks("bugprone-sizeof-expression,"
323 "bugprone-macro-repeated-side-effects,"
324 "modernize-deprecated-headers,"
325 "modernize-use-trailing-return-type,"
326 "misc-no-recursion");
327 TU
.ExtraArgs
.push_back("-Wno-unsequenced");
329 TU
.build().getDiagnostics(),
330 ifTidyChecks(UnorderedElementsAre(
331 AllOf(Diag(Test
.range("deprecated"),
332 "inclusion of deprecated C++ header 'assert.h'; consider "
333 "using 'cassert' instead"),
334 diagSource(Diag::ClangTidy
),
335 diagName("modernize-deprecated-headers"),
336 withFix(Fix(Test
.range("deprecated"), "<cassert>",
337 "change '\"assert.h\"' to '<cassert>'"))),
338 Diag(Test
.range("doubled"),
339 "suspicious usage of 'sizeof(sizeof(...))'"),
340 AllOf(Diag(Test
.range("macroarg"),
341 "side effects in the 1st macro argument 'X' are "
344 diagSource(Diag::ClangTidy
),
345 diagName("bugprone-macro-repeated-side-effects"),
346 withNote(Diag(Test
.range("macrodef"),
347 "macro 'SQUARE' defined here"))),
348 AllOf(Diag(Test
.range("main"),
349 "use a trailing return type for this function"),
350 diagSource(Diag::ClangTidy
),
351 diagName("modernize-use-trailing-return-type"),
352 // Verify there's no "[check-name]" suffix in the message.
354 "use a trailing return type for this function"))),
355 Diag(Test
.range("foo"),
356 "function 'foo' is within a recursive call chain"),
357 Diag(Test
.range("bar"),
358 "function 'bar' is within a recursive call chain"))));
361 TEST(DiagnosticsTest
, ClangTidyEOF
) {
363 Annotations
Test(R
"cpp(
365 #include "a
.h
")cpp");
367 auto TU
= TestTU::withCode(Test
.code());
368 TU
.ExtraArgs
= {"-isystem."};
369 TU
.AdditionalFiles
["a.h"] = TU
.AdditionalFiles
["b.h"] = "";
370 TU
.ClangTidyProvider
= addTidyChecks("llvm-include-order");
372 TU
.build().getDiagnostics(),
373 ifTidyChecks(Contains(
374 AllOf(Diag(Test
.range(), "#includes are not sorted properly"),
375 diagSource(Diag::ClangTidy
), diagName("llvm-include-order")))));
378 TEST(DiagnosticTest
, TemplatesInHeaders
) {
379 // Diagnostics from templates defined in headers are placed at the expansion.
380 Annotations
Main(R
"cpp(
381 Derived<int> [[y]]; // error-ok
383 Annotations
Header(R
"cpp(
384 template <typename T>
385 struct Derived : [[T]] {};
387 TestTU TU
= TestTU::withCode(Main
.code());
388 TU
.HeaderCode
= Header
.code().str();
390 TU
.build().getDiagnostics(),
392 Diag(Main
.range(), "in template: base specifier must name a class"),
393 withNote(Diag(Header
.range(), "error occurred here"),
394 Diag(Main
.range(), "in instantiation of template class "
395 "'Derived<int>' requested here")))));
398 TEST(DiagnosticTest
, MakeUnique
) {
399 // We usually miss diagnostics from header functions as we don't parse them.
400 // std::make_unique is an exception.
401 Annotations
Main(R
"cpp(
402 struct S { S(char*); };
403 auto x = std::[[make_unique]]<S>(42); // error-ok
405 TestTU TU
= TestTU::withCode(Main
.code());
406 TU
.HeaderCode
= R
"cpp(
408 // These mocks aren't quite right - we omit unique_ptr for simplicity.
409 // forward is included to show its body is not needed to get the diagnostic.
410 template <typename T> T&& forward(T& t);
411 template <typename T, typename... A> T* make_unique(A&&... args) {
412 return new T(std::forward<A>(args)...);
416 EXPECT_THAT(TU
.build().getDiagnostics(),
417 UnorderedElementsAre(
420 "no matching constructor for initialization of 'S'")));
423 TEST(DiagnosticTest
, CoroutineInHeader
) {
424 StringRef CoroutineH
= R
"cpp(
426 template <class Ret, typename... T>
427 struct coroutine_traits { using promise_type = typename Ret::promise_type; };
429 template <class Promise = void>
430 struct coroutine_handle {
431 static coroutine_handle from_address(void *) noexcept;
432 static coroutine_handle from_promise(Promise &promise);
433 constexpr void* address() const noexcept;
436 struct coroutine_handle<void> {
437 template <class PromiseType>
438 coroutine_handle(coroutine_handle<PromiseType>) noexcept;
439 static coroutine_handle from_address(void *);
440 constexpr void* address() const noexcept;
444 bool await_ready() noexcept { return false; }
445 void await_suspend(coroutine_handle<>) noexcept {}
446 void await_resume() noexcept {}
451 StringRef Header
= R
"cpp(
452 #include "coroutine
.h
"
453 template <typename T> struct [[clang::coro_return_type]] Gen {
454 struct promise_type {
455 Gen<T> get_return_object() {
458 std::awaitable initial_suspend();
459 std::awaitable final_suspend() noexcept;
460 void unhandled_exception();
461 void return_value(T t);
465 Gen<int> foo_coro(int b) { co_return b; }
467 Annotations
Main(R
"cpp(
469 #include "header
.hpp
"
470 Gen<int> $[[bar_coro]](int b) { return foo_coro(b); }
472 TestTU TU
= TestTU::withCode(Main
.code());
473 TU
.AdditionalFiles
["coroutine.h"] = std::string(CoroutineH
);
474 TU
.AdditionalFiles
["header.hpp"] = std::string(Header
);
475 TU
.ExtraArgs
.push_back("--std=c++20");
476 EXPECT_THAT(TU
.build().getDiagnostics(), ElementsAre(hasRange(Main
.range())));
479 TEST(DiagnosticTest
, MakeShared
) {
480 // We usually miss diagnostics from header functions as we don't parse them.
481 // std::make_shared is only parsed when --parse-forwarding-functions is set
482 Annotations
Main(R
"cpp(
483 struct S { S(char*); };
484 auto x = std::[[make_shared]]<S>(42); // error-ok
486 TestTU TU
= TestTU::withCode(Main
.code());
487 TU
.HeaderCode
= R
"cpp(
489 // These mocks aren't quite right - we omit shared_ptr for simplicity.
490 // forward is included to show its body is not needed to get the diagnostic.
491 template <typename T> T&& forward(T& t);
492 template <typename T, typename... A> T* make_shared(A&&... args) {
493 return new T(std::forward<A>(args)...);
497 TU
.ParseOpts
.PreambleParseForwardingFunctions
= true;
498 EXPECT_THAT(TU
.build().getDiagnostics(),
499 UnorderedElementsAre(
502 "no matching constructor for initialization of 'S'")));
505 TEST(DiagnosticTest
, NoMultipleDiagnosticInFlight
) {
506 Annotations
Main(R
"cpp(
507 template <typename T> struct Foo {
517 Foo<LabelInfo> label_info_map;
518 [[for]] (auto it = label_info_map.begin(); it != label_info_map.end(); ++it) {
523 TestTU TU
= TestTU::withCode(Main
.code());
524 TU
.ClangTidyProvider
= addTidyChecks("modernize-loop-convert");
526 TU
.build().getDiagnostics(),
527 ifTidyChecks(UnorderedElementsAre(::testing::AllOf(
528 Diag(Main
.range(), "use range-based for loop instead"),
529 diagSource(Diag::ClangTidy
), diagName("modernize-loop-convert")))));
532 TEST(DiagnosticTest
, RespectsDiagnosticConfig
) {
533 Annotations
Main(R
"cpp(
540 auto TU
= TestTU::withCode(Main
.code());
542 TU
.build().getDiagnostics(),
543 ElementsAre(Diag(Main
.range(), "use of undeclared identifier 'unknown'"),
544 Diag(Main
.range("ret"),
545 "void function 'x' should not return a value")));
547 Cfg
.Diagnostics
.Suppress
.insert("return-mismatch");
548 WithContextValue
WithCfg(Config::Key
, std::move(Cfg
));
549 EXPECT_THAT(TU
.build().getDiagnostics(),
550 ElementsAre(Diag(Main
.range(),
551 "use of undeclared identifier 'unknown'")));
554 TEST(DiagnosticTest
, RespectsDiagnosticConfigInHeader
) {
555 Annotations
Header(R
"cpp(
556 int x = "42"; // error-ok
558 Annotations
Main(R
"cpp(
559 #include "header
.hpp
"
561 auto TU
= TestTU::withCode(Main
.code());
562 TU
.AdditionalFiles
["header.hpp"] = std::string(Header
.code());
564 Cfg
.Diagnostics
.Suppress
.insert("init_conversion_failed");
565 WithContextValue
WithCfg(Config::Key
, std::move(Cfg
));
566 EXPECT_THAT(TU
.build().getDiagnostics(), IsEmpty());
569 TEST(DiagnosticTest
, ClangTidySuppressionComment
) {
570 Annotations
Main(R
"cpp(
573 double d = 8 / i; // NOLINT
577 double f = BAD; // NOLINT
578 double g = [[8]] / i;
580 double h = BAD2; // NOLINT
586 // verify no crashes on unmatched nolints.
590 TestTU TU
= TestTU::withCode(Main
.code());
591 TU
.ClangTidyProvider
= addTidyChecks("bugprone-integer-division");
593 TU
.build().getDiagnostics(),
594 ifTidyChecks(UnorderedElementsAre(::testing::AllOf(
595 Diag(Main
.range(), "result of integer division used in a floating "
596 "point context; possible loss of precision"),
597 diagSource(Diag::ClangTidy
),
598 diagName("bugprone-integer-division")))));
601 TEST(DiagnosticTest
, ClangTidySystemMacro
) {
602 Annotations
Main(R
"cpp(
606 double x = $inline[[8]] / i;
607 double y = $user[[DIVIDE_USER]](i);
608 double z = DIVIDE_SYS(i);
611 auto TU
= TestTU::withCode(Main
.code());
612 TU
.AdditionalFiles
["user.h"] = R
"cpp(
613 #define DIVIDE_USER(Y) 8/Y
615 TU
.AdditionalFiles
["system.h"] = R
"cpp(
616 #pragma clang system_header
617 #define DIVIDE_SYS(Y) 8/Y
620 TU
.ClangTidyProvider
= addTidyChecks("bugprone-integer-division");
621 std::string BadDivision
= "result of integer division used in a floating "
622 "point context; possible loss of precision";
624 // Expect to see warning from user macros, but not system macros.
625 // This matches clang-tidy --system-headers=0 (the default).
626 EXPECT_THAT(TU
.build().getDiagnostics(),
628 UnorderedElementsAre(Diag(Main
.range("inline"), BadDivision
),
629 Diag(Main
.range("user"), BadDivision
))));
632 TEST(DiagnosticTest
, ClangTidyWarningAsError
) {
633 Annotations
Main(R
"cpp(
636 double f = [[8]] / i; // error-ok
639 TestTU TU
= TestTU::withCode(Main
.code());
640 TU
.ClangTidyProvider
=
641 addTidyChecks("bugprone-integer-division", "bugprone-integer-division");
643 TU
.build().getDiagnostics(),
644 ifTidyChecks(UnorderedElementsAre(::testing::AllOf(
645 Diag(Main
.range(), "result of integer division used in a floating "
646 "point context; possible loss of precision"),
647 diagSource(Diag::ClangTidy
), diagName("bugprone-integer-division"),
648 diagSeverity(DiagnosticsEngine::Error
)))));
651 TidyProvider
addClangArgs(std::vector
<llvm::StringRef
> ExtraArgs
,
652 llvm::StringRef Checks
) {
653 return [ExtraArgs
= std::move(ExtraArgs
), Checks
= Checks
.str()](
654 tidy::ClangTidyOptions
&Opts
, llvm::StringRef
) {
656 Opts
.ExtraArgs
.emplace();
657 for (llvm::StringRef Arg
: ExtraArgs
)
658 Opts
.ExtraArgs
->emplace_back(Arg
);
660 Opts
.Checks
= Checks
;
664 TEST(DiagnosticTest
, ClangTidyEnablesClangWarning
) {
665 Annotations
Main(R
"cpp( // error-ok
666 static void [[foo]]() {}
668 TestTU TU
= TestTU::withCode(Main
.code());
669 // This is always emitted as a clang warning, not a clang-tidy diagnostic.
670 auto UnusedFooWarning
=
671 AllOf(Diag(Main
.range(), "unused function 'foo'"),
672 diagName("-Wunused-function"), diagSource(Diag::Clang
),
673 diagSeverity(DiagnosticsEngine::Warning
));
675 // Check the -Wunused warning isn't initially on.
676 EXPECT_THAT(TU
.build().getDiagnostics(), IsEmpty());
678 // We enable warnings based on clang-tidy extra args, if the matching
679 // clang-diagnostic- is there.
680 TU
.ClangTidyProvider
=
681 addClangArgs({"-Wunused"}, "clang-diagnostic-unused-function");
682 EXPECT_THAT(TU
.build().getDiagnostics(), ElementsAre(UnusedFooWarning
));
684 // clang-diagnostic-* is acceptable
685 TU
.ClangTidyProvider
= addClangArgs({"-Wunused"}, "clang-diagnostic-*");
686 EXPECT_THAT(TU
.build().getDiagnostics(), ElementsAre(UnusedFooWarning
));
687 // And plain * (may turn on other checks too).
688 TU
.ClangTidyProvider
= addClangArgs({"-Wunused"}, "*");
689 EXPECT_THAT(TU
.build().getDiagnostics(), Contains(UnusedFooWarning
));
690 // And we can explicitly exclude a category too.
691 TU
.ClangTidyProvider
= addClangArgs(
692 {"-Wunused"}, "clang-diagnostic-*,-clang-diagnostic-unused-function");
693 EXPECT_THAT(TU
.build().getDiagnostics(), IsEmpty());
695 // Without the exact check specified, the warnings are not enabled.
696 TU
.ClangTidyProvider
= addClangArgs({"-Wunused"}, "clang-diagnostic-unused");
697 EXPECT_THAT(TU
.build().getDiagnostics(), IsEmpty());
699 // We don't respect other args.
700 TU
.ClangTidyProvider
= addClangArgs({"-Wunused", "-Dfoo=bar"},
701 "clang-diagnostic-unused-function");
702 EXPECT_THAT(TU
.build().getDiagnostics(), ElementsAre(UnusedFooWarning
))
703 << "Not unused function 'bar'!";
705 // -Werror doesn't apply to warnings enabled by clang-tidy extra args.
706 TU
.ExtraArgs
= {"-Werror"};
707 TU
.ClangTidyProvider
=
708 addClangArgs({"-Wunused"}, "clang-diagnostic-unused-function");
709 EXPECT_THAT(TU
.build().getDiagnostics(),
710 ElementsAre(diagSeverity(DiagnosticsEngine::Warning
)));
712 // But clang-tidy extra args won't *downgrade* errors to warnings either.
713 TU
.ExtraArgs
= {"-Wunused", "-Werror"};
714 TU
.ClangTidyProvider
=
715 addClangArgs({"-Wunused"}, "clang-diagnostic-unused-function");
716 EXPECT_THAT(TU
.build().getDiagnostics(),
717 ElementsAre(diagSeverity(DiagnosticsEngine::Error
)));
719 // FIXME: we're erroneously downgrading the whole group, this should be Error.
720 TU
.ExtraArgs
= {"-Wunused-function", "-Werror"};
721 TU
.ClangTidyProvider
=
722 addClangArgs({"-Wunused"}, "clang-diagnostic-unused-label");
723 EXPECT_THAT(TU
.build().getDiagnostics(),
724 ElementsAre(diagSeverity(DiagnosticsEngine::Warning
)));
726 // This looks silly, but it's the typical result if a warning is enabled by a
727 // high-level .clang-tidy file and disabled by a low-level one.
729 TU
.ClangTidyProvider
= addClangArgs({"-Wunused", "-Wno-unused"},
730 "clang-diagnostic-unused-function");
731 EXPECT_THAT(TU
.build().getDiagnostics(), IsEmpty());
733 // Overriding only works in the proper order.
734 TU
.ClangTidyProvider
=
735 addClangArgs({"-Wunused"}, {"clang-diagnostic-unused-function"});
736 EXPECT_THAT(TU
.build().getDiagnostics(), SizeIs(1));
738 // More specific vs less-specific: match clang behavior
739 TU
.ClangTidyProvider
= addClangArgs({"-Wunused", "-Wno-unused-function"},
740 {"clang-diagnostic-unused-function"});
741 EXPECT_THAT(TU
.build().getDiagnostics(), IsEmpty());
742 TU
.ClangTidyProvider
= addClangArgs({"-Wunused-function", "-Wno-unused"},
743 {"clang-diagnostic-unused-function"});
744 EXPECT_THAT(TU
.build().getDiagnostics(), IsEmpty());
746 // We do allow clang-tidy config to disable warnings from the compile
747 // command. It's unclear this is ideal, but it's hard to avoid.
748 TU
.ExtraArgs
= {"-Wunused"};
749 TU
.ClangTidyProvider
= addClangArgs({"-Wno-unused"}, {});
750 EXPECT_THAT(TU
.build().getDiagnostics(), IsEmpty());
753 TEST(DiagnosticTest
, LongFixMessages
) {
754 // We limit the size of printed code.
755 Annotations
Source(R
"cpp(
758 int somereallyreallyreallyreallyreallyreallyreallyreallylongidentifier;
759 [[omereallyreallyreallyreallyreallyreallyreallyreallylongidentifier]]= 10;
762 TestTU TU
= TestTU::withCode(Source
.code());
764 TU
.build().getDiagnostics(),
765 ElementsAre(withFix(Fix(
767 "somereallyreallyreallyreallyreallyreallyreallyreallylongidentifier",
768 "change 'omereallyreallyreallyreallyreallyreallyreallyreall…' to "
769 "'somereallyreallyreallyreallyreallyreallyreallyreal…'"))));
770 // Only show changes up to a first newline.
771 Source
= Annotations(R
"cpp(
776 n]] = 10; // error-ok
779 TU
.Code
= std::string(Source
.code());
780 EXPECT_THAT(TU
.build().getDiagnostics(),
782 Fix(Source
.range(), "ident", "change 'ide\\…' to 'ident'"))));
785 TEST(DiagnosticTest
, NewLineFixMessage
) {
786 Annotations
Source("int a;[[]]");
787 TestTU TU
= TestTU::withCode(Source
.code());
788 TU
.ExtraArgs
= {"-Wnewline-eof"};
790 TU
.build().getDiagnostics(),
791 ElementsAre(withFix((Fix(Source
.range(), "\n", "insert '\\n'")))));
794 TEST(DiagnosticTest
, ClangTidySuppressionCommentTrumpsWarningAsError
) {
795 Annotations
Main(R
"cpp(
798 double f = [[8]] / i; // NOLINT
801 TestTU TU
= TestTU::withCode(Main
.code());
802 TU
.ClangTidyProvider
=
803 addTidyChecks("bugprone-integer-division", "bugprone-integer-division");
804 EXPECT_THAT(TU
.build().getDiagnostics(), UnorderedElementsAre());
807 TEST(DiagnosticTest
, ClangTidyNoLiteralDataInMacroToken
) {
808 Annotations
Main(R
"cpp(
810 using pthread_t = int;
811 int pthread_kill(pthread_t thread, int sig);
814 return pthread_kill(thread, 0);
817 TestTU TU
= TestTU::withCode(Main
.code());
818 TU
.ClangTidyProvider
= addTidyChecks("bugprone-bad-signal-to-kill-thread");
819 EXPECT_THAT(TU
.build().getDiagnostics(), UnorderedElementsAre()); // no-crash
822 TEST(DiagnosticTest
, ElseAfterReturnRange
) {
823 Annotations
Main(R
"cpp(
827 } [[else]] if (cond == 2) {
833 TestTU TU
= TestTU::withCode(Main
.code());
834 TU
.ClangTidyProvider
= addTidyChecks("llvm-else-after-return");
835 EXPECT_THAT(TU
.build().getDiagnostics(),
836 ifTidyChecks(ElementsAre(
837 Diag(Main
.range(), "do not use 'else' after 'return'"))));
840 TEST(DiagnosticTest
, ClangTidySelfContainedDiags
) {
841 Annotations
Main(R
"cpp($MathHeader[[]]
849 void InitVariables() {
850 float $C[[C]]$CFix[[]];
851 double $D[[D]]$DFix[[]];
854 TestTU TU
= TestTU::withCode(Main
.code());
855 TU
.ClangTidyProvider
=
856 addTidyChecks("cppcoreguidelines-prefer-member-initializer,"
857 "cppcoreguidelines-init-variables");
858 clangd::Fix ExpectedAFix
;
859 ExpectedAFix
.Message
=
860 "'A' should be initialized in a member initializer of the constructor";
861 ExpectedAFix
.Edits
.push_back(TextEdit
{Main
.range("Fix"), " : A(1)"});
862 ExpectedAFix
.Edits
.push_back(TextEdit
{Main
.range("A"), ""});
864 // When invoking clang-tidy normally, this code would produce `, B(1)` as the
865 // fix the `B` member, as it would think its already included the ` : ` from
866 // the previous `A` fix.
867 clangd::Fix ExpectedBFix
;
868 ExpectedBFix
.Message
=
869 "'B' should be initialized in a member initializer of the constructor";
870 ExpectedBFix
.Edits
.push_back(TextEdit
{Main
.range("Fix"), " : B(1)"});
871 ExpectedBFix
.Edits
.push_back(TextEdit
{Main
.range("B"), ""});
873 clangd::Fix ExpectedCFix
;
874 ExpectedCFix
.Message
= "variable 'C' is not initialized";
875 ExpectedCFix
.Edits
.push_back(TextEdit
{Main
.range("CFix"), " = NAN"});
876 ExpectedCFix
.Edits
.push_back(
877 TextEdit
{Main
.range("MathHeader"), "#include <math.h>\n\n"});
879 // Again in clang-tidy only the include directive would be emitted for the
880 // first warning. However we need the include attaching for both warnings.
881 clangd::Fix ExpectedDFix
;
882 ExpectedDFix
.Message
= "variable 'D' is not initialized";
883 ExpectedDFix
.Edits
.push_back(TextEdit
{Main
.range("DFix"), " = NAN"});
884 ExpectedDFix
.Edits
.push_back(
885 TextEdit
{Main
.range("MathHeader"), "#include <math.h>\n\n"});
887 TU
.build().getDiagnostics(),
888 ifTidyChecks(UnorderedElementsAre(
889 AllOf(Diag(Main
.range("A"), "'A' should be initialized in a member "
890 "initializer of the constructor"),
891 withFix(equalToFix(ExpectedAFix
))),
892 AllOf(Diag(Main
.range("B"), "'B' should be initialized in a member "
893 "initializer of the constructor"),
894 withFix(equalToFix(ExpectedBFix
))),
895 AllOf(Diag(Main
.range("C"), "variable 'C' is not initialized"),
896 withFix(equalToFix(ExpectedCFix
))),
897 AllOf(Diag(Main
.range("D"), "variable 'D' is not initialized"),
898 withFix(equalToFix(ExpectedDFix
))))));
901 TEST(DiagnosticTest
, ClangTidySelfContainedDiagsFormatting
) {
902 Annotations
Main(R
"cpp(
905 virtual void Reset1() = 0;
906 virtual void Reset2() = 0;
908 class A : public Interface {
909 // This will be marked by clangd to use override instead of virtual
910 $virtual1[[virtual ]]void $Reset1[[Reset1]]()$override1[[]];
911 $virtual2[[virtual ]]/**/void $Reset2[[Reset2]]()$override2[[]];
914 TestTU TU
= TestTU::withCode(Main
.code());
915 TU
.ClangTidyProvider
=
916 addTidyChecks("cppcoreguidelines-explicit-virtual-functions,");
917 clangd::Fix
const ExpectedFix1
{
918 "prefer using 'override' or (rarely) 'final' "
919 "instead of 'virtual'",
920 {TextEdit
{Main
.range("override1"), " override"},
921 TextEdit
{Main
.range("virtual1"), ""}},
923 clangd::Fix
const ExpectedFix2
{
924 "prefer using 'override' or (rarely) 'final' "
925 "instead of 'virtual'",
926 {TextEdit
{Main
.range("override2"), " override"},
927 TextEdit
{Main
.range("virtual2"), ""}},
929 // Note that in the Fix we expect the "virtual" keyword and the following
930 // whitespace to be deleted
931 EXPECT_THAT(TU
.build().getDiagnostics(),
932 ifTidyChecks(UnorderedElementsAre(
933 AllOf(Diag(Main
.range("Reset1"),
934 "prefer using 'override' or (rarely) 'final' "
935 "instead of 'virtual'"),
936 withFix(equalToFix(ExpectedFix1
))),
937 AllOf(Diag(Main
.range("Reset2"),
938 "prefer using 'override' or (rarely) 'final' "
939 "instead of 'virtual'"),
940 withFix(equalToFix(ExpectedFix2
))))));
943 TEST(DiagnosticsTest
, Preprocessor
) {
944 // This looks like a preamble, but there's an #else in the middle!
946 // - the #else doesn't generate diagnostics (we had this bug)
947 // - we get diagnostics from the taken branch
948 // - we get no diagnostics from the not taken branch
949 Annotations
Test(R
"cpp(
952 int a = [[b]]; // error-ok
958 TestTU::withCode(Test
.code()).build().getDiagnostics(),
959 ElementsAre(Diag(Test
.range(), "use of undeclared identifier 'b'")));
962 TEST(DiagnosticsTest
, IgnoreVerify
) {
963 auto TU
= TestTU::withCode(R
"cpp(
964 int a; // expected-error {{}}
966 TU
.ExtraArgs
.push_back("-Xclang");
967 TU
.ExtraArgs
.push_back("-verify");
968 EXPECT_THAT(TU
.build().getDiagnostics(), IsEmpty());
971 TEST(DiagnosticTest
, IgnoreBEFilelistOptions
) {
972 auto TU
= TestTU::withCode("");
973 TU
.ExtraArgs
.push_back("-Xclang");
974 for (const auto *DisableOption
:
975 {"-fsanitize-ignorelist=null", "-fprofile-list=null",
976 "-fxray-always-instrument=null", "-fxray-never-instrument=null",
977 "-fxray-attr-list=null"}) {
978 TU
.ExtraArgs
.push_back(DisableOption
);
979 EXPECT_THAT(TU
.build().getDiagnostics(), IsEmpty());
980 TU
.ExtraArgs
.pop_back();
984 // Recursive main-file include is diagnosed, and doesn't crash.
985 TEST(DiagnosticsTest
, RecursivePreamble
) {
986 auto TU
= TestTU::withCode(R
"cpp(
987 #include "foo
.h
" // error-ok
990 TU
.Filename
= "foo.h";
991 EXPECT_THAT(TU
.build().getDiagnostics(),
992 ElementsAre(diagName("pp_including_mainfile_in_preamble")));
993 EXPECT_THAT(TU
.build().getLocalTopLevelDecls(), SizeIs(1));
996 // Recursive main-file include with #pragma once guard is OK.
997 TEST(DiagnosticsTest
, RecursivePreamblePragmaOnce
) {
998 auto TU
= TestTU::withCode(R
"cpp(
1003 TU
.Filename
= "foo.h";
1004 EXPECT_THAT(TU
.build().getDiagnostics(),
1005 Not(Contains(diagName("pp_including_mainfile_in_preamble"))));
1006 EXPECT_THAT(TU
.build().getLocalTopLevelDecls(), SizeIs(1));
1009 // Recursive main-file include with #ifndef guard should be OK.
1010 // However, it's not yet recognized (incomplete at end of preamble).
1011 TEST(DiagnosticsTest
, RecursivePreambleIfndefGuard
) {
1012 auto TU
= TestTU::withCode(R
"cpp(
1015 #include "foo
.h
" // error-ok
1019 TU
.Filename
= "foo.h";
1020 // FIXME: should be no errors here.
1021 EXPECT_THAT(TU
.build().getDiagnostics(),
1022 ElementsAre(diagName("pp_including_mainfile_in_preamble")));
1023 EXPECT_THAT(TU
.build().getLocalTopLevelDecls(), SizeIs(1));
1026 TEST(DiagnosticsTest
, PreambleWithPragmaAssumeNonnull
) {
1027 auto TU
= TestTU::withCode(R
"cpp(
1028 #pragma clang assume_nonnull begin
1030 #pragma clang assume_nonnull end
1032 auto AST
= TU
.build();
1033 EXPECT_THAT(AST
.getDiagnostics(), IsEmpty());
1034 const auto *X
= cast
<FunctionDecl
>(findDecl(AST
, "foo")).getParamDecl(0);
1035 ASSERT_TRUE(X
->getOriginalType()->getNullability() ==
1036 NullabilityKind::NonNull
);
1039 TEST(DiagnosticsTest
, PreambleHeaderWithBadPragmaAssumeNonnull
) {
1040 Annotations
Header(R
"cpp(
1041 #pragma clang assume_nonnull begin // error-ok
1044 auto TU
= TestTU::withCode(R
"cpp(
1045 #include "foo
.h
" // unterminated assume_nonnull should not affect bar.
1048 TU
.AdditionalFiles
= {{"foo.h", std::string(Header
.code())}};
1049 auto AST
= TU
.build();
1050 EXPECT_THAT(AST
.getDiagnostics(),
1051 ElementsAre(diagName("pp_eof_in_assume_nonnull")));
1052 const auto *X
= cast
<FunctionDecl
>(findDecl(AST
, "foo")).getParamDecl(0);
1053 ASSERT_TRUE(X
->getOriginalType()->getNullability() ==
1054 NullabilityKind::NonNull
);
1055 const auto *Y
= cast
<FunctionDecl
>(findDecl(AST
, "bar")).getParamDecl(0);
1056 ASSERT_FALSE(Y
->getOriginalType()->getNullability());
1059 TEST(DiagnosticsTest
, InsideMacros
) {
1060 Annotations
Test(R
"cpp(
1062 #define RET(x) return x + 10
1065 RET($foo[[0]]); // error-ok
1071 EXPECT_THAT(TestTU::withCode(Test
.code()).build().getDiagnostics(),
1072 ElementsAre(Diag(Test
.range("foo"),
1073 "cannot initialize return object of type "
1074 "'int *' with an rvalue of type 'int'"),
1075 Diag(Test
.range("bar"),
1076 "cannot initialize return object of type "
1077 "'int *' with an rvalue of type 'int'")));
1080 TEST(DiagnosticsTest
, NoFixItInMacro
) {
1081 Annotations
Test(R
"cpp(
1082 #define Define(name) void name() {}
1084 [[Define]](main) // error-ok
1086 auto TU
= TestTU::withCode(Test
.code());
1087 EXPECT_THAT(TU
.build().getDiagnostics(),
1088 ElementsAre(AllOf(Diag(Test
.range(), "'main' must return 'int'"),
1092 TEST(DiagnosticsTest
, PragmaSystemHeader
) {
1093 Annotations
Test("#pragma clang [[system_header]]\n");
1094 auto TU
= TestTU::withCode(Test
.code());
1096 TU
.build().getDiagnostics(),
1098 Diag(Test
.range(), "#pragma system_header ignored in main file"))));
1099 TU
.Filename
= "TestTU.h";
1100 EXPECT_THAT(TU
.build().getDiagnostics(), IsEmpty());
1103 TEST(ClangdTest
, MSAsm
) {
1104 // Parsing MS assembly tries to use the target MCAsmInfo, which we don't link.
1105 // We used to crash here. Now clang emits a diagnostic, which we filter out.
1106 llvm::InitializeAllTargetInfos(); // As in ClangdMain
1107 auto TU
= TestTU::withCode("void fn() { __asm { cmp cl,64 } }");
1108 TU
.ExtraArgs
= {"-fms-extensions"};
1109 EXPECT_THAT(TU
.build().getDiagnostics(), IsEmpty());
1112 TEST(DiagnosticsTest
, ToLSP
) {
1113 URIForFile MainFile
=
1114 URIForFile::canonicalize(testPath("foo/bar/main.cpp"), "");
1115 URIForFile HeaderFile
=
1116 URIForFile::canonicalize(testPath("foo/bar/header.h"), "");
1119 D
.ID
= clang::diag::err_undeclared_var_use
;
1120 D
.Tags
= {DiagnosticTag::Unnecessary
};
1121 D
.Name
= "undeclared_var_use";
1122 D
.Source
= clangd::Diag::Clang
;
1123 D
.Message
= "something terrible happened";
1124 D
.Range
= {pos(1, 2), pos(3, 4)};
1125 D
.InsideMainFile
= true;
1126 D
.Severity
= DiagnosticsEngine::Error
;
1127 D
.File
= "foo/bar/main.cpp";
1128 D
.AbsFile
= std::string(MainFile
.file());
1129 D
.OpaqueData
["test"] = "bar";
1131 clangd::Note NoteInMain
;
1132 NoteInMain
.Message
= "declared somewhere in the main file";
1133 NoteInMain
.Range
= {pos(5, 6), pos(7, 8)};
1134 NoteInMain
.Severity
= DiagnosticsEngine::Remark
;
1135 NoteInMain
.File
= "../foo/bar/main.cpp";
1136 NoteInMain
.InsideMainFile
= true;
1137 NoteInMain
.AbsFile
= std::string(MainFile
.file());
1139 D
.Notes
.push_back(NoteInMain
);
1141 clangd::Note NoteInHeader
;
1142 NoteInHeader
.Message
= "declared somewhere in the header file";
1143 NoteInHeader
.Range
= {pos(9, 10), pos(11, 12)};
1144 NoteInHeader
.Severity
= DiagnosticsEngine::Note
;
1145 NoteInHeader
.File
= "../foo/baz/header.h";
1146 NoteInHeader
.InsideMainFile
= false;
1147 NoteInHeader
.AbsFile
= std::string(HeaderFile
.file());
1148 D
.Notes
.push_back(NoteInHeader
);
1151 F
.Message
= "do something";
1152 D
.Fixes
.push_back(F
);
1154 // Diagnostics should turn into these:
1155 clangd::Diagnostic MainLSP
;
1156 MainLSP
.range
= D
.Range
;
1157 MainLSP
.severity
= getSeverity(DiagnosticsEngine::Error
);
1158 MainLSP
.code
= "undeclared_var_use";
1159 MainLSP
.source
= "clang";
1161 R
"(Something terrible happened (fix available)
1163 main.cpp:6:7: remark: declared somewhere in the main file
1165 ../foo/baz/header.h:10:11:
1166 note: declared somewhere in the header file)";
1167 MainLSP
.tags
= {DiagnosticTag::Unnecessary
};
1168 MainLSP
.data
= D
.OpaqueData
;
1170 clangd::Diagnostic NoteInMainLSP
;
1171 NoteInMainLSP
.range
= NoteInMain
.Range
;
1172 NoteInMainLSP
.severity
= getSeverity(DiagnosticsEngine::Remark
);
1173 NoteInMainLSP
.message
= R
"(Declared somewhere in the main file
1175 main.cpp:2:3: error: something terrible happened)";
1177 ClangdDiagnosticOptions Opts
;
1178 // Transform diagnostics and check the results.
1179 std::vector
<std::pair
<clangd::Diagnostic
, std::vector
<clangd::Fix
>>> LSPDiags
;
1180 toLSPDiags(D
, MainFile
, Opts
,
1181 [&](clangd::Diagnostic LSPDiag
, ArrayRef
<clangd::Fix
> Fixes
) {
1183 {std::move(LSPDiag
),
1184 std::vector
<clangd::Fix
>(Fixes
.begin(), Fixes
.end())});
1189 ElementsAre(Pair(equalToLSPDiag(MainLSP
), ElementsAre(equalToFix(F
))),
1190 Pair(equalToLSPDiag(NoteInMainLSP
), IsEmpty())));
1191 EXPECT_EQ(LSPDiags
[0].first
.code
, "undeclared_var_use");
1192 EXPECT_EQ(LSPDiags
[0].first
.source
, "clang");
1193 EXPECT_EQ(LSPDiags
[1].first
.code
, "");
1194 EXPECT_EQ(LSPDiags
[1].first
.source
, "");
1196 // Same thing, but don't flatten notes into the main list.
1198 Opts
.EmitRelatedLocations
= true;
1199 toLSPDiags(D
, MainFile
, Opts
,
1200 [&](clangd::Diagnostic LSPDiag
, ArrayRef
<clangd::Fix
> Fixes
) {
1202 {std::move(LSPDiag
),
1203 std::vector
<clangd::Fix
>(Fixes
.begin(), Fixes
.end())});
1205 MainLSP
.message
= "Something terrible happened (fix available)";
1206 DiagnosticRelatedInformation NoteInMainDRI
;
1207 NoteInMainDRI
.message
= "Declared somewhere in the main file";
1208 NoteInMainDRI
.location
.range
= NoteInMain
.Range
;
1209 NoteInMainDRI
.location
.uri
= MainFile
;
1210 MainLSP
.relatedInformation
= {NoteInMainDRI
};
1211 DiagnosticRelatedInformation NoteInHeaderDRI
;
1212 NoteInHeaderDRI
.message
= "Declared somewhere in the header file";
1213 NoteInHeaderDRI
.location
.range
= NoteInHeader
.Range
;
1214 NoteInHeaderDRI
.location
.uri
= HeaderFile
;
1215 MainLSP
.relatedInformation
= {NoteInMainDRI
, NoteInHeaderDRI
};
1216 EXPECT_THAT(LSPDiags
, ElementsAre(Pair(equalToLSPDiag(MainLSP
),
1217 ElementsAre(equalToFix(F
)))));
1220 struct SymbolWithHeader
{
1222 std::string DeclaringFile
;
1223 std::string IncludeHeader
;
1226 std::unique_ptr
<SymbolIndex
>
1227 buildIndexWithSymbol(llvm::ArrayRef
<SymbolWithHeader
> Syms
) {
1228 SymbolSlab::Builder Slab
;
1229 for (const auto &S
: Syms
) {
1230 Symbol Sym
= cls(S
.QName
);
1231 Sym
.Flags
|= Symbol::IndexedForCodeCompletion
;
1232 Sym
.CanonicalDeclaration
.FileURI
= S
.DeclaringFile
.c_str();
1233 Sym
.Definition
.FileURI
= S
.DeclaringFile
.c_str();
1234 Sym
.IncludeHeaders
.emplace_back(S
.IncludeHeader
, 1, Symbol::Include
);
1237 return MemIndex::build(std::move(Slab
).build(), RefSlab(), RelationSlab());
1240 TEST(IncludeFixerTest
, IncompleteType
) {
1241 auto TU
= TestTU::withHeaderCode("namespace ns { class X; } ns::X *x;");
1242 TU
.ExtraArgs
.push_back("-std=c++20");
1243 auto Index
= buildIndexWithSymbol(
1244 {SymbolWithHeader
{"ns::X", "unittest:///x.h", "\"x.h\""}});
1245 TU
.ExternalIndex
= Index
.get();
1247 std::vector
<std::pair
<llvm::StringRef
, llvm::StringRef
>> Tests
{
1248 {"incomplete_nested_name_spec", "[[ns::X::]]Nested n;"},
1249 {"incomplete_base_class", "class Y : [[ns::X]] {};"},
1250 {"incomplete_member_access", "auto i = x[[->]]f();"},
1251 {"incomplete_type", "auto& [[[]]m] = *x;"},
1252 {"init_incomplete_type",
1253 "struct C { static int f(ns::X&); }; int i = C::f([[{]]});"},
1254 {"bad_cast_incomplete", "auto a = [[static_cast]]<ns::X>(0);"},
1255 {"template_nontype_parm_incomplete", "template <ns::X [[foo]]> int a;"},
1256 {"typecheck_decl_incomplete_type", "ns::X [[var]];"},
1257 {"typecheck_incomplete_tag", "auto i = [[(*x)]]->f();"},
1258 {"typecheck_nonviable_condition_incomplete",
1259 "struct A { operator ns::X(); } a; const ns::X &[[b]] = a;"},
1260 {"invalid_incomplete_type_use", "auto var = [[ns::X()]];"},
1261 {"sizeof_alignof_incomplete_or_sizeless_type",
1262 "auto s = [[sizeof]](ns::X);"},
1263 {"for_range_incomplete_type", "void foo() { for (auto i : [[*]]x ) {} }"},
1264 {"func_def_incomplete_result", "ns::X [[func]] () {}"},
1265 {"field_incomplete_or_sizeless", "class M { ns::X [[member]]; };"},
1266 {"array_incomplete_or_sizeless_type", "auto s = [[(ns::X[]){}]];"},
1267 {"call_incomplete_return", "ns::X f(); auto fp = &f; auto z = [[fp()]];"},
1268 {"call_function_incomplete_return", "ns::X foo(); auto a = [[foo()]];"},
1269 {"call_incomplete_argument", "int m(ns::X); int i = m([[*x]]);"},
1270 {"switch_incomplete_class_type", "void a() { [[switch]](*x) {} }"},
1271 {"delete_incomplete_class_type", "void f() { [[delete]] *x; }"},
1272 {"-Wdelete-incomplete", "void f() { [[delete]] x; }"},
1273 {"dereference_incomplete_type",
1274 R
"cpp(void f() { asm("" : "=r
"([[*]]x)::); })cpp"},
1276 for (auto Case
: Tests
) {
1277 Annotations
Main(Case
.second
);
1278 TU
.Code
= Main
.code().str() + "\n // error-ok";
1280 TU
.build().getDiagnostics(),
1281 ElementsAre(AllOf(diagName(Case
.first
), hasRange(Main
.range()),
1282 withFix(Fix(Range
{}, "#include \"x.h\"\n",
1283 "Include \"x.h\" for symbol ns::X")))))
1288 TEST(IncludeFixerTest
, IncompleteEnum
) {
1289 Symbol Sym
= enm("X");
1290 Sym
.Flags
|= Symbol::IndexedForCodeCompletion
;
1291 Sym
.CanonicalDeclaration
.FileURI
= Sym
.Definition
.FileURI
= "unittest:///x.h";
1292 Sym
.IncludeHeaders
.emplace_back("\"x.h\"", 1, Symbol::Include
);
1293 SymbolSlab::Builder Slab
;
1296 MemIndex::build(std::move(Slab
).build(), RefSlab(), RelationSlab());
1299 TU
.ExternalIndex
= Index
.get();
1300 TU
.ExtraArgs
.push_back("-std=c++20");
1301 TU
.ExtraArgs
.push_back("-fno-ms-compatibility"); // else incomplete enum is OK
1303 std::vector
<std::pair
<llvm::StringRef
, llvm::StringRef
>> Tests
{
1304 {"incomplete_enum", "enum class X : int; using enum [[X]];"},
1305 {"underlying_type_of_incomplete_enum",
1306 "[[__underlying_type]](enum X) i;"},
1308 for (auto Case
: Tests
) {
1309 Annotations
Main(Case
.second
);
1310 TU
.Code
= Main
.code().str() + "\n // error-ok";
1311 EXPECT_THAT(TU
.build().getDiagnostics(),
1312 Contains(AllOf(diagName(Case
.first
), hasRange(Main
.range()),
1313 withFix(Fix(Range
{}, "#include \"x.h\"\n",
1314 "Include \"x.h\" for symbol X")))))
1319 TEST(IncludeFixerTest
, NoSuggestIncludeWhenNoDefinitionInHeader
) {
1320 Annotations
Test(R
"cpp(// error-ok
1321 $insert[[]]namespace ns {
1324 class Y : $base[[public ns::X]] {};
1330 auto TU
= TestTU::withCode(Test
.code());
1331 Symbol Sym
= cls("ns::X");
1332 Sym
.Flags
|= Symbol::IndexedForCodeCompletion
;
1333 Sym
.CanonicalDeclaration
.FileURI
= "unittest:///x.h";
1334 Sym
.Definition
.FileURI
= "unittest:///x.cc";
1335 Sym
.IncludeHeaders
.emplace_back("\"x.h\"", 1, Symbol::Include
);
1337 SymbolSlab::Builder Slab
;
1340 MemIndex::build(std::move(Slab
).build(), RefSlab(), RelationSlab());
1341 TU
.ExternalIndex
= Index
.get();
1343 EXPECT_THAT(TU
.build().getDiagnostics(),
1344 UnorderedElementsAre(
1345 Diag(Test
.range("base"), "base class has incomplete type"),
1346 Diag(Test
.range("access"),
1347 "member access into incomplete type 'ns::X'")));
1350 TEST(IncludeFixerTest
, Typo
) {
1351 Annotations
Test(R
"cpp(// error-ok
1352 $insert[[]]namespace ns {
1354 $unqualified1[[X]] x;
1355 // No fix if the unresolved type is used as specifier. (ns::)X::Nested will be
1356 // considered the unresolved type.
1357 $unqualified2[[X]]::Nested n;
1359 struct S : $base[[X]] {};
1362 ns::$qualified1[[X]] x; // ns:: is valid.
1363 ns::$qualified2[[X]](); // Error: no member in namespace
1365 ::$global[[Global]] glob;
1367 using Type = ns::$template[[Foo]]<int>;
1369 auto TU
= TestTU::withCode(Test
.code());
1370 auto Index
= buildIndexWithSymbol(
1371 {SymbolWithHeader
{"ns::X", "unittest:///x.h", "\"x.h\""},
1372 SymbolWithHeader
{"Global", "unittest:///global.h", "\"global.h\""},
1373 SymbolWithHeader
{"ns::Foo", "unittest:///foo.h", "\"foo.h\""}});
1374 TU
.ExternalIndex
= Index
.get();
1377 TU
.build().getDiagnostics(),
1378 UnorderedElementsAre(
1379 AllOf(Diag(Test
.range("unqualified1"), "unknown type name 'X'"),
1380 diagName("unknown_typename"),
1381 withFix(Fix(Test
.range("insert"), "#include \"x.h\"\n",
1382 "Include \"x.h\" for symbol ns::X"))),
1383 Diag(Test
.range("unqualified2"), "use of undeclared identifier 'X'"),
1384 AllOf(Diag(Test
.range("qualified1"),
1385 "no type named 'X' in namespace 'ns'"),
1386 diagName("typename_nested_not_found"),
1387 withFix(Fix(Test
.range("insert"), "#include \"x.h\"\n",
1388 "Include \"x.h\" for symbol ns::X"))),
1389 AllOf(Diag(Test
.range("qualified2"),
1390 "no member named 'X' in namespace 'ns'"),
1391 diagName("no_member"),
1392 withFix(Fix(Test
.range("insert"), "#include \"x.h\"\n",
1393 "Include \"x.h\" for symbol ns::X"))),
1394 AllOf(Diag(Test
.range("global"),
1395 "no type named 'Global' in the global namespace"),
1396 diagName("typename_nested_not_found"),
1397 withFix(Fix(Test
.range("insert"), "#include \"global.h\"\n",
1398 "Include \"global.h\" for symbol Global"))),
1399 AllOf(Diag(Test
.range("template"),
1400 "no template named 'Foo' in namespace 'ns'"),
1401 diagName("no_member_template"),
1402 withFix(Fix(Test
.range("insert"), "#include \"foo.h\"\n",
1403 "Include \"foo.h\" for symbol ns::Foo"))),
1404 AllOf(Diag(Test
.range("base"), "expected class name"),
1405 diagName("expected_class_name"),
1406 withFix(Fix(Test
.range("insert"), "#include \"x.h\"\n",
1407 "Include \"x.h\" for symbol ns::X")))));
1410 TEST(IncludeFixerTest
, TypoInMacro
) {
1411 auto TU
= TestTU::withCode(R
"cpp(// error-ok
1421 auto Index
= buildIndexWithSymbol(
1422 {SymbolWithHeader
{"X", "unittest:///x.h", "\"x.h\""},
1423 SymbolWithHeader
{"ns::X", "unittest:///ns.h", "\"x.h\""}});
1424 TU
.ExternalIndex
= Index
.get();
1425 // FIXME: -fms-compatibility (which is default on windows) breaks the
1426 // ns::X cases when the namespace is undeclared. Find out why!
1427 TU
.ExtraArgs
= {"-fno-ms-compatibility"};
1428 EXPECT_THAT(TU
.build().getDiagnostics(), Each(withFix(_
)));
1431 TEST(IncludeFixerTest
, MultipleMatchedSymbols
) {
1432 Annotations
Test(R
"cpp(// error-ok
1433 $insert[[]]namespace na {
1436 $unqualified[[X]] x;
1441 auto TU
= TestTU::withCode(Test
.code());
1442 auto Index
= buildIndexWithSymbol(
1443 {SymbolWithHeader
{"na::X", "unittest:///a.h", "\"a.h\""},
1444 SymbolWithHeader
{"na::nb::X", "unittest:///b.h", "\"b.h\""}});
1445 TU
.ExternalIndex
= Index
.get();
1447 EXPECT_THAT(TU
.build().getDiagnostics(),
1448 UnorderedElementsAre(AllOf(
1449 Diag(Test
.range("unqualified"), "unknown type name 'X'"),
1450 diagName("unknown_typename"),
1451 withFix(Fix(Test
.range("insert"), "#include \"a.h\"\n",
1452 "Include \"a.h\" for symbol na::X"),
1453 Fix(Test
.range("insert"), "#include \"b.h\"\n",
1454 "Include \"b.h\" for symbol na::nb::X")))));
1457 TEST(IncludeFixerTest
, NoCrashMemberAccess
) {
1458 Annotations
Test(R
"cpp(// error-ok
1459 struct X { int xyz; };
1460 void g() { X x; x.$[[xy]]; }
1462 auto TU
= TestTU::withCode(Test
.code());
1463 auto Index
= buildIndexWithSymbol(
1464 SymbolWithHeader
{"na::X", "unittest:///a.h", "\"a.h\""});
1465 TU
.ExternalIndex
= Index
.get();
1468 TU
.build().getDiagnostics(),
1469 UnorderedElementsAre(Diag(Test
.range(), "no member named 'xy' in 'X'")));
1472 TEST(IncludeFixerTest
, UseCachedIndexResults
) {
1473 // As index results for the identical request are cached, more than 5 fixes
1475 Annotations
Test(R
"cpp(// error-ok
1476 $insert[[]]void foo() {
1497 auto TU
= TestTU::withCode(Test
.code());
1499 buildIndexWithSymbol(SymbolWithHeader
{"X", "unittest:///a.h", "\"a.h\""});
1500 TU
.ExternalIndex
= Index
.get();
1502 auto Parsed
= TU
.build();
1503 for (const auto &D
: Parsed
.getDiagnostics()) {
1504 if (D
.Fixes
.size() != 1) {
1505 ADD_FAILURE() << "D.Fixes.size() != 1";
1508 EXPECT_EQ(D
.Fixes
[0].Message
, std::string("Include \"a.h\" for symbol X"));
1512 TEST(IncludeFixerTest
, UnresolvedNameAsSpecifier
) {
1513 Annotations
Test(R
"cpp(// error-ok
1514 $insert[[]]namespace ns {
1516 void g() { ns::$[[scope]]::X_Y(); }
1519 TU
.Code
= std::string(Test
.code());
1520 // FIXME: Figure out why this is needed and remove it, PR43662.
1521 TU
.ExtraArgs
.push_back("-fno-ms-compatibility");
1522 auto Index
= buildIndexWithSymbol(
1523 SymbolWithHeader
{"ns::scope::X_Y", "unittest:///x.h", "\"x.h\""});
1524 TU
.ExternalIndex
= Index
.get();
1527 TU
.build().getDiagnostics(),
1528 UnorderedElementsAre(
1529 AllOf(Diag(Test
.range(), "no member named 'scope' in namespace 'ns'"),
1530 diagName("no_member"),
1531 withFix(Fix(Test
.range("insert"), "#include \"x.h\"\n",
1532 "Include \"x.h\" for symbol ns::scope::X_Y")))));
1535 TEST(IncludeFixerTest
, UnresolvedSpecifierWithSemaCorrection
) {
1536 Annotations
Test(R
"cpp(// error-ok
1537 $insert[[]]namespace clang {
1539 // "clangd::" will be corrected to "clang::" by Sema.
1540 $q1[[clangd]]::$x[[X]] x;
1541 $q2[[clangd]]::$ns[[ns]]::Y y;
1546 TU
.Code
= std::string(Test
.code());
1547 // FIXME: Figure out why this is needed and remove it, PR43662.
1548 TU
.ExtraArgs
.push_back("-fno-ms-compatibility");
1549 auto Index
= buildIndexWithSymbol(
1550 {SymbolWithHeader
{"clang::clangd::X", "unittest:///x.h", "\"x.h\""},
1551 SymbolWithHeader
{"clang::clangd::ns::Y", "unittest:///y.h", "\"y.h\""}});
1552 TU
.ExternalIndex
= Index
.get();
1555 TU
.build().getDiagnostics(),
1556 UnorderedElementsAre(
1557 AllOf(Diag(Test
.range("q1"), "use of undeclared identifier 'clangd'; "
1558 "did you mean 'clang'?"),
1559 diagName("undeclared_var_use_suggest"),
1560 withFix(_
, // change clangd to clang
1561 Fix(Test
.range("insert"), "#include \"x.h\"\n",
1562 "Include \"x.h\" for symbol clang::clangd::X"))),
1563 AllOf(Diag(Test
.range("x"), "no type named 'X' in namespace 'clang'"),
1564 diagName("typename_nested_not_found"),
1565 withFix(Fix(Test
.range("insert"), "#include \"x.h\"\n",
1566 "Include \"x.h\" for symbol clang::clangd::X"))),
1568 Diag(Test
.range("q2"), "use of undeclared identifier 'clangd'; "
1569 "did you mean 'clang'?"),
1570 diagName("undeclared_var_use_suggest"),
1571 withFix(_
, // change clangd to clang
1572 Fix(Test
.range("insert"), "#include \"y.h\"\n",
1573 "Include \"y.h\" for symbol clang::clangd::ns::Y"))),
1574 AllOf(Diag(Test
.range("ns"),
1575 "no member named 'ns' in namespace 'clang'"),
1576 diagName("no_member"),
1578 Fix(Test
.range("insert"), "#include \"y.h\"\n",
1579 "Include \"y.h\" for symbol clang::clangd::ns::Y")))));
1582 TEST(IncludeFixerTest
, SpecifiedScopeIsNamespaceAlias
) {
1583 Annotations
Test(R
"cpp(// error-ok
1584 $insert[[]]namespace a {}
1590 auto TU
= TestTU::withCode(Test
.code());
1591 auto Index
= buildIndexWithSymbol(
1592 SymbolWithHeader
{"a::X", "unittest:///x.h", "\"x.h\""});
1593 TU
.ExternalIndex
= Index
.get();
1595 EXPECT_THAT(TU
.build().getDiagnostics(),
1596 UnorderedElementsAre(AllOf(
1597 Diag(Test
.range(), "no type named 'X' in namespace 'a'"),
1598 diagName("typename_nested_not_found"),
1599 withFix(Fix(Test
.range("insert"), "#include \"x.h\"\n",
1600 "Include \"x.h\" for symbol a::X")))));
1603 TEST(IncludeFixerTest
, NoCrashOnTemplateInstantiations
) {
1604 Annotations
Test(R
"cpp(
1605 template <typename T> struct Templ {
1606 template <typename U>
1607 typename U::type operator=(const U &);
1612 A() { [[a]]; /*error-ok*/ } // crash if we compute scopes lazily.
1616 auto TU
= TestTU::withCode(Test
.code());
1617 auto Index
= buildIndexWithSymbol({});
1618 TU
.ExternalIndex
= Index
.get();
1621 TU
.build().getDiagnostics(),
1622 ElementsAre(Diag(Test
.range(), "use of undeclared identifier 'a'")));
1625 TEST(IncludeFixerTest
, HeaderNamedInDiag
) {
1626 Annotations
Test(R
"cpp(
1627 $insert[[]]int main() {
1631 auto TU
= TestTU::withCode(Test
.code());
1632 TU
.ExtraArgs
= {"-xc", "-std=c99",
1633 "-Wno-error=implicit-function-declaration"};
1634 auto Index
= buildIndexWithSymbol({});
1635 TU
.ExternalIndex
= Index
.get();
1638 TU
.build().getDiagnostics(),
1640 Diag(Test
.range(), "call to undeclared library function 'printf' "
1641 "with type 'int (const char *, ...)'; ISO C99 "
1642 "and later do not support implicit function "
1644 withFix(Fix(Test
.range("insert"), "#include <stdio.h>\n",
1645 "Include <stdio.h> for symbol printf")))));
1647 TU
.ExtraArgs
= {"-xc", "-std=c89"};
1649 TU
.build().getDiagnostics(),
1651 Diag(Test
.range(), "implicitly declaring library function 'printf' "
1652 "with type 'int (const char *, ...)'"),
1653 withFix(Fix(Test
.range("insert"), "#include <stdio.h>\n",
1654 "Include <stdio.h> for symbol printf")))));
1657 TEST(IncludeFixerTest
, CImplicitFunctionDecl
) {
1658 Annotations
Test("void x() { [[foo]](); }");
1659 auto TU
= TestTU::withCode(Test
.code());
1660 TU
.Filename
= "test.c";
1661 TU
.ExtraArgs
= {"-std=c99", "-Wno-error=implicit-function-declaration"};
1663 Symbol Sym
= func("foo");
1664 Sym
.Flags
|= Symbol::IndexedForCodeCompletion
;
1665 Sym
.CanonicalDeclaration
.FileURI
= "unittest:///foo.h";
1666 Sym
.IncludeHeaders
.emplace_back("\"foo.h\"", 1, Symbol::Include
);
1668 SymbolSlab::Builder Slab
;
1671 MemIndex::build(std::move(Slab
).build(), RefSlab(), RelationSlab());
1672 TU
.ExternalIndex
= Index
.get();
1675 TU
.build().getDiagnostics(),
1678 "call to undeclared function 'foo'; ISO C99 and later do not "
1679 "support implicit function declarations"),
1680 withFix(Fix(Range
{}, "#include \"foo.h\"\n",
1681 "Include \"foo.h\" for symbol foo")))));
1683 TU
.ExtraArgs
= {"-std=c89", "-Wall"};
1684 EXPECT_THAT(TU
.build().getDiagnostics(),
1686 Diag(Test
.range(), "implicit declaration of function 'foo'"),
1687 withFix(Fix(Range
{}, "#include \"foo.h\"\n",
1688 "Include \"foo.h\" for symbol foo")))));
1691 TEST(DiagsInHeaders
, DiagInsideHeader
) {
1692 Annotations
Main(R
"cpp(
1694 void foo() {})cpp");
1695 Annotations
Header("[[no_type_spec]]; // error-ok");
1696 TestTU TU
= TestTU::withCode(Main
.code());
1697 TU
.AdditionalFiles
= {{"a.h", std::string(Header
.code())}};
1698 EXPECT_THAT(TU
.build().getDiagnostics(),
1699 UnorderedElementsAre(AllOf(
1700 Diag(Main
.range(), "in included file: a type specifier is "
1701 "required for all declarations"),
1702 withNote(Diag(Header
.range(), "error occurred here")))));
1705 TEST(DiagsInHeaders
, DiagInTransitiveInclude
) {
1706 Annotations
Main(R
"cpp(
1708 void foo() {})cpp");
1709 TestTU TU
= TestTU::withCode(Main
.code());
1710 TU
.AdditionalFiles
= {{"a.h", "#include \"b.h\""},
1711 {"b.h", "no_type_spec; // error-ok"}};
1712 EXPECT_THAT(TU
.build().getDiagnostics(),
1713 UnorderedElementsAre(Diag(Main
.range(),
1714 "in included file: a type specifier is "
1715 "required for all declarations")));
1718 TEST(DiagsInHeaders
, DiagInMultipleHeaders
) {
1719 Annotations
Main(R
"cpp(
1720 #include $a[["a
.h
"]]
1721 #include $b[["b
.h
"]]
1722 void foo() {})cpp");
1723 TestTU TU
= TestTU::withCode(Main
.code());
1724 TU
.AdditionalFiles
= {{"a.h", "no_type_spec; // error-ok"},
1725 {"b.h", "no_type_spec; // error-ok"}};
1726 EXPECT_THAT(TU
.build().getDiagnostics(),
1727 UnorderedElementsAre(
1728 Diag(Main
.range("a"), "in included file: a type specifier is "
1729 "required for all declarations"),
1730 Diag(Main
.range("b"), "in included file: a type specifier is "
1731 "required for all declarations")));
1734 TEST(DiagsInHeaders
, PreferExpansionLocation
) {
1735 Annotations
Main(R
"cpp(
1738 void foo() {})cpp");
1739 TestTU TU
= TestTU::withCode(Main
.code());
1740 TU
.AdditionalFiles
= {
1741 {"a.h", "#include \"b.h\"\n"},
1742 {"b.h", "#ifndef X\n#define X\nno_type_spec; // error-ok\n#endif"}};
1743 EXPECT_THAT(TU
.build().getDiagnostics(),
1744 Contains(Diag(Main
.range(), "in included file: a type specifier "
1745 "is required for all declarations")));
1748 TEST(DiagsInHeaders
, PreferExpansionLocationMacros
) {
1749 Annotations
Main(R
"cpp(
1754 void foo() {})cpp");
1755 TestTU TU
= TestTU::withCode(Main
.code());
1756 TU
.AdditionalFiles
= {
1757 {"a.h", "#include \"c.h\"\n"},
1758 {"b.h", "#include \"c.h\"\n"},
1759 {"c.h", "#ifndef X\n#define X\nno_type_spec; // error-ok\n#endif"}};
1760 EXPECT_THAT(TU
.build().getDiagnostics(),
1761 UnorderedElementsAre(Diag(Main
.range(),
1762 "in included file: a type specifier is "
1763 "required for all declarations")));
1766 TEST(DiagsInHeaders
, LimitDiagsOutsideMainFile
) {
1767 Annotations
Main(R
"cpp(
1770 void foo() {})cpp");
1771 TestTU TU
= TestTU::withCode(Main
.code());
1772 TU
.AdditionalFiles
= {{"a.h", "#include \"c.h\"\n"},
1773 {"b.h", "#include \"c.h\"\n"},
1777 no_type_spec_0; // error-ok
1789 EXPECT_THAT(TU
.build().getDiagnostics(),
1790 UnorderedElementsAre(Diag(Main
.range(),
1791 "in included file: a type specifier is "
1792 "required for all declarations")));
1795 TEST(DiagsInHeaders
, OnlyErrorOrFatal
) {
1796 Annotations
Main(R
"cpp(
1798 void foo() {})cpp");
1799 Annotations
Header(R
"cpp(
1800 [[no_type_spec]]; // error-ok
1802 TestTU TU
= TestTU::withCode(Main
.code());
1803 TU
.AdditionalFiles
= {{"a.h", std::string(Header
.code())}};
1804 EXPECT_THAT(TU
.build().getDiagnostics(),
1805 UnorderedElementsAre(AllOf(
1806 Diag(Main
.range(), "in included file: a type specifier is "
1807 "required for all declarations"),
1808 withNote(Diag(Header
.range(), "error occurred here")))));
1811 TEST(DiagsInHeaders
, OnlyDefaultErrorOrFatal
) {
1812 Annotations
Main(R
"cpp(
1813 #include [["a
.h
"]] // get unused "foo
" warning when building preamble.
1815 Annotations
Header(R
"cpp(
1816 namespace { void foo() {} }
1817 void func() {foo();} ;)cpp");
1818 TestTU TU
= TestTU::withCode(Main
.code());
1819 TU
.AdditionalFiles
= {{"a.h", std::string(Header
.code())}};
1820 // promote warnings to errors.
1821 TU
.ExtraArgs
= {"-Werror", "-Wunused"};
1822 EXPECT_THAT(TU
.build().getDiagnostics(), IsEmpty());
1825 TEST(DiagsInHeaders
, FromNonWrittenSources
) {
1826 Annotations
Main(R
"cpp(
1828 void foo() {})cpp");
1829 Annotations
Header(R
"cpp(
1831 int b = [[FOO]]; // error-ok)cpp");
1832 TestTU TU
= TestTU::withCode(Main
.code());
1833 TU
.AdditionalFiles
= {{"a.h", std::string(Header
.code())}};
1834 TU
.ExtraArgs
= {"-DFOO=NOOO"};
1835 EXPECT_THAT(TU
.build().getDiagnostics(),
1836 UnorderedElementsAre(AllOf(
1838 "in included file: use of undeclared identifier 'NOOO'"),
1839 withNote(Diag(Header
.range(), "error occurred here")))));
1842 TEST(DiagsInHeaders
, ErrorFromMacroExpansion
) {
1843 Annotations
Main(R
"cpp(
1848 Annotations
Header(R
"cpp(
1851 TestTU TU
= TestTU::withCode(Main
.code());
1852 TU
.AdditionalFiles
= {{"a.h", std::string(Header
.code())}};
1853 EXPECT_THAT(TU
.build().getDiagnostics(),
1854 UnorderedElementsAre(
1855 Diag(Main
.range(), "in included file: use of undeclared "
1856 "identifier 'foo'; did you mean 'fo'?")));
1859 TEST(DiagsInHeaders
, ErrorFromMacroArgument
) {
1860 Annotations
Main(R
"cpp(
1865 Annotations
Header(R
"cpp(
1868 TestTU TU
= TestTU::withCode(Main
.code());
1869 TU
.AdditionalFiles
= {{"a.h", std::string(Header
.code())}};
1870 EXPECT_THAT(TU
.build().getDiagnostics(),
1871 UnorderedElementsAre(
1872 Diag(Main
.range(), "in included file: use of undeclared "
1873 "identifier 'foo'; did you mean 'fo'?")));
1876 TEST(IgnoreDiags
, FromNonWrittenInclude
) {
1878 TU
.ExtraArgs
.push_back("--include=a.h");
1879 TU
.AdditionalFiles
= {{"a.h", "void main();"}};
1880 // The diagnostic "main must return int" is from the header, we don't attempt
1881 // to render it in the main file as there is no written location there.
1882 EXPECT_THAT(TU
.build().getDiagnostics(), UnorderedElementsAre());
1885 TEST(ToLSPDiag
, RangeIsInMain
) {
1886 ClangdDiagnosticOptions Opts
;
1888 D
.Range
= {pos(1, 2), pos(3, 4)};
1889 D
.Notes
.emplace_back();
1890 Note
&N
= D
.Notes
.back();
1891 N
.Range
= {pos(2, 3), pos(3, 4)};
1893 D
.InsideMainFile
= true;
1894 N
.InsideMainFile
= false;
1895 toLSPDiags(D
, {}, Opts
,
1896 [&](clangd::Diagnostic LSPDiag
, ArrayRef
<clangd::Fix
>) {
1897 EXPECT_EQ(LSPDiag
.range
, D
.Range
);
1900 D
.InsideMainFile
= false;
1901 N
.InsideMainFile
= true;
1902 toLSPDiags(D
, {}, Opts
,
1903 [&](clangd::Diagnostic LSPDiag
, ArrayRef
<clangd::Fix
>) {
1904 EXPECT_EQ(LSPDiag
.range
, N
.Range
);
1908 TEST(ParsedASTTest
, ModuleSawDiag
) {
1911 auto AST
= TU
.build();
1913 EXPECT_THAT(AST
.getDiagnostics(),
1914 testing::Contains(Diag(Code
.range(), KDiagMsg
.str())));
1918 TEST(Preamble
, EndsOnNonEmptyLine
) {
1920 TU
.ExtraArgs
= {"-Wnewline-eof"};
1923 TU
.Code
= "#define FOO\n void bar();\n";
1924 auto AST
= TU
.build();
1925 EXPECT_THAT(AST
.getDiagnostics(), IsEmpty());
1928 Annotations
Code("#define FOO[[]]");
1929 TU
.Code
= Code
.code().str();
1930 auto AST
= TU
.build();
1932 AST
.getDiagnostics(),
1933 testing::Contains(Diag(Code
.range(), "no newline at end of file")));
1937 TEST(Diagnostics
, Tags
) {
1939 TU
.ExtraArgs
= {"-Wunused", "-Wdeprecated"};
1940 Annotations
Test(R
"cpp(
1941 void bar() __attribute__((deprecated));
1944 $deprecated[[bar]]();
1946 TU
.Code
= Test
.code().str();
1947 EXPECT_THAT(TU
.build().getDiagnostics(),
1948 UnorderedElementsAre(
1949 AllOf(Diag(Test
.range("unused"), "unused variable 'x'"),
1950 withTag(DiagnosticTag::Unnecessary
)),
1951 AllOf(Diag(Test
.range("deprecated"), "'bar' is deprecated"),
1952 withTag(DiagnosticTag::Deprecated
))));
1954 Test
= Annotations(R
"cpp(
1955 $typedef[[typedef int INT]];
1957 TU
.Code
= Test
.code();
1958 TU
.ClangTidyProvider
= addTidyChecks("modernize-use-using");
1960 TU
.build().getDiagnostics(),
1961 ifTidyChecks(UnorderedElementsAre(
1962 AllOf(Diag(Test
.range("typedef"), "use 'using' instead of 'typedef'"),
1963 withTag(DiagnosticTag::Deprecated
)))));
1966 TEST(Diagnostics
, DeprecatedDiagsAreHints
) {
1967 ClangdDiagnosticOptions Opts
;
1968 std::optional
<clangd::Diagnostic
> Diag
;
1970 D
.Range
= {pos(1, 2), pos(3, 4)};
1971 D
.InsideMainFile
= true;
1973 // Downgrade warnings with deprecated tags to remark.
1974 D
.Tags
= {Deprecated
};
1975 D
.Severity
= DiagnosticsEngine::Warning
;
1976 toLSPDiags(D
, {}, Opts
,
1977 [&](clangd::Diagnostic LSPDiag
, ArrayRef
<clangd::Fix
>) {
1978 Diag
= std::move(LSPDiag
);
1980 EXPECT_EQ(Diag
->severity
, getSeverity(DiagnosticsEngine::Remark
));
1984 D
.Severity
= DiagnosticsEngine::Error
;
1985 toLSPDiags(D
, {}, Opts
,
1986 [&](clangd::Diagnostic LSPDiag
, ArrayRef
<clangd::Fix
>) {
1987 Diag
= std::move(LSPDiag
);
1989 EXPECT_EQ(Diag
->severity
, getSeverity(DiagnosticsEngine::Error
));
1992 // No-op without tag.
1994 D
.Severity
= DiagnosticsEngine::Warning
;
1995 toLSPDiags(D
, {}, Opts
,
1996 [&](clangd::Diagnostic LSPDiag
, ArrayRef
<clangd::Fix
>) {
1997 Diag
= std::move(LSPDiag
);
1999 EXPECT_EQ(Diag
->severity
, getSeverity(DiagnosticsEngine::Warning
));
2002 TEST(DiagnosticsTest
, IncludeCleaner
) {
2003 Annotations
Test(R
"cpp(
2004 $fix[[ $diag[[#include "unused
.h
"]]
2010 #include <system_header.h>
2017 TU
.Code
= Test
.code().str();
2018 TU
.AdditionalFiles
["unused.h"] = R
"cpp(
2022 TU
.AdditionalFiles
["used.h"] = R
"cpp(
2026 TU
.AdditionalFiles
["ignore.h"] = R
"cpp(
2030 TU
.AdditionalFiles
["system/system_header.h"] = "";
2031 TU
.ExtraArgs
= {"-isystem" + testPath("system")};
2033 Cfg
.Diagnostics
.UnusedIncludes
= Config::IncludesPolicy::Strict
;
2035 Cfg
.Diagnostics
.Includes
.IgnoreHeader
.emplace_back(
2036 [](llvm::StringRef Header
) { return Header
.ends_with("ignore.h"); });
2037 WithContextValue
WithCfg(Config::Key
, std::move(Cfg
));
2038 auto AST
= TU
.build();
2040 AST
.getDiagnostics(),
2042 Diag(Test
.range("diag"),
2043 "included header unused.h is not used directly"),
2044 withTag(DiagnosticTag::Unnecessary
), diagSource(Diag::Clangd
),
2045 withFix(Fix(Test
.range("fix"), "", "remove #include directive")))));
2046 auto &Diag
= AST
.getDiagnostics().front();
2047 EXPECT_THAT(getDiagnosticDocURI(Diag
.Source
, Diag
.ID
, Diag
.Name
),
2048 llvm::ValueIs(Not(IsEmpty())));
2049 Cfg
.Diagnostics
.SuppressAll
= true;
2050 WithContextValue
SuppressAllWithCfg(Config::Key
, std::move(Cfg
));
2051 EXPECT_THAT(TU
.build().getDiagnostics(), IsEmpty());
2052 Cfg
.Diagnostics
.SuppressAll
= false;
2053 Cfg
.Diagnostics
.Suppress
= {"unused-includes"};
2054 WithContextValue
SuppressFilterWithCfg(Config::Key
, std::move(Cfg
));
2055 EXPECT_THAT(TU
.build().getDiagnostics(), IsEmpty());
2058 TEST(DiagnosticsTest
, FixItFromHeader
) {
2059 llvm::StringLiteral
Header(R
"cpp(
2061 void foo(int *, int);)cpp");
2062 Annotations
Source(R
"cpp(
2066 $diag[[foo]]($fix[[]]x, 1);
2069 TU
.Code
= Source
.code().str();
2070 TU
.HeaderCode
= Header
.str();
2072 TU
.build().getDiagnostics(),
2073 UnorderedElementsAre(AllOf(
2074 Diag(Source
.range("diag"), "no matching function for call to 'foo'"),
2075 withFix(Fix(Source
.range("fix"), "&",
2076 "candidate function not viable: no known conversion from "
2077 "'int' to 'int *' for 1st argument; take the address of "
2078 "the argument with &")))));
2081 TEST(DiagnosticsTest
, UnusedInHeader
) {
2082 // Clang diagnoses unused static inline functions outside headers.
2083 auto TU
= TestTU::withCode("static inline void foo(void) {}");
2084 TU
.ExtraArgs
.push_back("-Wunused-function");
2085 TU
.Filename
= "test.c";
2086 EXPECT_THAT(TU
.build().getDiagnostics(),
2087 ElementsAre(withID(diag::warn_unused_function
)));
2088 // Sema should recognize a *.h file open in clangd as a header.
2089 // https://github.com/clangd/vscode-clangd/issues/360
2090 TU
.Filename
= "test.h";
2091 EXPECT_THAT(TU
.build().getDiagnostics(), IsEmpty());
2095 } // namespace clangd
2096 } // namespace clang