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