10 #include "../clang-tidy/ClangTidyDiagnosticConsumer.h"
15 #include "clang/Basic/AllDiagnostics.h"
16 #include "clang/Basic/Diagnostic.h"
17 #include "clang/Basic/DiagnosticIDs.h"
18 #include "clang/Basic/SourceLocation.h"
19 #include "clang/Basic/SourceManager.h"
20 #include "clang/Lex/Lexer.h"
21 #include "clang/Lex/Token.h"
22 #include "llvm/ADT/ArrayRef.h"
23 #include "llvm/ADT/DenseSet.h"
24 #include "llvm/ADT/Optional.h"
25 #include "llvm/ADT/STLExtras.h"
26 #include "llvm/ADT/ScopeExit.h"
27 #include "llvm/ADT/SmallString.h"
28 #include "llvm/ADT/SmallVector.h"
29 #include "llvm/ADT/StringRef.h"
30 #include "llvm/ADT/Twine.h"
31 #include "llvm/Support/Path.h"
32 #include "llvm/Support/raw_ostream.h"
42 const char *getDiagnosticCode(
unsigned ID) {
44 #define DIAG(ENUM, CLASS, DEFAULT_MAPPING, DESC, GROPU, SFINAE, NOWERROR, \
45 SHOWINSYSHEADER, SHOWINSYSMACRO, DEFERRABLE, CATEGORY) \
46 case clang::diag::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"
65 bool mentionsMainFile(
const Diag &
D) {
71 for (
auto &N :
D.Notes) {
78 bool isExcluded(
unsigned DiagID) {
80 if (DiagID == clang::diag::err_msasm_unable_to_create_target ||
81 DiagID == clang::diag::err_msasm_unsupported_arch)
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))
94 return L != R.getEnd() &&
M.isPointWithin(L, R.getBegin(), R.getEnd());
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))
108 for (
const auto &F :
D.getFixItHints()) {
109 auto R = Lexer::makeFileCharRange(F.RemoveRange,
M, L);
110 if (locationInRange(
Loc, R,
M))
115 auto R = CharSourceRange::getCharRange(
Loc);
117 if (!Lexer::getRawToken(
Loc, Tok,
M, L,
true) && Tok.isNot(tok::comment)) {
118 R = CharSourceRange::getTokenRange(Tok.getLocation(), Tok.getEndLoc());
125 const char *getMainFileRange(
const Diag &
D,
const SourceManager &SM,
126 SourceLocation DiagLoc,
Range &R) {
128 for (
const auto &N :
D.Notes) {
129 if (N.InsideMainFile) {
131 case diag::note_template_class_instantiation_was_here:
132 case diag::note_template_class_explicit_specialization_was_here:
133 case diag::note_template_class_instantiation_here:
134 case diag::note_template_member_class_here:
135 case diag::note_template_member_function_here:
136 case diag::note_function_template_spec_here:
137 case diag::note_template_static_data_member_def_here:
138 case diag::note_template_variable_def_here:
139 case diag::note_template_enum_def_here:
140 case diag::note_template_nsdmi_here:
141 case diag::note_template_type_alias_instantiation_here:
142 case diag::note_template_exception_spec_instantiation_here:
143 case diag::note_template_requirement_instantiation_here:
144 case diag::note_evaluating_exception_spec_here:
145 case diag::note_default_arg_instantiation_here:
146 case diag::note_default_function_arg_instantiation_here:
147 case diag::note_explicit_template_arg_substitution_here:
148 case diag::note_function_template_deduction_instantiation_here:
149 case diag::note_deduced_template_arg_substitution_here:
150 case diag::note_prior_template_arg_substitution:
151 case diag::note_template_default_arg_checking:
152 case diag::note_concept_specialization_here:
153 case diag::note_nested_requirement_here:
154 case diag::note_checking_constraints_for_template_id_here:
155 case diag::note_checking_constraints_for_var_spec_id_here:
156 case diag::note_checking_constraints_for_class_spec_id_here:
157 case diag::note_checking_constraints_for_function_here:
158 case diag::note_constraint_substitution_here:
159 case diag::note_constraint_normalization_here:
160 case diag::note_parameter_mapping_substitution_here:
162 return "in template";
169 auto GetIncludeLoc = [&SM](SourceLocation SLoc) {
170 return SM.getIncludeLoc(SM.getFileID(SLoc));
172 for (
auto IncludeLocation = GetIncludeLoc(SM.getExpansionLoc(DiagLoc));
173 IncludeLocation.isValid();
174 IncludeLocation = GetIncludeLoc(IncludeLocation)) {
179 Lexer::getLocForEndOfToken(IncludeLocation, 0, SM, LangOptions()));
180 return "in included file";
190 bool tryMoveToMainFile(Diag &
D, FullSourceLoc DiagLoc) {
191 const SourceManager &SM = DiagLoc.getManager();
192 DiagLoc = DiagLoc.getExpansionLoc();
194 const char *Prefix = getMainFileRange(
D, SM, DiagLoc, R);
199 auto FE = *SM.getFileEntryRefForID(SM.getFileID(DiagLoc));
200 D.Notes.emplace(
D.Notes.begin());
201 Note &N =
D.Notes.front();
202 N.AbsFile = std::string(FE.getFileEntry().tryGetRealPathName());
203 N.File = std::string(FE.getName());
204 N.Message =
"error occurred here";
208 D.File = SM.getFileEntryRefForID(SM.getMainFileID())->getName().str();
209 D.Range = std::move(R);
210 D.InsideMainFile =
true;
212 D.Message = llvm::formatv(
"{0}: {1}", Prefix,
D.Message);
217 if (!
D.hasSourceManager())
223 bool isNote(DiagnosticsEngine::Level L) {
224 return L == DiagnosticsEngine::Note || L == DiagnosticsEngine::Remark;
227 llvm::StringRef diagLeveltoString(DiagnosticsEngine::Level Lvl) {
229 case DiagnosticsEngine::Ignored:
231 case DiagnosticsEngine::Note:
233 case DiagnosticsEngine::Remark:
239 case DiagnosticsEngine::Fatal:
240 return "fatal error";
242 llvm_unreachable(
"unhandled DiagnosticsEngine::Level");
256 void printDiag(llvm::raw_string_ostream &
OS,
const DiagBase &
D) {
257 if (
D.InsideMainFile) {
261 OS << llvm::sys::path::filename(
D.File) <<
":";
267 auto Pos =
D.Range.start;
268 OS << (
Pos.line + 1) <<
":" << (
Pos.character + 1) <<
":";
271 if (
D.InsideMainFile)
275 OS << diagLeveltoString(
D.Severity) <<
": " <<
D.Message;
279 std::string capitalize(std::string
Message) {
295 std::string mainMessage(
const Diag &
D,
const ClangdDiagnosticOptions &Opts) {
297 llvm::raw_string_ostream
OS(Result);
299 if (Opts.DisplayFixesCount && !
D.Fixes.empty())
300 OS <<
" (" << (
D.Fixes.size() > 1 ?
"fixes" :
"fix") <<
" available)";
302 if (!Opts.EmitRelatedLocations)
303 for (
auto &Note :
D.Notes) {
308 return capitalize(std::move(Result));
312 std::string noteMessage(
const Diag &Main,
const DiagBase &Note,
313 const ClangdDiagnosticOptions &Opts) {
315 llvm::raw_string_ostream
OS(Result);
319 if (!Opts.EmitRelatedLocations) {
324 return capitalize(std::move(Result));
327 void setTags(clangd::Diag &
D) {
328 static const auto *DeprecatedDiags =
new llvm::DenseSet<unsigned>{
329 diag::warn_access_decl_deprecated,
330 diag::warn_atl_uuid_deprecated,
331 diag::warn_deprecated,
332 diag::warn_deprecated_altivec_src_compat,
333 diag::warn_deprecated_comma_subscript,
334 diag::warn_deprecated_compound_assign_volatile,
335 diag::warn_deprecated_copy,
336 diag::warn_deprecated_copy_with_dtor,
337 diag::warn_deprecated_copy_with_user_provided_copy,
338 diag::warn_deprecated_copy_with_user_provided_dtor,
339 diag::warn_deprecated_def,
340 diag::warn_deprecated_increment_decrement_volatile,
341 diag::warn_deprecated_message,
342 diag::warn_deprecated_redundant_constexpr_static_def,
343 diag::warn_deprecated_register,
344 diag::warn_deprecated_simple_assign_volatile,
345 diag::warn_deprecated_string_literal_conversion,
346 diag::warn_deprecated_this_capture,
347 diag::warn_deprecated_volatile_param,
348 diag::warn_deprecated_volatile_return,
349 diag::warn_deprecated_volatile_structured_binding,
350 diag::warn_opencl_attr_deprecated_ignored,
351 diag::warn_property_method_deprecated,
352 diag::warn_vector_mode_deprecated,
354 static const auto *UnusedDiags =
new llvm::DenseSet<unsigned>{
355 diag::warn_opencl_attr_deprecated_ignored,
356 diag::warn_pragma_attribute_unused,
357 diag::warn_unused_but_set_parameter,
358 diag::warn_unused_but_set_variable,
359 diag::warn_unused_comparison,
360 diag::warn_unused_const_variable,
361 diag::warn_unused_exception_param,
362 diag::warn_unused_function,
363 diag::warn_unused_label,
364 diag::warn_unused_lambda_capture,
365 diag::warn_unused_local_typedef,
366 diag::warn_unused_member_function,
367 diag::warn_unused_parameter,
368 diag::warn_unused_private_field,
369 diag::warn_unused_property_backing_ivar,
370 diag::warn_unused_template,
371 diag::warn_unused_variable,
373 if (DeprecatedDiags->contains(
D.ID)) {
375 }
else if (UnusedDiags->contains(
D.ID)) {
384 if (!
D.InsideMainFile)
386 OS <<
D.Range.start <<
"-" <<
D.Range.end <<
"] ";
388 return OS <<
D.Message;
392 OS << F.Message <<
" {";
393 const char *Sep =
"";
394 for (
const auto &
Edit : F.Edits) {
402 OS << static_cast<const DiagBase &>(
D);
403 if (!
D.Notes.empty()) {
405 const char *Sep =
"";
406 for (
auto &
Note :
D.Notes) {
412 if (!
D.Fixes.empty()) {
414 const char *Sep =
"";
415 for (
auto &
Fix :
D.Fixes) {
429 Action.edit->changes[File.uri()] = {F.Edits.begin(), F.Edits.end()};
435 Result.Message =
D.getMessage().str();
436 switch (
D.getKind()) {
437 case llvm::SourceMgr::DK_Error:
440 case llvm::SourceMgr::DK_Warning:
446 Result.Source = Source;
447 Result.AbsFile =
D.getFilename().str();
448 Result.InsideMainFile =
D.getSourceMgr()->FindBufferContainingLoc(
449 D.getLoc()) ==
D.getSourceMgr()->getMainFileID();
450 if (
D.getRanges().empty())
451 Result.Range = {{
D.getLineNo() - 1,
D.getColumnNo()},
452 {
D.getLineNo() - 1,
D.getColumnNo()}};
454 Result.Range = {{
D.getLineNo() - 1, (int)
D.getRanges().front().first},
455 {
D.getLineNo() - 1, (int)
D.getRanges().front().second}};
469 if (
D.InsideMainFile) {
473 llvm::find_if(
D.Notes, [](
const Note &N) { return N.InsideMainFile; });
474 assert(It !=
D.Notes.end() &&
475 "neither the main diagnostic nor notes are inside main file");
476 Main.
range = It->Range;
489 Main.
source =
"clang-tidy";
495 Main.
source =
"clangd-config";
500 if (Opts.EmbedFixesInDiagnostics) {
502 for (
const auto &
Fix :
D.Fixes)
507 if (Opts.SendDiagnosticCategory && !
D.Category.empty())
510 Main.
message = mainMessage(
D, Opts);
511 if (Opts.EmitRelatedLocations) {
513 for (
auto &
Note :
D.Notes) {
515 vlog(
"Dropping note from unknown file: {0}",
Note);
527 OutFn(std::move(Main),
D.Fixes);
531 if (!Opts.EmitRelatedLocations)
532 for (
auto &
Note :
D.Notes) {
539 OutFn(std::move(Res), llvm::ArrayRef<Fix>());
543 for (
auto &
Entry :
D.OpaqueData)
549 case DiagnosticsEngine::Remark:
551 case DiagnosticsEngine::Note:
555 case DiagnosticsEngine::Fatal:
558 case DiagnosticsEngine::Ignored:
561 llvm_unreachable(
"Unknown diagnostic level!");
569 for (
auto &
Diag : Output) {
571 if (
const char *ClangDiag = getDiagnosticCode(
Diag.
ID)) {
573 StringRef
Warning = DiagnosticIDs::getWarningOptionForDiag(
Diag.
ID);
577 StringRef
Name(ClangDiag);
580 Name.consume_front(
"err_");
586 if (Tidy !=
nullptr) {
588 if (!TidyDiag.empty()) {
593 auto CleanMessage = [&](std::string &Msg) {
595 if (Rest.consume_back(
"]") && Rest.consume_back(
Diag.
Name) &&
596 Rest.consume_back(
" ["))
597 Msg.resize(Rest.size());
611 std::set<std::pair<Range, std::string>> SeenDiags;
613 return !SeenDiags.emplace(
D.Range,
D.Message).second;
619 const Preprocessor *
PP) {
622 OrigSrcMgr = &
PP->getSourceManager();
629 OrigSrcMgr =
nullptr;
635 constexpr
unsigned MaxLen = 50;
641 llvm::StringRef R =
Code.split(
'\n').first;
643 R = R.take_front(MaxLen);
646 if (R.size() !=
Code.size())
659 D.Message = std::string(
Message.str());
660 D.Severity = DiagLevel;
661 D.Category = DiagnosticIDs::getCategoryNameFromID(
662 DiagnosticIDs::getCategoryNumberForDiag(
Info.getID()))
672 if (OrigSrcMgr &&
Info.hasSourceManager() &&
673 OrigSrcMgr != &
Info.getSourceManager()) {
678 DiagnosticConsumer::HandleDiagnostic(DiagLevel,
Info);
679 bool OriginallyError =
680 Info.getDiags()->getDiagnosticIDs()->isDefaultMappingAsError(
683 if (
Info.getLocation().isInvalid()) {
686 if (!OriginallyError) {
695 LastDiagOriginallyError = OriginallyError;
696 LastDiag->ID =
Info.getID();
698 LastDiag->InsideMainFile =
true;
700 LastDiag->Range.start =
Position{0, 0};
701 LastDiag->Range.end =
Position{0, 0};
705 if (!LangOpts || !
Info.hasSourceManager()) {
710 SourceManager &SM =
Info.getSourceManager();
716 D.Range = diagnosticRange(
Info, *LangOpts);
717 D.File = std::string(SM.getFilename(
Info.getLocation()));
719 SM.getFileEntryForID(SM.getFileID(
Info.getLocation())), SM);
724 auto AddFix = [&](
bool SyntheticMessage) ->
bool {
725 assert(!
Info.getFixItHints().empty() &&
726 "diagnostic does not have attached fix-its");
728 if (!LastDiag->InsideMainFile)
731 auto FixIts =
Info.getFixItHints().vec();
732 llvm::SmallVector<TextEdit, 1> Edits;
733 for (
auto &
FixIt : FixIts) {
737 if (
FixIt.RemoveRange.getBegin().isMacroID() &&
738 FixIt.RemoveRange.getEnd().isMacroID() &&
739 SM.getFileID(
FixIt.RemoveRange.getBegin()) ==
740 SM.getFileID(
FixIt.RemoveRange.getEnd())) {
741 FixIt.RemoveRange = CharSourceRange(
742 {SM.getTopMacroCallerLoc(
FixIt.RemoveRange.getBegin()),
743 SM.getTopMacroCallerLoc(
FixIt.RemoveRange.getEnd())},
744 FixIt.RemoveRange.isTokenRange());
747 if (
FixIt.RemoveRange.getBegin().isMacroID() ||
748 FixIt.RemoveRange.getEnd().isMacroID())
757 if (SyntheticMessage && FixIts.size() == 1) {
758 const auto &
FixIt = FixIts.front();
759 bool Invalid =
false;
760 llvm::StringRef Remove =
762 llvm::StringRef Insert =
FixIt.CodeToInsert;
764 llvm::raw_svector_ostream
M(
Message);
765 if (!Remove.empty() && !Insert.empty()) {
771 }
else if (!Remove.empty()) {
775 }
else if (!Insert.empty()) {
786 LastDiag->Fixes.push_back(
787 Fix{std::string(
Message.str()), std::move(Edits)});
791 if (!isNote(DiagLevel)) {
798 DiagLevel = Adjuster(DiagLevel,
Info);
800 FillDiagBase(*LastDiag);
801 if (isExcluded(LastDiag->ID))
802 LastDiag->Severity = DiagnosticsEngine::Ignored;
804 DiagCB(
Info, *LastDiag);
806 if (LastDiag->Severity == DiagnosticsEngine::Ignored)
809 LastDiagLoc.emplace(
Info.getLocation(),
Info.getSourceManager());
810 LastDiagOriginallyError = OriginallyError;
811 if (!
Info.getFixItHints().empty())
814 auto ExtraFixes = Fixer(LastDiag->Severity,
Info);
815 LastDiag->Fixes.insert(LastDiag->Fixes.end(), ExtraFixes.begin(),
821 assert(
false &&
"Adding a note without main diagnostic");
828 if (LastDiag->Severity == DiagnosticsEngine::Ignored)
833 auto ReplacementFixes = Fixer(LastDiag->Severity,
Info);
834 if (!ReplacementFixes.empty()) {
835 assert(
Info.getNumFixItHints() == 0 &&
836 "Include-fixer replaced a note with clang fix-its attached!");
837 LastDiag->Fixes.insert(LastDiag->Fixes.end(), ReplacementFixes.begin(),
838 ReplacementFixes.end());
843 if (!
Info.getFixItHints().empty()) {
853 LastDiag->Notes.push_back(std::move(N));
858 void StoreDiags::flushLastDiag() {
861 auto Finish = llvm::make_scope_exit([&, NDiags(Output.size())] {
862 if (Output.size() == NDiags)
863 vlog(
"Dropped diagnostic: {0}: {1}", LastDiag->File, LastDiag->Message);
867 if (LastDiag->Severity == DiagnosticsEngine::Ignored)
870 if (!LastDiag->InsideMainFile && LastDiagLoc && LastDiagOriginallyError) {
871 if (tryMoveToMainFile(*LastDiag, *LastDiagLoc)) {
873 if (!IncludedErrorLocations
874 .insert({LastDiag->Range.start.line,
875 LastDiag->Range.start.character})
880 if (!mentionsMainFile(*LastDiag))
882 Output.push_back(std::move(*LastDiag));
886 const llvm::StringSet<> &Suppress,
891 if (
ID == diag::pp_pragma_sysheader_in_main_file &&
LangOpts.IsHeaderFile)
894 if (
const char *CodePtr = getDiagnosticCode(
ID)) {
898 StringRef
Warning = DiagnosticIDs::getWarningOptionForDiag(
ID);
905 Code.consume_front(
"err_");
906 Code.consume_front(
"-W");
912 llvm::StringRef
Name) {
922 return {(
"https://clang.llvm.org/extra/clang-tidy/checks/" +
Name +
".html")
925 if (
Name ==
"unused-includes")
926 return {
"https://clangd.llvm.org/guides/include-cleaner"};