diff --git a/clang-tools-extra/clang-tidy/ClangTidy.h b/clang-tools-extra/clang-tidy/ClangTidy.h --- a/clang-tools-extra/clang-tidy/ClangTidy.h +++ b/clang-tools-extra/clang-tidy/ClangTidy.h @@ -11,6 +11,7 @@ #include "ClangTidyDiagnosticConsumer.h" #include "ClangTidyOptions.h" +#include "llvm/ADT/StringSet.h" #include #include @@ -57,6 +58,14 @@ 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/clang-tools-extra/clang-tidy/ClangTidy.cpp b/clang-tools-extra/clang-tidy/ClangTidy.cpp --- a/clang-tools-extra/clang-tidy/ClangTidy.cpp +++ b/clang-tools-extra/clang-tidy/ClangTidy.cpp @@ -625,5 +625,40 @@ YAML << TUD; } +NamesAndOptions +getAllChecksAndOptions(bool AllowEnablingAnalyzerAlphaCheckers) { + NamesAndOptions Result; + ClangTidyOptions Opts; + Opts.Checks = "*"; + clang::tidy::ClangTidyContext Context( + std::make_unique(ClangTidyGlobalOptions(), Opts), + AllowEnablingAnalyzerAlphaCheckers); + ClangTidyCheckFactories Factories; + for (const ClangTidyModuleRegistry::entry &Module : + ClangTidyModuleRegistry::entries()) { + Module.instantiate()->addCheckFactories(Factories); + } + + for (const auto &Factory : Factories) + Result.Names.insert(Factory.getKey()); + +#if CLANG_TIDY_ENABLE_STATIC_ANALYZER + SmallString<64> Buffer(AnalyzerCheckNamePrefix); + size_t DefSize = Buffer.size(); + for (const auto &AnalyzerCheck : AnalyzerOptions::getRegisteredCheckers( + AllowEnablingAnalyzerAlphaCheckers)) { + Buffer.truncate(DefSize); + Buffer.append(AnalyzerCheck); + Result.Names.insert(Buffer); + } +#endif // CLANG_TIDY_ENABLE_STATIC_ANALYZER + + Context.setOptionsCollector(&Result.Options); + for (const auto &Factory : Factories) { + Factory.getValue()(Factory.getKey(), &Context); + } + + return Result; +} } // namespace tidy } // namespace clang diff --git a/clang-tools-extra/clang-tidy/ClangTidyCheck.cpp b/clang-tools-extra/clang-tidy/ClangTidyCheck.cpp --- a/clang-tools-extra/clang-tidy/ClangTidyCheck.cpp +++ b/clang-tools-extra/clang-tidy/ClangTidyCheck.cpp @@ -53,6 +53,8 @@ llvm::Optional ClangTidyCheck::OptionsView::get(StringRef LocalName) const { + if (Context->getOptionsCollector()) + Context->getOptionsCollector()->insert((NamePrefix + LocalName).str()); const auto &Iter = CheckOptions.find((NamePrefix + LocalName).str()); if (Iter != CheckOptions.end()) return StringRef(Iter->getValue().Value); @@ -60,8 +62,13 @@ } static ClangTidyOptions::OptionMap::const_iterator -findPriorityOption(const ClangTidyOptions::OptionMap &Options, StringRef NamePrefix, - StringRef LocalName) { +findPriorityOption(const ClangTidyOptions::OptionMap &Options, + StringRef NamePrefix, StringRef LocalName, + llvm::StringSet<> *Collector) { + if (Collector) { + Collector->insert((NamePrefix + LocalName).str()); + Collector->insert(LocalName); + } auto IterLocal = Options.find((NamePrefix + LocalName).str()); auto IterGlobal = Options.find(LocalName); if (IterLocal == Options.end()) @@ -75,7 +82,8 @@ llvm::Optional ClangTidyCheck::OptionsView::getLocalOrGlobal(StringRef LocalName) const { - auto Iter = findPriorityOption(CheckOptions, NamePrefix, LocalName); + auto Iter = findPriorityOption(CheckOptions, NamePrefix, LocalName, + Context->getOptionsCollector()); if (Iter != CheckOptions.end()) return StringRef(Iter->getValue().Value); return None; @@ -108,7 +116,8 @@ template <> llvm::Optional ClangTidyCheck::OptionsView::getLocalOrGlobal(StringRef LocalName) const { - auto Iter = findPriorityOption(CheckOptions, NamePrefix, LocalName); + auto Iter = findPriorityOption(CheckOptions, NamePrefix, LocalName, + Context->getOptionsCollector()); if (Iter != CheckOptions.end()) { if (auto Result = getAsBool(Iter->getValue().Value, Iter->getKey())) return Result; @@ -139,8 +148,11 @@ llvm::Optional ClangTidyCheck::OptionsView::getEnumInt( StringRef LocalName, ArrayRef Mapping, bool CheckGlobal, bool IgnoreCase) const { + if (!CheckGlobal && Context->getOptionsCollector()) + Context->getOptionsCollector()->insert((NamePrefix + LocalName).str()); auto Iter = CheckGlobal - ? findPriorityOption(CheckOptions, NamePrefix, LocalName) + ? findPriorityOption(CheckOptions, NamePrefix, LocalName, + Context->getOptionsCollector()) : CheckOptions.find((NamePrefix + LocalName).str()); if (Iter == CheckOptions.end()) return None; diff --git a/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.h b/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.h --- a/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.h +++ b/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.h @@ -15,6 +15,7 @@ #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 { @@ -201,6 +202,11 @@ DiagEngine->getDiagnosticIDs()->getDescription(DiagnosticID))); } + void setOptionsCollector(llvm::StringSet<> *Collector) { + OptionsCollector = Collector; + } + llvm::StringSet<> *getOptionsCollector() const { return OptionsCollector; } + private: // Writes to Stats. friend class ClangTidyDiagnosticConsumer; @@ -230,6 +236,7 @@ bool SelfContainedDiags; NoLintDirectiveHandler NoLintHandler; + llvm::StringSet<> *OptionsCollector = nullptr; }; /// Gets the Fix attached to \p Diagnostic. diff --git a/clang-tools-extra/clang-tidy/tool/ClangTidyMain.cpp b/clang-tools-extra/clang-tidy/tool/ClangTidyMain.cpp --- a/clang-tools-extra/clang-tidy/tool/ClangTidyMain.cpp +++ b/clang-tools-extra/clang-tidy/tool/ClangTidyMain.cpp @@ -19,6 +19,7 @@ #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" @@ -257,6 +258,12 @@ )"), 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 recognised. +)"), + cl::init(false), cl::cat(ClangTidyCategory)); + namespace clang { namespace tidy { @@ -385,6 +392,72 @@ 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 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 CheckGlob '" << 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\n"; + } + } 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 << "\n"; + else + Output << "; did you mean '" << Closest << "'\n"; + } + } + return AnyInvalid; +} + int clangTidyMain(int argc, const char **argv) { llvm::InitLLVM X(argc, argv); @@ -478,6 +551,39 @@ 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 << "\n"; + else + Output << "; did you mean '" << Closest << "'\n"; + } + } + 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); diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst --- a/clang-tools-extra/docs/ReleaseNotes.rst +++ b/clang-tools-extra/docs/ReleaseNotes.rst @@ -110,6 +110,9 @@ from suppressing diagnostics associated with macro arguments. This fixes `Issue 55134 `_. +- Added an option -verify-config which will check the config file to ensure each + `Checks` and `CheckOptions` entries are recognised. + New checks ^^^^^^^^^^ diff --git a/clang-tools-extra/docs/clang-tidy/index.rst b/clang-tools-extra/docs/clang-tidy/index.rst --- a/clang-tools-extra/docs/clang-tidy/index.rst +++ b/clang-tools-extra/docs/clang-tidy/index.rst @@ -244,6 +244,9 @@ standard output supports colors. This option overrides the 'UseColor' option in .clang-tidy file, if any. + --verify-config - + Check the config files to ensure each check and + option is recognised. --vfsoverlay= - Overlay the virtual filesystem described by file over the real file system. diff --git a/clang-tools-extra/test/clang-tidy/infrastructure/verify-config.cpp b/clang-tools-extra/test/clang-tidy/infrastructure/verify-config.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/infrastructure/verify-config.cpp @@ -0,0 +1,16 @@ +// RUN: clang-tidy -verify-config --config='' | FileCheck %s -check-prefix=CHECK-VERIFY-OK +// CHECK-VERIFY-OK: No config errors detected. + +// RUN: not clang-tidy -verify-config \ +// RUN: --checks='-*,bad*glob,llvm*,llvm-includeorder' --config='{Checks: "readability-else-after-ret", \ +// RUN: CheckOptions: [{key: "IgnoreMacros", value: "true"}, \ +// RUN: {key: "StriceMode", value: "true"}, \ +// RUN: {key: modernize-lop-convert.UseCxx20ReverseRanges, value: true} \ +// RUN: ]}' 2>&1 | FileCheck %s \ +// RUN: -check-prefix=CHECK-VERIFY -implicit-check-not='{{warning|error}}:' + +// CHECK-VERIFY-DAG: command-line option '-config': warning: Unknown Check 'readability-else-after-ret'; did you mean 'readability-else-after-return' +// CHECK-VERIFY-DAG: command-line option '-config': warning: Unknown Check Option 'modernize-lop-convert.UseCxx20ReverseRanges'; did you mean 'modernize-loop-convert.UseCxx20ReverseRanges' +// CHECK-VERIFY-DAG: command-line option '-config': warning: Unknown Check Option 'StriceMode'; did you mean 'StrictMode' +// CHECK-VERIFY: command-line option '-checks': warning: Check glob 'bad*glob' doesn't match any known check +// CHECK-VERIFY: command-line option '-checks': warning: Unknown Check 'llvm-includeorder'; did you mean 'llvm-include-order'