9#include "../clang-tidy/ClangTidyOptions.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"
52using ::testing::AllOf;
53using ::testing::Contains;
55using ::testing::ElementsAre;
56using ::testing::Field;
57using ::testing::IsEmpty;
60using ::testing::SizeIs;
61using ::testing::UnorderedElementsAre;
63::testing::Matcher<const Diag &> withFix(::testing::Matcher<Fix> FixMatcher) {
67::testing::Matcher<const Diag &> withFix(::testing::Matcher<Fix> FixMatcher1,
68 ::testing::Matcher<Fix> FixMatcher2) {
72::testing::Matcher<const Diag &> withID(
unsigned ID) {
75::testing::Matcher<const Diag &>
76withNote(::testing::Matcher<Note> NoteMatcher) {
80::testing::Matcher<const Diag &>
81withNote(::testing::Matcher<Note> NoteMatcher1,
82 ::testing::Matcher<Note> NoteMatcher2) {
83 return Field(&
Diag::Notes, UnorderedElementsAre(NoteMatcher1, NoteMatcher2));
86::testing::Matcher<const Diag &>
87withTag(::testing::Matcher<DiagnosticTag> TagMatcher) {
94 "Diag at " + llvm::to_string(
Range) +
" = [" + Message +
"]") {
95 return arg.Range ==
Range && arg.Message == Message;
98MATCHER_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;
105MATCHER_P(fixMessage, Message,
"") {
return arg.Message == Message; }
108 "LSP diagnostic " + llvm::to_string(LSPDiag)) {
110 *result_listener << llvm::formatv(
"expected:\n{0:2}\ngot\n{1:2}",
118MATCHER_P(diagSource, S,
"") {
return arg.Source == S; }
119MATCHER_P(diagName, N,
"") {
return arg.Name == N; }
120MATCHER_P(diagSeverity, S,
"") {
return arg.Severity == S; }
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)
136Position pos(
int Line,
int Character) {
148::testing::Matcher<std::vector<clangd::Diag>>
149ifTidyChecks(::testing::Matcher<std::vector<clangd::Diag>> M) {
150 if (!CLANGD_TIDY_CHECKS)
155TEST(DiagnosticsTest, DiagnosticRanges) {
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]])]]();
180 TU.build().getDiagnostics(),
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 '*'"))),
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'")),
194 withNote(
Diag(Test.range(
"decl"),
"'foo' declared here"))),
198 AllOf(
Diag(Test.range(
"semicolon"),
"expected ';' after expression"),
199 withFix(
Fix(Test.range(
"semicolon"),
";",
"insert ';'"))),
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'")))));
219TEST(DiagnosticsTest, WSwitch) {
228 TU.ExtraArgs = {
"-Wswitch"};
229 EXPECT_THAT(TU.build().getDiagnostics(),
230 ElementsAre(
Diag(Test.range(),
231 "enumeration value 'X' not handled in switch")));
234TEST(DiagnosticsTest, FlagsMatter) {
235 Annotations Test(
"[[void]] main() {} // error-ok");
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'")))));
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'")))));
250TEST(DiagnosticsTest, DiagnosticPreamble) {
252 #include $[["not-found.h"]] // error-ok
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"))));
262TEST(DiagnosticsTest, DeduplicatedClangTidyDiagnostics) {
264 float foo = [[0.1f]];
269 TU.ClangTidyProvider =
addTidyChecks(
"readability-uppercase-literal-suffix,"
270 "hicpp-uppercase-literal-suffix");
273 TU.build().getDiagnostics(),
274 ifTidyChecks(UnorderedElementsAre(::testing::AllOf(
276 "floating point literal has suffix 'f', which is not uppercase"),
289 TU.Code = std::string(Test.code());
293 TU.build().getDiagnostics(),
294 ifTidyChecks(UnorderedElementsAre(::testing::AllOf(
296 "floating point literal has suffix 'f', which is not uppercase"),
300TEST(DiagnosticsTest, ClangTidy) {
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
321 TU.HeaderFilename =
"assert.h";
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"),
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 "
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"),
351 diagName(
"modernize-use-trailing-return-type"),
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"))));
361TEST(DiagnosticsTest, ClangTidyEOF) {
365 #include "a.h")cpp");
368 TU.ExtraArgs = {
"-isystem."};
369 TU.AdditionalFiles[
"a.h"] = TU.AdditionalFiles[
"b.h"] =
"";
372 TU.build().getDiagnostics(),
373 ifTidyChecks(Contains(
374 AllOf(
Diag(Test.range(),
"#includes are not sorted properly"),
378TEST(DiagnosticTest, TemplatesInHeaders) {
381 Derived<int> [[y]]; // error-ok
384 template <typename T>
385 struct Derived : [[T]] {};
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")))));
398TEST(DiagnosticTest, MakeUnique) {
402 struct S { S(char*); };
403 auto x = std::[[make_unique]]<S>(42); // error-ok
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'")));
423TEST(DiagnosticTest, CoroutineInHeader) {
424 StringRef CoroutineH = R
"cpp(
426template <class Ret, typename... T>
427struct coroutine_traits { using promise_type = typename Ret::promise_type; };
429template <class Promise = void>
430struct coroutine_handle {
431 static coroutine_handle from_address(void *) noexcept;
432 static coroutine_handle from_promise(Promise &promise);
433 constexpr void* address() const noexcept;
436struct 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"
453template <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);
465Gen<int> foo_coro(int b) { co_return b; }
470Gen<int> $[[bar_coro]](int b) { return foo_coro(b); }
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())));
479TEST(DiagnosticTest, MakeShared) {
483 struct S { S(char*); };
484 auto x = std::[[make_shared]]<S>(42); // error-ok
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'")));
505TEST(DiagnosticTest, NoMultipleDiagnosticInFlight) {
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) {
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"),
532TEST(DiagnosticTest, RespectsDiagnosticConfig) {
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")));
549 EXPECT_THAT(TU.build().getDiagnostics(),
550 ElementsAre(
Diag(Main.range(),
551 "use of undeclared identifier 'unknown'")));
554TEST(DiagnosticTest, RespectsDiagnosticConfigInHeader) {
556 int x = "42"; // error-ok
559 #include "header.hpp"
562 TU.AdditionalFiles[
"header.hpp"] = std::string(Header.code());
566 EXPECT_THAT(TU.build().getDiagnostics(), IsEmpty());
569TEST(DiagnosticTest, ClangTidySuppressionComment) {
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.
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"),
598 diagName(
"bugprone-integer-division")))));
601TEST(DiagnosticTest, ClangTidySystemMacro) {
606 double x = $inline[[8]] / i;
607 double y = $user[[DIVIDE_USER]](i);
608 double z = DIVIDE_SYS(i);
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";
626 EXPECT_THAT(TU.build().getDiagnostics(),
628 UnorderedElementsAre(
Diag(Main.range(
"inline"), BadDivision),
629 Diag(Main.range(
"user"), BadDivision))));
632TEST(DiagnosticTest, ClangTidyWarningAsError) {
636 double f = [[8]] / i; // error-ok
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"),
648 diagSeverity(DiagnosticsEngine::Error)))));
651TidyProvider addClangArgs(std::vector<llvm::StringRef> ExtraArgs,
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);
664TEST(DiagnosticTest, ClangTidyEnablesClangWarning) {
666 static void [[foo]]() {}
670 auto UnusedFooWarning =
671 AllOf(
Diag(Main.range(),
"unused function 'foo'"),
672 diagName(
"-Wunused-function"), diagSource(
Diag::Clang),
673 diagSeverity(DiagnosticsEngine::Warning));
676 EXPECT_THAT(TU.build().getDiagnostics(), IsEmpty());
680 TU.ClangTidyProvider =
681 addClangArgs({
"-Wunused"},
"clang-diagnostic-unused-function");
682 EXPECT_THAT(TU.build().getDiagnostics(), ElementsAre(UnusedFooWarning));
685 TU.ClangTidyProvider = addClangArgs({
"-Wunused"},
"clang-diagnostic-*");
686 EXPECT_THAT(TU.build().getDiagnostics(), ElementsAre(UnusedFooWarning));
688 TU.ClangTidyProvider = addClangArgs({
"-Wunused"},
"*");
689 EXPECT_THAT(TU.build().getDiagnostics(), Contains(UnusedFooWarning));
691 TU.ClangTidyProvider = addClangArgs(
692 {
"-Wunused"},
"clang-diagnostic-*,-clang-diagnostic-unused-function");
693 EXPECT_THAT(TU.build().getDiagnostics(), IsEmpty());
696 TU.ClangTidyProvider = addClangArgs({
"-Wunused"},
"clang-diagnostic-unused");
697 EXPECT_THAT(TU.build().getDiagnostics(), IsEmpty());
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'!";
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)));
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)));
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)));
729 TU.ClangTidyProvider = addClangArgs({
"-Wunused",
"-Wno-unused"},
730 "clang-diagnostic-unused-function");
731 EXPECT_THAT(TU.build().getDiagnostics(), IsEmpty());
734 TU.ClangTidyProvider =
735 addClangArgs({
"-Wunused"}, {
"clang-diagnostic-unused-function"});
736 EXPECT_THAT(TU.build().getDiagnostics(), SizeIs(1));
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());
748 TU.ExtraArgs = {
"-Wunused"};
749 TU.ClangTidyProvider = addClangArgs({
"-Wno-unused"}, {});
750 EXPECT_THAT(TU.build().getDiagnostics(), IsEmpty());
752 TU.ExtraArgs = {
"-Wno-unused"};
753 TU.ClangTidyProvider = addClangArgs({
"-Wunused"}, {
"-*, clang-diagnostic-*"});
754 EXPECT_THAT(TU.build().getDiagnostics(), SizeIs(1));
757TEST(DiagnosticTest, LongFixMessages) {
762 int somereallyreallyreallyreallyreallyreallyreallyreallylongidentifier;
763 [[omereallyreallyreallyreallyreallyreallyreallyreallylongidentifier]]= 10;
768 TU.build().getDiagnostics(),
769 ElementsAre(withFix(Fix(
771 "somereallyreallyreallyreallyreallyreallyreallyreallylongidentifier",
772 "change 'omereallyreallyreallyreallyreallyreallyreallyreall…' to "
773 "'somereallyreallyreallyreallyreallyreallyreallyreal…'"))));
783 TU.Code = std::string(Source.code());
784 EXPECT_THAT(TU.build().getDiagnostics(),
786 Fix(Source.range(), "ident",
"change 'ide\\…' to 'ident'"))));
789TEST(DiagnosticTest, NewLineFixMessage) {
792 TU.ExtraArgs = {
"-Wnewline-eof"};
794 TU.build().getDiagnostics(),
795 ElementsAre(withFix((
Fix(Source.range(),
"\n",
"insert '\\n'")))));
798TEST(DiagnosticTest, ClangTidySuppressionCommentTrumpsWarningAsError) {
802 double f = [[8]] / i; // NOLINT
806 TU.ClangTidyProvider =
807 addTidyChecks("bugprone-integer-division",
"bugprone-integer-division");
808 EXPECT_THAT(TU.build().getDiagnostics(), UnorderedElementsAre());
811TEST(DiagnosticTest, ClangTidyNoLiteralDataInMacroToken) {
814 using pthread_t = int;
815 int pthread_kill(pthread_t thread, int sig);
818 return pthread_kill(thread, 0);
822 TU.ClangTidyProvider = addTidyChecks("bugprone-bad-signal-to-kill-thread");
823 EXPECT_THAT(TU.build().getDiagnostics(), UnorderedElementsAre());
826TEST(DiagnosticTest, ClangTidyMacroToEnumCheck) {
833 std::vector<TidyProvider> Providers;
835 addTidyChecks("cppcoreguidelines-macro-to-enum,modernize-macro-to-enum"));
837 TU.ClangTidyProvider =
combine(std::move(Providers));
838 EXPECT_THAT(TU.build().getDiagnostics(), UnorderedElementsAre());
841TEST(DiagnosticTest, ElseAfterReturnRange) {
846 } [[else]] if (cond == 2) {
853 TU.ClangTidyProvider = addTidyChecks("llvm-else-after-return");
854 EXPECT_THAT(TU.build().getDiagnostics(),
855 ifTidyChecks(ElementsAre(
856 Diag(Main.range(),
"do not use 'else' after 'return'"))));
859TEST(DiagnosticTest, ClangTidySelfContainedDiags) {
868 void InitVariables() {
869 float $C[[C]]$CFix[[]];
870 double $D[[D]]$DFix[[]];
874 TU.ClangTidyProvider =
876 "cppcoreguidelines-init-variables");
877 clangd::Fix ExpectedAFix;
878 ExpectedAFix.Message =
879 "'A' should be initialized in a member initializer of the constructor";
880 ExpectedAFix.Edits.push_back(
TextEdit{Main.range(
"Fix"),
" : A(1)"});
881 ExpectedAFix.Edits.push_back(
TextEdit{Main.range(
"A"),
""});
886 clangd::Fix ExpectedBFix;
887 ExpectedBFix.Message =
888 "'B' should be initialized in a member initializer of the constructor";
889 ExpectedBFix.Edits.push_back(
TextEdit{Main.range(
"Fix"),
" : B(1)"});
890 ExpectedBFix.Edits.push_back(
TextEdit{Main.range(
"B"),
""});
892 clangd::Fix ExpectedCFix;
893 ExpectedCFix.Message =
"variable 'C' is not initialized";
894 ExpectedCFix.Edits.push_back(
TextEdit{Main.range(
"CFix"),
" = NAN"});
895 ExpectedCFix.Edits.push_back(
896 TextEdit{Main.range(
"MathHeader"),
"#include <math.h>\n\n"});
900 clangd::Fix ExpectedDFix;
901 ExpectedDFix.Message =
"variable 'D' is not initialized";
902 ExpectedDFix.Edits.push_back(
TextEdit{Main.range(
"DFix"),
" = NAN"});
903 ExpectedDFix.Edits.push_back(
904 TextEdit{Main.range(
"MathHeader"),
"#include <math.h>\n\n"});
906 TU.build().getDiagnostics(),
907 ifTidyChecks(UnorderedElementsAre(
908 AllOf(
Diag(Main.range(
"A"),
"'A' should be initialized in a member "
909 "initializer of the constructor"),
910 withFix(equalToFix(ExpectedAFix))),
911 AllOf(
Diag(Main.range(
"B"),
"'B' should be initialized in a member "
912 "initializer of the constructor"),
913 withFix(equalToFix(ExpectedBFix))),
914 AllOf(
Diag(Main.range(
"C"),
"variable 'C' is not initialized"),
915 withFix(equalToFix(ExpectedCFix))),
916 AllOf(
Diag(Main.range(
"D"),
"variable 'D' is not initialized"),
917 withFix(equalToFix(ExpectedDFix))))));
920TEST(DiagnosticTest, ClangTidySelfContainedDiagsFormatting) {
924 virtual void Reset1() = 0;
925 virtual void Reset2() = 0;
927 class A : public Interface {
928 // This will be marked by clangd to use override instead of virtual
929 $virtual1[[virtual ]]void $Reset1[[Reset1]]()$override1[[]];
930 $virtual2[[virtual ]]/**/void $Reset2[[Reset2]]()$override2[[]];
934 TU.ClangTidyProvider =
935 addTidyChecks("cppcoreguidelines-explicit-virtual-functions,");
936 clangd::Fix
const ExpectedFix1{
937 "prefer using 'override' or (rarely) 'final' "
938 "instead of 'virtual'",
939 {
TextEdit{Main.range(
"override1"),
" override"},
940 TextEdit{Main.range(
"virtual1"),
""}},
942 clangd::Fix
const ExpectedFix2{
943 "prefer using 'override' or (rarely) 'final' "
944 "instead of 'virtual'",
945 {
TextEdit{Main.range(
"override2"),
" override"},
946 TextEdit{Main.range(
"virtual2"),
""}},
950 EXPECT_THAT(TU.build().getDiagnostics(),
951 ifTidyChecks(UnorderedElementsAre(
952 AllOf(
Diag(Main.range(
"Reset1"),
953 "prefer using 'override' or (rarely) 'final' "
954 "instead of 'virtual'"),
955 withFix(equalToFix(ExpectedFix1))),
956 AllOf(
Diag(Main.range(
"Reset2"),
957 "prefer using 'override' or (rarely) 'final' "
958 "instead of 'virtual'"),
959 withFix(equalToFix(ExpectedFix2))))));
962TEST(DiagnosticsTest, ClangTidyCallingIntoPreprocessor) {
963 std::string Main = R
"cpp(
968 std::string Header = R"cpp(
969 #define EXTERN extern
973 TU.AdditionalFiles[
"b.h"] = Header;
974 TU.ClangTidyProvider =
addTidyChecks(
"modernize-use-trailing-return-type");
979TEST(DiagnosticsTest, Preprocessor) {
988 int a = [[b]]; // error-ok
995 ElementsAre(Diag(Test.range(), "use of undeclared identifier 'b'")));
998TEST(DiagnosticsTest, IgnoreVerify) {
1000 int a; // expected-error {{}}
1002 TU.ExtraArgs.push_back("-Xclang");
1003 TU.ExtraArgs.push_back(
"-verify");
1004 EXPECT_THAT(TU.build().getDiagnostics(), IsEmpty());
1007TEST(DiagnosticTest, IgnoreBEFilelistOptions) {
1009 TU.ExtraArgs.push_back(
"-Xclang");
1010 for (
const auto *DisableOption :
1011 {
"-fsanitize-ignorelist=null",
"-fprofile-list=null",
1012 "-fxray-always-instrument=null",
"-fxray-never-instrument=null",
1013 "-fxray-attr-list=null"}) {
1014 TU.ExtraArgs.push_back(DisableOption);
1015 EXPECT_THAT(TU.build().getDiagnostics(), IsEmpty());
1016 TU.ExtraArgs.pop_back();
1021TEST(DiagnosticsTest, RecursivePreamble) {
1023 #include "foo.h" // error-ok
1026 TU.Filename = "foo.h";
1027 EXPECT_THAT(TU.build().getDiagnostics(),
1028 ElementsAre(diagName(
"pp_including_mainfile_in_preamble")));
1029 EXPECT_THAT(TU.build().getLocalTopLevelDecls(), SizeIs(1));
1033TEST(DiagnosticsTest, RecursivePreamblePragmaOnce) {
1039 TU.Filename = "foo.h";
1040 EXPECT_THAT(TU.build().getDiagnostics(),
1041 Not(Contains(diagName(
"pp_including_mainfile_in_preamble"))));
1042 EXPECT_THAT(TU.build().getLocalTopLevelDecls(), SizeIs(1));
1047TEST(DiagnosticsTest, RecursivePreambleIfndefGuard) {
1051 #include "foo.h" // error-ok
1055 TU.Filename = "foo.h";
1057 EXPECT_THAT(TU.build().getDiagnostics(),
1058 ElementsAre(diagName(
"pp_including_mainfile_in_preamble")));
1059 EXPECT_THAT(TU.build().getLocalTopLevelDecls(), SizeIs(1));
1062TEST(DiagnosticsTest, PreambleWithPragmaAssumeNonnull) {
1064#pragma clang assume_nonnull begin
1066#pragma clang assume_nonnull end
1068 auto AST = TU.build();
1069 EXPECT_THAT(
AST.getDiagnostics(), IsEmpty());
1070 const auto *
X = cast<FunctionDecl>(
findDecl(
AST,
"foo")).getParamDecl(0);
1071 ASSERT_TRUE(
X->getOriginalType()->getNullability() ==
1072 NullabilityKind::NonNull);
1075TEST(DiagnosticsTest, PreambleHeaderWithBadPragmaAssumeNonnull) {
1077#pragma clang assume_nonnull begin // error-ok
1081#include "foo.h" // unterminated assume_nonnull should not affect bar.
1084 TU.AdditionalFiles = {{"foo.h", std::string(Header.code())}};
1085 auto AST = TU.build();
1086 EXPECT_THAT(
AST.getDiagnostics(),
1087 ElementsAre(diagName(
"pp_eof_in_assume_nonnull")));
1088 const auto *
X = cast<FunctionDecl>(
findDecl(
AST,
"foo")).getParamDecl(0);
1089 ASSERT_TRUE(
X->getOriginalType()->getNullability() ==
1090 NullabilityKind::NonNull);
1091 const auto *Y = cast<FunctionDecl>(
findDecl(
AST,
"bar")).getParamDecl(0);
1092 ASSERT_FALSE(Y->getOriginalType()->getNullability());
1095TEST(DiagnosticsTest, InsideMacros) {
1098 #define RET(x) return x + 10
1101 RET($foo[[0]]); // error-ok
1108 ElementsAre(Diag(Test.range("foo"),
1109 "cannot initialize return object of type "
1110 "'int *' with an rvalue of type 'int'"),
1111 Diag(Test.range(
"bar"),
1112 "cannot initialize return object of type "
1113 "'int *' with an rvalue of type 'int'")));
1116TEST(DiagnosticsTest, NoFixItInMacro) {
1118 #define Define(name) void name() {}
1120 [[Define]](main) // error-ok
1123 EXPECT_THAT(TU.build().getDiagnostics(),
1124 ElementsAre(AllOf(
Diag(Test.range(),
"'main' must return 'int'"),
1128TEST(DiagnosticsTest, PragmaSystemHeader) {
1129 Annotations Test(
"#pragma clang [[system_header]]\n");
1132 TU.build().getDiagnostics(),
1134 Diag(Test.range(),
"#pragma system_header ignored in main file"))));
1135 TU.Filename =
"TestTU.h";
1136 EXPECT_THAT(TU.build().getDiagnostics(), IsEmpty());
1139TEST(ClangdTest, MSAsm) {
1142 llvm::InitializeAllTargetInfos();
1144 TU.ExtraArgs = {
"-fms-extensions"};
1145 EXPECT_THAT(TU.build().getDiagnostics(), IsEmpty());
1148TEST(DiagnosticsTest, ToLSP) {
1155 D.ID = clang::diag::err_undeclared_var_use;
1157 D.Name =
"undeclared_var_use";
1159 D.Message =
"something terrible happened";
1160 D.Range = {pos(1, 2), pos(3, 4)};
1161 D.InsideMainFile =
true;
1162 D.Severity = DiagnosticsEngine::Error;
1163 D.File =
"foo/bar/main.cpp";
1164 D.AbsFile = std::string(MainFile.file());
1165 D.OpaqueData[
"test"] =
"bar";
1167 clangd::Note NoteInMain;
1168 NoteInMain.Message =
"declared somewhere in the main file";
1169 NoteInMain.Range = {pos(5, 6), pos(7, 8)};
1170 NoteInMain.Severity = DiagnosticsEngine::Remark;
1171 NoteInMain.File =
"../foo/bar/main.cpp";
1172 NoteInMain.InsideMainFile =
true;
1173 NoteInMain.AbsFile = std::string(MainFile.file());
1175 D.Notes.push_back(NoteInMain);
1177 clangd::Note NoteInHeader;
1178 NoteInHeader.Message =
"declared somewhere in the header file";
1179 NoteInHeader.Range = {pos(9, 10), pos(11, 12)};
1180 NoteInHeader.Severity = DiagnosticsEngine::Note;
1181 NoteInHeader.File =
"../foo/baz/header.h";
1182 NoteInHeader.InsideMainFile =
false;
1183 NoteInHeader.AbsFile = std::string(
HeaderFile.file());
1184 D.Notes.push_back(NoteInHeader);
1187 F.Message =
"do something";
1188 D.Fixes.push_back(F);
1191 clangd::Diagnostic MainLSP;
1192 MainLSP.range =
D.Range;
1193 MainLSP.severity =
getSeverity(DiagnosticsEngine::Error);
1194 MainLSP.code =
"undeclared_var_use";
1195 MainLSP.source =
"clang";
1197 R
"(Something terrible happened (fix available)
1199main.cpp:6:7: remark: declared somewhere in the main file
1201../foo/baz/header.h:10:11:
1202note: declared somewhere in the header file)";
1204 MainLSP.data = D.OpaqueData;
1206 clangd::Diagnostic NoteInMainLSP;
1207 NoteInMainLSP.range = NoteInMain.Range;
1208 NoteInMainLSP.severity = getSeverity(DiagnosticsEngine::Remark);
1209 NoteInMainLSP.message = R"(Declared somewhere in the main file
1211main.cpp:2:3: error: something terrible happened)";
1215 std::vector<std::pair<clangd::Diagnostic, std::vector<clangd::Fix>>> LSPDiags;
1217 [&](clangd::Diagnostic LSPDiag, ArrayRef<clangd::Fix> Fixes) {
1219 {std::move(LSPDiag),
1220 std::vector<clangd::Fix>(Fixes.begin(), Fixes.end())});
1225 ElementsAre(Pair(equalToLSPDiag(MainLSP), ElementsAre(equalToFix(F))),
1226 Pair(equalToLSPDiag(NoteInMainLSP), IsEmpty())));
1227 EXPECT_EQ(LSPDiags[0].first.code,
"undeclared_var_use");
1228 EXPECT_EQ(LSPDiags[0].first.source,
"clang");
1229 EXPECT_EQ(LSPDiags[1].first.code,
"");
1230 EXPECT_EQ(LSPDiags[1].first.source,
"");
1234 Opts.EmitRelatedLocations =
true;
1236 [&](clangd::Diagnostic LSPDiag, ArrayRef<clangd::Fix> Fixes) {
1238 {std::move(LSPDiag),
1239 std::vector<clangd::Fix>(Fixes.begin(), Fixes.end())});
1241 MainLSP.message =
"Something terrible happened (fix available)";
1243 NoteInMainDRI.
message =
"Declared somewhere in the main file";
1244 NoteInMainDRI.location.range = NoteInMain.Range;
1245 NoteInMainDRI.location.uri = MainFile;
1246 MainLSP.relatedInformation = {NoteInMainDRI};
1248 NoteInHeaderDRI.
message =
"Declared somewhere in the header file";
1249 NoteInHeaderDRI.location.range = NoteInHeader.Range;
1251 MainLSP.relatedInformation = {NoteInMainDRI, NoteInHeaderDRI};
1252 EXPECT_THAT(LSPDiags, ElementsAre(Pair(equalToLSPDiag(MainLSP),
1253 ElementsAre(equalToFix(F)))));
1256struct SymbolWithHeader {
1258 std::string DeclaringFile;
1259 std::string IncludeHeader;
1262std::unique_ptr<SymbolIndex>
1263buildIndexWithSymbol(llvm::ArrayRef<SymbolWithHeader> Syms) {
1265 for (
const auto &S : Syms) {
1268 Sym.CanonicalDeclaration.FileURI = S.DeclaringFile.c_str();
1269 Sym.Definition.FileURI = S.DeclaringFile.c_str();
1276TEST(IncludeFixerTest, IncompleteType) {
1278 TU.ExtraArgs.push_back(
"-std=c++20");
1279 auto Index = buildIndexWithSymbol(
1280 {SymbolWithHeader{
"ns::X",
"unittest:///x.h",
"\"x.h\""}});
1281 TU.ExternalIndex = Index.get();
1283 std::vector<std::pair<llvm::StringRef, llvm::StringRef>> Tests{
1284 {
"incomplete_nested_name_spec",
"[[ns::X::]]Nested n;"},
1285 {
"incomplete_base_class",
"class Y : [[ns::X]] {};"},
1286 {
"incomplete_member_access",
"auto i = x[[->]]f();"},
1287 {
"incomplete_type",
"auto& [[[]]m] = *x;"},
1288 {
"init_incomplete_type",
1289 "struct C { static int f(ns::X&); }; int i = C::f([[{]]});"},
1290 {
"bad_cast_incomplete",
"auto a = [[static_cast]]<ns::X>(0);"},
1291 {
"template_nontype_parm_incomplete",
"template <ns::X [[foo]]> int a;"},
1292 {
"typecheck_decl_incomplete_type",
"ns::X [[var]];"},
1293 {
"typecheck_incomplete_tag",
"auto i = [[(*x)]]->f();"},
1294 {
"typecheck_nonviable_condition_incomplete",
1295 "struct A { operator ns::X(); } a; const ns::X &[[b]] = a;"},
1296 {
"invalid_incomplete_type_use",
"auto var = [[ns::X()]];"},
1297 {
"sizeof_alignof_incomplete_or_sizeless_type",
1298 "auto s = [[sizeof]](ns::X);"},
1299 {
"for_range_incomplete_type",
"void foo() { for (auto i : [[*]]x ) {} }"},
1300 {
"func_def_incomplete_result",
"ns::X [[func]] () {}"},
1301 {
"field_incomplete_or_sizeless",
"class M { ns::X [[member]]; };"},
1302 {
"array_incomplete_or_sizeless_type",
"auto s = [[(ns::X[]){}]];"},
1303 {
"call_incomplete_return",
"ns::X f(); auto fp = &f; auto z = [[fp()]];"},
1304 {
"call_function_incomplete_return",
"ns::X foo(); auto a = [[foo()]];"},
1305 {
"call_incomplete_argument",
"int m(ns::X); int i = m([[*x]]);"},
1306 {
"switch_incomplete_class_type",
"void a() { [[switch]](*x) {} }"},
1307 {
"delete_incomplete_class_type",
"void f() { [[delete]] *x; }"},
1308 {
"-Wdelete-incomplete",
"void f() { [[delete]] x; }"},
1309 {
"dereference_incomplete_type",
1310 R
"cpp(void f() { asm("" : "=r"([[*]]x)::); })cpp"},
1312 for (
auto Case : Tests) {
1314 TU.Code = Main.code().str() +
"\n // error-ok";
1316 TU.build().getDiagnostics(),
1317 ElementsAre(AllOf(diagName(Case.first), hasRange(Main.range()),
1318 withFix(
Fix(
Range{},
"#include \"x.h\"\n",
1319 "Include \"x.h\" for symbol ns::X")))))
1324TEST(IncludeFixerTest, IncompleteEnum) {
1327 Sym.CanonicalDeclaration.FileURI = Sym.Definition.FileURI =
"unittest:///x.h";
1336 TU.ExtraArgs.push_back(
"-std=c++20");
1337 TU.ExtraArgs.push_back(
"-fno-ms-compatibility");
1339 std::vector<std::pair<llvm::StringRef, llvm::StringRef>> Tests{
1340 {
"incomplete_enum",
"enum class X : int; using enum [[X]];"},
1341 {
"underlying_type_of_incomplete_enum",
1342 "[[__underlying_type]](enum X) i;"},
1344 for (
auto Case : Tests) {
1346 TU.Code = Main.code().str() +
"\n // error-ok";
1347 EXPECT_THAT(TU.build().getDiagnostics(),
1348 Contains(AllOf(diagName(Case.first), hasRange(Main.range()),
1349 withFix(
Fix(
Range{},
"#include \"x.h\"\n",
1350 "Include \"x.h\" for symbol X")))))
1355TEST(IncludeFixerTest, NoSuggestIncludeWhenNoDefinitionInHeader) {
1357$insert[[]]namespace ns {
1360class Y : $base[[public ns::X]] {};
1369 Sym.CanonicalDeclaration.FileURI =
"unittest:///x.h";
1370 Sym.Definition.FileURI =
"unittest:///x.cc";
1377 TU.ExternalIndex = Index.get();
1379 EXPECT_THAT(TU.build().getDiagnostics(),
1380 UnorderedElementsAre(
1381 Diag(Test.range(
"base"),
"base class has incomplete type"),
1382 Diag(Test.range(
"access"),
1383 "member access into incomplete type 'ns::X'")));
1386TEST(IncludeFixerTest, Typo) {
1388$insert[[]]namespace ns {
1390 $unqualified1[[X]] x;
1391 // No fix if the unresolved type is used as specifier. (ns::)X::Nested will be
1392 // considered the unresolved type.
1393 $unqualified2[[X]]::Nested n;
1395struct S : $base[[X]] {};
1398 ns::$qualified1[[X]] x; // ns:: is valid.
1399 ns::$qualified2[[X]](); // Error: no member in namespace
1401 ::$global[[Global]] glob;
1403using Type = ns::$template[[Foo]]<int>;
1406 auto Index = buildIndexWithSymbol(
1407 {SymbolWithHeader{
"ns::X",
"unittest:///x.h",
"\"x.h\""},
1408 SymbolWithHeader{
"Global",
"unittest:///global.h",
"\"global.h\""},
1409 SymbolWithHeader{
"ns::Foo",
"unittest:///foo.h",
"\"foo.h\""}});
1410 TU.ExternalIndex = Index.get();
1413 TU.build().getDiagnostics(),
1414 UnorderedElementsAre(
1415 AllOf(
Diag(Test.range(
"unqualified1"),
"unknown type name 'X'"),
1416 diagName(
"unknown_typename"),
1417 withFix(
Fix(Test.range(
"insert"),
"#include \"x.h\"\n",
1418 "Include \"x.h\" for symbol ns::X"))),
1419 Diag(Test.range(
"unqualified2"),
"use of undeclared identifier 'X'"),
1420 AllOf(
Diag(Test.range(
"qualified1"),
1421 "no type named 'X' in namespace 'ns'"),
1422 diagName(
"typename_nested_not_found"),
1423 withFix(
Fix(Test.range(
"insert"),
"#include \"x.h\"\n",
1424 "Include \"x.h\" for symbol ns::X"))),
1425 AllOf(
Diag(Test.range(
"qualified2"),
1426 "no member named 'X' in namespace 'ns'"),
1427 diagName(
"no_member"),
1428 withFix(
Fix(Test.range(
"insert"),
"#include \"x.h\"\n",
1429 "Include \"x.h\" for symbol ns::X"))),
1430 AllOf(
Diag(Test.range(
"global"),
1431 "no type named 'Global' in the global namespace"),
1432 diagName(
"typename_nested_not_found"),
1433 withFix(
Fix(Test.range(
"insert"),
"#include \"global.h\"\n",
1434 "Include \"global.h\" for symbol Global"))),
1435 AllOf(
Diag(Test.range(
"template"),
1436 "no template named 'Foo' in namespace 'ns'"),
1437 diagName(
"no_member_template"),
1438 withFix(
Fix(Test.range(
"insert"),
"#include \"foo.h\"\n",
1439 "Include \"foo.h\" for symbol ns::Foo"))),
1440 AllOf(
Diag(Test.range(
"base"),
"expected class name"),
1441 diagName(
"expected_class_name"),
1442 withFix(
Fix(Test.range(
"insert"),
"#include \"x.h\"\n",
1443 "Include \"x.h\" for symbol ns::X")))));
1446TEST(IncludeFixerTest, TypoInMacro) {
1457 auto Index = buildIndexWithSymbol(
1458 {SymbolWithHeader{
"X",
"unittest:///x.h",
"\"x.h\""},
1459 SymbolWithHeader{
"ns::X",
"unittest:///ns.h",
"\"x.h\""}});
1460 TU.ExternalIndex = Index.get();
1463 TU.ExtraArgs = {
"-fno-ms-compatibility"};
1464 EXPECT_THAT(TU.build().getDiagnostics(), Each(withFix(_)));
1467TEST(IncludeFixerTest, MultipleMatchedSymbols) {
1469$insert[[]]namespace na {
1472 $unqualified[[X]] x;
1478 auto Index = buildIndexWithSymbol(
1479 {SymbolWithHeader{
"na::X",
"unittest:///a.h",
"\"a.h\""},
1480 SymbolWithHeader{
"na::nb::X",
"unittest:///b.h",
"\"b.h\""}});
1481 TU.ExternalIndex = Index.get();
1483 EXPECT_THAT(TU.build().getDiagnostics(),
1484 UnorderedElementsAre(AllOf(
1485 Diag(Test.range(
"unqualified"),
"unknown type name 'X'"),
1486 diagName(
"unknown_typename"),
1487 withFix(
Fix(Test.range(
"insert"),
"#include \"a.h\"\n",
1488 "Include \"a.h\" for symbol na::X"),
1489 Fix(Test.range(
"insert"),
"#include \"b.h\"\n",
1490 "Include \"b.h\" for symbol na::nb::X")))));
1493TEST(IncludeFixerTest, NoCrashMemberAccess) {
1495 struct X { int xyz; };
1496 void g() { X x; x.$[[xy]]; }
1499 auto Index = buildIndexWithSymbol(
1500 SymbolWithHeader{
"na::X",
"unittest:///a.h",
"\"a.h\""});
1501 TU.ExternalIndex = Index.get();
1504 TU.build().getDiagnostics(),
1505 UnorderedElementsAre(
Diag(Test.range(),
"no member named 'xy' in 'X'")));
1508TEST(IncludeFixerTest, UseCachedIndexResults) {
1512$insert[[]]void foo() {
1535 buildIndexWithSymbol(SymbolWithHeader{
"X",
"unittest:///a.h",
"\"a.h\""});
1536 TU.ExternalIndex = Index.get();
1538 auto Parsed = TU.build();
1539 for (
const auto &D : Parsed.getDiagnostics()) {
1540 if (
D.Fixes.size() != 1) {
1541 ADD_FAILURE() <<
"D.Fixes.size() != 1";
1544 EXPECT_EQ(
D.Fixes[0].Message, std::string(
"Include \"a.h\" for symbol X"));
1548TEST(IncludeFixerTest, UnresolvedNameAsSpecifier) {
1550$insert[[]]namespace ns {
1552void g() { ns::$[[scope]]::X_Y(); }
1555 TU.Code = std::string(Test.code());
1557 TU.ExtraArgs.push_back(
"-fno-ms-compatibility");
1558 auto Index = buildIndexWithSymbol(
1559 SymbolWithHeader{
"ns::scope::X_Y",
"unittest:///x.h",
"\"x.h\""});
1560 TU.ExternalIndex = Index.get();
1563 TU.build().getDiagnostics(),
1564 UnorderedElementsAre(
1565 AllOf(
Diag(Test.range(),
"no member named 'scope' in namespace 'ns'"),
1566 diagName(
"no_member"),
1567 withFix(
Fix(Test.range(
"insert"),
"#include \"x.h\"\n",
1568 "Include \"x.h\" for symbol ns::scope::X_Y")))));
1571TEST(IncludeFixerTest, UnresolvedSpecifierWithSemaCorrection) {
1573$insert[[]]namespace clang {
1575 // "clangd::" will be corrected to "clang::" by Sema.
1576 $q1[[clangd]]::$x[[X]] x;
1577 $q2[[clangd]]::$ns[[ns]]::Y y;
1582 TU.Code = std::string(Test.code());
1584 TU.ExtraArgs.push_back(
"-fno-ms-compatibility");
1585 auto Index = buildIndexWithSymbol(
1586 {SymbolWithHeader{
"clang::clangd::X",
"unittest:///x.h",
"\"x.h\""},
1587 SymbolWithHeader{
"clang::clangd::ns::Y",
"unittest:///y.h",
"\"y.h\""}});
1588 TU.ExternalIndex = Index.get();
1591 TU.build().getDiagnostics(),
1592 UnorderedElementsAre(
1593 AllOf(
Diag(Test.range(
"q1"),
"use of undeclared identifier 'clangd'; "
1594 "did you mean 'clang'?"),
1595 diagName(
"undeclared_var_use_suggest"),
1597 Fix(Test.range(
"insert"),
"#include \"x.h\"\n",
1598 "Include \"x.h\" for symbol clang::clangd::X"))),
1599 AllOf(
Diag(Test.range(
"x"),
"no type named 'X' in namespace 'clang'"),
1600 diagName(
"typename_nested_not_found"),
1601 withFix(
Fix(Test.range(
"insert"),
"#include \"x.h\"\n",
1602 "Include \"x.h\" for symbol clang::clangd::X"))),
1604 Diag(Test.range(
"q2"),
"use of undeclared identifier 'clangd'; "
1605 "did you mean 'clang'?"),
1606 diagName(
"undeclared_var_use_suggest"),
1608 Fix(Test.range(
"insert"),
"#include \"y.h\"\n",
1609 "Include \"y.h\" for symbol clang::clangd::ns::Y"))),
1610 AllOf(
Diag(Test.range(
"ns"),
1611 "no member named 'ns' in namespace 'clang'"),
1612 diagName(
"no_member"),
1614 Fix(Test.range(
"insert"),
"#include \"y.h\"\n",
1615 "Include \"y.h\" for symbol clang::clangd::ns::Y")))));
1618TEST(IncludeFixerTest, SpecifiedScopeIsNamespaceAlias) {
1620$insert[[]]namespace a {}
1627 auto Index = buildIndexWithSymbol(
1628 SymbolWithHeader{
"a::X",
"unittest:///x.h",
"\"x.h\""});
1629 TU.ExternalIndex = Index.get();
1631 EXPECT_THAT(TU.build().getDiagnostics(),
1632 UnorderedElementsAre(AllOf(
1633 Diag(Test.range(),
"no type named 'X' in namespace 'a'"),
1634 diagName(
"typename_nested_not_found"),
1635 withFix(
Fix(Test.range(
"insert"),
"#include \"x.h\"\n",
1636 "Include \"x.h\" for symbol a::X")))));
1639TEST(IncludeFixerTest, NoCrashOnTemplateInstantiations) {
1641 template <typename T> struct Templ {
1642 template <typename U>
1643 typename U::type operator=(const U &);
1648 A() { [[a]]; /*error-ok*/ } // crash if we compute scopes lazily.
1653 auto Index = buildIndexWithSymbol({});
1654 TU.ExternalIndex = Index.get();
1657 TU.build().getDiagnostics(),
1658 ElementsAre(
Diag(Test.range(),
"use of undeclared identifier 'a'")));
1661TEST(IncludeFixerTest, HeaderNamedInDiag) {
1663 $insert[[]]int main() {
1668 TU.ExtraArgs = {
"-xc",
"-std=c99",
1669 "-Wno-error=implicit-function-declaration"};
1670 auto Index = buildIndexWithSymbol({});
1671 TU.ExternalIndex = Index.get();
1674 TU.build().getDiagnostics(),
1676 Diag(Test.range(),
"call to undeclared library function 'printf' "
1677 "with type 'int (const char *, ...)'; ISO C99 "
1678 "and later do not support implicit function "
1680 withFix(
Fix(Test.range(
"insert"),
"#include <stdio.h>\n",
1681 "Include <stdio.h> for symbol printf")))));
1683 TU.ExtraArgs = {
"-xc",
"-std=c89"};
1685 TU.build().getDiagnostics(),
1687 Diag(Test.range(),
"implicitly declaring library function 'printf' "
1688 "with type 'int (const char *, ...)'"),
1689 withFix(
Fix(Test.range(
"insert"),
"#include <stdio.h>\n",
1690 "Include <stdio.h> for symbol printf")))));
1693TEST(IncludeFixerTest, CImplicitFunctionDecl) {
1696 TU.Filename =
"test.c";
1697 TU.ExtraArgs = {
"-std=c99",
"-Wno-error=implicit-function-declaration"};
1701 Sym.CanonicalDeclaration.FileURI =
"unittest:///foo.h";
1708 TU.ExternalIndex = Index.get();
1711 TU.build().getDiagnostics(),
1714 "call to undeclared function 'foo'; ISO C99 and later do not "
1715 "support implicit function declarations"),
1716 withFix(
Fix(
Range{},
"#include \"foo.h\"\n",
1717 "Include \"foo.h\" for symbol foo")))));
1719 TU.ExtraArgs = {
"-std=c89",
"-Wall"};
1720 EXPECT_THAT(TU.build().getDiagnostics(),
1722 Diag(Test.range(),
"implicit declaration of function 'foo'"),
1723 withFix(
Fix(
Range{},
"#include \"foo.h\"\n",
1724 "Include \"foo.h\" for symbol foo")))));
1727TEST(DiagsInHeaders, DiagInsideHeader) {
1730 void foo() {})cpp");
1731 Annotations Header("[[no_type_spec]]; // error-ok");
1733 TU.AdditionalFiles = {{
"a.h", std::string(Header.code())}};
1734 EXPECT_THAT(TU.build().getDiagnostics(),
1735 UnorderedElementsAre(AllOf(
1736 Diag(Main.range(),
"in included file: a type specifier is "
1737 "required for all declarations"),
1738 withNote(
Diag(Header.range(),
"error occurred here")))));
1741TEST(DiagsInHeaders, DiagInTransitiveInclude) {
1744 void foo() {})cpp");
1746 TU.AdditionalFiles = {{"a.h",
"#include \"b.h\""},
1747 {
"b.h",
"no_type_spec; // error-ok"}};
1748 EXPECT_THAT(TU.build().getDiagnostics(),
1749 UnorderedElementsAre(
Diag(Main.range(),
1750 "in included file: a type specifier is "
1751 "required for all declarations")));
1754TEST(DiagsInHeaders, DiagInMultipleHeaders) {
1756 #include $a[["a.h"]]
1757 #include $b[["b.h"]]
1758 void foo() {})cpp");
1760 TU.AdditionalFiles = {{"a.h",
"no_type_spec; // error-ok"},
1761 {
"b.h",
"no_type_spec; // error-ok"}};
1762 EXPECT_THAT(TU.build().getDiagnostics(),
1763 UnorderedElementsAre(
1764 Diag(Main.range(
"a"),
"in included file: a type specifier is "
1765 "required for all declarations"),
1766 Diag(Main.range(
"b"),
"in included file: a type specifier is "
1767 "required for all declarations")));
1770TEST(DiagsInHeaders, PreferExpansionLocation) {
1774 void foo() {})cpp");
1776 TU.AdditionalFiles = {
1777 {"a.h",
"#include \"b.h\"\n"},
1778 {
"b.h",
"#ifndef X\n#define X\nno_type_spec; // error-ok\n#endif"}};
1779 EXPECT_THAT(TU.build().getDiagnostics(),
1780 Contains(
Diag(Main.range(),
"in included file: a type specifier "
1781 "is required for all declarations")));
1784TEST(DiagsInHeaders, PreferExpansionLocationMacros) {
1790 void foo() {})cpp");
1792 TU.AdditionalFiles = {
1793 {"a.h",
"#include \"c.h\"\n"},
1794 {
"b.h",
"#include \"c.h\"\n"},
1795 {
"c.h",
"#ifndef X\n#define X\nno_type_spec; // error-ok\n#endif"}};
1796 EXPECT_THAT(TU.build().getDiagnostics(),
1797 UnorderedElementsAre(
Diag(Main.range(),
1798 "in included file: a type specifier is "
1799 "required for all declarations")));
1802TEST(DiagsInHeaders, LimitDiagsOutsideMainFile) {
1806 void foo() {})cpp");
1808 TU.AdditionalFiles = {{"a.h",
"#include \"c.h\"\n"},
1809 {
"b.h",
"#include \"c.h\"\n"},
1813 no_type_spec_0; // error-ok
1825 EXPECT_THAT(TU.build().getDiagnostics(),
1826 UnorderedElementsAre(Diag(Main.range(),
1827 "in included file: a type specifier is "
1828 "required for all declarations")));
1831TEST(DiagsInHeaders, OnlyErrorOrFatal) {
1834 void foo() {})cpp");
1836 [[no_type_spec]]; // error-ok
1839 TU.AdditionalFiles = {{"a.h", std::string(Header.code())}};
1840 EXPECT_THAT(TU.build().getDiagnostics(),
1841 UnorderedElementsAre(AllOf(
1842 Diag(Main.range(),
"in included file: a type specifier is "
1843 "required for all declarations"),
1844 withNote(
Diag(Header.range(),
"error occurred here")))));
1847TEST(DiagsInHeaders, OnlyDefaultErrorOrFatal) {
1849 #include [["a.h"]] // get unused "foo" warning when building preamble.
1852 namespace { void foo() {} }
1853 void func() {foo();} ;)cpp");
1855 TU.AdditionalFiles = {{"a.h", std::string(Header.code())}};
1857 TU.ExtraArgs = {
"-Werror",
"-Wunused"};
1858 EXPECT_THAT(TU.build().getDiagnostics(), IsEmpty());
1861TEST(DiagsInHeaders, FromNonWrittenSources) {
1864 void foo() {})cpp");
1867 int b = [[FOO]]; // error-ok)cpp");
1869 TU.AdditionalFiles = {{"a.h", std::string(Header.code())}};
1870 TU.ExtraArgs = {
"-DFOO=NOOO"};
1871 EXPECT_THAT(TU.build().getDiagnostics(),
1872 UnorderedElementsAre(AllOf(
1874 "in included file: use of undeclared identifier 'NOOO'"),
1875 withNote(
Diag(Header.range(),
"error occurred here")))));
1878TEST(DiagsInHeaders, ErrorFromMacroExpansion) {
1888 TU.AdditionalFiles = {{"a.h", std::string(Header.code())}};
1889 EXPECT_THAT(TU.build().getDiagnostics(),
1890 UnorderedElementsAre(
1891 Diag(Main.range(),
"in included file: use of undeclared "
1892 "identifier 'foo'; did you mean 'fo'?")));
1895TEST(DiagsInHeaders, ErrorFromMacroArgument) {
1905 TU.AdditionalFiles = {{"a.h", std::string(Header.code())}};
1906 EXPECT_THAT(TU.build().getDiagnostics(),
1907 UnorderedElementsAre(
1908 Diag(Main.range(),
"in included file: use of undeclared "
1909 "identifier 'foo'; did you mean 'fo'?")));
1912TEST(IgnoreDiags, FromNonWrittenInclude) {
1914 TU.
ExtraArgs.push_back(
"--include=a.h");
1915 TU.AdditionalFiles = {{
"a.h",
"void main();"}};
1918 EXPECT_THAT(TU.build().getDiagnostics(), UnorderedElementsAre());
1921TEST(ToLSPDiag, RangeIsInMain) {
1924 D.Range = {pos(1, 2), pos(3, 4)};
1925 D.Notes.emplace_back();
1926 Note &N =
D.Notes.back();
1927 N.Range = {pos(2, 3), pos(3, 4)};
1929 D.InsideMainFile =
true;
1930 N.InsideMainFile =
false;
1932 [&](clangd::Diagnostic LSPDiag, ArrayRef<clangd::Fix>) {
1933 EXPECT_EQ(LSPDiag.range,
D.Range);
1936 D.InsideMainFile =
false;
1937 N.InsideMainFile =
true;
1939 [&](clangd::Diagnostic LSPDiag, ArrayRef<clangd::Fix>) {
1940 EXPECT_EQ(LSPDiag.range, N.Range);
1944TEST(ParsedASTTest, ModuleSawDiag) {
1947 auto AST = TU.build();
1949 EXPECT_THAT(
AST.getDiagnostics(),
1950 testing::Contains(
Diag(Code.range(), KDiagMsg.str())));
1959 TU.Code =
"#define FOO\n void bar();\n";
1960 auto AST = TU.build();
1961 EXPECT_THAT(
AST.getDiagnostics(), IsEmpty());
1965 TU.Code = Code.code().str();
1966 auto AST = TU.build();
1968 AST.getDiagnostics(),
1969 testing::Contains(
Diag(Code.range(),
"no newline at end of file")));
1973TEST(Diagnostics, Tags) {
1975 TU.
ExtraArgs = {
"-Wunused",
"-Wdeprecated"};
1977 void bar() __attribute__((deprecated));
1980 $deprecated[[bar]]();
1982 TU.Code = Test.code().str();
1983 EXPECT_THAT(TU.build().getDiagnostics(),
1984 UnorderedElementsAre(
1985 AllOf(Diag(Test.range("unused"),
"unused variable 'x'"),
1987 AllOf(
Diag(Test.range(
"deprecated"),
"'bar' is deprecated"),
1991 $typedef[[typedef int INT]];
1993 TU.Code = Test.code();
1994 TU.ClangTidyProvider = addTidyChecks("modernize-use-using");
1996 TU.build().getDiagnostics(),
1997 ifTidyChecks(UnorderedElementsAre(
1998 AllOf(
Diag(Test.range(
"typedef"),
"use 'using' instead of 'typedef'"),
2002TEST(Diagnostics, TidyDiagsArentAffectedFromWerror) {
2005 Annotations Test(R
"cpp($typedef[[typedef int INT]]; // error-ok)cpp");
2006 TU.Code = Test.code().str();
2007 TU.ClangTidyProvider = addTidyChecks("modernize-use-using");
2009 TU.build().getDiagnostics(),
2010 ifTidyChecks(UnorderedElementsAre(
2011 AllOf(
Diag(Test.range(
"typedef"),
"use 'using' instead of 'typedef'"),
2014 diagSeverity(DiagnosticsEngine::Warning)))));
2016 TU.ClangTidyProvider =
2019 TU.build().getDiagnostics(),
2020 ifTidyChecks(UnorderedElementsAre(
2021 AllOf(
Diag(Test.range(
"typedef"),
"use 'using' instead of 'typedef'"),
2023 diagSeverity(DiagnosticsEngine::Error)))));
2026TEST(Diagnostics, DeprecatedDiagsAreHints) {
2028 std::optional<clangd::Diagnostic>
Diag;
2030 D.Range = {pos(1, 2), pos(3, 4)};
2031 D.InsideMainFile =
true;
2035 D.Severity = DiagnosticsEngine::Warning;
2037 [&](clangd::Diagnostic LSPDiag, ArrayRef<clangd::Fix>) {
2038 Diag = std::move(LSPDiag);
2044 D.Severity = DiagnosticsEngine::Error;
2046 [&](clangd::Diagnostic LSPDiag, ArrayRef<clangd::Fix>) {
2047 Diag = std::move(LSPDiag);
2054 D.Severity = DiagnosticsEngine::Warning;
2056 [&](clangd::Diagnostic LSPDiag, ArrayRef<clangd::Fix>) {
2057 Diag = std::move(LSPDiag);
2062TEST(DiagnosticsTest, IncludeCleaner) {
2064$fix[[ $diag[[#include "unused.h"]]
2070 #include <system_header.h>
2077 TU.Code = Test.code().str();
2078 TU.AdditionalFiles["unused.h"] = R
"cpp(
2082 TU.AdditionalFiles["used.h"] = R
"cpp(
2086 TU.AdditionalFiles["ignore.h"] = R
"cpp(
2090 TU.AdditionalFiles["system/system_header.h"] =
"";
2091 TU.ExtraArgs = {
"-isystem" +
testPath(
"system")};
2095 Cfg.Diagnostics.Includes.IgnoreHeader.emplace_back(
2096 [](llvm::StringRef Header) {
return Header.ends_with(
"ignore.h"); });
2098 auto AST = TU.build();
2100 AST.getDiagnostics(),
2102 Diag(Test.range(
"diag"),
2103 "included header unused.h is not used directly"),
2105 withFix(
Fix(Test.range(
"fix"),
"",
"remove #include directive")))));
2106 auto &
Diag =
AST.getDiagnostics().front();
2108 llvm::ValueIs(Not(IsEmpty())));
2109 Cfg.Diagnostics.SuppressAll =
true;
2111 EXPECT_THAT(TU.build().getDiagnostics(), IsEmpty());
2112 Cfg.Diagnostics.SuppressAll =
false;
2113 Cfg.Diagnostics.Suppress = {
"unused-includes"};
2115 EXPECT_THAT(TU.build().getDiagnostics(), IsEmpty());
2118TEST(DiagnosticsTest, FixItFromHeader) {
2119 llvm::StringLiteral Header(R
"cpp(
2121 void foo(int *, int);)cpp");
2126 $diag[[foo]]($fix[[]]x, 1);
2129 TU.Code = Source.code().str();
2130 TU.HeaderCode = Header.str();
2132 TU.build().getDiagnostics(),
2133 UnorderedElementsAre(AllOf(
2134 Diag(Source.range("diag"),
"no matching function for call to 'foo'"),
2135 withFix(
Fix(Source.range(
"fix"),
"&",
2136 "candidate function not viable: no known conversion from "
2137 "'int' to 'int *' for 1st argument; take the address of "
2138 "the argument with &")))));
2141TEST(DiagnosticsTest, UnusedInHeader) {
2144 TU.ExtraArgs.push_back(
"-Wunused-function");
2145 TU.Filename =
"test.c";
2146 EXPECT_THAT(TU.build().getDiagnostics(),
2147 ElementsAre(withID(diag::warn_unused_function)));
2150 TU.Filename =
"test.h";
2151 EXPECT_THAT(TU.build().getDiagnostics(), IsEmpty());
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.
static std::unique_ptr< SymbolIndex > build(SymbolSlab Symbols, RefSlab Refs, RelationSlab Relations)
Builds an index from slabs. The index takes ownership of the data.
An efficient structure of storing large set of symbol references in memory.
SymbolSlab::Builder is a mutable container that can 'freeze' to SymbolSlab.
void insert(const Symbol &S)
Adds a symbol, overwriting any existing one with the same ID.
WithContextValue extends Context::current() with a single value.
FIXME: Skip testing on windows temporarily due to the different escaping code mode.
llvm::unique_function< void(tidy::ClangTidyOptions &, llvm::StringRef) const > TidyProvider
A factory to modify a tidy::ClangTidyOptions.
Symbol func(llvm::StringRef Name)
const NamedDecl & findDecl(ParsedAST &AST, llvm::StringRef QName)
Symbol cls(llvm::StringRef Name)
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, "")
std::string testPath(PathRef File, llvm::sys::path::Style Style)
static URISchemeRegistry::Add< TestScheme > X(TestScheme::Scheme, "Test schema")
llvm::json::Value toJSON(const FuzzyFindRequest &Request)
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)
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.
@ Unnecessary
Unused or unnecessary code.
std::optional< std::string > getDiagnosticDocURI(Diag::DiagSource Source, unsigned ID, llvm::StringRef Name)
Returns a URI providing more information about a particular diagnostic.
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
Settings that express user/project preferences and control clangd behavior.
static clangd::Key< Config > Key
Context key which can be used to set the current Config.
@ Strict
Diagnose missing and unused includes.
IncludesPolicy UnusedIncludes
struct clang::clangd::Config::@224206046260313204212274150166346126315121140114 Diagnostics
Controls warnings and errors when parsing code.
A top-level diagnostic that may have Notes and Fixes.
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 single fix-it that editor can apply to fix the error.
std::string Message
Message for the fix-it.
llvm::SmallVector< TextEdit, 1 > Edits
TextEdits from clang's fix-its. Must be non-empty.
Represents a note for the diagnostic.
int line
Line position in a document (zero-based).
The class presents a C++ symbol, e.g.
@ IndexedForCodeCompletion
Whether or not this symbol is meant to be used for the code completion.
@ Include
#include "header.h"
std::vector< std::string > ExtraArgs
static TestTU withHeaderCode(llvm::StringRef HeaderCode)
static TestTU withCode(llvm::StringRef Code)
const SymbolIndex * ExternalIndex
static URIForFile canonicalize(llvm::StringRef AbsPath, llvm::StringRef TUPath)
Canonicalizes AbsPath via URI.