1 | //===--- Diagnostics.h -------------------------------------------*- C++-*-===// |
2 | // |
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 |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | |
9 | #ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_DIAGNOSTICS_H |
10 | #define |
11 | |
12 | #include "Protocol.h" |
13 | #include "clang/Basic/Diagnostic.h" |
14 | #include "clang/Basic/LangOptions.h" |
15 | #include "clang/Basic/SourceLocation.h" |
16 | #include "llvm/ADT/ArrayRef.h" |
17 | #include "llvm/ADT/DenseSet.h" |
18 | #include "llvm/ADT/SmallVector.h" |
19 | #include "llvm/ADT/StringSet.h" |
20 | #include "llvm/Support/JSON.h" |
21 | #include "llvm/Support/SourceMgr.h" |
22 | #include <cassert> |
23 | #include <functional> |
24 | #include <memory> |
25 | #include <optional> |
26 | #include <string> |
27 | #include <utility> |
28 | #include <vector> |
29 | |
30 | namespace clang { |
31 | namespace tidy { |
32 | class ClangTidyContext; |
33 | } // namespace tidy |
34 | namespace clangd { |
35 | |
36 | struct ClangdDiagnosticOptions { |
37 | /// If true, Clangd uses an LSP extension to embed the fixes with the |
38 | /// diagnostics that are sent to the client. |
39 | bool EmbedFixesInDiagnostics = false; |
40 | |
41 | /// If true, Clangd uses the relatedInformation field to include other |
42 | /// locations (in particular attached notes). |
43 | /// Otherwise, these are flattened into the diagnostic message. |
44 | bool EmitRelatedLocations = false; |
45 | |
46 | /// If true, Clangd uses an LSP extension to send the diagnostic's |
47 | /// category to the client. The category typically describes the compilation |
48 | /// stage during which the issue was produced, e.g. "Semantic Issue" or "Parse |
49 | /// Issue". |
50 | bool SendDiagnosticCategory = false; |
51 | |
52 | /// If true, Clangd will add a number of available fixes to the diagnostic's |
53 | /// message. |
54 | bool DisplayFixesCount = true; |
55 | }; |
56 | |
57 | /// Contains basic information about a diagnostic. |
58 | struct DiagBase { |
59 | std::string Message; |
60 | // Intended to be used only in error messages. |
61 | // May be relative, absolute or even artificially constructed. |
62 | std::string File; |
63 | // Absolute path to containing file, if available. |
64 | std::optional<std::string> AbsFile; |
65 | |
66 | clangd::Range Range; |
67 | DiagnosticsEngine::Level Severity = DiagnosticsEngine::Note; |
68 | std::string Category; |
69 | // Since File is only descriptive, we store a separate flag to distinguish |
70 | // diags from the main file. |
71 | bool InsideMainFile = false; |
72 | unsigned ID = 0; // e.g. member of clang::diag, or clang-tidy assigned ID. |
73 | // Feature modules can make use of this field to propagate data from a |
74 | // diagnostic to a CodeAction request. Each module should only append to the |
75 | // list. |
76 | llvm::json::Object OpaqueData; |
77 | }; |
78 | llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const DiagBase &D); |
79 | |
80 | /// Represents a single fix-it that editor can apply to fix the error. |
81 | struct Fix { |
82 | /// Message for the fix-it. |
83 | std::string Message; |
84 | /// TextEdits from clang's fix-its. Must be non-empty. |
85 | llvm::SmallVector<TextEdit, 1> Edits; |
86 | |
87 | /// Annotations for the Edits. |
88 | llvm::SmallVector<std::pair<ChangeAnnotationIdentifier, ChangeAnnotation>> |
89 | Annotations; |
90 | }; |
91 | llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const Fix &F); |
92 | |
93 | /// Represents a note for the diagnostic. Severity of notes can only be 'note' |
94 | /// or 'remark'. |
95 | struct Note : DiagBase {}; |
96 | |
97 | /// A top-level diagnostic that may have Notes and Fixes. |
98 | struct Diag : DiagBase { |
99 | std::string Name; // if ID was recognized. |
100 | // The source of this diagnostic. |
101 | enum DiagSource { |
102 | Unknown, |
103 | Clang, |
104 | ClangTidy, |
105 | Clangd, |
106 | ClangdConfig, |
107 | } Source = Unknown; |
108 | /// Elaborate on the problem, usually pointing to a related piece of code. |
109 | std::vector<Note> Notes; |
110 | /// *Alternative* fixes for this diagnostic, one should be chosen. |
111 | std::vector<Fix> Fixes; |
112 | llvm::SmallVector<DiagnosticTag, 1> Tags; |
113 | }; |
114 | llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const Diag &D); |
115 | |
116 | Diag toDiag(const llvm::SMDiagnostic &, Diag::DiagSource Source); |
117 | |
118 | /// Conversion to LSP diagnostics. Formats the error message of each diagnostic |
119 | /// to include all its notes. Notes inside main file are also provided as |
120 | /// separate diagnostics with their corresponding fixits. Notes outside main |
121 | /// file do not have a corresponding LSP diagnostic, but can still be included |
122 | /// as part of their main diagnostic's message. |
123 | void toLSPDiags( |
124 | const Diag &D, const URIForFile &File, const ClangdDiagnosticOptions &Opts, |
125 | llvm::function_ref<void(clangd::Diagnostic, llvm::ArrayRef<Fix>)> OutFn); |
126 | |
127 | /// Convert from clang diagnostic level to LSP severity. |
128 | int getSeverity(DiagnosticsEngine::Level L); |
129 | |
130 | /// Returns a URI providing more information about a particular diagnostic. |
131 | std::optional<std::string> getDiagnosticDocURI(Diag::DiagSource, unsigned ID, |
132 | llvm::StringRef Name); |
133 | |
134 | /// StoreDiags collects the diagnostics that can later be reported by |
135 | /// clangd. It groups all notes for a diagnostic into a single Diag |
136 | /// and filters out diagnostics that don't mention the main file (i.e. neither |
137 | /// the diag itself nor its notes are in the main file). |
138 | class StoreDiags : public DiagnosticConsumer { |
139 | public: |
140 | // The ClangTidyContext populates Source and Name for clang-tidy diagnostics. |
141 | std::vector<Diag> take(const clang::tidy::ClangTidyContext *Tidy = nullptr); |
142 | |
143 | void BeginSourceFile(const LangOptions &Opts, |
144 | const Preprocessor *PP) override; |
145 | void EndSourceFile() override; |
146 | void HandleDiagnostic(DiagnosticsEngine::Level DiagLevel, |
147 | const clang::Diagnostic &Info) override; |
148 | |
149 | /// When passed a main diagnostic, returns fixes to add to it. |
150 | /// When passed a note diagnostic, returns fixes to replace it with. |
151 | using DiagFixer = std::function<std::vector<Fix>(DiagnosticsEngine::Level, |
152 | const clang::Diagnostic &)>; |
153 | using LevelAdjuster = std::function<DiagnosticsEngine::Level( |
154 | DiagnosticsEngine::Level, const clang::Diagnostic &)>; |
155 | using DiagCallback = |
156 | std::function<void(const clang::Diagnostic &, clangd::Diag &)>; |
157 | /// If set, possibly adds fixes for diagnostics using \p Fixer. |
158 | void contributeFixes(DiagFixer Fixer) { this->Fixer = Fixer; } |
159 | /// If set, this allows the client of this class to adjust the level of |
160 | /// diagnostics, such as promoting warnings to errors, or ignoring |
161 | /// diagnostics. |
162 | void setLevelAdjuster(LevelAdjuster Adjuster) { this->Adjuster = Adjuster; } |
163 | /// Invokes a callback every time a diagnostics is completely formed. Handler |
164 | /// of the callback can also mutate the diagnostic. |
165 | void setDiagCallback(DiagCallback CB) { DiagCB = std::move(CB); } |
166 | |
167 | private: |
168 | void flushLastDiag(); |
169 | |
170 | DiagFixer Fixer = nullptr; |
171 | LevelAdjuster Adjuster = nullptr; |
172 | DiagCallback DiagCB = nullptr; |
173 | std::vector<Diag> Output; |
174 | std::optional<LangOptions> LangOpts; |
175 | std::optional<Diag> LastDiag; |
176 | std::optional<FullSourceLoc> LastDiagLoc; // Valid only when LastDiag is set. |
177 | bool LastDiagOriginallyError = false; // Valid only when LastDiag is set. |
178 | SourceManager *OrigSrcMgr = nullptr; |
179 | |
180 | llvm::DenseSet<std::pair<unsigned, unsigned>> IncludedErrorLocations; |
181 | }; |
182 | |
183 | /// Determine whether a (non-clang-tidy) diagnostic is suppressed by config. |
184 | bool isBuiltinDiagnosticSuppressed(unsigned ID, |
185 | const llvm::StringSet<> &Suppressed, |
186 | const LangOptions &); |
187 | /// Take a user-specified diagnostic code, and convert it to a normalized form |
188 | /// stored in the config and consumed by isBuiltinDiagnosticsSuppressed. |
189 | /// |
190 | /// (This strips err_ and -W prefix so we can match with or without them.) |
191 | llvm::StringRef normalizeSuppressedCode(llvm::StringRef); |
192 | |
193 | } // namespace clangd |
194 | } // namespace clang |
195 | |
196 | #endif // LLVM_CLANG_TOOLS_EXTRA_CLANGD_DIAGNOSTICS_H |
197 | |