Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(forge): inspect - default to pretty output #9705

Merged
merged 18 commits into from
Jan 23, 2025
101 changes: 67 additions & 34 deletions crates/forge/bin/cmd/inspect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ use foundry_compilers::{
utils::canonicalize,
};
use regex::Regex;
use std::{fmt, sync::LazyLock};
use serde_json::{Map, Value};
use std::{collections::BTreeMap, fmt, sync::LazyLock};

/// CLI arguments for `forge inspect`.
#[derive(Clone, Debug, Parser)]
Expand All @@ -29,18 +30,14 @@ pub struct InspectArgs {
#[arg(value_enum)]
pub field: ContractArtifactField,

/// Pretty print the selected field, if supported.
#[arg(long)]
pub pretty: bool,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should keep this with a warning or document as a breaking change

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, will be marked in release notes as breaking change given the breaking label


/// All build arguments are supported
#[command(flatten)]
build: BuildOpts,
}

impl InspectArgs {
pub fn run(self) -> Result<()> {
let Self { contract, field, build, pretty } = self;
let Self { contract, field, build } = self;

trace!(target: "forge", ?field, ?contract, "running forge inspect");

Expand Down Expand Up @@ -85,7 +82,7 @@ impl InspectArgs {
.abi
.as_ref()
.ok_or_else(|| eyre::eyre!("Failed to fetch lossless ABI"))?;
if pretty {
if !shell::is_json() {
let source = foundry_cli::utils::abi_to_solidity(abi, &contract.name)?;
sh_println!("{source}")?;
} else {
Expand All @@ -105,7 +102,7 @@ impl InspectArgs {
print_json_str(&artifact.legacy_assembly, None)?;
}
ContractArtifactField::MethodIdentifiers => {
print_json(&artifact.method_identifiers)?;
print_method_identifiers(&artifact.method_identifiers)?;
}
ContractArtifactField::GasEstimates => {
print_json(&artifact.gas_estimates)?;
Expand All @@ -117,10 +114,10 @@ impl InspectArgs {
print_json(&artifact.devdoc)?;
}
ContractArtifactField::Ir => {
print_yul(artifact.ir.as_deref(), self.pretty)?;
print_yul(artifact.ir.as_deref())?;
}
ContractArtifactField::IrOptimized => {
print_yul(artifact.ir_optimized.as_deref(), self.pretty)?;
print_yul(artifact.ir_optimized.as_deref())?;
}
ContractArtifactField::Metadata => {
print_json(&artifact.metadata)?;
Expand All @@ -146,7 +143,7 @@ impl InspectArgs {
);
}
}
print_json(&out)?;
print_errors_events(&out, true)?;
}
ContractArtifactField::Events => {
let mut out = serde_json::Map::new();
Expand All @@ -162,7 +159,7 @@ impl InspectArgs {
);
}
}
print_json(&out)?;
print_errors_events(&out, false)?;
}
ContractArtifactField::Eof => {
print_eof(artifact.deployed_bytecode.and_then(|b| b.bytecode))?;
Expand All @@ -185,30 +182,70 @@ pub fn print_storage_layout(storage_layout: Option<&StorageLayout>) -> Result<()
return print_json(&storage_layout)
}

let mut table = Table::new();
table.apply_modifier(UTF8_ROUND_CORNERS);

table.set_header(vec![
let headers = vec![
Cell::new("Name"),
Cell::new("Type"),
Cell::new("Slot"),
Cell::new("Offset"),
Cell::new("Bytes"),
Cell::new("Contract"),
]);

for slot in &storage_layout.storage {
let storage_type = storage_layout.types.get(&slot.storage_type);
table.add_row([
slot.label.as_str(),
storage_type.map_or("?", |t| &t.label),
&slot.slot,
&slot.offset.to_string(),
storage_type.map_or("?", |t| &t.number_of_bytes),
&slot.contract,
]);
];

print_table(headers, |table| {
for slot in &storage_layout.storage {
let storage_type = storage_layout.types.get(&slot.storage_type);
table.add_row([
slot.label.as_str(),
storage_type.map_or("?", |t| &t.label),
&slot.slot,
&slot.offset.to_string(),
storage_type.map_or("?", |t| &t.number_of_bytes),
&slot.contract,
]);
}
})
}

fn print_method_identifiers(method_identifiers: &Option<BTreeMap<String, String>>) -> Result<()> {
let Some(method_identifiers) = method_identifiers else {
eyre::bail!("Could not get method identifiers");
};

if shell::is_json() {
return print_json(method_identifiers)
}

let headers = vec![Cell::new("Method"), Cell::new("Identifier")];

print_table(headers, |table| {
for (method, identifier) in method_identifiers {
table.add_row([method, identifier]);
}
})
}

fn print_errors_events(map: &Map<String, Value>, is_err: bool) -> Result<()> {
if shell::is_json() {
return print_json(map);
}

let headers = if is_err {
vec![Cell::new("Error"), Cell::new("Selector")]
} else {
vec![Cell::new("Event"), Cell::new("Topic")]
};
print_table(headers, |table| {
for (method, selector) in map {
table.add_row([method, selector.as_str().unwrap()]);
}
})
}

fn print_table(headers: Vec<Cell>, add_rows: impl FnOnce(&mut Table)) -> Result<()> {
let mut table = Table::new();
table.apply_modifier(UTF8_ROUND_CORNERS);
table.set_header(headers);
add_rows(&mut table);
sh_println!("\n{table}\n")?;
Ok(())
}
Expand Down Expand Up @@ -407,19 +444,15 @@ fn print_json_str(obj: &impl serde::Serialize, key: Option<&str>) -> Result<()>
Ok(())
}

fn print_yul(yul: Option<&str>, pretty: bool) -> Result<()> {
fn print_yul(yul: Option<&str>) -> Result<()> {
let Some(yul) = yul else {
eyre::bail!("Could not get IR output");
};

static YUL_COMMENTS: LazyLock<Regex> =
LazyLock::new(|| Regex::new(r"(///.*\n\s*)|(\s*/\*\*.*\*/)").unwrap());

if pretty {
sh_println!("{}", YUL_COMMENTS.replace_all(yul, ""))?;
} else {
sh_println!("{yul}")?;
}
sh_println!("{}", YUL_COMMENTS.replace_all(yul, ""))?;

Ok(())
}
Expand Down
6 changes: 2 additions & 4 deletions crates/forge/tests/cli/cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3232,17 +3232,15 @@ Compiler run successful!

// <https://github.com/foundry-rs/foundry/issues/6816>
forgetest_init!(can_inspect_counter_pretty, |prj, cmd| {
cmd.args(["inspect", "src/Counter.sol:Counter", "abi", "--pretty"]).assert_success().stdout_eq(
str![[r#"
cmd.args(["inspect", "src/Counter.sol:Counter", "abi"]).assert_success().stdout_eq(str![[r#"
interface Counter {
function increment() external;
function number() external view returns (uint256);
function setNumber(uint256 newNumber) external;
}


"#]],
);
"#]]);
});

// checks that `clean` also works with the "out" value set in Config
Expand Down
Loading