| Kirill Bobyrev | 8e35f1e | 2018-08-14 16:03:32 +0000 | [diff] [blame] | 1 | //===--- Diagnostics.cpp -----------------------------------------*- C++-*-===// | 
| Ilya Biryukov | 71028b8 | 2018-03-12 15:28:22 +0000 | [diff] [blame] | 2 | // | 
|  | 3 | //                     The LLVM Compiler Infrastructure | 
|  | 4 | // | 
|  | 5 | // This file is distributed under the University of Illinois Open Source | 
|  | 6 | // License. See LICENSE.TXT for details. | 
|  | 7 | // | 
| Kirill Bobyrev | 8e35f1e | 2018-08-14 16:03:32 +0000 | [diff] [blame] | 8 | //===----------------------------------------------------------------------===// | 
| Ilya Biryukov | 71028b8 | 2018-03-12 15:28:22 +0000 | [diff] [blame] | 9 |  | 
|  | 10 | #include "Diagnostics.h" | 
|  | 11 | #include "Compiler.h" | 
|  | 12 | #include "Logger.h" | 
|  | 13 | #include "SourceCode.h" | 
|  | 14 | #include "clang/Basic/SourceManager.h" | 
|  | 15 | #include "clang/Lex/Lexer.h" | 
|  | 16 | #include "llvm/Support/Capacity.h" | 
|  | 17 | #include "llvm/Support/Path.h" | 
|  | 18 | #include <algorithm> | 
|  | 19 |  | 
|  | 20 | namespace clang { | 
|  | 21 | namespace clangd { | 
|  | 22 |  | 
|  | 23 | namespace { | 
|  | 24 |  | 
|  | 25 | bool mentionsMainFile(const Diag &D) { | 
|  | 26 | if (D.InsideMainFile) | 
|  | 27 | return true; | 
|  | 28 | // Fixes are always in the main file. | 
|  | 29 | if (!D.Fixes.empty()) | 
|  | 30 | return true; | 
|  | 31 | for (auto &N : D.Notes) { | 
|  | 32 | if (N.InsideMainFile) | 
|  | 33 | return true; | 
|  | 34 | } | 
|  | 35 | return false; | 
|  | 36 | } | 
|  | 37 |  | 
|  | 38 | // Checks whether a location is within a half-open range. | 
|  | 39 | // Note that clang also uses closed source ranges, which this can't handle! | 
|  | 40 | bool locationInRange(SourceLocation L, CharSourceRange R, | 
|  | 41 | const SourceManager &M) { | 
|  | 42 | assert(R.isCharRange()); | 
|  | 43 | if (!R.isValid() || M.getFileID(R.getBegin()) != M.getFileID(R.getEnd()) || | 
|  | 44 | M.getFileID(R.getBegin()) != M.getFileID(L)) | 
|  | 45 | return false; | 
|  | 46 | return L != R.getEnd() && M.isPointWithin(L, R.getBegin(), R.getEnd()); | 
|  | 47 | } | 
|  | 48 |  | 
|  | 49 | // Clang diags have a location (shown as ^) and 0 or more ranges (~~~~). | 
|  | 50 | // LSP needs a single range. | 
|  | 51 | Range diagnosticRange(const clang::Diagnostic &D, const LangOptions &L) { | 
|  | 52 | auto &M = D.getSourceManager(); | 
|  | 53 | auto Loc = M.getFileLoc(D.getLocation()); | 
| Ilya Biryukov | 71028b8 | 2018-03-12 15:28:22 +0000 | [diff] [blame] | 54 | for (const auto &CR : D.getRanges()) { | 
|  | 55 | auto R = Lexer::makeFileCharRange(CR, M, L); | 
|  | 56 | if (locationInRange(Loc, R, M)) | 
|  | 57 | return halfOpenToRange(M, R); | 
|  | 58 | } | 
| Ilya Biryukov | f2001aa | 2019-01-07 15:45:19 +0000 | [diff] [blame^] | 59 | llvm::Optional<Range> FallbackRange; | 
| Ilya Biryukov | 71028b8 | 2018-03-12 15:28:22 +0000 | [diff] [blame] | 60 | // The range may be given as a fixit hint instead. | 
|  | 61 | for (const auto &F : D.getFixItHints()) { | 
|  | 62 | auto R = Lexer::makeFileCharRange(F.RemoveRange, M, L); | 
|  | 63 | if (locationInRange(Loc, R, M)) | 
|  | 64 | return halfOpenToRange(M, R); | 
| Kadir Cetinkaya | 2e3f1f1 | 2018-09-27 12:12:42 +0000 | [diff] [blame] | 65 | // If there's a fixit that performs insertion, it has zero-width. Therefore | 
|  | 66 | // it can't contain the location of the diag, but it might be possible that | 
|  | 67 | // this should be reported as range. For example missing semicolon. | 
| Kadir Cetinkaya | 84bf607 | 2018-10-09 08:41:12 +0000 | [diff] [blame] | 68 | if (R.getBegin() == R.getEnd() && Loc == R.getBegin()) | 
| Kadir Cetinkaya | 2e3f1f1 | 2018-09-27 12:12:42 +0000 | [diff] [blame] | 69 | FallbackRange = halfOpenToRange(M, R); | 
| Ilya Biryukov | 71028b8 | 2018-03-12 15:28:22 +0000 | [diff] [blame] | 70 | } | 
| Kadir Cetinkaya | 2e3f1f1 | 2018-09-27 12:12:42 +0000 | [diff] [blame] | 71 | if (FallbackRange) | 
|  | 72 | return *FallbackRange; | 
| Ilya Biryukov | 71028b8 | 2018-03-12 15:28:22 +0000 | [diff] [blame] | 73 | // If no suitable range is found, just use the token at the location. | 
|  | 74 | auto R = Lexer::makeFileCharRange(CharSourceRange::getTokenRange(Loc), M, L); | 
|  | 75 | if (!R.isValid()) // Fall back to location only, let the editor deal with it. | 
|  | 76 | R = CharSourceRange::getCharRange(Loc); | 
|  | 77 | return halfOpenToRange(M, R); | 
|  | 78 | } | 
|  | 79 |  | 
| Ilya Biryukov | 71028b8 | 2018-03-12 15:28:22 +0000 | [diff] [blame] | 80 | bool isInsideMainFile(const SourceLocation Loc, const SourceManager &M) { | 
| Ilya Biryukov | e8ccb82 | 2018-11-26 17:05:13 +0000 | [diff] [blame] | 81 | return Loc.isValid() && M.isWrittenInMainFile(M.getFileLoc(Loc)); | 
| Ilya Biryukov | 71028b8 | 2018-03-12 15:28:22 +0000 | [diff] [blame] | 82 | } | 
|  | 83 |  | 
|  | 84 | bool isInsideMainFile(const clang::Diagnostic &D) { | 
|  | 85 | if (!D.hasSourceManager()) | 
|  | 86 | return false; | 
|  | 87 |  | 
|  | 88 | return isInsideMainFile(D.getLocation(), D.getSourceManager()); | 
|  | 89 | } | 
|  | 90 |  | 
|  | 91 | bool isNote(DiagnosticsEngine::Level L) { | 
|  | 92 | return L == DiagnosticsEngine::Note || L == DiagnosticsEngine::Remark; | 
|  | 93 | } | 
|  | 94 |  | 
| Ilya Biryukov | f2001aa | 2019-01-07 15:45:19 +0000 | [diff] [blame^] | 95 | llvm::StringRef diagLeveltoString(DiagnosticsEngine::Level Lvl) { | 
| Ilya Biryukov | 71028b8 | 2018-03-12 15:28:22 +0000 | [diff] [blame] | 96 | switch (Lvl) { | 
|  | 97 | case DiagnosticsEngine::Ignored: | 
|  | 98 | return "ignored"; | 
|  | 99 | case DiagnosticsEngine::Note: | 
|  | 100 | return "note"; | 
|  | 101 | case DiagnosticsEngine::Remark: | 
|  | 102 | return "remark"; | 
|  | 103 | case DiagnosticsEngine::Warning: | 
|  | 104 | return "warning"; | 
|  | 105 | case DiagnosticsEngine::Error: | 
|  | 106 | return "error"; | 
|  | 107 | case DiagnosticsEngine::Fatal: | 
|  | 108 | return "fatal error"; | 
|  | 109 | } | 
|  | 110 | llvm_unreachable("unhandled DiagnosticsEngine::Level"); | 
|  | 111 | } | 
|  | 112 |  | 
|  | 113 | /// Prints a single diagnostic in a clang-like manner, the output includes | 
|  | 114 | /// location, severity and error message. An example of the output message is: | 
|  | 115 | /// | 
|  | 116 | ///     main.cpp:12:23: error: undeclared identifier | 
|  | 117 | /// | 
|  | 118 | /// For main file we only print the basename and for all other files we print | 
|  | 119 | /// the filename on a separate line to provide a slightly more readable output | 
|  | 120 | /// in the editors: | 
|  | 121 | /// | 
|  | 122 | ///     dir1/dir2/dir3/../../dir4/header.h:12:23 | 
|  | 123 | ///     error: undeclared identifier | 
| Ilya Biryukov | f2001aa | 2019-01-07 15:45:19 +0000 | [diff] [blame^] | 124 | void printDiag(llvm::raw_string_ostream &OS, const DiagBase &D) { | 
| Ilya Biryukov | 71028b8 | 2018-03-12 15:28:22 +0000 | [diff] [blame] | 125 | if (D.InsideMainFile) { | 
|  | 126 | // Paths to main files are often taken from compile_command.json, where they | 
|  | 127 | // are typically absolute. To reduce noise we print only basename for them, | 
|  | 128 | // it should not be confusing and saves space. | 
| Ilya Biryukov | f2001aa | 2019-01-07 15:45:19 +0000 | [diff] [blame^] | 129 | OS << llvm::sys::path::filename(D.File) << ":"; | 
| Ilya Biryukov | 71028b8 | 2018-03-12 15:28:22 +0000 | [diff] [blame] | 130 | } else { | 
|  | 131 | OS << D.File << ":"; | 
|  | 132 | } | 
|  | 133 | // Note +1 to line and character. clangd::Range is zero-based, but when | 
|  | 134 | // printing for users we want one-based indexes. | 
|  | 135 | auto Pos = D.Range.start; | 
|  | 136 | OS << (Pos.line + 1) << ":" << (Pos.character + 1) << ":"; | 
|  | 137 | // The non-main-file paths are often too long, putting them on a separate | 
|  | 138 | // line improves readability. | 
|  | 139 | if (D.InsideMainFile) | 
|  | 140 | OS << " "; | 
|  | 141 | else | 
|  | 142 | OS << "\n"; | 
|  | 143 | OS << diagLeveltoString(D.Severity) << ": " << D.Message; | 
|  | 144 | } | 
|  | 145 |  | 
| Alex Lorenz | b411cf3 | 2018-08-03 20:43:28 +0000 | [diff] [blame] | 146 | /// Capitalizes the first word in the diagnostic's message. | 
|  | 147 | std::string capitalize(std::string Message) { | 
|  | 148 | if (!Message.empty()) | 
| Ilya Biryukov | f2001aa | 2019-01-07 15:45:19 +0000 | [diff] [blame^] | 149 | Message[0] = llvm::toUpper(Message[0]); | 
| Alex Lorenz | b411cf3 | 2018-08-03 20:43:28 +0000 | [diff] [blame] | 150 | return Message; | 
|  | 151 | } | 
|  | 152 |  | 
| Ilya Biryukov | 71028b8 | 2018-03-12 15:28:22 +0000 | [diff] [blame] | 153 | /// Returns a message sent to LSP for the main diagnostic in \p D. | 
|  | 154 | /// The message includes all the notes with their corresponding locations. | 
|  | 155 | /// However, notes with fix-its are excluded as those usually only contain a | 
|  | 156 | /// fix-it message and just add noise if included in the message for diagnostic. | 
|  | 157 | /// Example output: | 
|  | 158 | /// | 
|  | 159 | ///     no matching function for call to 'foo' | 
|  | 160 | /// | 
|  | 161 | ///     main.cpp:3:5: note: candidate function not viable: requires 2 arguments | 
|  | 162 | /// | 
|  | 163 | ///     dir1/dir2/dir3/../../dir4/header.h:12:23 | 
|  | 164 | ///     note: candidate function not viable: requires 3 arguments | 
|  | 165 | std::string mainMessage(const Diag &D) { | 
|  | 166 | std::string Result; | 
| Ilya Biryukov | f2001aa | 2019-01-07 15:45:19 +0000 | [diff] [blame^] | 167 | llvm::raw_string_ostream OS(Result); | 
| Ilya Biryukov | 71028b8 | 2018-03-12 15:28:22 +0000 | [diff] [blame] | 168 | OS << D.Message; | 
|  | 169 | for (auto &Note : D.Notes) { | 
|  | 170 | OS << "\n\n"; | 
|  | 171 | printDiag(OS, Note); | 
|  | 172 | } | 
|  | 173 | OS.flush(); | 
| Alex Lorenz | b411cf3 | 2018-08-03 20:43:28 +0000 | [diff] [blame] | 174 | return capitalize(std::move(Result)); | 
| Ilya Biryukov | 71028b8 | 2018-03-12 15:28:22 +0000 | [diff] [blame] | 175 | } | 
|  | 176 |  | 
|  | 177 | /// Returns a message sent to LSP for the note of the main diagnostic. | 
|  | 178 | /// The message includes the main diagnostic to provide the necessary context | 
|  | 179 | /// for the user to understand the note. | 
|  | 180 | std::string noteMessage(const Diag &Main, const DiagBase &Note) { | 
|  | 181 | std::string Result; | 
| Ilya Biryukov | f2001aa | 2019-01-07 15:45:19 +0000 | [diff] [blame^] | 182 | llvm::raw_string_ostream OS(Result); | 
| Ilya Biryukov | 71028b8 | 2018-03-12 15:28:22 +0000 | [diff] [blame] | 183 | OS << Note.Message; | 
|  | 184 | OS << "\n\n"; | 
|  | 185 | printDiag(OS, Main); | 
|  | 186 | OS.flush(); | 
| Alex Lorenz | b411cf3 | 2018-08-03 20:43:28 +0000 | [diff] [blame] | 187 | return capitalize(std::move(Result)); | 
| Ilya Biryukov | 71028b8 | 2018-03-12 15:28:22 +0000 | [diff] [blame] | 188 | } | 
|  | 189 | } // namespace | 
|  | 190 |  | 
| Ilya Biryukov | f2001aa | 2019-01-07 15:45:19 +0000 | [diff] [blame^] | 191 | llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const DiagBase &D) { | 
| Sam McCall | 991e316 | 2018-11-20 10:58:48 +0000 | [diff] [blame] | 192 | OS << "["; | 
| Ilya Biryukov | 71028b8 | 2018-03-12 15:28:22 +0000 | [diff] [blame] | 193 | if (!D.InsideMainFile) | 
| Sam McCall | 991e316 | 2018-11-20 10:58:48 +0000 | [diff] [blame] | 194 | OS << D.File << ":"; | 
|  | 195 | OS << D.Range.start << "-" << D.Range.end << "] "; | 
|  | 196 |  | 
| Ilya Biryukov | 71028b8 | 2018-03-12 15:28:22 +0000 | [diff] [blame] | 197 | return OS << D.Message; | 
|  | 198 | } | 
|  | 199 |  | 
| Ilya Biryukov | f2001aa | 2019-01-07 15:45:19 +0000 | [diff] [blame^] | 200 | llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const Fix &F) { | 
| Ilya Biryukov | 71028b8 | 2018-03-12 15:28:22 +0000 | [diff] [blame] | 201 | OS << F.Message << " {"; | 
|  | 202 | const char *Sep = ""; | 
|  | 203 | for (const auto &Edit : F.Edits) { | 
|  | 204 | OS << Sep << Edit; | 
|  | 205 | Sep = ", "; | 
|  | 206 | } | 
|  | 207 | return OS << "}"; | 
|  | 208 | } | 
|  | 209 |  | 
| Ilya Biryukov | f2001aa | 2019-01-07 15:45:19 +0000 | [diff] [blame^] | 210 | llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const Diag &D) { | 
| Ilya Biryukov | 71028b8 | 2018-03-12 15:28:22 +0000 | [diff] [blame] | 211 | OS << static_cast<const DiagBase &>(D); | 
|  | 212 | if (!D.Notes.empty()) { | 
|  | 213 | OS << ", notes: {"; | 
|  | 214 | const char *Sep = ""; | 
|  | 215 | for (auto &Note : D.Notes) { | 
|  | 216 | OS << Sep << Note; | 
|  | 217 | Sep = ", "; | 
|  | 218 | } | 
|  | 219 | OS << "}"; | 
|  | 220 | } | 
|  | 221 | if (!D.Fixes.empty()) { | 
|  | 222 | OS << ", fixes: {"; | 
|  | 223 | const char *Sep = ""; | 
|  | 224 | for (auto &Fix : D.Fixes) { | 
|  | 225 | OS << Sep << Fix; | 
|  | 226 | Sep = ", "; | 
|  | 227 | } | 
|  | 228 | } | 
|  | 229 | return OS; | 
|  | 230 | } | 
|  | 231 |  | 
| Sam McCall | 16e7070 | 2018-10-24 07:59:38 +0000 | [diff] [blame] | 232 | CodeAction toCodeAction(const Fix &F, const URIForFile &File) { | 
|  | 233 | CodeAction Action; | 
|  | 234 | Action.title = F.Message; | 
|  | 235 | Action.kind = CodeAction::QUICKFIX_KIND; | 
|  | 236 | Action.edit.emplace(); | 
|  | 237 | Action.edit->changes.emplace(); | 
|  | 238 | (*Action.edit->changes)[File.uri()] = {F.Edits.begin(), F.Edits.end()}; | 
|  | 239 | return Action; | 
|  | 240 | } | 
|  | 241 |  | 
| Ilya Biryukov | f2001aa | 2019-01-07 15:45:19 +0000 | [diff] [blame^] | 242 | void toLSPDiags( | 
|  | 243 | const Diag &D, const URIForFile &File, const ClangdDiagnosticOptions &Opts, | 
|  | 244 | llvm::function_ref<void(clangd::Diagnostic, llvm::ArrayRef<Fix>)> OutFn) { | 
| Ilya Biryukov | 71028b8 | 2018-03-12 15:28:22 +0000 | [diff] [blame] | 245 | auto FillBasicFields = [](const DiagBase &D) -> clangd::Diagnostic { | 
|  | 246 | clangd::Diagnostic Res; | 
|  | 247 | Res.range = D.Range; | 
|  | 248 | Res.severity = getSeverity(D.Severity); | 
|  | 249 | return Res; | 
|  | 250 | }; | 
|  | 251 |  | 
|  | 252 | { | 
|  | 253 | clangd::Diagnostic Main = FillBasicFields(D); | 
|  | 254 | Main.message = mainMessage(D); | 
| Sam McCall | 16e7070 | 2018-10-24 07:59:38 +0000 | [diff] [blame] | 255 | if (Opts.EmbedFixesInDiagnostics) { | 
|  | 256 | Main.codeActions.emplace(); | 
|  | 257 | for (const auto &Fix : D.Fixes) | 
|  | 258 | Main.codeActions->push_back(toCodeAction(Fix, File)); | 
|  | 259 | } | 
|  | 260 | if (Opts.SendDiagnosticCategory && !D.Category.empty()) | 
|  | 261 | Main.category = D.Category; | 
|  | 262 |  | 
| Ilya Biryukov | 71028b8 | 2018-03-12 15:28:22 +0000 | [diff] [blame] | 263 | OutFn(std::move(Main), D.Fixes); | 
|  | 264 | } | 
|  | 265 |  | 
|  | 266 | for (auto &Note : D.Notes) { | 
|  | 267 | if (!Note.InsideMainFile) | 
|  | 268 | continue; | 
|  | 269 | clangd::Diagnostic Res = FillBasicFields(Note); | 
|  | 270 | Res.message = noteMessage(D, Note); | 
| Ilya Biryukov | f2001aa | 2019-01-07 15:45:19 +0000 | [diff] [blame^] | 271 | OutFn(std::move(Res), llvm::ArrayRef<Fix>()); | 
| Ilya Biryukov | 71028b8 | 2018-03-12 15:28:22 +0000 | [diff] [blame] | 272 | } | 
|  | 273 | } | 
|  | 274 |  | 
|  | 275 | int getSeverity(DiagnosticsEngine::Level L) { | 
|  | 276 | switch (L) { | 
|  | 277 | case DiagnosticsEngine::Remark: | 
|  | 278 | return 4; | 
|  | 279 | case DiagnosticsEngine::Note: | 
|  | 280 | return 3; | 
|  | 281 | case DiagnosticsEngine::Warning: | 
|  | 282 | return 2; | 
|  | 283 | case DiagnosticsEngine::Fatal: | 
|  | 284 | case DiagnosticsEngine::Error: | 
|  | 285 | return 1; | 
|  | 286 | case DiagnosticsEngine::Ignored: | 
|  | 287 | return 0; | 
|  | 288 | } | 
|  | 289 | llvm_unreachable("Unknown diagnostic level!"); | 
|  | 290 | } | 
|  | 291 |  | 
|  | 292 | std::vector<Diag> StoreDiags::take() { return std::move(Output); } | 
|  | 293 |  | 
|  | 294 | void StoreDiags::BeginSourceFile(const LangOptions &Opts, | 
|  | 295 | const Preprocessor *) { | 
|  | 296 | LangOpts = Opts; | 
|  | 297 | } | 
|  | 298 |  | 
|  | 299 | void StoreDiags::EndSourceFile() { | 
|  | 300 | flushLastDiag(); | 
| Sam McCall | c008af6 | 2018-10-20 15:30:37 +0000 | [diff] [blame] | 301 | LangOpts = None; | 
| Ilya Biryukov | 71028b8 | 2018-03-12 15:28:22 +0000 | [diff] [blame] | 302 | } | 
|  | 303 |  | 
|  | 304 | void StoreDiags::HandleDiagnostic(DiagnosticsEngine::Level DiagLevel, | 
|  | 305 | const clang::Diagnostic &Info) { | 
|  | 306 | DiagnosticConsumer::HandleDiagnostic(DiagLevel, Info); | 
|  | 307 |  | 
|  | 308 | if (!LangOpts || !Info.hasSourceManager()) { | 
|  | 309 | IgnoreDiagnostics::log(DiagLevel, Info); | 
|  | 310 | return; | 
|  | 311 | } | 
|  | 312 |  | 
|  | 313 | bool InsideMainFile = isInsideMainFile(Info); | 
|  | 314 |  | 
|  | 315 | auto FillDiagBase = [&](DiagBase &D) { | 
|  | 316 | D.Range = diagnosticRange(Info, *LangOpts); | 
| Ilya Biryukov | f2001aa | 2019-01-07 15:45:19 +0000 | [diff] [blame^] | 317 | llvm::SmallString<64> Message; | 
| Ilya Biryukov | 71028b8 | 2018-03-12 15:28:22 +0000 | [diff] [blame] | 318 | Info.FormatDiagnostic(Message); | 
|  | 319 | D.Message = Message.str(); | 
|  | 320 | D.InsideMainFile = InsideMainFile; | 
|  | 321 | D.File = Info.getSourceManager().getFilename(Info.getLocation()); | 
|  | 322 | D.Severity = DiagLevel; | 
| Alex Lorenz | 3714643 | 2018-08-14 22:21:40 +0000 | [diff] [blame] | 323 | D.Category = DiagnosticIDs::getCategoryNameFromID( | 
|  | 324 | DiagnosticIDs::getCategoryNumberForDiag(Info.getID())) | 
|  | 325 | .str(); | 
| Ilya Biryukov | 71028b8 | 2018-03-12 15:28:22 +0000 | [diff] [blame] | 326 | return D; | 
|  | 327 | }; | 
|  | 328 |  | 
| Sam McCall | ba3c02e | 2018-04-03 17:35:57 +0000 | [diff] [blame] | 329 | auto AddFix = [&](bool SyntheticMessage) -> bool { | 
| Ilya Biryukov | 71028b8 | 2018-03-12 15:28:22 +0000 | [diff] [blame] | 330 | assert(!Info.getFixItHints().empty() && | 
|  | 331 | "diagnostic does not have attached fix-its"); | 
|  | 332 | if (!InsideMainFile) | 
|  | 333 | return false; | 
|  | 334 |  | 
| Ilya Biryukov | f2001aa | 2019-01-07 15:45:19 +0000 | [diff] [blame^] | 335 | llvm::SmallVector<TextEdit, 1> Edits; | 
| Ilya Biryukov | 71028b8 | 2018-03-12 15:28:22 +0000 | [diff] [blame] | 336 | for (auto &FixIt : Info.getFixItHints()) { | 
|  | 337 | if (!isInsideMainFile(FixIt.RemoveRange.getBegin(), | 
|  | 338 | Info.getSourceManager())) | 
|  | 339 | return false; | 
|  | 340 | Edits.push_back(toTextEdit(FixIt, Info.getSourceManager(), *LangOpts)); | 
|  | 341 | } | 
|  | 342 |  | 
| Ilya Biryukov | f2001aa | 2019-01-07 15:45:19 +0000 | [diff] [blame^] | 343 | llvm::SmallString<64> Message; | 
| Sam McCall | ba3c02e | 2018-04-03 17:35:57 +0000 | [diff] [blame] | 344 | // If requested and possible, create a message like "change 'foo' to 'bar'". | 
|  | 345 | if (SyntheticMessage && Info.getNumFixItHints() == 1) { | 
|  | 346 | const auto &FixIt = Info.getFixItHint(0); | 
|  | 347 | bool Invalid = false; | 
| Ilya Biryukov | f2001aa | 2019-01-07 15:45:19 +0000 | [diff] [blame^] | 348 | llvm::StringRef Remove = Lexer::getSourceText( | 
| Sam McCall | ba3c02e | 2018-04-03 17:35:57 +0000 | [diff] [blame] | 349 | FixIt.RemoveRange, Info.getSourceManager(), *LangOpts, &Invalid); | 
| Ilya Biryukov | f2001aa | 2019-01-07 15:45:19 +0000 | [diff] [blame^] | 350 | llvm::StringRef Insert = FixIt.CodeToInsert; | 
| Sam McCall | ba3c02e | 2018-04-03 17:35:57 +0000 | [diff] [blame] | 351 | if (!Invalid) { | 
| Ilya Biryukov | f2001aa | 2019-01-07 15:45:19 +0000 | [diff] [blame^] | 352 | llvm::raw_svector_ostream M(Message); | 
| Sam McCall | ba3c02e | 2018-04-03 17:35:57 +0000 | [diff] [blame] | 353 | if (!Remove.empty() && !Insert.empty()) | 
|  | 354 | M << "change '" << Remove << "' to '" << Insert << "'"; | 
|  | 355 | else if (!Remove.empty()) | 
|  | 356 | M << "remove '" << Remove << "'"; | 
|  | 357 | else if (!Insert.empty()) | 
|  | 358 | M << "insert '" << Insert << "'"; | 
|  | 359 | // Don't allow source code to inject newlines into diagnostics. | 
|  | 360 | std::replace(Message.begin(), Message.end(), '\n', ' '); | 
|  | 361 | } | 
|  | 362 | } | 
|  | 363 | if (Message.empty()) // either !SytheticMessage, or we failed to make one. | 
|  | 364 | Info.FormatDiagnostic(Message); | 
| Ilya Biryukov | 71028b8 | 2018-03-12 15:28:22 +0000 | [diff] [blame] | 365 | LastDiag->Fixes.push_back(Fix{Message.str(), std::move(Edits)}); | 
|  | 366 | return true; | 
|  | 367 | }; | 
|  | 368 |  | 
|  | 369 | if (!isNote(DiagLevel)) { | 
|  | 370 | // Handle the new main diagnostic. | 
|  | 371 | flushLastDiag(); | 
|  | 372 |  | 
|  | 373 | LastDiag = Diag(); | 
|  | 374 | FillDiagBase(*LastDiag); | 
|  | 375 |  | 
|  | 376 | if (!Info.getFixItHints().empty()) | 
| Sam McCall | ba3c02e | 2018-04-03 17:35:57 +0000 | [diff] [blame] | 377 | AddFix(true /* try to invent a message instead of repeating the diag */); | 
| Ilya Biryukov | 71028b8 | 2018-03-12 15:28:22 +0000 | [diff] [blame] | 378 | } else { | 
|  | 379 | // Handle a note to an existing diagnostic. | 
|  | 380 | if (!LastDiag) { | 
|  | 381 | assert(false && "Adding a note without main diagnostic"); | 
|  | 382 | IgnoreDiagnostics::log(DiagLevel, Info); | 
|  | 383 | return; | 
|  | 384 | } | 
|  | 385 |  | 
|  | 386 | if (!Info.getFixItHints().empty()) { | 
|  | 387 | // A clang note with fix-it is not a separate diagnostic in clangd. We | 
|  | 388 | // attach it as a Fix to the main diagnostic instead. | 
| Sam McCall | ba3c02e | 2018-04-03 17:35:57 +0000 | [diff] [blame] | 389 | if (!AddFix(false /* use the note as the message */)) | 
| Ilya Biryukov | 71028b8 | 2018-03-12 15:28:22 +0000 | [diff] [blame] | 390 | IgnoreDiagnostics::log(DiagLevel, Info); | 
|  | 391 | } else { | 
|  | 392 | // A clang note without fix-its corresponds to clangd::Note. | 
|  | 393 | Note N; | 
|  | 394 | FillDiagBase(N); | 
|  | 395 |  | 
|  | 396 | LastDiag->Notes.push_back(std::move(N)); | 
|  | 397 | } | 
|  | 398 | } | 
|  | 399 | } | 
|  | 400 |  | 
|  | 401 | void StoreDiags::flushLastDiag() { | 
|  | 402 | if (!LastDiag) | 
|  | 403 | return; | 
|  | 404 | if (mentionsMainFile(*LastDiag)) | 
|  | 405 | Output.push_back(std::move(*LastDiag)); | 
|  | 406 | else | 
| Sam McCall | bed5885 | 2018-07-11 10:35:11 +0000 | [diff] [blame] | 407 | log("Dropped diagnostic outside main file: {0}: {1}", LastDiag->File, | 
| Ilya Biryukov | 71028b8 | 2018-03-12 15:28:22 +0000 | [diff] [blame] | 408 | LastDiag->Message); | 
|  | 409 | LastDiag.reset(); | 
|  | 410 | } | 
|  | 411 |  | 
|  | 412 | } // namespace clangd | 
|  | 413 | } // namespace clang |