Make --pretty-json work with Standard JSON output

This commit is contained in:
Ladislav Sladecek 2021-07-07 13:18:24 +02:00 committed by Kamil Śliwak
parent 7041b87c3b
commit 9bbeea58c8
24 changed files with 176 additions and 26 deletions

View File

@ -8,6 +8,7 @@ Compiler Features:
* Yul EVM Code Transform: Do not reuse stack slots that immediately become unreachable. * Yul EVM Code Transform: Do not reuse stack slots that immediately become unreachable.
* Yul EVM Code Transform: Also pop unused argument slots for functions without return variables (under the same restrictions as for functions with return variables). * Yul EVM Code Transform: Also pop unused argument slots for functions without return variables (under the same restrictions as for functions with return variables).
* Yul Optimizer: Move function arguments and return variables to memory with the experimental Stack Limit Evader (which is not enabled by default). * Yul Optimizer: Move function arguments and return variables to memory with the experimental Stack Limit Evader (which is not enabled by default).
* Commandline Interface: option ``--pretty-json`` works also with ``--standard--json``.
Bugfixes: Bugfixes:

View File

@ -1420,7 +1420,7 @@ string StandardCompiler::compile(string const& _input) noexcept
try try
{ {
if (!util::jsonParseStrict(_input, input, &errors)) if (!util::jsonParseStrict(_input, input, &errors))
return util::jsonCompactPrint(formatFatalError("JSONError", errors)); return util::jsonPrint(formatFatalError("JSONError", errors), m_jsonPrintingFormat);
} }
catch (...) catch (...)
{ {
@ -1433,7 +1433,7 @@ string StandardCompiler::compile(string const& _input) noexcept
try try
{ {
return util::jsonCompactPrint(output); return util::jsonPrint(output, m_jsonPrintingFormat);
} }
catch (...) catch (...)
{ {

View File

@ -24,6 +24,7 @@
#pragma once #pragma once
#include <libsolidity/interface/CompilerStack.h> #include <libsolidity/interface/CompilerStack.h>
#include <libsolutil/JSON.h>
#include <optional> #include <optional>
#include <utility> #include <utility>
@ -46,8 +47,10 @@ public:
/// Creates a new StandardCompiler. /// Creates a new StandardCompiler.
/// @param _readFile callback used to read files for import statements. Must return /// @param _readFile callback used to read files for import statements. Must return
/// and must not emit exceptions. /// and must not emit exceptions.
explicit StandardCompiler(ReadCallback::Callback _readFile = ReadCallback::Callback()): explicit StandardCompiler(ReadCallback::Callback _readFile = ReadCallback::Callback(),
m_readFile(std::move(_readFile)) util::JsonFormat const& _format = {}):
m_readFile(std::move(_readFile)),
m_jsonPrintingFormat(std::move(_format))
{ {
} }
@ -91,6 +94,8 @@ private:
Json::Value compileYul(InputsAndSettings _inputsAndSettings); Json::Value compileYul(InputsAndSettings _inputsAndSettings);
ReadCallback::Callback m_readFile; ReadCallback::Callback m_readFile;
util::JsonFormat m_jsonPrintingFormat;
}; };
} }

View File

@ -115,18 +115,29 @@ Json::Value removeNullMembers(Json::Value _json)
string jsonPrettyPrint(Json::Value const& _input) string jsonPrettyPrint(Json::Value const& _input)
{ {
static map<string, Json::Value> settings{{"indentation", " "}, {"enableYAMLCompatibility", true}}; return jsonPrint(_input, JsonFormat{ JsonFormat::Pretty });
static StreamWriterBuilder writerBuilder(settings);
string result = print(_input, writerBuilder);
boost::replace_all(result, " \n", "\n");
return result;
} }
string jsonCompactPrint(Json::Value const& _input) string jsonCompactPrint(Json::Value const& _input)
{ {
static map<string, Json::Value> settings{{"indentation", ""}}; return jsonPrint(_input, JsonFormat{ JsonFormat::Compact });
static StreamWriterBuilder writerBuilder(settings); }
return print(_input, writerBuilder);
string jsonPrint(Json::Value const& _input, JsonFormat const& _format)
{
map<string, Json::Value> settings;
if (_format.format == JsonFormat::Pretty)
{
settings["indentation"] = string(_format.indent, ' ');
settings["enableYAMLCompatibility"] = true;
}
else
settings["indentation"] = "";
StreamWriterBuilder writerBuilder(settings);
string result = print(_input, writerBuilder);
if (_format.format == JsonFormat::Pretty)
boost::replace_all(result, " \n", "\n");
return result;
} }
bool jsonParseStrict(string const& _input, Json::Value& _json, string* _errs /* = nullptr */) bool jsonParseStrict(string const& _input, Json::Value& _json, string* _errs /* = nullptr */)

View File

@ -33,12 +33,33 @@ namespace solidity::util
/// Removes members with null value recursively from (@a _json). /// Removes members with null value recursively from (@a _json).
Json::Value removeNullMembers(Json::Value _json); Json::Value removeNullMembers(Json::Value _json);
/// JSON printing format.
struct JsonFormat
{
enum Format
{
Compact,
Pretty
};
static constexpr uint32_t defaultIndent = 2;
bool operator==(JsonFormat const& _other) const noexcept { return (format == _other.format) && (indent == _other.indent); }
bool operator!=(JsonFormat const& _other) const noexcept { return !(*this == _other); }
Format format = Compact;
uint32_t indent = defaultIndent;
};
/// Serialise the JSON object (@a _input) with indentation /// Serialise the JSON object (@a _input) with indentation
std::string jsonPrettyPrint(Json::Value const& _input); std::string jsonPrettyPrint(Json::Value const& _input);
/// Serialise the JSON object (@a _input) without indentation /// Serialise the JSON object (@a _input) without indentation
std::string jsonCompactPrint(Json::Value const& _input); std::string jsonCompactPrint(Json::Value const& _input);
/// Serialise the JSON object (@a _input) using specified format (@a _format)
std::string jsonPrint(Json::Value const& _input, JsonFormat const& _format);
/// Parse a JSON string (@a _input) with enabled strict-mode and writes resulting JSON object to (@a _json) /// Parse a JSON string (@a _input) with enabled strict-mode and writes resulting JSON object to (@a _json)
/// \param _input JSON input string /// \param _input JSON input string
/// \param _json [out] resulting JSON object /// \param _json [out] resulting JSON object

View File

@ -532,7 +532,7 @@ bool CommandLineInterface::processInput()
return false; return false;
} }
} }
StandardCompiler compiler(m_fileReader.reader()); StandardCompiler compiler(m_fileReader.reader(), m_options.formatting.json);
sout() << compiler.compile(std::move(input)) << endl; sout() << compiler.compile(std::move(input)) << endl;
return true; return true;
} }
@ -771,9 +771,7 @@ void CommandLineInterface::handleCombinedJSON()
} }
} }
string json = m_options.formatting.prettyJson ? jsonPrettyPrint(removeNullMembers(std::move(output))) : string json = jsonPrint(removeNullMembers(std::move(output)), m_options.formatting.json);
jsonCompactPrint(removeNullMembers(std::move(output)));
if (!m_options.output.dir.empty()) if (!m_options.output.dir.empty())
createJson("combined", json); createJson("combined", json);
else else

View File

@ -125,6 +125,7 @@ static string const g_strStandardJSON = "standard-json";
static string const g_strStrictAssembly = "strict-assembly"; static string const g_strStrictAssembly = "strict-assembly";
static string const g_strSwarm = "swarm"; static string const g_strSwarm = "swarm";
static string const g_strPrettyJson = "pretty-json"; static string const g_strPrettyJson = "pretty-json";
static string const g_strJsonIndent = "json-indent";
static string const g_strVersion = "version"; static string const g_strVersion = "version";
static string const g_strIgnoreMissingFiles = "ignore-missing"; static string const g_strIgnoreMissingFiles = "ignore-missing";
static string const g_strColor = "color"; static string const g_strColor = "color";
@ -281,7 +282,7 @@ bool CommandLineOptions::operator==(CommandLineOptions const& _other) const noex
assembly.targetMachine == _other.assembly.targetMachine && assembly.targetMachine == _other.assembly.targetMachine &&
assembly.inputLanguage == _other.assembly.inputLanguage && assembly.inputLanguage == _other.assembly.inputLanguage &&
linker.libraries == _other.linker.libraries && linker.libraries == _other.linker.libraries &&
formatting.prettyJson == _other.formatting.prettyJson && formatting.json == _other.formatting.json &&
formatting.coloredOutput == _other.formatting.coloredOutput && formatting.coloredOutput == _other.formatting.coloredOutput &&
formatting.withErrorIds == _other.formatting.withErrorIds && formatting.withErrorIds == _other.formatting.withErrorIds &&
compiler.outputs == _other.compiler.outputs && compiler.outputs == _other.compiler.outputs &&
@ -582,7 +583,12 @@ General Information)").c_str(),
outputFormatting.add_options() outputFormatting.add_options()
( (
g_strPrettyJson.c_str(), g_strPrettyJson.c_str(),
"Output JSON in pretty format. Currently it only works with the combined JSON output." "Output JSON in pretty format."
)
(
g_strJsonIndent.c_str(),
po::value<uint32_t>()->value_name("N")->default_value(util::JsonFormat::defaultIndent),
"Indent pretty-printed JSON with N spaces. Enables '--pretty-json' automatically."
) )
( (
g_strColor.c_str(), g_strColor.c_str(),
@ -790,7 +796,16 @@ General Information)").c_str(),
m_options.output.dir = m_args.at(g_strOutputDir).as<string>(); m_options.output.dir = m_args.at(g_strOutputDir).as<string>();
m_options.output.overwriteFiles = (m_args.count(g_strOverwrite) > 0); m_options.output.overwriteFiles = (m_args.count(g_strOverwrite) > 0);
m_options.formatting.prettyJson = (m_args.count(g_strPrettyJson) > 0);
if (m_args.count(g_strPrettyJson) > 0)
{
m_options.formatting.json.format = JsonFormat::Pretty;
}
if (!m_args[g_strJsonIndent].defaulted())
{
m_options.formatting.json.format = JsonFormat::Pretty;
m_options.formatting.json.indent = m_args[g_strJsonIndent].as<uint32_t>();
}
static_assert( static_assert(
sizeof(m_options.compiler.outputs) == 15 * sizeof(bool), sizeof(m_options.compiler.outputs) == 15 * sizeof(bool),

View File

@ -26,6 +26,7 @@
#include <libsolidity/interface/ImportRemapper.h> #include <libsolidity/interface/ImportRemapper.h>
#include <libyul/AssemblyStack.h> #include <libyul/AssemblyStack.h>
#include <liblangutil/EVMVersion.h> #include <liblangutil/EVMVersion.h>
#include <libsolutil/JSON.h>
#include <boost/program_options.hpp> #include <boost/program_options.hpp>
#include <boost/filesystem/path.hpp> #include <boost/filesystem/path.hpp>
@ -101,6 +102,7 @@ struct CommandLineOptions
bool operator==(CommandLineOptions const& _other) const noexcept; bool operator==(CommandLineOptions const& _other) const noexcept;
bool operator!=(CommandLineOptions const& _other) const noexcept { return !(*this == _other); } bool operator!=(CommandLineOptions const& _other) const noexcept { return !(*this == _other); }
struct struct
{ {
InputMode mode = InputMode::Compiler; InputMode mode = InputMode::Compiler;
@ -137,7 +139,7 @@ struct CommandLineOptions
struct struct
{ {
bool prettyJson = false; util::JsonFormat json;
std::optional<bool> coloredOutput; std::optional<bool> coloredOutput;
bool withErrorIds = false; bool withErrorIds = false;
} formatting; } formatting;
@ -168,6 +170,7 @@ struct CommandLineOptions
bool initialize = false; bool initialize = false;
ModelCheckerSettings settings; ModelCheckerSettings settings;
} modelChecker; } modelChecker;
}; };
/// Parses the command-line arguments and produces a filled-out CommandLineOptions structure. /// Parses the command-line arguments and produces a filled-out CommandLineOptions structure.

View File

@ -141,9 +141,16 @@ function test_solc_behaviour()
if [[ " ${solc_args[*]} " == *" --standard-json "* ]] if [[ " ${solc_args[*]} " == *" --standard-json "* ]]
then then
sed -i.bak -e 's/{[^{]*Warning: This is a pre-release compiler version[^}]*},\{0,1\}//' "$stdout_path" python3 - <<EOF
import re, sys
json = open("$stdout_path", "r").read()
json = re.sub(r"{[^{}]*Warning: This is a pre-release compiler version[^{}]*},?", "", json)
json = re.sub(r"},\s*]", "}]", json) # },] -> }]
json = re.sub(r"\"errors\":\s*\[\s*\],?\s*","",json) # Remove "errors" array if it's not empty
json = re.sub("\n\\s+\n", "\n\n", json) # Remove any leftover trailing whitespace
open("$stdout_path", "w").write(json)
EOF
sed -i.bak -E -e 's/ Consider adding \\"pragma solidity \^[0-9.]*;\\"//g' "$stdout_path" sed -i.bak -E -e 's/ Consider adding \\"pragma solidity \^[0-9.]*;\\"//g' "$stdout_path"
sed -i.bak -e 's/"errors":\[\],\{0,1\}//' "$stdout_path"
sed -i.bak -E -e 's/\"opcodes\":\"[^"]+\"/\"opcodes\":\"<OPCODES REMOVED>\"/g' "$stdout_path" sed -i.bak -E -e 's/\"opcodes\":\"[^"]+\"/\"opcodes\":\"<OPCODES REMOVED>\"/g' "$stdout_path"
sed -i.bak -E -e 's/\"sourceMap\":\"[0-9:;-]+\"/\"sourceMap\":\"<SOURCEMAP REMOVED>\"/g' "$stdout_path" sed -i.bak -E -e 's/\"sourceMap\":\"[0-9:;-]+\"/\"sourceMap\":\"<SOURCEMAP REMOVED>\"/g' "$stdout_path"

View File

@ -0,0 +1 @@
--combined-json abi --pretty-json --json-indent 3

View File

@ -0,0 +1,3 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.0;
contract C {}

View File

@ -0,0 +1,10 @@
{
"contracts":
{
"pretty_json_combined/input.sol:C":
{
"abi": []
}
},
"version": "<VERSION REMOVED>"
}

View File

@ -0,0 +1 @@
--json-indent 7

View File

@ -0,0 +1,10 @@
{
"language": "Solidity",
"sources":
{
"A":
{
"content": "// SPDX-License-Identifier: GPL-3.0\npragma solidity >=0.0; contract C {}"
}
}
}

View File

@ -0,0 +1 @@

View File

@ -0,0 +1,9 @@
{
"sources":
{
"A":
{
"id": 0
}
}
}

View File

@ -0,0 +1 @@
--pretty-json

View File

@ -0,0 +1,10 @@
{
"language": "Solidity",
"sources":
{
"A":
{
"content": "// SPDX-License-Identifier: GPL-3.0\npragma solidity >=0.0; contract C {}"
}
}
}

View File

@ -0,0 +1,9 @@
{
"sources":
{
"A":
{
"id": 0
}
}
}

View File

@ -0,0 +1 @@
--pretty-json --json-indent 7

View File

@ -0,0 +1,10 @@
{
"language": "Solidity",
"sources":
{
"A":
{
"content": "// SPDX-License-Identifier: GPL-3.0\npragma solidity >=0.0; contract C { }"
}
}
}

View File

@ -0,0 +1,9 @@
{
"sources":
{
"A":
{
"id": 0
}
}
}

View File

@ -0,0 +1,9 @@
{
"sources":
{
"A":
{
"id": 0
}
}
}

View File

@ -138,6 +138,7 @@ BOOST_AUTO_TEST_CASE(cli_mode_options)
"--experimental-via-ir", "--experimental-via-ir",
"--revert-strings=strip", "--revert-strings=strip",
"--pretty-json", "--pretty-json",
"--json-indent=7",
"--no-color", "--no-color",
"--error-codes", "--error-codes",
"--libraries=" "--libraries="
@ -173,6 +174,7 @@ BOOST_AUTO_TEST_CASE(cli_mode_options)
{"a", "b", "c/d"}, {"a", "b", "c/d"},
{"", "contract.sol", ""}, {"", "contract.sol", ""},
}; };
expectedOptions.input.addStdin = true; expectedOptions.input.addStdin = true;
expectedOptions.input.basePath = "/home/user/"; expectedOptions.input.basePath = "/home/user/";
expectedOptions.input.allowedDirectories = {"/tmp", "/home", "project", "../contracts", "", "c", "/usr/lib"}; expectedOptions.input.allowedDirectories = {"/tmp", "/home", "project", "../contracts", "", "c", "/usr/lib"};
@ -183,11 +185,11 @@ BOOST_AUTO_TEST_CASE(cli_mode_options)
expectedOptions.output.evmVersion = EVMVersion::spuriousDragon(); expectedOptions.output.evmVersion = EVMVersion::spuriousDragon();
expectedOptions.output.experimentalViaIR = true; expectedOptions.output.experimentalViaIR = true;
expectedOptions.output.revertStrings = RevertStrings::Strip; expectedOptions.output.revertStrings = RevertStrings::Strip;
expectedOptions.formatting.json = JsonFormat{JsonFormat::Pretty, 7};
expectedOptions.linker.libraries = { expectedOptions.linker.libraries = {
{"dir1/file1.sol:L", h160("1234567890123456789012345678901234567890")}, {"dir1/file1.sol:L", h160("1234567890123456789012345678901234567890")},
{"dir2/file2.sol:L", h160("1111122222333334444455555666667777788888")}, {"dir2/file2.sol:L", h160("1111122222333334444455555666667777788888")},
}; };
expectedOptions.formatting.prettyJson = true;
expectedOptions.formatting.coloredOutput = false; expectedOptions.formatting.coloredOutput = false;
expectedOptions.formatting.withErrorIds = true; expectedOptions.formatting.withErrorIds = true;
expectedOptions.compiler.outputs = { expectedOptions.compiler.outputs = {
@ -269,6 +271,7 @@ BOOST_AUTO_TEST_CASE(assembly_mode_options)
"--experimental-via-ir", // Ignored in assembly mode "--experimental-via-ir", // Ignored in assembly mode
"--revert-strings=strip", // Accepted but has no effect in assembly mode "--revert-strings=strip", // Accepted but has no effect in assembly mode
"--pretty-json", "--pretty-json",
"--json-indent=1",
"--no-color", "--no-color",
"--error-codes", "--error-codes",
"--libraries=" "--libraries="
@ -313,13 +316,13 @@ BOOST_AUTO_TEST_CASE(assembly_mode_options)
expectedOptions.output.overwriteFiles = true; expectedOptions.output.overwriteFiles = true;
expectedOptions.output.evmVersion = EVMVersion::spuriousDragon(); expectedOptions.output.evmVersion = EVMVersion::spuriousDragon();
expectedOptions.output.revertStrings = RevertStrings::Strip; expectedOptions.output.revertStrings = RevertStrings::Strip;
expectedOptions.formatting.json = JsonFormat {JsonFormat::Pretty, 1};
expectedOptions.assembly.targetMachine = expectedMachine; expectedOptions.assembly.targetMachine = expectedMachine;
expectedOptions.assembly.inputLanguage = expectedLanguage; expectedOptions.assembly.inputLanguage = expectedLanguage;
expectedOptions.linker.libraries = { expectedOptions.linker.libraries = {
{"dir1/file1.sol:L", h160("1234567890123456789012345678901234567890")}, {"dir1/file1.sol:L", h160("1234567890123456789012345678901234567890")},
{"dir2/file2.sol:L", h160("1111122222333334444455555666667777788888")}, {"dir2/file2.sol:L", h160("1111122222333334444455555666667777788888")},
}; };
expectedOptions.formatting.prettyJson = true;
expectedOptions.formatting.coloredOutput = false; expectedOptions.formatting.coloredOutput = false;
expectedOptions.formatting.withErrorIds = true; expectedOptions.formatting.withErrorIds = true;
expectedOptions.compiler.outputs = { expectedOptions.compiler.outputs = {
@ -359,7 +362,8 @@ BOOST_AUTO_TEST_CASE(standard_json_mode_options)
"--evm-version=spuriousDragon", // Ignored in Standard JSON mode "--evm-version=spuriousDragon", // Ignored in Standard JSON mode
"--experimental-via-ir", // Ignored in Standard JSON mode "--experimental-via-ir", // Ignored in Standard JSON mode
"--revert-strings=strip", // Accepted but has no effect in Standard JSON mode "--revert-strings=strip", // Accepted but has no effect in Standard JSON mode
"--pretty-json", // Accepted but has no effect in Standard JSON mode "--pretty-json",
"--json-indent=1",
"--no-color", // Accepted but has no effect in Standard JSON mode "--no-color", // Accepted but has no effect in Standard JSON mode
"--error-codes", // Accepted but has no effect in Standard JSON mode "--error-codes", // Accepted but has no effect in Standard JSON mode
"--libraries=" // Ignored in Standard JSON mode "--libraries=" // Ignored in Standard JSON mode
@ -387,6 +391,7 @@ BOOST_AUTO_TEST_CASE(standard_json_mode_options)
}; };
CommandLineOptions expectedOptions; CommandLineOptions expectedOptions;
expectedOptions.input.mode = InputMode::StandardJson; expectedOptions.input.mode = InputMode::StandardJson;
expectedOptions.input.paths = {}; expectedOptions.input.paths = {};
expectedOptions.input.standardJsonFile = "input.json"; expectedOptions.input.standardJsonFile = "input.json";
@ -395,7 +400,7 @@ BOOST_AUTO_TEST_CASE(standard_json_mode_options)
expectedOptions.output.dir = "/tmp/out"; expectedOptions.output.dir = "/tmp/out";
expectedOptions.output.overwriteFiles = true; expectedOptions.output.overwriteFiles = true;
expectedOptions.output.revertStrings = RevertStrings::Strip; expectedOptions.output.revertStrings = RevertStrings::Strip;
expectedOptions.formatting.prettyJson = true; expectedOptions.formatting.json = JsonFormat {JsonFormat::Pretty, 1};
expectedOptions.formatting.coloredOutput = false; expectedOptions.formatting.coloredOutput = false;
expectedOptions.formatting.withErrorIds = true; expectedOptions.formatting.withErrorIds = true;
expectedOptions.compiler.outputs = { expectedOptions.compiler.outputs = {