mirror of
https://github.com/Kitware/CMake.git
synced 2025-05-09 06:42:18 +08:00

f134468a98 JSON: Improve JSON error message formatting Acked-by: Kitware Robot <kwrobot@kitware.com> Acked-by: buildbot <buildbot@kitware.com> Merge-request: !10463
166 lines
4.2 KiB
C++
166 lines
4.2 KiB
C++
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
|
|
file LICENSE.rst or https://cmake.org/licensing for details. */
|
|
|
|
#include "cmJSONState.h"
|
|
|
|
#include <iterator>
|
|
#include <sstream>
|
|
|
|
#include <cm3p/json/reader.h>
|
|
#include <cm3p/json/value.h>
|
|
|
|
#include "cmsys/FStream.hxx"
|
|
|
|
#include "cmStringAlgorithms.h"
|
|
#include "cmSystemTools.h"
|
|
|
|
cmJSONState::cmJSONState(std::string jsonFile, Json::Value* root)
|
|
: Filename(std::move(jsonFile))
|
|
{
|
|
cmsys::ifstream fin(this->Filename.c_str(), std::ios::in | std::ios::binary);
|
|
if (!fin) {
|
|
this->AddError(cmStrCat("File not found: ", this->Filename));
|
|
return;
|
|
}
|
|
// If there's a BOM, toss it.
|
|
cmsys::FStream::ReadBOM(fin);
|
|
|
|
// Save the entire document.
|
|
std::streampos finBegin = fin.tellg();
|
|
this->doc = std::string(std::istreambuf_iterator<char>(fin),
|
|
std::istreambuf_iterator<char>());
|
|
if (this->doc.empty()) {
|
|
this->AddError("A JSON document cannot be empty");
|
|
return;
|
|
}
|
|
fin.seekg(finBegin);
|
|
|
|
// Parse the document.
|
|
Json::CharReaderBuilder builder;
|
|
Json::CharReaderBuilder::strictMode(&builder.settings_);
|
|
std::string errMsg;
|
|
if (!Json::parseFromStream(builder, fin, root, &errMsg)) {
|
|
errMsg = cmStrCat("JSON Parse Error: ", this->Filename, ":\n", errMsg);
|
|
this->AddError(errMsg);
|
|
}
|
|
}
|
|
|
|
void cmJSONState::AddError(std::string const& errMsg)
|
|
{
|
|
this->errors.emplace_back(errMsg);
|
|
}
|
|
|
|
void cmJSONState::AddErrorAtValue(std::string const& errMsg,
|
|
Json::Value const* value)
|
|
{
|
|
if (value && !value->isNull()) {
|
|
this->AddErrorAtOffset(errMsg, value->getOffsetStart());
|
|
} else {
|
|
this->AddError(errMsg);
|
|
}
|
|
}
|
|
|
|
void cmJSONState::AddErrorAtOffset(std::string const& errMsg,
|
|
std::ptrdiff_t offset)
|
|
{
|
|
if (doc.empty()) {
|
|
this->AddError(errMsg);
|
|
} else {
|
|
Location loc = LocateInDocument(offset);
|
|
this->errors.emplace_back(loc, errMsg);
|
|
}
|
|
}
|
|
|
|
std::string cmJSONState::GetErrorMessage(bool showContext)
|
|
{
|
|
std::string message;
|
|
std::string filenameName = cmSystemTools::GetFilenameName(this->Filename);
|
|
for (auto const& error : this->errors) {
|
|
Location loc = error.GetLocation();
|
|
if (!filenameName.empty() && loc.line > 0) {
|
|
message = cmStrCat(message, filenameName, ':', loc.line, ": ");
|
|
}
|
|
message = cmStrCat(message, error.GetErrorMessage(), "\n");
|
|
if (showContext && loc.line > 0) {
|
|
message = cmStrCat(message, GetJsonContext(loc), "\n");
|
|
}
|
|
}
|
|
message.pop_back();
|
|
return message;
|
|
}
|
|
|
|
std::string cmJSONState::key()
|
|
{
|
|
if (!this->parseStack.empty()) {
|
|
return this->parseStack.back().first;
|
|
}
|
|
return "";
|
|
}
|
|
|
|
std::string cmJSONState::key_after(std::string const& k)
|
|
{
|
|
for (auto it = this->parseStack.begin(); it != this->parseStack.end();
|
|
++it) {
|
|
if (it->first == k && (++it) != this->parseStack.end()) {
|
|
return it->first;
|
|
}
|
|
}
|
|
return "";
|
|
}
|
|
|
|
Json::Value const* cmJSONState::value_after(std::string const& k)
|
|
{
|
|
for (auto it = this->parseStack.begin(); it != this->parseStack.end();
|
|
++it) {
|
|
if (it->first == k && (++it) != this->parseStack.end()) {
|
|
return it->second;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
void cmJSONState::push_stack(std::string const& k, Json::Value const* value)
|
|
{
|
|
this->parseStack.emplace_back(k, value);
|
|
}
|
|
|
|
void cmJSONState::pop_stack()
|
|
{
|
|
this->parseStack.pop_back();
|
|
}
|
|
|
|
std::string cmJSONState::GetJsonContext(Location loc)
|
|
{
|
|
std::string line;
|
|
std::stringstream sstream(doc);
|
|
for (int i = 0; i < loc.line; ++i) {
|
|
std::getline(sstream, line, '\n');
|
|
}
|
|
return cmStrCat(line, '\n', std::string(loc.column - 1, ' '), '^');
|
|
}
|
|
|
|
cmJSONState::Location cmJSONState::LocateInDocument(ptrdiff_t offset)
|
|
{
|
|
int line = 1;
|
|
int col = 1;
|
|
char const* beginDoc = doc.data();
|
|
char const* last = beginDoc + offset;
|
|
for (; beginDoc != last; ++beginDoc) {
|
|
switch (*beginDoc) {
|
|
case '\r':
|
|
if (beginDoc + 1 != last && beginDoc[1] == '\n') {
|
|
continue; // consume CRLF as a single token.
|
|
}
|
|
CM_FALLTHROUGH; // CR without a following LF is same as LF
|
|
case '\n':
|
|
col = 1;
|
|
++line;
|
|
break;
|
|
default:
|
|
++col;
|
|
break;
|
|
}
|
|
}
|
|
return { line, col };
|
|
}
|