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);
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");
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
MATCHER_P(Named, N, "")
llvm::Optional< std::string > ClangTidyChecks
Definition: TestTU.h:59
llvm::json::Value toJSON(const FuzzyFindRequest &Request)
Definition: Index.cpp:48
Symbol cls(llvm::StringRef Name)
Definition: TestIndex.cpp:64
constexpr llvm::StringLiteral Message
std::string MainFile
std::vector< Fix > Fixes
Alternative fixes for this diagnostic, one should be chosen.
Definition: Diagnostics.h:96
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
std::string QName
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.
Whether or not this symbol is meant to be used for the code completion.
Definition: Symbol.h:119
int getSeverity(DiagnosticsEngine::Level L)
Convert from clang diagnostic level to LSP severity.
TEST(BackgroundQueueTest, Priority)
IgnoringDiagConsumer IgnoreDiags
std::string DeclaringFile
std::string testPath(PathRef File)
Definition: TestFS.cpp:82
static URIForFile canonicalize(llvm::StringRef AbsPath, llvm::StringRef TUPath)
Canonicalizes AbsPath via URI.
Definition: Protocol.cpp:32
static TestTU withCode(llvm::StringRef Code)
Definition: TestTU.h:33
CodeCompletionBuilder Builder
std::vector< Note > Notes
Elaborate on the problem, usually pointing to a related piece of code.
Definition: Diagnostics.h:94
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
CharSourceRange Range
SourceRange for the file name.
std::string IncludeHeader
std::string Filename
Definition: TestTU.h:47
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))
std::string HeaderFilename
Definition: TestTU.h:51
llvm::StringMap< std::string > AdditionalFiles
Definition: TestTU.h:54
const SymbolIndex * Index
Definition: Dexp.cpp:84