1//===--- ClangTidyDiagnosticConsumer.h - clang-tidy -------------*- 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_CLANG_TIDY_CLANGTIDYDIAGNOSTICCONSUMER_H
10#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CLANGTIDYDIAGNOSTICCONSUMER_H
11
12#include "ClangTidyOptions.h"
13#include "ClangTidyProfiling.h"
14#include "FileExtensionsSet.h"
15#include "NoLintDirectiveHandler.h"
16#include "clang/Basic/Diagnostic.h"
17#include "clang/Tooling/Core/Diagnostic.h"
18#include "llvm/ADT/DenseMap.h"
19#include "llvm/ADT/StringSet.h"
20#include "llvm/Support/Regex.h"
21#include <optional>
22
23namespace clang {
24
25class ASTContext;
26class SourceManager;
27
28namespace tidy {
29class CachedGlobList;
30
31/// A detected error complete with information to display diagnostic and
32/// automatic fix.
33///
34/// This is used as an intermediate format to transport Diagnostics without a
35/// dependency on a SourceManager.
36///
37/// FIXME: Make Diagnostics flexible enough to support this directly.
38struct ClangTidyError : tooling::Diagnostic {
39 ClangTidyError(StringRef CheckName, Level DiagLevel, StringRef BuildDirectory,
40 bool IsWarningAsError);
41
42 bool IsWarningAsError;
43 std::vector<std::string> EnabledDiagnosticAliases;
44};
45
46/// Contains displayed and ignored diagnostic counters for a ClangTidy run.
47struct ClangTidyStats {
48 unsigned ErrorsDisplayed = 0;
49 unsigned ErrorsIgnoredCheckFilter = 0;
50 unsigned ErrorsIgnoredNOLINT = 0;
51 unsigned ErrorsIgnoredNonUserCode = 0;
52 unsigned ErrorsIgnoredLineFilter = 0;
53
54 unsigned errorsIgnored() const {
55 return ErrorsIgnoredNOLINT + ErrorsIgnoredCheckFilter +
56 ErrorsIgnoredNonUserCode + ErrorsIgnoredLineFilter;
57 }
58};
59
60/// Every \c ClangTidyCheck reports errors through a \c DiagnosticsEngine
61/// provided by this context.
62///
63/// A \c ClangTidyCheck always has access to the active context to report
64/// warnings like:
65/// \code
66/// Context->Diag(Loc, "Single-argument constructors must be explicit")
67/// << FixItHint::CreateInsertion(Loc, "explicit ");
68/// \endcode
69class ClangTidyContext {
70public:
71 /// Initializes \c ClangTidyContext instance.
72 ClangTidyContext(std::unique_ptr<ClangTidyOptionsProvider> OptionsProvider,
73 bool AllowEnablingAnalyzerAlphaCheckers = false,
74 bool EnableModuleHeadersParsing = false);
75 /// Sets the DiagnosticsEngine that diag() will emit diagnostics to.
76 // FIXME: this is required initialization, and should be a constructor param.
77 // Fix the context -> diag engine -> consumer -> context initialization cycle.
78 void setDiagnosticsEngine(DiagnosticsEngine *DiagEngine) {
79 this->DiagEngine = DiagEngine;
80 }
81
82 ~ClangTidyContext();
83
84 ClangTidyContext(const ClangTidyContext &) = delete;
85 ClangTidyContext &operator=(const ClangTidyContext &) = delete;
86
87 /// Report any errors detected using this method.
88 ///
89 /// This is still under heavy development and will likely change towards using
90 /// tablegen'd diagnostic IDs.
91 /// FIXME: Figure out a way to manage ID spaces.
92 DiagnosticBuilder diag(StringRef CheckName, SourceLocation Loc,
93 StringRef Description,
94 DiagnosticIDs::Level Level = DiagnosticIDs::Warning);
95
96 DiagnosticBuilder diag(StringRef CheckName, StringRef Description,
97 DiagnosticIDs::Level Level = DiagnosticIDs::Warning);
98
99 DiagnosticBuilder diag(const tooling::Diagnostic &Error);
100
101 /// Report any errors to do with reading the configuration using this method.
102 DiagnosticBuilder
103 configurationDiag(StringRef Message,
104 DiagnosticIDs::Level Level = DiagnosticIDs::Warning);
105
106 /// Check whether a given diagnostic should be suppressed due to the presence
107 /// of a "NOLINT" suppression comment.
108 /// This is exposed so that other tools that present clang-tidy diagnostics
109 /// (such as clangd) can respect the same suppression rules as clang-tidy.
110 /// This does not handle suppression of notes following a suppressed
111 /// diagnostic; that is left to the caller as it requires maintaining state in
112 /// between calls to this function.
113 /// If any NOLINT is malformed, e.g. a BEGIN without a subsequent END, output
114 /// \param NoLintErrors will return an error about it.
115 /// If \param AllowIO is false, the function does not attempt to read source
116 /// files from disk which are not already mapped into memory; such files are
117 /// treated as not containing a suppression comment.
118 /// \param EnableNoLintBlocks controls whether to honor NOLINTBEGIN/NOLINTEND
119 /// blocks; if false, only considers line-level disabling.
120 bool
121 shouldSuppressDiagnostic(DiagnosticsEngine::Level DiagLevel,
122 const Diagnostic &Info,
123 SmallVectorImpl<tooling::Diagnostic> &NoLintErrors,
124 bool AllowIO = true, bool EnableNoLintBlocks = true);
125
126 /// Sets the \c SourceManager of the used \c DiagnosticsEngine.
127 ///
128 /// This is called from the \c ClangTidyCheck base class.
129 void setSourceManager(SourceManager *SourceMgr);
130
131 /// Should be called when starting to process new translation unit.
132 void setCurrentFile(StringRef File);
133
134 /// Returns the main file name of the current translation unit.
135 StringRef getCurrentFile() const { return CurrentFile; }
136
137 /// Sets ASTContext for the current translation unit.
138 void setASTContext(ASTContext *Context);
139
140 /// Gets the language options from the AST context.
141 const LangOptions &getLangOpts() const { return LangOpts; }
142
143 /// Returns the name of the clang-tidy check which produced this
144 /// diagnostic ID.
145 std::string getCheckName(unsigned DiagnosticID) const;
146
147 /// Returns \c true if the check is enabled for the \c CurrentFile.
148 ///
149 /// The \c CurrentFile can be changed using \c setCurrentFile.
150 bool isCheckEnabled(StringRef CheckName) const;
151
152 /// Returns \c true if the check should be upgraded to error for the
153 /// \c CurrentFile.
154 bool treatAsError(StringRef CheckName) const;
155
156 /// Returns global options.
157 const ClangTidyGlobalOptions &getGlobalOptions() const;
158
159 /// Returns options for \c CurrentFile.
160 ///
161 /// The \c CurrentFile can be changed using \c setCurrentFile.
162 const ClangTidyOptions &getOptions() const;
163
164 /// Returns options for \c File. Does not change or depend on
165 /// \c CurrentFile.
166 ClangTidyOptions getOptionsForFile(StringRef File) const;
167
168 const FileExtensionsSet &getHeaderFileExtensions() const {
169 return HeaderFileExtensions;
170 }
171
172 const FileExtensionsSet &getImplementationFileExtensions() const {
173 return ImplementationFileExtensions;
174 }
175
176 /// Returns \c ClangTidyStats containing issued and ignored diagnostic
177 /// counters.
178 const ClangTidyStats &getStats() const { return Stats; }
179
180 /// Control profile collection in clang-tidy.
181 void setEnableProfiling(bool Profile);
182 bool getEnableProfiling() const { return Profile; }
183
184 /// Control storage of profile date.
185 void setProfileStoragePrefix(StringRef ProfilePrefix);
186 std::optional<ClangTidyProfiling::StorageParams>
187 getProfileStorageParams() const;
188
189 /// Should be called when starting to process new translation unit.
190 void setCurrentBuildDirectory(StringRef BuildDirectory) {
191 CurrentBuildDirectory = std::string(BuildDirectory);
192 }
193
194 /// Returns build directory of the current translation unit.
195 const std::string &getCurrentBuildDirectory() const {
196 return CurrentBuildDirectory;
197 }
198
199 /// If the experimental alpha checkers from the static analyzer can be
200 /// enabled.
201 bool canEnableAnalyzerAlphaCheckers() const {
202 return AllowEnablingAnalyzerAlphaCheckers;
203 }
204
205 // This method determines whether preprocessor-level module header parsing is
206 // enabled using the `--experimental-enable-module-headers-parsing` option.
207 bool canEnableModuleHeadersParsing() const {
208 return EnableModuleHeadersParsing;
209 }
210
211 void setSelfContainedDiags(bool Value) { SelfContainedDiags = Value; }
212
213 bool areDiagsSelfContained() const { return SelfContainedDiags; }
214
215 using DiagLevelAndFormatString = std::pair<DiagnosticIDs::Level, std::string>;
216 DiagLevelAndFormatString getDiagLevelAndFormatString(unsigned DiagnosticID,
217 SourceLocation Loc) {
218 return {
219 static_cast<DiagnosticIDs::Level>(
220 DiagEngine->getDiagnosticLevel(DiagID: DiagnosticID, Loc)),
221 std::string(
222 DiagEngine->getDiagnosticIDs()->getDescription(DiagID: DiagnosticID))};
223 }
224
225 void setOptionsCollector(llvm::StringSet<> *Collector) {
226 OptionsCollector = Collector;
227 }
228 llvm::StringSet<> *getOptionsCollector() const { return OptionsCollector; }
229
230private:
231 // Writes to Stats.
232 friend class ClangTidyDiagnosticConsumer;
233
234 DiagnosticsEngine *DiagEngine = nullptr;
235 std::unique_ptr<ClangTidyOptionsProvider> OptionsProvider;
236
237 std::string CurrentFile;
238 ClangTidyOptions CurrentOptions;
239
240 std::unique_ptr<CachedGlobList> CheckFilter;
241 std::unique_ptr<CachedGlobList> WarningAsErrorFilter;
242
243 FileExtensionsSet HeaderFileExtensions;
244 FileExtensionsSet ImplementationFileExtensions;
245
246 LangOptions LangOpts;
247
248 ClangTidyStats Stats;
249
250 std::string CurrentBuildDirectory;
251
252 llvm::DenseMap<unsigned, std::string> CheckNamesByDiagnosticID;
253
254 bool Profile = false;
255 std::string ProfilePrefix;
256
257 bool AllowEnablingAnalyzerAlphaCheckers;
258 bool EnableModuleHeadersParsing;
259
260 bool SelfContainedDiags = false;
261
262 NoLintDirectiveHandler NoLintHandler;
263 llvm::StringSet<> *OptionsCollector = nullptr;
264};
265
266/// Gets the Fix attached to \p Diagnostic.
267/// If there isn't a Fix attached to the diagnostic and \p AnyFix is true, Check
268/// to see if exactly one note has a Fix and return it. Otherwise return
269/// nullptr.
270const llvm::StringMap<tooling::Replacements> *
271getFixIt(const tooling::Diagnostic &Diagnostic, bool AnyFix);
272
273/// A diagnostic consumer that turns each \c Diagnostic into a
274/// \c SourceManager-independent \c ClangTidyError.
275// FIXME: If we move away from unit-tests, this can be moved to a private
276// implementation file.
277class ClangTidyDiagnosticConsumer : public DiagnosticConsumer {
278public:
279 /// \param EnableNolintBlocks Enables diagnostic-disabling inside blocks of
280 /// code, delimited by NOLINTBEGIN and NOLINTEND.
281 ClangTidyDiagnosticConsumer(ClangTidyContext &Ctx,
282 DiagnosticsEngine *ExternalDiagEngine = nullptr,
283 bool RemoveIncompatibleErrors = true,
284 bool GetFixesFromNotes = false,
285 bool EnableNolintBlocks = true);
286
287 // FIXME: The concept of converting between FixItHints and Replacements is
288 // more generic and should be pulled out into a more useful Diagnostics
289 // library.
290 void HandleDiagnostic(DiagnosticsEngine::Level DiagLevel,
291 const Diagnostic &Info) override;
292
293 // Retrieve the diagnostics that were captured.
294 std::vector<ClangTidyError> take();
295
296private:
297 void finalizeLastError();
298 void removeIncompatibleErrors();
299 void removeDuplicatedDiagnosticsOfAliasCheckers();
300
301 /// Returns the \c HeaderFilter constructed for the options set in the
302 /// context.
303 llvm::Regex *getHeaderFilter();
304
305 /// Updates \c LastErrorRelatesToUserCode and LastErrorPassesLineFilter
306 /// according to the diagnostic \p Location.
307 void checkFilters(SourceLocation Location, const SourceManager &Sources);
308 bool passesLineFilter(StringRef FileName, unsigned LineNumber) const;
309
310 void forwardDiagnostic(const Diagnostic &Info);
311
312 ClangTidyContext &Context;
313 DiagnosticsEngine *ExternalDiagEngine;
314 bool RemoveIncompatibleErrors;
315 bool GetFixesFromNotes;
316 bool EnableNolintBlocks;
317 std::vector<ClangTidyError> Errors;
318 std::unique_ptr<llvm::Regex> HeaderFilter;
319 std::unique_ptr<llvm::Regex> ExcludeHeaderFilter;
320 bool LastErrorRelatesToUserCode = false;
321 bool LastErrorPassesLineFilter = false;
322 bool LastErrorWasIgnored = false;
323};
324
325} // end namespace tidy
326} // end namespace clang
327
328#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CLANGTIDYDIAGNOSTICCONSUMER_H
329