clang-tools 10.0.0

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 "Annotations.h"
10 #include "Diagnostics.h"
11 #include "ParsedAST.h"
12 #include "Path.h"
13 #include "Protocol.h"
14 #include "SourceCode.h"
15 #include "TestFS.h"
16 #include "TestIndex.h"
17 #include "TestTU.h"
18 #include "index/MemIndex.h"
19 #include "clang/Basic/Diagnostic.h"
20 #include "clang/Basic/DiagnosticSema.h"
21 #include "llvm/Support/ScopedPrinter.h"
22 #include "llvm/Support/TargetSelect.h"
23 #include "gmock/gmock.h"
24 #include "gtest/gtest.h"
25 #include <algorithm>
26
27 namespace clang {
28 namespace clangd {
29 namespace {
30
31 using ::testing::_;
32 using ::testing::ElementsAre;
33 using ::testing::Field;
34 using ::testing::IsEmpty;
35 using ::testing::Pair;
36 using ::testing::UnorderedElementsAre;
37
38 ::testing::Matcher<const Diag &> WithFix(::testing::Matcher<Fix> FixMatcher) {
39 return Field(&Diag::Fixes, ElementsAre(FixMatcher));
40 }
41
42 ::testing::Matcher<const Diag &> WithFix(::testing::Matcher<Fix> FixMatcher1,
43 ::testing::Matcher<Fix> FixMatcher2) {
44 return Field(&Diag::Fixes, UnorderedElementsAre(FixMatcher1, FixMatcher2));
45 }
46
47 ::testing::Matcher<const Diag &>
48 WithNote(::testing::Matcher<Note> NoteMatcher) {
49 return Field(&Diag::Notes, ElementsAre(NoteMatcher));
50 }
51
52 MATCHER_P2(Diag, Range, Message,
53 "Diag at " + llvm::to_string(Range) + " = [" + Message + "]") {
54 return arg.Range == Range && arg.Message == Message;
55 }
56
57 MATCHER_P3(Fix, Range, Replacement, Message,
58 "Fix " + llvm::to_string(Range) + " => " +
59 ::testing::PrintToString(Replacement) + " = [" + Message + "]") {
60 return arg.Message == Message && arg.Edits.size() == 1 &&
61 arg.Edits[0].range == Range && arg.Edits[0].newText == Replacement;
62 }
63
64 MATCHER_P(FixMessage, Message, "") {
65 return arg.Message == Message;
66 }
67
68 MATCHER_P(EqualToLSPDiag, LSPDiag,
69 "LSP diagnostic " + llvm::to_string(LSPDiag)) {
70 if (toJSON(arg) != toJSON(LSPDiag)) {
71 *result_listener << llvm::formatv("expected:\n{0:2}\ngot\n{1:2}",
72 toJSON(LSPDiag), toJSON(arg))
73 .str();
74 return false;
75 }
76 return true;
77 }
78
79 MATCHER_P(DiagSource, S, "") { return arg.Source == S; }
80 MATCHER_P(DiagName, N, "") { return arg.Name == N; }
81 MATCHER_P(DiagSeverity, S, "") { return arg.Severity == S; }
82
83 MATCHER_P(EqualToFix, Fix, "LSP fix " + llvm::to_string(Fix)) {
84 if (arg.Message != Fix.Message)
85 return false;
86 if (arg.Edits.size() != Fix.Edits.size())
87 return false;
88 for (std::size_t I = 0; I < arg.Edits.size(); ++I) {
89 if (arg.Edits[I].range != Fix.Edits[I].range ||
90 arg.Edits[I].newText != Fix.Edits[I].newText)
91 return false;
92 }
93 return true;
94 }
95
96 // Helper function to make tests shorter.
97 Position pos(int line, int character) {
98 Position Res;
99 Res.line = line;
100 Res.character = character;
101 return Res;
102 }
103
104 TEST(DiagnosticsTest, DiagnosticRanges) {
105 // Check we report correct ranges, including various edge-cases.
106 Annotations Test(R"cpp(
107 namespace test{};
108 void $decl[[foo]]();
109 class T{$explicit[[]]$constructor[[T]](int a);};
110 int main() {
111 $typo[[go\
112 o]]();
113 foo()$semicolon[[]]//with comments
114 $unk[[unknown]]();
115 double $type[[bar]] = "foo";
116 struct Foo { int x; }; Foo a;
117 a.$nomember[[y]];
118 test::$nomembernamespace[[test]];
119 }
120 )cpp");
121 auto TU = TestTU::withCode(Test.code());
122 TU.ClangTidyChecks = "-*,google-explicit-constructor";
123 EXPECT_THAT(
124 TU.build().getDiagnostics(),
125 ElementsAre(
126 // This range spans lines.
127 AllOf(Diag(Test.range("typo"),
128 "use of undeclared identifier 'goo'; did you mean 'foo'?"),
129 DiagSource(Diag::Clang), DiagName("undeclared_var_use_suggest"),
130 WithFix(
131 Fix(Test.range("typo"), "foo", "change 'go\\…' to 'foo'")),
132 // This is a pretty normal range.
133 WithNote(Diag(Test.range("decl"), "'foo' declared here"))),
134 // This range is zero-width and insertion. Therefore make sure we are
135 // not expanding it into other tokens. Since we are not going to
136 // replace those.
137 AllOf(Diag(Test.range("semicolon"), "expected ';' after expression"),
138 WithFix(Fix(Test.range("semicolon"), ";", "insert ';'"))),
139 // This range isn't provided by clang, we expand to the token.
140 Diag(Test.range("unk"), "use of undeclared identifier 'unknown'"),
141 Diag(Test.range("type"),
142 "cannot initialize a variable of type 'double' with an lvalue "
143 "of type 'const char [4]'"),
144 Diag(Test.range("nomember"), "no member named 'y' in 'Foo'"),
145 Diag(Test.range("nomembernamespace"),
146 "no member named 'test' in namespace 'test'"),
147 // We make sure here that the entire token is highlighted
148 AllOf(Diag(Test.range("constructor"),
149 "single-argument constructors must be marked explicit to "
150 "avoid unintentional implicit conversions"),
151 WithFix(Fix(Test.range("explicit"), "explicit ",
152 "insert 'explicit '")))));
153 }
154
155 TEST(DiagnosticsTest, FlagsMatter) {
156 Annotations Test("[[void]] main() {}");
157 auto TU = TestTU::withCode(Test.code());
158 EXPECT_THAT(TU.build().getDiagnostics(),
159 ElementsAre(AllOf(Diag(Test.range(), "'main' must return 'int'"),
160 WithFix(Fix(Test.range(), "int",
161 "change 'void' to 'int'")))));
162 // Same code built as C gets different diagnostics.
163 TU.Filename = "Plain.c";
164 EXPECT_THAT(
165 TU.build().getDiagnostics(),
166 ElementsAre(AllOf(
167 Diag(Test.range(), "return type of 'main' is not 'int'"),
168 WithFix(Fix(Test.range(), "int", "change return type to 'int'")))));
169 }
170
171 TEST(DiagnosticsTest, DiagnosticPreamble) {
172 Annotations Test(R"cpp(
173 #include $[["not-found.h"]]
174 )cpp");
175
176 auto TU = TestTU::withCode(Test.code());
177 EXPECT_THAT(TU.build().getDiagnostics(),
178 ElementsAre(::testing::AllOf(
179 Diag(Test.range(), "'not-found.h' file not found"),
180 DiagSource(Diag::Clang), DiagName("pp_file_not_found"))));
181 }
182
183 TEST(DiagnosticsTest, DeduplicatedClangTidyDiagnostics) {
184 Annotations Test(R"cpp(
185 float foo = [[0.1f]];
186 )cpp");
187 auto TU = TestTU::withCode(Test.code());
188 // Enable alias clang-tidy checks, these check emits the same diagnostics
189 // (except the check name).
190 TU.ClangTidyChecks = "-*, readability-uppercase-literal-suffix, "
191 "hicpp-uppercase-literal-suffix";
192 // Verify that we filter out the duplicated diagnostic message.
193 EXPECT_THAT(
194 TU.build().getDiagnostics(),
195 UnorderedElementsAre(::testing::AllOf(
196 Diag(Test.range(),
197 "floating point literal has suffix 'f', which is not uppercase"),
198 DiagSource(Diag::ClangTidy))));
199
200 Test = Annotations(R"cpp(
201 template<typename T>
202 void func(T) {
203 float f = [[0.3f]];
204 }
205 void k() {
206 func(123);
207 func(2.0);
208 }
209 )cpp");
210 TU.Code = Test.code();
211 // The check doesn't handle template instantiations which ends up emitting
212 // duplicated messages, verify that we deduplicate them.
213 EXPECT_THAT(
214 TU.build().getDiagnostics(),
215 UnorderedElementsAre(::testing::AllOf(
216 Diag(Test.range(),
217 "floating point literal has suffix 'f', which is not uppercase"),
218 DiagSource(Diag::ClangTidy))));
219 }
220
221 TEST(DiagnosticsTest, ClangTidy) {
222 Annotations Test(R"cpp(
223 #include $deprecated[["assert.h"]]
224
225 #define $macrodef[[SQUARE]](X) (X)*(X)
226 int $main[[main]]() {
227 int y = 4;
228 return SQUARE($macroarg[[++]]y);
229 return $doubled[[sizeof]](sizeof(int));
230 }
231 )cpp");
232 auto TU = TestTU::withCode(Test.code());
233 TU.HeaderFilename = "assert.h"; // Suppress "not found" error.
234 TU.ClangTidyChecks =
235 "-*, bugprone-sizeof-expression, bugprone-macro-repeated-side-effects, "
236 "modernize-deprecated-headers, modernize-use-trailing-return-type";
237 EXPECT_THAT(
238 TU.build().getDiagnostics(),
239 UnorderedElementsAre(
240 AllOf(Diag(Test.range("deprecated"),
241 "inclusion of deprecated C++ header 'assert.h'; consider "
242 "using 'cassert' instead"),
243 DiagSource(Diag::ClangTidy),
244 DiagName("modernize-deprecated-headers"),
245 WithFix(Fix(Test.range("deprecated"), "<cassert>",
246 "change '\"assert.h\"' to '<cassert>'"))),
247 Diag(Test.range("doubled"),
248 "suspicious usage of 'sizeof(sizeof(...))'"),
249 AllOf(
250 Diag(Test.range("macroarg"),
251 "side effects in the 1st macro argument 'X' are repeated in "
252 "macro expansion"),
253 DiagSource(Diag::ClangTidy),
254 DiagName("bugprone-macro-repeated-side-effects"),
255 WithNote(
256 Diag(Test.range("macrodef"), "macro 'SQUARE' defined here"))),
257 Diag(Test.range("macroarg"),
258 "multiple unsequenced modifications to 'y'"),
259 AllOf(
260 Diag(Test.range("main"),
261 "use a trailing return type for this function"),
262 DiagSource(Diag::ClangTidy),
263 DiagName("modernize-use-trailing-return-type"),
264 // Verify that we don't have "[check-name]" suffix in the message.
265 WithFix(FixMessage("use a trailing return type for this function")))
266 ));
267 }
268
269 TEST(DiagnosticTest, ClangTidySuppressionComment) {
270 Annotations Main(R"cpp(
271 int main() {
272 int i = 3;
273 double d = 8 / i; // NOLINT
274 // NOLINTNEXTLINE
275 double e = 8 / i;
276 double f = [[8]] / i;
277 }
278 )cpp");
279 TestTU TU = TestTU::withCode(Main.code());
280 TU.ClangTidyChecks = "bugprone-integer-division";
281 EXPECT_THAT(
282 TU.build().getDiagnostics(),
283 UnorderedElementsAre(::testing::AllOf(
284 Diag(Main.range(), "result of integer division used in a floating "
285 "point context; possible loss of precision"),
286 DiagSource(Diag::ClangTidy), DiagName("bugprone-integer-division"))));
287 }
288
289 TEST(DiagnosticTest, ClangTidyWarningAsError) {
290 Annotations Main(R"cpp(
291 int main() {
292 int i = 3;
293 double f = [[8]] / i;
294 }
295 )cpp");
296 TestTU TU = TestTU::withCode(Main.code());
297 TU.ClangTidyChecks = "bugprone-integer-division";
298 TU.ClangTidyWarningsAsErrors = "bugprone-integer-division";
299 EXPECT_THAT(
300 TU.build().getDiagnostics(),
301 UnorderedElementsAre(::testing::AllOf(
302 Diag(Main.range(), "result of integer division used in a floating "
303 "point context; possible loss of precision"),
304 DiagSource(Diag::ClangTidy), DiagName("bugprone-integer-division"),
305 DiagSeverity(DiagnosticsEngine::Error))));
306 }
307
308 TEST(DiagnosticTest, LongFixMessages) {
309 // We limit the size of printed code.
310 Annotations Source(R"cpp(
311 int main() {
312 int somereallyreallyreallyreallyreallyreallyreallyreallylongidentifier;
313 [[omereallyreallyreallyreallyreallyreallyreallyreallylongidentifier]]= 10;
314 }
315 )cpp");
316 TestTU TU = TestTU::withCode(Source.code());
317 EXPECT_THAT(
318 TU.build().getDiagnostics(),
319 ElementsAre(WithFix(Fix(
320 Source.range(),
321 "somereallyreallyreallyreallyreallyreallyreallyreallylongidentifier",
322 "change 'omereallyreallyreallyreallyreallyreallyreallyreall…' to "
323 "'somereallyreallyreallyreallyreallyreallyreallyreal…'"))));
324 // Only show changes up to a first newline.
325 Source = Annotations(R"cpp(
326 int main() {
327 int ident;
328 [[ide\
329 n]] = 10;
330 }
331 )cpp");
332 TU = TestTU::withCode(Source.code());
333 EXPECT_THAT(TU.build().getDiagnostics(),
334 ElementsAre(WithFix(
335 Fix(Source.range(), "ident", "change 'ide\\…' to 'ident'"))));
336 }
337
338 TEST(DiagnosticTest, ClangTidyWarningAsErrorTrumpsSuppressionComment) {
339 Annotations Main(R"cpp(
340 int main() {
341 int i = 3;
342 double f = [[8]] / i; // NOLINT
343 }
344 )cpp");
345 TestTU TU = TestTU::withCode(Main.code());
346 TU.ClangTidyChecks = "bugprone-integer-division";
347 TU.ClangTidyWarningsAsErrors = "bugprone-integer-division";
348 EXPECT_THAT(
349 TU.build().getDiagnostics(),
350 UnorderedElementsAre(::testing::AllOf(
351 Diag(Main.range(), "result of integer division used in a floating "
352 "point context; possible loss of precision"),
353 DiagSource(Diag::ClangTidy), DiagName("bugprone-integer-division"),
354 DiagSeverity(DiagnosticsEngine::Error))));
355 }
356
357 TEST(DiagnosticsTest, Preprocessor) {
358 // This looks like a preamble, but there's an #else in the middle!
359 // Check that:
360 // - the #else doesn't generate diagnostics (we had this bug)
361 // - we get diagnostics from the taken branch
362 // - we get no diagnostics from the not taken branch
363 Annotations Test(R"cpp(
364 #ifndef FOO
365 #define FOO
366 int a = [[b]];
367 #else
368 int x = y;
369 #endif
370 )cpp");
371 EXPECT_THAT(
372 TestTU::withCode(Test.code()).build().getDiagnostics(),
373 ElementsAre(Diag(Test.range(), "use of undeclared identifier 'b'")));
374 }
375
376 TEST(DiagnosticsTest, InsideMacros) {
377 Annotations Test(R"cpp(
378 #define TEN 10
379 #define RET(x) return x + 10
380
381 int* foo() {
382 RET($foo[[0]]);
383 }
384 int* bar() {
385 return $bar[[TEN]];
386 }
387 )cpp");
388 EXPECT_THAT(TestTU::withCode(Test.code()).build().getDiagnostics(),
389 ElementsAre(Diag(Test.range("foo"),
390 "cannot initialize return object of type "
391 "'int *' with an rvalue of type 'int'"),
392 Diag(Test.range("bar"),
393 "cannot initialize return object of type "
394 "'int *' with an rvalue of type 'int'")));
395 }
396
397 TEST(DiagnosticsTest, NoFixItInMacro) {
398 Annotations Test(R"cpp(
399 #define Define(name) void name() {}
400
401 [[Define]](main)
402 )cpp");
403 auto TU = TestTU::withCode(Test.code());
404 EXPECT_THAT(TU.build().getDiagnostics(),
405 ElementsAre(AllOf(Diag(Test.range(), "'main' must return 'int'"),
406 Not(WithFix(_)))));
407 }
408
409 TEST(ClangdTest, MSAsm) {
410 // Parsing MS assembly tries to use the target MCAsmInfo, which we don't link.
411 // We used to crash here. Now clang emits a diagnostic, which we filter out.
412 llvm::InitializeAllTargetInfos(); // As in ClangdMain
413 auto TU = TestTU::withCode("void fn() { __asm { cmp cl,64 } }");
414 TU.ExtraArgs = {"-fms-extensions"};
415 EXPECT_THAT(TU.build().getDiagnostics(), IsEmpty());
416 }
417
418 TEST(DiagnosticsTest, ToLSP) {
419 URIForFile MainFile =
420 URIForFile::canonicalize(testPath("foo/bar/main.cpp"), "");
421 URIForFile HeaderFile =
422 URIForFile::canonicalize(testPath("foo/bar/header.h"), "");
423
424 clangd::Diag D;
425 D.ID = clang::diag::err_enum_class_reference;
426 D.Name = "enum_class_reference";
427 D.Source = clangd::Diag::Clang;
428 D.Message = "something terrible happened";
429 D.Range = {pos(1, 2), pos(3, 4)};
430 D.InsideMainFile = true;
431 D.Severity = DiagnosticsEngine::Error;
432 D.File = "foo/bar/main.cpp";
433 D.AbsFile = MainFile.file();
434
435 clangd::Note NoteInMain;
436 NoteInMain.Message = "declared somewhere in the main file";
437 NoteInMain.Range = {pos(5, 6), pos(7, 8)};
438 NoteInMain.Severity = DiagnosticsEngine::Remark;
439 NoteInMain.File = "../foo/bar/main.cpp";
440 NoteInMain.InsideMainFile = true;
441 NoteInMain.AbsFile = MainFile.file();
442
443 D.Notes.push_back(NoteInMain);
444
445 clangd::Note NoteInHeader;
446 NoteInHeader.Message = "declared somewhere in the header file";
447 NoteInHeader.Range = {pos(9, 10), pos(11, 12)};
448 NoteInHeader.Severity = DiagnosticsEngine::Note;
449 NoteInHeader.File = "../foo/baz/header.h";
450 NoteInHeader.InsideMainFile = false;
451 NoteInHeader.AbsFile = HeaderFile.file();
452 D.Notes.push_back(NoteInHeader);
453
454 clangd::Fix F;
455 F.Message = "do something";
456 D.Fixes.push_back(F);
457
458 // Diagnostics should turn into these:
459 clangd::Diagnostic MainLSP;
460 MainLSP.range = D.Range;
461 MainLSP.severity = getSeverity(DiagnosticsEngine::Error);
462 MainLSP.code = "enum_class_reference";
463 MainLSP.source = "clang";
464 MainLSP.message =
465 R"(Something terrible happened (fix available)
466
467 main.cpp:6:7: remark: declared somewhere in the main file
468
469 ../foo/baz/header.h:10:11:
470 note: declared somewhere in the header file)";
471
472 clangd::Diagnostic NoteInMainLSP;
473 NoteInMainLSP.range = NoteInMain.Range;
474 NoteInMainLSP.severity = getSeverity(DiagnosticsEngine::Remark);
475 NoteInMainLSP.message = R"(Declared somewhere in the main file
476
477 main.cpp:2:3: error: something terrible happened)";
478
479 ClangdDiagnosticOptions Opts;
480 // Transform diagnostics and check the results.
481 std::vector<std::pair<clangd::Diagnostic, std::vector<clangd::Fix>>> LSPDiags;
482 toLSPDiags(D, MainFile, Opts,
483 [&](clangd::Diagnostic LSPDiag, ArrayRef<clangd::Fix> Fixes) {
484 LSPDiags.push_back(
485 {std::move(LSPDiag),
486 std::vector<clangd::Fix>(Fixes.begin(), Fixes.end())});
487 });
488
489 EXPECT_THAT(
490 LSPDiags,
491 ElementsAre(Pair(EqualToLSPDiag(MainLSP), ElementsAre(EqualToFix(F))),
492 Pair(EqualToLSPDiag(NoteInMainLSP), IsEmpty())));
493 EXPECT_EQ(LSPDiags[0].first.code, "enum_class_reference");
494 EXPECT_EQ(LSPDiags[0].first.source, "clang");
495 EXPECT_EQ(LSPDiags[1].first.code, "");
496 EXPECT_EQ(LSPDiags[1].first.source, "");
497
498 // Same thing, but don't flatten notes into the main list.
499 LSPDiags.clear();
500 Opts.EmitRelatedLocations = true;
501 toLSPDiags(D, MainFile, Opts,
502 [&](clangd::Diagnostic LSPDiag, ArrayRef<clangd::Fix> Fixes) {
503 LSPDiags.push_back(
504 {std::move(LSPDiag),
505 std::vector<clangd::Fix>(Fixes.begin(), Fixes.end())});
506 });
507 MainLSP.message = "Something terrible happened (fix available)";
508 DiagnosticRelatedInformation NoteInMainDRI;
509 NoteInMainDRI.message = "Declared somewhere in the main file";
510 NoteInMainDRI.location.range = NoteInMain.Range;
511 NoteInMainDRI.location.uri = MainFile;
512 MainLSP.relatedInformation = {NoteInMainDRI};
513 DiagnosticRelatedInformation NoteInHeaderDRI;
514 NoteInHeaderDRI.message = "Declared somewhere in the header file";
515 NoteInHeaderDRI.location.range = NoteInHeader.Range;
516 NoteInHeaderDRI.location.uri = HeaderFile;
517 MainLSP.relatedInformation = {NoteInMainDRI, NoteInHeaderDRI};
518 EXPECT_THAT(LSPDiags, ElementsAre(Pair(EqualToLSPDiag(MainLSP),
519 ElementsAre(EqualToFix(F)))));
520 }
521
522 struct SymbolWithHeader {
523 std::string QName;
524 std::string DeclaringFile;
525 std::string IncludeHeader;
526 };
527
528 std::unique_ptr<SymbolIndex>
529 buildIndexWithSymbol(llvm::ArrayRef<SymbolWithHeader> Syms) {
530 SymbolSlab::Builder Slab;
531 for (const auto &S : Syms) {
532 Symbol Sym = cls(S.QName);
533 Sym.Flags |= Symbol::IndexedForCodeCompletion;
534 Sym.CanonicalDeclaration.FileURI = S.DeclaringFile.c_str();
535 Sym.Definition.FileURI = S.DeclaringFile.c_str();
536 Sym.IncludeHeaders.emplace_back(S.IncludeHeader, 1);
537 Slab.insert(Sym);
538 }
539 return MemIndex::build(std::move(Slab).build(), RefSlab(), RelationSlab());
540 }
541
542 TEST(IncludeFixerTest, IncompleteType) {
543 Annotations Test(R"cpp(
544 $insert[[]]namespace ns {
545 class X;
546 $nested[[X::]]Nested n;
547 }
548 class Y : $base[[public ns::X]] {};
549 int main() {
550 ns::X *x;
551 x$access[[->]]f();
552 }
553 )cpp");
554 auto TU = TestTU::withCode(Test.code());
555 auto Index = buildIndexWithSymbol(
556 {SymbolWithHeader{"ns::X", "unittest:///x.h", "\"x.h\""}});
557 TU.ExternalIndex = Index.get();
558
559 EXPECT_THAT(
560 TU.build().getDiagnostics(),
561 UnorderedElementsAre(
562 AllOf(Diag(Test.range("nested"),
563 "incomplete type 'ns::X' named in nested name specifier"),
564 WithFix(Fix(Test.range("insert"), "#include \"x.h\"\n",
565 "Add include \"x.h\" for symbol ns::X"))),
566 AllOf(Diag(Test.range("base"), "base class has incomplete type"),
567 WithFix(Fix(Test.range("insert"), "#include \"x.h\"\n",
568 "Add include \"x.h\" for symbol ns::X"))),
569 AllOf(Diag(Test.range("access"),
570 "member access into incomplete type 'ns::X'"),
571 WithFix(Fix(Test.range("insert"), "#include \"x.h\"\n",
572 "Add include \"x.h\" for symbol ns::X")))));
573 }
574
575 TEST(IncludeFixerTest, NoSuggestIncludeWhenNoDefinitionInHeader) {
576 Annotations Test(R"cpp(
577 $insert[[]]namespace ns {
578 class X;
579 }
580 class Y : $base[[public ns::X]] {};
581 int main() {
582 ns::X *x;
583 x$access[[->]]f();
584 }
585 )cpp");
586 auto TU = TestTU::withCode(Test.code());
587 Symbol Sym = cls("ns::X");
588 Sym.Flags |= Symbol::IndexedForCodeCompletion;
589 Sym.CanonicalDeclaration.FileURI = "unittest:///x.h";
590 Sym.Definition.FileURI = "unittest:///x.cc";
591 Sym.IncludeHeaders.emplace_back("\"x.h\"", 1);
592
593 SymbolSlab::Builder Slab;
594 Slab.insert(Sym);
595 auto Index =
596 MemIndex::build(std::move(Slab).build(), RefSlab(), RelationSlab());
597 TU.ExternalIndex = Index.get();
598
599 EXPECT_THAT(TU.build().getDiagnostics(),
600 UnorderedElementsAre(
601 Diag(Test.range("base"), "base class has incomplete type"),
602 Diag(Test.range("access"),
603 "member access into incomplete type 'ns::X'")));
604 }
605
606 TEST(IncludeFixerTest, Typo) {
607 Annotations Test(R"cpp(
608 $insert[[]]namespace ns {
609 void foo() {
610 $unqualified1[[X]] x;
611 // No fix if the unresolved type is used as specifier. (ns::)X::Nested will be
612 // considered the unresolved type.
613 $unqualified2[[X]]::Nested n;
614 }
615 }
616 void bar() {
617 ns::$qualified1[[X]] x; // ns:: is valid.
618 ns::$qualified2[[X]](); // Error: no member in namespace
619
620 ::$global[[Global]] glob;
621 }
622 )cpp");
623 auto TU = TestTU::withCode(Test.code());
624 auto Index = buildIndexWithSymbol(
625 {SymbolWithHeader{"ns::X", "unittest:///x.h", "\"x.h\""},
626 SymbolWithHeader{"Global", "unittest:///global.h", "\"global.h\""}});
627 TU.ExternalIndex = Index.get();
628
629 EXPECT_THAT(
630 TU.build().getDiagnostics(),
631 UnorderedElementsAre(
632 AllOf(Diag(Test.range("unqualified1"), "unknown type name 'X'"),
633 WithFix(Fix(Test.range("insert"), "#include \"x.h\"\n",
634 "Add include \"x.h\" for symbol ns::X"))),
635 Diag(Test.range("unqualified2"), "use of undeclared identifier 'X'"),
636 AllOf(Diag(Test.range("qualified1"),
637 "no type named 'X' in namespace 'ns'"),
638 WithFix(Fix(Test.range("insert"), "#include \"x.h\"\n",
639 "Add include \"x.h\" for symbol ns::X"))),
640 AllOf(Diag(Test.range("qualified2"),
641 "no member named 'X' in namespace 'ns'"),
642 WithFix(Fix(Test.range("insert"), "#include \"x.h\"\n",
643 "Add include \"x.h\" for symbol ns::X"))),
644 AllOf(Diag(Test.range("global"),
645 "no type named 'Global' in the global namespace"),
646 WithFix(Fix(Test.range("insert"), "#include \"global.h\"\n",
647 "Add include \"global.h\" for symbol Global")))));
648 }
649
650 TEST(IncludeFixerTest, MultipleMatchedSymbols) {
651 Annotations Test(R"cpp(
652 $insert[[]]namespace na {
653 namespace nb {
654 void foo() {
655 $unqualified[[X]] x;
656 }
657 }
658 }
659 )cpp");
660 auto TU = TestTU::withCode(Test.code());
661 auto Index = buildIndexWithSymbol(
662 {SymbolWithHeader{"na::X", "unittest:///a.h", "\"a.h\""},
663 SymbolWithHeader{"na::nb::X", "unittest:///b.h", "\"b.h\""}});
664 TU.ExternalIndex = Index.get();
665
666 EXPECT_THAT(TU.build().getDiagnostics(),
667 UnorderedElementsAre(AllOf(
668 Diag(Test.range("unqualified"), "unknown type name 'X'"),
669 WithFix(Fix(Test.range("insert"), "#include \"a.h\"\n",
670 "Add include \"a.h\" for symbol na::X"),
671 Fix(Test.range("insert"), "#include \"b.h\"\n",
672 "Add include \"b.h\" for symbol na::nb::X")))));
673 }
674
675 TEST(IncludeFixerTest, NoCrashMemebrAccess) {
676 Annotations Test(R"cpp(
677 struct X { int xyz; };
678 void g() { X x; x.$[[xy]] }
679 )cpp");
680 auto TU = TestTU::withCode(Test.code());
681 auto Index = buildIndexWithSymbol(
682 SymbolWithHeader{"na::X", "unittest:///a.h", "\"a.h\""});
683 TU.ExternalIndex = Index.get();
684
685 EXPECT_THAT(
686 TU.build().getDiagnostics(),
687 UnorderedElementsAre(Diag(Test.range(), "no member named 'xy' in 'X'")));
688 }
689
690 TEST(IncludeFixerTest, UseCachedIndexResults) {
691 // As index results for the identical request are cached, more than 5 fixes
692 // are generated.
693 Annotations Test(R"cpp(
694 $insert[[]]void foo() {
695 $x1[[X]] x;
696 $x2[[X]] x;
697 $x3[[X]] x;
698 $x4[[X]] x;
699 $x5[[X]] x;
700 $x6[[X]] x;
701 $x7[[X]] x;
702 }
703
704 class X;
705 void bar(X *x) {
706 x$a1[[->]]f();
707 x$a2[[->]]f();
708 x$a3[[->]]f();
709 x$a4[[->]]f();
710 x$a5[[->]]f();
711 x$a6[[->]]f();
712 x$a7[[->]]f();
713 }
714 )cpp");
715 auto TU = TestTU::withCode(Test.code());
716 auto Index =
717 buildIndexWithSymbol(SymbolWithHeader{"X", "unittest:///a.h", "\"a.h\""});
718 TU.ExternalIndex = Index.get();
719
720 auto Parsed = TU.build();
721 for (const auto &D : Parsed.getDiagnostics()) {
722 if (D.Fixes.size() != 1) {
723 ADD_FAILURE() << "D.Fixes.size() != 1";
724 continue;
725 }
726 EXPECT_EQ(D.Fixes[0].Message,
727 std::string("Add include \"a.h\" for symbol X"));
728 }
729 }
730
731 TEST(IncludeFixerTest, UnresolvedNameAsSpecifier) {
732 Annotations Test(R"cpp(
733 $insert[[]]namespace ns {
734 }
735 void g() { ns::$[[scope]]::X_Y(); }
736 )cpp");
737 TestTU TU;
738 TU.Code = Test.code();
739 // FIXME: Figure out why this is needed and remove it, PR43662.
740 TU.ExtraArgs.push_back("-fno-ms-compatibility");
741 auto Index = buildIndexWithSymbol(
742 SymbolWithHeader{"ns::scope::X_Y", "unittest:///x.h", "\"x.h\""});
743 TU.ExternalIndex = Index.get();
744
745 EXPECT_THAT(
746 TU.build().getDiagnostics(),
747 UnorderedElementsAre(AllOf(
748 Diag(Test.range(), "no member named 'scope' in namespace 'ns'"),
749 WithFix(Fix(Test.range("insert"), "#include \"x.h\"\n",
750 "Add include \"x.h\" for symbol ns::scope::X_Y")))));
751 }
752
753 TEST(IncludeFixerTest, UnresolvedSpecifierWithSemaCorrection) {
754 Annotations Test(R"cpp(
755 $insert[[]]namespace clang {
756 void f() {
757 // "clangd::" will be corrected to "clang::" by Sema.
758 $q1[[clangd]]::$x[[X]] x;
759 $q2[[clangd]]::$ns[[ns]]::Y y;
760 }
761 }
762 )cpp");
763 TestTU TU;
764 TU.Code = Test.code();
765 // FIXME: Figure out why this is needed and remove it, PR43662.
766 TU.ExtraArgs.push_back("-fno-ms-compatibility");
767 auto Index = buildIndexWithSymbol(
768 {SymbolWithHeader{"clang::clangd::X", "unittest:///x.h", "\"x.h\""},
769 SymbolWithHeader{"clang::clangd::ns::Y", "unittest:///y.h", "\"y.h\""}});
770 TU.ExternalIndex = Index.get();
771
772 EXPECT_THAT(
773 TU.build().getDiagnostics(),
774 UnorderedElementsAre(
775 AllOf(
776 Diag(Test.range("q1"), "use of undeclared identifier 'clangd'; "
777 "did you mean 'clang'?"),
778 WithFix(_, // change clangd to clang
779 Fix(Test.range("insert"), "#include \"x.h\"\n",
780 "Add include \"x.h\" for symbol clang::clangd::X"))),
781 AllOf(
782 Diag(Test.range("x"), "no type named 'X' in namespace 'clang'"),
783 WithFix(Fix(Test.range("insert"), "#include \"x.h\"\n",
784 "Add include \"x.h\" for symbol clang::clangd::X"))),
785 AllOf(
786 Diag(Test.range("q2"), "use of undeclared identifier 'clangd'; "
787 "did you mean 'clang'?"),
788 WithFix(
789 _, // change clangd to clangd
790 Fix(Test.range("insert"), "#include \"y.h\"\n",
791 "Add include \"y.h\" for symbol clang::clangd::ns::Y"))),
792 AllOf(Diag(Test.range("ns"),
793 "no member named 'ns' in namespace 'clang'"),
794 WithFix(Fix(
795 Test.range("insert"), "#include \"y.h\"\n",
796 "Add include \"y.h\" for symbol clang::clangd::ns::Y")))));
797 }
798
799 TEST(IncludeFixerTest, SpecifiedScopeIsNamespaceAlias) {
800 Annotations Test(R"cpp(
801 $insert[[]]namespace a {}
802 namespace b = a;
803 namespace c {
804 b::$[[X]] x;
805 }
806 )cpp");
807 auto TU = TestTU::withCode(Test.code());
808 auto Index = buildIndexWithSymbol(
809 SymbolWithHeader{"a::X", "unittest:///x.h", "\"x.h\""});
810 TU.ExternalIndex = Index.get();
811
812 EXPECT_THAT(TU.build().getDiagnostics(),
813 UnorderedElementsAre(AllOf(
814 Diag(Test.range(), "no type named 'X' in namespace 'a'"),
815 WithFix(Fix(Test.range("insert"), "#include \"x.h\"\n",
816 "Add include \"x.h\" for symbol a::X")))));
817 }
818
819 TEST(IncludeFixerTest, NoCrashOnTemplateInstantiations) {
820 Annotations Test(R"cpp(
821 template <typename T> struct Templ {
822 template <typename U>
823 typename U::type operator=(const U &);
824 };
825
826 struct A {
827 Templ<char> s;
828 A() { [[a]]; } // this caused crashes if we compute scopes lazily.
829 };
830 )cpp");
831
832 auto TU = TestTU::withCode(Test.code());
833 auto Index = buildIndexWithSymbol({});
834 TU.ExternalIndex = Index.get();
835
836 EXPECT_THAT(TU.build().getDiagnostics(),
837 ElementsAre(Diag(Test.range(), "use of undeclared identifier 'a'")));
838 }
839
840 TEST(DiagsInHeaders, DiagInsideHeader) {
841 Annotations Main(R"cpp(
842 #include [["a.h"]]
843 void foo() {})cpp");
844 Annotations Header("[[no_type_spec]];");
845 TestTU TU = TestTU::withCode(Main.code());
846 TU.AdditionalFiles = {{"a.h", Header.code()}};
847 EXPECT_THAT(TU.build().getDiagnostics(),
848 UnorderedElementsAre(AllOf(
849 Diag(Main.range(), "in included file: C++ requires a "
850 "type specifier for all declarations"),
851 WithNote(Diag(Header.range(), "error occurred here")))));
852 }
853
854 TEST(DiagsInHeaders, DiagInTransitiveInclude) {
855 Annotations Main(R"cpp(
856 #include [["a.h"]]
857 void foo() {})cpp");
858 TestTU TU = TestTU::withCode(Main.code());
859 TU.AdditionalFiles = {{"a.h", "#include \"b.h\""}, {"b.h", "no_type_spec;"}};
860 EXPECT_THAT(TU.build().getDiagnostics(),
861 UnorderedElementsAre(
862 Diag(Main.range(), "in included file: C++ requires a "
863 "type specifier for all declarations")));
864 }
865
866 TEST(DiagsInHeaders, DiagInMultipleHeaders) {
867 Annotations Main(R"cpp(
868 #include $a[["a.h"]]
869 #include $b[["b.h"]]
870 void foo() {})cpp");
871 TestTU TU = TestTU::withCode(Main.code());
872 TU.AdditionalFiles = {{"a.h", "no_type_spec;"}, {"b.h", "no_type_spec;"}};
873 EXPECT_THAT(TU.build().getDiagnostics(),
874 UnorderedElementsAre(
875 Diag(Main.range("a"), "in included file: C++ requires a type "
876 "specifier for all declarations"),
877 Diag(Main.range("b"), "in included file: C++ requires a type "
878 "specifier for all declarations")));
879 }
880
881 TEST(DiagsInHeaders, PreferExpansionLocation) {
882 Annotations Main(R"cpp(
883 #include [["a.h"]]
884 #include "b.h"
885 void foo() {})cpp");
886 TestTU TU = TestTU::withCode(Main.code());
887 TU.AdditionalFiles = {{"a.h", "#include \"b.h\"\n"},
888 {"b.h", "#ifndef X\n#define X\nno_type_spec;\n#endif"}};
889 EXPECT_THAT(TU.build().getDiagnostics(),
890 UnorderedElementsAre(Diag(Main.range(),
891 "in included file: C++ requires a type "
892 "specifier for all declarations")));
893 }
894
895 TEST(DiagsInHeaders, PreferExpansionLocationMacros) {
896 Annotations Main(R"cpp(
897 #define X
898 #include "a.h"
899 #undef X
900 #include [["b.h"]]
901 void foo() {})cpp");
902 TestTU TU = TestTU::withCode(Main.code());
903 TU.AdditionalFiles = {{"a.h", "#include \"c.h\"\n"},
904 {"b.h", "#include \"c.h\"\n"},
905 {"c.h", "#ifndef X\n#define X\nno_type_spec;\n#endif"}};
906 EXPECT_THAT(TU.build().getDiagnostics(),
907 UnorderedElementsAre(
908 Diag(Main.range(), "in included file: C++ requires a "
909 "type specifier for all declarations")));
910 }
911
912 TEST(DiagsInHeaders, LimitDiagsOutsideMainFile) {
913 Annotations Main(R"cpp(
914 #include [["a.h"]]
915 #include "b.h"
916 void foo() {})cpp");
917 TestTU TU = TestTU::withCode(Main.code());
918 TU.AdditionalFiles = {{"a.h", "#include \"c.h\"\n"},
919 {"b.h", "#include \"c.h\"\n"},
920 {"c.h", R"cpp(
921 #ifndef X
922 #define X
923 no_type_spec_0;
924 no_type_spec_1;
925 no_type_spec_2;
926 no_type_spec_3;
927 no_type_spec_4;
928 no_type_spec_5;
929 no_type_spec_6;
930 no_type_spec_7;
931 no_type_spec_8;
932 no_type_spec_9;
933 no_type_spec_10;
934 #endif)cpp"}};
935 EXPECT_THAT(TU.build().getDiagnostics(),
936 UnorderedElementsAre(
937 Diag(Main.range(), "in included file: C++ requires a "
938 "type specifier for all declarations")));
939 }
940
941 TEST(DiagsInHeaders, OnlyErrorOrFatal) {
942 Annotations Main(R"cpp(
943 #include [["a.h"]]
944 void foo() {})cpp");
945 Annotations Header(R"cpp(
946 [[no_type_spec]];
947 int x = 5/0;)cpp");
948 TestTU TU = TestTU::withCode(Main.code());
949 TU.AdditionalFiles = {{"a.h", Header.code()}};
950 EXPECT_THAT(TU.build().getDiagnostics(),
951 UnorderedElementsAre(AllOf(
952 Diag(Main.range(), "in included file: C++ requires "
953 "a type specifier for all declarations"),
954 WithNote(Diag(Header.range(), "error occurred here")))));
955 }
956
957 TEST(DiagsInHeaders, FromNonWrittenSources) {
958 Annotations Main(R"cpp(
959 #include [["a.h"]]
960 void foo() {})cpp");
961 Annotations Header(R"cpp(
962 int x = 5/0;
963 int b = [[FOO]];)cpp");
964 TestTU TU = TestTU::withCode(Main.code());
965 TU.AdditionalFiles = {{"a.h", Header.code()}};
966 TU.ExtraArgs = {"-DFOO=NOOO"};
967 EXPECT_THAT(TU.build().getDiagnostics(),
968 UnorderedElementsAre(AllOf(
969 Diag(Main.range(),
970 "in included file: use of undeclared identifier 'NOOO'"),
971 WithNote(Diag(Header.range(), "error occurred here")))));
972 }
973
974 TEST(DiagsInHeaders, ErrorFromMacroExpansion) {
975 Annotations Main(R"cpp(
976 void bar() {
977 int fo;
978 #include [["a.h"]]
979 })cpp");
980 Annotations Header(R"cpp(
981 #define X foo
982 X;)cpp");
983 TestTU TU = TestTU::withCode(Main.code());
984 TU.AdditionalFiles = {{"a.h", Header.code()}};
985 EXPECT_THAT(TU.build().getDiagnostics(),
986 UnorderedElementsAre(
987 Diag(Main.range(), "in included file: use of undeclared "
988 "identifier 'foo'; did you mean 'fo'?")));
989 }
990
991 TEST(DiagsInHeaders, ErrorFromMacroArgument) {
992 Annotations Main(R"cpp(
993 void bar() {
994 int fo;
995 #include [["a.h"]]
996 })cpp");
997 Annotations Header(R"cpp(
998 #define X(arg) arg
999 X(foo);)cpp");
1000 TestTU TU = TestTU::withCode(Main.code());
1001 TU.AdditionalFiles = {{"a.h", Header.code()}};
1002 EXPECT_THAT(TU.build().getDiagnostics(),
1003 UnorderedElementsAre(
1004 Diag(Main.range(), "in included file: use of undeclared "
1005 "identifier 'foo'; did you mean 'fo'?")));
1006 }
1007
1008 TEST(IgnoreDiags, FromNonWrittenInclude) {
1009 TestTU TU;
1010 TU.ExtraArgs.push_back("--include=a.h");
1011 TU.AdditionalFiles = {{"a.h", "void main();"}};
1012 // The diagnostic "main must return int" is from the header, we don't attempt
1013 // to render it in the main file as there is no written location there.
1014 EXPECT_THAT(TU.build().getDiagnostics(), UnorderedElementsAre());
1015 }
1016
1017 TEST(ToLSPDiag, RangeIsInMain) {
1018 ClangdDiagnosticOptions Opts;
1019 clangd::Diag D;
1020 D.Range = {pos(1, 2), pos(3, 4)};
1021 D.Notes.emplace_back();
1022 Note &N = D.Notes.back();
1023 N.Range = {pos(2, 3), pos(3, 4)};
1024
1025 D.InsideMainFile = true;
1026 N.InsideMainFile = false;
1027 toLSPDiags(D, {}, Opts,
1028 [&](clangd::Diagnostic LSPDiag, ArrayRef<clangd::Fix>) {
1029 EXPECT_EQ(LSPDiag.range, D.Range);
1030 });
1031
1032 D.InsideMainFile = false;
1033 N.InsideMainFile = true;
1034 toLSPDiags(D, {}, Opts,
1035 [&](clangd::Diagnostic LSPDiag, ArrayRef<clangd::Fix>) {
1036 EXPECT_EQ(LSPDiag.range, N.Range);
1037 });
1038 }
1039
1040 } // namespace
1041 } // namespace clangd
1042 } // namespace clang
clang::clangd::Diag::Clang
Definition: Diagnostics.h:90
clang::clangd::MATCHER_P
MATCHER_P(Named, N, "")
Definition: BackgroundIndexTests.cpp:26
clang::clangd::TestTU::ClangTidyChecks
llvm::Optional< std::string > ClangTidyChecks
Definition: TestTU.h:59
clang::clangd::toJSON
llvm::json::Value toJSON(const FuzzyFindRequest &Request)
Definition: Index.cpp:48
clang::clangd::cls
Symbol cls(llvm::StringRef Name)
Definition: TestIndex.cpp:64
clang::clangd::MessageType::Error
An error message.
SourceCode.h
Protocol.h
clang::tidy::modernize::Message
constexpr llvm::StringLiteral Message
Definition: UseTrailingReturnTypeCheck.cpp:101
MemIndex.h
TestFS.h
TestTU.h
MainFile
std::string MainFile
Definition: HeadersTests.cpp:121
clang::clangd::Diag::Fixes
std::vector< Fix > Fixes
Alternative fixes for this diagnostic, one should be chosen.
Definition: Diagnostics.h:96
clang::clangd::MemIndex::build
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:19
QName
std::string QName
Definition: DiagnosticsTests.cpp:523
clang::clangd::toLSPDiags
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.
Definition: Diagnostics.cpp:330
Annotations.h
clang::clangd::Symbol::IndexedForCodeCompletion
Whether or not this symbol is meant to be used for the code completion.
Definition: Symbol.h:119
clang::clangd::getSeverity
int getSeverity(DiagnosticsEngine::Level L)
Convert from clang diagnostic level to LSP severity.
Definition: Diagnostics.cpp:400
clang::clangd::TEST
TEST(BackgroundQueueTest, Priority)
Definition: BackgroundIndexTests.cpp:647
IgnoreDiags
IgnoringDiagConsumer IgnoreDiags
Definition: HeadersTests.cpp:124
DeclaringFile
std::string DeclaringFile
Definition: DiagnosticsTests.cpp:524
clang::clangd::testPath
std::string testPath(PathRef File)
Definition: TestFS.cpp:82
clang::clangd::CompletionItemKind::Field
Diagnostics.h
clang::clangd::URIForFile::canonicalize
static URIForFile canonicalize(llvm::StringRef AbsPath, llvm::StringRef TUPath)
Canonicalizes AbsPath via URI.
Definition: Protocol.cpp:32
clang::clangd::Diag::ClangTidy
Definition: Diagnostics.h:91
clang::clangd::TestTU::withCode
static TestTU withCode(llvm::StringRef Code)
Definition: TestTU.h:33
Builder
CodeCompletionBuilder Builder
Definition: CodeCompletionStringsTests.cpp:35
clang::clangd::Diag::Notes
std::vector< Note > Notes
Elaborate on the problem, usually pointing to a related piece of code.
Definition: Diagnostics.h:94
clang
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
Definition: ApplyReplacements.h:27
ParsedAST.h
Path.h
Range
CharSourceRange Range
SourceRange for the file name.
Definition: IncludeOrderCheck.cpp:38
IncludeHeader
std::string IncludeHeader
Definition: DiagnosticsTests.cpp:525
clang::clangd::TestTU::Filename
std::string Filename
Definition: TestTU.h:47
TestIndex.h
Fix
static cl::opt< bool > Fix("fix", cl::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))
clang::clangd::TestTU::HeaderFilename
std::string HeaderFilename
Definition: TestTU.h:51
clang::clangd::TestTU::AdditionalFiles
llvm::StringMap< std::string > AdditionalFiles
Definition: TestTU.h:54
Index
const SymbolIndex * Index
Definition: Dexp.cpp:84