diff --git a/ClangTidy.h b/ClangTidy.h index bbe4fe6..51d9e22 100644 --- a/ClangTidy.h +++ b/ClangTidy.h @@ -11,6 +11,7 @@ #include "ClangTidyDiagnosticConsumer.h" #include "ClangTidyOptions.h" +#include "llvm/ADT/StringSet.h" #include #include @@ -38,7 +39,7 @@ class ClangTidyASTConsumerFactory { /// Returns an ASTConsumer that runs the specified clang-tidy checks. std::unique_ptr - CreateASTConsumer(clang::CompilerInstance &Compiler, StringRef File); + createASTConsumer(clang::CompilerInstance &Compiler, StringRef File); /// Get the list of enabled checks. std::vector getCheckNames(); @@ -57,6 +58,14 @@ class ClangTidyASTConsumerFactory { std::vector getCheckNames(const ClangTidyOptions &Options, bool AllowEnablingAnalyzerAlphaCheckers); +struct NamesAndOptions { + llvm::StringSet<> Names; + llvm::StringSet<> Options; +}; + +NamesAndOptions +getAllChecksAndOptions(bool AllowEnablingAnalyzerAlphaCheckers = true); + /// Returns the effective check-specific options. /// /// The method configures ClangTidy with the specified \p Options and collects diff --git a/ClangTidyCheck.h b/ClangTidyCheck.h index 20e9b8e..abf528d 100644 --- a/ClangTidyCheck.h +++ b/ClangTidyCheck.h @@ -20,7 +20,6 @@ namespace clang { -class CompilerInstance; class SourceManager; namespace tidy { @@ -123,7 +122,7 @@ class ClangTidyCheck : public ast_matchers::MatchFinder::MatchCallback { /// Adds a diagnostic to report errors in the check's configuration. DiagnosticBuilder configurationDiag(StringRef Description, - DiagnosticIDs::Level Level = DiagnosticIDs::Warning); + DiagnosticIDs::Level Level = DiagnosticIDs::Warning) const; /// Should store all options supported by this check with their /// current values or default values for options that haven't been overridden. @@ -156,14 +155,14 @@ class ClangTidyCheck : public ast_matchers::MatchFinder::MatchCallback { /// Reads the option with the check-local name \p LocalName from the /// ``CheckOptions``. If the corresponding key is not present, return /// ``None``. - llvm::Optional get(StringRef LocalName) const; + llvm::Optional get(StringRef LocalName) const; /// Read a named option from the ``Context``. /// /// Reads the option with the check-local name \p LocalName from the /// ``CheckOptions``. If the corresponding key is not present, returns /// \p Default. - std::string get(StringRef LocalName, StringRef Default) const; + StringRef get(StringRef LocalName, StringRef Default) const; /// Read a named option from the ``Context``. /// @@ -171,7 +170,7 @@ class ClangTidyCheck : public ast_matchers::MatchFinder::MatchCallback { /// global ``CheckOptions``. Gets local option first. If local is not /// present, falls back to get global option. If global option is not /// present either, return ``None``. - llvm::Optional getLocalOrGlobal(StringRef LocalName) const; + llvm::Optional getLocalOrGlobal(StringRef LocalName) const; /// Read a named option from the ``Context``. /// @@ -179,7 +178,7 @@ class ClangTidyCheck : public ast_matchers::MatchFinder::MatchCallback { /// global ``CheckOptions``. Gets local option first. If local is not /// present, falls back to get global option. If global option is not /// present either, returns \p Default. - std::string getLocalOrGlobal(StringRef LocalName, StringRef Default) const; + StringRef getLocalOrGlobal(StringRef LocalName, StringRef Default) const; /// Read a named option from the ``Context`` and parse it as an /// integral type ``T``. @@ -193,7 +192,7 @@ class ClangTidyCheck : public ast_matchers::MatchFinder::MatchCallback { template std::enable_if_t::value, llvm::Optional> get(StringRef LocalName) const { - if (llvm::Optional Value = get(LocalName)) { + if (llvm::Optional Value = get(LocalName)) { T Result{}; if (!StringRef(*Value).getAsInteger(10, Result)) return Result; @@ -214,7 +213,7 @@ class ClangTidyCheck : public ast_matchers::MatchFinder::MatchCallback { template std::enable_if_t::value, T> get(StringRef LocalName, T Default) const { - return get(LocalName).getValueOr(Default); + return get(LocalName).value_or(Default); } /// Read a named option from the ``Context`` and parse it as an @@ -230,7 +229,7 @@ class ClangTidyCheck : public ast_matchers::MatchFinder::MatchCallback { template std::enable_if_t::value, llvm::Optional> getLocalOrGlobal(StringRef LocalName) const { - llvm::Optional ValueOr = get(LocalName); + llvm::Optional ValueOr = get(LocalName); bool IsGlobal = false; if (!ValueOr) { IsGlobal = true; @@ -259,7 +258,7 @@ class ClangTidyCheck : public ast_matchers::MatchFinder::MatchCallback { template std::enable_if_t::value, T> getLocalOrGlobal(StringRef LocalName, T Default) const { - return getLocalOrGlobal(LocalName).getValueOr(Default); + return getLocalOrGlobal(LocalName).value_or(Default); } /// Read a named option from the ``Context`` and parse it as an @@ -298,7 +297,7 @@ class ClangTidyCheck : public ast_matchers::MatchFinder::MatchCallback { template std::enable_if_t::value, T> get(StringRef LocalName, T Default, bool IgnoreCase = false) const { - return get(LocalName, IgnoreCase).getValueOr(Default); + return get(LocalName, IgnoreCase).value_or(Default); } /// Read a named option from the ``Context`` and parse it as an @@ -340,7 +339,7 @@ class ClangTidyCheck : public ast_matchers::MatchFinder::MatchCallback { std::enable_if_t::value, T> getLocalOrGlobal(StringRef LocalName, T Default, bool IgnoreCase = false) const { - return getLocalOrGlobal(LocalName, IgnoreCase).getValueOr(Default); + return getLocalOrGlobal(LocalName, IgnoreCase).value_or(Default); } /// Stores an option with the check-local name \p LocalName with @@ -418,6 +417,11 @@ class ClangTidyCheck : public ast_matchers::MatchFinder::MatchCallback { StringRef getCurrentMainFile() const { return Context->getCurrentFile(); } /// Returns the language options from the context. const LangOptions &getLangOpts() const { return Context->getLangOpts(); } + /// Returns true when the check is run in a use case when only 1 fix will be + /// applied at a time. + bool areDiagsSelfContained() const { + return Context->areDiagsSelfContained(); + } }; /// Read a named option from the ``Context`` and parse it as a bool. diff --git a/ClangTidyDiagnosticConsumer.h b/ClangTidyDiagnosticConsumer.h index 13372cc..261e4f7 100644 --- a/ClangTidyDiagnosticConsumer.h +++ b/ClangTidyDiagnosticConsumer.h @@ -11,24 +11,20 @@ #include "ClangTidyOptions.h" #include "ClangTidyProfiling.h" +#include "NoLintDirectiveHandler.h" #include "clang/Basic/Diagnostic.h" #include "clang/Tooling/Core/Diagnostic.h" #include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/StringSet.h" #include "llvm/Support/Regex.h" namespace clang { class ASTContext; -class CompilerInstance; class SourceManager; -namespace ast_matchers { -class MatchFinder; -} -namespace tooling { -class CompilationDatabase; -} namespace tidy { +class CachedGlobList; /// A detected error complete with information to display diagnostic and /// automatic fix. @@ -45,18 +41,13 @@ struct ClangTidyError : tooling::Diagnostic { std::vector EnabledDiagnosticAliases; }; -/// Contains displayed and ignored diagnostic counters for a ClangTidy -/// run. +/// Contains displayed and ignored diagnostic counters for a ClangTidy run. struct ClangTidyStats { - ClangTidyStats() - : ErrorsDisplayed(0), ErrorsIgnoredCheckFilter(0), ErrorsIgnoredNOLINT(0), - ErrorsIgnoredNonUserCode(0), ErrorsIgnoredLineFilter(0) {} - - unsigned ErrorsDisplayed; - unsigned ErrorsIgnoredCheckFilter; - unsigned ErrorsIgnoredNOLINT; - unsigned ErrorsIgnoredNonUserCode; - unsigned ErrorsIgnoredLineFilter; + unsigned ErrorsDisplayed = 0; + unsigned ErrorsIgnoredCheckFilter = 0; + unsigned ErrorsIgnoredNOLINT = 0; + unsigned ErrorsIgnoredNonUserCode = 0; + unsigned ErrorsIgnoredLineFilter = 0; unsigned errorsIgnored() const { return ErrorsIgnoredNOLINT + ErrorsIgnoredCheckFilter + @@ -99,11 +90,33 @@ class ClangTidyContext { DiagnosticBuilder diag(StringRef CheckName, StringRef Message, DiagnosticIDs::Level Level = DiagnosticIDs::Warning); + DiagnosticBuilder diag(const tooling::Diagnostic &Error); + /// Report any errors to do with reading the configuration using this method. DiagnosticBuilder configurationDiag(StringRef Message, DiagnosticIDs::Level Level = DiagnosticIDs::Warning); + /// Check whether a given diagnostic should be suppressed due to the presence + /// of a "NOLINT" suppression comment. + /// This is exposed so that other tools that present clang-tidy diagnostics + /// (such as clangd) can respect the same suppression rules as clang-tidy. + /// This does not handle suppression of notes following a suppressed + /// diagnostic; that is left to the caller as it requires maintaining state in + /// between calls to this function. + /// If any NOLINT is malformed, e.g. a BEGIN without a subsequent END, output + /// \param NoLintErrors will return an error about it. + /// If \param AllowIO is false, the function does not attempt to read source + /// files from disk which are not already mapped into memory; such files are + /// treated as not containing a suppression comment. + /// \param EnableNoLintBlocks controls whether to honor NOLINTBEGIN/NOLINTEND + /// blocks; if false, only considers line-level disabling. + bool + shouldSuppressDiagnostic(DiagnosticsEngine::Level DiagLevel, + const Diagnostic &Info, + SmallVectorImpl &NoLintErrors, + bool AllowIO = true, bool EnableNoLintBlocks = true); + /// Sets the \c SourceManager of the used \c DiagnosticsEngine. /// /// This is called from the \c ClangTidyCheck base class. @@ -165,7 +178,7 @@ class ClangTidyContext { } /// Returns build directory of the current translation unit. - const std::string &getCurrentBuildDirectory() { + const std::string &getCurrentBuildDirectory() const { return CurrentBuildDirectory; } @@ -175,6 +188,10 @@ class ClangTidyContext { return AllowEnablingAnalyzerAlphaCheckers; } + void setSelfContainedDiags(bool Value) { SelfContainedDiags = Value; } + + bool areDiagsSelfContained() const { return SelfContainedDiags; } + using DiagLevelAndFormatString = std::pair; DiagLevelAndFormatString getDiagLevelAndFormatString(unsigned DiagnosticID, SourceLocation Loc) { @@ -185,6 +202,11 @@ class ClangTidyContext { DiagEngine->getDiagnosticIDs()->getDescription(DiagnosticID))); } + void setOptionsCollector(llvm::StringSet<> *Collector) { + OptionsCollector = Collector; + } + llvm::StringSet<> *getOptionsCollector() const { return OptionsCollector; } + private: // Writes to Stats. friend class ClangTidyDiagnosticConsumer; @@ -194,7 +216,7 @@ class ClangTidyContext { std::string CurrentFile; ClangTidyOptions CurrentOptions; - class CachedGlobList; + std::unique_ptr CheckFilter; std::unique_ptr WarningAsErrorFilter; @@ -210,21 +232,12 @@ class ClangTidyContext { std::string ProfilePrefix; bool AllowEnablingAnalyzerAlphaCheckers; -}; -/// Check whether a given diagnostic should be suppressed due to the presence -/// of a "NOLINT" suppression comment. -/// This is exposed so that other tools that present clang-tidy diagnostics -/// (such as clangd) can respect the same suppression rules as clang-tidy. -/// This does not handle suppression of notes following a suppressed diagnostic; -/// that is left to the caller is it requires maintaining state in between calls -/// to this function. -/// If `AllowIO` is false, the function does not attempt to read source files -/// from disk which are not already mapped into memory; such files are treated -/// as not containing a suppression comment. -bool shouldSuppressDiagnostic(DiagnosticsEngine::Level DiagLevel, - const Diagnostic &Info, ClangTidyContext &Context, - bool AllowIO = true); + bool SelfContainedDiags; + + NoLintDirectiveHandler NoLintHandler; + llvm::StringSet<> *OptionsCollector = nullptr; +}; /// Gets the Fix attached to \p Diagnostic. /// If there isn't a Fix attached to the diagnostic and \p AnyFix is true, Check @@ -235,15 +248,17 @@ getFixIt(const tooling::Diagnostic &Diagnostic, bool AnyFix); /// A diagnostic consumer that turns each \c Diagnostic into a /// \c SourceManager-independent \c ClangTidyError. -// // FIXME: If we move away from unit-tests, this can be moved to a private // implementation file. class ClangTidyDiagnosticConsumer : public DiagnosticConsumer { public: + /// \param EnableNolintBlocks Enables diagnostic-disabling inside blocks of + /// code, delimited by NOLINTBEGIN and NOLINTEND. ClangTidyDiagnosticConsumer(ClangTidyContext &Ctx, DiagnosticsEngine *ExternalDiagEngine = nullptr, bool RemoveIncompatibleErrors = true, - bool GetFixesFromNotes = false); + bool GetFixesFromNotes = false, + bool EnableNolintBlocks = true); // FIXME: The concept of converting between FixItHints and Replacements is // more generic and should be pulled out into a more useful Diagnostics @@ -274,6 +289,7 @@ class ClangTidyDiagnosticConsumer : public DiagnosticConsumer { DiagnosticsEngine *ExternalDiagEngine; bool RemoveIncompatibleErrors; bool GetFixesFromNotes; + bool EnableNolintBlocks; std::vector Errors; std::unique_ptr HeaderFilter; bool LastErrorRelatesToUserCode; diff --git a/ClangTidyModule.h b/ClangTidyModule.h index dd21a8d..f44457d 100644 --- a/ClangTidyModule.h +++ b/ClangTidyModule.h @@ -67,6 +67,10 @@ class ClangTidyCheckFactories { std::vector> createChecks(ClangTidyContext *Context); + /// Create instances of checks that are enabled for the current Language. + std::vector> + createChecksForLanguage(ClangTidyContext *Context); + typedef llvm::StringMap FactoryMap; FactoryMap::const_iterator begin() const { return Factories.begin(); } FactoryMap::const_iterator end() const { return Factories.end(); } diff --git a/ClangTidyOptions.h b/ClangTidyOptions.h index d8a4a14..3f09d39 100644 --- a/ClangTidyOptions.h +++ b/ClangTidyOptions.h @@ -108,7 +108,7 @@ struct ClangTidyOptions { std::string Value; /// Priority stores relative precedence of the value loaded from config - /// files to disambigute local vs global value from different levels. + /// files to disambiguate local vs global value from different levels. unsigned Priority; }; typedef std::pair StringPair; @@ -129,8 +129,8 @@ struct ClangTidyOptions { /// and using a FileOptionsProvider, it will take a configuration file in the /// parent directory (if any exists) and apply this config file on top of the /// parent one. IF true and using a ConfigOptionsProvider, it will apply this - /// config on top of any configuation file it finds in the directory using the - /// same logic as FileOptionsProvider. If false or missing, only this + /// config on top of any configuration file it finds in the directory using + /// the same logic as FileOptionsProvider. If false or missing, only this /// configuration file will be used. llvm::Optional InheritParentConfig; diff --git a/GlobList.h b/GlobList.h index fe68a34..3eec92e 100644 --- a/GlobList.h +++ b/GlobList.h @@ -11,6 +11,7 @@ #include "clang/Basic/LLVM.h" #include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/StringMap.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/Regex.h" @@ -24,19 +25,23 @@ namespace tidy { /// them in the order of appearance in the list. class GlobList { public: + virtual ~GlobList() = default; + /// \p Globs is a comma-separated list of globs (only the '*' metacharacter is /// supported) with an optional '-' prefix to denote exclusion. /// /// An empty \p Globs string is interpreted as one glob that matches an empty /// string. - GlobList(StringRef Globs); + /// + /// \p KeepNegativeGlobs a bool flag indicating whether to keep negative + /// globs from \p Globs or not. When false, negative globs are simply ignored. + GlobList(StringRef Globs, bool KeepNegativeGlobs = true); /// Returns \c true if the pattern matches \p S. The result is the last /// matching glob's Positive flag. - bool contains(StringRef S) const; + virtual bool contains(StringRef S) const; private: - struct GlobListItem { bool IsPositive; llvm::Regex Regex; @@ -44,7 +49,20 @@ class GlobList { SmallVector Items; }; -} // end namespace tidy -} // end namespace clang +/// A \p GlobList that caches search results, so that search is performed only +/// once for the same query. +class CachedGlobList final : public GlobList { +public: + using GlobList::GlobList; + + /// \see GlobList::contains + bool contains(StringRef S) const override; + +private: + mutable llvm::StringMap Cache; +}; + +} // namespace tidy +} // namespace clang #endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GLOBLIST_H diff --git a/NoLintDirectiveHandler.h b/NoLintDirectiveHandler.h new file mode 100644 index 0000000..db9fd59 --- /dev/null +++ b/NoLintDirectiveHandler.h @@ -0,0 +1,51 @@ +//===-- clang-tools-extra/clang-tidy/NoLintDirectiveHandler.h ----*- C++ *-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_NOLINTDIRECTIVEHANDLER_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_NOLINTDIRECTIVEHANDLER_H + +#include "clang/Basic/Diagnostic.h" +#include "llvm/ADT/StringRef.h" +#include + +namespace clang { +namespace tooling { +struct Diagnostic; +} // namespace tooling +} // namespace clang + +namespace llvm { +template class SmallVectorImpl; +} // namespace llvm + +namespace clang { +namespace tidy { + +/// This class is used to locate NOLINT comments in the file being analyzed, to +/// decide whether a diagnostic should be suppressed. +/// This class keeps a cache of every NOLINT comment found so that files do not +/// have to be repeatedly parsed each time a new diagnostic is raised. +class NoLintDirectiveHandler { +public: + NoLintDirectiveHandler(); + ~NoLintDirectiveHandler(); + + bool shouldSuppress(DiagnosticsEngine::Level DiagLevel, + const Diagnostic &Diag, llvm::StringRef DiagName, + llvm::SmallVectorImpl &NoLintErrors, + bool AllowIO, bool EnableNoLintBlocks); + +private: + class Impl; + std::unique_ptr PImpl; +}; + +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_NOLINTDIRECTIVEHANDLER_H diff --git a/aliceO2/NamespaceNamingCheck.cpp b/aliceO2/NamespaceNamingCheck.cpp index edd94d2..ca9790a 100644 --- a/aliceO2/NamespaceNamingCheck.cpp +++ b/aliceO2/NamespaceNamingCheck.cpp @@ -143,7 +143,7 @@ void NamespaceNamingCheck::check(const MatchFinder::MatchResult &Result) { bool NamespaceNamingCheck::fixNamespaceName(std::string &name) { - std::string replace_option = Options.get(name, ""); + std::string replace_option = Options.get(name, "").str(); if( replace_option != "" ) { name = replace_option; diff --git a/tool/ClangTidyMain.cpp b/tool/ClangTidyMain.cpp index d171944..6e9618d 100644 --- a/tool/ClangTidyMain.cpp +++ b/tool/ClangTidyMain.cpp @@ -19,7 +19,9 @@ #include "../ClangTidyForceLinker.h" #include "../GlobList.h" #include "clang/Tooling/CommonOptionsParser.h" +#include "llvm/ADT/StringSet.h" #include "llvm/Support/InitLLVM.h" +#include "llvm/Support/PluginLoader.h" #include "llvm/Support/Process.h" #include "llvm/Support/Signals.h" #include "llvm/Support/TargetSelect.h" @@ -50,8 +52,7 @@ Configuration files: InheritParentConfig: true User: user CheckOptions: - - key: some-check.SomeOption - value: 'some value' + some-check.SomeOption: 'some value' ... )"); @@ -169,8 +170,7 @@ line or a specific configuration file. static cl::opt Config("config", cl::desc(R"( Specifies a configuration in YAML/JSON format: -config="{Checks: '*', - CheckOptions: [{key: x, - value: y}]}" + CheckOptions: {x: y}}" When the value is empty, clang-tidy will attempt to find a file named .clang-tidy for each source file in its parent directories. @@ -256,6 +256,12 @@ This option overrides the 'UseColor' option in )"), cl::init(false), cl::cat(ClangTidyCategory)); +static cl::opt VerifyConfig("verify-config", cl::desc(R"( +Check the config files to ensure each check and +option is recognized. +)"), + cl::init(false), cl::cat(ClangTidyCategory)); + namespace clang { namespace tidy { @@ -384,8 +390,81 @@ getVfsFromFile(const std::string &OverlayFile, return FS; } +static StringRef closest(StringRef Value, const StringSet<> &Allowed) { + unsigned MaxEdit = 5U; + StringRef Closest; + for (auto Item : Allowed.keys()) { + unsigned Cur = Value.edit_distance_insensitive(Item, true, MaxEdit); + if (Cur < MaxEdit) { + Closest = Item; + MaxEdit = Cur; + } + } + return Closest; +} + +static constexpr StringLiteral VerifyConfigWarningEnd = " [-verify-config]\n"; + +static bool verifyChecks(const StringSet<> &AllChecks, StringRef CheckGlob, + StringRef Source) { + llvm::StringRef Cur, Rest; + bool AnyInvalid = false; + for (std::tie(Cur, Rest) = CheckGlob.split(','); + !(Cur.empty() && Rest.empty()); std::tie(Cur, Rest) = Rest.split(',')) { + Cur = Cur.trim(); + if (Cur.empty()) + continue; + Cur.consume_front("-"); + if (Cur.startswith("clang-diagnostic")) + continue; + if (Cur.contains('*')) { + SmallString<128> RegexText("^"); + StringRef MetaChars("()^$|*+?.[]\\{}"); + for (char C : Cur) { + if (C == '*') + RegexText.push_back('.'); + else if (MetaChars.contains(C)) + RegexText.push_back('\\'); + RegexText.push_back(C); + } + RegexText.push_back('$'); + llvm::Regex Glob(RegexText); + std::string Error; + if (!Glob.isValid(Error)) { + AnyInvalid = true; + llvm::WithColor::error(llvm::errs(), Source) + << "building check glob '" << Cur << "' " << Error << "'\n"; + continue; + } + if (llvm::none_of(AllChecks.keys(), + [&Glob](StringRef S) { return Glob.match(S); })) { + AnyInvalid = true; + llvm::WithColor::warning(llvm::errs(), Source) + << "check glob '" << Cur << "' doesn't match any known check" + << VerifyConfigWarningEnd; + } + } else { + if (AllChecks.contains(Cur)) + continue; + AnyInvalid = true; + llvm::raw_ostream &Output = llvm::WithColor::warning(llvm::errs(), Source) + << "unknown check '" << Cur << '\''; + llvm::StringRef Closest = closest(Cur, AllChecks); + if (!Closest.empty()) + Output << "; did you mean '" << Closest << '\''; + Output << VerifyConfigWarningEnd; + } + } + return AnyInvalid; +} + int clangTidyMain(int argc, const char **argv) { llvm::InitLLVM X(argc, argv); + + // Enable help for -load option, if plugins are enabled. + if (cl::Option *LoadOpt = cl::getRegisteredOptions().lookup("load")) + LoadOpt->addCategory(ClangTidyCategory); + llvm::Expected OptionsParser = CommonOptionsParser::create(argc, argv, ClangTidyCategory, cl::ZeroOrMore); @@ -472,6 +551,38 @@ int clangTidyMain(int argc, const char **argv) { return 0; } + if (VerifyConfig) { + std::vector RawOptions = + OptionsProvider->getRawOptions(FileName); + NamesAndOptions Valid = + getAllChecksAndOptions(AllowEnablingAnalyzerAlphaCheckers); + bool AnyInvalid = false; + for (const std::pair &OptionWithSource : + RawOptions) { + const ClangTidyOptions &Opts = OptionWithSource.first; + if (Opts.Checks) + AnyInvalid |= + verifyChecks(Valid.Names, *Opts.Checks, OptionWithSource.second); + + for (auto Key : Opts.CheckOptions.keys()) { + if (Valid.Options.contains(Key)) + continue; + AnyInvalid = true; + auto &Output = + llvm::WithColor::warning(llvm::errs(), OptionWithSource.second) + << "unknown check option '" << Key << '\''; + llvm::StringRef Closest = closest(Key, Valid.Options); + if (!Closest.empty()) + Output << "; did you mean '" << Closest << '\''; + Output << VerifyConfigWarningEnd; + } + } + if (AnyInvalid) + return 1; + llvm::outs() << "No config errors detected.\n"; + return 0; + } + if (EnabledChecks.empty()) { llvm::errs() << "Error: no checks enabled.\n"; llvm::cl::PrintHelpMessage(/*Hidden=*/false, /*Categorized=*/true); @@ -493,9 +604,9 @@ int clangTidyMain(int argc, const char **argv) { std::vector Errors = runClangTidy(Context, OptionsParser->getCompilations(), PathList, BaseFS, FixNotes, EnableCheckProfile, ProfilePrefix); - bool FoundErrors = llvm::find_if(Errors, [](const ClangTidyError &E) { - return E.DiagLevel == ClangTidyError::Error; - }) != Errors.end(); + bool FoundErrors = llvm::any_of(Errors, [](const ClangTidyError &E) { + return E.DiagLevel == ClangTidyError::Error; + }); // --fix-errors and --fix-notes imply --fix. FixBehaviour Behaviour = FixNotes ? FB_FixNotes