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