[clang-tools-extra] fcf7cc2 - [clang-tidy] Added support for validating configuration options
Nathan James via cfe-commits
cfe-commits at lists.llvm.org
Tue Apr 7 11:54:42 PDT 2020
Author: Nathan James
Date: 2020-04-07T19:54:34+01:00
New Revision: fcf7cc268fe4560bc7cd751494beceff45f5dd10
URL: https://github.com/llvm/llvm-project/commit/fcf7cc268fe4560bc7cd751494beceff45f5dd10
DIFF: https://github.com/llvm/llvm-project/commit/fcf7cc268fe4560bc7cd751494beceff45f5dd10.diff
LOG: [clang-tidy] Added support for validating configuration options
Summary:
Adds support for `ClangTidyCheck::OptionsView` to deteremine:
- If an option is found in the configuration.
- If an integer option read from configuration is parsable to an integer.
- Parse and Serialize enum configuration options directly using a mapping from `llvm::StringRef` to `EnumType`.
- If an integer or enum option isn't parseable but there is a default value it will issue a warning to stderr that the config value hasn't been used.
- If an enum option isn't parsable it can provide a hint if the value was a typo.
Reviewers: aaron.ballman, alexfh, gribozavr2
Reviewed By: aaron.ballman
Subscribers: xazax.hun, cfe-commits
Tags: #clang, #clang-tools-extra
Differential Revision: https://reviews.llvm.org/D77085
Added:
Modified:
clang-tools-extra/clang-tidy/ClangTidyCheck.cpp
clang-tools-extra/clang-tidy/ClangTidyCheck.h
clang-tools-extra/unittests/clang-tidy/ClangTidyOptionsTest.cpp
Removed:
################################################################################
diff --git a/clang-tools-extra/clang-tidy/ClangTidyCheck.cpp b/clang-tools-extra/clang-tidy/ClangTidyCheck.cpp
index cc817d85dbcd..aadf372cda1a 100644
--- a/clang-tools-extra/clang-tidy/ClangTidyCheck.cpp
+++ b/clang-tools-extra/clang-tidy/ClangTidyCheck.cpp
@@ -7,10 +7,44 @@
//===----------------------------------------------------------------------===//
#include "ClangTidyCheck.h"
+#include "llvm/ADT/SmallString.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Error.h"
+#include "llvm/Support/raw_ostream.h"
namespace clang {
namespace tidy {
+char MissingOptionError::ID;
+char UnparseableEnumOptionError::ID;
+char UnparseableIntegerOptionError::ID;
+
+std::string MissingOptionError::message() const {
+ llvm::SmallString<128> Buffer;
+ llvm::raw_svector_ostream Output(Buffer);
+ Output << "option not found '" << OptionName << '\'';
+ return std::string(Buffer);
+}
+
+std::string UnparseableEnumOptionError::message() const {
+ llvm::SmallString<128> Buffer;
+ llvm::raw_svector_ostream Output(Buffer);
+ Output << "invalid configuration value '" << LookupValue << "' for option '"
+ << LookupName << '\'';
+ if (SuggestedValue)
+ Output << "; did you mean '" << *SuggestedValue << "'?";
+ return std::string(Buffer);
+}
+
+std::string UnparseableIntegerOptionError::message() const {
+ llvm::SmallString<128> Buffer;
+ llvm::raw_svector_ostream Output(Buffer);
+ Output << "invalid configuration value '" << LookupValue << "' for option '"
+ << LookupName << "'; expected "
+ << (IsBoolean ? "a bool" : "an integer value");
+ return std::string(Buffer);
+}
+
ClangTidyCheck::ClangTidyCheck(StringRef CheckName, ClangTidyContext *Context)
: CheckName(CheckName), Context(Context),
Options(CheckName, Context->getOptions().CheckOptions) {
@@ -34,17 +68,16 @@ ClangTidyCheck::OptionsView::OptionsView(StringRef CheckName,
const ClangTidyOptions::OptionMap &CheckOptions)
: NamePrefix(CheckName.str() + "."), CheckOptions(CheckOptions) {}
-std::string ClangTidyCheck::OptionsView::get(StringRef LocalName,
- StringRef Default) const {
+llvm::Expected<std::string>
+ClangTidyCheck::OptionsView::get(StringRef LocalName) const {
const auto &Iter = CheckOptions.find(NamePrefix + LocalName.str());
if (Iter != CheckOptions.end())
return Iter->second;
- return std::string(Default);
+ return llvm::make_error<MissingOptionError>((NamePrefix + LocalName).str());
}
-std::string
-ClangTidyCheck::OptionsView::getLocalOrGlobal(StringRef LocalName,
- StringRef Default) const {
+llvm::Expected<std::string>
+ClangTidyCheck::OptionsView::getLocalOrGlobal(StringRef LocalName) const {
auto Iter = CheckOptions.find(NamePrefix + LocalName.str());
if (Iter != CheckOptions.end())
return Iter->second;
@@ -52,7 +85,65 @@ ClangTidyCheck::OptionsView::getLocalOrGlobal(StringRef LocalName,
Iter = CheckOptions.find(LocalName.str());
if (Iter != CheckOptions.end())
return Iter->second;
- return std::string(Default);
+ return llvm::make_error<MissingOptionError>((NamePrefix + LocalName).str());
+}
+
+static llvm::Expected<bool> getAsBool(StringRef Value,
+ const llvm::Twine &LookupName) {
+ if (Value == "true")
+ return true;
+ if (Value == "false")
+ return false;
+ bool Result;
+ if (!Value.getAsInteger(10, Result))
+ return Result;
+ return llvm::make_error<UnparseableIntegerOptionError>(LookupName.str(),
+ Value.str(), true);
+}
+
+template <>
+llvm::Expected<bool>
+ClangTidyCheck::OptionsView::get<bool>(StringRef LocalName) const {
+ llvm::Expected<std::string> ValueOr = get(LocalName);
+ if (ValueOr)
+ return getAsBool(*ValueOr, NamePrefix + LocalName);
+ return ValueOr.takeError();
+}
+
+template <>
+bool ClangTidyCheck::OptionsView::get<bool>(StringRef LocalName,
+ bool Default) const {
+ llvm::Expected<bool> ValueOr = get<bool>(LocalName);
+ if (ValueOr)
+ return *ValueOr;
+ logErrToStdErr(ValueOr.takeError());
+ return Default;
+}
+
+template <>
+llvm::Expected<bool>
+ClangTidyCheck::OptionsView::getLocalOrGlobal<bool>(StringRef LocalName) const {
+ llvm::Expected<std::string> ValueOr = get(LocalName);
+ bool IsGlobal = false;
+ if (!ValueOr) {
+ llvm::consumeError(ValueOr.takeError());
+ ValueOr = getLocalOrGlobal(LocalName);
+ IsGlobal = true;
+ }
+ if (!ValueOr)
+ return ValueOr.takeError();
+ return getAsBool(*ValueOr, IsGlobal ? llvm::Twine(LocalName)
+ : (NamePrefix + LocalName));
+}
+
+template <>
+bool ClangTidyCheck::OptionsView::getLocalOrGlobal<bool>(StringRef LocalName,
+ bool Default) const {
+ llvm::Expected<bool> ValueOr = getLocalOrGlobal<bool>(LocalName);
+ if (ValueOr)
+ return *ValueOr;
+ logErrToStdErr(ValueOr.takeError());
+ return Default;
}
void ClangTidyCheck::OptionsView::store(ClangTidyOptions::OptionMap &Options,
@@ -67,5 +158,49 @@ void ClangTidyCheck::OptionsView::store(ClangTidyOptions::OptionMap &Options,
store(Options, LocalName, llvm::itostr(Value));
}
+llvm::Expected<int64_t> ClangTidyCheck::OptionsView::getEnumInt(
+ StringRef LocalName, ArrayRef<std::pair<StringRef, int64_t>> Mapping,
+ bool CheckGlobal, bool IgnoreCase) {
+ auto Iter = CheckOptions.find((NamePrefix + LocalName).str());
+ if (CheckGlobal && Iter == CheckOptions.end())
+ Iter = CheckOptions.find(LocalName.str());
+ if (Iter == CheckOptions.end())
+ return llvm::make_error<MissingOptionError>((NamePrefix + LocalName).str());
+
+ StringRef Value = Iter->second;
+ StringRef Closest;
+ unsigned EditDistance = -1;
+ for (const auto &NameAndEnum : Mapping) {
+ if (IgnoreCase) {
+ if (Value.equals_lower(NameAndEnum.first))
+ return NameAndEnum.second;
+ } else if (Value.equals(NameAndEnum.first)) {
+ return NameAndEnum.second;
+ } else if (Value.equals_lower(NameAndEnum.first)) {
+ Closest = NameAndEnum.first;
+ EditDistance = 0;
+ continue;
+ }
+ unsigned Distance = Value.edit_distance(NameAndEnum.first);
+ if (Distance < EditDistance) {
+ EditDistance = Distance;
+ Closest = NameAndEnum.first;
+ }
+ }
+ if (EditDistance < 3)
+ return llvm::make_error<UnparseableEnumOptionError>(
+ Iter->first, Iter->second, std::string(Closest));
+ return llvm::make_error<UnparseableEnumOptionError>(Iter->first,
+ Iter->second);
+}
+
+void ClangTidyCheck::OptionsView::logErrToStdErr(llvm::Error &&Err) {
+ llvm::logAllUnhandledErrors(
+ llvm::handleErrors(std::move(Err),
+ [](const MissingOptionError &) -> llvm::Error {
+ return llvm::Error::success();
+ }),
+ llvm::errs(), "warning: ");
+}
} // namespace tidy
} // namespace clang
diff --git a/clang-tools-extra/clang-tidy/ClangTidyCheck.h b/clang-tools-extra/clang-tidy/ClangTidyCheck.h
index f274cf9d48c2..e90e92f6e136 100644
--- a/clang-tools-extra/clang-tidy/ClangTidyCheck.h
+++ b/clang-tools-extra/clang-tidy/ClangTidyCheck.h
@@ -14,7 +14,9 @@
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/Basic/Diagnostic.h"
#include "clang/Basic/SourceManager.h"
+#include "llvm/ADT/Optional.h"
#include "llvm/ADT/StringExtras.h"
+#include "llvm/Support/Error.h"
#include <memory>
#include <type_traits>
#include <vector>
@@ -25,6 +27,65 @@ class CompilerInstance;
namespace tidy {
+template <typename T> class OptionError : public llvm::ErrorInfo<T> {
+ std::error_code convertToErrorCode() const override {
+ return llvm::inconvertibleErrorCode();
+ }
+
+public:
+ void log(raw_ostream &OS) const override { OS << this->message(); }
+};
+
+class MissingOptionError : public OptionError<MissingOptionError> {
+public:
+ explicit MissingOptionError(std::string OptionName)
+ : OptionName(OptionName) {}
+
+ std::string message() const override;
+ static char ID;
+private:
+ const std::string OptionName;
+};
+
+class UnparseableEnumOptionError
+ : public OptionError<UnparseableEnumOptionError> {
+public:
+ explicit UnparseableEnumOptionError(std::string LookupName,
+ std::string LookupValue)
+ : LookupName(LookupName), LookupValue(LookupValue) {}
+ explicit UnparseableEnumOptionError(std::string LookupName,
+ std::string LookupValue,
+ std::string SuggestedValue)
+ : LookupName(LookupName), LookupValue(LookupValue),
+ SuggestedValue(SuggestedValue) {}
+
+ std::string message() const override;
+ static char ID;
+
+private:
+ const std::string LookupName;
+ const std::string LookupValue;
+ const llvm::Optional<std::string> SuggestedValue;
+};
+
+class UnparseableIntegerOptionError
+ : public OptionError<UnparseableIntegerOptionError> {
+public:
+ explicit UnparseableIntegerOptionError(std::string LookupName,
+ std::string LookupValue,
+ bool IsBoolean = false)
+ : LookupName(LookupName), LookupValue(LookupValue), IsBoolean(IsBoolean) {
+ }
+
+ std::string message() const override;
+ static char ID;
+
+private:
+ const std::string LookupName;
+ const std::string LookupValue;
+ const bool IsBoolean;
+};
+
/// Base class for all clang-tidy checks.
///
/// To implement a ``ClangTidyCheck``, write a subclass and override some of the
@@ -127,35 +188,82 @@ class ClangTidyCheck : public ast_matchers::MatchFinder::MatchCallback {
OptionsView(StringRef CheckName,
const ClangTidyOptions::OptionMap &CheckOptions);
+ /// 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
+ /// a ``MissingOptionError``.
+ llvm::Expected<std::string> 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;
+ std::string get(StringRef LocalName, StringRef Default) const {
+ if (llvm::Expected<std::string> Val = get(LocalName))
+ return *Val;
+ else
+ llvm::consumeError(Val.takeError());
+ return Default.str();
+ }
+
+ /// Read a named option from the ``Context``.
+ ///
+ /// Reads the option with the check-local name \p LocalName from local or
+ /// 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 a ``MissingOptionError``.
+ llvm::Expected<std::string> getLocalOrGlobal(StringRef LocalName) const;
/// Read a named option from the ``Context``.
///
/// Reads the option with the check-local name \p LocalName from local or
/// 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 Default.
- std::string getLocalOrGlobal(StringRef LocalName, StringRef Default) const;
+ /// present either, returns \p Default.
+ std::string getLocalOrGlobal(StringRef LocalName, StringRef Default) const {
+ if (llvm::Expected<std::string> Val = getLocalOrGlobal(LocalName))
+ return *Val;
+ else
+ llvm::consumeError(Val.takeError());
+ return Default.str();
+ }
/// Read a named option from the ``Context`` and parse it as an
/// integral type ``T``.
///
/// Reads the option with the check-local name \p LocalName from the
/// ``CheckOptions``. If the corresponding key is not present, returns
- /// \p Default.
+ /// a ``MissingOptionError``. If the corresponding key can't be parsed as
+ /// a ``T``, return an ``UnparseableIntegerOptionError``.
+ template <typename T>
+ std::enable_if_t<std::is_integral<T>::value, llvm::Expected<T>>
+ get(StringRef LocalName) const {
+ if (llvm::Expected<std::string> Value = get(LocalName)) {
+ T Result{};
+ if (!StringRef(*Value).getAsInteger(10, Result))
+ return Result;
+ return llvm::make_error<UnparseableIntegerOptionError>(
+ (NamePrefix + LocalName).str(), *Value);
+ } else
+ return std::move(Value.takeError());
+ }
+
+ /// Read a named option from the ``Context`` and parse it as an
+ /// integral type ``T``.
+ ///
+ /// Reads the option with the check-local name \p LocalName from the
+ /// ``CheckOptions``. If the corresponding key is not present or it can't be
+ /// parsed as a ``T``, returns \p Default.
template <typename T>
std::enable_if_t<std::is_integral<T>::value, T> get(StringRef LocalName,
T Default) const {
- std::string Value = get(LocalName, "");
- T Result = Default;
- if (!Value.empty())
- StringRef(Value).getAsInteger(10, Result);
- return Result;
+ if (llvm::Expected<T> ValueOr = get<T>(LocalName))
+ return *ValueOr;
+ else
+ logErrToStdErr(ValueOr.takeError());
+ return Default;
}
/// Read a named option from the ``Context`` and parse it as an
@@ -164,15 +272,157 @@ class ClangTidyCheck : public ast_matchers::MatchFinder::MatchCallback {
/// Reads the option with the check-local name \p LocalName from local or
/// 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 Default.
+ /// present either, returns a ``MissingOptionError``. If the corresponding
+ /// key can't be parsed as a ``T``, return an
+ /// ``UnparseableIntegerOptionError``.
+ template <typename T>
+ std::enable_if_t<std::is_integral<T>::value, llvm::Expected<T>>
+ getLocalOrGlobal(StringRef LocalName) const {
+ llvm::Expected<std::string> ValueOr = get(LocalName);
+ bool IsGlobal = false;
+ if (!ValueOr) {
+ IsGlobal = true;
+ llvm::consumeError(ValueOr.takeError());
+ ValueOr = getLocalOrGlobal(LocalName);
+ if (!ValueOr)
+ return std::move(ValueOr.takeError());
+ }
+ T Result{};
+ if (!StringRef(*ValueOr).getAsInteger(10, Result))
+ return Result;
+ return llvm::make_error<UnparseableIntegerOptionError>(
+ (IsGlobal ? LocalName.str() : (NamePrefix + LocalName).str()),
+ *ValueOr);
+ }
+
+ /// Read a named option from the ``Context`` and parse it as an
+ /// integral type ``T``.
+ ///
+ /// Reads the option with the check-local name \p LocalName from local or
+ /// global ``CheckOptions``. Gets local option first. If local is not
+ /// present, falls back to get global option. If global option is not
+ /// present either or it can't be parsed as a ``T``, returns \p Default.
template <typename T>
std::enable_if_t<std::is_integral<T>::value, T>
getLocalOrGlobal(StringRef LocalName, T Default) const {
- std::string Value = getLocalOrGlobal(LocalName, "");
- T Result = Default;
- if (!Value.empty())
- StringRef(Value).getAsInteger(10, Result);
- return Result;
+ if (llvm::Expected<T> ValueOr = getLocalOrGlobal<T>(LocalName))
+ return *ValueOr;
+ else
+ logErrToStdErr(ValueOr.takeError());
+ return Default;
+ }
+
+ /// Read a named option from the ``Context`` and parse it as a bool.
+ ///
+ /// Reads the option with the check-local name \p LocalName from the
+ /// ``CheckOptions``. If the corresponding key is not present, returns
+ /// a ``MissingOptionError``. If the corresponding key can't be parsed as
+ /// a bool, return an ``UnparseableIntegerOptionError``.
+ template <> llvm::Expected<bool> get<bool>(StringRef LocalName) const;
+
+ /// Read a named option from the ``Context`` and parse it as a bool.
+ ///
+ /// Reads the option with the check-local name \p LocalName from the
+ /// ``CheckOptions``. If the corresponding key is not present or it can't be
+ /// parsed as a bool, returns \p Default.
+ template <> bool get<bool>(StringRef LocalName, bool Default) const;
+
+ /// Read a named option from the ``Context`` and parse it as a bool.
+ ///
+ /// Reads the option with the check-local name \p LocalName from local or
+ /// 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 a ``MissingOptionError``. If the corresponding
+ /// key can't be parsed as a bool, return an
+ /// ``UnparseableIntegerOptionError``.
+ template <>
+ llvm::Expected<bool> getLocalOrGlobal<bool>(StringRef LocalName) const;
+
+ /// Read a named option from the ``Context`` and parse it as a bool.
+ ///
+ /// Reads the option with the check-local name \p LocalName from local or
+ /// global ``CheckOptions``. Gets local option first. If local is not
+ /// present, falls back to get global option. If global option is not
+ /// present either or it can't be parsed as a bool, returns \p Default.
+ template <>
+ bool getLocalOrGlobal<bool>(StringRef LocalName, bool Default) const;
+
+ /// Read a named option from the ``Context`` and parse it as an
+ /// enum type ``T`` using the \p Mapping provided. If \p IgnoreCase is set,
+ /// it will search the mapping ignoring the case.
+ ///
+ /// Reads the option with the check-local name \p LocalName from the
+ /// ``CheckOptions``. If the corresponding key is not present, returns a
+ /// ``MissingOptionError``. If the key can't be parsed as a ``T`` returns a
+ /// ``UnparseableEnumOptionError``.
+ template <typename T>
+ std::enable_if_t<std::is_enum<T>::value, llvm::Expected<T>>
+ get(StringRef LocalName, ArrayRef<std::pair<StringRef, T>> Mapping,
+ bool IgnoreCase = false) {
+ if (llvm::Expected<int64_t> ValueOr = getEnumInt(
+ LocalName, typeEraseMapping(Mapping), false, IgnoreCase))
+ return static_cast<T>(*ValueOr);
+ else
+ return std::move(ValueOr.takeError());
+ }
+
+ /// Read a named option from the ``Context`` and parse it as an
+ /// enum type ``T`` using the \p Mapping provided. If \p IgnoreCase is set,
+ /// it will search the mapping ignoring the case.
+ ///
+ /// Reads the option with the check-local name \p LocalName from the
+ /// ``CheckOptions``. If the corresponding key is not present or it can't be
+ /// parsed as a ``T``, returns \p Default.
+ template <typename T>
+ std::enable_if_t<std::is_enum<T>::value, T>
+ get(StringRef LocalName, ArrayRef<std::pair<StringRef, T>> Mapping,
+ T Default, bool IgnoreCase = false) {
+ if (auto ValueOr = get(LocalName, Mapping, IgnoreCase))
+ return *ValueOr;
+ else
+ logErrToStdErr(ValueOr.takeError());
+ return Default;
+ }
+
+ /// Read a named option from the ``Context`` and parse it as an
+ /// enum type ``T`` using the \p Mapping provided. If \p IgnoreCase is set,
+ /// it will search the mapping ignoring the case.
+ ///
+ /// Reads the option with the check-local name \p LocalName from local or
+ /// 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 a ``MissingOptionError``. If the key can't be
+ /// parsed as a ``T`` returns a ``UnparseableEnumOptionError``.
+ template <typename T>
+ std::enable_if_t<std::is_enum<T>::value, llvm::Expected<T>>
+ getLocalOrGlobal(StringRef LocalName,
+ ArrayRef<std::pair<StringRef, T>> Mapping,
+ bool IgnoreCase = false) {
+ if (llvm::Expected<int64_t> ValueOr = getEnumInt(
+ LocalName, typeEraseMapping(Mapping), true, IgnoreCase))
+ return static_cast<T>(*ValueOr);
+ else
+ return std::move(ValueOr.takeError());
+ }
+
+ /// Read a named option from the ``Context`` and parse it as an
+ /// enum type ``T`` using the \p Mapping provided. If \p IgnoreCase is set,
+ /// it will search the mapping ignoring the case.
+ ///
+ /// Reads the option with the check-local name \p LocalName from local or
+ /// global ``CheckOptions``. Gets local option first. If local is not
+ /// present, falls back to get global option. If global option is not
+ /// present either or it can't be parsed as a ``T``, returns \p Default.
+ template <typename T>
+ std::enable_if_t<std::is_enum<T>::value, T>
+ getLocalOrGlobal(StringRef LocalName,
+ ArrayRef<std::pair<StringRef, T>> Mapping, T Default,
+ bool IgnoreCase = false) {
+ if (auto ValueOr = getLocalOrGlobal(LocalName, Mapping, IgnoreCase))
+ return *ValueOr;
+ else
+ logErrToStdErr(ValueOr.takeError());
+ return Default;
}
/// Stores an option with the check-local name \p LocalName with
@@ -185,7 +435,41 @@ class ClangTidyCheck : public ast_matchers::MatchFinder::MatchCallback {
void store(ClangTidyOptions::OptionMap &Options, StringRef LocalName,
int64_t Value) const;
+ /// Stores an option with the check-local name \p LocalName as the string
+ /// representation of the Enum \p Value using the \p Mapping to \p Options.
+ template <typename T>
+ std::enable_if_t<std::is_enum<T>::value>
+ store(ClangTidyOptions::OptionMap &Options, StringRef LocalName, T Value,
+ ArrayRef<std::pair<StringRef, T>> Mapping) {
+ auto Iter = llvm::find_if(
+ Mapping, [&](const std::pair<StringRef, T> &NameAndEnum) {
+ return NameAndEnum.second == Value;
+ });
+ assert(Iter != Mapping.end() && "Unknown Case Value");
+ store(Options, LocalName, Iter->first);
+ }
+
private:
+ using NameAndValue = std::pair<StringRef, int64_t>;
+
+ llvm::Expected<int64_t> getEnumInt(StringRef LocalName,
+ ArrayRef<NameAndValue> Mapping,
+ bool CheckGlobal, bool IgnoreCase);
+
+ template <typename T>
+ std::enable_if_t<std::is_enum<T>::value, std::vector<NameAndValue>>
+ typeEraseMapping(ArrayRef<std::pair<StringRef, T>> Mapping) {
+ std::vector<NameAndValue> Result;
+ Result.reserve(Mapping.size());
+ for (auto &MappedItem : Mapping) {
+ Result.emplace_back(MappedItem.first,
+ static_cast<int64_t>(MappedItem.second));
+ }
+ return Result;
+ }
+
+ static void logErrToStdErr(llvm::Error &&Err);
+
std::string NamePrefix;
const ClangTidyOptions::OptionMap &CheckOptions;
};
diff --git a/clang-tools-extra/unittests/clang-tidy/ClangTidyOptionsTest.cpp b/clang-tools-extra/unittests/clang-tidy/ClangTidyOptionsTest.cpp
index f50672ca5eca..b40b92e8828b 100644
--- a/clang-tools-extra/unittests/clang-tidy/ClangTidyOptionsTest.cpp
+++ b/clang-tools-extra/unittests/clang-tidy/ClangTidyOptionsTest.cpp
@@ -1,6 +1,8 @@
#include "ClangTidyOptions.h"
-#include "gtest/gtest.h"
+#include "ClangTidyCheck.h"
+#include "ClangTidyDiagnosticConsumer.h"
#include "llvm/ADT/StringExtras.h"
+#include "gtest/gtest.h"
namespace clang {
namespace tidy {
@@ -97,6 +99,172 @@ TEST(ParseConfiguration, MergeConfigurations) {
llvm::join(Options.ExtraArgsBefore->begin(),
Options.ExtraArgsBefore->end(), ","));
}
+
+class TestCheck : public ClangTidyCheck {
+public:
+ TestCheck(ClangTidyContext *Context) : ClangTidyCheck("test", Context) {}
+
+ template <typename... Args> auto getLocal(Args &&... Arguments) {
+ return Options.get(std::forward<Args>(Arguments)...);
+ }
+
+ template <typename... Args> auto getGlobal(Args &&... Arguments) {
+ return Options.getLocalOrGlobal(std::forward<Args>(Arguments)...);
+ }
+
+ template <typename IntType = int, typename... Args>
+ auto getIntLocal(Args &&... Arguments) {
+ return Options.get<IntType>(std::forward<Args>(Arguments)...);
+ }
+
+ template <typename IntType = int, typename... Args>
+ auto getIntGlobal(Args &&... Arguments) {
+ return Options.getLocalOrGlobal<IntType>(std::forward<Args>(Arguments)...);
+ }
+};
+
+#define CHECK_VAL(Value, Expected) \
+ do { \
+ auto Item = Value; \
+ ASSERT_TRUE(!!Item); \
+ EXPECT_EQ(*Item, Expected); \
+ } while (false)
+
+#define CHECK_ERROR(Value, ErrorType, ExpectedMessage) \
+ do { \
+ auto Item = Value; \
+ ASSERT_FALSE(Item); \
+ ASSERT_TRUE(Item.errorIsA<ErrorType>()); \
+ ASSERT_FALSE(llvm::handleErrors( \
+ Item.takeError(), [&](const ErrorType &Err) -> llvm::Error { \
+ EXPECT_EQ(Err.message(), ExpectedMessage); \
+ return llvm::Error::success(); \
+ })); \
+ } while (false)
+
+TEST(CheckOptionsValidation, MissingOptions) {
+ ClangTidyOptions Options;
+ ClangTidyContext Context(std::make_unique<DefaultOptionsProvider>(
+ ClangTidyGlobalOptions(), Options));
+ TestCheck TestCheck(&Context);
+ CHECK_ERROR(TestCheck.getLocal("Opt"), MissingOptionError,
+ "option not found 'test.Opt'");
+ EXPECT_EQ(TestCheck.getLocal("Opt", "Unknown"), "Unknown");
+}
+
+TEST(CheckOptionsValidation, ValidIntOptions) {
+ ClangTidyOptions Options;
+ auto &CheckOptions = Options.CheckOptions;
+ CheckOptions["test.IntExpected1"] = "1";
+ CheckOptions["test.IntExpected2"] = "1WithMore";
+ CheckOptions["test.IntExpected3"] = "NoInt";
+ CheckOptions["GlobalIntExpected1"] = "1";
+ CheckOptions["GlobalIntExpected2"] = "NoInt";
+ CheckOptions["test.DefaultedIntInvalid"] = "NoInt";
+ CheckOptions["GlobalIntInvalid"] = "NoInt";
+ CheckOptions["test.BoolITrueValue"] = "1";
+ CheckOptions["test.BoolIFalseValue"] = "0";
+ CheckOptions["test.BoolTrueValue"] = "true";
+ CheckOptions["test.BoolFalseValue"] = "false";
+ CheckOptions["test.BoolUnparseable"] = "Nothing";
+ CheckOptions["test.BoolCaseMismatch"] = "True";
+
+ ClangTidyContext Context(std::make_unique<DefaultOptionsProvider>(
+ ClangTidyGlobalOptions(), Options));
+ TestCheck TestCheck(&Context);
+
+#define CHECK_ERROR_INT(Name, Expected) \
+ CHECK_ERROR(Name, UnparseableIntegerOptionError, Expected)
+
+ CHECK_VAL(TestCheck.getIntLocal("IntExpected1"), 1);
+ CHECK_VAL(TestCheck.getIntGlobal("GlobalIntExpected1"), 1);
+ CHECK_ERROR_INT(TestCheck.getIntLocal("IntExpected2"),
+ "invalid configuration value '1WithMore' for option "
+ "'test.IntExpected2'; expected an integer value");
+ CHECK_ERROR_INT(TestCheck.getIntLocal("IntExpected3"),
+ "invalid configuration value 'NoInt' for option "
+ "'test.IntExpected3'; expected an integer value");
+ CHECK_ERROR_INT(TestCheck.getIntGlobal("GlobalIntExpected2"),
+ "invalid configuration value 'NoInt' for option "
+ "'GlobalIntExpected2'; expected an integer value");
+ ASSERT_EQ(TestCheck.getIntLocal("DefaultedIntInvalid", 1), 1);
+ ASSERT_EQ(TestCheck.getIntGlobal("GlobalIntInvalid", 1), 1);
+
+ CHECK_VAL(TestCheck.getIntLocal<bool>("BoolITrueValue"), true);
+ CHECK_VAL(TestCheck.getIntLocal<bool>("BoolIFalseValue"), false);
+ CHECK_VAL(TestCheck.getIntLocal<bool>("BoolTrueValue"), true);
+ CHECK_VAL(TestCheck.getIntLocal<bool>("BoolFalseValue"), false);
+ CHECK_ERROR_INT(TestCheck.getIntLocal<bool>("BoolUnparseable"),
+ "invalid configuration value 'Nothing' for option "
+ "'test.BoolUnparseable'; expected a bool");
+ CHECK_ERROR_INT(TestCheck.getIntLocal<bool>("BoolCaseMismatch"),
+ "invalid configuration value 'True' for option "
+ "'test.BoolCaseMismatch'; expected a bool");
+
+#undef CHECK_ERROR_INT
+}
+
+TEST(ValidConfiguration, ValidEnumOptions) {
+
+ enum class Colours { Red, Orange, Yellow, Green, Blue, Indigo, Violet };
+ static constexpr std::pair<StringRef, Colours> Mapping[] = {
+ {"Red", Colours::Red}, {"Orange", Colours::Orange},
+ {"Yellow", Colours::Yellow}, {"Green", Colours::Green},
+ {"Blue", Colours::Blue}, {"Indigo", Colours::Indigo},
+ {"Violet", Colours::Violet}};
+ static const auto Map = makeArrayRef(Mapping);
+
+ ClangTidyOptions Options;
+ auto &CheckOptions = Options.CheckOptions;
+
+ CheckOptions["test.Valid"] = "Red";
+ CheckOptions["test.Invalid"] = "Scarlet";
+ CheckOptions["test.ValidWrongCase"] = "rED";
+ CheckOptions["test.NearMiss"] = "Oragne";
+ CheckOptions["GlobalValid"] = "Violet";
+ CheckOptions["GlobalInvalid"] = "Purple";
+ CheckOptions["GlobalValidWrongCase"] = "vIOLET";
+ CheckOptions["GlobalNearMiss"] = "Yelow";
+
+ ClangTidyContext Context(std::make_unique<DefaultOptionsProvider>(
+ ClangTidyGlobalOptions(), Options));
+ TestCheck TestCheck(&Context);
+
+#define CHECK_ERROR_ENUM(Name, Expected) \
+ CHECK_ERROR(Name, UnparseableEnumOptionError, Expected)
+
+ CHECK_VAL(TestCheck.getLocal("Valid", Map), Colours::Red);
+ CHECK_VAL(TestCheck.getGlobal("GlobalValid", Map), Colours::Violet);
+ CHECK_VAL(TestCheck.getLocal("ValidWrongCase", Map, /*IgnoreCase*/ true),
+ Colours::Red);
+ CHECK_VAL(
+ TestCheck.getGlobal("GlobalValidWrongCase", Map, /*IgnoreCase*/ true),
+ Colours::Violet);
+ CHECK_ERROR_ENUM(TestCheck.getLocal("Invalid", Map),
+ "invalid configuration value "
+ "'Scarlet' for option 'test.Invalid'");
+ CHECK_ERROR_ENUM(TestCheck.getLocal("ValidWrongCase", Map),
+ "invalid configuration value 'rED' for option "
+ "'test.ValidWrongCase'; did you mean 'Red'?");
+ CHECK_ERROR_ENUM(TestCheck.getLocal("NearMiss", Map),
+ "invalid configuration value 'Oragne' for option "
+ "'test.NearMiss'; did you mean 'Orange'?");
+ CHECK_ERROR_ENUM(TestCheck.getGlobal("GlobalInvalid", Map),
+ "invalid configuration value "
+ "'Purple' for option 'GlobalInvalid'");
+ CHECK_ERROR_ENUM(TestCheck.getGlobal("GlobalValidWrongCase", Map),
+ "invalid configuration value 'vIOLET' for option "
+ "'GlobalValidWrongCase'; did you mean 'Violet'?");
+ CHECK_ERROR_ENUM(TestCheck.getGlobal("GlobalNearMiss", Map),
+ "invalid configuration value 'Yelow' for option "
+ "'GlobalNearMiss'; did you mean 'Yellow'?");
+
+#undef CHECK_ERROR_ENUM
+}
+
+#undef CHECK_VAL
+#undef CHECK_ERROR
+
} // namespace test
} // namespace tidy
} // namespace clang
More information about the cfe-commits
mailing list