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, BadSignalToKillThreadInPreamble) {
829 using pthread_t = int;
830 int pthread_kill(pthread_t thread, int sig);
833 return pthread_kill(thread, 15);
837 TU.HeaderFilename = "signal.h";
838 TU.HeaderCode =
"#define SIGTERM 15";
839 TU.ClangTidyProvider =
addTidyChecks(
"bugprone-bad-signal-to-kill-thread");
840 EXPECT_THAT(TU.build().getDiagnostics(),
841 ifTidyChecks(UnorderedElementsAre(
842 diagName(
"bugprone-bad-signal-to-kill-thread"))));
845TEST(DiagnosticTest, ClangTidyMacroToEnumCheck) {
852 std::vector<TidyProvider> Providers;
854 addTidyChecks("cppcoreguidelines-macro-to-enum,modernize-macro-to-enum"));
856 TU.ClangTidyProvider =
combine(std::move(Providers));
857 EXPECT_THAT(TU.build().getDiagnostics(), UnorderedElementsAre());
860TEST(DiagnosticTest, ElseAfterReturnRange) {
865 } [[else]] if (cond == 2) {
872 TU.ClangTidyProvider = addTidyChecks("llvm-else-after-return");
873 EXPECT_THAT(TU.build().getDiagnostics(),
874 ifTidyChecks(ElementsAre(
875 Diag(Main.range(),
"do not use 'else' after 'return'"))));
878TEST(DiagnosticTest, ClangTidySelfContainedDiags) {
887 void InitVariables() {
888 float $C[[C]]$CFix[[]];
889 double $D[[D]]$DFix[[]];
893 TU.ClangTidyProvider =
895 "cppcoreguidelines-init-variables");
896 clangd::Fix ExpectedAFix;
897 ExpectedAFix.Message =
898 "'A' should be initialized in a member initializer of the constructor";
899 ExpectedAFix.Edits.push_back(
TextEdit{Main.range(
"Fix"),
" : A(1)"});
900 ExpectedAFix.Edits.push_back(
TextEdit{Main.range(
"A"),
""});
905 clangd::Fix ExpectedBFix;
906 ExpectedBFix.Message =
907 "'B' should be initialized in a member initializer of the constructor";
908 ExpectedBFix.Edits.push_back(
TextEdit{Main.range(
"Fix"),
" : B(1)"});
909 ExpectedBFix.Edits.push_back(
TextEdit{Main.range(
"B"),
""});
911 clangd::Fix ExpectedCFix;
912 ExpectedCFix.Message =
"variable 'C' is not initialized";
913 ExpectedCFix.Edits.push_back(
TextEdit{Main.range(
"CFix"),
" = NAN"});
914 ExpectedCFix.Edits.push_back(
915 TextEdit{Main.range(
"MathHeader"),
"#include <math.h>\n\n"});
919 clangd::Fix ExpectedDFix;
920 ExpectedDFix.Message =
"variable 'D' is not initialized";
921 ExpectedDFix.Edits.push_back(
TextEdit{Main.range(
"DFix"),
" = NAN"});
922 ExpectedDFix.Edits.push_back(
923 TextEdit{Main.range(
"MathHeader"),
"#include <math.h>\n\n"});
925 TU.build().getDiagnostics(),
926 ifTidyChecks(UnorderedElementsAre(
927 AllOf(
Diag(Main.range(
"A"),
"'A' should be initialized in a member "
928 "initializer of the constructor"),
929 withFix(equalToFix(ExpectedAFix))),
930 AllOf(
Diag(Main.range(
"B"),
"'B' should be initialized in a member "
931 "initializer of the constructor"),
932 withFix(equalToFix(ExpectedBFix))),
933 AllOf(
Diag(Main.range(
"C"),
"variable 'C' is not initialized"),
934 withFix(equalToFix(ExpectedCFix))),
935 AllOf(
Diag(Main.range(
"D"),
"variable 'D' is not initialized"),
936 withFix(equalToFix(ExpectedDFix))))));
939TEST(DiagnosticTest, ClangTidySelfContainedDiagsFormatting) {
943 virtual void Reset1() = 0;
944 virtual void Reset2() = 0;
946 class A : public Interface {
947 // This will be marked by clangd to use override instead of virtual
948 $virtual1[[virtual ]]void $Reset1[[Reset1]]()$override1[[]];
949 $virtual2[[virtual ]]/**/void $Reset2[[Reset2]]()$override2[[]];
953 TU.ClangTidyProvider =
954 addTidyChecks("cppcoreguidelines-explicit-virtual-functions,");
955 clangd::Fix
const ExpectedFix1{
956 "prefer using 'override' or (rarely) 'final' "
957 "instead of 'virtual'",
958 {
TextEdit{Main.range(
"override1"),
" override"},
959 TextEdit{Main.range(
"virtual1"),
""}},
961 clangd::Fix
const ExpectedFix2{
962 "prefer using 'override' or (rarely) 'final' "
963 "instead of 'virtual'",
964 {
TextEdit{Main.range(
"override2"),
" override"},
965 TextEdit{Main.range(
"virtual2"),
""}},
969 EXPECT_THAT(TU.build().getDiagnostics(),
970 ifTidyChecks(UnorderedElementsAre(
971 AllOf(
Diag(Main.range(
"Reset1"),
972 "prefer using 'override' or (rarely) 'final' "
973 "instead of 'virtual'"),
974 withFix(equalToFix(ExpectedFix1))),
975 AllOf(
Diag(Main.range(
"Reset2"),
976 "prefer using 'override' or (rarely) 'final' "
977 "instead of 'virtual'"),
978 withFix(equalToFix(ExpectedFix2))))));
981TEST(DiagnosticsTest, ClangTidyCallingIntoPreprocessor) {
982 std::string Main = R
"cpp(
987 std::string Header = R"cpp(
988 #define EXTERN extern
992 TU.AdditionalFiles[
"b.h"] = Header;
993 TU.ClangTidyProvider =
addTidyChecks(
"modernize-use-trailing-return-type");
998TEST(DiagnosticsTest, Preprocessor) {
1007 int a = [[b]]; // error-ok
1014 ElementsAre(Diag(Test.range(), "use of undeclared identifier 'b'")));
1017TEST(DiagnosticsTest, IgnoreVerify) {
1019 int a; // expected-error {{}}
1021 TU.ExtraArgs.push_back("-Xclang");
1022 TU.ExtraArgs.push_back(
"-verify");
1023 EXPECT_THAT(TU.build().getDiagnostics(), IsEmpty());
1026TEST(DiagnosticTest, IgnoreBEFilelistOptions) {
1028 TU.ExtraArgs.push_back(
"-Xclang");
1029 for (
const auto *DisableOption :
1030 {
"-fsanitize-ignorelist=null",
"-fprofile-list=null",
1031 "-fxray-always-instrument=null",
"-fxray-never-instrument=null",
1032 "-fxray-attr-list=null"}) {
1033 TU.ExtraArgs.push_back(DisableOption);
1034 EXPECT_THAT(TU.build().getDiagnostics(), IsEmpty());
1035 TU.ExtraArgs.pop_back();
1040TEST(DiagnosticsTest, RecursivePreamble) {
1042 #include "foo.h" // error-ok
1045 TU.Filename = "foo.h";
1046 EXPECT_THAT(TU.build().getDiagnostics(),
1047 ElementsAre(diagName(
"pp_including_mainfile_in_preamble")));
1048 EXPECT_THAT(TU.build().getLocalTopLevelDecls(), SizeIs(1));
1052TEST(DiagnosticsTest, RecursivePreamblePragmaOnce) {
1058 TU.Filename = "foo.h";
1059 EXPECT_THAT(TU.build().getDiagnostics(),
1060 Not(Contains(diagName(
"pp_including_mainfile_in_preamble"))));
1061 EXPECT_THAT(TU.build().getLocalTopLevelDecls(), SizeIs(1));
1066TEST(DiagnosticsTest, RecursivePreambleIfndefGuard) {
1070 #include "foo.h" // error-ok
1074 TU.Filename = "foo.h";
1076 EXPECT_THAT(TU.build().getDiagnostics(),
1077 ElementsAre(diagName(
"pp_including_mainfile_in_preamble")));
1078 EXPECT_THAT(TU.build().getLocalTopLevelDecls(), SizeIs(1));
1081TEST(DiagnosticsTest, PreambleWithPragmaAssumeNonnull) {
1083#pragma clang assume_nonnull begin
1085#pragma clang assume_nonnull end
1087 auto AST = TU.build();
1088 EXPECT_THAT(
AST.getDiagnostics(), IsEmpty());
1089 const auto *
X = cast<FunctionDecl>(
findDecl(
AST,
"foo")).getParamDecl(0);
1090 ASSERT_TRUE(
X->getOriginalType()->getNullability() ==
1091 NullabilityKind::NonNull);
1094TEST(DiagnosticsTest, PreambleHeaderWithBadPragmaAssumeNonnull) {
1096#pragma clang assume_nonnull begin // error-ok
1100#include "foo.h" // unterminated assume_nonnull should not affect bar.
1103 TU.AdditionalFiles = {{"foo.h", std::string(Header.code())}};
1104 auto AST = TU.build();
1105 EXPECT_THAT(
AST.getDiagnostics(),
1106 ElementsAre(diagName(
"pp_eof_in_assume_nonnull")));
1107 const auto *
X = cast<FunctionDecl>(
findDecl(
AST,
"foo")).getParamDecl(0);
1108 ASSERT_TRUE(
X->getOriginalType()->getNullability() ==
1109 NullabilityKind::NonNull);
1110 const auto *Y = cast<FunctionDecl>(
findDecl(
AST,
"bar")).getParamDecl(0);
1111 ASSERT_FALSE(Y->getOriginalType()->getNullability());
1114TEST(DiagnosticsTest, InsideMacros) {
1117 #define RET(x) return x + 10
1120 RET($foo[[0]]); // error-ok
1127 ElementsAre(Diag(Test.range("foo"),
1128 "cannot initialize return object of type "
1129 "'int *' with an rvalue of type 'int'"),
1130 Diag(Test.range(
"bar"),
1131 "cannot initialize return object of type "
1132 "'int *' with an rvalue of type 'int'")));
1135TEST(DiagnosticsTest, NoFixItInMacro) {
1137 #define Define(name) void name() {}
1139 [[Define]](main) // error-ok
1142 EXPECT_THAT(TU.build().getDiagnostics(),
1143 ElementsAre(AllOf(
Diag(Test.range(),
"'main' must return 'int'"),
1147TEST(DiagnosticsTest, PragmaSystemHeader) {
1148 Annotations Test(
"#pragma clang [[system_header]]\n");
1151 TU.build().getDiagnostics(),
1153 Diag(Test.range(),
"#pragma system_header ignored in main file"))));
1154 TU.Filename =
"TestTU.h";
1155 EXPECT_THAT(TU.build().getDiagnostics(), IsEmpty());
1158TEST(ClangdTest, MSAsm) {
1161 llvm::InitializeAllTargetInfos();
1163 TU.ExtraArgs = {
"-fms-extensions"};
1164 EXPECT_THAT(TU.build().getDiagnostics(), IsEmpty());
1167TEST(DiagnosticsTest, ToLSP) {
1174 D.ID = clang::diag::err_undeclared_var_use;
1176 D.Name =
"undeclared_var_use";
1178 D.Message =
"something terrible happened";
1179 D.Range = {pos(1, 2), pos(3, 4)};
1180 D.InsideMainFile =
true;
1181 D.Severity = DiagnosticsEngine::Error;
1182 D.File =
"foo/bar/main.cpp";
1183 D.AbsFile = std::string(MainFile.file());
1184 D.OpaqueData[
"test"] =
"bar";
1186 clangd::Note NoteInMain;
1187 NoteInMain.Message =
"declared somewhere in the main file";
1188 NoteInMain.Range = {pos(5, 6), pos(7, 8)};
1189 NoteInMain.Severity = DiagnosticsEngine::Remark;
1190 NoteInMain.File =
"../foo/bar/main.cpp";
1191 NoteInMain.InsideMainFile =
true;
1192 NoteInMain.AbsFile = std::string(MainFile.file());
1194 D.Notes.push_back(NoteInMain);
1196 clangd::Note NoteInHeader;
1197 NoteInHeader.Message =
"declared somewhere in the header file";
1198 NoteInHeader.Range = {pos(9, 10), pos(11, 12)};
1199 NoteInHeader.Severity = DiagnosticsEngine::Note;
1200 NoteInHeader.File =
"../foo/baz/header.h";
1201 NoteInHeader.InsideMainFile =
false;
1202 NoteInHeader.AbsFile = std::string(
HeaderFile.file());
1203 D.Notes.push_back(NoteInHeader);
1206 F.Message =
"do something";
1207 D.Fixes.push_back(F);
1210 clangd::Diagnostic MainLSP;
1211 MainLSP.range =
D.Range;
1212 MainLSP.severity =
getSeverity(DiagnosticsEngine::Error);
1213 MainLSP.code =
"undeclared_var_use";
1214 MainLSP.source =
"clang";
1216 R
"(Something terrible happened (fix available)
1218main.cpp:6:7: remark: declared somewhere in the main file
1220../foo/baz/header.h:10:11:
1221note: declared somewhere in the header file)";
1223 MainLSP.data = D.OpaqueData;
1225 clangd::Diagnostic NoteInMainLSP;
1226 NoteInMainLSP.range = NoteInMain.Range;
1227 NoteInMainLSP.severity = getSeverity(DiagnosticsEngine::Remark);
1228 NoteInMainLSP.message = R"(Declared somewhere in the main file
1230main.cpp:2:3: error: something terrible happened)";
1234 std::vector<std::pair<clangd::Diagnostic, std::vector<clangd::Fix>>> LSPDiags;
1236 [&](clangd::Diagnostic LSPDiag, ArrayRef<clangd::Fix> Fixes) {
1238 {std::move(LSPDiag),
1239 std::vector<clangd::Fix>(Fixes.begin(), Fixes.end())});
1244 ElementsAre(Pair(equalToLSPDiag(MainLSP), ElementsAre(equalToFix(F))),
1245 Pair(equalToLSPDiag(NoteInMainLSP), IsEmpty())));
1246 EXPECT_EQ(LSPDiags[0].first.code,
"undeclared_var_use");
1247 EXPECT_EQ(LSPDiags[0].first.source,
"clang");
1248 EXPECT_EQ(LSPDiags[1].first.code,
"");
1249 EXPECT_EQ(LSPDiags[1].first.source,
"");
1253 Opts.EmitRelatedLocations =
true;
1255 [&](clangd::Diagnostic LSPDiag, ArrayRef<clangd::Fix> Fixes) {
1257 {std::move(LSPDiag),
1258 std::vector<clangd::Fix>(Fixes.begin(), Fixes.end())});
1260 MainLSP.message =
"Something terrible happened (fix available)";
1262 NoteInMainDRI.
message =
"Declared somewhere in the main file";
1263 NoteInMainDRI.location.range = NoteInMain.Range;
1264 NoteInMainDRI.location.uri = MainFile;
1265 MainLSP.relatedInformation = {NoteInMainDRI};
1267 NoteInHeaderDRI.
message =
"Declared somewhere in the header file";
1268 NoteInHeaderDRI.location.range = NoteInHeader.Range;
1270 MainLSP.relatedInformation = {NoteInMainDRI, NoteInHeaderDRI};
1271 EXPECT_THAT(LSPDiags, ElementsAre(Pair(equalToLSPDiag(MainLSP),
1272 ElementsAre(equalToFix(F)))));
1275struct SymbolWithHeader {
1277 std::string DeclaringFile;
1278 std::string IncludeHeader;
1281std::unique_ptr<SymbolIndex>
1282buildIndexWithSymbol(llvm::ArrayRef<SymbolWithHeader> Syms) {
1284 for (
const auto &S : Syms) {
1287 Sym.CanonicalDeclaration.FileURI = S.DeclaringFile.c_str();
1288 Sym.Definition.FileURI = S.DeclaringFile.c_str();
1295TEST(IncludeFixerTest, IncompleteType) {
1297 TU.ExtraArgs.push_back(
"-std=c++20");
1298 auto Index = buildIndexWithSymbol(
1299 {SymbolWithHeader{
"ns::X",
"unittest:///x.h",
"\"x.h\""}});
1300 TU.ExternalIndex = Index.get();
1302 std::vector<std::pair<llvm::StringRef, llvm::StringRef>> Tests{
1303 {
"incomplete_nested_name_spec",
"[[ns::X::]]Nested n;"},
1304 {
"incomplete_base_class",
"class Y : [[ns::X]] {};"},
1305 {
"incomplete_member_access",
"auto i = x[[->]]f();"},
1306 {
"incomplete_type",
"auto& [[[]]m] = *x;"},
1307 {
"init_incomplete_type",
1308 "struct C { static int f(ns::X&); }; int i = C::f([[{]]});"},
1309 {
"bad_cast_incomplete",
"auto a = [[static_cast]]<ns::X>(0);"},
1310 {
"template_nontype_parm_incomplete",
"template <ns::X [[foo]]> int a;"},
1311 {
"typecheck_decl_incomplete_type",
"ns::X [[var]];"},
1312 {
"typecheck_incomplete_tag",
"auto i = [[(*x)]]->f();"},
1313 {
"typecheck_nonviable_condition_incomplete",
1314 "struct A { operator ns::X(); } a; const ns::X &[[b]] = a;"},
1315 {
"invalid_incomplete_type_use",
"auto var = [[ns::X()]];"},
1316 {
"sizeof_alignof_incomplete_or_sizeless_type",
1317 "auto s = [[sizeof]](ns::X);"},
1318 {
"for_range_incomplete_type",
"void foo() { for (auto i : [[*]]x ) {} }"},
1319 {
"func_def_incomplete_result",
"ns::X [[func]] () {}"},
1320 {
"field_incomplete_or_sizeless",
"class M { ns::X [[member]]; };"},
1321 {
"array_incomplete_or_sizeless_type",
"auto s = [[(ns::X[]){}]];"},
1322 {
"call_incomplete_return",
"ns::X f(); auto fp = &f; auto z = [[fp()]];"},
1323 {
"call_function_incomplete_return",
"ns::X foo(); auto a = [[foo()]];"},
1324 {
"call_incomplete_argument",
"int m(ns::X); int i = m([[*x]]);"},
1325 {
"switch_incomplete_class_type",
"void a() { [[switch]](*x) {} }"},
1326 {
"delete_incomplete_class_type",
"void f() { [[delete]] *x; }"},
1327 {
"-Wdelete-incomplete",
"void f() { [[delete]] x; }"},
1328 {
"dereference_incomplete_type",
1329 R
"cpp(void f() { asm("" : "=r"([[*]]x)::); })cpp"},
1331 for (
auto Case : Tests) {
1333 TU.Code = Main.code().str() +
"\n // error-ok";
1335 TU.build().getDiagnostics(),
1336 ElementsAre(AllOf(diagName(Case.first), hasRange(Main.range()),
1337 withFix(
Fix(
Range{},
"#include \"x.h\"\n",
1338 "Include \"x.h\" for symbol ns::X")))))
1343TEST(IncludeFixerTest, IncompleteEnum) {
1346 Sym.CanonicalDeclaration.FileURI = Sym.Definition.FileURI =
"unittest:///x.h";
1355 TU.ExtraArgs.push_back(
"-std=c++20");
1356 TU.ExtraArgs.push_back(
"-fno-ms-compatibility");
1358 std::vector<std::pair<llvm::StringRef, llvm::StringRef>> Tests{
1359 {
"incomplete_enum",
"enum class X : int; using enum [[X]];"},
1360 {
"underlying_type_of_incomplete_enum",
1361 "[[__underlying_type]](enum X) i;"},
1363 for (
auto Case : Tests) {
1365 TU.Code = Main.code().str() +
"\n // error-ok";
1366 EXPECT_THAT(TU.build().getDiagnostics(),
1367 Contains(AllOf(diagName(Case.first), hasRange(Main.range()),
1368 withFix(
Fix(
Range{},
"#include \"x.h\"\n",
1369 "Include \"x.h\" for symbol X")))))
1374TEST(IncludeFixerTest, NoSuggestIncludeWhenNoDefinitionInHeader) {
1376$insert[[]]namespace ns {
1379class Y : $base[[public ns::X]] {};
1388 Sym.CanonicalDeclaration.FileURI =
"unittest:///x.h";
1389 Sym.Definition.FileURI =
"unittest:///x.cc";
1396 TU.ExternalIndex = Index.get();
1398 EXPECT_THAT(TU.build().getDiagnostics(),
1399 UnorderedElementsAre(
1400 Diag(Test.range(
"base"),
"base class has incomplete type"),
1401 Diag(Test.range(
"access"),
1402 "member access into incomplete type 'ns::X'")));
1405TEST(IncludeFixerTest, Typo) {
1407$insert[[]]namespace ns {
1409 $unqualified1[[X]] x;
1410 // No fix if the unresolved type is used as specifier. (ns::)X::Nested will be
1411 // considered the unresolved type.
1412 $unqualified2[[X]]::Nested n;
1414struct S : $base[[X]] {};
1417 ns::$qualified1[[X]] x; // ns:: is valid.
1418 ns::$qualified2[[X]](); // Error: no member in namespace
1420 ::$global[[Global]] glob;
1422using Type = ns::$template[[Foo]]<int>;
1425 auto Index = buildIndexWithSymbol(
1426 {SymbolWithHeader{
"ns::X",
"unittest:///x.h",
"\"x.h\""},
1427 SymbolWithHeader{
"Global",
"unittest:///global.h",
"\"global.h\""},
1428 SymbolWithHeader{
"ns::Foo",
"unittest:///foo.h",
"\"foo.h\""}});
1429 TU.ExternalIndex = Index.get();
1432 TU.build().getDiagnostics(),
1433 UnorderedElementsAre(
1434 AllOf(
Diag(Test.range(
"unqualified1"),
"unknown type name 'X'"),
1435 diagName(
"unknown_typename"),
1436 withFix(
Fix(Test.range(
"insert"),
"#include \"x.h\"\n",
1437 "Include \"x.h\" for symbol ns::X"))),
1438 Diag(Test.range(
"unqualified2"),
"use of undeclared identifier 'X'"),
1439 AllOf(
Diag(Test.range(
"qualified1"),
1440 "no type named 'X' in namespace 'ns'"),
1441 diagName(
"typename_nested_not_found"),
1442 withFix(
Fix(Test.range(
"insert"),
"#include \"x.h\"\n",
1443 "Include \"x.h\" for symbol ns::X"))),
1444 AllOf(
Diag(Test.range(
"qualified2"),
1445 "no member named 'X' in namespace 'ns'"),
1446 diagName(
"no_member"),
1447 withFix(
Fix(Test.range(
"insert"),
"#include \"x.h\"\n",
1448 "Include \"x.h\" for symbol ns::X"))),
1449 AllOf(
Diag(Test.range(
"global"),
1450 "no type named 'Global' in the global namespace"),
1451 diagName(
"typename_nested_not_found"),
1452 withFix(
Fix(Test.range(
"insert"),
"#include \"global.h\"\n",
1453 "Include \"global.h\" for symbol Global"))),
1454 AllOf(
Diag(Test.range(
"template"),
1455 "no template named 'Foo' in namespace 'ns'"),
1456 diagName(
"no_member_template"),
1457 withFix(
Fix(Test.range(
"insert"),
"#include \"foo.h\"\n",
1458 "Include \"foo.h\" for symbol ns::Foo"))),
1459 AllOf(
Diag(Test.range(
"base"),
"expected class name"),
1460 diagName(
"expected_class_name"),
1461 withFix(
Fix(Test.range(
"insert"),
"#include \"x.h\"\n",
1462 "Include \"x.h\" for symbol ns::X")))));
1465TEST(IncludeFixerTest, TypoInMacro) {
1476 auto Index = buildIndexWithSymbol(
1477 {SymbolWithHeader{
"X",
"unittest:///x.h",
"\"x.h\""},
1478 SymbolWithHeader{
"ns::X",
"unittest:///ns.h",
"\"x.h\""}});
1479 TU.ExternalIndex = Index.get();
1482 TU.ExtraArgs = {
"-fno-ms-compatibility"};
1483 EXPECT_THAT(TU.build().getDiagnostics(), Each(withFix(_)));
1486TEST(IncludeFixerTest, MultipleMatchedSymbols) {
1488$insert[[]]namespace na {
1491 $unqualified[[X]] x;
1497 auto Index = buildIndexWithSymbol(
1498 {SymbolWithHeader{
"na::X",
"unittest:///a.h",
"\"a.h\""},
1499 SymbolWithHeader{
"na::nb::X",
"unittest:///b.h",
"\"b.h\""}});
1500 TU.ExternalIndex = Index.get();
1502 EXPECT_THAT(TU.build().getDiagnostics(),
1503 UnorderedElementsAre(AllOf(
1504 Diag(Test.range(
"unqualified"),
"unknown type name 'X'"),
1505 diagName(
"unknown_typename"),
1506 withFix(
Fix(Test.range(
"insert"),
"#include \"a.h\"\n",
1507 "Include \"a.h\" for symbol na::X"),
1508 Fix(Test.range(
"insert"),
"#include \"b.h\"\n",
1509 "Include \"b.h\" for symbol na::nb::X")))));
1512TEST(IncludeFixerTest, NoCrashMemberAccess) {
1514 struct X { int xyz; };
1515 void g() { X x; x.$[[xy]]; }
1518 auto Index = buildIndexWithSymbol(
1519 SymbolWithHeader{
"na::X",
"unittest:///a.h",
"\"a.h\""});
1520 TU.ExternalIndex = Index.get();
1523 TU.build().getDiagnostics(),
1524 UnorderedElementsAre(
Diag(Test.range(),
"no member named 'xy' in 'X'")));
1527TEST(IncludeFixerTest, UseCachedIndexResults) {
1531$insert[[]]void foo() {
1554 buildIndexWithSymbol(SymbolWithHeader{
"X",
"unittest:///a.h",
"\"a.h\""});
1555 TU.ExternalIndex = Index.get();
1557 auto Parsed = TU.build();
1558 for (
const auto &D : Parsed.getDiagnostics()) {
1559 if (
D.Fixes.size() != 1) {
1560 ADD_FAILURE() <<
"D.Fixes.size() != 1";
1563 EXPECT_EQ(
D.Fixes[0].Message, std::string(
"Include \"a.h\" for symbol X"));
1567TEST(IncludeFixerTest, UnresolvedNameAsSpecifier) {
1569$insert[[]]namespace ns {
1571void g() { ns::$[[scope]]::X_Y(); }
1574 TU.Code = std::string(Test.code());
1576 TU.ExtraArgs.push_back(
"-fno-ms-compatibility");
1577 auto Index = buildIndexWithSymbol(
1578 SymbolWithHeader{
"ns::scope::X_Y",
"unittest:///x.h",
"\"x.h\""});
1579 TU.ExternalIndex = Index.get();
1582 TU.build().getDiagnostics(),
1583 UnorderedElementsAre(
1584 AllOf(
Diag(Test.range(),
"no member named 'scope' in namespace 'ns'"),
1585 diagName(
"no_member"),
1586 withFix(
Fix(Test.range(
"insert"),
"#include \"x.h\"\n",
1587 "Include \"x.h\" for symbol ns::scope::X_Y")))));
1590TEST(IncludeFixerTest, UnresolvedSpecifierWithSemaCorrection) {
1592$insert[[]]namespace clang {
1594 // "clangd::" will be corrected to "clang::" by Sema.
1595 $q1[[clangd]]::$x[[X]] x;
1596 $q2[[clangd]]::$ns[[ns]]::Y y;
1601 TU.Code = std::string(Test.code());
1603 TU.ExtraArgs.push_back(
"-fno-ms-compatibility");
1604 auto Index = buildIndexWithSymbol(
1605 {SymbolWithHeader{
"clang::clangd::X",
"unittest:///x.h",
"\"x.h\""},
1606 SymbolWithHeader{
"clang::clangd::ns::Y",
"unittest:///y.h",
"\"y.h\""}});
1607 TU.ExternalIndex = Index.get();
1610 TU.build().getDiagnostics(),
1611 UnorderedElementsAre(
1612 AllOf(
Diag(Test.range(
"q1"),
"use of undeclared identifier 'clangd'; "
1613 "did you mean 'clang'?"),
1614 diagName(
"undeclared_var_use_suggest"),
1616 Fix(Test.range(
"insert"),
"#include \"x.h\"\n",
1617 "Include \"x.h\" for symbol clang::clangd::X"))),
1618 AllOf(
Diag(Test.range(
"x"),
"no type named 'X' in namespace 'clang'"),
1619 diagName(
"typename_nested_not_found"),
1620 withFix(
Fix(Test.range(
"insert"),
"#include \"x.h\"\n",
1621 "Include \"x.h\" for symbol clang::clangd::X"))),
1623 Diag(Test.range(
"q2"),
"use of undeclared identifier 'clangd'; "
1624 "did you mean 'clang'?"),
1625 diagName(
"undeclared_var_use_suggest"),
1627 Fix(Test.range(
"insert"),
"#include \"y.h\"\n",
1628 "Include \"y.h\" for symbol clang::clangd::ns::Y"))),
1629 AllOf(
Diag(Test.range(
"ns"),
1630 "no member named 'ns' in namespace 'clang'"),
1631 diagName(
"no_member"),
1633 Fix(Test.range(
"insert"),
"#include \"y.h\"\n",
1634 "Include \"y.h\" for symbol clang::clangd::ns::Y")))));
1637TEST(IncludeFixerTest, SpecifiedScopeIsNamespaceAlias) {
1639$insert[[]]namespace a {}
1646 auto Index = buildIndexWithSymbol(
1647 SymbolWithHeader{
"a::X",
"unittest:///x.h",
"\"x.h\""});
1648 TU.ExternalIndex = Index.get();
1650 EXPECT_THAT(TU.build().getDiagnostics(),
1651 UnorderedElementsAre(AllOf(
1652 Diag(Test.range(),
"no type named 'X' in namespace 'a'"),
1653 diagName(
"typename_nested_not_found"),
1654 withFix(
Fix(Test.range(
"insert"),
"#include \"x.h\"\n",
1655 "Include \"x.h\" for symbol a::X")))));
1658TEST(IncludeFixerTest, NoCrashOnTemplateInstantiations) {
1660 template <typename T> struct Templ {
1661 template <typename U>
1662 typename U::type operator=(const U &);
1667 A() { [[a]]; /*error-ok*/ } // crash if we compute scopes lazily.
1672 auto Index = buildIndexWithSymbol({});
1673 TU.ExternalIndex = Index.get();
1676 TU.build().getDiagnostics(),
1677 ElementsAre(
Diag(Test.range(),
"use of undeclared identifier 'a'")));
1680TEST(IncludeFixerTest, HeaderNamedInDiag) {
1682 $insert[[]]int main() {
1687 TU.ExtraArgs = {
"-xc",
"-std=c99",
1688 "-Wno-error=implicit-function-declaration"};
1689 auto Index = buildIndexWithSymbol({});
1690 TU.ExternalIndex = Index.get();
1693 TU.build().getDiagnostics(),
1695 Diag(Test.range(),
"call to undeclared library function 'printf' "
1696 "with type 'int (const char *, ...)'; ISO C99 "
1697 "and later do not support implicit function "
1699 withFix(
Fix(Test.range(
"insert"),
"#include <stdio.h>\n",
1700 "Include <stdio.h> for symbol printf")))));
1702 TU.ExtraArgs = {
"-xc",
"-std=c89"};
1704 TU.build().getDiagnostics(),
1706 Diag(Test.range(),
"implicitly declaring library function 'printf' "
1707 "with type 'int (const char *, ...)'"),
1708 withFix(
Fix(Test.range(
"insert"),
"#include <stdio.h>\n",
1709 "Include <stdio.h> for symbol printf")))));
1712TEST(IncludeFixerTest, CImplicitFunctionDecl) {
1715 TU.Filename =
"test.c";
1716 TU.ExtraArgs = {
"-std=c99",
"-Wno-error=implicit-function-declaration"};
1720 Sym.CanonicalDeclaration.FileURI =
"unittest:///foo.h";
1727 TU.ExternalIndex = Index.get();
1730 TU.build().getDiagnostics(),
1733 "call to undeclared function 'foo'; ISO C99 and later do not "
1734 "support implicit function declarations"),
1735 withFix(
Fix(
Range{},
"#include \"foo.h\"\n",
1736 "Include \"foo.h\" for symbol foo")))));
1738 TU.ExtraArgs = {
"-std=c89",
"-Wall"};
1739 EXPECT_THAT(TU.build().getDiagnostics(),
1741 Diag(Test.range(),
"implicit declaration of function 'foo'"),
1742 withFix(
Fix(
Range{},
"#include \"foo.h\"\n",
1743 "Include \"foo.h\" for symbol foo")))));
1746TEST(DiagsInHeaders, DiagInsideHeader) {
1749 void foo() {})cpp");
1750 Annotations Header("[[no_type_spec]]; // error-ok");
1752 TU.AdditionalFiles = {{
"a.h", std::string(Header.code())}};
1753 EXPECT_THAT(TU.build().getDiagnostics(),
1754 UnorderedElementsAre(AllOf(
1755 Diag(Main.range(),
"in included file: a type specifier is "
1756 "required for all declarations"),
1757 withNote(
Diag(Header.range(),
"error occurred here")))));
1760TEST(DiagsInHeaders, DiagInTransitiveInclude) {
1763 void foo() {})cpp");
1765 TU.AdditionalFiles = {{"a.h",
"#include \"b.h\""},
1766 {
"b.h",
"no_type_spec; // error-ok"}};
1767 EXPECT_THAT(TU.build().getDiagnostics(),
1768 UnorderedElementsAre(
Diag(Main.range(),
1769 "in included file: a type specifier is "
1770 "required for all declarations")));
1773TEST(DiagsInHeaders, DiagInMultipleHeaders) {
1775 #include $a[["a.h"]]
1776 #include $b[["b.h"]]
1777 void foo() {})cpp");
1779 TU.AdditionalFiles = {{"a.h",
"no_type_spec; // error-ok"},
1780 {
"b.h",
"no_type_spec; // error-ok"}};
1781 EXPECT_THAT(TU.build().getDiagnostics(),
1782 UnorderedElementsAre(
1783 Diag(Main.range(
"a"),
"in included file: a type specifier is "
1784 "required for all declarations"),
1785 Diag(Main.range(
"b"),
"in included file: a type specifier is "
1786 "required for all declarations")));
1789TEST(DiagsInHeaders, PreferExpansionLocation) {
1793 void foo() {})cpp");
1795 TU.AdditionalFiles = {
1796 {"a.h",
"#include \"b.h\"\n"},
1797 {
"b.h",
"#ifndef X\n#define X\nno_type_spec; // error-ok\n#endif"}};
1798 EXPECT_THAT(TU.build().getDiagnostics(),
1799 Contains(
Diag(Main.range(),
"in included file: a type specifier "
1800 "is required for all declarations")));
1803TEST(DiagsInHeaders, PreferExpansionLocationMacros) {
1809 void foo() {})cpp");
1811 TU.AdditionalFiles = {
1812 {"a.h",
"#include \"c.h\"\n"},
1813 {
"b.h",
"#include \"c.h\"\n"},
1814 {
"c.h",
"#ifndef X\n#define X\nno_type_spec; // error-ok\n#endif"}};
1815 EXPECT_THAT(TU.build().getDiagnostics(),
1816 UnorderedElementsAre(
Diag(Main.range(),
1817 "in included file: a type specifier is "
1818 "required for all declarations")));
1821TEST(DiagsInHeaders, LimitDiagsOutsideMainFile) {
1825 void foo() {})cpp");
1827 TU.AdditionalFiles = {{"a.h",
"#include \"c.h\"\n"},
1828 {
"b.h",
"#include \"c.h\"\n"},
1832 no_type_spec_0; // error-ok
1844 EXPECT_THAT(TU.build().getDiagnostics(),
1845 UnorderedElementsAre(Diag(Main.range(),
1846 "in included file: a type specifier is "
1847 "required for all declarations")));
1850TEST(DiagsInHeaders, OnlyErrorOrFatal) {
1853 void foo() {})cpp");
1855 [[no_type_spec]]; // error-ok
1858 TU.AdditionalFiles = {{"a.h", std::string(Header.code())}};
1859 EXPECT_THAT(TU.build().getDiagnostics(),
1860 UnorderedElementsAre(AllOf(
1861 Diag(Main.range(),
"in included file: a type specifier is "
1862 "required for all declarations"),
1863 withNote(
Diag(Header.range(),
"error occurred here")))));
1866TEST(DiagsInHeaders, OnlyDefaultErrorOrFatal) {
1868 #include [["a.h"]] // get unused "foo" warning when building preamble.
1871 namespace { void foo() {} }
1872 void func() {foo();} ;)cpp");
1874 TU.AdditionalFiles = {{"a.h", std::string(Header.code())}};
1876 TU.ExtraArgs = {
"-Werror",
"-Wunused"};
1877 EXPECT_THAT(TU.build().getDiagnostics(), IsEmpty());
1880TEST(DiagsInHeaders, FromNonWrittenSources) {
1883 void foo() {})cpp");
1886 int b = [[FOO]]; // error-ok)cpp");
1888 TU.AdditionalFiles = {{"a.h", std::string(Header.code())}};
1889 TU.ExtraArgs = {
"-DFOO=NOOO"};
1890 EXPECT_THAT(TU.build().getDiagnostics(),
1891 UnorderedElementsAre(AllOf(
1893 "in included file: use of undeclared identifier 'NOOO'"),
1894 withNote(
Diag(Header.range(),
"error occurred here")))));
1897TEST(DiagsInHeaders, ErrorFromMacroExpansion) {
1907 TU.AdditionalFiles = {{"a.h", std::string(Header.code())}};
1908 EXPECT_THAT(TU.build().getDiagnostics(),
1909 UnorderedElementsAre(
1910 Diag(Main.range(),
"in included file: use of undeclared "
1911 "identifier 'foo'; did you mean 'fo'?")));
1914TEST(DiagsInHeaders, ErrorFromMacroArgument) {
1924 TU.AdditionalFiles = {{"a.h", std::string(Header.code())}};
1925 EXPECT_THAT(TU.build().getDiagnostics(),
1926 UnorderedElementsAre(
1927 Diag(Main.range(),
"in included file: use of undeclared "
1928 "identifier 'foo'; did you mean 'fo'?")));
1931TEST(IgnoreDiags, FromNonWrittenInclude) {
1933 TU.
ExtraArgs.push_back(
"--include=a.h");
1934 TU.AdditionalFiles = {{
"a.h",
"void main();"}};
1937 EXPECT_THAT(TU.build().getDiagnostics(), UnorderedElementsAre());
1940TEST(ToLSPDiag, RangeIsInMain) {
1943 D.Range = {pos(1, 2), pos(3, 4)};
1944 D.Notes.emplace_back();
1945 Note &N =
D.Notes.back();
1946 N.Range = {pos(2, 3), pos(3, 4)};
1948 D.InsideMainFile =
true;
1949 N.InsideMainFile =
false;
1951 [&](clangd::Diagnostic LSPDiag, ArrayRef<clangd::Fix>) {
1952 EXPECT_EQ(LSPDiag.range,
D.Range);
1955 D.InsideMainFile =
false;
1956 N.InsideMainFile =
true;
1958 [&](clangd::Diagnostic LSPDiag, ArrayRef<clangd::Fix>) {
1959 EXPECT_EQ(LSPDiag.range, N.Range);
1963TEST(ParsedASTTest, ModuleSawDiag) {
1966 auto AST = TU.build();
1968 EXPECT_THAT(
AST.getDiagnostics(),
1969 testing::Contains(
Diag(Code.range(), KDiagMsg.str())));
1978 TU.Code =
"#define FOO\n void bar();\n";
1979 auto AST = TU.build();
1980 EXPECT_THAT(
AST.getDiagnostics(), IsEmpty());
1984 TU.Code = Code.code().str();
1985 auto AST = TU.build();
1987 AST.getDiagnostics(),
1988 testing::Contains(
Diag(Code.range(),
"no newline at end of file")));
1992TEST(Diagnostics, Tags) {
1994 TU.
ExtraArgs = {
"-Wunused",
"-Wdeprecated"};
1996 void bar() __attribute__((deprecated));
1999 $deprecated[[bar]]();
2001 TU.Code = Test.code().str();
2002 EXPECT_THAT(TU.build().getDiagnostics(),
2003 UnorderedElementsAre(
2004 AllOf(Diag(Test.range("unused"),
"unused variable 'x'"),
2006 AllOf(
Diag(Test.range(
"deprecated"),
"'bar' is deprecated"),
2010 $typedef[[typedef int INT]];
2012 TU.Code = Test.code();
2013 TU.ClangTidyProvider = addTidyChecks("modernize-use-using");
2015 TU.build().getDiagnostics(),
2016 ifTidyChecks(UnorderedElementsAre(
2017 AllOf(
Diag(Test.range(
"typedef"),
"use 'using' instead of 'typedef'"),
2021TEST(Diagnostics, TidyDiagsArentAffectedFromWerror) {
2024 Annotations Test(R
"cpp($typedef[[typedef int INT]]; // error-ok)cpp");
2025 TU.Code = Test.code().str();
2026 TU.ClangTidyProvider = addTidyChecks("modernize-use-using");
2028 TU.build().getDiagnostics(),
2029 ifTidyChecks(UnorderedElementsAre(
2030 AllOf(
Diag(Test.range(
"typedef"),
"use 'using' instead of 'typedef'"),
2033 diagSeverity(DiagnosticsEngine::Warning)))));
2035 TU.ClangTidyProvider =
2038 TU.build().getDiagnostics(),
2039 ifTidyChecks(UnorderedElementsAre(
2040 AllOf(
Diag(Test.range(
"typedef"),
"use 'using' instead of 'typedef'"),
2042 diagSeverity(DiagnosticsEngine::Error)))));
2045TEST(Diagnostics, DeprecatedDiagsAreHints) {
2047 std::optional<clangd::Diagnostic>
Diag;
2049 D.Range = {pos(1, 2), pos(3, 4)};
2050 D.InsideMainFile =
true;
2054 D.Severity = DiagnosticsEngine::Warning;
2056 [&](clangd::Diagnostic LSPDiag, ArrayRef<clangd::Fix>) {
2057 Diag = std::move(LSPDiag);
2063 D.Severity = DiagnosticsEngine::Error;
2065 [&](clangd::Diagnostic LSPDiag, ArrayRef<clangd::Fix>) {
2066 Diag = std::move(LSPDiag);
2073 D.Severity = DiagnosticsEngine::Warning;
2075 [&](clangd::Diagnostic LSPDiag, ArrayRef<clangd::Fix>) {
2076 Diag = std::move(LSPDiag);
2081TEST(DiagnosticsTest, IncludeCleaner) {
2083$fix[[ $diag[[#include "unused.h"]]
2089 #include <system_header.h>
2096 TU.Code = Test.code().str();
2097 TU.AdditionalFiles["unused.h"] = R
"cpp(
2101 TU.AdditionalFiles["used.h"] = R
"cpp(
2105 TU.AdditionalFiles["ignore.h"] = R
"cpp(
2109 TU.AdditionalFiles["system/system_header.h"] =
"";
2110 TU.ExtraArgs = {
"-isystem" +
testPath(
"system")};
2114 Cfg.Diagnostics.Includes.IgnoreHeader.emplace_back(
2115 [](llvm::StringRef Header) {
return Header.ends_with(
"ignore.h"); });
2117 auto AST = TU.build();
2119 AST.getDiagnostics(),
2121 Diag(Test.range(
"diag"),
2122 "included header unused.h is not used directly"),
2124 withFix(
Fix(Test.range(
"fix"),
"",
"remove #include directive")))));
2125 auto &
Diag =
AST.getDiagnostics().front();
2127 llvm::ValueIs(Not(IsEmpty())));
2128 Cfg.Diagnostics.SuppressAll =
true;
2130 EXPECT_THAT(TU.build().getDiagnostics(), IsEmpty());
2131 Cfg.Diagnostics.SuppressAll =
false;
2132 Cfg.Diagnostics.Suppress = {
"unused-includes"};
2134 EXPECT_THAT(TU.build().getDiagnostics(), IsEmpty());
2137TEST(DiagnosticsTest, FixItFromHeader) {
2138 llvm::StringLiteral Header(R
"cpp(
2140 void foo(int *, int);)cpp");
2145 $diag[[foo]]($fix[[]]x, 1);
2148 TU.Code = Source.code().str();
2149 TU.HeaderCode = Header.str();
2151 TU.build().getDiagnostics(),
2152 UnorderedElementsAre(AllOf(
2153 Diag(Source.range("diag"),
"no matching function for call to 'foo'"),
2154 withFix(
Fix(Source.range(
"fix"),
"&",
2155 "candidate function not viable: no known conversion from "
2156 "'int' to 'int *' for 1st argument; take the address of "
2157 "the argument with &")))));
2160TEST(DiagnosticsTest, UnusedInHeader) {
2163 TU.ExtraArgs.push_back(
"-Wunused-function");
2164 TU.Filename =
"test.c";
2165 EXPECT_THAT(TU.build().getDiagnostics(),
2166 ElementsAre(withID(diag::warn_unused_function)));
2169 TU.Filename =
"test.h";
2170 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.
struct clang::clangd::Config::@343034053122374337352226322054223376344037116252 Diagnostics
Controls warnings and errors when parsing code.
IncludesPolicy UnusedIncludes
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.