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/DenseSet.h" 24 #include "llvm/ADT/StringRef.h" 25 #include "llvm/ADT/Twine.h" 26 #include "llvm/Support/Capacity.h" 27 #include "llvm/Support/Path.h" 28 #include "llvm/Support/ScopedPrinter.h" 29 #include "llvm/Support/Signals.h" 30 #include "llvm/Support/raw_ostream.h" 38 const char *getDiagnosticCode(
unsigned ID) {
40 #define DIAG(ENUM, CLASS, DEFAULT_MAPPING, DESC, GROPU, SFINAE, NOWERROR, \ 41 SHOWINSYSHEADER, CATEGORY) \ 42 case clang::diag::ENUM: \ 44 #include "clang/Basic/DiagnosticASTKinds.inc" 45 #include "clang/Basic/DiagnosticAnalysisKinds.inc" 46 #include "clang/Basic/DiagnosticCommentKinds.inc" 47 #include "clang/Basic/DiagnosticCommonKinds.inc" 48 #include "clang/Basic/DiagnosticDriverKinds.inc" 49 #include "clang/Basic/DiagnosticFrontendKinds.inc" 50 #include "clang/Basic/DiagnosticLexKinds.inc" 51 #include "clang/Basic/DiagnosticParseKinds.inc" 52 #include "clang/Basic/DiagnosticRefactoringKinds.inc" 53 #include "clang/Basic/DiagnosticSemaKinds.inc" 54 #include "clang/Basic/DiagnosticSerializationKinds.inc" 61 bool mentionsMainFile(
const Diag &
D) {
67 for (
auto &N : D.Notes) {
76 bool locationInRange(SourceLocation L, CharSourceRange R,
77 const SourceManager &M) {
78 assert(R.isCharRange());
79 if (!R.isValid() || M.getFileID(R.getBegin()) != M.getFileID(R.getEnd()) ||
80 M.getFileID(R.getBegin()) != M.getFileID(L))
82 return L != R.getEnd() && M.isPointWithin(L, R.getBegin(), R.getEnd());
87 Range diagnosticRange(
const clang::Diagnostic &D,
const LangOptions &L) {
88 auto &M = D.getSourceManager();
89 auto Loc = M.getFileLoc(D.getLocation());
90 for (
const auto &CR : D.getRanges()) {
91 auto R = Lexer::makeFileCharRange(CR, M, L);
92 if (locationInRange(
Loc, R, M))
96 for (
const auto &F : D.getFixItHints()) {
97 auto R = Lexer::makeFileCharRange(F.RemoveRange, M, L);
98 if (locationInRange(
Loc, R, M))
103 auto R = CharSourceRange::getCharRange(
Loc);
105 if (!Lexer::getRawToken(
Loc, Tok, M, L,
true) && Tok.isNot(tok::comment)) {
112 bool adjustDiagFromHeader(Diag &D,
const clang::Diagnostic &Info,
113 const LangOptions &LangOpts) {
115 if (D.Severity < DiagnosticsEngine::Level::Error)
118 const SourceLocation &DiagLoc = Info.getLocation();
119 const SourceManager &SM = Info.getSourceManager();
120 SourceLocation IncludeInMainFile;
121 auto GetIncludeLoc = [&SM](SourceLocation SLoc) {
122 return SM.getIncludeLoc(SM.getFileID(SLoc));
124 for (
auto IncludeLocation = GetIncludeLoc(DiagLoc); IncludeLocation.isValid();
125 IncludeLocation = GetIncludeLoc(IncludeLocation)) {
127 IncludeInMainFile = IncludeLocation;
131 if (IncludeInMainFile.isInvalid())
135 D.File = SM.getFileEntryForID(SM.getMainFileID())->getName().str();
138 SM, Lexer::getLocForEndOfToken(IncludeInMainFile, 0, SM, LangOpts));
139 D.InsideMainFile =
true;
142 const auto *FE = SM.getFileEntryForID(SM.getFileID(DiagLoc));
143 D.Notes.emplace_back();
144 Note &N = D.Notes.back();
145 N.AbsFile = FE->tryGetRealPathName();
146 N.File = FE->getName();
147 N.Message =
"error occurred here";
148 N.Range = diagnosticRange(Info, LangOpts);
151 D.Message = llvm::Twine(
"in included file: ", D.Message).str();
156 if (!D.hasSourceManager())
162 bool isNote(DiagnosticsEngine::Level L) {
163 return L == DiagnosticsEngine::Note || L == DiagnosticsEngine::Remark;
166 llvm::StringRef diagLeveltoString(DiagnosticsEngine::Level Lvl) {
168 case DiagnosticsEngine::Ignored:
170 case DiagnosticsEngine::Note:
172 case DiagnosticsEngine::Remark:
178 case DiagnosticsEngine::Fatal:
179 return "fatal error";
181 llvm_unreachable(
"unhandled DiagnosticsEngine::Level");
195 void printDiag(llvm::raw_string_ostream &OS,
const DiagBase &D) {
196 if (D.InsideMainFile) {
200 OS << llvm::sys::path::filename(D.File) <<
":";
206 auto Pos = D.Range.start;
207 OS << (Pos.line + 1) <<
":" << (Pos.character + 1) <<
":";
210 if (D.InsideMainFile)
214 OS << diagLeveltoString(D.Severity) <<
": " << D.Message;
218 std::string capitalize(std::string
Message) {
219 if (!Message.empty())
220 Message[0] = llvm::toUpper(Message[0]);
234 std::string mainMessage(
const Diag &D,
const ClangdDiagnosticOptions &Opts) {
236 llvm::raw_string_ostream OS(Result);
238 if (Opts.DisplayFixesCount && !D.Fixes.empty())
239 OS <<
" (" << (D.Fixes.size() > 1 ?
"fixes" :
"fix") <<
" available)";
241 if (!Opts.EmitRelatedLocations)
242 for (
auto &Note : D.Notes) {
247 return capitalize(std::move(Result));
251 std::string noteMessage(
const Diag &Main,
const DiagBase &Note,
252 const ClangdDiagnosticOptions &Opts) {
254 llvm::raw_string_ostream OS(Result);
258 if (!Opts.EmitRelatedLocations) {
263 return capitalize(std::move(Result));
278 const char *Sep =
"";
279 for (
const auto &Edit : F.
Edits) {
287 OS << static_cast<const DiagBase &>(
D);
288 if (!D.
Notes.empty()) {
290 const char *Sep =
"";
291 for (
auto &Note : D.
Notes) {
297 if (!D.
Fixes.empty()) {
299 const char *Sep =
"";
312 Action.
edit.emplace();
313 Action.
edit->changes.emplace();
335 Main.
source =
"clang-tidy";
348 Main.
message = mainMessage(D, Opts);
351 for (
auto &Note : D.
Notes) {
353 vlog(
"Dropping note from unknown file: {0}", Note);
360 RelInfo.
message = noteMessage(D, Note, Opts);
364 OutFn(std::move(Main), D.
Fixes);
369 for (
auto &Note : D.
Notes) {
370 if (!Note.InsideMainFile)
373 Res.
message = noteMessage(D, Note, Opts);
374 OutFn(std::move(Res), llvm::ArrayRef<Fix>());
380 case DiagnosticsEngine::Remark:
382 case DiagnosticsEngine::Note:
386 case DiagnosticsEngine::Fatal:
389 case DiagnosticsEngine::Ignored:
392 llvm_unreachable(
"Unknown diagnostic level!");
397 for (
auto &
Diag : Output) {
398 if (
const char *ClangDiag = getDiagnosticCode(
Diag.
ID)) {
400 StringRef
Warning = DiagnosticIDs::getWarningOptionForDiag(
Diag.
ID);
401 if (!Warning.empty()) {
404 StringRef
Name(ClangDiag);
407 Name.consume_front(
"err_");
413 if (Tidy !=
nullptr) {
415 if (!TidyDiag.empty()) {
420 auto CleanMessage = [&](std::string &Msg) {
422 if (Rest.consume_back(
"]") && Rest.consume_back(
Diag.
Name) &&
423 Rest.consume_back(
" ["))
424 Msg.resize(Rest.size());
428 CleanMessage(Note.Message);
438 std::set<std::pair<Range, std::string>> SeenDiags;
439 llvm::erase_if(Output, [&](
const Diag& D) {
442 return std::move(Output);
446 const Preprocessor *) {
458 constexpr
unsigned MaxLen = 50;
461 llvm::StringRef R = Code.split(
'\n').first;
463 R = R.take_front(MaxLen);
466 if (R.size() != Code.size())
471 const clang::Diagnostic &Info) {
472 DiagnosticConsumer::HandleDiagnostic(DiagLevel, Info);
474 if (!LangOpts || !Info.hasSourceManager()) {
480 SourceManager &SM = Info.getSourceManager();
483 D.
Range = diagnosticRange(Info, *LangOpts);
485 Info.FormatDiagnostic(Message);
488 D.
File = SM.getFilename(Info.getLocation());
490 SM.getFileEntryForID(SM.getFileID(Info.getLocation())), SM);
492 D.
Category = DiagnosticIDs::getCategoryNameFromID(
493 DiagnosticIDs::getCategoryNumberForDiag(Info.getID()))
498 auto AddFix = [&](
bool SyntheticMessage) ->
bool {
499 assert(!Info.getFixItHints().empty() &&
500 "diagnostic does not have attached fix-its");
504 llvm::SmallVector<TextEdit, 1> Edits;
505 for (
auto &
FixIt : Info.getFixItHints()) {
508 if (
FixIt.RemoveRange.getBegin().isMacroID() ||
509 FixIt.RemoveRange.getEnd().isMacroID())
518 if (SyntheticMessage && Info.getNumFixItHints() == 1) {
519 const auto &
FixIt = Info.getFixItHint(0);
520 bool Invalid =
false;
521 llvm::StringRef Remove =
522 Lexer::getSourceText(
FixIt.RemoveRange, SM, *LangOpts, &Invalid);
523 llvm::StringRef Insert =
FixIt.CodeToInsert;
525 llvm::raw_svector_ostream M(Message);
526 if (!Remove.empty() && !Insert.empty()) {
532 }
else if (!Remove.empty()) {
536 }
else if (!Insert.empty()) {
542 std::replace(Message.begin(), Message.end(),
'\n',
' ');
546 Info.FormatDiagnostic(Message);
547 LastDiag->Fixes.push_back(
Fix{Message.str(), std::move(Edits)});
551 if (!isNote(DiagLevel)) {
556 DiagLevel = Adjuster(DiagLevel, Info);
557 if (DiagLevel == DiagnosticsEngine::Ignored) {
558 LastPrimaryDiagnosticWasSuppressed =
true;
562 LastPrimaryDiagnosticWasSuppressed =
false;
565 LastDiag->ID = Info.getID();
566 FillDiagBase(*LastDiag);
567 LastDiagWasAdjusted =
false;
569 LastDiagWasAdjusted = adjustDiagFromHeader(*LastDiag, Info, *LangOpts);
571 if (!Info.getFixItHints().empty())
574 auto ExtraFixes = Fixer(DiagLevel, Info);
575 LastDiag->Fixes.insert(LastDiag->Fixes.end(), ExtraFixes.begin(),
583 if (LastPrimaryDiagnosticWasSuppressed) {
588 assert(
false &&
"Adding a note without main diagnostic");
593 if (!Info.getFixItHints().empty()) {
603 LastDiag->Notes.push_back(std::move(N));
608 void StoreDiags::flushLastDiag() {
611 if (mentionsMainFile(*LastDiag) &&
612 (!LastDiagWasAdjusted ||
614 IncludeLinesWithErrors.insert(LastDiag->Range.start.line).second)) {
615 Output.push_back(std::move(*LastDiag));
617 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.
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.
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.