diff --git a/Cargo.lock b/Cargo.lock index 61fc83a8..b217928d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1378,6 +1378,7 @@ dependencies = [ "eyre", "getrandom 0.2.8", "hex", + "prettyplease", "proc-macro2", "quote", "regex", @@ -3162,6 +3163,16 @@ dependencies = [ "yansi", ] +[[package]] +name = "prettyplease" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e97e3215779627f01ee256d2fad52f3d95e8e1c11e9fc6fd08f7cd455d5d5c78" +dependencies = [ + "proc-macro2", + "syn", +] + [[package]] name = "primitive-types" version = "0.12.1" diff --git a/ethers-contract/ethers-contract-abigen/Cargo.toml b/ethers-contract/ethers-contract-abigen/Cargo.toml index 164d8b9e..842eeb20 100644 --- a/ethers-contract/ethers-contract-abigen/Cargo.toml +++ b/ethers-contract/ethers-contract-abigen/Cargo.toml @@ -16,10 +16,12 @@ keywords = ["ethereum", "web3", "celo", "ethers"] [dependencies] ethers-core = { version = "^1.0.0", path = "../../ethers-core", features = ["macros"] } -Inflector = "0.11" proc-macro2 = "1.0" quote = "1.0" -syn = "1.0.12" +syn = { version = "1.0.12", default-features = false, features = ["full"] } +prettyplease = "0.1.23" + +Inflector = "0.11" url = "2.1" serde_json = "1.0.61" serde = { version = "1.0.124", features = ["derive"] } diff --git a/ethers-contract/ethers-contract-abigen/src/lib.rs b/ethers-contract/ethers-contract-abigen/src/lib.rs index 6e06533f..cf0659b6 100644 --- a/ethers-contract/ethers-contract-abigen/src/lib.rs +++ b/ethers-contract/ethers-contract-abigen/src/lib.rs @@ -16,7 +16,6 @@ pub mod contract; pub use contract::structs::InternalStructs; use contract::Context; -mod rustfmt; mod source; mod util; @@ -69,8 +68,8 @@ pub struct Abigen { /// Derives added to event structs and enums. event_derives: Vec, - /// Format the code using a locally installed copy of `rustfmt`. - rustfmt: bool, + /// Whether to format the code. Uses [`prettyplease`]. + format: bool, /// Manually specified event name aliases. event_aliases: HashMap, @@ -89,7 +88,7 @@ impl Abigen { method_aliases: HashMap::new(), event_derives: Vec::new(), event_aliases: HashMap::new(), - rustfmt: true, + format: true, error_aliases: Default::default(), }) } @@ -147,14 +146,20 @@ impl Abigen { self } - /// Specify whether or not to format the code using a locally installed copy - /// of `rustfmt`. - /// - /// Note that in case `rustfmt` does not exist or produces an error, the - /// unformatted code will be used. #[must_use] + #[deprecated = "Use format instead"] + #[doc(hidden)] pub fn rustfmt(mut self, rustfmt: bool) -> Self { - self.rustfmt = rustfmt; + self.format = rustfmt; + self + } + + /// Specify whether to format the code or not. True by default. + /// + /// This will use [`prettyplease`], so the resulting formatted code **will not** be affected by + /// the local `rustfmt` version or config. + pub fn format(mut self, format: bool) -> Self { + self.format = format; self } @@ -173,10 +178,10 @@ impl Abigen { /// Generates the contract bindings. pub fn generate(self) -> Result { - let rustfmt = self.rustfmt; + let format = self.format; let name = self.contract_name.clone(); let (expanded, _) = self.expand()?; - Ok(ContractBindings { tokens: expanded.into_tokens(), rustfmt, name }) + Ok(ContractBindings { tokens: expanded.into_tokens(), format, name }) } /// Expands the `Abigen` and returns the [`ExpandedContract`] that holds all tokens and the @@ -193,7 +198,7 @@ pub struct ContractBindings { /// The TokenStream representing the contract bindings. tokens: TokenStream, /// The output options used for serialization. - rustfmt: bool, + format: bool, /// The contract name name: String, } @@ -204,14 +209,11 @@ impl ContractBindings { where W: Write, { - let source = { - let raw = self.tokens.to_string(); - - if self.rustfmt { - rustfmt::format(&raw).unwrap_or(raw) - } else { - raw - } + let source = if self.format { + let syntax_tree = syn::parse2::(self.tokens.clone()).unwrap(); + prettyplease::unparse(&syntax_tree) + } else { + self.tokens.to_string() }; w.write_all(source.as_bytes())?; diff --git a/ethers-contract/ethers-contract-abigen/src/multi.rs b/ethers-contract/ethers-contract-abigen/src/multi.rs index a0c4a21d..3a033813 100644 --- a/ethers-contract/ethers-contract-abigen/src/multi.rs +++ b/ethers-contract/ethers-contract-abigen/src/multi.rs @@ -139,11 +139,8 @@ impl MultiAbigen { /// Build the contract bindings and prepare for writing pub fn build(self) -> Result { - let rustfmt = self.abigens.iter().any(|gen| gen.rustfmt); - Ok(MultiBindings { - expansion: MultiExpansion::from_abigen(self.abigens)?.expand(), - rustfmt, - }) + let format = self.abigens.iter().any(|gen| gen.format); + Ok(MultiBindings { expansion: MultiExpansion::from_abigen(self.abigens)?.expand(), format }) } } @@ -298,14 +295,14 @@ impl MultiExpansionResult { } /// Converts this result into [`MultiBindingsInner`] - fn into_bindings(mut self, single_file: bool, rustfmt: bool) -> MultiBindingsInner { + fn into_bindings(mut self, single_file: bool, format: bool) -> MultiBindingsInner { self.set_shared_import_path(single_file); let Self { contracts, shared_types, root, .. } = self; let bindings = contracts .into_iter() .map(|(expanded, ctx)| ContractBindings { tokens: expanded.into_tokens(), - rustfmt, + format, name: ctx.contract_name().to_string(), }) .map(|v| (v.name.clone(), v)) @@ -325,7 +322,7 @@ impl MultiExpansionResult { }; Some(ContractBindings { tokens: shared_types, - rustfmt, + format, name: "shared_types".to_string(), }) } else { @@ -364,7 +361,7 @@ impl MultiExpansionResult { /// changed) pub struct MultiBindings { expansion: MultiExpansionResult, - rustfmt: bool, + format: bool, } impl MultiBindings { @@ -378,18 +375,25 @@ impl MultiBindings { self.expansion.contracts.is_empty() } - /// Specify whether or not to format the code using a locally installed copy - /// of `rustfmt`. - /// - /// Note that in case `rustfmt` does not exist or produces an error, the - /// unformatted code will be used. + #[must_use] + #[deprecated = "Use format instead"] + #[doc(hidden)] pub fn rustfmt(mut self, rustfmt: bool) -> Self { - self.rustfmt = rustfmt; + self.format = rustfmt; + self + } + + /// Specify whether to format the code or not. True by default. + /// + /// This will use [`prettyplease`], so the resulting formatted code **will not** be affected by + /// the local `rustfmt` version or config. + pub fn format(mut self, format: bool) -> Self { + self.format = format; self } fn into_inner(self, single_file: bool) -> MultiBindingsInner { - self.expansion.into_bindings(single_file, self.rustfmt) + self.expansion.into_bindings(single_file, self.format) } /// Generates all the bindings and writes them to the given module diff --git a/ethers-contract/ethers-contract-abigen/src/rustfmt.rs b/ethers-contract/ethers-contract-abigen/src/rustfmt.rs deleted file mode 100644 index c9bb546d..00000000 --- a/ethers-contract/ethers-contract-abigen/src/rustfmt.rs +++ /dev/null @@ -1,36 +0,0 @@ -//! This module implements basic `rustfmt` code formatting. - -use eyre::{eyre, Result}; -use std::{ - io::Write, - process::{Command, Stdio}, -}; - -/// Format the raw input source string and return formatted output. -pub fn format(source: S) -> Result -where - S: AsRef, -{ - let mut rustfmt = - Command::new("rustfmt").stdin(Stdio::piped()).stdout(Stdio::piped()).spawn()?; - - { - let stdin = rustfmt - .stdin - .as_mut() - .ok_or_else(|| eyre!("stdin was not created for `rustfmt` child process"))?; - stdin.write_all(source.as_ref().as_bytes())?; - } - - let output = rustfmt.wait_with_output()?; - - eyre::ensure!( - output.status.success(), - "`rustfmt` exited with code {}:\n{}", - output.status, - String::from_utf8_lossy(&output.stderr), - ); - - let stdout = String::from_utf8(output.stdout)?; - Ok(stdout) -}