clang-tools  10.0.0
Diagnostics.cpp
Go to the documentation of this file.
1 //===--- Diagnostics.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 "Diagnostics.h"
10 #include "../clang-tidy/ClangTidyDiagnosticConsumer.h"
11 #include "Compiler.h"
12 #include "Logger.h"
13 #include "Protocol.h"
14 #include "SourceCode.h"
15 #include "clang/Basic/AllDiagnostics.h"
16 #include "clang/Basic/Diagnostic.h"
17 #include "clang/Basic/DiagnosticIDs.h"
18 #include "clang/Basic/FileManager.h"
19 #include "clang/Basic/SourceLocation.h"
20 #include "clang/Basic/SourceManager.h"
21 #include "clang/Lex/Lexer.h"
22 #include "clang/Lex/Token.h"
23 #include "llvm/ADT/ArrayRef.h"
24 #include "llvm/ADT/DenseSet.h"
25 #include "llvm/ADT/Optional.h"
26 #include "llvm/ADT/STLExtras.h"
27 #include "llvm/ADT/SmallString.h"
28 #include "llvm/ADT/StringRef.h"
29 #include "llvm/ADT/Twine.h"
30 #include "llvm/Support/Capacity.h"
31 #include "llvm/Support/Path.h"
32 #include "llvm/Support/ScopedPrinter.h"
33 #include "llvm/Support/Signals.h"
34 #include "llvm/Support/raw_ostream.h"
35 #include <algorithm>
36 #include <cstddef>
37 
38 namespace clang {
39 namespace clangd {
40 namespace {
41 
42 const char *getDiagnosticCode(unsigned ID) {
43  switch (ID) {
44 #define DIAG(ENUM, CLASS, DEFAULT_MAPPING, DESC, GROPU, SFINAE, NOWERROR, \
45  SHOWINSYSHEADER, CATEGORY) \
46  case clang::diag::ENUM: \
47  return #ENUM;
48 #include "clang/Basic/DiagnosticASTKinds.inc"
49 #include "clang/Basic/DiagnosticAnalysisKinds.inc"
50 #include "clang/Basic/DiagnosticCommentKinds.inc"
51 #include "clang/Basic/DiagnosticCommonKinds.inc"
52 #include "clang/Basic/DiagnosticDriverKinds.inc"
53 #include "clang/Basic/DiagnosticFrontendKinds.inc"
54 #include "clang/Basic/DiagnosticLexKinds.inc"
55 #include "clang/Basic/DiagnosticParseKinds.inc"
56 #include "clang/Basic/DiagnosticRefactoringKinds.inc"
57 #include "clang/Basic/DiagnosticSemaKinds.inc"
58 #include "clang/Basic/DiagnosticSerializationKinds.inc"
59 #undef DIAG
60  default:
61  return nullptr;
62  }
63 }
64 
65 bool mentionsMainFile(const Diag &D) {
66  if (D.InsideMainFile)
67  return true;
68  // Fixes are always in the main file.
69  if (!D.Fixes.empty())
70  return true;
71  for (auto &N : D.Notes) {
72  if (N.InsideMainFile)
73  return true;
74  }
75  return false;
76 }
77 
78 bool isBlacklisted(const Diag &D) {
79  // clang will always fail parsing MS ASM, we don't link in desc + asm parser.
80  if (D.ID == clang::diag::err_msasm_unable_to_create_target ||
81  D.ID == clang::diag::err_msasm_unsupported_arch)
82  return true;
83  return false;
84 }
85 
86 // Checks whether a location is within a half-open range.
87 // Note that clang also uses closed source ranges, which this can't handle!
88 bool locationInRange(SourceLocation L, CharSourceRange R,
89  const SourceManager &M) {
90  assert(R.isCharRange());
91  if (!R.isValid() || M.getFileID(R.getBegin()) != M.getFileID(R.getEnd()) ||
92  M.getFileID(R.getBegin()) != M.getFileID(L))
93  return false;
94  return L != R.getEnd() && M.isPointWithin(L, R.getBegin(), R.getEnd());
95 }
96 
97 // Clang diags have a location (shown as ^) and 0 or more ranges (~~~~).
98 // LSP needs a single range.
99 Range diagnosticRange(const clang::Diagnostic &D, const LangOptions &L) {
100  auto &M = D.getSourceManager();
101  auto Loc = M.getFileLoc(D.getLocation());
102  for (const auto &CR : D.getRanges()) {
103  auto R = Lexer::makeFileCharRange(CR, M, L);
104  if (locationInRange(Loc, R, M))
105  return halfOpenToRange(M, R);
106  }
107  // The range may be given as a fixit hint instead.
108  for (const auto &F : D.getFixItHints()) {
109  auto R = Lexer::makeFileCharRange(F.RemoveRange, M, L);
110  if (locationInRange(Loc, R, M))
111  return halfOpenToRange(M, R);
112  }
113  // If the token at the location is not a comment, we use the token.
114  // If we can't get the token at the location, fall back to using the location
115  auto R = CharSourceRange::getCharRange(Loc);
116  Token Tok;
117  if (!Lexer::getRawToken(Loc, Tok, M, L, true) && Tok.isNot(tok::comment)) {
118  R = CharSourceRange::getTokenRange(Tok.getLocation(), Tok.getEndLoc());
119  }
120  return halfOpenToRange(M, R);
121 }
122 
123 // Returns whether the \p D is modified.
124 bool adjustDiagFromHeader(Diag &D, const clang::Diagnostic &Info,
125  const LangOptions &LangOpts) {
126  // We only report diagnostics with at least error severity from headers.
127  if (D.Severity < DiagnosticsEngine::Level::Error)
128  return false;
129 
130  const SourceManager &SM = Info.getSourceManager();
131  const SourceLocation &DiagLoc = SM.getExpansionLoc(Info.getLocation());
132  SourceLocation IncludeInMainFile;
133  auto GetIncludeLoc = [&SM](SourceLocation SLoc) {
134  return SM.getIncludeLoc(SM.getFileID(SLoc));
135  };
136  for (auto IncludeLocation = GetIncludeLoc(DiagLoc); IncludeLocation.isValid();
137  IncludeLocation = GetIncludeLoc(IncludeLocation)) {
138  if (clangd::isInsideMainFile(IncludeLocation, SM)) {
139  IncludeInMainFile = IncludeLocation;
140  break;
141  }
142  }
143  if (IncludeInMainFile.isInvalid())
144  return false;
145 
146  // Update diag to point at include inside main file.
147  D.File = SM.getFileEntryForID(SM.getMainFileID())->getName().str();
148  D.Range.start = sourceLocToPosition(SM, IncludeInMainFile);
149  D.Range.end = sourceLocToPosition(
150  SM, Lexer::getLocForEndOfToken(IncludeInMainFile, 0, SM, LangOpts));
151  D.InsideMainFile = true;
152 
153  // Add a note that will point to real diagnostic.
154  const auto *FE = SM.getFileEntryForID(SM.getFileID(DiagLoc));
155  D.Notes.emplace_back();
156  Note &N = D.Notes.back();
157  N.AbsFile = FE->tryGetRealPathName();
158  N.File = FE->getName();
159  N.Message = "error occurred here";
160  N.Range = diagnosticRange(Info, LangOpts);
161 
162  // Update message to mention original file.
163  D.Message = llvm::Twine("in included file: ", D.Message).str();
164  return true;
165 }
166 
167 bool isInsideMainFile(const clang::Diagnostic &D) {
168  if (!D.hasSourceManager())
169  return false;
170 
171  return clangd::isInsideMainFile(D.getLocation(), D.getSourceManager());
172 }
173 
174 bool isNote(DiagnosticsEngine::Level L) {
175  return L == DiagnosticsEngine::Note || L == DiagnosticsEngine::Remark;
176 }
177 
178 llvm::StringRef diagLeveltoString(DiagnosticsEngine::Level Lvl) {
179  switch (Lvl) {
180  case DiagnosticsEngine::Ignored:
181  return "ignored";
182  case DiagnosticsEngine::Note:
183  return "note";
184  case DiagnosticsEngine::Remark:
185  return "remark";
187  return "warning";
189  return "error";
190  case DiagnosticsEngine::Fatal:
191  return "fatal error";
192  }
193  llvm_unreachable("unhandled DiagnosticsEngine::Level");
194 }
195 
196 /// Prints a single diagnostic in a clang-like manner, the output includes
197 /// location, severity and error message. An example of the output message is:
198 ///
199 /// main.cpp:12:23: error: undeclared identifier
200 ///
201 /// For main file we only print the basename and for all other files we print
202 /// the filename on a separate line to provide a slightly more readable output
203 /// in the editors:
204 ///
205 /// dir1/dir2/dir3/../../dir4/header.h:12:23
206 /// error: undeclared identifier
207 void printDiag(llvm::raw_string_ostream &OS, const DiagBase &D) {
208  if (D.InsideMainFile) {
209  // Paths to main files are often taken from compile_command.json, where they
210  // are typically absolute. To reduce noise we print only basename for them,
211  // it should not be confusing and saves space.
212  OS << llvm::sys::path::filename(D.File) << ":";
213  } else {
214  OS << D.File << ":";
215  }
216  // Note +1 to line and character. clangd::Range is zero-based, but when
217  // printing for users we want one-based indexes.
218  auto Pos = D.Range.start;
219  OS << (Pos.line + 1) << ":" << (Pos.character + 1) << ":";
220  // The non-main-file paths are often too long, putting them on a separate
221  // line improves readability.
222  if (D.InsideMainFile)
223  OS << " ";
224  else
225  OS << "\n";
226  OS << diagLeveltoString(D.Severity) << ": " << D.Message;
227 }
228 
229 /// Capitalizes the first word in the diagnostic's message.
230 std::string capitalize(std::string Message) {
231  if (!Message.empty())
232  Message[0] = llvm::toUpper(Message[0]);
233  return Message;
234 }
235 
236 /// Returns a message sent to LSP for the main diagnostic in \p D.
237 /// This message may include notes, if they're not emitted in some other way.
238 /// Example output:
239 ///
240 /// no matching function for call to 'foo'
241 ///
242 /// main.cpp:3:5: note: candidate function not viable: requires 2 arguments
243 ///
244 /// dir1/dir2/dir3/../../dir4/header.h:12:23
245 /// note: candidate function not viable: requires 3 arguments
246 std::string mainMessage(const Diag &D, const ClangdDiagnosticOptions &Opts) {
247  std::string Result;
248  llvm::raw_string_ostream OS(Result);
249  OS << D.Message;
250  if (Opts.DisplayFixesCount && !D.Fixes.empty())
251  OS << " (" << (D.Fixes.size() > 1 ? "fixes" : "fix") << " available)";
252  // If notes aren't emitted as structured info, add them to the message.
253  if (!Opts.EmitRelatedLocations)
254  for (auto &Note : D.Notes) {
255  OS << "\n\n";
256  printDiag(OS, Note);
257  }
258  OS.flush();
259  return capitalize(std::move(Result));
260 }
261 
262 /// Returns a message sent to LSP for the note of the main diagnostic.
263 std::string noteMessage(const Diag &Main, const DiagBase &Note,
264  const ClangdDiagnosticOptions &Opts) {
265  std::string Result;
266  llvm::raw_string_ostream OS(Result);
267  OS << Note.Message;
268  // If the client doesn't support structured links between the note and the
269  // original diagnostic, then emit the main diagnostic to give context.
270  if (!Opts.EmitRelatedLocations) {
271  OS << "\n\n";
272  printDiag(OS, Main);
273  }
274  OS.flush();
275  return capitalize(std::move(Result));
276 }
277 } // namespace
278 
279 llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const DiagBase &D) {
280  OS << "[";
281  if (!D.InsideMainFile)
282  OS << D.File << ":";
283  OS << D.Range.start << "-" << D.Range.end << "] ";
284 
285  return OS << D.Message;
286 }
287 
288 llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const Fix &F) {
289  OS << F.Message << " {";
290  const char *Sep = "";
291  for (const auto &Edit : F.Edits) {
292  OS << Sep << Edit;
293  Sep = ", ";
294  }
295  return OS << "}";
296 }
297 
298 llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const Diag &D) {
299  OS << static_cast<const DiagBase &>(D);
300  if (!D.Notes.empty()) {
301  OS << ", notes: {";
302  const char *Sep = "";
303  for (auto &Note : D.Notes) {
304  OS << Sep << Note;
305  Sep = ", ";
306  }
307  OS << "}";
308  }
309  if (!D.Fixes.empty()) {
310  OS << ", fixes: {";
311  const char *Sep = "";
312  for (auto &Fix : D.Fixes) {
313  OS << Sep << Fix;
314  Sep = ", ";
315  }
316  }
317  return OS;
318 }
319 
320 CodeAction toCodeAction(const Fix &F, const URIForFile &File) {
322  Action.title = F.Message;
324  Action.edit.emplace();
325  Action.edit->changes.emplace();
326  (*Action.edit->changes)[File.uri()] = {F.Edits.begin(), F.Edits.end()};
327  return Action;
328 }
329 
331  const Diag &D, const URIForFile &File, const ClangdDiagnosticOptions &Opts,
332  llvm::function_ref<void(clangd::Diagnostic, llvm::ArrayRef<Fix>)> OutFn) {
333  clangd::Diagnostic Main;
334  Main.severity = getSeverity(D.Severity);
335 
336  // Main diagnostic should always refer to a range inside main file. If a
337  // diagnostic made it so for, it means either itself or one of its notes is
338  // inside main file.
339  if (D.InsideMainFile) {
340  Main.range = D.Range;
341  } else {
342  auto It =
343  llvm::find_if(D.Notes, [](const Note &N) { return N.InsideMainFile; });
344  assert(It != D.Notes.end() &&
345  "neither the main diagnostic nor notes are inside main file");
346  Main.range = It->Range;
347  }
348 
349  Main.code = D.Name;
350  switch (D.Source) {
351  case Diag::Clang:
352  Main.source = "clang";
353  break;
354  case Diag::ClangTidy:
355  Main.source = "clang-tidy";
356  break;
357  case Diag::Unknown:
358  break;
359  }
360  if (Opts.EmbedFixesInDiagnostics) {
361  Main.codeActions.emplace();
362  for (const auto &Fix : D.Fixes)
363  Main.codeActions->push_back(toCodeAction(Fix, File));
364  }
365  if (Opts.SendDiagnosticCategory && !D.Category.empty())
366  Main.category = D.Category;
367 
368  Main.message = mainMessage(D, Opts);
369  if (Opts.EmitRelatedLocations) {
370  Main.relatedInformation.emplace();
371  for (auto &Note : D.Notes) {
372  if (!Note.AbsFile) {
373  vlog("Dropping note from unknown file: {0}", Note);
374  continue;
375  }
377  RelInfo.location.range = Note.Range;
378  RelInfo.location.uri =
379  URIForFile::canonicalize(*Note.AbsFile, File.file());
380  RelInfo.message = noteMessage(D, Note, Opts);
381  Main.relatedInformation->push_back(std::move(RelInfo));
382  }
383  }
384  OutFn(std::move(Main), D.Fixes);
385 
386  // If we didn't emit the notes as relatedLocations, emit separate diagnostics
387  // so the user can find the locations easily.
388  if (!Opts.EmitRelatedLocations)
389  for (auto &Note : D.Notes) {
390  if (!Note.InsideMainFile)
391  continue;
392  clangd::Diagnostic Res;
393  Res.severity = getSeverity(Note.Severity);
394  Res.range = Note.Range;
395  Res.message = noteMessage(D, Note, Opts);
396  OutFn(std::move(Res), llvm::ArrayRef<Fix>());
397  }
398 }
399 
400 int getSeverity(DiagnosticsEngine::Level L) {
401  switch (L) {
402  case DiagnosticsEngine::Remark:
403  return 4;
404  case DiagnosticsEngine::Note:
405  return 3;
407  return 2;
408  case DiagnosticsEngine::Fatal:
410  return 1;
411  case DiagnosticsEngine::Ignored:
412  return 0;
413  }
414  llvm_unreachable("Unknown diagnostic level!");
415 }
416 
417 std::vector<Diag> StoreDiags::take(const clang::tidy::ClangTidyContext *Tidy) {
418  // Do not forget to emit a pending diagnostic if there is one.
419  flushLastDiag();
420 
421  // Fill in name/source now that we have all the context needed to map them.
422  for (auto &Diag : Output) {
423  if (const char *ClangDiag = getDiagnosticCode(Diag.ID)) {
424  // Warnings controlled by -Wfoo are better recognized by that name.
425  StringRef Warning = DiagnosticIDs::getWarningOptionForDiag(Diag.ID);
426  if (!Warning.empty()) {
427  Diag.Name = ("-W" + Warning).str();
428  } else {
429  StringRef Name(ClangDiag);
430  // Almost always an error, with a name like err_enum_class_reference.
431  // Drop the err_ prefix for brevity.
432  Name.consume_front("err_");
433  Diag.Name = Name;
434  }
436  continue;
437  }
438  if (Tidy != nullptr) {
439  std::string TidyDiag = Tidy->getCheckName(Diag.ID);
440  if (!TidyDiag.empty()) {
441  Diag.Name = std::move(TidyDiag);
443  // clang-tidy bakes the name into diagnostic messages. Strip it out.
444  // It would be much nicer to make clang-tidy not do this.
445  auto CleanMessage = [&](std::string &Msg) {
446  StringRef Rest(Msg);
447  if (Rest.consume_back("]") && Rest.consume_back(Diag.Name) &&
448  Rest.consume_back(" ["))
449  Msg.resize(Rest.size());
450  };
451  CleanMessage(Diag.Message);
452  for (auto &Note : Diag.Notes)
453  CleanMessage(Note.Message);
454  for (auto &Fix : Diag.Fixes)
455  CleanMessage(Fix.Message);
456  continue;
457  }
458  }
459  }
460  // Deduplicate clang-tidy diagnostics -- some clang-tidy checks may emit
461  // duplicated messages due to various reasons (e.g. the check doesn't handle
462  // template instantiations well; clang-tidy alias checks).
463  std::set<std::pair<Range, std::string>> SeenDiags;
464  llvm::erase_if(Output, [&](const Diag& D) {
465  return !SeenDiags.emplace(D.Range, D.Message).second;
466  });
467  return std::move(Output);
468 }
469 
470 void StoreDiags::BeginSourceFile(const LangOptions &Opts,
471  const Preprocessor *) {
472  LangOpts = Opts;
473 }
474 
476  LangOpts = None;
477 }
478 
479 /// Sanitizes a piece for presenting it in a synthesized fix message. Ensures
480 /// the result is not too large and does not contain newlines.
481 static void writeCodeToFixMessage(llvm::raw_ostream &OS, llvm::StringRef Code) {
482  constexpr unsigned MaxLen = 50;
483 
484  // Only show the first line if there are many.
485  llvm::StringRef R = Code.split('\n').first;
486  // Shorten the message if it's too long.
487  R = R.take_front(MaxLen);
488 
489  OS << R;
490  if (R.size() != Code.size())
491  OS << "…";
492 }
493 
494 /// Fills \p D with all information, except the location-related bits.
495 /// Also note that ID and Name are not part of clangd::DiagBase and should be
496 /// set elsewhere.
497 static void fillNonLocationData(DiagnosticsEngine::Level DiagLevel,
498  const clang::Diagnostic &Info,
499  clangd::DiagBase &D) {
500  llvm::SmallString<64> Message;
501  Info.FormatDiagnostic(Message);
502 
503  D.Message = Message.str();
504  D.Severity = DiagLevel;
505  D.Category = DiagnosticIDs::getCategoryNameFromID(
506  DiagnosticIDs::getCategoryNumberForDiag(Info.getID()))
507  .str();
508 }
509 
510 void StoreDiags::HandleDiagnostic(DiagnosticsEngine::Level DiagLevel,
511  const clang::Diagnostic &Info) {
512  DiagnosticConsumer::HandleDiagnostic(DiagLevel, Info);
513 
514  if (Info.getLocation().isInvalid()) {
515  // Handle diagnostics coming from command-line arguments. The source manager
516  // is *not* available at this point, so we cannot use it.
517  if (DiagLevel < DiagnosticsEngine::Level::Error) {
518  IgnoreDiagnostics::log(DiagLevel, Info);
519  return; // non-errors add too much noise, do not show them.
520  }
521 
522  flushLastDiag();
523 
524  LastDiag = Diag();
525  LastDiag->ID = Info.getID();
526  fillNonLocationData(DiagLevel, Info, *LastDiag);
527  LastDiag->InsideMainFile = true;
528  // Put it at the start of the main file, for a lack of a better place.
529  LastDiag->Range.start = Position{0, 0};
530  LastDiag->Range.end = Position{0, 0};
531  return;
532  }
533 
534  if (!LangOpts || !Info.hasSourceManager()) {
535  IgnoreDiagnostics::log(DiagLevel, Info);
536  return;
537  }
538 
539  bool InsideMainFile = isInsideMainFile(Info);
540  SourceManager &SM = Info.getSourceManager();
541 
542  auto FillDiagBase = [&](DiagBase &D) {
543  fillNonLocationData(DiagLevel, Info, D);
544 
545  D.InsideMainFile = InsideMainFile;
546  D.Range = diagnosticRange(Info, *LangOpts);
547  D.File = SM.getFilename(Info.getLocation());
549  SM.getFileEntryForID(SM.getFileID(Info.getLocation())), SM);
550  return D;
551  };
552 
553  auto AddFix = [&](bool SyntheticMessage) -> bool {
554  assert(!Info.getFixItHints().empty() &&
555  "diagnostic does not have attached fix-its");
556  if (!InsideMainFile)
557  return false;
558 
559  llvm::SmallVector<TextEdit, 1> Edits;
560  for (auto &FixIt : Info.getFixItHints()) {
561  // Follow clang's behavior, don't apply FixIt to the code in macros,
562  // we are less certain it is the right fix.
563  if (FixIt.RemoveRange.getBegin().isMacroID() ||
564  FixIt.RemoveRange.getEnd().isMacroID())
565  return false;
566  if (!isInsideMainFile(FixIt.RemoveRange.getBegin(), SM))
567  return false;
568  Edits.push_back(toTextEdit(FixIt, SM, *LangOpts));
569  }
570 
571  llvm::SmallString<64> Message;
572  // If requested and possible, create a message like "change 'foo' to 'bar'".
573  if (SyntheticMessage && Info.getNumFixItHints() == 1) {
574  const auto &FixIt = Info.getFixItHint(0);
575  bool Invalid = false;
576  llvm::StringRef Remove =
577  Lexer::getSourceText(FixIt.RemoveRange, SM, *LangOpts, &Invalid);
578  llvm::StringRef Insert = FixIt.CodeToInsert;
579  if (!Invalid) {
580  llvm::raw_svector_ostream M(Message);
581  if (!Remove.empty() && !Insert.empty()) {
582  M << "change '";
583  writeCodeToFixMessage(M, Remove);
584  M << "' to '";
585  writeCodeToFixMessage(M, Insert);
586  M << "'";
587  } else if (!Remove.empty()) {
588  M << "remove '";
589  writeCodeToFixMessage(M, Remove);
590  M << "'";
591  } else if (!Insert.empty()) {
592  M << "insert '";
593  writeCodeToFixMessage(M, Insert);
594  M << "'";
595  }
596  // Don't allow source code to inject newlines into diagnostics.
597  std::replace(Message.begin(), Message.end(), '\n', ' ');
598  }
599  }
600  if (Message.empty()) // either !SytheticMessage, or we failed to make one.
601  Info.FormatDiagnostic(Message);
602  LastDiag->Fixes.push_back(Fix{Message.str(), std::move(Edits)});
603  return true;
604  };
605 
606  if (!isNote(DiagLevel)) {
607  // Handle the new main diagnostic.
608  flushLastDiag();
609 
610  if (Adjuster) {
611  DiagLevel = Adjuster(DiagLevel, Info);
612  if (DiagLevel == DiagnosticsEngine::Ignored) {
613  LastPrimaryDiagnosticWasSuppressed = true;
614  return;
615  }
616  }
617  LastPrimaryDiagnosticWasSuppressed = false;
618 
619  LastDiag = Diag();
620  LastDiag->ID = Info.getID();
621  FillDiagBase(*LastDiag);
622  if (!InsideMainFile)
623  LastDiagWasAdjusted = adjustDiagFromHeader(*LastDiag, Info, *LangOpts);
624 
625  if (!Info.getFixItHints().empty())
626  AddFix(true /* try to invent a message instead of repeating the diag */);
627  if (Fixer) {
628  auto ExtraFixes = Fixer(DiagLevel, Info);
629  LastDiag->Fixes.insert(LastDiag->Fixes.end(), ExtraFixes.begin(),
630  ExtraFixes.end());
631  }
632  } else {
633  // Handle a note to an existing diagnostic.
634 
635  // If a diagnostic was suppressed due to the suppression filter,
636  // also suppress notes associated with it.
637  if (LastPrimaryDiagnosticWasSuppressed) {
638  return;
639  }
640 
641  if (!LastDiag) {
642  assert(false && "Adding a note without main diagnostic");
643  IgnoreDiagnostics::log(DiagLevel, Info);
644  return;
645  }
646 
647  if (!Info.getFixItHints().empty()) {
648  // A clang note with fix-it is not a separate diagnostic in clangd. We
649  // attach it as a Fix to the main diagnostic instead.
650  if (!AddFix(false /* use the note as the message */))
651  IgnoreDiagnostics::log(DiagLevel, Info);
652  } else {
653  // A clang note without fix-its corresponds to clangd::Note.
654  Note N;
655  FillDiagBase(N);
656 
657  LastDiag->Notes.push_back(std::move(N));
658  }
659  }
660 }
661 
662 void StoreDiags::flushLastDiag() {
663  if (!LastDiag)
664  return;
665  if (!isBlacklisted(*LastDiag) && mentionsMainFile(*LastDiag) &&
666  (!LastDiagWasAdjusted ||
667  // Only report the first diagnostic coming from each particular header.
668  IncludeLinesWithErrors.insert(LastDiag->Range.start.line).second)) {
669  Output.push_back(std::move(*LastDiag));
670  } else {
671  vlog("Dropped diagnostic: {0}: {1}", LastDiag->File, LastDiag->Message);
672  }
673  LastDiag.reset();
674  LastDiagWasAdjusted = false;
675 }
676 
677 } // namespace clangd
678 } // namespace clang
SourceLocation Loc
&#39;#&#39; location in the include directive
std::string code
The diagnostic&#39;s code. Can be omitted.
Definition: Protocol.h:656
std::string Code
static void log(DiagnosticsEngine::Level DiagLevel, const clang::Diagnostic &Info)
Definition: Compiler.cpp:20
Position start
The range&#39;s start position.
Definition: Protocol.h:158
bool EmbedFixesInDiagnostics
If true, Clangd uses an LSP extension to embed the fixes with the diagnostics that are sent to the cl...
Definition: Diagnostics.h:34
Contains basic information about a diagnostic.
Definition: Diagnostics.h:53
CodeAction toCodeAction(const Fix &F, const URIForFile &File)
Convert from Fix to LSP CodeAction.
llvm::Optional< std::vector< CodeAction > > codeActions
Clangd extension: code actions related to this diagnostic.
Definition: Protocol.h:678
llvm::Optional< std::string > kind
The kind of the code action.
Definition: Protocol.h:777
std::string title
A short, human-readable, title for this code action.
Definition: Protocol.h:773
Location location
The location of this related diagnostic information.
Definition: Protocol.h:640
Represents a related message and source code location for a diagnostic.
Definition: Protocol.h:638
bool isInsideMainFile(SourceLocation Loc, const SourceManager &SM)
Returns true iff Loc is inside the main file.
Definition: SourceCode.cpp:534
A code action represents a change that can be performed in code, e.g.
Definition: Protocol.h:771
constexpr llvm::StringLiteral Message
URIForFile uri
The text document&#39;s URI.
Definition: Protocol.h:184
llvm::Optional< WorkspaceEdit > edit
The workspace edit this code action performs.
Definition: Protocol.h:786
Documents should not be synced at all.
void BeginSourceFile(const LangOptions &Opts, const Preprocessor *) override
void vlog(const char *Fmt, Ts &&... Vals)
Definition: Logger.h:67
std::string Name
Definition: Diagnostics.h:86
static std::string replace(llvm::StringRef Haystack, llvm::StringRef Needle, llvm::StringRef Repl)
Definition: TestIndex.cpp:30
A top-level diagnostic that may have Notes and Fixes.
Definition: Diagnostics.h:84
std::string uri() const
Definition: Protocol.h:96
std::string Message
Message for the fix-it.
Definition: Diagnostics.h:73
std::vector< Fix > Fixes
Alternative fixes for this diagnostic, one should be chosen.
Definition: Diagnostics.h:96
std::string source
A human-readable string describing the source of this diagnostic, e.g.
Definition: Protocol.h:660
llvm::SmallVector< TextEdit, 1 > Edits
TextEdits from clang&#39;s fix-its. Must be non-empty.
Definition: Diagnostics.h:75
TextEdit toTextEdit(const FixItHint &FixIt, const SourceManager &M, const LangOptions &L)
Definition: SourceCode.cpp:661
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.
int getSeverity(DiagnosticsEngine::Level L)
Convert from clang diagnostic level to LSP severity.
void HandleDiagnostic(DiagnosticsEngine::Level DiagLevel, const clang::Diagnostic &Info) override
llvm::unique_function< void()> Action
llvm::Optional< std::string > category
The diagnostic&#39;s category.
Definition: Protocol.h:673
clangd::Range Range
Definition: Diagnostics.h:61
DiagnosticsEngine::Level Severity
Definition: Diagnostics.h:62
static URIForFile canonicalize(llvm::StringRef AbsPath, llvm::StringRef TUPath)
Canonicalizes AbsPath via URI.
Definition: Protocol.cpp:32
static constexpr llvm::StringLiteral Name
std::string message
The message of this related diagnostic information.
Definition: Protocol.h:642
Represents a single fix-it that editor can apply to fix the error.
Definition: Diagnostics.h:71
llvm::Optional< Range > getTokenRange(const SourceManager &SM, const LangOptions &LangOpts, SourceLocation TokLoc)
Returns the taken range at TokLoc.
Definition: SourceCode.cpp:227
Position Pos
Definition: SourceCode.cpp:772
Position sourceLocToPosition(const SourceManager &SM, SourceLocation Loc)
Turn a SourceLocation into a [line, column] pair.
Definition: SourceCode.cpp:200
static void fillNonLocationData(DiagnosticsEngine::Level DiagLevel, const clang::Diagnostic &Info, clangd::DiagBase &D)
Fills D with all information, except the location-related bits.
int line
Line position in a document (zero-based).
Definition: Protocol.h:129
int character
Character offset on a line in a document (zero-based).
Definition: Protocol.h:134
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++ -*-===//
std::string message
The diagnostic&#39;s message.
Definition: Protocol.h:663
bool EmitRelatedLocations
If true, Clangd uses the relatedInformation field to include other locations (in particular attached ...
Definition: Diagnostics.h:39
CharSourceRange Range
SourceRange for the file name.
static const llvm::StringLiteral QUICKFIX_KIND
Definition: Protocol.h:778
llvm::Optional< std::string > getCanonicalPath(const FileEntry *F, const SourceManager &SourceMgr)
Get the canonical path of F.
Definition: SourceCode.cpp:622
std::string getCheckName(unsigned DiagnosticID) const
Returns the name of the clang-tidy check which produced this diagnostic ID.
int severity
The diagnostic&#39;s severity.
Definition: Protocol.h:653
bool SendDiagnosticCategory
If true, Clangd uses an LSP extension to send the diagnostic&#39;s category to the client.
Definition: Diagnostics.h:45
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
void EndSourceFile() override
llvm::Optional< FixItHint > FixIt
Range range
The range at which the message applies.
Definition: Protocol.h:649
enum clang::clangd::Diag::@0 Source
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))
Position end
The range&#39;s end position.
Definition: Protocol.h:161
llvm::raw_ostream & operator<<(llvm::raw_ostream &OS, const CodeCompletion &C)
A set of edits generated for a single file.
Definition: SourceCode.h:204
std::vector< Diag > take(const clang::tidy::ClangTidyContext *Tidy=nullptr)
llvm::Optional< std::vector< DiagnosticRelatedInformation > > relatedInformation
An array of related diagnostic information, e.g.
Definition: Protocol.h:667
static void writeCodeToFixMessage(llvm::raw_ostream &OS, llvm::StringRef Code)
Sanitizes a piece for presenting it in a synthesized fix message.
llvm::Optional< std::string > AbsFile
Definition: Diagnostics.h:59
Range halfOpenToRange(const SourceManager &SM, CharSourceRange R)
Definition: SourceCode.cpp:581
llvm::StringRef file() const
Retrieves absolute path to the file.
Definition: Protocol.h:93