blob: 88bd6fdcad115ac635bde8af9cf8489c6744c8da [file] [log] [blame]
Kirill Bobyrev8e35f1e2018-08-14 16:03:32 +00001//===--- Diagnostics.cpp -----------------------------------------*- C++-*-===//
Ilya Biryukov71028b82018-03-12 15:28:22 +00002//
Chandler Carruth2946cd72019-01-19 08:50:56 +00003// 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
Ilya Biryukov71028b82018-03-12 15:28:22 +00006//
Kirill Bobyrev8e35f1e2018-08-14 16:03:32 +00007//===----------------------------------------------------------------------===//
Ilya Biryukov71028b82018-03-12 15:28:22 +00008
9#include "Diagnostics.h"
Sam McCall641caa52019-04-17 12:35:16 +000010#include "../clang-tidy/ClangTidyDiagnosticConsumer.h"
Ilya Biryukov71028b82018-03-12 15:28:22 +000011#include "Compiler.h"
12#include "Logger.h"
Sam McCallc9e4ee92019-04-18 15:17:07 +000013#include "Protocol.h"
Ilya Biryukov71028b82018-03-12 15:28:22 +000014#include "SourceCode.h"
Sam McCall641caa52019-04-17 12:35:16 +000015#include "clang/Basic/AllDiagnostics.h"
Kadir Cetinkaya01efe642019-04-29 10:25:44 +000016#include "clang/Basic/Diagnostic.h"
Sam McCall641caa52019-04-17 12:35:16 +000017#include "clang/Basic/DiagnosticIDs.h"
Kadir Cetinkaya01efe642019-04-29 10:25:44 +000018#include "clang/Basic/FileManager.h"
Ilya Biryukov71028b82018-03-12 15:28:22 +000019#include "clang/Basic/SourceManager.h"
20#include "clang/Lex/Lexer.h"
Kadir Cetinkaya88e636d2019-06-13 12:31:36 +000021#include "clang/Lex/Token.h"
Kadir Cetinkaya01efe642019-04-29 10:25:44 +000022#include "llvm/ADT/ArrayRef.h"
23#include "llvm/ADT/StringRef.h"
24#include "llvm/ADT/Twine.h"
Ilya Biryukov71028b82018-03-12 15:28:22 +000025#include "llvm/Support/Capacity.h"
26#include "llvm/Support/Path.h"
Kadir Cetinkaya01efe642019-04-29 10:25:44 +000027#include "llvm/Support/ScopedPrinter.h"
28#include "llvm/Support/Signals.h"
Ilya Biryukov0f748e62019-05-24 10:26:23 +000029#include "llvm/Support/raw_ostream.h"
Ilya Biryukov71028b82018-03-12 15:28:22 +000030#include <algorithm>
Ilya Biryukov0f748e62019-05-24 10:26:23 +000031#include <cstddef>
Ilya Biryukov71028b82018-03-12 15:28:22 +000032
33namespace clang {
34namespace clangd {
Ilya Biryukov71028b82018-03-12 15:28:22 +000035namespace {
36
Sam McCall641caa52019-04-17 12:35:16 +000037const char *getDiagnosticCode(unsigned ID) {
38 switch (ID) {
39#define DIAG(ENUM, CLASS, DEFAULT_MAPPING, DESC, GROPU, SFINAE, NOWERROR, \
40 SHOWINSYSHEADER, CATEGORY) \
41 case clang::diag::ENUM: \
42 return #ENUM;
43#include "clang/Basic/DiagnosticASTKinds.inc"
44#include "clang/Basic/DiagnosticAnalysisKinds.inc"
45#include "clang/Basic/DiagnosticCommentKinds.inc"
46#include "clang/Basic/DiagnosticCommonKinds.inc"
47#include "clang/Basic/DiagnosticDriverKinds.inc"
48#include "clang/Basic/DiagnosticFrontendKinds.inc"
49#include "clang/Basic/DiagnosticLexKinds.inc"
50#include "clang/Basic/DiagnosticParseKinds.inc"
51#include "clang/Basic/DiagnosticRefactoringKinds.inc"
52#include "clang/Basic/DiagnosticSemaKinds.inc"
53#include "clang/Basic/DiagnosticSerializationKinds.inc"
54#undef DIAG
55 default:
56 return nullptr;
57 }
58}
59
Ilya Biryukov71028b82018-03-12 15:28:22 +000060bool mentionsMainFile(const Diag &D) {
61 if (D.InsideMainFile)
62 return true;
63 // Fixes are always in the main file.
64 if (!D.Fixes.empty())
65 return true;
66 for (auto &N : D.Notes) {
67 if (N.InsideMainFile)
68 return true;
69 }
70 return false;
71}
72
73// Checks whether a location is within a half-open range.
74// Note that clang also uses closed source ranges, which this can't handle!
75bool locationInRange(SourceLocation L, CharSourceRange R,
76 const SourceManager &M) {
77 assert(R.isCharRange());
78 if (!R.isValid() || M.getFileID(R.getBegin()) != M.getFileID(R.getEnd()) ||
79 M.getFileID(R.getBegin()) != M.getFileID(L))
80 return false;
81 return L != R.getEnd() && M.isPointWithin(L, R.getBegin(), R.getEnd());
82}
83
84// Clang diags have a location (shown as ^) and 0 or more ranges (~~~~).
85// LSP needs a single range.
86Range diagnosticRange(const clang::Diagnostic &D, const LangOptions &L) {
87 auto &M = D.getSourceManager();
88 auto Loc = M.getFileLoc(D.getLocation());
Ilya Biryukov71028b82018-03-12 15:28:22 +000089 for (const auto &CR : D.getRanges()) {
90 auto R = Lexer::makeFileCharRange(CR, M, L);
91 if (locationInRange(Loc, R, M))
92 return halfOpenToRange(M, R);
93 }
94 // The range may be given as a fixit hint instead.
95 for (const auto &F : D.getFixItHints()) {
96 auto R = Lexer::makeFileCharRange(F.RemoveRange, M, L);
97 if (locationInRange(Loc, R, M))
98 return halfOpenToRange(M, R);
99 }
Kadir Cetinkaya88e636d2019-06-13 12:31:36 +0000100 // If the token at the location is not a comment, we use the token.
101 // If we can't get the token at the location, fall back to using the location
102 auto R = CharSourceRange::getCharRange(Loc);
103 Token Tok;
104 if (!Lexer::getRawToken(Loc, Tok, M, L, true) && Tok.isNot(tok::comment)) {
105 R = CharSourceRange::getTokenRange(Tok.getLocation(), Tok.getEndLoc());
106 }
Ilya Biryukov71028b82018-03-12 15:28:22 +0000107 return halfOpenToRange(M, R);
108}
109
Kadir Cetinkaya01efe642019-04-29 10:25:44 +0000110void adjustDiagFromHeader(Diag &D, const clang::Diagnostic &Info,
111 const LangOptions &LangOpts) {
112 const SourceLocation &DiagLoc = Info.getLocation();
113 const SourceManager &SM = Info.getSourceManager();
114 SourceLocation IncludeInMainFile;
115 auto GetIncludeLoc = [&SM](SourceLocation SLoc) {
116 return SM.getIncludeLoc(SM.getFileID(SLoc));
117 };
118 for (auto IncludeLocation = GetIncludeLoc(DiagLoc); IncludeLocation.isValid();
119 IncludeLocation = GetIncludeLoc(IncludeLocation))
120 IncludeInMainFile = IncludeLocation;
121 if (IncludeInMainFile.isInvalid())
122 return;
123
124 // Update diag to point at include inside main file.
125 D.File = SM.getFileEntryForID(SM.getMainFileID())->getName().str();
126 D.Range.start = sourceLocToPosition(SM, IncludeInMainFile);
127 D.Range.end = sourceLocToPosition(
128 SM, Lexer::getLocForEndOfToken(IncludeInMainFile, 0, SM, LangOpts));
129
130 // Add a note that will point to real diagnostic.
131 const auto *FE = SM.getFileEntryForID(SM.getFileID(DiagLoc));
132 D.Notes.emplace_back();
133 Note &N = D.Notes.back();
134 N.AbsFile = FE->tryGetRealPathName();
135 N.File = FE->getName();
136 N.Message = "error occurred here";
137 N.Range = diagnosticRange(Info, LangOpts);
138
139 // Update message to mention original file.
140 D.Message = llvm::Twine("in included file: ", D.Message).str();
141}
142
Ilya Biryukov71028b82018-03-12 15:28:22 +0000143bool isInsideMainFile(const SourceLocation Loc, const SourceManager &M) {
Ilya Biryukove8ccb822018-11-26 17:05:13 +0000144 return Loc.isValid() && M.isWrittenInMainFile(M.getFileLoc(Loc));
Ilya Biryukov71028b82018-03-12 15:28:22 +0000145}
146
147bool isInsideMainFile(const clang::Diagnostic &D) {
148 if (!D.hasSourceManager())
149 return false;
150
151 return isInsideMainFile(D.getLocation(), D.getSourceManager());
152}
153
154bool isNote(DiagnosticsEngine::Level L) {
155 return L == DiagnosticsEngine::Note || L == DiagnosticsEngine::Remark;
156}
157
Ilya Biryukovf2001aa2019-01-07 15:45:19 +0000158llvm::StringRef diagLeveltoString(DiagnosticsEngine::Level Lvl) {
Ilya Biryukov71028b82018-03-12 15:28:22 +0000159 switch (Lvl) {
160 case DiagnosticsEngine::Ignored:
161 return "ignored";
162 case DiagnosticsEngine::Note:
163 return "note";
164 case DiagnosticsEngine::Remark:
165 return "remark";
166 case DiagnosticsEngine::Warning:
167 return "warning";
168 case DiagnosticsEngine::Error:
169 return "error";
170 case DiagnosticsEngine::Fatal:
171 return "fatal error";
172 }
173 llvm_unreachable("unhandled DiagnosticsEngine::Level");
174}
175
176/// Prints a single diagnostic in a clang-like manner, the output includes
177/// location, severity and error message. An example of the output message is:
178///
179/// main.cpp:12:23: error: undeclared identifier
180///
181/// For main file we only print the basename and for all other files we print
182/// the filename on a separate line to provide a slightly more readable output
183/// in the editors:
184///
185/// dir1/dir2/dir3/../../dir4/header.h:12:23
186/// error: undeclared identifier
Ilya Biryukovf2001aa2019-01-07 15:45:19 +0000187void printDiag(llvm::raw_string_ostream &OS, const DiagBase &D) {
Ilya Biryukov71028b82018-03-12 15:28:22 +0000188 if (D.InsideMainFile) {
189 // Paths to main files are often taken from compile_command.json, where they
190 // are typically absolute. To reduce noise we print only basename for them,
191 // it should not be confusing and saves space.
Ilya Biryukovf2001aa2019-01-07 15:45:19 +0000192 OS << llvm::sys::path::filename(D.File) << ":";
Ilya Biryukov71028b82018-03-12 15:28:22 +0000193 } else {
194 OS << D.File << ":";
195 }
196 // Note +1 to line and character. clangd::Range is zero-based, but when
197 // printing for users we want one-based indexes.
198 auto Pos = D.Range.start;
199 OS << (Pos.line + 1) << ":" << (Pos.character + 1) << ":";
200 // The non-main-file paths are often too long, putting them on a separate
201 // line improves readability.
202 if (D.InsideMainFile)
203 OS << " ";
204 else
205 OS << "\n";
206 OS << diagLeveltoString(D.Severity) << ": " << D.Message;
207}
208
Alex Lorenzb411cf32018-08-03 20:43:28 +0000209/// Capitalizes the first word in the diagnostic's message.
210std::string capitalize(std::string Message) {
211 if (!Message.empty())
Ilya Biryukovf2001aa2019-01-07 15:45:19 +0000212 Message[0] = llvm::toUpper(Message[0]);
Alex Lorenzb411cf32018-08-03 20:43:28 +0000213 return Message;
214}
215
Ilya Biryukov71028b82018-03-12 15:28:22 +0000216/// Returns a message sent to LSP for the main diagnostic in \p D.
Sam McCallc9e4ee92019-04-18 15:17:07 +0000217/// This message may include notes, if they're not emited in some other way.
Ilya Biryukov71028b82018-03-12 15:28:22 +0000218/// Example output:
219///
220/// no matching function for call to 'foo'
221///
222/// main.cpp:3:5: note: candidate function not viable: requires 2 arguments
223///
224/// dir1/dir2/dir3/../../dir4/header.h:12:23
225/// note: candidate function not viable: requires 3 arguments
Sam McCallc9e4ee92019-04-18 15:17:07 +0000226std::string mainMessage(const Diag &D, const ClangdDiagnosticOptions &Opts) {
Ilya Biryukov71028b82018-03-12 15:28:22 +0000227 std::string Result;
Ilya Biryukovf2001aa2019-01-07 15:45:19 +0000228 llvm::raw_string_ostream OS(Result);
Ilya Biryukov71028b82018-03-12 15:28:22 +0000229 OS << D.Message;
Sam McCallc9e4ee92019-04-18 15:17:07 +0000230 if (Opts.DisplayFixesCount && !D.Fixes.empty())
Eric Liu00eaf672019-01-31 16:09:25 +0000231 OS << " (" << (D.Fixes.size() > 1 ? "fixes" : "fix") << " available)";
Sam McCallc9e4ee92019-04-18 15:17:07 +0000232 // If notes aren't emitted as structured info, add them to the message.
233 if (!Opts.EmitRelatedLocations)
234 for (auto &Note : D.Notes) {
235 OS << "\n\n";
236 printDiag(OS, Note);
237 }
Ilya Biryukov71028b82018-03-12 15:28:22 +0000238 OS.flush();
Alex Lorenzb411cf32018-08-03 20:43:28 +0000239 return capitalize(std::move(Result));
Ilya Biryukov71028b82018-03-12 15:28:22 +0000240}
241
242/// Returns a message sent to LSP for the note of the main diagnostic.
Sam McCallc9e4ee92019-04-18 15:17:07 +0000243std::string noteMessage(const Diag &Main, const DiagBase &Note,
244 const ClangdDiagnosticOptions &Opts) {
Ilya Biryukov71028b82018-03-12 15:28:22 +0000245 std::string Result;
Ilya Biryukovf2001aa2019-01-07 15:45:19 +0000246 llvm::raw_string_ostream OS(Result);
Ilya Biryukov71028b82018-03-12 15:28:22 +0000247 OS << Note.Message;
Sam McCallc9e4ee92019-04-18 15:17:07 +0000248 // If the client doesn't support structured links between the note and the
249 // original diagnostic, then emit the main diagnostic to give context.
250 if (!Opts.EmitRelatedLocations) {
251 OS << "\n\n";
252 printDiag(OS, Main);
253 }
Ilya Biryukov71028b82018-03-12 15:28:22 +0000254 OS.flush();
Alex Lorenzb411cf32018-08-03 20:43:28 +0000255 return capitalize(std::move(Result));
Ilya Biryukov71028b82018-03-12 15:28:22 +0000256}
257} // namespace
258
Ilya Biryukovf2001aa2019-01-07 15:45:19 +0000259llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const DiagBase &D) {
Sam McCall991e3162018-11-20 10:58:48 +0000260 OS << "[";
Ilya Biryukov71028b82018-03-12 15:28:22 +0000261 if (!D.InsideMainFile)
Sam McCall991e3162018-11-20 10:58:48 +0000262 OS << D.File << ":";
263 OS << D.Range.start << "-" << D.Range.end << "] ";
264
Ilya Biryukov71028b82018-03-12 15:28:22 +0000265 return OS << D.Message;
266}
267
Ilya Biryukovf2001aa2019-01-07 15:45:19 +0000268llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const Fix &F) {
Ilya Biryukov71028b82018-03-12 15:28:22 +0000269 OS << F.Message << " {";
270 const char *Sep = "";
271 for (const auto &Edit : F.Edits) {
272 OS << Sep << Edit;
273 Sep = ", ";
274 }
275 return OS << "}";
276}
277
Ilya Biryukovf2001aa2019-01-07 15:45:19 +0000278llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const Diag &D) {
Ilya Biryukov71028b82018-03-12 15:28:22 +0000279 OS << static_cast<const DiagBase &>(D);
280 if (!D.Notes.empty()) {
281 OS << ", notes: {";
282 const char *Sep = "";
283 for (auto &Note : D.Notes) {
284 OS << Sep << Note;
285 Sep = ", ";
286 }
287 OS << "}";
288 }
289 if (!D.Fixes.empty()) {
290 OS << ", fixes: {";
291 const char *Sep = "";
292 for (auto &Fix : D.Fixes) {
293 OS << Sep << Fix;
294 Sep = ", ";
295 }
296 }
297 return OS;
298}
299
Sam McCall16e70702018-10-24 07:59:38 +0000300CodeAction toCodeAction(const Fix &F, const URIForFile &File) {
301 CodeAction Action;
302 Action.title = F.Message;
303 Action.kind = CodeAction::QUICKFIX_KIND;
304 Action.edit.emplace();
305 Action.edit->changes.emplace();
306 (*Action.edit->changes)[File.uri()] = {F.Edits.begin(), F.Edits.end()};
307 return Action;
308}
309
Ilya Biryukovf2001aa2019-01-07 15:45:19 +0000310void toLSPDiags(
311 const Diag &D, const URIForFile &File, const ClangdDiagnosticOptions &Opts,
312 llvm::function_ref<void(clangd::Diagnostic, llvm::ArrayRef<Fix>)> OutFn) {
Ilya Biryukov71028b82018-03-12 15:28:22 +0000313 auto FillBasicFields = [](const DiagBase &D) -> clangd::Diagnostic {
314 clangd::Diagnostic Res;
315 Res.range = D.Range;
316 Res.severity = getSeverity(D.Severity);
317 return Res;
318 };
319
Sam McCallc9e4ee92019-04-18 15:17:07 +0000320 clangd::Diagnostic Main = FillBasicFields(D);
321 Main.code = D.Name;
322 switch (D.Source) {
323 case Diag::Clang:
324 Main.source = "clang";
325 break;
326 case Diag::ClangTidy:
327 Main.source = "clang-tidy";
328 break;
329 case Diag::Unknown:
330 break;
Ilya Biryukov71028b82018-03-12 15:28:22 +0000331 }
Sam McCallc9e4ee92019-04-18 15:17:07 +0000332 if (Opts.EmbedFixesInDiagnostics) {
333 Main.codeActions.emplace();
334 for (const auto &Fix : D.Fixes)
335 Main.codeActions->push_back(toCodeAction(Fix, File));
Ilya Biryukov71028b82018-03-12 15:28:22 +0000336 }
Sam McCallc9e4ee92019-04-18 15:17:07 +0000337 if (Opts.SendDiagnosticCategory && !D.Category.empty())
338 Main.category = D.Category;
339
340 Main.message = mainMessage(D, Opts);
341 if (Opts.EmitRelatedLocations) {
342 Main.relatedInformation.emplace();
343 for (auto &Note : D.Notes) {
344 if (!Note.AbsFile) {
345 vlog("Dropping note from unknown file: {0}", Note);
346 continue;
347 }
348 DiagnosticRelatedInformation RelInfo;
349 RelInfo.location.range = Note.Range;
350 RelInfo.location.uri =
351 URIForFile::canonicalize(*Note.AbsFile, File.file());
352 RelInfo.message = noteMessage(D, Note, Opts);
353 Main.relatedInformation->push_back(std::move(RelInfo));
354 }
355 }
356 OutFn(std::move(Main), D.Fixes);
357
358 // If we didn't emit the notes as relatedLocations, emit separate diagnostics
359 // so the user can find the locations easily.
360 if (!Opts.EmitRelatedLocations)
361 for (auto &Note : D.Notes) {
362 if (!Note.InsideMainFile)
363 continue;
364 clangd::Diagnostic Res = FillBasicFields(Note);
365 Res.message = noteMessage(D, Note, Opts);
366 OutFn(std::move(Res), llvm::ArrayRef<Fix>());
367 }
Ilya Biryukov71028b82018-03-12 15:28:22 +0000368}
369
370int getSeverity(DiagnosticsEngine::Level L) {
371 switch (L) {
372 case DiagnosticsEngine::Remark:
373 return 4;
374 case DiagnosticsEngine::Note:
375 return 3;
376 case DiagnosticsEngine::Warning:
377 return 2;
378 case DiagnosticsEngine::Fatal:
379 case DiagnosticsEngine::Error:
380 return 1;
381 case DiagnosticsEngine::Ignored:
382 return 0;
383 }
384 llvm_unreachable("Unknown diagnostic level!");
385}
386
Sam McCall641caa52019-04-17 12:35:16 +0000387std::vector<Diag> StoreDiags::take(const clang::tidy::ClangTidyContext *Tidy) {
388 // Fill in name/source now that we have all the context needed to map them.
389 for (auto &Diag : Output) {
390 if (const char *ClangDiag = getDiagnosticCode(Diag.ID)) {
Sam McCalld98170c2019-04-17 20:12:03 +0000391 // Warnings controlled by -Wfoo are better recognized by that name.
392 StringRef Warning = DiagnosticIDs::getWarningOptionForDiag(Diag.ID);
393 if (!Warning.empty()) {
394 Diag.Name = ("-W" + Warning).str();
395 } else {
396 StringRef Name(ClangDiag);
397 // Almost always an error, with a name like err_enum_class_reference.
398 // Drop the err_ prefix for brevity.
399 Name.consume_front("err_");
400 Diag.Name = Name;
401 }
Sam McCall641caa52019-04-17 12:35:16 +0000402 Diag.Source = Diag::Clang;
403 continue;
404 }
405 if (Tidy != nullptr) {
406 std::string TidyDiag = Tidy->getCheckName(Diag.ID);
407 if (!TidyDiag.empty()) {
408 Diag.Name = std::move(TidyDiag);
409 Diag.Source = Diag::ClangTidy;
Sam McCallaa4eb102019-04-17 20:15:08 +0000410 // clang-tidy bakes the name into diagnostic messages. Strip it out.
411 // It would be much nicer to make clang-tidy not do this.
412 auto CleanMessage = [&](std::string &Msg) {
413 StringRef Rest(Msg);
414 if (Rest.consume_back("]") && Rest.consume_back(Diag.Name) &&
415 Rest.consume_back(" ["))
416 Msg.resize(Rest.size());
417 };
418 CleanMessage(Diag.Message);
Kadir Cetinkaya01efe642019-04-29 10:25:44 +0000419 for (auto &Note : Diag.Notes)
Sam McCallaa4eb102019-04-17 20:15:08 +0000420 CleanMessage(Note.Message);
Haojian Wub739b912019-07-01 08:05:53 +0000421 for (auto &Fix : Diag.Fixes)
422 CleanMessage(Fix.Message);
Sam McCall641caa52019-04-17 12:35:16 +0000423 continue;
424 }
425 }
426 }
Haojian Wuee080362019-07-05 12:57:56 +0000427 // Deduplicate clang-tidy diagnostics -- some clang-tidy checks may emit
428 // duplicated messages due to various reasons (e.g. the check doesn't handle
429 // template instantiations well; clang-tidy alias checks).
430 std::set<std::pair<Range, std::string>> SeenDiags;
431 llvm::erase_if(Output, [&](const Diag& D) {
432 return !SeenDiags.emplace(D.Range, D.Message).second;
433 });
Sam McCall641caa52019-04-17 12:35:16 +0000434 return std::move(Output);
435}
Ilya Biryukov71028b82018-03-12 15:28:22 +0000436
437void StoreDiags::BeginSourceFile(const LangOptions &Opts,
438 const Preprocessor *) {
439 LangOpts = Opts;
440}
441
442void StoreDiags::EndSourceFile() {
443 flushLastDiag();
Sam McCallc008af62018-10-20 15:30:37 +0000444 LangOpts = None;
Ilya Biryukov71028b82018-03-12 15:28:22 +0000445}
446
Ilya Biryukov0f748e62019-05-24 10:26:23 +0000447/// Sanitizes a piece for presenting it in a synthesized fix message. Ensures
448/// the result is not too large and does not contain newlines.
449static void writeCodeToFixMessage(llvm::raw_ostream &OS, llvm::StringRef Code) {
450 constexpr unsigned MaxLen = 50;
451
452 // Only show the first line if there are many.
453 llvm::StringRef R = Code.split('\n').first;
454 // Shorten the message if it's too long.
455 R = R.take_front(MaxLen);
456
457 OS << R;
458 if (R.size() != Code.size())
459 OS << "…";
460}
461
Ilya Biryukov71028b82018-03-12 15:28:22 +0000462void StoreDiags::HandleDiagnostic(DiagnosticsEngine::Level DiagLevel,
463 const clang::Diagnostic &Info) {
464 DiagnosticConsumer::HandleDiagnostic(DiagLevel, Info);
465
466 if (!LangOpts || !Info.hasSourceManager()) {
467 IgnoreDiagnostics::log(DiagLevel, Info);
468 return;
469 }
470
471 bool InsideMainFile = isInsideMainFile(Info);
472
473 auto FillDiagBase = [&](DiagBase &D) {
474 D.Range = diagnosticRange(Info, *LangOpts);
Ilya Biryukovf2001aa2019-01-07 15:45:19 +0000475 llvm::SmallString<64> Message;
Ilya Biryukov71028b82018-03-12 15:28:22 +0000476 Info.FormatDiagnostic(Message);
477 D.Message = Message.str();
478 D.InsideMainFile = InsideMainFile;
479 D.File = Info.getSourceManager().getFilename(Info.getLocation());
Sam McCallc9e4ee92019-04-18 15:17:07 +0000480 auto &SM = Info.getSourceManager();
481 D.AbsFile = getCanonicalPath(
482 SM.getFileEntryForID(SM.getFileID(Info.getLocation())), SM);
Ilya Biryukov71028b82018-03-12 15:28:22 +0000483 D.Severity = DiagLevel;
Alex Lorenz37146432018-08-14 22:21:40 +0000484 D.Category = DiagnosticIDs::getCategoryNameFromID(
485 DiagnosticIDs::getCategoryNumberForDiag(Info.getID()))
486 .str();
Ilya Biryukov71028b82018-03-12 15:28:22 +0000487 return D;
488 };
489
Sam McCallba3c02e2018-04-03 17:35:57 +0000490 auto AddFix = [&](bool SyntheticMessage) -> bool {
Ilya Biryukov71028b82018-03-12 15:28:22 +0000491 assert(!Info.getFixItHints().empty() &&
492 "diagnostic does not have attached fix-its");
493 if (!InsideMainFile)
494 return false;
495
Ilya Biryukovf2001aa2019-01-07 15:45:19 +0000496 llvm::SmallVector<TextEdit, 1> Edits;
Ilya Biryukov71028b82018-03-12 15:28:22 +0000497 for (auto &FixIt : Info.getFixItHints()) {
Haojian Wuc8f74962019-02-22 09:43:56 +0000498 // Follow clang's behavior, don't apply FixIt to the code in macros,
499 // we are less certain it is the right fix.
500 if (FixIt.RemoveRange.getBegin().isMacroID() ||
501 FixIt.RemoveRange.getEnd().isMacroID())
502 return false;
Ilya Biryukov71028b82018-03-12 15:28:22 +0000503 if (!isInsideMainFile(FixIt.RemoveRange.getBegin(),
504 Info.getSourceManager()))
505 return false;
506 Edits.push_back(toTextEdit(FixIt, Info.getSourceManager(), *LangOpts));
507 }
508
Ilya Biryukovf2001aa2019-01-07 15:45:19 +0000509 llvm::SmallString<64> Message;
Sam McCallba3c02e2018-04-03 17:35:57 +0000510 // If requested and possible, create a message like "change 'foo' to 'bar'".
511 if (SyntheticMessage && Info.getNumFixItHints() == 1) {
512 const auto &FixIt = Info.getFixItHint(0);
513 bool Invalid = false;
Ilya Biryukovf2001aa2019-01-07 15:45:19 +0000514 llvm::StringRef Remove = Lexer::getSourceText(
Sam McCallba3c02e2018-04-03 17:35:57 +0000515 FixIt.RemoveRange, Info.getSourceManager(), *LangOpts, &Invalid);
Ilya Biryukovf2001aa2019-01-07 15:45:19 +0000516 llvm::StringRef Insert = FixIt.CodeToInsert;
Sam McCallba3c02e2018-04-03 17:35:57 +0000517 if (!Invalid) {
Ilya Biryukovf2001aa2019-01-07 15:45:19 +0000518 llvm::raw_svector_ostream M(Message);
Ilya Biryukov0f748e62019-05-24 10:26:23 +0000519 if (!Remove.empty() && !Insert.empty()) {
520 M << "change '";
521 writeCodeToFixMessage(M, Remove);
522 M << "' to '";
523 writeCodeToFixMessage(M, Insert);
524 M << "'";
525 } else if (!Remove.empty()) {
526 M << "remove '";
527 writeCodeToFixMessage(M, Remove);
528 M << "'";
529 } else if (!Insert.empty()) {
530 M << "insert '";
531 writeCodeToFixMessage(M, Insert);
532 M << "'";
533 }
Sam McCallba3c02e2018-04-03 17:35:57 +0000534 // Don't allow source code to inject newlines into diagnostics.
535 std::replace(Message.begin(), Message.end(), '\n', ' ');
536 }
537 }
538 if (Message.empty()) // either !SytheticMessage, or we failed to make one.
539 Info.FormatDiagnostic(Message);
Ilya Biryukov71028b82018-03-12 15:28:22 +0000540 LastDiag->Fixes.push_back(Fix{Message.str(), std::move(Edits)});
541 return true;
542 };
543
544 if (!isNote(DiagLevel)) {
545 // Handle the new main diagnostic.
546 flushLastDiag();
547
Fangrui Songcb4b3e52019-05-19 04:19:14 +0000548 if (Adjuster) {
549 DiagLevel = Adjuster(DiagLevel, Info);
550 if (DiagLevel == DiagnosticsEngine::Ignored) {
551 LastPrimaryDiagnosticWasSuppressed = true;
552 return;
553 }
Fangrui Songc2aded52019-05-19 04:06:52 +0000554 }
555 LastPrimaryDiagnosticWasSuppressed = false;
556
Ilya Biryukov71028b82018-03-12 15:28:22 +0000557 LastDiag = Diag();
Haojian Wu24a8f1c2019-03-06 10:51:38 +0000558 LastDiag->ID = Info.getID();
Ilya Biryukov71028b82018-03-12 15:28:22 +0000559 FillDiagBase(*LastDiag);
Kadir Cetinkaya01efe642019-04-29 10:25:44 +0000560 adjustDiagFromHeader(*LastDiag, Info, *LangOpts);
Ilya Biryukov71028b82018-03-12 15:28:22 +0000561
562 if (!Info.getFixItHints().empty())
Sam McCallba3c02e2018-04-03 17:35:57 +0000563 AddFix(true /* try to invent a message instead of repeating the diag */);
Eric Liudd662772019-01-28 14:01:55 +0000564 if (Fixer) {
565 auto ExtraFixes = Fixer(DiagLevel, Info);
566 LastDiag->Fixes.insert(LastDiag->Fixes.end(), ExtraFixes.begin(),
567 ExtraFixes.end());
568 }
Ilya Biryukov71028b82018-03-12 15:28:22 +0000569 } else {
570 // Handle a note to an existing diagnostic.
Fangrui Songc2aded52019-05-19 04:06:52 +0000571
572 // If a diagnostic was suppressed due to the suppression filter,
573 // also suppress notes associated with it.
574 if (LastPrimaryDiagnosticWasSuppressed) {
575 return;
576 }
577
Ilya Biryukov71028b82018-03-12 15:28:22 +0000578 if (!LastDiag) {
579 assert(false && "Adding a note without main diagnostic");
580 IgnoreDiagnostics::log(DiagLevel, Info);
581 return;
582 }
583
584 if (!Info.getFixItHints().empty()) {
585 // A clang note with fix-it is not a separate diagnostic in clangd. We
586 // attach it as a Fix to the main diagnostic instead.
Sam McCallba3c02e2018-04-03 17:35:57 +0000587 if (!AddFix(false /* use the note as the message */))
Ilya Biryukov71028b82018-03-12 15:28:22 +0000588 IgnoreDiagnostics::log(DiagLevel, Info);
589 } else {
590 // A clang note without fix-its corresponds to clangd::Note.
591 Note N;
592 FillDiagBase(N);
593
594 LastDiag->Notes.push_back(std::move(N));
595 }
596 }
597}
598
599void StoreDiags::flushLastDiag() {
600 if (!LastDiag)
601 return;
Kadir Cetinkaya01efe642019-04-29 10:25:44 +0000602 // Only keeps diagnostics inside main file or the first one coming from a
603 // header.
604 if (mentionsMainFile(*LastDiag) ||
605 (LastDiag->Severity >= DiagnosticsEngine::Level::Error &&
606 IncludeLinesWithErrors.insert(LastDiag->Range.start.line).second)) {
Ilya Biryukov71028b82018-03-12 15:28:22 +0000607 Output.push_back(std::move(*LastDiag));
Kadir Cetinkaya01efe642019-04-29 10:25:44 +0000608 } else {
609 vlog("Dropped diagnostic: {0}: {1}", LastDiag->File, LastDiag->Message);
610 }
Ilya Biryukov71028b82018-03-12 15:28:22 +0000611 LastDiag.reset();
612}
613
614} // namespace clangd
615} // namespace clang