| 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 | // | 
| Chandler Carruth | 2946cd7 | 2019-01-19 08:50:56 +0000 | [diff] [blame] | 3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | 
|  | 4 | // See https://llvm.org/LICENSE.txt for license information. | 
|  | 5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | 
| Ilya Biryukov | 71028b8 | 2018-03-12 15:28:22 +0000 | [diff] [blame] | 6 | // | 
| Kirill Bobyrev | 8e35f1e | 2018-08-14 16:03:32 +0000 | [diff] [blame] | 7 | //===----------------------------------------------------------------------===// | 
| Ilya Biryukov | 71028b8 | 2018-03-12 15:28:22 +0000 | [diff] [blame] | 8 |  | 
|  | 9 | #include "Diagnostics.h" | 
| Sam McCall | 641caa5 | 2019-04-17 12:35:16 +0000 | [diff] [blame] | 10 | #include "../clang-tidy/ClangTidyDiagnosticConsumer.h" | 
| Ilya Biryukov | 71028b8 | 2018-03-12 15:28:22 +0000 | [diff] [blame] | 11 | #include "Compiler.h" | 
|  | 12 | #include "Logger.h" | 
| Sam McCall | c9e4ee9 | 2019-04-18 15:17:07 +0000 | [diff] [blame] | 13 | #include "Protocol.h" | 
| Ilya Biryukov | 71028b8 | 2018-03-12 15:28:22 +0000 | [diff] [blame] | 14 | #include "SourceCode.h" | 
| Sam McCall | 641caa5 | 2019-04-17 12:35:16 +0000 | [diff] [blame] | 15 | #include "clang/Basic/AllDiagnostics.h" | 
| Kadir Cetinkaya | 01efe64 | 2019-04-29 10:25:44 +0000 | [diff] [blame] | 16 | #include "clang/Basic/Diagnostic.h" | 
| Sam McCall | 641caa5 | 2019-04-17 12:35:16 +0000 | [diff] [blame] | 17 | #include "clang/Basic/DiagnosticIDs.h" | 
| Kadir Cetinkaya | 01efe64 | 2019-04-29 10:25:44 +0000 | [diff] [blame] | 18 | #include "clang/Basic/FileManager.h" | 
| Ilya Biryukov | 71028b8 | 2018-03-12 15:28:22 +0000 | [diff] [blame] | 19 | #include "clang/Basic/SourceManager.h" | 
|  | 20 | #include "clang/Lex/Lexer.h" | 
| Kadir Cetinkaya | 88e636d | 2019-06-13 12:31:36 +0000 | [diff] [blame] | 21 | #include "clang/Lex/Token.h" | 
| Kadir Cetinkaya | 01efe64 | 2019-04-29 10:25:44 +0000 | [diff] [blame] | 22 | #include "llvm/ADT/ArrayRef.h" | 
|  | 23 | #include "llvm/ADT/StringRef.h" | 
|  | 24 | #include "llvm/ADT/Twine.h" | 
| Ilya Biryukov | 71028b8 | 2018-03-12 15:28:22 +0000 | [diff] [blame] | 25 | #include "llvm/Support/Capacity.h" | 
|  | 26 | #include "llvm/Support/Path.h" | 
| Kadir Cetinkaya | 01efe64 | 2019-04-29 10:25:44 +0000 | [diff] [blame] | 27 | #include "llvm/Support/ScopedPrinter.h" | 
|  | 28 | #include "llvm/Support/Signals.h" | 
| Ilya Biryukov | 0f748e6 | 2019-05-24 10:26:23 +0000 | [diff] [blame] | 29 | #include "llvm/Support/raw_ostream.h" | 
| Ilya Biryukov | 71028b8 | 2018-03-12 15:28:22 +0000 | [diff] [blame] | 30 | #include <algorithm> | 
| Ilya Biryukov | 0f748e6 | 2019-05-24 10:26:23 +0000 | [diff] [blame] | 31 | #include <cstddef> | 
| Ilya Biryukov | 71028b8 | 2018-03-12 15:28:22 +0000 | [diff] [blame] | 32 |  | 
|  | 33 | namespace clang { | 
|  | 34 | namespace clangd { | 
| Ilya Biryukov | 71028b8 | 2018-03-12 15:28:22 +0000 | [diff] [blame] | 35 | namespace { | 
|  | 36 |  | 
| Sam McCall | 641caa5 | 2019-04-17 12:35:16 +0000 | [diff] [blame] | 37 | const char *getDiagnosticCode(unsigned ID) { | 
|  | 38 | switch (ID) { | 
|  | 39 | #define DIAG(ENUM, CLASS, DEFAULT_MAPPING, DESC, GROPU, SFINAE, NOWERROR,      \ | 
|  | 40 | SHOWINSYSHEADER, CATEGORY)                                        \ | 
|  | 41 | case clang::diag::ENUM:                                                      \ | 
|  | 42 | return #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" | 
|  | 54 | #undef DIAG | 
|  | 55 | default: | 
|  | 56 | return nullptr; | 
|  | 57 | } | 
|  | 58 | } | 
|  | 59 |  | 
| Ilya Biryukov | 71028b8 | 2018-03-12 15:28:22 +0000 | [diff] [blame] | 60 | bool mentionsMainFile(const Diag &D) { | 
|  | 61 | if (D.InsideMainFile) | 
|  | 62 | return true; | 
|  | 63 | // Fixes are always in the main file. | 
|  | 64 | if (!D.Fixes.empty()) | 
|  | 65 | return true; | 
|  | 66 | for (auto &N : D.Notes) { | 
|  | 67 | if (N.InsideMainFile) | 
|  | 68 | return true; | 
|  | 69 | } | 
|  | 70 | return false; | 
|  | 71 | } | 
|  | 72 |  | 
|  | 73 | // Checks whether a location is within a half-open range. | 
|  | 74 | // Note that clang also uses closed source ranges, which this can't handle! | 
|  | 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)) | 
|  | 80 | return false; | 
|  | 81 | return L != R.getEnd() && M.isPointWithin(L, R.getBegin(), R.getEnd()); | 
|  | 82 | } | 
|  | 83 |  | 
|  | 84 | // Clang diags have a location (shown as ^) and 0 or more ranges (~~~~). | 
|  | 85 | // LSP needs a single range. | 
|  | 86 | Range diagnosticRange(const clang::Diagnostic &D, const LangOptions &L) { | 
|  | 87 | auto &M = D.getSourceManager(); | 
|  | 88 | auto Loc = M.getFileLoc(D.getLocation()); | 
| Ilya Biryukov | 71028b8 | 2018-03-12 15:28:22 +0000 | [diff] [blame] | 89 | for (const auto &CR : D.getRanges()) { | 
|  | 90 | auto R = Lexer::makeFileCharRange(CR, M, L); | 
|  | 91 | if (locationInRange(Loc, R, M)) | 
|  | 92 | return halfOpenToRange(M, R); | 
|  | 93 | } | 
|  | 94 | // The range may be given as a fixit hint instead. | 
|  | 95 | for (const auto &F : D.getFixItHints()) { | 
|  | 96 | auto R = Lexer::makeFileCharRange(F.RemoveRange, M, L); | 
|  | 97 | if (locationInRange(Loc, R, M)) | 
|  | 98 | return halfOpenToRange(M, R); | 
|  | 99 | } | 
| Kadir Cetinkaya | 88e636d | 2019-06-13 12:31:36 +0000 | [diff] [blame] | 100 | // If the token at the location is not a comment, we use the token. | 
|  | 101 | // If we can't get the token at the location, fall back to using the location | 
|  | 102 | auto R = CharSourceRange::getCharRange(Loc); | 
|  | 103 | Token Tok; | 
|  | 104 | if (!Lexer::getRawToken(Loc, Tok, M, L, true) && Tok.isNot(tok::comment)) { | 
|  | 105 | R = CharSourceRange::getTokenRange(Tok.getLocation(), Tok.getEndLoc()); | 
|  | 106 | } | 
| Ilya Biryukov | 71028b8 | 2018-03-12 15:28:22 +0000 | [diff] [blame] | 107 | return halfOpenToRange(M, R); | 
|  | 108 | } | 
|  | 109 |  | 
| Kadir Cetinkaya | 01efe64 | 2019-04-29 10:25:44 +0000 | [diff] [blame] | 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)); | 
|  | 117 | }; | 
|  | 118 | for (auto IncludeLocation = GetIncludeLoc(DiagLoc); IncludeLocation.isValid(); | 
|  | 119 | IncludeLocation = GetIncludeLoc(IncludeLocation)) | 
|  | 120 | IncludeInMainFile = IncludeLocation; | 
|  | 121 | if (IncludeInMainFile.isInvalid()) | 
|  | 122 | return; | 
|  | 123 |  | 
|  | 124 | // Update diag to point at include inside main file. | 
|  | 125 | D.File = SM.getFileEntryForID(SM.getMainFileID())->getName().str(); | 
|  | 126 | D.Range.start = sourceLocToPosition(SM, IncludeInMainFile); | 
|  | 127 | D.Range.end = sourceLocToPosition( | 
|  | 128 | SM, Lexer::getLocForEndOfToken(IncludeInMainFile, 0, SM, LangOpts)); | 
|  | 129 |  | 
|  | 130 | // Add a note that will point to real diagnostic. | 
|  | 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); | 
|  | 138 |  | 
|  | 139 | // Update message to mention original file. | 
|  | 140 | D.Message = llvm::Twine("in included file: ", D.Message).str(); | 
|  | 141 | } | 
|  | 142 |  | 
| Ilya Biryukov | 71028b8 | 2018-03-12 15:28:22 +0000 | [diff] [blame] | 143 | bool isInsideMainFile(const SourceLocation Loc, const SourceManager &M) { | 
| Ilya Biryukov | e8ccb82 | 2018-11-26 17:05:13 +0000 | [diff] [blame] | 144 | return Loc.isValid() && M.isWrittenInMainFile(M.getFileLoc(Loc)); | 
| Ilya Biryukov | 71028b8 | 2018-03-12 15:28:22 +0000 | [diff] [blame] | 145 | } | 
|  | 146 |  | 
|  | 147 | bool isInsideMainFile(const clang::Diagnostic &D) { | 
|  | 148 | if (!D.hasSourceManager()) | 
|  | 149 | return false; | 
|  | 150 |  | 
|  | 151 | return isInsideMainFile(D.getLocation(), D.getSourceManager()); | 
|  | 152 | } | 
|  | 153 |  | 
|  | 154 | bool isNote(DiagnosticsEngine::Level L) { | 
|  | 155 | return L == DiagnosticsEngine::Note || L == DiagnosticsEngine::Remark; | 
|  | 156 | } | 
|  | 157 |  | 
| Ilya Biryukov | f2001aa | 2019-01-07 15:45:19 +0000 | [diff] [blame] | 158 | llvm::StringRef diagLeveltoString(DiagnosticsEngine::Level Lvl) { | 
| Ilya Biryukov | 71028b8 | 2018-03-12 15:28:22 +0000 | [diff] [blame] | 159 | switch (Lvl) { | 
|  | 160 | case DiagnosticsEngine::Ignored: | 
|  | 161 | return "ignored"; | 
|  | 162 | case DiagnosticsEngine::Note: | 
|  | 163 | return "note"; | 
|  | 164 | case DiagnosticsEngine::Remark: | 
|  | 165 | return "remark"; | 
|  | 166 | case DiagnosticsEngine::Warning: | 
|  | 167 | return "warning"; | 
|  | 168 | case DiagnosticsEngine::Error: | 
|  | 169 | return "error"; | 
|  | 170 | case DiagnosticsEngine::Fatal: | 
|  | 171 | return "fatal error"; | 
|  | 172 | } | 
|  | 173 | llvm_unreachable("unhandled DiagnosticsEngine::Level"); | 
|  | 174 | } | 
|  | 175 |  | 
|  | 176 | /// Prints a single diagnostic in a clang-like manner, the output includes | 
|  | 177 | /// location, severity and error message. An example of the output message is: | 
|  | 178 | /// | 
|  | 179 | ///     main.cpp:12:23: error: undeclared identifier | 
|  | 180 | /// | 
|  | 181 | /// For main file we only print the basename and for all other files we print | 
|  | 182 | /// the filename on a separate line to provide a slightly more readable output | 
|  | 183 | /// in the editors: | 
|  | 184 | /// | 
|  | 185 | ///     dir1/dir2/dir3/../../dir4/header.h:12:23 | 
|  | 186 | ///     error: undeclared identifier | 
| Ilya Biryukov | f2001aa | 2019-01-07 15:45:19 +0000 | [diff] [blame] | 187 | void printDiag(llvm::raw_string_ostream &OS, const DiagBase &D) { | 
| Ilya Biryukov | 71028b8 | 2018-03-12 15:28:22 +0000 | [diff] [blame] | 188 | if (D.InsideMainFile) { | 
|  | 189 | // Paths to main files are often taken from compile_command.json, where they | 
|  | 190 | // are typically absolute. To reduce noise we print only basename for them, | 
|  | 191 | // it should not be confusing and saves space. | 
| Ilya Biryukov | f2001aa | 2019-01-07 15:45:19 +0000 | [diff] [blame] | 192 | OS << llvm::sys::path::filename(D.File) << ":"; | 
| Ilya Biryukov | 71028b8 | 2018-03-12 15:28:22 +0000 | [diff] [blame] | 193 | } else { | 
|  | 194 | OS << D.File << ":"; | 
|  | 195 | } | 
|  | 196 | // Note +1 to line and character. clangd::Range is zero-based, but when | 
|  | 197 | // printing for users we want one-based indexes. | 
|  | 198 | auto Pos = D.Range.start; | 
|  | 199 | OS << (Pos.line + 1) << ":" << (Pos.character + 1) << ":"; | 
|  | 200 | // The non-main-file paths are often too long, putting them on a separate | 
|  | 201 | // line improves readability. | 
|  | 202 | if (D.InsideMainFile) | 
|  | 203 | OS << " "; | 
|  | 204 | else | 
|  | 205 | OS << "\n"; | 
|  | 206 | OS << diagLeveltoString(D.Severity) << ": " << D.Message; | 
|  | 207 | } | 
|  | 208 |  | 
| Alex Lorenz | b411cf3 | 2018-08-03 20:43:28 +0000 | [diff] [blame] | 209 | /// Capitalizes the first word in the diagnostic's message. | 
|  | 210 | std::string capitalize(std::string Message) { | 
|  | 211 | if (!Message.empty()) | 
| Ilya Biryukov | f2001aa | 2019-01-07 15:45:19 +0000 | [diff] [blame] | 212 | Message[0] = llvm::toUpper(Message[0]); | 
| Alex Lorenz | b411cf3 | 2018-08-03 20:43:28 +0000 | [diff] [blame] | 213 | return Message; | 
|  | 214 | } | 
|  | 215 |  | 
| Ilya Biryukov | 71028b8 | 2018-03-12 15:28:22 +0000 | [diff] [blame] | 216 | /// Returns a message sent to LSP for the main diagnostic in \p D. | 
| Sam McCall | c9e4ee9 | 2019-04-18 15:17:07 +0000 | [diff] [blame] | 217 | /// This message may include notes, if they're not emited in some other way. | 
| Ilya Biryukov | 71028b8 | 2018-03-12 15:28:22 +0000 | [diff] [blame] | 218 | /// Example output: | 
|  | 219 | /// | 
|  | 220 | ///     no matching function for call to 'foo' | 
|  | 221 | /// | 
|  | 222 | ///     main.cpp:3:5: note: candidate function not viable: requires 2 arguments | 
|  | 223 | /// | 
|  | 224 | ///     dir1/dir2/dir3/../../dir4/header.h:12:23 | 
|  | 225 | ///     note: candidate function not viable: requires 3 arguments | 
| Sam McCall | c9e4ee9 | 2019-04-18 15:17:07 +0000 | [diff] [blame] | 226 | std::string mainMessage(const Diag &D, const ClangdDiagnosticOptions &Opts) { | 
| Ilya Biryukov | 71028b8 | 2018-03-12 15:28:22 +0000 | [diff] [blame] | 227 | std::string Result; | 
| Ilya Biryukov | f2001aa | 2019-01-07 15:45:19 +0000 | [diff] [blame] | 228 | llvm::raw_string_ostream OS(Result); | 
| Ilya Biryukov | 71028b8 | 2018-03-12 15:28:22 +0000 | [diff] [blame] | 229 | OS << D.Message; | 
| Sam McCall | c9e4ee9 | 2019-04-18 15:17:07 +0000 | [diff] [blame] | 230 | if (Opts.DisplayFixesCount && !D.Fixes.empty()) | 
| Eric Liu | 00eaf67 | 2019-01-31 16:09:25 +0000 | [diff] [blame] | 231 | OS << " (" << (D.Fixes.size() > 1 ? "fixes" : "fix") << " available)"; | 
| Sam McCall | c9e4ee9 | 2019-04-18 15:17:07 +0000 | [diff] [blame] | 232 | // If notes aren't emitted as structured info, add them to the message. | 
|  | 233 | if (!Opts.EmitRelatedLocations) | 
|  | 234 | for (auto &Note : D.Notes) { | 
|  | 235 | OS << "\n\n"; | 
|  | 236 | printDiag(OS, Note); | 
|  | 237 | } | 
| Ilya Biryukov | 71028b8 | 2018-03-12 15:28:22 +0000 | [diff] [blame] | 238 | OS.flush(); | 
| Alex Lorenz | b411cf3 | 2018-08-03 20:43:28 +0000 | [diff] [blame] | 239 | return capitalize(std::move(Result)); | 
| Ilya Biryukov | 71028b8 | 2018-03-12 15:28:22 +0000 | [diff] [blame] | 240 | } | 
|  | 241 |  | 
|  | 242 | /// Returns a message sent to LSP for the note of the main diagnostic. | 
| Sam McCall | c9e4ee9 | 2019-04-18 15:17:07 +0000 | [diff] [blame] | 243 | std::string noteMessage(const Diag &Main, const DiagBase &Note, | 
|  | 244 | const ClangdDiagnosticOptions &Opts) { | 
| Ilya Biryukov | 71028b8 | 2018-03-12 15:28:22 +0000 | [diff] [blame] | 245 | std::string Result; | 
| Ilya Biryukov | f2001aa | 2019-01-07 15:45:19 +0000 | [diff] [blame] | 246 | llvm::raw_string_ostream OS(Result); | 
| Ilya Biryukov | 71028b8 | 2018-03-12 15:28:22 +0000 | [diff] [blame] | 247 | OS << Note.Message; | 
| Sam McCall | c9e4ee9 | 2019-04-18 15:17:07 +0000 | [diff] [blame] | 248 | // If the client doesn't support structured links between the note and the | 
|  | 249 | // original diagnostic, then emit the main diagnostic to give context. | 
|  | 250 | if (!Opts.EmitRelatedLocations) { | 
|  | 251 | OS << "\n\n"; | 
|  | 252 | printDiag(OS, Main); | 
|  | 253 | } | 
| Ilya Biryukov | 71028b8 | 2018-03-12 15:28:22 +0000 | [diff] [blame] | 254 | OS.flush(); | 
| Alex Lorenz | b411cf3 | 2018-08-03 20:43:28 +0000 | [diff] [blame] | 255 | return capitalize(std::move(Result)); | 
| Ilya Biryukov | 71028b8 | 2018-03-12 15:28:22 +0000 | [diff] [blame] | 256 | } | 
|  | 257 | } // namespace | 
|  | 258 |  | 
| Ilya Biryukov | f2001aa | 2019-01-07 15:45:19 +0000 | [diff] [blame] | 259 | llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const DiagBase &D) { | 
| Sam McCall | 991e316 | 2018-11-20 10:58:48 +0000 | [diff] [blame] | 260 | OS << "["; | 
| Ilya Biryukov | 71028b8 | 2018-03-12 15:28:22 +0000 | [diff] [blame] | 261 | if (!D.InsideMainFile) | 
| Sam McCall | 991e316 | 2018-11-20 10:58:48 +0000 | [diff] [blame] | 262 | OS << D.File << ":"; | 
|  | 263 | OS << D.Range.start << "-" << D.Range.end << "] "; | 
|  | 264 |  | 
| Ilya Biryukov | 71028b8 | 2018-03-12 15:28:22 +0000 | [diff] [blame] | 265 | return OS << D.Message; | 
|  | 266 | } | 
|  | 267 |  | 
| Ilya Biryukov | f2001aa | 2019-01-07 15:45:19 +0000 | [diff] [blame] | 268 | llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const Fix &F) { | 
| Ilya Biryukov | 71028b8 | 2018-03-12 15:28:22 +0000 | [diff] [blame] | 269 | OS << F.Message << " {"; | 
|  | 270 | const char *Sep = ""; | 
|  | 271 | for (const auto &Edit : F.Edits) { | 
|  | 272 | OS << Sep << Edit; | 
|  | 273 | Sep = ", "; | 
|  | 274 | } | 
|  | 275 | return OS << "}"; | 
|  | 276 | } | 
|  | 277 |  | 
| Ilya Biryukov | f2001aa | 2019-01-07 15:45:19 +0000 | [diff] [blame] | 278 | llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const Diag &D) { | 
| Ilya Biryukov | 71028b8 | 2018-03-12 15:28:22 +0000 | [diff] [blame] | 279 | OS << static_cast<const DiagBase &>(D); | 
|  | 280 | if (!D.Notes.empty()) { | 
|  | 281 | OS << ", notes: {"; | 
|  | 282 | const char *Sep = ""; | 
|  | 283 | for (auto &Note : D.Notes) { | 
|  | 284 | OS << Sep << Note; | 
|  | 285 | Sep = ", "; | 
|  | 286 | } | 
|  | 287 | OS << "}"; | 
|  | 288 | } | 
|  | 289 | if (!D.Fixes.empty()) { | 
|  | 290 | OS << ", fixes: {"; | 
|  | 291 | const char *Sep = ""; | 
|  | 292 | for (auto &Fix : D.Fixes) { | 
|  | 293 | OS << Sep << Fix; | 
|  | 294 | Sep = ", "; | 
|  | 295 | } | 
|  | 296 | } | 
|  | 297 | return OS; | 
|  | 298 | } | 
|  | 299 |  | 
| Sam McCall | 16e7070 | 2018-10-24 07:59:38 +0000 | [diff] [blame] | 300 | CodeAction toCodeAction(const Fix &F, const URIForFile &File) { | 
|  | 301 | CodeAction Action; | 
|  | 302 | Action.title = F.Message; | 
|  | 303 | Action.kind = CodeAction::QUICKFIX_KIND; | 
|  | 304 | Action.edit.emplace(); | 
|  | 305 | Action.edit->changes.emplace(); | 
|  | 306 | (*Action.edit->changes)[File.uri()] = {F.Edits.begin(), F.Edits.end()}; | 
|  | 307 | return Action; | 
|  | 308 | } | 
|  | 309 |  | 
| Ilya Biryukov | f2001aa | 2019-01-07 15:45:19 +0000 | [diff] [blame] | 310 | void toLSPDiags( | 
|  | 311 | const Diag &D, const URIForFile &File, const ClangdDiagnosticOptions &Opts, | 
|  | 312 | llvm::function_ref<void(clangd::Diagnostic, llvm::ArrayRef<Fix>)> OutFn) { | 
| Ilya Biryukov | 71028b8 | 2018-03-12 15:28:22 +0000 | [diff] [blame] | 313 | auto FillBasicFields = [](const DiagBase &D) -> clangd::Diagnostic { | 
|  | 314 | clangd::Diagnostic Res; | 
|  | 315 | Res.range = D.Range; | 
|  | 316 | Res.severity = getSeverity(D.Severity); | 
|  | 317 | return Res; | 
|  | 318 | }; | 
|  | 319 |  | 
| Sam McCall | c9e4ee9 | 2019-04-18 15:17:07 +0000 | [diff] [blame] | 320 | clangd::Diagnostic Main = FillBasicFields(D); | 
|  | 321 | Main.code = D.Name; | 
|  | 322 | switch (D.Source) { | 
|  | 323 | case Diag::Clang: | 
|  | 324 | Main.source = "clang"; | 
|  | 325 | break; | 
|  | 326 | case Diag::ClangTidy: | 
|  | 327 | Main.source = "clang-tidy"; | 
|  | 328 | break; | 
|  | 329 | case Diag::Unknown: | 
|  | 330 | break; | 
| Ilya Biryukov | 71028b8 | 2018-03-12 15:28:22 +0000 | [diff] [blame] | 331 | } | 
| Sam McCall | c9e4ee9 | 2019-04-18 15:17:07 +0000 | [diff] [blame] | 332 | if (Opts.EmbedFixesInDiagnostics) { | 
|  | 333 | Main.codeActions.emplace(); | 
|  | 334 | for (const auto &Fix : D.Fixes) | 
|  | 335 | Main.codeActions->push_back(toCodeAction(Fix, File)); | 
| Ilya Biryukov | 71028b8 | 2018-03-12 15:28:22 +0000 | [diff] [blame] | 336 | } | 
| Sam McCall | c9e4ee9 | 2019-04-18 15:17:07 +0000 | [diff] [blame] | 337 | if (Opts.SendDiagnosticCategory && !D.Category.empty()) | 
|  | 338 | Main.category = D.Category; | 
|  | 339 |  | 
|  | 340 | Main.message = mainMessage(D, Opts); | 
|  | 341 | if (Opts.EmitRelatedLocations) { | 
|  | 342 | Main.relatedInformation.emplace(); | 
|  | 343 | for (auto &Note : D.Notes) { | 
|  | 344 | if (!Note.AbsFile) { | 
|  | 345 | vlog("Dropping note from unknown file: {0}", Note); | 
|  | 346 | continue; | 
|  | 347 | } | 
|  | 348 | DiagnosticRelatedInformation RelInfo; | 
|  | 349 | RelInfo.location.range = Note.Range; | 
|  | 350 | RelInfo.location.uri = | 
|  | 351 | URIForFile::canonicalize(*Note.AbsFile, File.file()); | 
|  | 352 | RelInfo.message = noteMessage(D, Note, Opts); | 
|  | 353 | Main.relatedInformation->push_back(std::move(RelInfo)); | 
|  | 354 | } | 
|  | 355 | } | 
|  | 356 | OutFn(std::move(Main), D.Fixes); | 
|  | 357 |  | 
|  | 358 | // If we didn't emit the notes as relatedLocations, emit separate diagnostics | 
|  | 359 | // so the user can find the locations easily. | 
|  | 360 | if (!Opts.EmitRelatedLocations) | 
|  | 361 | for (auto &Note : D.Notes) { | 
|  | 362 | if (!Note.InsideMainFile) | 
|  | 363 | continue; | 
|  | 364 | clangd::Diagnostic Res = FillBasicFields(Note); | 
|  | 365 | Res.message = noteMessage(D, Note, Opts); | 
|  | 366 | OutFn(std::move(Res), llvm::ArrayRef<Fix>()); | 
|  | 367 | } | 
| Ilya Biryukov | 71028b8 | 2018-03-12 15:28:22 +0000 | [diff] [blame] | 368 | } | 
|  | 369 |  | 
|  | 370 | int getSeverity(DiagnosticsEngine::Level L) { | 
|  | 371 | switch (L) { | 
|  | 372 | case DiagnosticsEngine::Remark: | 
|  | 373 | return 4; | 
|  | 374 | case DiagnosticsEngine::Note: | 
|  | 375 | return 3; | 
|  | 376 | case DiagnosticsEngine::Warning: | 
|  | 377 | return 2; | 
|  | 378 | case DiagnosticsEngine::Fatal: | 
|  | 379 | case DiagnosticsEngine::Error: | 
|  | 380 | return 1; | 
|  | 381 | case DiagnosticsEngine::Ignored: | 
|  | 382 | return 0; | 
|  | 383 | } | 
|  | 384 | llvm_unreachable("Unknown diagnostic level!"); | 
|  | 385 | } | 
|  | 386 |  | 
| Sam McCall | 641caa5 | 2019-04-17 12:35:16 +0000 | [diff] [blame] | 387 | std::vector<Diag> StoreDiags::take(const clang::tidy::ClangTidyContext *Tidy) { | 
|  | 388 | // Fill in name/source now that we have all the context needed to map them. | 
|  | 389 | for (auto &Diag : Output) { | 
|  | 390 | if (const char *ClangDiag = getDiagnosticCode(Diag.ID)) { | 
| Sam McCall | d98170c | 2019-04-17 20:12:03 +0000 | [diff] [blame] | 391 | // Warnings controlled by -Wfoo are better recognized by that name. | 
|  | 392 | StringRef Warning = DiagnosticIDs::getWarningOptionForDiag(Diag.ID); | 
|  | 393 | if (!Warning.empty()) { | 
|  | 394 | Diag.Name = ("-W" + Warning).str(); | 
|  | 395 | } else { | 
|  | 396 | StringRef Name(ClangDiag); | 
|  | 397 | // Almost always an error, with a name like err_enum_class_reference. | 
|  | 398 | // Drop the err_ prefix for brevity. | 
|  | 399 | Name.consume_front("err_"); | 
|  | 400 | Diag.Name = Name; | 
|  | 401 | } | 
| Sam McCall | 641caa5 | 2019-04-17 12:35:16 +0000 | [diff] [blame] | 402 | Diag.Source = Diag::Clang; | 
|  | 403 | continue; | 
|  | 404 | } | 
|  | 405 | if (Tidy != nullptr) { | 
|  | 406 | std::string TidyDiag = Tidy->getCheckName(Diag.ID); | 
|  | 407 | if (!TidyDiag.empty()) { | 
|  | 408 | Diag.Name = std::move(TidyDiag); | 
|  | 409 | Diag.Source = Diag::ClangTidy; | 
| Sam McCall | aa4eb10 | 2019-04-17 20:15:08 +0000 | [diff] [blame] | 410 | // clang-tidy bakes the name into diagnostic messages. Strip it out. | 
|  | 411 | // It would be much nicer to make clang-tidy not do this. | 
|  | 412 | auto CleanMessage = [&](std::string &Msg) { | 
|  | 413 | StringRef Rest(Msg); | 
|  | 414 | if (Rest.consume_back("]") && Rest.consume_back(Diag.Name) && | 
|  | 415 | Rest.consume_back(" [")) | 
|  | 416 | Msg.resize(Rest.size()); | 
|  | 417 | }; | 
|  | 418 | CleanMessage(Diag.Message); | 
| Kadir Cetinkaya | 01efe64 | 2019-04-29 10:25:44 +0000 | [diff] [blame] | 419 | for (auto &Note : Diag.Notes) | 
| Sam McCall | aa4eb10 | 2019-04-17 20:15:08 +0000 | [diff] [blame] | 420 | CleanMessage(Note.Message); | 
| Haojian Wu | b739b91 | 2019-07-01 08:05:53 +0000 | [diff] [blame] | 421 | for (auto &Fix : Diag.Fixes) | 
|  | 422 | CleanMessage(Fix.Message); | 
| Sam McCall | 641caa5 | 2019-04-17 12:35:16 +0000 | [diff] [blame] | 423 | continue; | 
|  | 424 | } | 
|  | 425 | } | 
|  | 426 | } | 
| Haojian Wu | ee08036 | 2019-07-05 12:57:56 +0000 | [diff] [blame^] | 427 | // Deduplicate clang-tidy diagnostics -- some clang-tidy checks may emit | 
|  | 428 | // duplicated messages due to various reasons (e.g. the check doesn't handle | 
|  | 429 | // template instantiations well; clang-tidy alias checks). | 
|  | 430 | std::set<std::pair<Range, std::string>> SeenDiags; | 
|  | 431 | llvm::erase_if(Output, [&](const Diag& D) { | 
|  | 432 | return !SeenDiags.emplace(D.Range, D.Message).second; | 
|  | 433 | }); | 
| Sam McCall | 641caa5 | 2019-04-17 12:35:16 +0000 | [diff] [blame] | 434 | return std::move(Output); | 
|  | 435 | } | 
| Ilya Biryukov | 71028b8 | 2018-03-12 15:28:22 +0000 | [diff] [blame] | 436 |  | 
|  | 437 | void StoreDiags::BeginSourceFile(const LangOptions &Opts, | 
|  | 438 | const Preprocessor *) { | 
|  | 439 | LangOpts = Opts; | 
|  | 440 | } | 
|  | 441 |  | 
|  | 442 | void StoreDiags::EndSourceFile() { | 
|  | 443 | flushLastDiag(); | 
| Sam McCall | c008af6 | 2018-10-20 15:30:37 +0000 | [diff] [blame] | 444 | LangOpts = None; | 
| Ilya Biryukov | 71028b8 | 2018-03-12 15:28:22 +0000 | [diff] [blame] | 445 | } | 
|  | 446 |  | 
| Ilya Biryukov | 0f748e6 | 2019-05-24 10:26:23 +0000 | [diff] [blame] | 447 | /// Sanitizes a piece for presenting it in a synthesized fix message. Ensures | 
|  | 448 | /// the result is not too large and does not contain newlines. | 
|  | 449 | static void writeCodeToFixMessage(llvm::raw_ostream &OS, llvm::StringRef Code) { | 
|  | 450 | constexpr unsigned MaxLen = 50; | 
|  | 451 |  | 
|  | 452 | // Only show the first line if there are many. | 
|  | 453 | llvm::StringRef R = Code.split('\n').first; | 
|  | 454 | // Shorten the message if it's too long. | 
|  | 455 | R = R.take_front(MaxLen); | 
|  | 456 |  | 
|  | 457 | OS << R; | 
|  | 458 | if (R.size() != Code.size()) | 
|  | 459 | OS << "…"; | 
|  | 460 | } | 
|  | 461 |  | 
| Ilya Biryukov | 71028b8 | 2018-03-12 15:28:22 +0000 | [diff] [blame] | 462 | void StoreDiags::HandleDiagnostic(DiagnosticsEngine::Level DiagLevel, | 
|  | 463 | const clang::Diagnostic &Info) { | 
|  | 464 | DiagnosticConsumer::HandleDiagnostic(DiagLevel, Info); | 
|  | 465 |  | 
|  | 466 | if (!LangOpts || !Info.hasSourceManager()) { | 
|  | 467 | IgnoreDiagnostics::log(DiagLevel, Info); | 
|  | 468 | return; | 
|  | 469 | } | 
|  | 470 |  | 
|  | 471 | bool InsideMainFile = isInsideMainFile(Info); | 
|  | 472 |  | 
|  | 473 | auto FillDiagBase = [&](DiagBase &D) { | 
|  | 474 | D.Range = diagnosticRange(Info, *LangOpts); | 
| Ilya Biryukov | f2001aa | 2019-01-07 15:45:19 +0000 | [diff] [blame] | 475 | llvm::SmallString<64> Message; | 
| Ilya Biryukov | 71028b8 | 2018-03-12 15:28:22 +0000 | [diff] [blame] | 476 | Info.FormatDiagnostic(Message); | 
|  | 477 | D.Message = Message.str(); | 
|  | 478 | D.InsideMainFile = InsideMainFile; | 
|  | 479 | D.File = Info.getSourceManager().getFilename(Info.getLocation()); | 
| Sam McCall | c9e4ee9 | 2019-04-18 15:17:07 +0000 | [diff] [blame] | 480 | auto &SM = Info.getSourceManager(); | 
|  | 481 | D.AbsFile = getCanonicalPath( | 
|  | 482 | SM.getFileEntryForID(SM.getFileID(Info.getLocation())), SM); | 
| Ilya Biryukov | 71028b8 | 2018-03-12 15:28:22 +0000 | [diff] [blame] | 483 | D.Severity = DiagLevel; | 
| Alex Lorenz | 3714643 | 2018-08-14 22:21:40 +0000 | [diff] [blame] | 484 | D.Category = DiagnosticIDs::getCategoryNameFromID( | 
|  | 485 | DiagnosticIDs::getCategoryNumberForDiag(Info.getID())) | 
|  | 486 | .str(); | 
| Ilya Biryukov | 71028b8 | 2018-03-12 15:28:22 +0000 | [diff] [blame] | 487 | return D; | 
|  | 488 | }; | 
|  | 489 |  | 
| Sam McCall | ba3c02e | 2018-04-03 17:35:57 +0000 | [diff] [blame] | 490 | auto AddFix = [&](bool SyntheticMessage) -> bool { | 
| Ilya Biryukov | 71028b8 | 2018-03-12 15:28:22 +0000 | [diff] [blame] | 491 | assert(!Info.getFixItHints().empty() && | 
|  | 492 | "diagnostic does not have attached fix-its"); | 
|  | 493 | if (!InsideMainFile) | 
|  | 494 | return false; | 
|  | 495 |  | 
| Ilya Biryukov | f2001aa | 2019-01-07 15:45:19 +0000 | [diff] [blame] | 496 | llvm::SmallVector<TextEdit, 1> Edits; | 
| Ilya Biryukov | 71028b8 | 2018-03-12 15:28:22 +0000 | [diff] [blame] | 497 | for (auto &FixIt : Info.getFixItHints()) { | 
| Haojian Wu | c8f7496 | 2019-02-22 09:43:56 +0000 | [diff] [blame] | 498 | // Follow clang's behavior, don't apply FixIt to the code in macros, | 
|  | 499 | // we are less certain it is the right fix. | 
|  | 500 | if (FixIt.RemoveRange.getBegin().isMacroID() || | 
|  | 501 | FixIt.RemoveRange.getEnd().isMacroID()) | 
|  | 502 | return false; | 
| Ilya Biryukov | 71028b8 | 2018-03-12 15:28:22 +0000 | [diff] [blame] | 503 | if (!isInsideMainFile(FixIt.RemoveRange.getBegin(), | 
|  | 504 | Info.getSourceManager())) | 
|  | 505 | return false; | 
|  | 506 | Edits.push_back(toTextEdit(FixIt, Info.getSourceManager(), *LangOpts)); | 
|  | 507 | } | 
|  | 508 |  | 
| Ilya Biryukov | f2001aa | 2019-01-07 15:45:19 +0000 | [diff] [blame] | 509 | llvm::SmallString<64> Message; | 
| Sam McCall | ba3c02e | 2018-04-03 17:35:57 +0000 | [diff] [blame] | 510 | // If requested and possible, create a message like "change 'foo' to 'bar'". | 
|  | 511 | if (SyntheticMessage && Info.getNumFixItHints() == 1) { | 
|  | 512 | const auto &FixIt = Info.getFixItHint(0); | 
|  | 513 | bool Invalid = false; | 
| Ilya Biryukov | f2001aa | 2019-01-07 15:45:19 +0000 | [diff] [blame] | 514 | llvm::StringRef Remove = Lexer::getSourceText( | 
| Sam McCall | ba3c02e | 2018-04-03 17:35:57 +0000 | [diff] [blame] | 515 | FixIt.RemoveRange, Info.getSourceManager(), *LangOpts, &Invalid); | 
| Ilya Biryukov | f2001aa | 2019-01-07 15:45:19 +0000 | [diff] [blame] | 516 | llvm::StringRef Insert = FixIt.CodeToInsert; | 
| Sam McCall | ba3c02e | 2018-04-03 17:35:57 +0000 | [diff] [blame] | 517 | if (!Invalid) { | 
| Ilya Biryukov | f2001aa | 2019-01-07 15:45:19 +0000 | [diff] [blame] | 518 | llvm::raw_svector_ostream M(Message); | 
| Ilya Biryukov | 0f748e6 | 2019-05-24 10:26:23 +0000 | [diff] [blame] | 519 | if (!Remove.empty() && !Insert.empty()) { | 
|  | 520 | M << "change '"; | 
|  | 521 | writeCodeToFixMessage(M, Remove); | 
|  | 522 | M << "' to '"; | 
|  | 523 | writeCodeToFixMessage(M, Insert); | 
|  | 524 | M << "'"; | 
|  | 525 | } else if (!Remove.empty()) { | 
|  | 526 | M << "remove '"; | 
|  | 527 | writeCodeToFixMessage(M, Remove); | 
|  | 528 | M << "'"; | 
|  | 529 | } else if (!Insert.empty()) { | 
|  | 530 | M << "insert '"; | 
|  | 531 | writeCodeToFixMessage(M, Insert); | 
|  | 532 | M << "'"; | 
|  | 533 | } | 
| Sam McCall | ba3c02e | 2018-04-03 17:35:57 +0000 | [diff] [blame] | 534 | // Don't allow source code to inject newlines into diagnostics. | 
|  | 535 | std::replace(Message.begin(), Message.end(), '\n', ' '); | 
|  | 536 | } | 
|  | 537 | } | 
|  | 538 | if (Message.empty()) // either !SytheticMessage, or we failed to make one. | 
|  | 539 | Info.FormatDiagnostic(Message); | 
| Ilya Biryukov | 71028b8 | 2018-03-12 15:28:22 +0000 | [diff] [blame] | 540 | LastDiag->Fixes.push_back(Fix{Message.str(), std::move(Edits)}); | 
|  | 541 | return true; | 
|  | 542 | }; | 
|  | 543 |  | 
|  | 544 | if (!isNote(DiagLevel)) { | 
|  | 545 | // Handle the new main diagnostic. | 
|  | 546 | flushLastDiag(); | 
|  | 547 |  | 
| Fangrui Song | cb4b3e5 | 2019-05-19 04:19:14 +0000 | [diff] [blame] | 548 | if (Adjuster) { | 
|  | 549 | DiagLevel = Adjuster(DiagLevel, Info); | 
|  | 550 | if (DiagLevel == DiagnosticsEngine::Ignored) { | 
|  | 551 | LastPrimaryDiagnosticWasSuppressed = true; | 
|  | 552 | return; | 
|  | 553 | } | 
| Fangrui Song | c2aded5 | 2019-05-19 04:06:52 +0000 | [diff] [blame] | 554 | } | 
|  | 555 | LastPrimaryDiagnosticWasSuppressed = false; | 
|  | 556 |  | 
| Ilya Biryukov | 71028b8 | 2018-03-12 15:28:22 +0000 | [diff] [blame] | 557 | LastDiag = Diag(); | 
| Haojian Wu | 24a8f1c | 2019-03-06 10:51:38 +0000 | [diff] [blame] | 558 | LastDiag->ID = Info.getID(); | 
| Ilya Biryukov | 71028b8 | 2018-03-12 15:28:22 +0000 | [diff] [blame] | 559 | FillDiagBase(*LastDiag); | 
| Kadir Cetinkaya | 01efe64 | 2019-04-29 10:25:44 +0000 | [diff] [blame] | 560 | adjustDiagFromHeader(*LastDiag, Info, *LangOpts); | 
| Ilya Biryukov | 71028b8 | 2018-03-12 15:28:22 +0000 | [diff] [blame] | 561 |  | 
|  | 562 | if (!Info.getFixItHints().empty()) | 
| Sam McCall | ba3c02e | 2018-04-03 17:35:57 +0000 | [diff] [blame] | 563 | AddFix(true /* try to invent a message instead of repeating the diag */); | 
| Eric Liu | dd66277 | 2019-01-28 14:01:55 +0000 | [diff] [blame] | 564 | if (Fixer) { | 
|  | 565 | auto ExtraFixes = Fixer(DiagLevel, Info); | 
|  | 566 | LastDiag->Fixes.insert(LastDiag->Fixes.end(), ExtraFixes.begin(), | 
|  | 567 | ExtraFixes.end()); | 
|  | 568 | } | 
| Ilya Biryukov | 71028b8 | 2018-03-12 15:28:22 +0000 | [diff] [blame] | 569 | } else { | 
|  | 570 | // Handle a note to an existing diagnostic. | 
| Fangrui Song | c2aded5 | 2019-05-19 04:06:52 +0000 | [diff] [blame] | 571 |  | 
|  | 572 | // If a diagnostic was suppressed due to the suppression filter, | 
|  | 573 | // also suppress notes associated with it. | 
|  | 574 | if (LastPrimaryDiagnosticWasSuppressed) { | 
|  | 575 | return; | 
|  | 576 | } | 
|  | 577 |  | 
| Ilya Biryukov | 71028b8 | 2018-03-12 15:28:22 +0000 | [diff] [blame] | 578 | if (!LastDiag) { | 
|  | 579 | assert(false && "Adding a note without main diagnostic"); | 
|  | 580 | IgnoreDiagnostics::log(DiagLevel, Info); | 
|  | 581 | return; | 
|  | 582 | } | 
|  | 583 |  | 
|  | 584 | if (!Info.getFixItHints().empty()) { | 
|  | 585 | // A clang note with fix-it is not a separate diagnostic in clangd. We | 
|  | 586 | // attach it as a Fix to the main diagnostic instead. | 
| Sam McCall | ba3c02e | 2018-04-03 17:35:57 +0000 | [diff] [blame] | 587 | if (!AddFix(false /* use the note as the message */)) | 
| Ilya Biryukov | 71028b8 | 2018-03-12 15:28:22 +0000 | [diff] [blame] | 588 | IgnoreDiagnostics::log(DiagLevel, Info); | 
|  | 589 | } else { | 
|  | 590 | // A clang note without fix-its corresponds to clangd::Note. | 
|  | 591 | Note N; | 
|  | 592 | FillDiagBase(N); | 
|  | 593 |  | 
|  | 594 | LastDiag->Notes.push_back(std::move(N)); | 
|  | 595 | } | 
|  | 596 | } | 
|  | 597 | } | 
|  | 598 |  | 
|  | 599 | void StoreDiags::flushLastDiag() { | 
|  | 600 | if (!LastDiag) | 
|  | 601 | return; | 
| Kadir Cetinkaya | 01efe64 | 2019-04-29 10:25:44 +0000 | [diff] [blame] | 602 | // Only keeps diagnostics inside main file or the first one coming from a | 
|  | 603 | // header. | 
|  | 604 | if (mentionsMainFile(*LastDiag) || | 
|  | 605 | (LastDiag->Severity >= DiagnosticsEngine::Level::Error && | 
|  | 606 | IncludeLinesWithErrors.insert(LastDiag->Range.start.line).second)) { | 
| Ilya Biryukov | 71028b8 | 2018-03-12 15:28:22 +0000 | [diff] [blame] | 607 | Output.push_back(std::move(*LastDiag)); | 
| Kadir Cetinkaya | 01efe64 | 2019-04-29 10:25:44 +0000 | [diff] [blame] | 608 | } else { | 
|  | 609 | vlog("Dropped diagnostic: {0}: {1}", LastDiag->File, LastDiag->Message); | 
|  | 610 | } | 
| Ilya Biryukov | 71028b8 | 2018-03-12 15:28:22 +0000 | [diff] [blame] | 611 | LastDiag.reset(); | 
|  | 612 | } | 
|  | 613 |  | 
|  | 614 | } // namespace clangd | 
|  | 615 | } // namespace clang |