diff --git a/clang-tools-extra/clang-tidy/ClangTidyOptions.h b/clang-tools-extra/clang-tidy/ClangTidyOptions.h --- a/clang-tools-extra/clang-tidy/ClangTidyOptions.h +++ b/clang-tools-extra/clang-tidy/ClangTidyOptions.h @@ -14,6 +14,7 @@ #include "llvm/ADT/StringMap.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/ErrorOr.h" +#include "llvm/Support/MemoryBufferRef.h" #include "llvm/Support/VirtualFileSystem.h" #include #include @@ -311,6 +312,11 @@ llvm::ErrorOr parseConfiguration(llvm::MemoryBufferRef Config); +using DiagCallback = llvm::function_ref; + +llvm::ErrorOr +parseConfigurationWithDiags(llvm::MemoryBufferRef Config, DiagCallback Handler); + /// Serializes configuration to a YAML-encoded string. std::string configurationAsText(const ClangTidyOptions &Options); diff --git a/clang-tools-extra/clang-tidy/ClangTidyOptions.cpp b/clang-tools-extra/clang-tidy/ClangTidyOptions.cpp --- a/clang-tools-extra/clang-tidy/ClangTidyOptions.cpp +++ b/clang-tools-extra/clang-tidy/ClangTidyOptions.cpp @@ -13,6 +13,7 @@ #include "llvm/Support/Debug.h" #include "llvm/Support/Errc.h" #include "llvm/Support/FileSystem.h" +#include "llvm/Support/MemoryBufferRef.h" #include "llvm/Support/Path.h" #include "llvm/Support/YAMLTraits.h" #include "llvm/Support/raw_ostream.h" @@ -390,6 +391,22 @@ return Options; } +static void diagHandlerImpl(const llvm::SMDiagnostic &Diag, void *Ctx) { + (*reinterpret_cast(Ctx))(Diag); +}; + +llvm::ErrorOr +parseConfigurationWithDiags(llvm::MemoryBufferRef Config, + DiagCallback Handler) { + llvm::yaml::Input Input(Config, nullptr, Handler ? diagHandlerImpl : nullptr, + &Handler); + ClangTidyOptions Options; + Input >> Options; + if (Input.error()) + return Input.error(); + return Options; +} + std::string configurationAsText(const ClangTidyOptions &Options) { std::string Text; llvm::raw_string_ostream Stream(Text); diff --git a/clang-tools-extra/unittests/clang-tidy/CMakeLists.txt b/clang-tools-extra/unittests/clang-tidy/CMakeLists.txt --- a/clang-tools-extra/unittests/clang-tidy/CMakeLists.txt +++ b/clang-tools-extra/unittests/clang-tidy/CMakeLists.txt @@ -1,6 +1,7 @@ set(LLVM_LINK_COMPONENTS FrontendOpenMP Support + TestingSupport ) get_filename_component(CLANG_LINT_SOURCE_DIR 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 @@ -2,6 +2,9 @@ #include "ClangTidyCheck.h" #include "ClangTidyDiagnosticConsumer.h" #include "llvm/ADT/StringExtras.h" +#include "llvm/Support/ScopedPrinter.h" +#include "llvm/Testing/Support/Annotations.h" +#include "gmock/gmock.h" #include "gtest/gtest.h" namespace clang { @@ -123,6 +126,100 @@ EXPECT_TRUE(*Options.UseColor); } +namespace { +class DiagCollecter { +public: + struct Diag { + private: + static size_t posToOffset(const llvm::SMLoc Loc, + const llvm::SourceMgr *Src) { + return Loc.getPointer() - + Src->getMemoryBuffer(Src->FindBufferContainingLoc(Loc)) + ->getBufferStart(); + } + + public: + Diag(const llvm::SMDiagnostic &D) + : Message(D.getMessage()), Kind(D.getKind()), + Pos(posToOffset(D.getLoc(), D.getSourceMgr())) { + if (!D.getRanges().empty()) { + // Ranges are stored as column numbers on the line that has the error. + unsigned Offset = Pos - D.getColumnNo(); + Range.emplace(); + Range->Begin = Offset + D.getRanges().front().first, + Range->End = Offset + D.getRanges().front().second; + } + } + std::string Message; + llvm::SourceMgr::DiagKind Kind; + size_t Pos; + Optional Range; + + friend void PrintTo(const Diag &D, std::ostream *OS) { + *OS << (D.Kind == llvm::SourceMgr::DK_Error ? "error: " : "warning: ") + << D.Message << "@" << llvm::to_string(D.Pos); + if (D.Range) + *OS << ":[" << D.Range->Begin << ", " << D.Range->End << ")"; + } + }; + + DiagCollecter() = default; + DiagCollecter(const DiagCollecter &) = delete; + + std::function + getCallback(bool Clear = true) & { + if (Clear) + Diags.clear(); + return [&](const llvm::SMDiagnostic &Diag) { Diags.emplace_back(Diag); }; + } + + std::function + getCallback(bool Clear = true) && = delete; + + llvm::ArrayRef getDiags() const { return Diags; } + +private: + std::vector Diags; +}; + +MATCHER_P(DiagMessage, M, "") { return arg.Message == M; } +MATCHER_P(DiagKind, K, "") { return arg.Kind == K; } +MATCHER_P(DiagPos, P, "") { return arg.Pos == P; } +MATCHER_P(DiagRange, P, "") { return arg.Range && *arg.Range == P; } +} // namespace + +using ::testing::AllOf; +using ::testing::ElementsAre; + +TEST(ParseConfiguration, CollectDiags) { + DiagCollecter Collector; + auto ParseWithDiags = [&](llvm::StringRef Buffer) { + return parseConfigurationWithDiags(llvm::MemoryBufferRef(Buffer, "Options"), + Collector.getCallback()); + }; + llvm::Annotations Options(R"( + [[Check]]: llvm-include-order + )"); + llvm::ErrorOr ParsedOpt = ParseWithDiags(Options.code()); + EXPECT_TRUE(!ParsedOpt); + EXPECT_THAT(Collector.getDiags(), + testing::ElementsAre(AllOf(DiagMessage("unknown key 'Check'"), + DiagKind(llvm::SourceMgr::DK_Error), + DiagPos(Options.range().Begin), + DiagRange(Options.range())))); + + Options = llvm::Annotations(R"( + UseColor: [[NotABool]] + )"); + ParsedOpt = ParseWithDiags(Options.code()); + EXPECT_TRUE(!ParsedOpt); + EXPECT_THAT(Collector.getDiags(), + testing::ElementsAre(AllOf(DiagMessage("invalid boolean"), + DiagKind(llvm::SourceMgr::DK_Error), + DiagPos(Options.range().Begin), + DiagRange(Options.range())))); +} + namespace { class TestCheck : public ClangTidyCheck { public: