diff options
Diffstat (limited to 'clangd/Diagnostics.cpp')
-rw-r--r-- | clangd/Diagnostics.cpp | 237 |
1 files changed, 196 insertions, 41 deletions
diff --git a/clangd/Diagnostics.cpp b/clangd/Diagnostics.cpp index 1cd52ad3..c004fa32 100644 --- a/clangd/Diagnostics.cpp +++ b/clangd/Diagnostics.cpp @@ -1,27 +1,59 @@ //===--- Diagnostics.cpp -----------------------------------------*- C++-*-===// // -// The LLVM Compiler Infrastructure -// -// This file is distributed under the University of Illinois Open Source -// License. See LICENSE.TXT for details. +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include "Diagnostics.h" +#include "../clang-tidy/ClangTidyDiagnosticConsumer.h" #include "Compiler.h" #include "Logger.h" +#include "Protocol.h" #include "SourceCode.h" +#include "clang/Basic/AllDiagnostics.h" +#include "clang/Basic/Diagnostic.h" +#include "clang/Basic/DiagnosticIDs.h" +#include "clang/Basic/FileManager.h" #include "clang/Basic/SourceManager.h" #include "clang/Lex/Lexer.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/ADT/Twine.h" #include "llvm/Support/Capacity.h" #include "llvm/Support/Path.h" +#include "llvm/Support/ScopedPrinter.h" +#include "llvm/Support/Signals.h" #include <algorithm> namespace clang { namespace clangd { - namespace { +const char *getDiagnosticCode(unsigned ID) { + switch (ID) { +#define DIAG(ENUM, CLASS, DEFAULT_MAPPING, DESC, GROPU, SFINAE, NOWERROR, \ + SHOWINSYSHEADER, CATEGORY) \ + case clang::diag::ENUM: \ + return #ENUM; +#include "clang/Basic/DiagnosticASTKinds.inc" +#include "clang/Basic/DiagnosticAnalysisKinds.inc" +#include "clang/Basic/DiagnosticCommentKinds.inc" +#include "clang/Basic/DiagnosticCommonKinds.inc" +#include "clang/Basic/DiagnosticDriverKinds.inc" +#include "clang/Basic/DiagnosticFrontendKinds.inc" +#include "clang/Basic/DiagnosticLexKinds.inc" +#include "clang/Basic/DiagnosticParseKinds.inc" +#include "clang/Basic/DiagnosticRefactoringKinds.inc" +#include "clang/Basic/DiagnosticSemaKinds.inc" +#include "clang/Basic/DiagnosticSerializationKinds.inc" +#undef DIAG + default: + return nullptr; + } +} + bool mentionsMainFile(const Diag &D) { if (D.InsideMainFile) return true; @@ -77,6 +109,39 @@ Range diagnosticRange(const clang::Diagnostic &D, const LangOptions &L) { return halfOpenToRange(M, R); } +void adjustDiagFromHeader(Diag &D, const clang::Diagnostic &Info, + const LangOptions &LangOpts) { + const SourceLocation &DiagLoc = Info.getLocation(); + const SourceManager &SM = Info.getSourceManager(); + SourceLocation IncludeInMainFile; + auto GetIncludeLoc = [&SM](SourceLocation SLoc) { + return SM.getIncludeLoc(SM.getFileID(SLoc)); + }; + for (auto IncludeLocation = GetIncludeLoc(DiagLoc); IncludeLocation.isValid(); + IncludeLocation = GetIncludeLoc(IncludeLocation)) + IncludeInMainFile = IncludeLocation; + if (IncludeInMainFile.isInvalid()) + return; + + // Update diag to point at include inside main file. + D.File = SM.getFileEntryForID(SM.getMainFileID())->getName().str(); + D.Range.start = sourceLocToPosition(SM, IncludeInMainFile); + D.Range.end = sourceLocToPosition( + SM, Lexer::getLocForEndOfToken(IncludeInMainFile, 0, SM, LangOpts)); + + // Add a note that will point to real diagnostic. + const auto *FE = SM.getFileEntryForID(SM.getFileID(DiagLoc)); + D.Notes.emplace_back(); + Note &N = D.Notes.back(); + N.AbsFile = FE->tryGetRealPathName(); + N.File = FE->getName(); + N.Message = "error occurred here"; + N.Range = diagnosticRange(Info, LangOpts); + + // Update message to mention original file. + D.Message = llvm::Twine("in included file: ", D.Message).str(); +} + bool isInsideMainFile(const SourceLocation Loc, const SourceManager &M) { return Loc.isValid() && M.isWrittenInMainFile(M.getFileLoc(Loc)); } @@ -151,9 +216,7 @@ std::string capitalize(std::string Message) { } /// Returns a message sent to LSP for the main diagnostic in \p D. -/// The message includes all the notes with their corresponding locations. -/// However, notes with fix-its are excluded as those usually only contain a -/// fix-it message and just add noise if included in the message for diagnostic. +/// This message may include notes, if they're not emited in some other way. /// Example output: /// /// no matching function for call to 'foo' @@ -162,27 +225,34 @@ std::string capitalize(std::string Message) { /// /// dir1/dir2/dir3/../../dir4/header.h:12:23 /// note: candidate function not viable: requires 3 arguments -std::string mainMessage(const Diag &D) { +std::string mainMessage(const Diag &D, const ClangdDiagnosticOptions &Opts) { std::string Result; llvm::raw_string_ostream OS(Result); OS << D.Message; - for (auto &Note : D.Notes) { - OS << "\n\n"; - printDiag(OS, Note); - } + if (Opts.DisplayFixesCount && !D.Fixes.empty()) + OS << " (" << (D.Fixes.size() > 1 ? "fixes" : "fix") << " available)"; + // If notes aren't emitted as structured info, add them to the message. + if (!Opts.EmitRelatedLocations) + for (auto &Note : D.Notes) { + OS << "\n\n"; + printDiag(OS, Note); + } OS.flush(); return capitalize(std::move(Result)); } /// Returns a message sent to LSP for the note of the main diagnostic. -/// The message includes the main diagnostic to provide the necessary context -/// for the user to understand the note. -std::string noteMessage(const Diag &Main, const DiagBase &Note) { +std::string noteMessage(const Diag &Main, const DiagBase &Note, + const ClangdDiagnosticOptions &Opts) { std::string Result; llvm::raw_string_ostream OS(Result); OS << Note.Message; - OS << "\n\n"; - printDiag(OS, Main); + // If the client doesn't support structured links between the note and the + // original diagnostic, then emit the main diagnostic to give context. + if (!Opts.EmitRelatedLocations) { + OS << "\n\n"; + printDiag(OS, Main); + } OS.flush(); return capitalize(std::move(Result)); } @@ -249,27 +319,54 @@ void toLSPDiags( return Res; }; - { - clangd::Diagnostic Main = FillBasicFields(D); - Main.message = mainMessage(D); - if (Opts.EmbedFixesInDiagnostics) { - Main.codeActions.emplace(); - for (const auto &Fix : D.Fixes) - Main.codeActions->push_back(toCodeAction(Fix, File)); - } - if (Opts.SendDiagnosticCategory && !D.Category.empty()) - Main.category = D.Category; - - OutFn(std::move(Main), D.Fixes); + clangd::Diagnostic Main = FillBasicFields(D); + Main.code = D.Name; + switch (D.Source) { + case Diag::Clang: + Main.source = "clang"; + break; + case Diag::ClangTidy: + Main.source = "clang-tidy"; + break; + case Diag::Unknown: + break; + } + if (Opts.EmbedFixesInDiagnostics) { + Main.codeActions.emplace(); + for (const auto &Fix : D.Fixes) + Main.codeActions->push_back(toCodeAction(Fix, File)); } + if (Opts.SendDiagnosticCategory && !D.Category.empty()) + Main.category = D.Category; - for (auto &Note : D.Notes) { - if (!Note.InsideMainFile) - continue; - clangd::Diagnostic Res = FillBasicFields(Note); - Res.message = noteMessage(D, Note); - OutFn(std::move(Res), llvm::ArrayRef<Fix>()); + Main.message = mainMessage(D, Opts); + if (Opts.EmitRelatedLocations) { + Main.relatedInformation.emplace(); + for (auto &Note : D.Notes) { + if (!Note.AbsFile) { + vlog("Dropping note from unknown file: {0}", Note); + continue; + } + DiagnosticRelatedInformation RelInfo; + RelInfo.location.range = Note.Range; + RelInfo.location.uri = + URIForFile::canonicalize(*Note.AbsFile, File.file()); + RelInfo.message = noteMessage(D, Note, Opts); + Main.relatedInformation->push_back(std::move(RelInfo)); + } } + OutFn(std::move(Main), D.Fixes); + + // If we didn't emit the notes as relatedLocations, emit separate diagnostics + // so the user can find the locations easily. + if (!Opts.EmitRelatedLocations) + for (auto &Note : D.Notes) { + if (!Note.InsideMainFile) + continue; + clangd::Diagnostic Res = FillBasicFields(Note); + Res.message = noteMessage(D, Note, Opts); + OutFn(std::move(Res), llvm::ArrayRef<Fix>()); + } } int getSeverity(DiagnosticsEngine::Level L) { @@ -289,7 +386,46 @@ int getSeverity(DiagnosticsEngine::Level L) { llvm_unreachable("Unknown diagnostic level!"); } -std::vector<Diag> StoreDiags::take() { return std::move(Output); } +std::vector<Diag> StoreDiags::take(const clang::tidy::ClangTidyContext *Tidy) { + // Fill in name/source now that we have all the context needed to map them. + for (auto &Diag : Output) { + if (const char *ClangDiag = getDiagnosticCode(Diag.ID)) { + // Warnings controlled by -Wfoo are better recognized by that name. + StringRef Warning = DiagnosticIDs::getWarningOptionForDiag(Diag.ID); + if (!Warning.empty()) { + Diag.Name = ("-W" + Warning).str(); + } else { + StringRef Name(ClangDiag); + // Almost always an error, with a name like err_enum_class_reference. + // Drop the err_ prefix for brevity. + Name.consume_front("err_"); + Diag.Name = Name; + } + Diag.Source = Diag::Clang; + continue; + } + if (Tidy != nullptr) { + std::string TidyDiag = Tidy->getCheckName(Diag.ID); + if (!TidyDiag.empty()) { + Diag.Name = std::move(TidyDiag); + Diag.Source = Diag::ClangTidy; + // clang-tidy bakes the name into diagnostic messages. Strip it out. + // It would be much nicer to make clang-tidy not do this. + auto CleanMessage = [&](std::string &Msg) { + StringRef Rest(Msg); + if (Rest.consume_back("]") && Rest.consume_back(Diag.Name) && + Rest.consume_back(" [")) + Msg.resize(Rest.size()); + }; + CleanMessage(Diag.Message); + for (auto &Note : Diag.Notes) + CleanMessage(Note.Message); + continue; + } + } + } + return std::move(Output); +} void StoreDiags::BeginSourceFile(const LangOptions &Opts, const Preprocessor *) { @@ -319,6 +455,9 @@ void StoreDiags::HandleDiagnostic(DiagnosticsEngine::Level DiagLevel, D.Message = Message.str(); D.InsideMainFile = InsideMainFile; D.File = Info.getSourceManager().getFilename(Info.getLocation()); + auto &SM = Info.getSourceManager(); + D.AbsFile = getCanonicalPath( + SM.getFileEntryForID(SM.getFileID(Info.getLocation())), SM); D.Severity = DiagLevel; D.Category = DiagnosticIDs::getCategoryNameFromID( DiagnosticIDs::getCategoryNumberForDiag(Info.getID())) @@ -334,6 +473,11 @@ void StoreDiags::HandleDiagnostic(DiagnosticsEngine::Level DiagLevel, llvm::SmallVector<TextEdit, 1> Edits; for (auto &FixIt : Info.getFixItHints()) { + // Follow clang's behavior, don't apply FixIt to the code in macros, + // we are less certain it is the right fix. + if (FixIt.RemoveRange.getBegin().isMacroID() || + FixIt.RemoveRange.getEnd().isMacroID()) + return false; if (!isInsideMainFile(FixIt.RemoveRange.getBegin(), Info.getSourceManager())) return false; @@ -371,10 +515,17 @@ void StoreDiags::HandleDiagnostic(DiagnosticsEngine::Level DiagLevel, flushLastDiag(); LastDiag = Diag(); + LastDiag->ID = Info.getID(); FillDiagBase(*LastDiag); + adjustDiagFromHeader(*LastDiag, Info, *LangOpts); if (!Info.getFixItHints().empty()) AddFix(true /* try to invent a message instead of repeating the diag */); + if (Fixer) { + auto ExtraFixes = Fixer(DiagLevel, Info); + LastDiag->Fixes.insert(LastDiag->Fixes.end(), ExtraFixes.begin(), + ExtraFixes.end()); + } } else { // Handle a note to an existing diagnostic. if (!LastDiag) { @@ -401,11 +552,15 @@ void StoreDiags::HandleDiagnostic(DiagnosticsEngine::Level DiagLevel, void StoreDiags::flushLastDiag() { if (!LastDiag) return; - if (mentionsMainFile(*LastDiag)) + // Only keeps diagnostics inside main file or the first one coming from a + // header. + if (mentionsMainFile(*LastDiag) || + (LastDiag->Severity >= DiagnosticsEngine::Level::Error && + IncludeLinesWithErrors.insert(LastDiag->Range.start.line).second)) { Output.push_back(std::move(*LastDiag)); - else - log("Dropped diagnostic outside main file: {0}: {1}", LastDiag->File, - LastDiag->Message); + } else { + vlog("Dropped diagnostic: {0}: {1}", LastDiag->File, LastDiag->Message); + } LastDiag.reset(); } |