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