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/SourceManager.h" 20 #include "clang/Lex/Lexer.h" 21 #include "clang/Lex/Token.h" 22 #include "llvm/ADT/ArrayRef.h" 23 #include "llvm/ADT/StringRef.h" 24 #include "llvm/ADT/Twine.h" 25 #include "llvm/Support/Capacity.h" 26 #include "llvm/Support/Path.h" 27 #include "llvm/Support/ScopedPrinter.h" 28 #include "llvm/Support/Signals.h" 29 #include "llvm/Support/raw_ostream.h" 37 const char *getDiagnosticCode(
unsigned ID) {
39 #define DIAG(ENUM, CLASS, DEFAULT_MAPPING, DESC, GROPU, SFINAE, NOWERROR, \ 40 SHOWINSYSHEADER, CATEGORY) \ 41 case clang::diag::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" 60 bool mentionsMainFile(
const Diag &
D) {
66 for (
auto &N : D.Notes) {
75 bool 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))
81 return L != R.getEnd() && M.isPointWithin(L, R.getBegin(), R.getEnd());
86 Range diagnosticRange(
const clang::Diagnostic &D,
const LangOptions &L) {
87 auto &M = D.getSourceManager();
88 auto Loc = M.getFileLoc(D.getLocation());
89 for (
const auto &CR : D.getRanges()) {
90 auto R = Lexer::makeFileCharRange(CR, M, L);
91 if (locationInRange(
Loc, R, M))
95 for (
const auto &F : D.getFixItHints()) {
96 auto R = Lexer::makeFileCharRange(F.RemoveRange, M, L);
97 if (locationInRange(
Loc, R, M))
102 auto R = CharSourceRange::getCharRange(
Loc);
104 if (!Lexer::getRawToken(
Loc, Tok, M, L,
true) && Tok.isNot(tok::comment)) {
110 void 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));
118 for (
auto IncludeLocation = GetIncludeLoc(DiagLoc); IncludeLocation.isValid();
119 IncludeLocation = GetIncludeLoc(IncludeLocation))
120 IncludeInMainFile = IncludeLocation;
121 if (IncludeInMainFile.isInvalid())
125 D.File = SM.getFileEntryForID(SM.getMainFileID())->getName().str();
128 SM, Lexer::getLocForEndOfToken(IncludeInMainFile, 0, SM, LangOpts));
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);
140 D.Message = llvm::Twine(
"in included file: ", D.Message).str();
143 bool isInsideMainFile(
const SourceLocation
Loc,
const SourceManager &M) {
144 return Loc.isValid() && M.isWrittenInMainFile(M.getFileLoc(Loc));
147 bool isInsideMainFile(
const clang::Diagnostic &D) {
148 if (!D.hasSourceManager())
151 return isInsideMainFile(D.getLocation(), D.getSourceManager());
154 bool isNote(DiagnosticsEngine::Level L) {
155 return L == DiagnosticsEngine::Note || L == DiagnosticsEngine::Remark;
158 llvm::StringRef diagLeveltoString(DiagnosticsEngine::Level Lvl) {
160 case DiagnosticsEngine::Ignored:
162 case DiagnosticsEngine::Note:
164 case DiagnosticsEngine::Remark:
170 case DiagnosticsEngine::Fatal:
171 return "fatal error";
173 llvm_unreachable(
"unhandled DiagnosticsEngine::Level");
187 void printDiag(llvm::raw_string_ostream &OS,
const DiagBase &D) {
188 if (D.InsideMainFile) {
192 OS << llvm::sys::path::filename(D.File) <<
":";
198 auto Pos = D.Range.start;
199 OS << (Pos.line + 1) <<
":" << (Pos.character + 1) <<
":";
202 if (D.InsideMainFile)
206 OS << diagLeveltoString(D.Severity) <<
": " << D.Message;
210 std::string capitalize(std::string
Message) {
211 if (!Message.empty())
212 Message[0] = llvm::toUpper(Message[0]);
226 std::string mainMessage(
const Diag &D,
const ClangdDiagnosticOptions &Opts) {
228 llvm::raw_string_ostream OS(Result);
230 if (Opts.DisplayFixesCount && !D.Fixes.empty())
231 OS <<
" (" << (D.Fixes.size() > 1 ?
"fixes" :
"fix") <<
" available)";
233 if (!Opts.EmitRelatedLocations)
234 for (
auto &Note : D.Notes) {
239 return capitalize(std::move(Result));
243 std::string noteMessage(
const Diag &Main,
const DiagBase &Note,
244 const ClangdDiagnosticOptions &Opts) {
246 llvm::raw_string_ostream OS(Result);
250 if (!Opts.EmitRelatedLocations) {
255 return capitalize(std::move(Result));
270 const char *Sep =
"";
271 for (
const auto &Edit : F.
Edits) {
279 OS << static_cast<const DiagBase &>(
D);
280 if (!D.
Notes.empty()) {
282 const char *Sep =
"";
283 for (
auto &Note : D.
Notes) {
289 if (!D.
Fixes.empty()) {
291 const char *Sep =
"";
304 Action.
edit.emplace();
305 Action.
edit->changes.emplace();
327 Main.
source =
"clang-tidy";
340 Main.
message = mainMessage(D, Opts);
343 for (
auto &Note : D.
Notes) {
345 vlog(
"Dropping note from unknown file: {0}", Note);
352 RelInfo.
message = noteMessage(D, Note, Opts);
356 OutFn(std::move(Main), D.
Fixes);
361 for (
auto &Note : D.
Notes) {
362 if (!Note.InsideMainFile)
365 Res.
message = noteMessage(D, Note, Opts);
366 OutFn(std::move(Res), llvm::ArrayRef<Fix>());
372 case DiagnosticsEngine::Remark:
374 case DiagnosticsEngine::Note:
378 case DiagnosticsEngine::Fatal:
381 case DiagnosticsEngine::Ignored:
384 llvm_unreachable(
"Unknown diagnostic level!");
389 for (
auto &
Diag : Output) {
390 if (
const char *ClangDiag = getDiagnosticCode(
Diag.
ID)) {
392 StringRef
Warning = DiagnosticIDs::getWarningOptionForDiag(
Diag.
ID);
393 if (!Warning.empty()) {
396 StringRef
Name(ClangDiag);
399 Name.consume_front(
"err_");
405 if (Tidy !=
nullptr) {
407 if (!TidyDiag.empty()) {
412 auto CleanMessage = [&](std::string &Msg) {
414 if (Rest.consume_back(
"]") && Rest.consume_back(
Diag.
Name) &&
415 Rest.consume_back(
" ["))
416 Msg.resize(Rest.size());
420 CleanMessage(Note.Message);
430 std::set<std::pair<Range, std::string>> SeenDiags;
431 llvm::erase_if(Output, [&](
const Diag& D) {
434 return std::move(Output);
438 const Preprocessor *) {
450 constexpr
unsigned MaxLen = 50;
453 llvm::StringRef R = Code.split(
'\n').first;
455 R = R.take_front(MaxLen);
458 if (R.size() != Code.size())
463 const clang::Diagnostic &Info) {
464 DiagnosticConsumer::HandleDiagnostic(DiagLevel, Info);
466 if (!LangOpts || !Info.hasSourceManager()) {
471 bool InsideMainFile = isInsideMainFile(Info);
474 D.
Range = diagnosticRange(Info, *LangOpts);
476 Info.FormatDiagnostic(Message);
479 D.
File = Info.getSourceManager().getFilename(Info.getLocation());
480 auto &SM = Info.getSourceManager();
482 SM.getFileEntryForID(SM.getFileID(Info.getLocation())), SM);
484 D.
Category = DiagnosticIDs::getCategoryNameFromID(
485 DiagnosticIDs::getCategoryNumberForDiag(Info.getID()))
490 auto AddFix = [&](
bool SyntheticMessage) ->
bool {
491 assert(!Info.getFixItHints().empty() &&
492 "diagnostic does not have attached fix-its");
496 llvm::SmallVector<TextEdit, 1> Edits;
497 for (
auto &
FixIt : Info.getFixItHints()) {
500 if (
FixIt.RemoveRange.getBegin().isMacroID() ||
501 FixIt.RemoveRange.getEnd().isMacroID())
503 if (!isInsideMainFile(
FixIt.RemoveRange.getBegin(),
504 Info.getSourceManager()))
506 Edits.push_back(
toTextEdit(
FixIt, Info.getSourceManager(), *LangOpts));
511 if (SyntheticMessage && Info.getNumFixItHints() == 1) {
512 const auto &
FixIt = Info.getFixItHint(0);
513 bool Invalid =
false;
514 llvm::StringRef Remove = Lexer::getSourceText(
515 FixIt.RemoveRange, Info.getSourceManager(), *LangOpts, &Invalid);
516 llvm::StringRef Insert =
FixIt.CodeToInsert;
518 llvm::raw_svector_ostream M(Message);
519 if (!Remove.empty() && !Insert.empty()) {
525 }
else if (!Remove.empty()) {
529 }
else if (!Insert.empty()) {
535 std::replace(Message.begin(), Message.end(),
'\n',
' ');
539 Info.FormatDiagnostic(Message);
540 LastDiag->Fixes.push_back(
Fix{Message.str(), std::move(Edits)});
544 if (!isNote(DiagLevel)) {
549 DiagLevel = Adjuster(DiagLevel, Info);
550 if (DiagLevel == DiagnosticsEngine::Ignored) {
551 LastPrimaryDiagnosticWasSuppressed =
true;
555 LastPrimaryDiagnosticWasSuppressed =
false;
558 LastDiag->ID = Info.getID();
559 FillDiagBase(*LastDiag);
560 adjustDiagFromHeader(*LastDiag, Info, *LangOpts);
562 if (!Info.getFixItHints().empty())
565 auto ExtraFixes = Fixer(DiagLevel, Info);
566 LastDiag->Fixes.insert(LastDiag->Fixes.end(), ExtraFixes.begin(),
574 if (LastPrimaryDiagnosticWasSuppressed) {
579 assert(
false &&
"Adding a note without main diagnostic");
584 if (!Info.getFixItHints().empty()) {
594 LastDiag->Notes.push_back(std::move(N));
599 void StoreDiags::flushLastDiag() {
604 if (mentionsMainFile(*LastDiag) ||
605 (LastDiag->Severity >= DiagnosticsEngine::Level::Error &&
606 IncludeLinesWithErrors.insert(LastDiag->Range.start.line).second)) {
607 Output.push_back(std::move(*LastDiag));
609 vlog(
"Dropped diagnostic: {0}: {1}", LastDiag->File, LastDiag->Message);
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.
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.
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.
llvm::Optional< llvm::Expected< tooling::AtomicChanges > > Result
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)
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.