1
0
mirror of https://github.com/Kitware/CMake.git synced 2025-10-15 03:48:02 +08:00
Files
CMake/Source/cmJSONState.cxx
Martin Duffy f134468a98 JSON: Improve JSON error message formatting
This improves the output of JSON-related error messages. It adds the filename to
the output and excludes the column number.

This is particularly useful when there are multiple JSON files being read that
could be responsible for an error, ie CMakePresets.json and
CMakeUserPresets.json, or multiple instrumentation queries.

Issue: #26717
2025-03-13 10:44:31 -04:00

166 lines
4.2 KiB
C++

/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
file Copyright.txt 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 };
}