xref: /llvm-project/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.h (revision f59b600c21add076d6a876f29f94990b24b8e321)
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 
23 namespace clang {
24 
25 class ASTContext;
26 class SourceManager;
27 
28 namespace tidy {
29 class 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.
38 struct 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.
47 struct 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
69 class ClangTidyContext {
70 public:
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(DiagnosticID, Loc)),
221         std::string(
222             DiagEngine->getDiagnosticIDs()->getDescription(DiagnosticID))};
223   }
224 
225   void setOptionsCollector(llvm::StringSet<> *Collector) {
226     OptionsCollector = Collector;
227   }
228   llvm::StringSet<> *getOptionsCollector() const { return OptionsCollector; }
229 
230 private:
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.
270 const llvm::StringMap<tooling::Replacements> *
271 getFixIt(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.
277 class ClangTidyDiagnosticConsumer : public DiagnosticConsumer {
278 public:
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 
296 private:
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