1 //===--- Diagnostics.cpp -----------------------------------------*- C++-*-===//
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
7 //===----------------------------------------------------------------------===//
9 #include "Diagnostics.h"
10 #include "../clang-tidy/ClangTidyDiagnosticConsumer.h"
13 #include "SourceCode.h"
14 #include "support/Logger.h"
15 #include "clang/Basic/AllDiagnostics.h" // IWYU pragma: keep
16 #include "clang/Basic/Diagnostic.h"
17 #include "clang/Basic/DiagnosticIDs.h"
18 #include "clang/Basic/LLVM.h"
19 #include "clang/Basic/SourceLocation.h"
20 #include "clang/Basic/SourceManager.h"
21 #include "clang/Basic/TokenKinds.h"
22 #include "clang/Lex/Lexer.h"
23 #include "clang/Lex/Token.h"
24 #include "llvm/ADT/ArrayRef.h"
25 #include "llvm/ADT/DenseSet.h"
26 #include "llvm/ADT/STLExtras.h"
27 #include "llvm/ADT/STLFunctionalExtras.h"
28 #include "llvm/ADT/ScopeExit.h"
29 #include "llvm/ADT/SmallString.h"
30 #include "llvm/ADT/SmallVector.h"
31 #include "llvm/ADT/StringExtras.h"
32 #include "llvm/ADT/StringRef.h"
33 #include "llvm/ADT/StringSet.h"
34 #include "llvm/ADT/Twine.h"
35 #include "llvm/Support/ErrorHandling.h"
36 #include "llvm/Support/FormatVariadic.h"
37 #include "llvm/Support/Path.h"
38 #include "llvm/Support/SourceMgr.h"
39 #include "llvm/Support/raw_ostream.h"
53 const char *getDiagnosticCode(unsigned ID
) {
55 #define DIAG(ENUM, CLASS, DEFAULT_MAPPING, DESC, GROPU, SFINAE, NOWERROR, \
56 SHOWINSYSHEADER, SHOWINSYSMACRO, DEFERRABLE, CATEGORY) \
57 case clang::diag::ENUM: \
59 #include "clang/Basic/DiagnosticASTKinds.inc"
60 #include "clang/Basic/DiagnosticAnalysisKinds.inc"
61 #include "clang/Basic/DiagnosticCommentKinds.inc"
62 #include "clang/Basic/DiagnosticCommonKinds.inc"
63 #include "clang/Basic/DiagnosticDriverKinds.inc"
64 #include "clang/Basic/DiagnosticFrontendKinds.inc"
65 #include "clang/Basic/DiagnosticLexKinds.inc"
66 #include "clang/Basic/DiagnosticParseKinds.inc"
67 #include "clang/Basic/DiagnosticRefactoringKinds.inc"
68 #include "clang/Basic/DiagnosticSemaKinds.inc"
69 #include "clang/Basic/DiagnosticSerializationKinds.inc"
76 bool mentionsMainFile(const Diag
&D
) {
79 // Fixes are always in the main file.
82 for (auto &N
: D
.Notes
) {
89 bool isExcluded(unsigned DiagID
) {
90 // clang will always fail parsing MS ASM, we don't link in desc + asm parser.
91 if (DiagID
== clang::diag::err_msasm_unable_to_create_target
||
92 DiagID
== clang::diag::err_msasm_unsupported_arch
)
97 // Checks whether a location is within a half-open range.
98 // Note that clang also uses closed source ranges, which this can't handle!
99 bool locationInRange(SourceLocation L
, CharSourceRange R
,
100 const SourceManager
&M
) {
101 assert(R
.isCharRange());
102 if (!R
.isValid() || M
.getFileID(R
.getBegin()) != M
.getFileID(R
.getEnd()) ||
103 M
.getFileID(R
.getBegin()) != M
.getFileID(L
))
105 return L
!= R
.getEnd() && M
.isPointWithin(L
, R
.getBegin(), R
.getEnd());
108 // Clang diags have a location (shown as ^) and 0 or more ranges (~~~~).
109 // LSP needs a single range.
110 std::optional
<Range
> diagnosticRange(const clang::Diagnostic
&D
,
111 const LangOptions
&L
) {
112 auto &M
= D
.getSourceManager();
113 auto PatchedRange
= [&M
](CharSourceRange
&R
) {
114 R
.setBegin(translatePreamblePatchLocation(R
.getBegin(), M
));
115 R
.setEnd(translatePreamblePatchLocation(R
.getEnd(), M
));
118 auto Loc
= M
.getFileLoc(D
.getLocation());
119 for (const auto &CR
: D
.getRanges()) {
120 auto R
= Lexer::makeFileCharRange(CR
, M
, L
);
121 if (locationInRange(Loc
, R
, M
))
122 return halfOpenToRange(M
, PatchedRange(R
));
124 // The range may be given as a fixit hint instead.
125 for (const auto &F
: D
.getFixItHints()) {
126 auto R
= Lexer::makeFileCharRange(F
.RemoveRange
, M
, L
);
127 if (locationInRange(Loc
, R
, M
))
128 return halfOpenToRange(M
, PatchedRange(R
));
130 // Source locations from stale preambles might become OOB.
131 // FIXME: These diagnostics might point to wrong locations even when they're
133 auto [FID
, Offset
] = M
.getDecomposedLoc(Loc
);
134 if (Offset
> M
.getBufferData(FID
).size())
136 // If the token at the location is not a comment, we use the token.
137 // If we can't get the token at the location, fall back to using the location
138 auto R
= CharSourceRange::getCharRange(Loc
);
140 if (!Lexer::getRawToken(Loc
, Tok
, M
, L
, true) && Tok
.isNot(tok::comment
))
141 R
= CharSourceRange::getTokenRange(Tok
.getLocation(), Tok
.getEndLoc());
142 return halfOpenToRange(M
, PatchedRange(R
));
145 // Try to find a location in the main-file to report the diagnostic D.
146 // Returns a description like "in included file", or nullptr on failure.
147 const char *getMainFileRange(const Diag
&D
, const SourceManager
&SM
,
148 SourceLocation DiagLoc
, Range
&R
) {
149 // Look for a note in the main file indicating template instantiation.
150 for (const auto &N
: D
.Notes
) {
151 if (N
.InsideMainFile
) {
153 case diag::note_template_class_instantiation_was_here
:
154 case diag::note_template_class_explicit_specialization_was_here
:
155 case diag::note_template_class_instantiation_here
:
156 case diag::note_template_member_class_here
:
157 case diag::note_template_member_function_here
:
158 case diag::note_function_template_spec_here
:
159 case diag::note_template_static_data_member_def_here
:
160 case diag::note_template_variable_def_here
:
161 case diag::note_template_enum_def_here
:
162 case diag::note_template_nsdmi_here
:
163 case diag::note_template_type_alias_instantiation_here
:
164 case diag::note_template_exception_spec_instantiation_here
:
165 case diag::note_template_requirement_instantiation_here
:
166 case diag::note_evaluating_exception_spec_here
:
167 case diag::note_default_arg_instantiation_here
:
168 case diag::note_default_function_arg_instantiation_here
:
169 case diag::note_explicit_template_arg_substitution_here
:
170 case diag::note_function_template_deduction_instantiation_here
:
171 case diag::note_deduced_template_arg_substitution_here
:
172 case diag::note_prior_template_arg_substitution
:
173 case diag::note_template_default_arg_checking
:
174 case diag::note_concept_specialization_here
:
175 case diag::note_nested_requirement_here
:
176 case diag::note_checking_constraints_for_template_id_here
:
177 case diag::note_checking_constraints_for_var_spec_id_here
:
178 case diag::note_checking_constraints_for_class_spec_id_here
:
179 case diag::note_checking_constraints_for_function_here
:
180 case diag::note_constraint_substitution_here
:
181 case diag::note_constraint_normalization_here
:
182 case diag::note_parameter_mapping_substitution_here
:
184 return "in template";
190 // Look for where the file with the error was #included.
191 auto GetIncludeLoc
= [&SM
](SourceLocation SLoc
) {
192 return SM
.getIncludeLoc(SM
.getFileID(SLoc
));
194 for (auto IncludeLocation
= GetIncludeLoc(SM
.getExpansionLoc(DiagLoc
));
195 IncludeLocation
.isValid();
196 IncludeLocation
= GetIncludeLoc(IncludeLocation
)) {
197 if (clangd::isInsideMainFile(IncludeLocation
, SM
)) {
198 R
.start
= sourceLocToPosition(SM
, IncludeLocation
);
199 R
.end
= sourceLocToPosition(
201 Lexer::getLocForEndOfToken(IncludeLocation
, 0, SM
, LangOptions()));
202 return "in included file";
208 // Place the diagnostic the main file, rather than the header, if possible:
209 // - for errors in included files, use the #include location
210 // - for errors in template instantiation, use the instantiation location
211 // In both cases, add the original header location as a note.
212 bool tryMoveToMainFile(Diag
&D
, FullSourceLoc DiagLoc
) {
213 const SourceManager
&SM
= DiagLoc
.getManager();
214 DiagLoc
= DiagLoc
.getExpansionLoc();
216 const char *Prefix
= getMainFileRange(D
, SM
, DiagLoc
, R
);
220 // Add a note that will point to real diagnostic.
221 auto FE
= *SM
.getFileEntryRefForID(SM
.getFileID(DiagLoc
));
222 D
.Notes
.emplace(D
.Notes
.begin());
223 Note
&N
= D
.Notes
.front();
224 N
.AbsFile
= std::string(FE
.getFileEntry().tryGetRealPathName());
225 N
.File
= std::string(FE
.getName());
226 N
.Message
= "error occurred here";
229 // Update diag to point at include inside main file.
230 D
.File
= SM
.getFileEntryRefForID(SM
.getMainFileID())->getName().str();
231 D
.Range
= std::move(R
);
232 D
.InsideMainFile
= true;
233 // Update message to mention original file.
234 D
.Message
= llvm::formatv("{0}: {1}", Prefix
, D
.Message
);
238 bool isNote(DiagnosticsEngine::Level L
) {
239 return L
== DiagnosticsEngine::Note
|| L
== DiagnosticsEngine::Remark
;
242 llvm::StringRef
diagLeveltoString(DiagnosticsEngine::Level Lvl
) {
244 case DiagnosticsEngine::Ignored
:
246 case DiagnosticsEngine::Note
:
248 case DiagnosticsEngine::Remark
:
250 case DiagnosticsEngine::Warning
:
252 case DiagnosticsEngine::Error
:
254 case DiagnosticsEngine::Fatal
:
255 return "fatal error";
257 llvm_unreachable("unhandled DiagnosticsEngine::Level");
260 /// Prints a single diagnostic in a clang-like manner, the output includes
261 /// location, severity and error message. An example of the output message is:
263 /// main.cpp:12:23: error: undeclared identifier
265 /// For main file we only print the basename and for all other files we print
266 /// the filename on a separate line to provide a slightly more readable output
269 /// dir1/dir2/dir3/../../dir4/header.h:12:23
270 /// error: undeclared identifier
271 void printDiag(llvm::raw_string_ostream
&OS
, const DiagBase
&D
) {
272 if (D
.InsideMainFile
) {
273 // Paths to main files are often taken from compile_command.json, where they
274 // are typically absolute. To reduce noise we print only basename for them,
275 // it should not be confusing and saves space.
276 OS
<< llvm::sys::path::filename(D
.File
) << ":";
280 // Note +1 to line and character. clangd::Range is zero-based, but when
281 // printing for users we want one-based indexes.
282 auto Pos
= D
.Range
.start
;
283 OS
<< (Pos
.line
+ 1) << ":" << (Pos
.character
+ 1) << ":";
284 // The non-main-file paths are often too long, putting them on a separate
285 // line improves readability.
286 if (D
.InsideMainFile
)
290 OS
<< diagLeveltoString(D
.Severity
) << ": " << D
.Message
;
293 /// Capitalizes the first word in the diagnostic's message.
294 std::string
capitalize(std::string Message
) {
295 if (!Message
.empty())
296 Message
[0] = llvm::toUpper(Message
[0]);
300 /// Returns a message sent to LSP for the main diagnostic in \p D.
301 /// This message may include notes, if they're not emitted in some other way.
304 /// no matching function for call to 'foo'
306 /// main.cpp:3:5: note: candidate function not viable: requires 2 arguments
308 /// dir1/dir2/dir3/../../dir4/header.h:12:23
309 /// note: candidate function not viable: requires 3 arguments
310 std::string
mainMessage(const Diag
&D
, const ClangdDiagnosticOptions
&Opts
) {
312 llvm::raw_string_ostream
OS(Result
);
314 if (Opts
.DisplayFixesCount
&& !D
.Fixes
.empty())
315 OS
<< " (" << (D
.Fixes
.size() > 1 ? "fixes" : "fix") << " available)";
316 // If notes aren't emitted as structured info, add them to the message.
317 if (!Opts
.EmitRelatedLocations
)
318 for (auto &Note
: D
.Notes
) {
323 return capitalize(std::move(Result
));
326 /// Returns a message sent to LSP for the note of the main diagnostic.
327 std::string
noteMessage(const Diag
&Main
, const DiagBase
&Note
,
328 const ClangdDiagnosticOptions
&Opts
) {
330 llvm::raw_string_ostream
OS(Result
);
332 // If the client doesn't support structured links between the note and the
333 // original diagnostic, then emit the main diagnostic to give context.
334 if (!Opts
.EmitRelatedLocations
) {
339 return capitalize(std::move(Result
));
342 void setTags(clangd::Diag
&D
) {
343 static const auto *DeprecatedDiags
= new llvm::DenseSet
<unsigned>{
344 diag::warn_access_decl_deprecated
,
345 diag::warn_atl_uuid_deprecated
,
346 diag::warn_deprecated
,
347 diag::warn_deprecated_altivec_src_compat
,
348 diag::warn_deprecated_comma_subscript
,
349 diag::warn_deprecated_copy
,
350 diag::warn_deprecated_copy_with_dtor
,
351 diag::warn_deprecated_copy_with_user_provided_copy
,
352 diag::warn_deprecated_copy_with_user_provided_dtor
,
353 diag::warn_deprecated_def
,
354 diag::warn_deprecated_increment_decrement_volatile
,
355 diag::warn_deprecated_message
,
356 diag::warn_deprecated_redundant_constexpr_static_def
,
357 diag::warn_deprecated_register
,
358 diag::warn_deprecated_simple_assign_volatile
,
359 diag::warn_deprecated_string_literal_conversion
,
360 diag::warn_deprecated_this_capture
,
361 diag::warn_deprecated_volatile_param
,
362 diag::warn_deprecated_volatile_return
,
363 diag::warn_deprecated_volatile_structured_binding
,
364 diag::warn_opencl_attr_deprecated_ignored
,
365 diag::warn_property_method_deprecated
,
366 diag::warn_vector_mode_deprecated
,
368 static const auto *UnusedDiags
= new llvm::DenseSet
<unsigned>{
369 diag::warn_opencl_attr_deprecated_ignored
,
370 diag::warn_pragma_attribute_unused
,
371 diag::warn_unused_but_set_parameter
,
372 diag::warn_unused_but_set_variable
,
373 diag::warn_unused_comparison
,
374 diag::warn_unused_const_variable
,
375 diag::warn_unused_exception_param
,
376 diag::warn_unused_function
,
377 diag::warn_unused_label
,
378 diag::warn_unused_lambda_capture
,
379 diag::warn_unused_local_typedef
,
380 diag::warn_unused_member_function
,
381 diag::warn_unused_parameter
,
382 diag::warn_unused_private_field
,
383 diag::warn_unused_property_backing_ivar
,
384 diag::warn_unused_template
,
385 diag::warn_unused_variable
,
387 if (DeprecatedDiags
->contains(D
.ID
)) {
388 D
.Tags
.push_back(DiagnosticTag::Deprecated
);
389 } else if (UnusedDiags
->contains(D
.ID
)) {
390 D
.Tags
.push_back(DiagnosticTag::Unnecessary
);
392 if (D
.Source
== Diag::ClangTidy
) {
393 if (llvm::StringRef(D
.Name
).starts_with("misc-unused-"))
394 D
.Tags
.push_back(DiagnosticTag::Unnecessary
);
395 if (llvm::StringRef(D
.Name
).starts_with("modernize-"))
396 D
.Tags
.push_back(DiagnosticTag::Deprecated
);
401 llvm::raw_ostream
&operator<<(llvm::raw_ostream
&OS
, const DiagBase
&D
) {
403 if (!D
.InsideMainFile
)
405 OS
<< D
.Range
.start
<< "-" << D
.Range
.end
<< "] ";
407 return OS
<< D
.Message
;
410 llvm::raw_ostream
&operator<<(llvm::raw_ostream
&OS
, const Fix
&F
) {
411 OS
<< F
.Message
<< " {";
412 const char *Sep
= "";
413 for (const auto &Edit
: F
.Edits
) {
420 llvm::raw_ostream
&operator<<(llvm::raw_ostream
&OS
, const Diag
&D
) {
421 OS
<< static_cast<const DiagBase
&>(D
);
422 if (!D
.Notes
.empty()) {
424 const char *Sep
= "";
425 for (auto &Note
: D
.Notes
) {
431 if (!D
.Fixes
.empty()) {
433 const char *Sep
= "";
434 for (auto &Fix
: D
.Fixes
) {
443 Diag
toDiag(const llvm::SMDiagnostic
&D
, Diag::DiagSource Source
) {
445 Result
.Message
= D
.getMessage().str();
446 switch (D
.getKind()) {
447 case llvm::SourceMgr::DK_Error
:
448 Result
.Severity
= DiagnosticsEngine::Error
;
450 case llvm::SourceMgr::DK_Warning
:
451 Result
.Severity
= DiagnosticsEngine::Warning
;
456 Result
.Source
= Source
;
457 Result
.AbsFile
= D
.getFilename().str();
458 Result
.InsideMainFile
= D
.getSourceMgr()->FindBufferContainingLoc(
459 D
.getLoc()) == D
.getSourceMgr()->getMainFileID();
460 if (D
.getRanges().empty())
461 Result
.Range
= {{D
.getLineNo() - 1, D
.getColumnNo()},
462 {D
.getLineNo() - 1, D
.getColumnNo()}};
464 Result
.Range
= {{D
.getLineNo() - 1, (int)D
.getRanges().front().first
},
465 {D
.getLineNo() - 1, (int)D
.getRanges().front().second
}};
470 const Diag
&D
, const URIForFile
&File
, const ClangdDiagnosticOptions
&Opts
,
471 llvm::function_ref
<void(clangd::Diagnostic
, llvm::ArrayRef
<Fix
>)> OutFn
) {
472 clangd::Diagnostic Main
;
473 Main
.severity
= getSeverity(D
.Severity
);
474 // We downgrade severity for certain noisy warnings, like deprecated
475 // declartions. These already have visible decorations inside the editor and
476 // most users find the extra clutter in the UI (gutter, minimap, diagnostics
477 // views) overwhelming.
478 if (D
.Severity
== DiagnosticsEngine::Warning
) {
479 if (llvm::is_contained(D
.Tags
, DiagnosticTag::Deprecated
))
480 Main
.severity
= getSeverity(DiagnosticsEngine::Remark
);
483 // Main diagnostic should always refer to a range inside main file. If a
484 // diagnostic made it so for, it means either itself or one of its notes is
485 // inside main file. It's also possible that there's a fix in the main file,
486 // but we preserve fixes iff primary diagnostic is in the main file.
487 if (D
.InsideMainFile
) {
488 Main
.range
= D
.Range
;
491 llvm::find_if(D
.Notes
, [](const Note
&N
) { return N
.InsideMainFile
; });
492 assert(It
!= D
.Notes
.end() &&
493 "neither the main diagnostic nor notes are inside main file");
494 Main
.range
= It
->Range
;
498 if (auto URI
= getDiagnosticDocURI(D
.Source
, D
.ID
, D
.Name
)) {
499 Main
.codeDescription
.emplace();
500 Main
.codeDescription
->href
= std::move(*URI
);
504 Main
.source
= "clang";
506 case Diag::ClangTidy
:
507 Main
.source
= "clang-tidy";
510 Main
.source
= "clangd";
512 case Diag::ClangdConfig
:
513 Main
.source
= "clangd-config";
518 if (Opts
.SendDiagnosticCategory
&& !D
.Category
.empty())
519 Main
.category
= D
.Category
;
521 Main
.message
= mainMessage(D
, Opts
);
522 if (Opts
.EmitRelatedLocations
) {
523 Main
.relatedInformation
.emplace();
524 for (auto &Note
: D
.Notes
) {
526 vlog("Dropping note from unknown file: {0}", Note
);
529 DiagnosticRelatedInformation RelInfo
;
530 RelInfo
.location
.range
= Note
.Range
;
531 RelInfo
.location
.uri
=
532 URIForFile::canonicalize(*Note
.AbsFile
, File
.file());
533 RelInfo
.message
= noteMessage(D
, Note
, Opts
);
534 Main
.relatedInformation
->push_back(std::move(RelInfo
));
538 // FIXME: Get rid of the copies here by taking in a mutable clangd::Diag.
539 for (auto &Entry
: D
.OpaqueData
)
540 Main
.data
.insert({Entry
.first
, Entry
.second
});
541 OutFn(std::move(Main
), D
.Fixes
);
543 // If we didn't emit the notes as relatedLocations, emit separate diagnostics
544 // so the user can find the locations easily.
545 if (!Opts
.EmitRelatedLocations
)
546 for (auto &Note
: D
.Notes
) {
547 if (!Note
.InsideMainFile
)
549 clangd::Diagnostic Res
;
550 Res
.severity
= getSeverity(Note
.Severity
);
551 Res
.range
= Note
.Range
;
552 Res
.message
= noteMessage(D
, Note
, Opts
);
553 OutFn(std::move(Res
), llvm::ArrayRef
<Fix
>());
557 int getSeverity(DiagnosticsEngine::Level L
) {
559 case DiagnosticsEngine::Remark
:
561 case DiagnosticsEngine::Note
:
563 case DiagnosticsEngine::Warning
:
565 case DiagnosticsEngine::Fatal
:
566 case DiagnosticsEngine::Error
:
568 case DiagnosticsEngine::Ignored
:
571 llvm_unreachable("Unknown diagnostic level!");
574 std::vector
<Diag
> StoreDiags::take(const clang::tidy::ClangTidyContext
*Tidy
) {
575 // Do not forget to emit a pending diagnostic if there is one.
578 // Fill in name/source now that we have all the context needed to map them.
579 for (auto &Diag
: Output
) {
580 if (const char *ClangDiag
= getDiagnosticCode(Diag
.ID
)) {
581 // Warnings controlled by -Wfoo are better recognized by that name.
582 StringRef Warning
= DiagnosticIDs::getWarningOptionForDiag(Diag
.ID
);
583 if (!Warning
.empty()) {
584 Diag
.Name
= ("-W" + Warning
).str();
586 StringRef
Name(ClangDiag
);
587 // Almost always an error, with a name like err_enum_class_reference.
588 // Drop the err_ prefix for brevity.
589 Name
.consume_front("err_");
590 Diag
.Name
= std::string(Name
);
592 Diag
.Source
= Diag::Clang
;
593 } else if (Tidy
!= nullptr) {
594 std::string TidyDiag
= Tidy
->getCheckName(Diag
.ID
);
595 if (!TidyDiag
.empty()) {
596 Diag
.Name
= std::move(TidyDiag
);
597 Diag
.Source
= Diag::ClangTidy
;
598 // clang-tidy bakes the name into diagnostic messages. Strip it out.
599 // It would be much nicer to make clang-tidy not do this.
600 auto CleanMessage
= [&](std::string
&Msg
) {
602 if (Rest
.consume_back("]") && Rest
.consume_back(Diag
.Name
) &&
603 Rest
.consume_back(" ["))
604 Msg
.resize(Rest
.size());
606 CleanMessage(Diag
.Message
);
607 for (auto &Note
: Diag
.Notes
)
608 CleanMessage(Note
.Message
);
609 for (auto &Fix
: Diag
.Fixes
)
610 CleanMessage(Fix
.Message
);
615 // Deduplicate clang-tidy diagnostics -- some clang-tidy checks may emit
616 // duplicated messages due to various reasons (e.g. the check doesn't handle
617 // template instantiations well; clang-tidy alias checks).
618 std::set
<std::pair
<Range
, std::string
>> SeenDiags
;
619 llvm::erase_if(Output
, [&](const Diag
&D
) {
620 return !SeenDiags
.emplace(D
.Range
, D
.Message
).second
;
622 return std::move(Output
);
625 void StoreDiags::BeginSourceFile(const LangOptions
&Opts
,
626 const Preprocessor
*PP
) {
629 OrigSrcMgr
= &PP
->getSourceManager();
633 void StoreDiags::EndSourceFile() {
635 LangOpts
= std::nullopt
;
636 OrigSrcMgr
= nullptr;
639 /// Sanitizes a piece for presenting it in a synthesized fix message. Ensures
640 /// the result is not too large and does not contain newlines.
641 static void writeCodeToFixMessage(llvm::raw_ostream
&OS
, llvm::StringRef Code
) {
642 constexpr unsigned MaxLen
= 50;
647 // Only show the first line if there are many.
648 llvm::StringRef R
= Code
.split('\n').first
;
649 // Shorten the message if it's too long.
650 R
= R
.take_front(MaxLen
);
653 if (R
.size() != Code
.size())
657 /// Fills \p D with all information, except the location-related bits.
658 /// Also note that ID and Name are not part of clangd::DiagBase and should be
660 static void fillNonLocationData(DiagnosticsEngine::Level DiagLevel
,
661 const clang::Diagnostic
&Info
,
662 clangd::DiagBase
&D
) {
663 llvm::SmallString
<64> Message
;
664 Info
.FormatDiagnostic(Message
);
666 D
.Message
= std::string(Message
.str());
667 D
.Severity
= DiagLevel
;
668 D
.Category
= DiagnosticIDs::getCategoryNameFromID(
669 DiagnosticIDs::getCategoryNumberForDiag(Info
.getID()))
673 void StoreDiags::HandleDiagnostic(DiagnosticsEngine::Level DiagLevel
,
674 const clang::Diagnostic
&Info
) {
675 // If the diagnostic was generated for a different SourceManager, skip it.
676 // This happens when a module is imported and needs to be implicitly built.
677 // The compilation of that module will use the same StoreDiags, but different
679 if (OrigSrcMgr
&& Info
.hasSourceManager() &&
680 OrigSrcMgr
!= &Info
.getSourceManager()) {
681 IgnoreDiagnostics::log(DiagLevel
, Info
);
685 DiagnosticConsumer::HandleDiagnostic(DiagLevel
, Info
);
686 bool OriginallyError
=
687 Info
.getDiags()->getDiagnosticIDs()->isDefaultMappingAsError(
690 if (Info
.getLocation().isInvalid()) {
691 // Handle diagnostics coming from command-line arguments. The source manager
692 // is *not* available at this point, so we cannot use it.
693 if (!OriginallyError
) {
694 IgnoreDiagnostics::log(DiagLevel
, Info
);
695 return; // non-errors add too much noise, do not show them.
702 LastDiagOriginallyError
= OriginallyError
;
703 LastDiag
->ID
= Info
.getID();
704 fillNonLocationData(DiagLevel
, Info
, *LastDiag
);
705 LastDiag
->InsideMainFile
= true;
706 // Put it at the start of the main file, for a lack of a better place.
707 LastDiag
->Range
.start
= Position
{0, 0};
708 LastDiag
->Range
.end
= Position
{0, 0};
712 if (!LangOpts
|| !Info
.hasSourceManager()) {
713 IgnoreDiagnostics::log(DiagLevel
, Info
);
717 SourceManager
&SM
= Info
.getSourceManager();
719 auto FillDiagBase
= [&](DiagBase
&D
) {
720 fillNonLocationData(DiagLevel
, Info
, D
);
722 SourceLocation PatchLoc
=
723 translatePreamblePatchLocation(Info
.getLocation(), SM
);
724 D
.InsideMainFile
= isInsideMainFile(PatchLoc
, SM
);
725 if (auto DRange
= diagnosticRange(Info
, *LangOpts
))
728 D
.Severity
= DiagnosticsEngine::Ignored
;
729 auto FID
= SM
.getFileID(Info
.getLocation());
730 if (const auto FE
= SM
.getFileEntryRefForID(FID
)) {
731 D
.File
= FE
->getName().str();
732 D
.AbsFile
= getCanonicalPath(*FE
, SM
.getFileManager());
738 auto AddFix
= [&](bool SyntheticMessage
) -> bool {
739 assert(!Info
.getFixItHints().empty() &&
740 "diagnostic does not have attached fix-its");
741 // No point in generating fixes, if the diagnostic is for a different file.
742 if (!LastDiag
->InsideMainFile
)
744 // Copy as we may modify the ranges.
745 auto FixIts
= Info
.getFixItHints().vec();
746 llvm::SmallVector
<TextEdit
, 1> Edits
;
747 for (auto &FixIt
: FixIts
) {
748 // Allow fixits within a single macro-arg expansion to be applied.
749 // This can be incorrect if the argument is expanded multiple times in
750 // different contexts. Hopefully this is rare!
751 if (FixIt
.RemoveRange
.getBegin().isMacroID() &&
752 FixIt
.RemoveRange
.getEnd().isMacroID() &&
753 SM
.getFileID(FixIt
.RemoveRange
.getBegin()) ==
754 SM
.getFileID(FixIt
.RemoveRange
.getEnd())) {
755 FixIt
.RemoveRange
= CharSourceRange(
756 {SM
.getTopMacroCallerLoc(FixIt
.RemoveRange
.getBegin()),
757 SM
.getTopMacroCallerLoc(FixIt
.RemoveRange
.getEnd())},
758 FixIt
.RemoveRange
.isTokenRange());
760 // Otherwise, follow clang's behavior: no fixits in macros.
761 if (FixIt
.RemoveRange
.getBegin().isMacroID() ||
762 FixIt
.RemoveRange
.getEnd().isMacroID())
764 if (!isInsideMainFile(FixIt
.RemoveRange
.getBegin(), SM
))
766 Edits
.push_back(toTextEdit(FixIt
, SM
, *LangOpts
));
769 llvm::SmallString
<64> Message
;
770 // If requested and possible, create a message like "change 'foo' to 'bar'".
771 if (SyntheticMessage
&& FixIts
.size() == 1) {
772 const auto &FixIt
= FixIts
.front();
773 bool Invalid
= false;
774 llvm::StringRef Remove
=
775 Lexer::getSourceText(FixIt
.RemoveRange
, SM
, *LangOpts
, &Invalid
);
776 llvm::StringRef Insert
= FixIt
.CodeToInsert
;
778 llvm::raw_svector_ostream
M(Message
);
779 if (!Remove
.empty() && !Insert
.empty()) {
781 writeCodeToFixMessage(M
, Remove
);
783 writeCodeToFixMessage(M
, Insert
);
785 } else if (!Remove
.empty()) {
787 writeCodeToFixMessage(M
, Remove
);
789 } else if (!Insert
.empty()) {
791 writeCodeToFixMessage(M
, Insert
);
794 // Don't allow source code to inject newlines into diagnostics.
795 std::replace(Message
.begin(), Message
.end(), '\n', ' ');
798 if (Message
.empty()) // either !SyntheticMessage, or we failed to make one.
799 Info
.FormatDiagnostic(Message
);
800 LastDiag
->Fixes
.push_back(
801 Fix
{std::string(Message
.str()), std::move(Edits
), {}});
805 if (!isNote(DiagLevel
)) {
806 // Handle the new main diagnostic.
810 // FIXME: Merge with feature modules.
812 DiagLevel
= Adjuster(DiagLevel
, Info
);
814 FillDiagBase(*LastDiag
);
815 if (isExcluded(LastDiag
->ID
))
816 LastDiag
->Severity
= DiagnosticsEngine::Ignored
;
818 DiagCB(Info
, *LastDiag
);
819 // Don't bother filling in the rest if diag is going to be dropped.
820 if (LastDiag
->Severity
== DiagnosticsEngine::Ignored
)
823 LastDiagLoc
.emplace(Info
.getLocation(), Info
.getSourceManager());
824 LastDiagOriginallyError
= OriginallyError
;
825 if (!Info
.getFixItHints().empty())
826 AddFix(true /* try to invent a message instead of repeating the diag */);
828 auto ExtraFixes
= Fixer(LastDiag
->Severity
, Info
);
829 LastDiag
->Fixes
.insert(LastDiag
->Fixes
.end(), ExtraFixes
.begin(),
833 // Handle a note to an existing diagnostic.
835 assert(false && "Adding a note without main diagnostic");
836 IgnoreDiagnostics::log(DiagLevel
, Info
);
840 // If a diagnostic was suppressed due to the suppression filter,
841 // also suppress notes associated with it.
842 if (LastDiag
->Severity
== DiagnosticsEngine::Ignored
)
845 // Give include-fixer a chance to replace a note with a fix.
847 auto ReplacementFixes
= Fixer(LastDiag
->Severity
, Info
);
848 if (!ReplacementFixes
.empty()) {
849 assert(Info
.getNumFixItHints() == 0 &&
850 "Include-fixer replaced a note with clang fix-its attached!");
851 LastDiag
->Fixes
.insert(LastDiag
->Fixes
.end(), ReplacementFixes
.begin(),
852 ReplacementFixes
.end());
857 if (!Info
.getFixItHints().empty()) {
858 // A clang note with fix-it is not a separate diagnostic in clangd. We
859 // attach it as a Fix to the main diagnostic instead.
860 if (!AddFix(false /* use the note as the message */))
861 IgnoreDiagnostics::log(DiagLevel
, Info
);
863 // A clang note without fix-its corresponds to clangd::Note.
867 LastDiag
->Notes
.push_back(std::move(N
));
872 void StoreDiags::flushLastDiag() {
875 auto Finish
= llvm::make_scope_exit([&, NDiags(Output
.size())] {
876 if (Output
.size() == NDiags
) // No new diag emitted.
877 vlog("Dropped diagnostic: {0}: {1}", LastDiag
->File
, LastDiag
->Message
);
881 if (LastDiag
->Severity
== DiagnosticsEngine::Ignored
)
883 // Move errors that occur from headers into main file.
884 if (!LastDiag
->InsideMainFile
&& LastDiagLoc
&& LastDiagOriginallyError
) {
885 if (tryMoveToMainFile(*LastDiag
, *LastDiagLoc
)) {
886 // Suppress multiple errors from the same inclusion.
887 if (!IncludedErrorLocations
888 .insert({LastDiag
->Range
.start
.line
,
889 LastDiag
->Range
.start
.character
})
894 if (!mentionsMainFile(*LastDiag
))
896 Output
.push_back(std::move(*LastDiag
));
899 bool isBuiltinDiagnosticSuppressed(unsigned ID
,
900 const llvm::StringSet
<> &Suppress
,
901 const LangOptions
&LangOpts
) {
902 // Don't complain about header-only stuff in mainfiles if it's a header.
903 // FIXME: would be cleaner to suppress in clang, once we decide whether the
904 // behavior should be to silently-ignore or respect the pragma.
905 if (ID
== diag::pp_pragma_sysheader_in_main_file
&& LangOpts
.IsHeaderFile
)
908 if (const char *CodePtr
= getDiagnosticCode(ID
)) {
909 if (Suppress
.contains(normalizeSuppressedCode(CodePtr
)))
912 StringRef Warning
= DiagnosticIDs::getWarningOptionForDiag(ID
);
913 if (!Warning
.empty() && Suppress
.contains(Warning
))
918 llvm::StringRef
normalizeSuppressedCode(llvm::StringRef Code
) {
919 Code
.consume_front("err_");
920 Code
.consume_front("-W");
924 std::optional
<std::string
> getDiagnosticDocURI(Diag::DiagSource Source
,
926 llvm::StringRef Name
) {
931 // There is a page listing many warning flags, but it provides too little
932 // information to be worth linking.
933 // https://clang.llvm.org/docs/DiagnosticsReference.html
935 case Diag::ClangTidy
: {
936 StringRef Module
, Check
;
937 // This won't correctly get the module for clang-analyzer checks, but as we
938 // don't link in the analyzer that shouldn't be an issue.
939 // This would also need updating if anyone decides to create a module with a
941 std::tie(Module
, Check
) = Name
.split('-');
942 if (Module
.empty() || Check
.empty())
944 return ("https://clang.llvm.org/extra/clang-tidy/checks/" + Module
+ "/" +
949 if (Name
== "unused-includes" || Name
== "missing-includes")
950 return {"https://clangd.llvm.org/guides/include-cleaner"};
952 case Diag::ClangdConfig
:
953 // FIXME: we should link to https://clangd.llvm.org/config
954 // However we have no diagnostic codes, which the link should describe!
960 } // namespace clangd