diff --git a/clang-tools-extra/clang-tidy/ClangTidyCheck.h b/clang-tools-extra/clang-tidy/ClangTidyCheck.h --- 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 #include #include @@ -25,6 +27,65 @@ namespace tidy { +template class OptionError : public llvm::ErrorInfo { + std::error_code convertToErrorCode() const override { + return llvm::inconvertibleErrorCode(); + } + +public: + void log(raw_ostream &OS) const override { OS << this->message(); } +}; + +class MissingOptionError : public OptionError { +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 { +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 SuggestedValue; +}; + +class UnparseableIntegerOptionError + : public OptionError { +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 @@ -131,31 +192,78 @@ /// /// 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 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 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 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 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 + std::enable_if_t::value, llvm::Expected> + get(StringRef LocalName) const { + if (llvm::Expected Value = get(LocalName)) { + T Result{}; + if (!StringRef(*Value).getAsInteger(10, Result)) + return Result; + return llvm::make_error( + (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 std::enable_if_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 ValueOr = get(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 @@ /// 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 + std::enable_if_t::value, llvm::Expected> + getLocalOrGlobal(StringRef LocalName) const { + llvm::Expected 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( + (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 std::enable_if_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 ValueOr = getLocalOrGlobal(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 get(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(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 getLocalOrGlobal(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(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 + std::enable_if_t::value, llvm::Expected> + get(StringRef LocalName, ArrayRef> Mapping, + bool IgnoreCase = false) { + if (llvm::Expected ValueOr = getEnumInt( + LocalName, typeEraseMapping(Mapping), false, IgnoreCase)) + return static_cast(*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 + std::enable_if_t::value, T> + get(StringRef LocalName, ArrayRef> 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 + std::enable_if_t::value, llvm::Expected> + getLocalOrGlobal(StringRef LocalName, + ArrayRef> Mapping, + bool IgnoreCase = false) { + if (llvm::Expected ValueOr = getEnumInt( + LocalName, typeEraseMapping(Mapping), true, IgnoreCase)) + return static_cast(*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 + std::enable_if_t::value, T> + getLocalOrGlobal(StringRef LocalName, + ArrayRef> 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 @@ 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 + std::enable_if_t::value> + store(ClangTidyOptions::OptionMap &Options, StringRef LocalName, T Value, + ArrayRef> Mapping) { + auto Iter = llvm::find_if( + Mapping, [&](const std::pair &NameAndEnum) { + return NameAndEnum.second == Value; + }); + assert(Iter != Mapping.end() && "Unknown Case Value"); + store(Options, LocalName, Iter->first); + } + private: + using NameAndValue = std::pair; + + llvm::Expected getEnumInt(StringRef LocalName, + ArrayRef Mapping, + bool CheckGlobal, bool IgnoreCase); + + template + std::enable_if_t::value, std::vector> + typeEraseMapping(ArrayRef> Mapping) { + std::vector Result; + Result.reserve(Mapping.size()); + for (auto &MappedItem : Mapping) { + Result.emplace_back(MappedItem.first, + static_cast(MappedItem.second)); + } + return Result; + } + + static void logErrToStdErr(llvm::Error &&Err); + std::string NamePrefix; const ClangTidyOptions::OptionMap &CheckOptions; }; 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 @@ -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 @@ const ClangTidyOptions::OptionMap &CheckOptions) : NamePrefix(CheckName.str() + "."), CheckOptions(CheckOptions) {} -std::string ClangTidyCheck::OptionsView::get(StringRef LocalName, - StringRef Default) const { +llvm::Expected +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((NamePrefix + LocalName).str()); } -std::string -ClangTidyCheck::OptionsView::getLocalOrGlobal(StringRef LocalName, - StringRef Default) const { +llvm::Expected +ClangTidyCheck::OptionsView::getLocalOrGlobal(StringRef LocalName) const { auto Iter = CheckOptions.find(NamePrefix + LocalName.str()); if (Iter != CheckOptions.end()) return Iter->second; @@ -52,7 +85,65 @@ Iter = CheckOptions.find(LocalName.str()); if (Iter != CheckOptions.end()) return Iter->second; - return std::string(Default); + return llvm::make_error((NamePrefix + LocalName).str()); +} + +static llvm::Expected 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(LookupName.str(), + Value.str(), true); +} + +template <> +llvm::Expected +ClangTidyCheck::OptionsView::get(StringRef LocalName) const { + llvm::Expected ValueOr = get(LocalName); + if (ValueOr) + return getAsBool(*ValueOr, NamePrefix + LocalName); + return ValueOr.takeError(); +} + +template <> +bool ClangTidyCheck::OptionsView::get(StringRef LocalName, + bool Default) const { + llvm::Expected ValueOr = get(LocalName); + if (ValueOr) + return *ValueOr; + logErrToStdErr(ValueOr.takeError()); + return Default; +} + +template <> +llvm::Expected +ClangTidyCheck::OptionsView::getLocalOrGlobal(StringRef LocalName) const { + llvm::Expected 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(StringRef LocalName, + bool Default) const { + llvm::Expected ValueOr = getLocalOrGlobal(LocalName); + if (ValueOr) + return *ValueOr; + logErrToStdErr(ValueOr.takeError()); + return Default; } void ClangTidyCheck::OptionsView::store(ClangTidyOptions::OptionMap &Options, @@ -67,5 +158,49 @@ store(Options, LocalName, llvm::itostr(Value)); } +llvm::Expected ClangTidyCheck::OptionsView::getEnumInt( + StringRef LocalName, ArrayRef> 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((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( + Iter->first, Iter->second, std::string(Closest)); + return llvm::make_error(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/unittests/clang-tidy/ClangTidyOptionsTest.cpp b/clang-tools-extra/unittests/clang-tidy/ClangTidyOptionsTest.cpp --- 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 @@ llvm::join(Options.ExtraArgsBefore->begin(), Options.ExtraArgsBefore->end(), ",")); } + +class TestCheck : public ClangTidyCheck { +public: + TestCheck(ClangTidyContext *Context) : ClangTidyCheck("test", Context) {} + + template auto getLocal(Args &&... Arguments) { + return Options.get(std::forward(Arguments)...); + } + + template auto getGlobal(Args &&... Arguments) { + return Options.getLocalOrGlobal(std::forward(Arguments)...); + } + + template + auto getIntLocal(Args &&... Arguments) { + return Options.get(std::forward(Arguments)...); + } + + template + auto getIntGlobal(Args &&... Arguments) { + return Options.getLocalOrGlobal(std::forward(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()); \ + 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( + 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( + 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("BoolITrueValue"), true); + CHECK_VAL(TestCheck.getIntLocal("BoolIFalseValue"), false); + CHECK_VAL(TestCheck.getIntLocal("BoolTrueValue"), true); + CHECK_VAL(TestCheck.getIntLocal("BoolFalseValue"), false); + CHECK_ERROR_INT(TestCheck.getIntLocal("BoolUnparseable"), + "invalid configuration value 'Nothing' for option " + "'test.BoolUnparseable'; expected a bool"); + CHECK_ERROR_INT(TestCheck.getIntLocal("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 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( + 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