[clang-tidy] Add a diagnostic callback to parseConfiguration

Currently errors detected when parsing the YAML for .clang-tidy files are always printed to errs.
For clang-tidy binary workflows this usually isn't an issue, but using clang-tidy as a library for integrations may want to handle displaying those errors in their own specific way.

Reviewed By: aaron.ballman

Differential Revision: https://reviews.llvm.org/D92920
This commit is contained in:
Nathan James 2020-12-17 00:24:58 +00:00
parent b17a181563
commit ddffcdf0a6
No known key found for this signature in database
GPG Key ID: CC007AFCDA90AA5F
4 changed files with 121 additions and 0 deletions

View File

@ -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 @@ parseConfiguration(llvm::MemoryBufferRef Config) {
return Options;
}
static void diagHandlerImpl(const llvm::SMDiagnostic &Diag, void *Ctx) {
(*reinterpret_cast<DiagCallback *>(Ctx))(Diag);
};
llvm::ErrorOr<ClangTidyOptions>
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);

View File

@ -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 <functional>
#include <string>
@ -311,6 +312,11 @@ std::error_code parseLineFilter(llvm::StringRef LineFilter,
llvm::ErrorOr<ClangTidyOptions>
parseConfiguration(llvm::MemoryBufferRef Config);
using DiagCallback = llvm::function_ref<void(const llvm::SMDiagnostic &)>;
llvm::ErrorOr<ClangTidyOptions>
parseConfigurationWithDiags(llvm::MemoryBufferRef Config, DiagCallback Handler);
/// Serializes configuration to a YAML-encoded string.
std::string configurationAsText(const ClangTidyOptions &Options);

View File

@ -1,6 +1,7 @@
set(LLVM_LINK_COMPONENTS
FrontendOpenMP
Support
TestingSupport
)
get_filename_component(CLANG_LINT_SOURCE_DIR

View File

@ -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 @@ TEST(ParseConfiguration, MergeConfigurations) {
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<llvm::Annotations::Range> 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<void(const llvm::SMDiagnostic &)>
getCallback(bool Clear = true) & {
if (Clear)
Diags.clear();
return [&](const llvm::SMDiagnostic &Diag) { Diags.emplace_back(Diag); };
}
std::function<void(const llvm::SMDiagnostic &)>
getCallback(bool Clear = true) && = delete;
llvm::ArrayRef<Diag> getDiags() const { return Diags; }
private:
std::vector<Diag> 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<ClangTidyOptions> 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: