blob: 75c534ab315e697d946271e61eeb79038c58632e [file] [log] [blame]
// Part of the Crubit project, under the Apache License v2.0 with LLVM
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#include "rs_bindings_from_cc/recording_diagnostic_consumer.h"
#include <cassert>
#include <functional>
#include <memory>
#include <string>
#include <utility>
#include "absl/log/log.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_format.h"
#include "absl/strings/str_join.h"
#include "absl/strings/string_view.h"
#include "absl/strings/strip.h"
#include "clang/Basic/Diagnostic.h"
#include "clang/Basic/SourceManager.h"
#include "llvm/ADT/SmallString.h"
namespace crubit {
// Clang doesn't provide a public utility method to stringify the diagnostic
// level, so we roll our own here.
static ::absl::string_view GetDiagnosticLevelName(
clang::DiagnosticsEngine::Level level) {
switch (level) {
case clang::DiagnosticsEngine::Ignored:
return "ignored";
case clang::DiagnosticsEngine::Remark:
return "remark";
case clang::DiagnosticsEngine::Note:
return "note";
case clang::DiagnosticsEngine::Warning:
return "warning";
case clang::DiagnosticsEngine::Error:
return "error";
case clang::DiagnosticsEngine::Fatal:
return "fatal error";
default:
LOG(DFATAL) << "Unknown diagnostic level: " << static_cast<int>(level);
return "<unknown diagnostic level>";
};
}
std::string RecordingDiagnosticConsumer::Diagnostic::FormattedDiagnostics()
const {
constexpr absl::string_view kDiagnosticFormat = "%s:%u:%u: %s: %s";
return absl::StrFormat(kDiagnosticFormat,
// `getFileName` has the form `./relative/path.h`.
absl::StripPrefix(source_location.getFilename(), "./"),
source_location.getLine(), source_location.getColumn(),
GetDiagnosticLevelName(diagnostic_level),
diagnostic.str());
};
void RecordingDiagnosticConsumer::HandleDiagnostic(
clang::DiagnosticsEngine::Level diagnostic_level,
const clang::Diagnostic& info) {
clang::DiagnosticConsumer::HandleDiagnostic(diagnostic_level, info);
llvm::SmallString<64> diagnostic;
info.FormatDiagnostic(diagnostic);
auto source_loc = info.getLocation();
auto presumed_source_loc = info.getSourceManager().getPresumedLoc(source_loc);
diagnostics_.push_back({info.getID(), diagnostic_level, presumed_source_loc,
std::move(diagnostic)});
}
void RecordingDiagnosticConsumer::clear() {
clang::DiagnosticConsumer::clear();
diagnostics_.clear();
}
std::string RecordingDiagnosticConsumer::ConcatenatedDiagnostics(
absl::string_view prefix) const {
// In reverse order, so that it's easier to backtrace.
std::string concatenated =
absl::StrJoin(diagnostics_.rbegin(), diagnostics_.rend(), "\n",
[](std::string* out, const Diagnostic diagnostic) {
absl::StrAppend(out, diagnostic.FormattedDiagnostics());
});
if (concatenated.empty()) {
return concatenated;
}
return absl::StrCat(prefix, concatenated);
}
RecordingDiagnosticConsumer RecordDiagnostics(
clang::DiagnosticsEngine& diagnostic_engine,
std::function<void(void)> callback) {
RecordingDiagnosticConsumer diagnostic_recorder;
std::unique_ptr<clang::DiagnosticConsumer> original_consumer =
diagnostic_engine.takeClient();
diagnostic_engine.setClient(&diagnostic_recorder, /*ShouldOwnClient=*/false);
callback();
diagnostic_engine.setClient(original_consumer.release(),
/*ShouldOwnClient=*/true);
return diagnostic_recorder;
}
} // namespace crubit