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/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" 42 const char *getDiagnosticCode(
unsigned ID) {
44 #define DIAG(ENUM, CLASS, DEFAULT_MAPPING, DESC, GROPU, SFINAE, NOWERROR, \ 45 SHOWINSYSHEADER, 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 isBlacklisted(
const Diag &D) {
80 if (D.ID == clang::diag::err_msasm_unable_to_create_target ||
81 D.ID == 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());
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))
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)) {
124 bool adjustDiagFromHeader(Diag &D,
const clang::Diagnostic &Info,
125 const LangOptions &LangOpts) {
127 if (D.Severity < DiagnosticsEngine::Level::Error)
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));
136 for (
auto IncludeLocation = GetIncludeLoc(DiagLoc); IncludeLocation.isValid();
137 IncludeLocation = GetIncludeLoc(IncludeLocation)) {
139 IncludeInMainFile = IncludeLocation;
143 if (IncludeInMainFile.isInvalid())
147 D.File = SM.getFileEntryForID(SM.getMainFileID())->getName().str();
150 SM, Lexer::getLocForEndOfToken(IncludeInMainFile, 0, SM, LangOpts));
151 D.InsideMainFile =
true;
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);
163 D.Message = llvm::Twine(
"in included file: ", D.Message).str();
168 if (!D.hasSourceManager())
174 bool isNote(DiagnosticsEngine::Level L) {
175 return L == DiagnosticsEngine::Note || L == DiagnosticsEngine::Remark;
178 llvm::StringRef diagLeveltoString(DiagnosticsEngine::Level Lvl) {
180 case DiagnosticsEngine::Ignored:
182 case DiagnosticsEngine::Note:
184 case DiagnosticsEngine::Remark:
190 case DiagnosticsEngine::Fatal:
191 return "fatal error";
193 llvm_unreachable(
"unhandled DiagnosticsEngine::Level");
207 void printDiag(llvm::raw_string_ostream &OS,
const DiagBase &D) {
208 if (D.InsideMainFile) {
212 OS << llvm::sys::path::filename(D.File) <<
":";
218 auto Pos = D.Range.start;
222 if (D.InsideMainFile)
226 OS << diagLeveltoString(D.Severity) <<
": " << D.Message;
230 std::string capitalize(std::string
Message) {
231 if (!Message.empty())
232 Message[0] = llvm::toUpper(Message[0]);
246 std::string mainMessage(
const Diag &D,
const ClangdDiagnosticOptions &Opts) {
248 llvm::raw_string_ostream OS(Result);
250 if (Opts.DisplayFixesCount && !D.Fixes.empty())
251 OS <<
" (" << (D.Fixes.size() > 1 ?
"fixes" :
"fix") <<
" available)";
253 if (!Opts.EmitRelatedLocations)
254 for (
auto &Note : D.Notes) {
259 return capitalize(std::move(Result));
263 std::string noteMessage(
const Diag &Main,
const DiagBase &Note,
264 const ClangdDiagnosticOptions &Opts) {
266 llvm::raw_string_ostream OS(Result);
270 if (!Opts.EmitRelatedLocations) {
275 return capitalize(std::move(Result));
290 const char *Sep =
"";
299 OS << static_cast<const DiagBase &>(D);
300 if (!D.
Notes.empty()) {
302 const char *Sep =
"";
303 for (
auto &Note : D.
Notes) {
309 if (!D.
Fixes.empty()) {
311 const char *Sep =
"";
324 Action.
edit.emplace();
325 Action.
edit->changes.emplace();
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;
355 Main.
source =
"clang-tidy";
368 Main.
message = mainMessage(D, Opts);
371 for (
auto &Note : D.
Notes) {
373 vlog(
"Dropping note from unknown file: {0}", Note);
380 RelInfo.
message = noteMessage(D, Note, Opts);
384 OutFn(std::move(Main), D.
Fixes);
389 for (
auto &Note : D.
Notes) {
390 if (!Note.InsideMainFile)
394 Res.
range = Note.Range;
395 Res.
message = noteMessage(D, Note, Opts);
396 OutFn(std::move(Res), llvm::ArrayRef<Fix>());
402 case DiagnosticsEngine::Remark:
404 case DiagnosticsEngine::Note:
408 case DiagnosticsEngine::Fatal:
411 case DiagnosticsEngine::Ignored:
414 llvm_unreachable(
"Unknown diagnostic level!");
422 for (
auto &
Diag : Output) {
423 if (
const char *ClangDiag = getDiagnosticCode(
Diag.
ID)) {
425 StringRef
Warning = DiagnosticIDs::getWarningOptionForDiag(
Diag.
ID);
426 if (!Warning.empty()) {
429 StringRef
Name(ClangDiag);
432 Name.consume_front(
"err_");
438 if (Tidy !=
nullptr) {
440 if (!TidyDiag.empty()) {
445 auto CleanMessage = [&](std::string &Msg) {
447 if (Rest.consume_back(
"]") && Rest.consume_back(
Diag.
Name) &&
448 Rest.consume_back(
" ["))
449 Msg.resize(Rest.size());
453 CleanMessage(Note.Message);
463 std::set<std::pair<Range, std::string>> SeenDiags;
464 llvm::erase_if(Output, [&](
const Diag& D) {
467 return std::move(Output);
471 const Preprocessor *) {
482 constexpr
unsigned MaxLen = 50;
485 llvm::StringRef R = Code.split(
'\n').first;
487 R = R.take_front(MaxLen);
490 if (R.size() != Code.size())
498 const clang::Diagnostic &Info,
501 Info.FormatDiagnostic(Message);
505 D.
Category = DiagnosticIDs::getCategoryNameFromID(
506 DiagnosticIDs::getCategoryNumberForDiag(Info.getID()))
511 const clang::Diagnostic &Info) {
512 DiagnosticConsumer::HandleDiagnostic(DiagLevel, Info);
514 if (Info.getLocation().isInvalid()) {
517 if (DiagLevel < DiagnosticsEngine::Level::Error) {
525 LastDiag->ID = Info.getID();
527 LastDiag->InsideMainFile =
true;
529 LastDiag->Range.start =
Position{0, 0};
530 LastDiag->Range.end =
Position{0, 0};
534 if (!LangOpts || !Info.hasSourceManager()) {
540 SourceManager &SM = Info.getSourceManager();
542 auto FillDiagBase = [&](
DiagBase &D) {
546 D.
Range = diagnosticRange(Info, *LangOpts);
547 D.
File = SM.getFilename(Info.getLocation());
549 SM.getFileEntryForID(SM.getFileID(Info.getLocation())), SM);
553 auto AddFix = [&](
bool SyntheticMessage) ->
bool {
554 assert(!Info.getFixItHints().empty() &&
555 "diagnostic does not have attached fix-its");
559 llvm::SmallVector<TextEdit, 1> Edits;
560 for (
auto &
FixIt : Info.getFixItHints()) {
563 if (
FixIt.RemoveRange.getBegin().isMacroID() ||
564 FixIt.RemoveRange.getEnd().isMacroID())
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;
580 llvm::raw_svector_ostream M(Message);
581 if (!Remove.empty() && !Insert.empty()) {
587 }
else if (!Remove.empty()) {
591 }
else if (!Insert.empty()) {
597 std::replace(Message.begin(), Message.end(),
'\n',
' ');
601 Info.FormatDiagnostic(Message);
602 LastDiag->Fixes.push_back(
Fix{Message.str(), std::move(Edits)});
606 if (!isNote(DiagLevel)) {
611 DiagLevel = Adjuster(DiagLevel, Info);
612 if (DiagLevel == DiagnosticsEngine::Ignored) {
613 LastPrimaryDiagnosticWasSuppressed =
true;
617 LastPrimaryDiagnosticWasSuppressed =
false;
620 LastDiag->ID = Info.getID();
621 FillDiagBase(*LastDiag);
623 LastDiagWasAdjusted = adjustDiagFromHeader(*LastDiag, Info, *LangOpts);
625 if (!Info.getFixItHints().empty())
628 auto ExtraFixes = Fixer(DiagLevel, Info);
629 LastDiag->Fixes.insert(LastDiag->Fixes.end(), ExtraFixes.begin(),
637 if (LastPrimaryDiagnosticWasSuppressed) {
642 assert(
false &&
"Adding a note without main diagnostic");
647 if (!Info.getFixItHints().empty()) {
657 LastDiag->Notes.push_back(std::move(N));
662 void StoreDiags::flushLastDiag() {
665 if (!isBlacklisted(*LastDiag) && mentionsMainFile(*LastDiag) &&
666 (!LastDiagWasAdjusted ||
668 IncludeLinesWithErrors.insert(LastDiag->Range.start.line).second)) {
669 Output.push_back(std::move(*LastDiag));
671 vlog(
"Dropped diagnostic: {0}: {1}", LastDiag->File, LastDiag->Message);
674 LastDiagWasAdjusted =
false;
SourceLocation Loc
'#' location in the include directive
std::string code
The diagnostic's code. Can be omitted.
static void log(DiagnosticsEngine::Level DiagLevel, const clang::Diagnostic &Info)
Position start
The range's start position.
bool EmbedFixesInDiagnostics
If true, Clangd uses an LSP extension to embed the fixes with the diagnostics that are sent to the cl...
Contains basic information about a diagnostic.
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.
llvm::Optional< std::string > kind
The kind of the code action.
std::string title
A short, human-readable, title for this code action.
bool isInsideMainFile(SourceLocation Loc, const SourceManager &SM)
Returns true iff Loc is inside the main file.
A code action represents a change that can be performed in code, e.g.
constexpr llvm::StringLiteral Message
URIForFile uri
The text document's URI.
llvm::Optional< WorkspaceEdit > edit
The workspace edit this code action performs.
Documents should not be synced at all.
void BeginSourceFile(const LangOptions &Opts, const Preprocessor *) override
void vlog(const char *Fmt, Ts &&... Vals)
static std::string replace(llvm::StringRef Haystack, llvm::StringRef Needle, llvm::StringRef Repl)
A top-level diagnostic that may have Notes and Fixes.
std::string Message
Message for the fix-it.
std::vector< Fix > Fixes
Alternative fixes for this diagnostic, one should be chosen.
std::string source
A human-readable string describing the source of this diagnostic, e.g.
llvm::SmallVector< TextEdit, 1 > Edits
TextEdits from clang's fix-its. Must be non-empty.
TextEdit toTextEdit(const FixItHint &FixIt, const SourceManager &M, const LangOptions &L)
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's category.
DiagnosticsEngine::Level Severity
static URIForFile canonicalize(llvm::StringRef AbsPath, llvm::StringRef TUPath)
Canonicalizes AbsPath via URI.
static constexpr llvm::StringLiteral Name
Represents a single fix-it that editor can apply to fix the error.
llvm::Optional< Range > getTokenRange(const SourceManager &SM, const LangOptions &LangOpts, SourceLocation TokLoc)
Returns the taken range at TokLoc.
Position sourceLocToPosition(const SourceManager &SM, SourceLocation Loc)
Turn a SourceLocation into a [line, column] pair.
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).
int character
Character offset on a line in a document (zero-based).
std::vector< Note > Notes
Elaborate on the problem, usually pointing to a related piece of code.
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
std::string message
The diagnostic's message.
bool EmitRelatedLocations
If true, Clangd uses the relatedInformation field to include other locations (in particular attached ...
CharSourceRange Range
SourceRange for the file name.
static const llvm::StringLiteral QUICKFIX_KIND
llvm::Optional< std::string > getCanonicalPath(const FileEntry *F, const SourceManager &SourceMgr)
Get the canonical path of F.
std::string getCheckName(unsigned DiagnosticID) const
Returns the name of the clang-tidy check which produced this diagnostic ID.
int severity
The diagnostic's severity.
bool SendDiagnosticCategory
If true, Clangd uses an LSP extension to send the diagnostic's category to the client.
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.
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's end position.
llvm::raw_ostream & operator<<(llvm::raw_ostream &OS, const CodeCompletion &C)
A set of edits generated for a single file.
std::vector< Diag > take(const clang::tidy::ClangTidyContext *Tidy=nullptr)
llvm::Optional< std::vector< DiagnosticRelatedInformation > > relatedInformation
An array of related diagnostic information, e.g.
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
Range halfOpenToRange(const SourceManager &SM, CharSourceRange R)
llvm::StringRef file() const
Retrieves absolute path to the file.