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