From 425569b121094fb365bd8c2ea15fbfba8907bef3 Mon Sep 17 00:00:00 2001 From: Alexander Gonzalez Date: Sat, 6 Jul 2024 21:18:11 +0200 Subject: [PATCH 1/2] lint: add better rust formatting --- .github/workflows/ci.yml | 53 ++-- .rustfmt.toml | 13 +- benches/bench.rs | 3 +- src/check/context.rs | 67 +++--- src/check/mod.rs | 55 +++-- src/check/rules/structural_match.rs | 49 +++- src/check/utils.rs | 5 +- src/check/violation.rs | 185 ++++++++------ src/error.rs | 57 ++--- src/hir/combiner.rs | 112 ++++++--- src/hir/mod.rs | 23 +- src/hir/translator.rs | 147 ++++++----- src/hir/visitor.rs | 36 ++- src/scaffold/emitter.rs | 95 +++++--- src/scaffold/mod.rs | 24 +- src/scaffold/modifiers.rs | 50 ++-- src/sol/fmt.rs | 76 ++++-- src/sol/mod.rs | 50 ++-- src/sol/translator.rs | 361 ++++++++++++++++------------ src/sol/visitor.rs | 46 +++- src/span.rs | 12 +- src/syntax/parser.rs | 114 +++++---- src/syntax/semantics.rs | 80 +++--- src/syntax/tokenizer.rs | 106 ++++---- src/syntax/visitor.rs | 15 +- src/utils.rs | 14 +- tests/check.rs | 80 +++--- tests/common/mod.rs | 7 +- tests/scaffold.rs | 4 +- 29 files changed, 1146 insertions(+), 793 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bba87c6..a6ee57d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -35,25 +35,48 @@ jobs: command: build args: --all-targets - lint: + fmt: runs-on: ubuntu-latest + name: nightly / fmt + steps: + - uses: actions/checkout@v4 + with: + submodules: true + - name: Install stable + # We run in nightly to make use of some features only available there. + # Check out `rustfmt.toml` to see which ones. + uses: dtolnay/rust-toolchain@nightly + with: + components: rustfmt + - name: cargo fmt --all --check + run: cargo fmt --all --check + + clippy: + runs-on: ubuntu-latest + name: ${{ matrix.toolchain }} / clippy permissions: + contents: read checks: write + strategy: + fail-fast: false + matrix: + # Get early warning of new lints which are regularly introduced in beta + # channels. + toolchain: [ stable, beta ] steps: - - uses: actions/checkout@v1 - - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: stable - override: true - components: rustfmt, clippy - - name: Check code formatting - run: cargo fmt -- --check - - name: Run Clippy - uses: actions-rs/clippy-check@v1 - with: - token: ${{ secrets.GITHUB_TOKEN }} - args: --all-features + - uses: actions/checkout@v4 + with: + submodules: true + - name: Install ${{ matrix.toolchain }} + uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ matrix.toolchain }} + components: clippy + - name: cargo clippy + uses: giraffate/clippy-action@v1 + with: + reporter: 'github-pr-check' + github_token: ${{ secrets.GITHUB_TOKEN }} coverage: runs-on: ubuntu-latest diff --git a/.rustfmt.toml b/.rustfmt.toml index b1483ac..769661d 100644 --- a/.rustfmt.toml +++ b/.rustfmt.toml @@ -1,5 +1,8 @@ -# This file intentionally left almost blank -# -# The empty `rustfmt.toml` makes rustfmt use the default configuration, -# overriding any which may be found in the contributor's home or parent -# folders. +max_width = 80 +format_macro_matchers = true +group_imports = "StdExternalCrate" +imports_granularity = "Crate" +reorder_impl_items = true +use_field_init_shorthand = true +use_small_heuristics = "Max" +wrap_comments = true diff --git a/benches/bench.rs b/benches/bench.rs index 81a1922..1b396fa 100644 --- a/benches/bench.rs +++ b/benches/bench.rs @@ -2,7 +2,8 @@ use bulloak::{self, scaffold}; use criterion::{black_box, criterion_group, criterion_main, Criterion}; fn big_tree(c: &mut Criterion) { - let tree = std::fs::read_to_string("benches/bench_data/cancel.tree").unwrap(); + let tree = + std::fs::read_to_string("benches/bench_data/cancel.tree").unwrap(); let cfg = Default::default(); let mut group = c.benchmark_group("sample-size-10"); diff --git a/src/check/context.rs b/src/check/context.rs index 36bb8f1..2fd59ca 100644 --- a/src/check/context.rs +++ b/src/check/context.rs @@ -1,21 +1,24 @@ //! Defines the context in which rule-checking occurs. -use forge_fmt::{format, Comments, FormatterConfig, FormatterError, InlineConfig, Parsed}; -use solang_parser::pt::SourceUnit; use std::{ ffi::OsStr, fs, path::{Path, PathBuf}, }; -use crate::{check::Violation, scaffold::emitter::Emitter}; +use forge_fmt::{ + format, Comments, FormatterConfig, FormatterError, InlineConfig, Parsed, +}; +use solang_parser::pt::SourceUnit; + +use super::{location::Location, violation::ViolationKind}; use crate::{ + check::Violation, config::Config, hir::{self, Hir}, + scaffold::emitter::Emitter, }; -use super::{location::Location, violation::ViolationKind}; - /// The context in which rule-checking happens. /// /// This is a utility struct that abstracts away the requirements for a `check` @@ -50,34 +53,29 @@ impl Context { pub(crate) fn new(tree: PathBuf, cfg: Config) -> Result { let tree_path_cow = tree.to_string_lossy(); let tree_contents = try_read_to_string(&tree)?; - let hir = crate::hir::translate(&tree_contents, &Default::default()).map_err(|e| { - Violation::new( - ViolationKind::ParsingFailed(e), - Location::File(tree_path_cow.into_owned()), - ) - })?; + let hir = crate::hir::translate(&tree_contents, &Default::default()) + .map_err(|e| { + Violation::new( + ViolationKind::ParsingFailed(e), + Location::File(tree_path_cow.into_owned()), + ) + })?; let sol = get_path_with_ext(&tree, "t.sol")?; let src = try_read_to_string(&sol)?; let parsed = forge_fmt::parse(&src).map_err(|_| { let sol_filename = sol.to_string_lossy().into_owned(); Violation::new( - ViolationKind::ParsingFailed(anyhow::anyhow!("Failed to parse {sol_filename}")), + ViolationKind::ParsingFailed(anyhow::anyhow!( + "Failed to parse {sol_filename}" + )), Location::File(sol_filename), ) })?; let pt = parsed.pt.clone(); let comments = parsed.comments; - Ok(Context { - tree, - hir, - sol, - src, - pt, - comments, - cfg: cfg.clone(), - }) + Ok(Context { tree, hir, sol, src, pt, comments, cfg: cfg.clone() }) } /// Updates this `Context` with the result of parsing a Solidity file. @@ -108,16 +106,24 @@ impl Context { Ok(formatted) } - /// Inserts a function definition into the source string at a specified offset. + /// Inserts a function definition into the source string at a specified + /// offset. /// - /// This function takes a `FunctionDefinition` from the High-Level Intermediate Representation (HIR), - /// converts it into a Solidity function definition string using an `Emitter`, and then inserts - /// this string into the specified source code at a given offset. + /// This function takes a `FunctionDefinition` from the High-Level + /// Intermediate Representation (HIR), converts it into a Solidity + /// function definition string using an `Emitter`, and then inserts this + /// string into the specified source code at a given offset. /// /// # Arguments - /// * `function` - A reference to the HIR `FunctionDefinition` to be inserted. - /// * `offset` - The character position in the source string where the function definition should be inserted. - pub(crate) fn insert_function_at(&mut self, function: &hir::FunctionDefinition, offset: usize) { + /// * `function` - A reference to the HIR `FunctionDefinition` to be + /// inserted. + /// * `offset` - The character position in the source string where the + /// function definition should be inserted. + pub(crate) fn insert_function_at( + &mut self, + function: &hir::FunctionDefinition, + offset: usize, + ) { let cfg = &Default::default(); let f = &Hir::FunctionDefinition(function.clone()); let function = Emitter::new(cfg).emit(f); @@ -130,7 +136,10 @@ impl Context { } } -fn get_path_with_ext(path: impl AsRef, ext: impl AsRef) -> Result { +fn get_path_with_ext( + path: impl AsRef, + ext: impl AsRef, +) -> Result { let path = path.as_ref(); let mut sol = path.to_path_buf(); sol.set_extension(ext); diff --git a/src/check/mod.rs b/src/check/mod.rs index abe9496..c053ca4 100644 --- a/src/check/mod.rs +++ b/src/check/mod.rs @@ -1,23 +1,20 @@ //! Defines the `bulloak check` command. //! -//! This command performs checks on the relationship between a bulloak tree and a -//! Solidity file. +//! This command performs checks on the relationship between a bulloak tree and +//! a Solidity file. -use std::fs; -use std::path::PathBuf; +use std::{fs, path::PathBuf}; use clap::Parser; use owo_colors::OwoColorize; use serde::{Deserialize, Serialize}; use violation::{Violation, ViolationKind}; -use crate::check::violation::fix_order; -use crate::config::Config; -use crate::sol::find_contract; -use crate::utils::pluralize; - -use self::context::Context; -use self::rules::Checker; +use self::{context::Context, rules::Checker}; +use crate::{ + check::violation::fix_order, config::Config, sol::find_contract, + utils::pluralize, +}; mod context; mod location; @@ -71,12 +68,16 @@ impl Check { let mut fixed_count = 0; for mut ctx in ctxs { let violations = rules::StructuralMatcher::check(&ctx); - let fixable_count = violations.iter().filter(|v| v.is_fixable()).count(); + let fixable_count = + violations.iter().filter(|v| v.is_fixable()).count(); // Process violations that affect function order first. - let violations = violations - .into_iter() - .filter(|v| !matches!(v.kind, ViolationKind::FunctionOrderMismatch(_, _, _))); + let violations = violations.into_iter().filter(|v| { + !matches!( + v.kind, + ViolationKind::FunctionOrderMismatch(_, _, _) + ) + }); for violation in violations { ctx = violation.kind.fix(ctx); } @@ -85,18 +86,31 @@ impl Check { let violations = rules::StructuralMatcher::check(&ctx); let violations: Vec = violations .into_iter() - .filter(|v| matches!(v.kind, ViolationKind::FunctionOrderMismatch(_, _, _))) + .filter(|v| { + matches!( + v.kind, + ViolationKind::FunctionOrderMismatch(_, _, _) + ) + }) .collect(); if !violations.is_empty() { if let Some(contract_sol) = find_contract(&ctx.pt) { - if let Some(contract_hir) = ctx.hir.clone().find_contract() { - ctx = fix_order(&violations, &contract_sol, contract_hir, ctx); + if let Some(contract_hir) = + ctx.hir.clone().find_contract() + { + ctx = fix_order( + &violations, + &contract_sol, + contract_hir, + ctx, + ); } } } let sol = ctx.sol.clone(); - let formatted = ctx.fmt().expect("should format the emitted solidity code"); + let formatted = + ctx.fmt().expect("should format the emitted solidity code"); self.write(&formatted, sol); fixed_count += fixable_count; @@ -153,7 +167,8 @@ fn exit(violations: &[Violation]) { violations.len(), check_literal ); - let fixable_count = violations.iter().filter(|v| v.is_fixable()).count(); + let fixable_count = + violations.iter().filter(|v| v.is_fixable()).count(); if fixable_count > 0 { let fix_literal = pluralize(fixable_count, "fix", "fixes"); eprintln!( diff --git a/src/check/rules/structural_match.rs b/src/check/rules/structural_match.rs index 76d9069..15bb8d2 100644 --- a/src/check/rules/structural_match.rs +++ b/src/check/rules/structural_match.rs @@ -2,7 +2,8 @@ //! //! This rule enforces the following: //! - All spec-generated functions & modifiers are present in the output file. -//! - The order of the spec-generated functions & modifiers matches the output file. +//! - The order of the spec-generated functions & modifiers matches the output +//! file. //! //! Matching is name-based, which means that two functions are considered the //! same if: @@ -14,6 +15,7 @@ use std::collections::BTreeSet; use solang_parser::pt; +use super::{Checker, Context}; use crate::{ check::{ location::Location, @@ -25,8 +27,6 @@ use crate::{ utils::sanitize, }; -use super::{Checker, Context}; - /// An implementation of a structural matching rule. /// /// Read more at the [module-level documentation]. @@ -72,8 +72,16 @@ impl Checker for StructuralMatcher { let contract_hir = contract_hir.unwrap(); let contract_sol = contract_sol.unwrap(); if let Hir::ContractDefinition(contract_hir) = contract_hir { - violations.append(&mut check_contract_names(contract_hir, &contract_sol, ctx)); - violations.append(&mut check_fns_structure(contract_hir, &contract_sol, ctx)); + violations.append(&mut check_contract_names( + contract_hir, + &contract_sol, + ctx, + )); + violations.append(&mut check_fns_structure( + contract_hir, + &contract_sol, + ctx, + )); }; violations @@ -93,7 +101,10 @@ fn check_contract_names( let contract_name = sanitize(&contract_hir.identifier); if identifier.name != contract_name { let violation = Violation::new( - ViolationKind::ContractNameNotMatches(contract_name, identifier.name.clone()), + ViolationKind::ContractNameNotMatches( + contract_name, + identifier.name.clone(), + ), Location::Code( ctx.sol.as_path().to_string_lossy().into_owned(), offset_to_line(&ctx.src, contract_sol.loc.start()), @@ -107,8 +118,8 @@ fn check_contract_names( } /// Checks that function structures match between the HIR and the Solidity AST. -/// i.e. that all the functions are present in the output file in the right order. -/// This could be better, currently it is O(N^2). +/// i.e. that all the functions are present in the output file in the right +/// order. This could be better, currently it is O(N^2). fn check_fns_structure( contract_hir: &hir::ContractDefinition, contract_sol: &pt::ContractDefinition, @@ -118,7 +129,8 @@ fn check_fns_structure( // Check that hir functions are present in the solidity contract. Store // their indices for later processing. - let mut present_fn_indices = Vec::with_capacity(contract_hir.children.len()); + let mut present_fn_indices = + Vec::with_capacity(contract_hir.children.len()); for (hir_idx, fn_hir) in contract_hir.children.iter().enumerate() { if let Hir::FunctionDefinition(fn_hir) = fn_hir { let fn_sol = find_matching_fn(contract_sol, fn_hir); @@ -126,13 +138,18 @@ fn check_fns_structure( match fn_sol { // Store the matched function to check it is at the // appropriate place later. - Some((sol_idx, _)) => present_fn_indices.push((hir_idx, sol_idx)), + Some((sol_idx, _)) => { + present_fn_indices.push((hir_idx, sol_idx)) + } // We didn't find a matching function, so this is a // violation. None => { if !ctx.cfg.check().skip_modifiers { violations.push(Violation::new( - ViolationKind::MatchingFunctionMissing(fn_hir.clone(), hir_idx), + ViolationKind::MatchingFunctionMissing( + fn_hir.clone(), + hir_idx, + ), Location::Code( ctx.tree.to_string_lossy().into_owned(), fn_hir.span.start.line, @@ -169,9 +186,15 @@ fn check_fns_structure( // Emit a violation per unsorted item. for (hir_idx, sol_idx) in unsorted_set { if let Hir::FunctionDefinition(_) = contract_hir.children[hir_idx] { - if let pt::ContractPart::FunctionDefinition(ref fn_sol) = contract_sol.parts[sol_idx] { + if let pt::ContractPart::FunctionDefinition(ref fn_sol) = + contract_sol.parts[sol_idx] + { violations.push(Violation::new( - ViolationKind::FunctionOrderMismatch(*fn_sol.clone(), sol_idx, hir_idx), + ViolationKind::FunctionOrderMismatch( + *fn_sol.clone(), + sol_idx, + hir_idx, + ), Location::Code( ctx.sol.clone().to_string_lossy().into_owned(), offset_to_line(&ctx.src, fn_sol.loc.start()), diff --git a/src/check/utils.rs b/src/check/utils.rs index 7dec275..dfc4698 100644 --- a/src/check/utils.rs +++ b/src/check/utils.rs @@ -1,5 +1,8 @@ /// Converts the start offset of a `Loc` to `(line, col)`. Modified from -pub(crate) fn offset_to_line_column(content: &str, start: usize) -> (usize, usize) { +pub(crate) fn offset_to_line_column( + content: &str, + start: usize, +) -> (usize, usize) { debug_assert!(content.len() > start); // first line is `1` diff --git a/src/check/violation.rs b/src/check/violation.rs index af875bd..303b7b8 100644 --- a/src/check/violation.rs +++ b/src/check/violation.rs @@ -1,22 +1,23 @@ //! Defines a rule-checking error object. -use std::borrow::Cow; -use std::fmt; +use std::{borrow::Cow, collections::HashSet, fmt}; -use forge_fmt::parse; -use forge_fmt::solang_ext::{CodeLocationExt, SafeUnwrap}; +use forge_fmt::{ + parse, + solang_ext::{CodeLocationExt, SafeUnwrap}, +}; use owo_colors::OwoColorize; -use solang_parser::pt; -use solang_parser::pt::{ContractDefinition, ContractPart}; -use std::collections::HashSet; +use solang_parser::{ + pt, + pt::{ContractDefinition, ContractPart}, +}; -use crate::error; -use crate::hir::{self, Hir}; -use crate::sol::find_matching_fn; -use crate::sol::{self, find_contract}; - -use super::context::Context; -use super::location::Location; +use super::{context::Context, location::Location}; +use crate::{ + error, + hir::{self, Hir}, + sol::{self, find_contract, find_matching_fn}, +}; /// An error that occurred while checking specification rules between /// a tree and a Solidity contract. @@ -40,12 +41,12 @@ impl Violation { } } -/// The type of an error that occurred while checking specification rules between -/// a tree and a Solidity contract. +/// The type of an error that occurred while checking specification rules +/// between a tree and a Solidity contract. /// -/// NOTE: Adding a variant to this enum most certainly will mean adding a variant to the -/// `Rules` section of `bulloak`'s README. Please, do not forget to add it if you are -/// implementing a rule. +/// NOTE: Adding a variant to this enum most certainly will mean adding a +/// variant to the `Rules` section of `bulloak`'s README. Please, do not forget +/// to add it if you are implementing a rule. #[derive(Debug)] #[non_exhaustive] pub(crate) enum ViolationKind { @@ -93,11 +94,14 @@ impl fmt::Display for Violation { impl fmt::Display for ViolationKind { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { use self::ViolationKind::{ - ContractMissing, ContractNameNotMatches, FileUnreadable, FunctionOrderMismatch, - MatchingFunctionMissing, ParsingFailed, SolidityFileMissing, + ContractMissing, ContractNameNotMatches, FileUnreadable, + FunctionOrderMismatch, MatchingFunctionMissing, ParsingFailed, + SolidityFileMissing, }; match self { - ContractMissing(contract) => write!(f, r#"contract "{contract}" is missing in .sol"#), + ContractMissing(contract) => { + write!(f, r#"contract "{contract}" is missing in .sol"#) + } ContractNameNotMatches(tree_name, sol_name) => write!( f, r#"contract "{tree_name}" is missing in .sol -- found "{sol_name}" instead"# @@ -140,7 +144,10 @@ impl fmt::Display for ViolationKind { ), } } else { - write!(f, "an error occurred while parsing the solidity file") + write!( + f, + "an error occurred while parsing the solidity file" + ) } } } @@ -159,11 +166,13 @@ impl ViolationKind { ) } - /// Optionally returns a help text to be used when displaying the violation kind. + /// Optionally returns a help text to be used when displaying the violation + /// kind. pub(crate) fn help(&self) -> Option> { let text = match self { ViolationKind::ContractMissing(name) => { - format!(r#"consider adding a contract with name "{name}""#).into() + format!(r#"consider adding a contract with name "{name}""#) + .into() } ViolationKind::ContractNameNotMatches(name, _) => { format!(r#"consider renaming the contract to "{name}""#).into() @@ -184,9 +193,11 @@ impl ViolationKind { pub(crate) fn fix(&self, mut ctx: Context) -> Context { match self { ViolationKind::ContractMissing(_) => { - let pt = sol::Translator::new(&Default::default()).translate(&ctx.hir); + let pt = sol::Translator::new(&Default::default()) + .translate(&ctx.hir); let source = sol::Formatter::new().emit(pt.clone()); - let parsed = parse(&source).expect("should parse Solidity string"); + let parsed = + parse(&source).expect("should parse Solidity string"); ctx.from_parsed(parsed) } ViolationKind::ContractNameNotMatches(new_name, old_name) => { @@ -194,7 +205,8 @@ impl ViolationKind { &format!("contract {old_name}"), &format!("contract {new_name}"), ); - let parsed = parse(&source).expect("should parse Solidity string"); + let parsed = + parse(&source).expect("should parse Solidity string"); ctx.from_parsed(parsed) } // Assume order violations have been taken care of first. @@ -206,11 +218,17 @@ impl ViolationKind { return ctx; }; - let offset = get_insertion_offset(&contract_sol, contract_hir, *index, &ctx.src); + let offset = get_insertion_offset( + &contract_sol, + contract_hir, + *index, + &ctx.src, + ); ctx.insert_function_at(fn_hir, offset); let source = ctx.src.clone(); - let parsed = parse(&source).expect("should parse solidity string"); + let parsed = + parse(&source).expect("should parse solidity string"); ctx.from_parsed(parsed) } _ => ctx, @@ -218,26 +236,32 @@ impl ViolationKind { } } -/// Determines the appropriate insertion offset for a function within a contract source code. +/// Determines the appropriate insertion offset for a function within a contract +/// source code. /// -/// This function calculates the character position in the source code at which a new function -/// should be inserted. If the function to be inserted is the first one (`index` is 0), the -/// offset is calculated as the position right after the opening brace of the contract. Otherwise, -/// it finds the location immediately after the function that precedes the insertion point in the +/// This function calculates the character position in the source code at which +/// a new function should be inserted. If the function to be inserted is the +/// first one (`index` is 0), the offset is calculated as the position right +/// after the opening brace of the contract. Otherwise, it finds the location +/// immediately after the function that precedes the insertion point in the /// HIR structure. /// /// # Arguments /// * `contract_sol` - A `ContractDefinition` from the Solidity parse tree. -/// * `contract_hir` - A `ContractDefinition` in the HIR node corresponding to the contract. -/// * `index` - The index at which the function is to be inserted in the contract children. +/// * `contract_hir` - A `ContractDefinition` in the HIR node corresponding to +/// the contract. +/// * `index` - The index at which the function is to be inserted in the +/// contract children. /// * `src` - A reference to the source code. /// /// # Returns -/// An `usize` representing the calculated offset position where the function should be inserted. +/// An `usize` representing the calculated offset position where the function +/// should be inserted. /// /// # Panics -/// Panics if it fails to locate the opening brace of the contract while processing the first function -/// (`index` is 0), indicating an issue with the solidity program structure. +/// Panics if it fails to locate the opening brace of the contract while +/// processing the first function (`index` is 0), indicating an issue with the +/// solidity program structure. fn get_insertion_offset( contract_sol: &pt::ContractDefinition, contract_hir: &hir::ContractDefinition, @@ -251,20 +275,24 @@ fn get_insertion_offset( .chars() .skip(contract_start) .position(|c| c == '{') - // We know this can't happen unless there is a bug in `solang-parser`, - // because this is a well-formed contract definition. + // We know this can't happen unless there is a bug in + // `solang-parser`, because this is a well-formed + // contract definition. .expect("should search over a valid solidity program"); return contract_start + opening_brace_pos + 1; } - if let Hir::FunctionDefinition(ref prev_fn_hir) = contract_hir.children[index - 1] { + if let Hir::FunctionDefinition(ref prev_fn_hir) = + contract_hir.children[index - 1] + { // It's fine to unwrap here since: // - We check index 0 above, which doesn't have a predecessor. - // - This function is called in a context where we know a matching function - // will exist. In this specific case, we are fixing a `MatchingFunctionMissing` - // violation, so we know there's a predecessor, otherwise we would be - // analyzing index 0. + // - This function is called in a context where we know a matching + // function + // will exist. In this specific case, we are fixing a + // `MatchingFunctionMissing` violation, so we know there's a + // predecessor, otherwise we would be analyzing index 0. let (_, prev_fn) = find_matching_fn(contract_sol, prev_fn_hir).unwrap(); return prev_fn.loc().end(); } @@ -273,41 +301,45 @@ fn get_insertion_offset( unreachable!() } -/// `fix_order` rearranges the functions in a Solidity contract (`contract_sol`) to match the order -/// specified in a higher-level intermediate representation (`contract_hir`). +/// `fix_order` rearranges the functions in a Solidity contract (`contract_sol`) +/// to match the order specified in a higher-level intermediate representation +/// (`contract_hir`). /// /// # Arguments -/// * `violations`: A slice of `Violation` instances. Each `Violation` represents a discrepancy -/// between the order of functions in the Solidity contract and the higher-level intermediate -/// representation. -/// * `contract_sol`: A reference to a `Box`, representing the Solidity contract -/// whose function order needs correction. -/// * `contract_hir`: A reference to a `hir::ContractDefinition`, representing the higher-level -/// intermediate representation that dictates the correct order of functions. -/// * `ctx`: The current `Context` which holds the source code and other relevant data for -/// processing the contract. +/// * `violations`: A slice of `Violation` instances. Each `Violation` +/// represents a discrepancy between the order of functions in the Solidity +/// contract and the higher-level intermediate representation. +/// * `contract_sol`: A reference to a `Box`, representing +/// the Solidity contract whose function order needs correction. +/// * `contract_hir`: A reference to a `hir::ContractDefinition`, representing +/// the higher-level intermediate representation that dictates the correct +/// order of functions. +/// * `ctx`: The current `Context` which holds the source code and other +/// relevant data for processing the contract. /// /// # Returns -/// Returns a `Context` instance, which is an updated version of the input `ctx`. This updated -/// `Context` contains the Solidity source code with the functions reordered as per the order -/// specified in `contract_hir`. +/// Returns a `Context` instance, which is an updated version of the input +/// `ctx`. This updated `Context` contains the Solidity source code with the +/// functions reordered as per the order specified in `contract_hir`. /// /// # Process /// The function works in several steps: -/// 1. It first creates a set of function names present in `contract_hir` to identify the functions -/// that need to be sorted. -/// 2. It then iterates over the `violations` to correct the order of functions in `contract_sol`, -/// matching them with `contract_hir`. -/// 3. Functions not part of `contract_hir` are removed, as their correct position is unknown. -/// 4. The sorted functions are then compiled into a string (`source`) and simultaneously removed -/// from a temporary string (`scratch`) that mirrors the original source code. -/// 5. Finally, the function reconstructs the contract's body by combining the sorted functions -/// and any remaining parts of the contract (preserved in `scratch`), ensuring all components -/// are included in the output. +/// 1. It first creates a set of function names present in `contract_hir` to +/// identify the functions that need to be sorted. +/// 2. It then iterates over the `violations` to correct the order of functions +/// in `contract_sol`, matching them with `contract_hir`. +/// 3. Functions not part of `contract_hir` are removed, as their correct +/// position is unknown. +/// 4. The sorted functions are then compiled into a string (`source`) and +/// simultaneously removed from a temporary string (`scratch`) that mirrors +/// the original source code. +/// 5. Finally, the function reconstructs the contract's body by combining the +/// sorted functions and any remaining parts of the contract (preserved in +/// `scratch`), ensuring all components are included in the output. /// /// # Panics -/// The function will panic if the reconstructed Solidity string fails to parse. This is a safeguard -/// to ensure the output is always a valid Solidity code. +/// The function will panic if the reconstructed Solidity string fails to parse. +/// This is a safeguard to ensure the output is always a valid Solidity code. pub(crate) fn fix_order( violations: &[Violation], contract_sol: &Box, @@ -332,7 +364,9 @@ pub(crate) fn fix_order( // 2. Properly sort functions in a new vec. let mut fns = contract_sol.parts.clone(); for violation in violations { - if let ViolationKind::FunctionOrderMismatch(f, sol_idx, hir_idx) = &violation.kind { + if let ViolationKind::FunctionOrderMismatch(f, sol_idx, hir_idx) = + &violation.kind + { fns.remove(*sol_idx); fns.insert( *hir_idx, @@ -376,7 +410,8 @@ pub(crate) fn fix_order( let first_part_loc = contract_sol.parts[0].loc(); // If the functions in the solidity file are exactly the functions in the // tree file, then we just print them. We still need to include the scratch - // because it might contain comments or other constructs that we need to keep. + // because it might contain comments or other constructs that we need to + // keep. let source = if fns.len() == contract_sol.parts.len() { format!( "{}{}{}{}", diff --git a/src/error.rs b/src/error.rs index 25d653e..2a9b229 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,13 +1,11 @@ -use std::cmp; -use std::fmt; -use std::result; +use std::{cmp, fmt, result}; -use crate::hir::combiner; -use crate::span; -use crate::syntax::parser; -use crate::syntax::semantics; -use crate::syntax::tokenizer; -use crate::utils::repeat_str; +use crate::{ + hir::combiner, + span, + syntax::{parser, semantics, tokenizer}, + utils::repeat_str, +}; /// A type alias for dealing with errors returned when parsing. pub(crate) type Result = result::Result; @@ -86,41 +84,25 @@ pub(crate) struct Formatter<'e, E> { impl<'e> From<&'e parser::Error> for Formatter<'e, parser::ErrorKind> { fn from(err: &'e parser::Error) -> Self { - Formatter { - text: err.text(), - err: err.kind(), - span: err.span(), - } + Formatter { text: err.text(), err: err.kind(), span: err.span() } } } impl<'e> From<&'e tokenizer::Error> for Formatter<'e, tokenizer::ErrorKind> { fn from(err: &'e tokenizer::Error) -> Self { - Formatter { - text: err.text(), - err: err.kind(), - span: err.span(), - } + Formatter { text: err.text(), err: err.kind(), span: err.span() } } } impl<'e> From<&'e combiner::Error> for Formatter<'e, combiner::ErrorKind> { fn from(err: &'e combiner::Error) -> Self { - Formatter { - text: err.text(), - err: err.kind(), - span: err.span(), - } + Formatter { text: err.text(), err: err.kind(), span: err.span() } } } impl<'e> From<&'e semantics::Error> for Formatter<'e, semantics::ErrorKind> { fn from(err: &'e semantics::Error) -> Self { - Formatter { - text: err.text(), - err: err.kind(), - span: err.span(), - } + Formatter { text: err.text(), err: err.kind(), span: err.span() } } } @@ -155,7 +137,8 @@ fn notate(f: &Formatter<'_, E>) -> String { notated.push_str(line); notated.push('\n'); notated.push_str(&repeat_str(" ", f.span.start.column - 1)); - let note_len = f.span.end.column.saturating_sub(f.span.start.column) + 1; + let note_len = + f.span.end.column.saturating_sub(f.span.start.column) + 1; let note_len = cmp::max(1, note_len); notated.push_str(&repeat_str("^", note_len)); notated.push('\n'); @@ -166,12 +149,15 @@ fn notate(f: &Formatter<'_, E>) -> String { #[cfg(test)] mod test { - use super::repeat_str; - use crate::error::Formatter; - use crate::span::{Position, Span}; - use crate::syntax::{parser, semantics}; use pretty_assertions::assert_eq; + use super::repeat_str; + use crate::{ + error::Formatter, + span::{Position, Span}, + syntax::{parser, semantics}, + }; + #[test] fn test_notate() { let text = "hello\nworld\n"; @@ -186,7 +172,8 @@ mod test { let mut expected = String::from(""); expected.push_str(&repeat_str("•", 79)); expected.push('\n'); - expected.push_str(format!("bulloak error: {}\n\n", formatter.err).as_str()); + expected + .push_str(format!("bulloak error: {}\n\n", formatter.err).as_str()); expected.push_str("world\n"); expected.push_str("^^^^^\n\n"); expected.push_str( diff --git a/src/hir/combiner.rs b/src/hir/combiner.rs index cb70b40..3f66958 100644 --- a/src/hir/combiner.rs +++ b/src/hir/combiner.rs @@ -1,9 +1,12 @@ -//! The implementation of a high-level intermediate representation (HIR) combiner. +//! The implementation of a high-level intermediate representation (HIR) +//! combiner. use std::{collections::HashSet, fmt, mem, result}; -use crate::{constants::CONTRACT_IDENTIFIER_SEPARATOR, span::Span, utils::capitalize_first_letter}; - use super::{ContractDefinition, Hir, Root}; +use crate::{ + constants::CONTRACT_IDENTIFIER_SEPARATOR, span::Span, + utils::capitalize_first_letter, +}; type Result = result::Result; @@ -65,7 +68,9 @@ impl fmt::Display for Error { impl fmt::Display for ErrorKind { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - use self::ErrorKind::{ContractNameMismatch, ContractNameMissing, SeparatorMissing}; + use self::ErrorKind::{ + ContractNameMismatch, ContractNameMissing, SeparatorMissing, + }; match self { ContractNameMismatch(actual, expected) => write!( f, @@ -124,11 +129,7 @@ impl<'t> CombinerI<'t> { /// Create a new error with the given span and error type. fn error(&self, span: Span, kind: ErrorKind) -> Error { - Error { - kind, - text: self.text.to_owned(), - span, - } + Error { kind, text: self.text.to_owned(), span } } /// Internal implementation of `Combiner::combine`. @@ -158,22 +159,30 @@ impl<'t> CombinerI<'t> { let (contract_name, function_name) = contract .identifier .split_once(CONTRACT_IDENTIFIER_SEPARATOR) - .ok_or(self.error(Span::default(), ErrorKind::SeparatorMissing(idx + 1)))?; + .ok_or(self.error( + Span::default(), + ErrorKind::SeparatorMissing(idx + 1), + ))?; if contract_name.trim().is_empty() { - return Err( - self.error(Span::default(), ErrorKind::ContractNameMissing(idx + 1)) - ); + return Err(self.error( + Span::default(), + ErrorKind::ContractNameMissing(idx + 1), + )); } - // If the accumulated identifier is empty, we're on the first contract. + // If the accumulated identifier is empty, we're on the first + // contract. if acc_contract.identifier.is_empty() { - // Add modifiers to the list of added modifiers and prefix test names. + // Add modifiers to the list of added modifiers and prefix + // test names. let children = contract .children .into_iter() .map(|c| prefix_test(c, function_name)) - .filter_map(|c| collect_modifier(c, &mut unique_modifiers)) + .filter_map(|c| { + collect_modifier(c, &mut unique_modifiers) + }) .collect(); let first_contract = ContractDefinition { identifier: contract_name.to_owned(), @@ -194,8 +203,11 @@ impl<'t> CombinerI<'t> { )); } - let children = - update_children(contract.children, function_name, &mut unique_modifiers); + let children = update_children( + contract.children, + function_name, + &mut unique_modifiers, + ); acc_contract.children.extend(children); } } @@ -213,7 +225,8 @@ fn prefix_test(child: Hir, prefix: &str) -> Hir { }; if test_or_modifier.is_function() { - test_or_modifier.identifier = prefix_test_with(&test_or_modifier.identifier, prefix); + test_or_modifier.identifier = + prefix_test_with(&test_or_modifier.identifier, prefix); } Hir::FunctionDefinition(test_or_modifier) @@ -239,7 +252,10 @@ fn prefix_test_with(test_name: &str, prefix: &str) -> String { format!("test_{capitalized_fn_name}{test_suffix}") } -fn collect_modifier(child: Hir, unique_modifiers: &mut HashSet) -> Option { +fn collect_modifier( + child: Hir, + unique_modifiers: &mut HashSet, +) -> Option { let Hir::FunctionDefinition(test_or_modifier) = child else { return Some(child); }; @@ -260,12 +276,13 @@ mod tests { use anyhow::{Error, Result}; use pretty_assertions::assert_eq; - use crate::config::Config; - use crate::hir::{self, Hir}; - use crate::scaffold::modifiers; - use crate::span::{Position, Span}; - use crate::syntax::parser::Parser; - use crate::syntax::tokenizer::Tokenizer; + use crate::{ + config::Config, + hir::{self, Hir}, + scaffold::modifiers, + span::{Position, Span}, + syntax::{parser::Parser, tokenizer::Tokenizer}, + }; fn translate(text: &str) -> Result { let tokens = Tokenizer::new().tokenize(&text)?; @@ -354,7 +371,8 @@ bulloak error: contract name missing at tree root #2"; "Contract::function1\n└── when something bad happens\n └── it should revert", "Contract::function2\n└── when something shit happens\n └── it should revert", ]; - let mut hirs: Vec<_> = trees.iter().map(|tree| translate(tree).unwrap()).collect(); + let mut hirs: Vec<_> = + trees.iter().map(|tree| translate(tree).unwrap()).collect(); // Append a comment HIR to the hirs. hirs.push(root(vec![comment("this is a random comment".to_owned())])); @@ -371,9 +389,13 @@ bulloak error: contract name missing at tree root #2"; "Contract".to_owned(), vec![ function( - "test_Function1RevertWhen_SomethingBadHappens".to_owned(), + "test_Function1RevertWhen_SomethingBadHappens" + .to_owned(), hir::FunctionTy::Function, - Span::new(Position::new(20, 2, 1), Position::new(86, 3, 24)), + Span::new( + Position::new(20, 2, 1), + Position::new(86, 3, 24) + ), None, Some(vec![ comment("it should revert".to_owned()), @@ -381,9 +403,13 @@ bulloak error: contract name missing at tree root #2"; ]) ), function( - "test_Function2RevertWhen_SomethingShitHappens".to_owned(), + "test_Function2RevertWhen_SomethingShitHappens" + .to_owned(), hir::FunctionTy::Function, - Span::new(Position::new(20, 2, 1), Position::new(87, 3, 24)), + Span::new( + Position::new(20, 2, 1), + Position::new(87, 3, 24) + ), None, Some(vec![ comment("it should revert".to_owned()), @@ -401,7 +427,8 @@ bulloak error: contract name missing at tree root #2"; "Contract::function1\n└── when something bad happens\n └── given something else happens\n └── it should revert", "Contract::function2\n└── when something bad happens\n └── given the caller is 0x1337\n └── it should revert", ]; - let mut hirs: Vec<_> = trees.iter().map(|tree| translate(tree).unwrap()).collect(); + let mut hirs: Vec<_> = + trees.iter().map(|tree| translate(tree).unwrap()).collect(); // Append a comment HIR to the hirs. hirs.push(root(vec![comment("this is a random comment".to_owned())])); @@ -420,14 +447,21 @@ bulloak error: contract name missing at tree root #2"; function( "whenSomethingBadHappens".to_owned(), hir::FunctionTy::Modifier, - Span::new(Position::new(20, 2, 1), Position::new(133, 4, 28)), + Span::new( + Position::new(20, 2, 1), + Position::new(133, 4, 28) + ), None, None ), function( - "test_Function1RevertGiven_SomethingElseHappens".to_owned(), + "test_Function1RevertGiven_SomethingElseHappens" + .to_owned(), hir::FunctionTy::Function, - Span::new(Position::new(61, 3, 5), Position::new(133, 4, 28)), + Span::new( + Position::new(61, 3, 5), + Position::new(133, 4, 28) + ), Some(vec!["whenSomethingBadHappens".to_owned()]), Some(vec![ comment("it should revert".to_owned()), @@ -435,9 +469,13 @@ bulloak error: contract name missing at tree root #2"; ]) ), function( - "test_Function2RevertGiven_TheCallerIs0x1337".to_owned(), + "test_Function2RevertGiven_TheCallerIs0x1337" + .to_owned(), hir::FunctionTy::Function, - Span::new(Position::new(61, 3, 5), Position::new(131, 4, 28)), + Span::new( + Position::new(61, 3, 5), + Position::new(131, 4, 28) + ), Some(vec!["whenSomethingBadHappens".to_owned()]), Some(vec![ comment("it should revert".to_owned()), diff --git a/src/hir/mod.rs b/src/hir/mod.rs index dc0c15e..ae2a525 100644 --- a/src/hir/mod.rs +++ b/src/hir/mod.rs @@ -10,22 +10,28 @@ pub mod visitor; pub use hir::*; use crate::{ - config::Config, error, scaffold::modifiers::ModifierDiscoverer, syntax, utils::split_trees, + config::Config, error, scaffold::modifiers::ModifierDiscoverer, syntax, + utils::split_trees, }; /// High-level function that returns a HIR given the contents of a `.tree` file. /// -/// This function leverages [`translate_tree_to_hir`] to generate the HIR for each tree, -/// and [`crate::hir::combiner::Combiner::combine`] to combine the HIRs into a single HIR. +/// This function leverages [`translate_tree_to_hir`] to generate the HIR for +/// each tree, and [`crate::hir::combiner::Combiner::combine`] to combine the +/// HIRs into a single HIR. pub fn translate(text: &str, cfg: &Config) -> anyhow::Result { Ok(translate_and_combine_trees(text, cfg)?) } /// High-level function that returns a HIR given the contents of a `.tree` file. /// -/// This function leverages [`translate_tree_to_hir`] to generate the HIR for each tree, -/// and [`crate::hir::combiner::Combiner::combine`] to combine the HIRs into a single HIR. -pub(crate) fn translate_and_combine_trees(text: &str, cfg: &Config) -> error::Result { +/// This function leverages [`translate_tree_to_hir`] to generate the HIR for +/// each tree, and [`crate::hir::combiner::Combiner::combine`] to combine the +/// HIRs into a single HIR. +pub(crate) fn translate_and_combine_trees( + text: &str, + cfg: &Config, +) -> error::Result { let trees = split_trees(text); let hirs = trees .map(|tree| translate_tree_to_hir(tree, cfg)) @@ -35,8 +41,9 @@ pub(crate) fn translate_and_combine_trees(text: &str, cfg: &Config) -> error::Re /// Generates the HIR for a single tree. /// -/// This function leverages [`crate::syntax::parse`] and [`crate::hir::translator::Translator::translate`] -/// to hide away most of the complexity of `bulloak`'s internal compiler. +/// This function leverages [`crate::syntax::parse`] and +/// [`crate::hir::translator::Translator::translate`] to hide away most of the +/// complexity of `bulloak`'s internal compiler. pub fn translate_tree_to_hir(tree: &str, cfg: &Config) -> error::Result { let ast = syntax::parse(tree)?; let mut discoverer = ModifierDiscoverer::new(); diff --git a/src/hir/translator.rs b/src/hir/translator.rs index 4749c3b..714f28e 100644 --- a/src/hir/translator.rs +++ b/src/hir/translator.rs @@ -2,11 +2,12 @@ //! high-level intermediate representation (HIR) -- AST -> HIR. use indexmap::IndexMap; -use crate::config::Config; -use crate::hir::{self, Hir}; -use crate::syntax::ast; -use crate::syntax::visitor::Visitor; -use crate::utils::{capitalize_first_letter, sanitize}; +use crate::{ + config::Config, + hir::{self, Hir}, + syntax::{ast, visitor::Visitor}, + utils::{capitalize_first_letter, sanitize}, +}; /// A translator between a bulloak tree abstract syntax tree (AST) /// and a high-level intermediate representation (HIR) -- AST -> HIR. @@ -63,11 +64,7 @@ impl<'a> TranslatorI<'a> { /// Creates a new internal translator. fn new(modifiers: &'a IndexMap, cfg: &Config) -> Self { let with_vm_skip = cfg.scaffold().with_vm_skip; - Self { - modifier_stack: Vec::new(), - modifiers, - with_vm_skip, - } + Self { modifier_stack: Vec::new(), modifiers, with_vm_skip } } /// Concrete implementation of the translation from AST to HIR. @@ -84,58 +81,71 @@ impl<'a> TranslatorI<'a> { } impl<'a> Visitor for TranslatorI<'a> { - type Output = Vec; type Error = (); + type Output = Vec; - fn visit_root(&mut self, root: &crate::syntax::ast::Root) -> Result { + fn visit_root( + &mut self, + root: &crate::syntax::ast::Root, + ) -> Result { let mut root_children = Vec::new(); let mut contract_children = Vec::new(); for ast in &root.children { match ast { - // Root or ActionDescription nodes cannot be children of a root node. This - // must be handled in a previous pass. - ast::Ast::Root(_) | ast::Ast::ActionDescription(_) => unreachable!(), + // Root or ActionDescription nodes cannot be children of a root + // node. This must be handled in a previous + // pass. + ast::Ast::Root(_) | ast::Ast::ActionDescription(_) => { + unreachable!() + } // Found a top-level action. This corresponds to a function. ast::Ast::Action(action) => { let words = action.title.split_whitespace(); let words = words.skip(1); // Removes "it" from the test name. - // Map an iterator over the words of an action to the test name. + // Map an iterator over the words of an action to the test + // name. // // Example: [do, stuff] -> DoStuff - let test_name = - words.fold(String::with_capacity(action.title.len()), |mut acc, w| { + let test_name = words.fold( + String::with_capacity(action.title.len()), + |mut acc, w| { acc.reserve(w.len() + 1); acc.push_str(&capitalize_first_letter(w)); acc - }); + }, + ); // We need to sanitize here and not in a previous compiler - // phase because we want to emit the action as is in a comment. + // phase because we want to emit the action as is in a + // comment. let test_name = sanitize(&test_name); let test_name = format!("test_{test_name}"); let mut hirs = self.visit_action(action)?; - // Include any optional statement for the first function node. + // Include any optional statement for the first function + // node. if self.with_vm_skip { hirs.push(Hir::Statement(hir::Statement { ty: hir::StatementType::VmSkip, })); } - let hir = Hir::FunctionDefinition(hir::FunctionDefinition { - identifier: test_name, - ty: hir::FunctionTy::Function, - span: action.span, - modifiers: None, - children: Some(hirs), - }); + let hir = + Hir::FunctionDefinition(hir::FunctionDefinition { + identifier: test_name, + ty: hir::FunctionTy::Function, + span: action.span, + modifiers: None, + children: Some(hirs), + }); contract_children.push(hir); } ast::Ast::Condition(condition) => { - contract_children.append(&mut self.visit_condition(condition)?); + contract_children + .append(&mut self.visit_condition(condition)?); } } } @@ -146,9 +156,7 @@ impl<'a> Visitor for TranslatorI<'a> { children: contract_children, })); - Ok(vec![Hir::Root(hir::Root { - children: root_children, - })]) + Ok(vec![Hir::Root(hir::Root { children: root_children })]) } fn visit_condition( @@ -162,8 +170,9 @@ impl<'a> Visitor for TranslatorI<'a> { .iter() .filter(|child| ast::Ast::is_action(child)) .count(); - // If this condition only has actions as children, then we don't generate - // a modifier for it, since it would only be used in the emitted function. + // If this condition only has actions as children, then we don't + // generate a modifier for it, since it would only be used in + // the emitted function. if condition.children.len() != action_count { if let Some(modifier) = self.modifiers.get(&condition.title) { self.modifier_stack.push(modifier); @@ -190,11 +199,12 @@ impl<'a> Visitor for TranslatorI<'a> { // Add this condition's function definition if it has children actions. if !actions.is_empty() { - // If the only action is `it should revert`, we slightly change the function name - // to reflect this. + // If the only action is `it should revert`, we slightly change the + // function name to reflect this. let is_revert = actions.first().is_some_and(|action| { if let hir::Hir::Comment(comment) = action { - let sanitized_lexeme = sanitize(&comment.lexeme.trim().to_lowercase()); + let sanitized_lexeme = + sanitize(&comment.lexeme.trim().to_lowercase()); sanitized_lexeme == "it should revert" } else { false @@ -202,15 +212,19 @@ impl<'a> Visitor for TranslatorI<'a> { }); let mut words = condition.title.split_whitespace(); - // It is fine to unwrap because conditions have at least one word in them. + // It is fine to unwrap because conditions have at least one word in + // them. let keyword = capitalize_first_letter(words.next().unwrap()); let function_name = if is_revert { - // Map an iterator over the words of a condition to the test name. + // Map an iterator over the words of a condition to the test + // name. // // Example: [when, something, happens] -> WhenSomethingHappens let test_name = words.fold( - String::with_capacity(condition.title.len() - keyword.len()), + String::with_capacity( + condition.title.len() - keyword.len(), + ), |mut acc, w| { acc.reserve(w.len() + 1); acc.push_str(&capitalize_first_letter(w)); @@ -225,7 +239,8 @@ impl<'a> Visitor for TranslatorI<'a> { // where `KEYWORD` is the starting word of the condition. format!("test_Revert{keyword}_{test_name}") } else { - // Map an iterator over the words of a condition to the test name. + // Map an iterator over the words of a condition to the test + // name. // // Example: [when, something, happens] -> WhenSomethingHappens let test_name = words.fold(keyword, |mut acc, w| { @@ -240,7 +255,9 @@ impl<'a> Visitor for TranslatorI<'a> { let modifiers = if self.modifier_stack.is_empty() { None } else { - Some(self.modifier_stack.iter().map(|&m| m.to_owned()).collect()) + Some( + self.modifier_stack.iter().map(|&m| m.to_owned()).collect(), + ) }; // Add a `vm.skip(true);` at the start of the function. @@ -307,12 +324,13 @@ mod tests { use anyhow::Result; use pretty_assertions::assert_eq; - use crate::config::Config; - use crate::hir::{self, Hir}; - use crate::scaffold::modifiers; - use crate::span::{Position, Span}; - use crate::syntax::parser::Parser; - use crate::syntax::tokenizer::Tokenizer; + use crate::{ + config::Config, + hir::{self, Hir}, + scaffold::modifiers, + span::{Position, Span}, + syntax::{parser::Parser, tokenizer::Tokenizer}, + }; fn translate(text: &str) -> Result { let tokens = Tokenizer::new().tokenize(&text)?; @@ -362,7 +380,8 @@ mod tests { #[test] fn one_child() { - let file_contents = "Foo_Test\n└── when something bad happens\n └── it should revert"; + let file_contents = + "Foo_Test\n└── when something bad happens\n └── it should revert"; assert_eq!( translate(file_contents).unwrap(), root(vec![contract( @@ -396,7 +415,10 @@ mod tests { function( "test_RevertWhen_StuffCalled".to_owned(), hir::FunctionTy::Function, - Span::new(Position::new(19, 2, 1), Position::new(77, 3, 23)), + Span::new( + Position::new(19, 2, 1), + Position::new(77, 3, 23) + ), None, Some(vec![ comment("it should revert".to_owned()), @@ -406,7 +428,10 @@ mod tests { function( "test_RevertGiven_NotStuffCalled".to_owned(), hir::FunctionTy::Function, - Span::new(Position::new(79, 4, 1), Position::new(140, 5, 23)), + Span::new( + Position::new(79, 4, 1), + Position::new(140, 5, 23) + ), None, Some(vec![ comment("it should revert".to_owned()), @@ -440,14 +465,20 @@ Foo_Test function( "whenStuffCalled".to_owned(), hir::FunctionTy::Modifier, - Span::new(Position::new(10, 3, 1), Position::new(235, 9, 32)), + Span::new( + Position::new(10, 3, 1), + Position::new(235, 9, 32) + ), None, None ), function( "test_WhenStuffCalled".to_owned(), hir::FunctionTy::Function, - Span::new(Position::new(10, 3, 1), Position::new(235, 9, 32)), + Span::new( + Position::new(10, 3, 1), + Position::new(235, 9, 32) + ), Some(vec!["whenStuffCalled".to_owned()]), Some(vec![ comment("It should do stuff.".to_owned()), @@ -458,7 +489,10 @@ Foo_Test function( "test_RevertWhen_ACalled".to_owned(), hir::FunctionTy::Function, - Span::new(Position::new(76, 5, 5), Position::new(135, 6, 28)), + Span::new( + Position::new(76, 5, 5), + Position::new(135, 6, 28) + ), Some(vec!["whenStuffCalled".to_owned()]), Some(vec![ comment("it should revert".to_owned()), @@ -468,7 +502,10 @@ Foo_Test function( "test_WhenBCalled".to_owned(), hir::FunctionTy::Function, - Span::new(Position::new(174, 8, 5), Position::new(235, 9, 32)), + Span::new( + Position::new(174, 8, 5), + Position::new(235, 9, 32) + ), Some(vec!["whenStuffCalled".to_owned()]), Some(vec![ comment("it should not revert".to_owned()), diff --git a/src/hir/visitor.rs b/src/hir/visitor.rs index dcbb764..08b468e 100644 --- a/src/hir/visitor.rs +++ b/src/hir/visitor.rs @@ -20,22 +20,27 @@ pub trait Visitor { /// An error that might occur when visiting the HIR. type Error; - /// Visits the root node of the HIR. This method is typically where the traversal - /// of the HIR begins. + /// Visits the root node of the HIR. This method is typically where the + /// traversal of the HIR begins. /// /// # Arguments /// * `root` - A reference to the root node of the HIR. /// /// # Returns - /// A `Result` containing either the output of visiting the root node or an error. - fn visit_root(&mut self, root: &hir::Root) -> Result; + /// A `Result` containing either the output of visiting the root node or an + /// error. + fn visit_root( + &mut self, + root: &hir::Root, + ) -> Result; /// Visits a contract definition node within the HIR. /// /// # Arguments /// * `contract` - A reference to the contract definition node in the HIR. /// /// # Returns - /// A `Result` containing either the output of visiting the contract definition node or an error. + /// A `Result` containing either the output of visiting the contract + /// definition node or an error. fn visit_contract( &mut self, contract: &hir::ContractDefinition, @@ -46,29 +51,34 @@ pub trait Visitor { /// * `function` - A reference to the function definition node in the HIR. /// /// # Returns - /// A `Result` containing either the output of visiting the function definition node or an error. + /// A `Result` containing either the output of visiting the function + /// definition node or an error. fn visit_function( &mut self, function: &hir::FunctionDefinition, ) -> Result; - /// Visits a comment node within the HIR. This allows for handling comments in the - /// context of the HIR, potentially for documentation generation or other purposes. + /// Visits a comment node within the HIR. This allows for handling comments + /// in the context of the HIR, potentially for documentation generation + /// or other purposes. /// /// # Arguments /// * `comment` - A reference to the comment node in the HIR. /// /// # Returns - /// A `Result` containing either the output of visiting the comment node or an error. - fn visit_comment(&mut self, comment: &hir::Comment) - -> Result; + /// A `Result` containing either the output of visiting the comment node or + /// an error. + fn visit_comment( + &mut self, + comment: &hir::Comment, + ) -> Result; /// Visits a statement node within the HIR. /// /// # Arguments /// * `statement` - A reference to the statement node in the HIR. - /// /// # Returns - /// A `Result` containing either the output of visiting the statement node or an error. + /// A `Result` containing either the output of visiting the statement node + /// or an error. fn visit_statement( &mut self, statement: &hir::Statement, diff --git a/src/scaffold/emitter.rs b/src/scaffold/emitter.rs index fe9f68e..94d4e40 100644 --- a/src/scaffold/emitter.rs +++ b/src/scaffold/emitter.rs @@ -2,11 +2,12 @@ use std::result; -use crate::config::Config; -use crate::constants::INTERNAL_DEFAULT_INDENTATION; -use crate::hir::visitor::Visitor; -use crate::hir::{self, Hir}; -use crate::utils::sanitize; +use crate::{ + config::Config, + constants::INTERNAL_DEFAULT_INDENTATION, + hir::{self, visitor::Visitor, Hir}, + utils::sanitize, +}; /// Solidity code emitter. /// @@ -66,11 +67,17 @@ impl EmitterI { fn emit(&mut self, hir: &hir::Hir) -> String { match hir { Hir::Root(ref inner) => self.visit_root(inner).unwrap(), - Hir::ContractDefinition(ref inner) => self.visit_contract(inner).unwrap(), - Hir::FunctionDefinition(ref inner) => self.visit_function(inner).unwrap(), + Hir::ContractDefinition(ref inner) => { + self.visit_contract(inner).unwrap() + } + Hir::FunctionDefinition(ref inner) => { + self.visit_function(inner).unwrap() + } Hir::Comment(ref inner) => self.visit_comment(inner).unwrap(), Hir::Statement(_) => { - unreachable!("a statement can't be a top-level source unit in Solidity") + unreachable!( + "a statement can't be a top-level source unit in Solidity" + ) } } } @@ -80,10 +87,14 @@ impl EmitterI { /// This includes: /// - The Solidity version pragma. /// - The contract's name. - fn emit_contract_header(&self, contract: &hir::ContractDefinition) -> String { + fn emit_contract_header( + &self, + contract: &hir::ContractDefinition, + ) -> String { let mut emitted = String::new(); - // It's fine to unwrap here because we check that the filename always has an extension. + // It's fine to unwrap here because we check that the filename always + // has an extension. let contract_name = sanitize(&contract.identifier); emitted.push_str(format!("contract {contract_name} {{\n").as_str()); @@ -124,19 +135,28 @@ impl EmitterI { let has_modifiers = function.modifiers.is_some(); if has_modifiers { emitted.push_str( - format!("{}function {}()\n", fn_indentation, function.identifier).as_str(), + format!( + "{}function {}()\n", + fn_indentation, function.identifier + ) + .as_str(), ); - emitted.push_str(format!("{fn_body_indentation}external\n").as_str()); - } else { emitted - .push_str(format!("{}function {}()", fn_indentation, function.identifier).as_str()); + .push_str(format!("{fn_body_indentation}external\n").as_str()); + } else { + emitted.push_str( + format!("{}function {}()", fn_indentation, function.identifier) + .as_str(), + ); emitted.push_str(" external"); } // Emit the modifiers that should be applied to this function. if let Some(ref modifiers) = function.modifiers { for modifier in modifiers { - emitted.push_str(format!("{fn_body_indentation}{modifier}\n").as_str()); + emitted.push_str( + format!("{fn_body_indentation}{modifier}\n").as_str(), + ); } } @@ -156,14 +176,17 @@ impl EmitterI { /// passes ensure that the HIR is valid. In case an error /// is found, it should be added to a previous pass. impl Visitor for EmitterI { - type RootOutput = String; + type CommentOutput = String; type ContractDefinitionOutput = String; + type Error = (); type FunctionDefinitionOutput = String; - type CommentOutput = String; + type RootOutput = String; type StatementOutput = String; - type Error = (); - fn visit_root(&mut self, root: &hir::Root) -> result::Result { + fn visit_root( + &mut self, + root: &hir::Root, + ) -> result::Result { let mut emitted = String::new(); emitted.push_str("// SPDX-License-Identifier: UNLICENSED\n"); emitted.push_str(&format!( @@ -173,7 +196,9 @@ impl Visitor for EmitterI { for hir in &root.children { let result = match hir { - Hir::ContractDefinition(contract) => self.visit_contract(contract)?, + Hir::ContractDefinition(contract) => { + self.visit_contract(contract)? + } _ => unreachable!(), }; @@ -241,7 +266,8 @@ impl Visitor for EmitterI { ) -> result::Result { let mut emitted = String::new(); let indentation = self.emitter.indent().repeat(2); - emitted.push_str(format!("{indentation}// {}\n", comment.lexeme).as_str()); + emitted + .push_str(format!("{indentation}// {}\n", comment.lexeme).as_str()); Ok(emitted) } @@ -256,7 +282,9 @@ impl Visitor for EmitterI { // Match any supported statement to its string representation match statement.ty { hir::StatementType::VmSkip => { - emitted.push_str(format!("{indentation}vm.skip(true);\n").as_str()); + emitted.push_str( + format!("{indentation}vm.skip(true);\n").as_str(), + ); } } @@ -268,10 +296,12 @@ impl Visitor for EmitterI { mod tests { use pretty_assertions::assert_eq; - use crate::config::Config; - use crate::error::Result; - use crate::hir::{translate_and_combine_trees, Hir, Statement, StatementType}; - use crate::scaffold::emitter; + use crate::{ + config::Config, + error::Result, + hir::{translate_and_combine_trees, Hir, Statement, StatementType}, + scaffold::emitter, + }; fn scaffold(text: &str) -> Result { let cfg = Default::default(); @@ -297,8 +327,9 @@ contract FileTest { ); // Test that "it should revert" actions change the test name. - let file_contents = - String::from("FileTest\n└── when something bad happens\n └── it should revert"); + let file_contents = String::from( + "FileTest\n└── when something bad happens\n └── it should revert", + ); assert_eq!( &scaffold(&file_contents)?, @@ -317,7 +348,9 @@ contract FileTest { #[test] fn actions_without_conditions() -> Result<()> { - let file_contents = String::from("FileTest\n├── it should do st-ff\n└── It never reverts."); + let file_contents = String::from( + "FileTest\n├── it should do st-ff\n└── It never reverts.", + ); assert_eq!( &scaffold(&file_contents)?, @@ -456,9 +489,7 @@ contract FileTest { #[test] #[should_panic] fn with_vm_skip_top_level_statement() { - let hir = Hir::Statement(Statement { - ty: StatementType::VmSkip, - }); + let hir = Hir::Statement(Statement { ty: StatementType::VmSkip }); let _ = emitter::Emitter::new(&Default::default()).emit(&hir); } diff --git a/src/scaffold/mod.rs b/src/scaffold/mod.rs index 8b2b500..77df350 100644 --- a/src/scaffold/mod.rs +++ b/src/scaffold/mod.rs @@ -2,18 +2,20 @@ //! //! This command scaffolds a Solidity file from a spec `.tree` file. -use std::path::Path; -use std::{fs, path::PathBuf}; +use std::{ + fs, + path::{Path, PathBuf}, +}; use clap::Parser; use forge_fmt::fmt; use owo_colors::OwoColorize; use serde::{Deserialize, Serialize}; -use crate::config::Config; -use crate::constants::INTERNAL_DEFAULT_SOL_VERSION; -use crate::hir::translate_and_combine_trees; -use crate::sol; +use crate::{ + config::Config, constants::INTERNAL_DEFAULT_SOL_VERSION, + hir::translate_and_combine_trees, sol, +}; pub mod emitter; pub mod modifiers; @@ -37,7 +39,12 @@ pub struct Scaffold { pub write_files: bool, /// When `--write-files` is passed, use `--force-write` to /// overwrite the output files. - #[arg(short = 'f', long, requires = "file-handling", default_value_t = false)] + #[arg( + short = 'f', + long, + requires = "file-handling", + default_value_t = false + )] pub force_write: bool, /// Sets a Solidity version for the test contracts. #[arg(short = 's', long, default_value = INTERNAL_DEFAULT_SOL_VERSION)] @@ -142,7 +149,8 @@ pub fn scaffold(text: &str, cfg: &Config) -> crate::error::Result { let hir = translate_and_combine_trees(text, cfg)?; let pt = sol::Translator::new(cfg).translate(&hir); let source = sol::Formatter::new().emit(pt); - let formatted = fmt(&source).expect("should format the emitted solidity code"); + let formatted = + fmt(&source).expect("should format the emitted solidity code"); Ok(formatted) } diff --git a/src/scaffold/modifiers.rs b/src/scaffold/modifiers.rs index 5bc9fe6..9b2b879 100644 --- a/src/scaffold/modifiers.rs +++ b/src/scaffold/modifiers.rs @@ -5,15 +5,19 @@ use indexmap::IndexMap; -use crate::syntax::ast::{self, Ast}; -use crate::syntax::visitor::Visitor; -use crate::utils::{lower_first_letter, to_pascal_case}; +use crate::{ + syntax::{ + ast::{self, Ast}, + visitor::Visitor, + }, + utils::{lower_first_letter, to_pascal_case}, +}; /// AST visitor that discovers modifiers. /// -/// Modifiers are discovered by visiting the AST and collecting all condition titles. -/// The collected titles are then converted to modifiers. For example, the title -/// `when only owner` is converted to the `whenOnlyOwner` modifier. +/// Modifiers are discovered by visiting the AST and collecting all condition +/// titles. The collected titles are then converted to modifiers. For example, +/// the title `when only owner` is converted to the `whenOnlyOwner` modifier. /// /// For ease of retrieval, the discovered modifiers are stored in a `IndexMap` /// for the later phases of the compiler. @@ -29,9 +33,7 @@ impl ModifierDiscoverer { /// Create a new discoverer. #[must_use] pub fn new() -> Self { - Self { - modifiers: IndexMap::new(), - } + Self { modifiers: IndexMap::new() } } /// Discover modifiers in the given AST. @@ -52,10 +54,13 @@ impl ModifierDiscoverer { /// A visitor that stores key-value pairs of condition titles and /// their corresponding modifiers. impl Visitor for ModifierDiscoverer { - type Output = (); type Error = (); + type Output = (); - fn visit_root(&mut self, root: &ast::Root) -> Result { + fn visit_root( + &mut self, + root: &ast::Root, + ) -> Result { for condition in &root.children { if let Ast::Condition(condition) = condition { self.visit_condition(condition)?; @@ -65,7 +70,10 @@ impl Visitor for ModifierDiscoverer { Ok(()) } - fn visit_condition(&mut self, condition: &ast::Condition) -> Result { + fn visit_condition( + &mut self, + condition: &ast::Condition, + ) -> Result { self.modifiers.insert( condition.title.clone(), lower_first_letter(&to_pascal_case(&condition.title)), @@ -80,7 +88,10 @@ impl Visitor for ModifierDiscoverer { Ok(()) } - fn visit_action(&mut self, _action: &ast::Action) -> Result { + fn visit_action( + &mut self, + _action: &ast::Action, + ) -> Result { // No-op. Ok(()) } @@ -97,13 +108,13 @@ impl Visitor for ModifierDiscoverer { #[cfg(test)] mod tests { use indexmap::IndexMap; - use pretty_assertions::assert_eq; - use crate::error::Result; - use crate::scaffold::modifiers::ModifierDiscoverer; - use crate::syntax::parser::Parser; - use crate::syntax::tokenizer::Tokenizer; + use crate::{ + error::Result, + scaffold::modifiers::ModifierDiscoverer, + syntax::{parser::Parser, tokenizer::Tokenizer}, + }; fn discover(file_contents: &str) -> Result> { let tokens = Tokenizer::new().tokenize(file_contents)?; @@ -202,7 +213,8 @@ mod tests { "whenTheAssetMissesTheERC_20ReturnValue".to_owned() ), ( - "when the asset does not miss the ERC_20 return value".to_owned(), + "when the asset does not miss the ERC_20 return value" + .to_owned(), "whenTheAssetDoesNotMissTheERC_20ReturnValue".to_owned() ), ]) diff --git a/src/sol/fmt.rs b/src/sol/fmt.rs index 202e8e3..8299055 100644 --- a/src/sol/fmt.rs +++ b/src/sol/fmt.rs @@ -1,14 +1,14 @@ use once_cell::sync::Lazy; use regex::Regex; use solang_parser::pt::{ - Base, ContractDefinition, ContractPart, ErrorDefinition, ErrorParameter, EventDefinition, - EventParameter, Expression, FunctionAttribute, FunctionDefinition, Parameter, SourceUnit, - SourceUnitPart, Statement, StructDefinition, TypeDefinition, VariableAttribute, + Base, ContractDefinition, ContractPart, ErrorDefinition, ErrorParameter, + EventDefinition, EventParameter, Expression, FunctionAttribute, + FunctionDefinition, Parameter, SourceUnit, SourceUnitPart, Statement, + StructDefinition, TypeDefinition, VariableAttribute, }; -use crate::utils::sanitize; - use super::visitor::Visitor; +use crate::utils::sanitize; trait Identified { fn name(&self) -> String; @@ -37,8 +37,8 @@ impl Formatter { } impl Visitor for Formatter { - type Output = String; type Error = (); + type Output = String; fn visit_source_unit( &mut self, @@ -63,8 +63,12 @@ impl Visitor for Formatter { Ok(header) } - SourceUnitPart::ContractDefinition(inner) => self.visit_contract(inner), - SourceUnitPart::FunctionDefinition(inner) => self.visit_function(inner), + SourceUnitPart::ContractDefinition(inner) => { + self.visit_contract(inner) + } + SourceUnitPart::FunctionDefinition(inner) => { + self.visit_function(inner) + } SourceUnitPart::StraySemicolon(_) => Ok(";".to_owned()), part => Ok(format!("{part}")), } @@ -116,7 +120,9 @@ impl Visitor for Formatter { ContractPart::StructDefinition(inner) => Ok(format!("{inner}")), ContractPart::EventDefinition(inner) => Ok(format!("{inner}")), ContractPart::ErrorDefinition(inner) => Ok(format!("{inner}")), - ContractPart::FunctionDefinition(inner) => self.visit_function(inner), + ContractPart::FunctionDefinition(inner) => { + self.visit_function(inner) + } ContractPart::VariableDefinition(inner) => Ok(format!("{inner}")), ContractPart::TypeDefinition(inner) => Ok(format!("{inner}")), ContractPart::Annotation(inner) => Ok(format!("{inner}")), @@ -179,13 +185,12 @@ impl Visitor for Formatter { Ok(result) } - fn visit_statement(&mut self, statement: &mut Statement) -> Result { + fn visit_statement( + &mut self, + statement: &mut Statement, + ) -> Result { match statement { - Statement::Block { - unchecked, - statements, - .. - } => { + Statement::Block { unchecked, statements, .. } => { let mut result = String::new(); if *unchecked { @@ -206,12 +211,17 @@ impl Visitor for Formatter { Ok(result) } - Statement::Expression(_, ref mut expression) => self.visit_expr(expression), + Statement::Expression(_, ref mut expression) => { + self.visit_expr(expression) + } statement => Ok(format!("{statement}")), } } - fn visit_expr(&mut self, expression: &mut Expression) -> Result { + fn visit_expr( + &mut self, + expression: &mut Expression, + ) -> Result { match expression { Expression::Variable(identifier) => { // We need to special case `_`. See @@ -241,11 +251,17 @@ impl Visitor for Formatter { Ok(format!("{attribute}")) } - fn visit_base(&mut self, base: &mut Base) -> Result { + fn visit_base( + &mut self, + base: &mut Base, + ) -> Result { Ok(format!("{base}")) } - fn visit_parameter(&mut self, parameter: &mut Parameter) -> Result { + fn visit_parameter( + &mut self, + parameter: &mut Parameter, + ) -> Result { Ok(format!("{parameter}")) } @@ -256,7 +272,10 @@ impl Visitor for Formatter { Ok(format!("{structure}")) } - fn visit_event(&mut self, event: &mut EventDefinition) -> Result { + fn visit_event( + &mut self, + event: &mut EventDefinition, + ) -> Result { Ok(format!("{event}")) } @@ -267,7 +286,10 @@ impl Visitor for Formatter { Ok(format!("{param}")) } - fn visit_error(&mut self, error: &mut ErrorDefinition) -> Result { + fn visit_error( + &mut self, + error: &mut ErrorDefinition, + ) -> Result { Ok(format!("{error}")) } @@ -286,13 +308,15 @@ impl Visitor for Formatter { } } -/// Converts special `__bulloak_comment__` variables to regular solidity comments. +/// Converts special `__bulloak_comment__` variables to regular solidity +/// comments. /// -/// Specifically, it looks for patterns matching `string __bulloak_comment__ = "";` -/// and converts them into `// ` format. +/// Specifically, it looks for patterns matching `string __bulloak_comment__ = +/// "";` and converts them into `// ` format. fn cleanup_comments(source: &str) -> String { - static RE_BULLOAK_COMMENT: Lazy = - Lazy::new(|| Regex::new(r#"string __bulloak_comment__ = "(.*)";"#).unwrap()); + static RE_BULLOAK_COMMENT: Lazy = Lazy::new(|| { + Regex::new(r#"string __bulloak_comment__ = "(.*)";"#).unwrap() + }); RE_BULLOAK_COMMENT.replace_all(source, "// $1").to_string() } diff --git a/src/sol/mod.rs b/src/sol/mod.rs index 8a89043..16072e2 100644 --- a/src/sol/mod.rs +++ b/src/sol/mod.rs @@ -1,8 +1,9 @@ -//! This module implements functionality related to operating on a parse tree (PT) from `solang_parser`. +//! This module implements functionality related to operating on a parse tree +//! (PT) from `solang_parser`. use solang_parser::pt::{ - ContractDefinition, ContractPart, FunctionDefinition, FunctionTy, Identifier, SourceUnit, - SourceUnitPart, + ContractDefinition, ContractPart, FunctionDefinition, FunctionTy, + Identifier, SourceUnit, SourceUnitPart, }; use crate::hir::hir; @@ -13,8 +14,11 @@ mod visitor; pub(crate) use fmt::Formatter; pub(crate) use translator::Translator; -/// Searches for and returns the first `ContractDefinition` found in a given `SourceUnit`. -pub(crate) fn find_contract(pt: &SourceUnit) -> Option> { +/// Searches for and returns the first `ContractDefinition` found in a given +/// `SourceUnit`. +pub(crate) fn find_contract( + pt: &SourceUnit, +) -> Option> { pt.0.iter().find_map(|part| match part { SourceUnitPart::ContractDefinition(contract) => Some(contract.clone()), _ => None, @@ -27,31 +31,27 @@ pub(crate) fn find_matching_fn<'a>( contract_sol: &'a ContractDefinition, fn_hir: &'a hir::FunctionDefinition, ) -> Option<(usize, &'a FunctionDefinition)> { - contract_sol - .parts - .iter() - .enumerate() - .find_map(|(idx, part)| { - if let ContractPart::FunctionDefinition(fn_sol) = part { - if fns_match(fn_hir, fn_sol) { - return Some((idx, &**fn_sol)); - } - }; - - None - }) + contract_sol.parts.iter().enumerate().find_map(|(idx, part)| { + if let ContractPart::FunctionDefinition(fn_sol) = part { + if fns_match(fn_hir, fn_sol) { + return Some((idx, &**fn_sol)); + } + }; + + None + }) } /// Check whether a Solidity function matches its bulloak counterpart. /// /// Two functions match if they have the same name and their types match. -fn fns_match(fn_hir: &hir::FunctionDefinition, fn_sol: &FunctionDefinition) -> bool { - fn_sol - .name - .clone() - .is_some_and(|Identifier { ref name, .. }| { - name == &fn_hir.identifier && fn_types_match(&fn_hir.ty, fn_sol.ty) - }) +fn fns_match( + fn_hir: &hir::FunctionDefinition, + fn_sol: &FunctionDefinition, +) -> bool { + fn_sol.name.clone().is_some_and(|Identifier { ref name, .. }| { + name == &fn_hir.identifier && fn_types_match(&fn_hir.ty, fn_sol.ty) + }) } /// Checks that the function types between a HIR function diff --git a/src/sol/translator.rs b/src/sol/translator.rs index 8d3a7b5..8e3cd77 100644 --- a/src/sol/translator.rs +++ b/src/sol/translator.rs @@ -1,31 +1,34 @@ -//! This module implements a translator between a Bulloak tree's High-Level Intermediate -//! Representation (HIR) and a `solang_parser` parse tree (PT). The primary purpose of -//! this module is to facilitate the conversion of a custom HIR into a format that is -//! compatible with the `solang_parser`, which is used for further processing or -//! compilation in the Solidity language context. +//! This module implements a translator between a Bulloak tree's High-Level +//! Intermediate Representation (HIR) and a `solang_parser` parse tree (PT). The +//! primary purpose of this module is to facilitate the conversion of a custom +//! HIR into a format that is compatible with the `solang_parser`, which is used +//! for further processing or compilation in the Solidity language context. //! -//! The translator operates by traversing the HIR in a depth-first order and systematically -//! converting each node into its corresponding PT representation. The translation covers -//! various aspects of the HIR, such as root nodes, contract definitions, function definitions, -//! and comments, each requiring specific handling to maintain the semantics and structure +//! The translator operates by traversing the HIR in a depth-first order and +//! systematically converting each node into its corresponding PT +//! representation. The translation covers various aspects of the HIR, such as +//! root nodes, contract definitions, function definitions, and comments, each +//! requiring specific handling to maintain the semantics and structure //! in the resulting PT. //! -//! The module is structured with a main `Translator` struct that serves as the interface for -//! initiating the translation process. Internally, a `TranslatorI` struct implements the -//! detailed translation logic. +//! The module is structured with a main `Translator` struct that serves as the +//! interface for initiating the translation process. Internally, a +//! `TranslatorI` struct implements the detailed translation logic. use std::cell::Cell; use solang_parser::pt::{ - Base, ContractDefinition, ContractPart, ContractTy, Expression, FunctionAttribute, - FunctionDefinition, FunctionTy, Identifier, IdentifierPath, Import, ImportPath, Loc, - SourceUnit, SourceUnitPart, Statement, StringLiteral, Type, VariableDeclaration, Visibility, + Base, ContractDefinition, ContractPart, ContractTy, Expression, + FunctionAttribute, FunctionDefinition, FunctionTy, Identifier, + IdentifierPath, Import, ImportPath, Loc, SourceUnit, SourceUnitPart, + Statement, StringLiteral, Type, VariableDeclaration, Visibility, }; -use crate::config::Config; -use crate::hir::visitor::Visitor; -use crate::hir::{self, Hir}; -use crate::utils::sanitize; +use crate::{ + config::Config, + hir::{self, visitor::Visitor, Hir}, + utils::sanitize, +}; /// The implementation of a translator between a bulloak tree HIR and a /// `solang_parser` parse tree -- HIR -> PT. @@ -67,8 +70,8 @@ impl Translator { /// The internal implementation of the Translator. struct TranslatorI { - /// Current byte offset the translator is emitting. Helps in computing locations - /// for the parse tree nodes. + /// Current byte offset the translator is emitting. Helps in computing + /// locations for the parse tree nodes. offset: Cell, /// The translator state. translator: Translator, @@ -77,10 +80,7 @@ struct TranslatorI { impl TranslatorI { /// Creates a new internal translator. fn new(translator: Translator) -> Self { - Self { - offset: Cell::new(0), - translator, - } + Self { offset: Cell::new(0), translator } } /// Concrete implementation of the translation from AST to HIR. @@ -120,10 +120,7 @@ impl TranslatorI { /// appropriate `Identifier` instance. fn translate_function_id(&self, identifier: &str) -> Identifier { let function_name_loc = self.bump(identifier); - Identifier { - loc: function_name_loc, - name: identifier.to_owned(), - } + Identifier { loc: function_name_loc, name: identifier.to_owned() } } /// Bumps `self.offset` given a modifier and returns the appropriate @@ -146,33 +143,42 @@ impl TranslatorI { FunctionAttribute::BaseOrModifier(modifier_loc, modifier) } - /// Generates a list of attributes for a function based on its type in the High-Level - /// Intermediate Representation (HIR). This function processes the function definition - /// and constructs a corresponding set of `FunctionAttribute` items, which represent - /// various attributes of a function in the parse tree (PT), such as visibility and modifiers. + /// Generates a list of attributes for a function based on its type in the + /// High-Level Intermediate Representation (HIR). This function + /// processes the function definition and constructs a corresponding set + /// of `FunctionAttribute` items, which represent various attributes of + /// a function in the parse tree (PT), such as visibility and modifiers. /// - /// In the case of a modifier function, an empty vector is returned as modifiers generally - /// do not have additional attributes in this context. For a regular function, the function - /// generates the visibility attribute (defaulted to 'external') and includes any modifiers - /// that are part of the function definition. + /// In the case of a modifier function, an empty vector is returned as + /// modifiers generally do not have additional attributes in this + /// context. For a regular function, the function generates the + /// visibility attribute (defaulted to 'external') and includes any + /// modifiers that are part of the function definition. /// /// # Arguments - /// * `function` - A reference to the `FunctionDefinition` node in the HIR. This node contains - /// details about the function, including its type and any modifiers associated with it. + /// * `function` - A reference to the `FunctionDefinition` node in the HIR. + /// This node contains details about the function, including its type and + /// any modifiers associated with it. /// /// # Returns - /// A vector of `FunctionAttribute` objects representing the attributes of the function. This - /// can include visibility attributes and any modifiers that apply to the function. - fn gen_function_attr(&self, function: &hir::FunctionDefinition) -> Vec { + /// A vector of `FunctionAttribute` objects representing the attributes of + /// the function. This can include visibility attributes and any + /// modifiers that apply to the function. + fn gen_function_attr( + &self, + function: &hir::FunctionDefinition, + ) -> Vec { match function.ty { hir::FunctionTy::Modifier => vec![], hir::FunctionTy::Function => { - let mut attrs = vec![FunctionAttribute::Visibility(Visibility::External(Some( - self.bump("external"), - )))]; + let mut attrs = vec![FunctionAttribute::Visibility( + Visibility::External(Some(self.bump("external"))), + )]; self.bump(" "); if let Some(ref modifiers) = function.modifiers { - attrs.extend(modifiers.iter().map(|m| self.translate_modifier(m))); + attrs.extend( + modifiers.iter().map(|m| self.translate_modifier(m)), + ); }; attrs @@ -180,22 +186,25 @@ impl TranslatorI { } } - /// Generates the statements of a modifier function. In the context of this translation, a modifier's - /// body is represented by a special variable definition. This function creates and returns - /// a vector of statements that constitute this body. + /// Generates the statements of a modifier function. In the context of this + /// translation, a modifier's body is represented by a special variable + /// definition. This function creates and returns a vector of statements + /// that constitute this body. /// - /// The body consists of a single `VariableDefinition` statement with a special identifier `_`, - /// which is a common convention in Solidity for representing a placeholder in modifier bodies. + /// The body consists of a single `VariableDefinition` statement with a + /// special identifier `_`, which is a common convention in Solidity for + /// representing a placeholder in modifier bodies. /// /// # Returns - /// A `Vec` representing the body of the modifier. This vector contains a single - /// `VariableDefinition` statement for the `_` placeholder. + /// A `Vec` representing the body of the modifier. This vector + /// contains a single `VariableDefinition` statement for the `_` + /// placeholder. // TODO: After - // makes it to production, we should update this function and mirror the structure of - // solang's pt. + // makes it to production, we should update this function and mirror the + // structure of solang's pt. // - // It is currently a `VariableDefinition` because the formatter does not append - // a `;` after the variable which causes `forge-fmt` to fail. + // It is currently a `VariableDefinition` because the formatter does not + // append a `;` after the variable which causes `forge-fmt` to fail. fn gen_modifier_statements(&self) -> Vec { let mut stmts = Vec::with_capacity(1); let variable_loc = self.bump("_"); @@ -218,25 +227,31 @@ impl TranslatorI { stmts } - /// Generates the statements of a function by processing its child nodes. This function iterates - /// through each child node in the provided vector, translating comments into statements - /// and adding them to the function body. + /// Generates the statements of a function by processing its child nodes. + /// This function iterates through each child node in the provided + /// vector, translating comments into statements and adding them to the + /// function body. /// - /// A newline character is added after processing all children for proper formatting in the - /// output. This function is called in the context of translating a function's HIR representation - /// to its PT (parse tree) counterpart. + /// A newline character is added after processing all children for proper + /// formatting in the output. This function is called in the context of + /// translating a function's HIR representation to its PT (parse tree) + /// counterpart. /// /// # Arguments - /// * `children` - A reference to a vector of `Hir` nodes, representing the child nodes - /// of a function in the HIR. + /// * `children` - A reference to a vector of `Hir` nodes, representing the + /// child nodes of a function in the HIR. /// /// # Returns - /// A `Result` containing a `Vec` representing the translated function body. If - /// the translation of any child node fails, an error is returned. + /// A `Result` containing a `Vec` representing the translated + /// function body. If the translation of any child node fails, an error + /// is returned. /// /// # Errors /// Returns an error if any of the child nodes' translation fails. - fn gen_function_statements(&mut self, children: &Vec) -> Result, ()> { + fn gen_function_statements( + &mut self, + children: &Vec, + ) -> Result, ()> { let mut stmts = Vec::with_capacity(children.len()); for child in children { if let Hir::Statement(statement) = child { @@ -256,29 +271,32 @@ impl TranslatorI { Ok(stmts) } - /// Generates the body of a function or modifier as a sequence of statements. This function - /// differentiates between function and modifier types and generates the respective bodies - /// accordingly. + /// Generates the body of a function or modifier as a sequence of + /// statements. This function differentiates between function and + /// modifier types and generates the respective bodies accordingly. /// - /// For a function, the body is generated by processing its child nodes, which may include - /// comments and other elements specific to the function's logic. For a modifier, a standard - /// placeholder statement is created, following Solidity's convention for modifiers. + /// For a function, the body is generated by processing its child nodes, + /// which may include comments and other elements specific to the + /// function's logic. For a modifier, a standard placeholder statement + /// is created, following Solidity's convention for modifiers. /// /// The function leverages `gen_modifier_statements` for modifiers and /// `gen_function_statements` for functions. /// /// # Arguments - /// * `function` - A reference to the `FunctionDefinition` node in the High-Level Intermediate - /// Representation (HIR). This node contains information about the function, including its - /// type (function or modifier) and child nodes. + /// * `function` - A reference to the `FunctionDefinition` node in the + /// High-Level Intermediate Representation (HIR). This node contains + /// information about the function, including its type (function or + /// modifier) and child nodes. /// /// # Returns - /// A `Result` containing a vector of `Statement` objects that represent the body of the - /// function or modifier. In case of an error during statement generation, an error is returned. + /// A `Result` containing a vector of `Statement` objects that represent the + /// body of the function or modifier. In case of an error during + /// statement generation, an error is returned. /// /// # Errors - /// Returns an error if the generation of function statements encounters an issue, such as - /// failing to process a child node. + /// Returns an error if the generation of function statements encounters an + /// issue, such as failing to process a child node. fn gen_function_body( &mut self, function: &hir::FunctionDefinition, @@ -299,36 +317,42 @@ impl TranslatorI { } impl Visitor for TranslatorI { - type RootOutput = SourceUnit; + type CommentOutput = Statement; type ContractDefinitionOutput = SourceUnitPart; + type Error = (); type FunctionDefinitionOutput = ContractPart; - type CommentOutput = Statement; + type RootOutput = SourceUnit; type StatementOutput = Statement; - type Error = (); - /// Visits the root node of a High-Level Intermediate Representation (HIR) and translates - /// it into a `SourceUnit` as part of a `solang_parser` parse tree (PT). This function - /// serves as the entry point for the translation process, converting the top-level structure - /// of the HIR into a corresponding PT structure. + /// Visits the root node of a High-Level Intermediate Representation (HIR) + /// and translates it into a `SourceUnit` as part of a `solang_parser` + /// parse tree (PT). This function serves as the entry point for the + /// translation process, converting the top-level structure of the HIR + /// into a corresponding PT structure. /// - /// The translation involves creating a `SourceUnit`, starting with a pragma directive - /// based on the translator's Solidity version as well as optional file imports (e.g. forge-std) - /// if required. It then iterates over each child node within the root. - /// Each contract definition is translated and incorporated into the `SourceUnit`. + /// The translation involves creating a `SourceUnit`, starting with a pragma + /// directive based on the translator's Solidity version as well as + /// optional file imports (e.g. forge-std) if required. It then iterates + /// over each child node within the root. Each contract definition is + /// translated and incorporated into the `SourceUnit`. /// /// # Arguments - /// * `root` - A reference to the root of the HIR structure, representing the highest level - /// of the program's structure. + /// * `root` - A reference to the root of the HIR structure, representing + /// the highest level of the program's structure. /// /// # Returns - /// A `Result` containing the `SourceUnit` if the translation is successful, or an `Error` - /// otherwise. The `SourceUnit` is a key component of the PT, representing the entire - /// translated program. + /// A `Result` containing the `SourceUnit` if the translation is successful, + /// or an `Error` otherwise. The `SourceUnit` is a key component of the + /// PT, representing the entire translated program. /// /// # Errors - /// This function may return an error if any part of the translation process fails, such - /// as issues in converting specific HIR nodes to their PT equivalents. - fn visit_root(&mut self, root: &hir::Root) -> Result { + /// This function may return an error if any part of the translation process + /// fails, such as issues in converting specific HIR nodes to their PT + /// equivalents. + fn visit_root( + &mut self, + root: &hir::Root, + ) -> Result { let mut source_unit = Vec::with_capacity(2); let pragma_start = self.offset.get(); @@ -353,14 +377,16 @@ impl Visitor for TranslatorI { // Add the forge-std's Test import, if needed. if self.translator.with_forge_std { - // Getting the relevant offsets for `import {Test} from "forge-std/Test.sol"`. + // Getting the relevant offsets for `import {Test} from + // "forge-std/Test.sol"`. let loc_import_start = self.offset.get(); self.bump("import { "); let loc_identifier = self.bump("Test"); self.bump(" } from \""); let loc_path = self.bump("forge-std/Test.sol"); - // The import directive `Rename` corresponds to `import {x} from y.sol`. + // The import directive `Rename` corresponds to `import {x} from + // y.sol`. source_unit.push(SourceUnitPart::ImportDirective(Import::Rename( ImportPath::Filename(StringLiteral { loc: loc_path, @@ -389,23 +415,27 @@ impl Visitor for TranslatorI { Ok(SourceUnit(source_unit)) } - /// Visits a `ContractDefinition` node within the High-Level Intermediate Representation (HIR) - /// and translates it into a `SourceUnitPart` for inclusion in the `solang_parser` parse tree (PT). - /// This function handles the translation of contract definitions, turning them into a format - /// suitable for the PT, which includes converting each child node of the contract. + /// Visits a `ContractDefinition` node within the High-Level Intermediate + /// Representation (HIR) and translates it into a `SourceUnitPart` for + /// inclusion in the `solang_parser` parse tree (PT). This function + /// handles the translation of contract definitions, turning them into a + /// format suitable for the PT, which includes converting each child + /// node of the contract. /// /// # Arguments - /// * `contract` - A reference to the `ContractDefinition` node in the HIR, representing a - /// single contract within the HIR structure. + /// * `contract` - A reference to the `ContractDefinition` node in the HIR, + /// representing a single contract within the HIR structure. /// /// # Returns - /// A `Result` containing the `SourceUnitPart` representing the translated contract if the - /// translation is successful, or an `Error` otherwise. The `SourceUnitPart` encapsulates the - /// contract's PT representation, including its components like functions. + /// A `Result` containing the `SourceUnitPart` representing the translated + /// contract if the translation is successful, or an `Error` otherwise. + /// The `SourceUnitPart` encapsulates the contract's PT representation, + /// including its components like functions. /// /// # Errors - /// This function may return an error if the translation of any component within the contract - /// encounters issues, such as failing to translate a function definition. + /// This function may return an error if the translation of any component + /// within the contract encounters issues, such as failing to translate + /// a function definition. fn visit_contract( &mut self, contract: &hir::ContractDefinition, @@ -463,30 +493,37 @@ impl Visitor for TranslatorI { Ok(SourceUnitPart::ContractDefinition(Box::new(contract_def))) } - /// Visits a `FunctionDefinition` node in the High-Level Intermediate Representation (HIR) - /// and translates it into a `ContractPart` for inclusion in the `solang_parser` parse tree (PT). - /// This function handles the translation of function definitions, converting them into a format - /// suitable for the PT. + /// Visits a `FunctionDefinition` node in the High-Level Intermediate + /// Representation (HIR) and translates it into a `ContractPart` for + /// inclusion in the `solang_parser` parse tree (PT). This function + /// handles the translation of function definitions, converting them into a + /// format suitable for the PT. /// /// The translation process involves several steps: - /// 1. Determining the function type and translating it to the corresponding PT representation. - /// 2. Translating the function identifier and storing its location information. + /// 1. Determining the function type and translating it to the corresponding + /// PT representation. + /// 2. Translating the function identifier and storing its location + /// information. /// 3. Generating function attributes based on the HIR function definition. - /// 4. Translating the function body, including statements and comments, into PT statements. - /// 5. Constructing the final `FunctionDefinition` object with the translated components. + /// 4. Translating the function body, including statements and comments, + /// into PT statements. + /// 5. Constructing the final `FunctionDefinition` object with the + /// translated components. /// /// # Arguments - /// * `function` - A reference to the `FunctionDefinition` node in the HIR, representing a - /// single function within the HIR structure. + /// * `function` - A reference to the `FunctionDefinition` node in the HIR, + /// representing a single function within the HIR structure. /// /// # Returns - /// A `Result` containing the `ContractPart::FunctionDefinition` representing the translated - /// function if the translation is successful, or an `Error` otherwise. The `ContractPart` - /// encapsulates the function's PT representation. + /// A `Result` containing the `ContractPart::FunctionDefinition` + /// representing the translated function if the translation is + /// successful, or an `Error` otherwise. The `ContractPart` encapsulates + /// the function's PT representation. /// /// # Errors - /// This function may return an error if the translation of any component within the function - /// encounters issues, such as failing to translate the function body. + /// This function may return an error if the translation of any component + /// within the function encounters issues, such as failing to translate + /// the function body. fn visit_function( &mut self, function: &hir::FunctionDefinition, @@ -494,7 +531,8 @@ impl Visitor for TranslatorI { let start_offset = self.offset.get(); let function_ty = self.translate_function_ty(&function.ty); self.bump(" "); - let function_identifier = self.translate_function_id(&function.identifier); + let function_identifier = + self.translate_function_id(&function.identifier); let function_id_loc = function_identifier.loc; let function_name = Some(function_identifier); self.bump("() "); // `() `. @@ -524,33 +562,38 @@ impl Visitor for TranslatorI { Ok(ContractPart::FunctionDefinition(Box::new(func_def))) } - /// Visits a comment node in the High-Level Intermediate Representation (HIR) and - /// translates it into a `Statement` for inclusion in the parse tree (PT). This function - /// handles comments in a unique way by representing them as disguised `VariableDefinition` - /// statements in the PT. + /// Visits a comment node in the High-Level Intermediate Representation + /// (HIR) and translates it into a `Statement` for inclusion in the + /// parse tree (PT). This function handles comments in a unique way by + /// representing them as disguised `VariableDefinition` statements in + /// the PT. /// - /// The approach involves creating a `VariableDefinition` with a special name and assigning - /// the comment text to it as a string literal. This method allows for preserving comments - /// in the PT while maintaining compatibility with the structure of the PT. During a later - /// phase, we convert the variables back to comments. + /// The approach involves creating a `VariableDefinition` with a special + /// name and assigning the comment text to it as a string literal. This + /// method allows for preserving comments in the PT while maintaining + /// compatibility with the structure of the PT. During a later phase, we + /// convert the variables back to comments. /// /// # Arguments - /// * `comment` - A reference to the `Comment` node in the HIR, containing the comment text. + /// * `comment` - A reference to the `Comment` node in the HIR, containing + /// the comment text. /// /// # Returns - /// A `Result` containing the `Statement` representing the comment. The `Statement` is a - /// `VariableDefinition` with the comment's content. In case of an error, a corresponding - /// error type is returned. + /// A `Result` containing the `Statement` representing the comment. The + /// `Statement` is a `VariableDefinition` with the comment's content. In + /// case of an error, a corresponding error type is returned. /// /// # Errors - /// This function currently does not generate errors but returns a `Result` for consistency - /// with other visit methods and to allow for error handling in future implementations. + /// This function currently does not generate errors but returns a `Result` + /// for consistency with other visit methods and to allow for error + /// handling in future implementations. fn visit_comment( &mut self, comment: &hir::Comment, ) -> Result { // After exploring several paths forward, the least convoluted way to - // handle comments is to disguise them as `VariableDefinition` statements. + // handle comments is to disguise them as `VariableDefinition` + // statements. // // The idea is to remove the extra parts with a search and replace when // emitting the parse tree and leave the comment's lexeme as is. @@ -560,11 +603,10 @@ impl Visitor for TranslatorI { self.bump(" "); // ` ` after type. let variable_name = "__bulloak_comment__"; let variable_loc = self.bump(variable_name); - let declaration_loc = Loc::File(0, declaration_start, self.offset.get()); - let name = Identifier { - loc: variable_loc, - name: variable_name.to_owned(), - }; + let declaration_loc = + Loc::File(0, declaration_start, self.offset.get()); + let name = + Identifier { loc: variable_loc, name: variable_name.to_owned() }; let variable = VariableDeclaration { loc: declaration_loc, ty, @@ -573,11 +615,12 @@ impl Visitor for TranslatorI { }; self.bump(" = "); let comment_loc = self.bump(&format!(r#""{}""#, &comment.lexeme)); - let string_literal = Some(Expression::StringLiteral(vec![StringLiteral { - loc: comment_loc, - unicode: false, - string: comment.lexeme.clone(), - }])); + let string_literal = + Some(Expression::StringLiteral(vec![StringLiteral { + loc: comment_loc, + unicode: false, + string: comment.lexeme.clone(), + }])); self.bump(";"); // `;` after string literal. let definition = Statement::VariableDefinition( Loc::File(0, definition_start, self.offset.get()), @@ -606,10 +649,12 @@ impl Visitor for TranslatorI { let vm_interface = Expression::MemberAccess( Loc::File(0, start_offset, loc_skip.end()), - Box::new(Expression::Variable(solang_parser::pt::Identifier { - loc: loc_vm, - name: "vm".to_owned(), - })), + Box::new(Expression::Variable( + solang_parser::pt::Identifier { + loc: loc_vm, + name: "vm".to_owned(), + }, + )), solang_parser::pt::Identifier { loc: loc_skip, name: "skip".to_owned(), diff --git a/src/sol/visitor.rs b/src/sol/visitor.rs index ce7c3d1..5f24260 100644 --- a/src/sol/visitor.rs +++ b/src/sol/visitor.rs @@ -1,13 +1,15 @@ -//! Visitor helpers to traverse the [solang Solidity Parse Tree](solang_parser::pt). +//! Visitor helpers to traverse the [solang Solidity Parse +//! Tree](solang_parser::pt). //! //! This is based on //! . #![allow(unused)] use solang_parser::pt::{ - Base, ContractDefinition, ContractPart, ErrorDefinition, ErrorParameter, EventDefinition, - EventParameter, Expression, FunctionAttribute, FunctionDefinition, Parameter, SourceUnit, - SourceUnitPart, Statement, StructDefinition, TypeDefinition, VariableAttribute, + Base, ContractDefinition, ContractPart, ErrorDefinition, ErrorParameter, + EventDefinition, EventParameter, Expression, FunctionAttribute, + FunctionDefinition, Parameter, SourceUnit, SourceUnitPart, Statement, + StructDefinition, TypeDefinition, VariableAttribute, }; /// A trait that is invoked while traversing the Solidity Parse Tree. @@ -33,8 +35,10 @@ pub(crate) trait Visitor { contract: &mut ContractDefinition, ) -> Result; - fn visit_contract_part(&mut self, part: &mut ContractPart) - -> Result; + fn visit_contract_part( + &mut self, + part: &mut ContractPart, + ) -> Result; fn visit_function( &mut self, @@ -51,27 +55,45 @@ pub(crate) trait Visitor { attribute: &mut VariableAttribute, ) -> Result; - fn visit_base(&mut self, base: &mut Base) -> Result; + fn visit_base( + &mut self, + base: &mut Base, + ) -> Result; - fn visit_parameter(&mut self, parameter: &mut Parameter) -> Result; + fn visit_parameter( + &mut self, + parameter: &mut Parameter, + ) -> Result; - fn visit_statement(&mut self, statement: &mut Statement) -> Result; + fn visit_statement( + &mut self, + statement: &mut Statement, + ) -> Result; - fn visit_expr(&mut self, expression: &mut Expression) -> Result; + fn visit_expr( + &mut self, + expression: &mut Expression, + ) -> Result; fn visit_struct( &mut self, structure: &mut StructDefinition, ) -> Result; - fn visit_event(&mut self, event: &mut EventDefinition) -> Result; + fn visit_event( + &mut self, + event: &mut EventDefinition, + ) -> Result; fn visit_event_parameter( &mut self, param: &mut EventParameter, ) -> Result; - fn visit_error(&mut self, error: &mut ErrorDefinition) -> Result; + fn visit_error( + &mut self, + error: &mut ErrorDefinition, + ) -> Result; fn visit_error_parameter( &mut self, diff --git a/src/span.rs b/src/span.rs index 109a33d..656c736 100644 --- a/src/span.rs +++ b/src/span.rs @@ -50,11 +50,7 @@ pub struct Position { impl Default for Position { fn default() -> Self { - Self { - offset: Default::default(), - line: 1, - column: 1, - } + Self { offset: Default::default(), line: 1, column: 1 } } } @@ -114,10 +110,6 @@ impl Position { /// /// `column` is the approximate column number, starting at `1`. pub const fn new(offset: usize, line: usize, column: usize) -> Self { - Self { - offset, - line, - column, - } + Self { offset, line, column } } } diff --git a/src/syntax/parser.rs b/src/syntax/parser.rs index 60515ed..5feebfc 100644 --- a/src/syntax/parser.rs +++ b/src/syntax/parser.rs @@ -1,12 +1,15 @@ //! A parser implementation for a stream of tokens representing a bulloak tree. -use std::fmt; -use std::{borrow::Borrow, cell::Cell, result}; +use std::{borrow::Borrow, cell::Cell, fmt, result}; -use super::ast::{Action, Ast, Condition, Description, Root}; -use super::tokenizer::{Token, TokenKind}; -use crate::span::Span; -use crate::utils::{repeat_str, sanitize}; +use super::{ + ast::{Action, Ast, Condition, Description, Root}, + tokenizer::{Token, TokenKind}, +}; +use crate::{ + span::Span, + utils::{repeat_str, sanitize}, +}; type Result = result::Result; @@ -90,9 +93,10 @@ impl fmt::Display for Error { impl fmt::Display for ErrorKind { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { use self::ErrorKind::{ - CornerNotLastChild, DescriptionTokenUnexpected, EofUnexpected, GivenUnexpected, - ItUnexpected, TeeLastChild, TitleMissing, TokenUnexpected, TreeEmpty, TreeRootless, - WhenUnexpected, WordUnexpected, + CornerNotLastChild, DescriptionTokenUnexpected, EofUnexpected, + GivenUnexpected, ItUnexpected, TeeLastChild, TitleMissing, + TokenUnexpected, TreeEmpty, TreeRootless, WhenUnexpected, + WordUnexpected, }; match self { TokenUnexpected(lexeme) => write!(f, "unexpected token '{lexeme}'"), @@ -106,8 +110,12 @@ impl fmt::Display for ErrorKind { EofUnexpected => write!(f, "unexpected end of file"), TreeEmpty => write!(f, "found an empty tree"), TreeRootless => write!(f, "missing a root"), - TitleMissing => write!(f, "found a condition/action without a title"), - CornerNotLastChild => write!(f, "a `Corner` must be the last child"), + TitleMissing => { + write!(f, "found a condition/action without a title") + } + CornerNotLastChild => { + write!(f, "a `Corner` must be the last child") + } TeeLastChild => write!(f, "a `Tee` must not be the last child"), } } @@ -127,9 +135,7 @@ impl Parser { /// Create a new parser. #[must_use] pub const fn new() -> Self { - Self { - current: Cell::new(0), - } + Self { current: Cell::new(0) } } /// Parse the given tokens into an abstract syntax tree (AST). @@ -159,11 +165,7 @@ struct ParserI<'t, P> { impl<'t, P: Borrow> ParserI<'t, P> { /// Create a new parser given the parser state, input text, and tokens. const fn new(parser: P, text: &'t str, tokens: &'t [Token]) -> Self { - Self { - text, - tokens, - parser, - } + Self { text, tokens, parser } } /// Return a reference to the state of the parser. @@ -173,11 +175,7 @@ impl<'t, P: Borrow> ParserI<'t, P> { /// Create a new error with the given span and error type. fn error(&self, span: Span, kind: ErrorKind) -> Error { - Error { - kind, - text: self.text.to_owned(), - span, - } + Error { kind, text: self.text.to_owned(), span } } /// Returns true if the next call to `current` would @@ -261,18 +259,25 @@ impl<'t, P: Borrow> ParserI<'t, P> { // `Tee` or the last `Corner`. let mut children = vec![]; while let Some(current_token) = self.current() { - let child = match current_token.kind { - TokenKind::Corner | TokenKind::Tee => self.parse_branch(current_token)?, - TokenKind::Word => Err(self.error( - current_token.span, - ErrorKind::WordUnexpected(current_token.lexeme.clone()), - ))?, - TokenKind::When => Err(self.error(current_token.span, ErrorKind::WhenUnexpected))?, - TokenKind::Given => { - Err(self.error(current_token.span, ErrorKind::GivenUnexpected))? - } - TokenKind::It => Err(self.error(current_token.span, ErrorKind::ItUnexpected))?, - }; + let child = + match current_token.kind { + TokenKind::Corner | TokenKind::Tee => { + self.parse_branch(current_token)? + } + TokenKind::Word => Err(self.error( + current_token.span, + ErrorKind::WordUnexpected(current_token.lexeme.clone()), + ))?, + TokenKind::When => Err(self + .error(current_token.span, ErrorKind::WhenUnexpected))?, + TokenKind::Given => Err(self.error( + current_token.span, + ErrorKind::GivenUnexpected, + ))?, + TokenKind::It => Err( + self.error(current_token.span, ErrorKind::ItUnexpected) + )?, + }; children.push(child); } @@ -306,7 +311,9 @@ impl<'t, P: Borrow> ParserI<'t, P> { ))?; let ast = match first_token.kind { - TokenKind::When | TokenKind::Given => self.parse_condition(token)?, + TokenKind::When | TokenKind::Given => { + self.parse_condition(token)? + } TokenKind::It => self.parse_action(token)?, _ => Err(self.error( first_token.span, @@ -367,7 +374,9 @@ impl<'t, P: Borrow> ParserI<'t, P> { let current_token = self.current().unwrap(); let ast = match next_token.kind { - TokenKind::When | TokenKind::Given => self.parse_condition(current_token)?, + TokenKind::When | TokenKind::Given => { + self.parse_condition(current_token)? + } TokenKind::It => self.parse_action(current_token)?, _ => Err(self.error( next_token.span, @@ -426,7 +435,9 @@ impl<'t, P: Borrow> ParserI<'t, P> { )?, _ => Err(self.error( next_token.span, - ErrorKind::DescriptionTokenUnexpected(next_token.lexeme.clone()), + ErrorKind::DescriptionTokenUnexpected( + next_token.lexeme.clone(), + ), ))?, }; @@ -465,7 +476,11 @@ impl<'t, P: Borrow> ParserI<'t, P> { /// /// Panics if called when the parser is not at a `Tee` or a `Corner` /// token. - fn parse_description(&self, token: &Token, column_delta: usize) -> Result { + fn parse_description( + &self, + token: &Token, + column_delta: usize, + ) -> Result { assert!(matches!(token.kind, TokenKind::Tee | TokenKind::Corner)); let start_token = self.peek().ok_or(self.error( @@ -494,7 +509,10 @@ impl<'t, P: Borrow> ParserI<'t, P> { // Consume all words. while let Some(token) = self.consume() { match token.kind { - TokenKind::Word | TokenKind::It | TokenKind::When | TokenKind::Given => { + TokenKind::Word + | TokenKind::It + | TokenKind::When + | TokenKind::Given => { string = string + " " + &token.lexeme; } _ => break, @@ -509,11 +527,15 @@ impl<'t, P: Borrow> ParserI<'t, P> { mod tests { use pretty_assertions::assert_eq; - use crate::span::Span; - use crate::syntax::ast::{Action, Ast, Condition, Description, Root}; - use crate::syntax::parser::{self, ErrorKind, Parser}; - use crate::syntax::test_utils::{p, s, TestError}; - use crate::syntax::tokenizer::Tokenizer; + use crate::{ + span::Span, + syntax::{ + ast::{Action, Ast, Condition, Description, Root}, + parser::{self, ErrorKind, Parser}, + test_utils::{p, s, TestError}, + tokenizer::Tokenizer, + }, + }; impl PartialEq for TestError { fn eq(&self, other: &parser::Error) -> bool { diff --git a/src/syntax/semantics.rs b/src/syntax/semantics.rs index 7b3833c..b567d47 100644 --- a/src/syntax/semantics.rs +++ b/src/syntax/semantics.rs @@ -1,12 +1,13 @@ //! Implementation of the semantic analysis of a bulloak tree. -use std::collections::HashMap; -use std::{fmt, result}; +use std::{collections::HashMap, fmt, result}; use super::ast::{self, Ast}; -use crate::span::Span; -use crate::syntax::visitor::Visitor; -use crate::utils::{lower_first_letter, sanitize, to_pascal_case}; +use crate::{ + span::Span, + syntax::visitor::Visitor, + utils::{lower_first_letter, sanitize, to_pascal_case}, +}; type Result = result::Result>; @@ -73,7 +74,9 @@ impl fmt::Display for Error { impl fmt::Display for ErrorKind { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - use self::ErrorKind::{ConditionEmpty, IdentifierDuplicated, NodeUnexpected, TreeEmpty}; + use self::ErrorKind::{ + ConditionEmpty, IdentifierDuplicated, NodeUnexpected, TreeEmpty, + }; match *self { IdentifierDuplicated(ref spans) => { let lines = spans @@ -81,7 +84,10 @@ impl fmt::Display for ErrorKind { .map(|s| s.start.line.to_string()) .collect::>() .join(", "); - write!(f, "found an identifier more than once in lines: {lines}") + write!( + f, + "found an identifier more than once in lines: {lines}" + ) } ConditionEmpty => write!(f, "found a condition with no children"), NodeUnexpected => write!(f, "unexpected child node"), @@ -114,11 +120,7 @@ impl<'t> SemanticAnalyzer<'t> { /// Create a new error given an AST node and error type. fn error(&mut self, span: Span, kind: ErrorKind) { - self.errors.push(Error { - kind, - text: self.text.to_owned(), - span, - }); + self.errors.push(Error { kind, text: self.text.to_owned(), span }); } /// Traverse the given AST and store any errors that occur. @@ -130,7 +132,9 @@ impl<'t> SemanticAnalyzer<'t> { Ast::Root(root) => self.visit_root(root), Ast::Condition(condition) => self.visit_condition(condition), Ast::Action(action) => self.visit_action(action), - Ast::ActionDescription(description) => self.visit_description(description), + Ast::ActionDescription(description) => { + self.visit_description(description) + } } // It is fine to unwrap here since analysis errors will // be stored in `self.errors`. @@ -140,8 +144,9 @@ impl<'t> SemanticAnalyzer<'t> { for spans in self.identifiers.clone().into_values() { if spans.len() > 1 { self.error( - // FIXME: This is a patch until we start storing locations for - // parts of an AST node. In this case, we need the location of + // FIXME: This is a patch until we start storing locations + // for parts of an AST node. In this + // case, we need the location of // the condition's title. spans[0].with_end(spans[0].start), ErrorKind::IdentifierDuplicated(spans), @@ -159,10 +164,13 @@ impl<'t> SemanticAnalyzer<'t> { /// A visitor that performs semantic analysis on an AST. impl Visitor for SemanticAnalyzer<'_> { - type Output = (); type Error = (); + type Output = (); - fn visit_root(&mut self, root: &ast::Root) -> result::Result { + fn visit_root( + &mut self, + root: &ast::Root, + ) -> result::Result { if root.children.is_empty() { self.error(Span::splat(root.span.end), ErrorKind::TreeEmpty); } @@ -173,13 +181,16 @@ impl Visitor for SemanticAnalyzer<'_> { self.visit_condition(condition)?; } Ast::Action(action) => { - // Top-level actions must be checked for duplicates since they will become - // Solidity functions. - let identifier = lower_first_letter(&to_pascal_case(&sanitize(&action.title))); + // Top-level actions must be checked for duplicates since + // they will become Solidity functions. + let identifier = lower_first_letter(&to_pascal_case( + &sanitize(&action.title), + )); match self.identifiers.get_mut(&identifier) { Some(spans) => spans.push(action.span), None => { - self.identifiers.insert(identifier, vec![action.span]); + self.identifiers + .insert(identifier, vec![action.span]); } } self.visit_action(action)?; @@ -201,7 +212,8 @@ impl Visitor for SemanticAnalyzer<'_> { self.error(condition.span, ErrorKind::ConditionEmpty); } - let modifier = lower_first_letter(&to_pascal_case(&sanitize(&condition.title))); + let modifier = + lower_first_letter(&to_pascal_case(&sanitize(&condition.title))); match self.identifiers.get_mut(&modifier) { Some(spans) => spans.push(condition.span), None => { @@ -226,7 +238,10 @@ impl Visitor for SemanticAnalyzer<'_> { Ok(()) } - fn visit_action(&mut self, _action: &ast::Action) -> result::Result { + fn visit_action( + &mut self, + _action: &ast::Action, + ) -> result::Result { // We don't implement any semantic rules here for now. Ok(()) } @@ -243,11 +258,15 @@ impl Visitor for SemanticAnalyzer<'_> { #[cfg(test)] mod tests { - use crate::span::{Position, Span}; - use crate::syntax::ast; - use crate::syntax::parser::Parser; - use crate::syntax::semantics::{self, ErrorKind::*}; - use crate::syntax::tokenizer::Tokenizer; + use crate::{ + span::{Position, Span}, + syntax::{ + ast, + parser::Parser, + semantics::{self, ErrorKind::*}, + tokenizer::Tokenizer, + }, + }; fn analyze(text: &str) -> semantics::Result<()> { let tokens = Tokenizer::new().tokenize(text).unwrap(); @@ -338,7 +357,10 @@ mod tests { vec![semantics::Error { kind: ConditionEmpty, text: "Foo_Test\n└── when something".to_owned(), - span: Span::new(Position::new(9, 2, 1), Position::new(32, 2, 18)), + span: Span::new( + Position::new(9, 2, 1), + Position::new(32, 2, 18) + ), }] ); } diff --git a/src/syntax/tokenizer.rs b/src/syntax/tokenizer.rs index 7964a6a..ee08a48 100644 --- a/src/syntax/tokenizer.rs +++ b/src/syntax/tokenizer.rs @@ -87,18 +87,17 @@ impl Token { fn is_branch(&self) -> bool { match self.kind { TokenKind::Tee | TokenKind::Corner => true, - TokenKind::Word | TokenKind::When | TokenKind::Given | TokenKind::It => false, + TokenKind::Word + | TokenKind::When + | TokenKind::Given + | TokenKind::It => false, } } } impl fmt::Debug for Token { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "Token({:?}, {:?}, {:?})", - self.kind, self.lexeme, self.span - ) + write!(f, "Token({:?}, {:?}, {:?})", self.kind, self.lexeme, self.span) } } @@ -192,11 +191,7 @@ impl<'s, T: Borrow> TokenizerI<'s, T> { /// Create a new error with the given span and error type. fn error(&self, span: Span, kind: ErrorKind) -> Error { - Error { - kind, - text: self.text.to_owned(), - span, - } + Error { kind, text: self.text.to_owned(), span } } /// Return a reference to the text being parsed. @@ -252,9 +247,7 @@ impl<'s, T: Borrow> TokenizerI<'s, T> { if self.is_eof() { return None; } - self.text()[self.offset() + self.char().len_utf8()..] - .chars() - .next() + self.text()[self.offset() + self.char().len_utf8()..].chars().next() } /// Enters identifier mode. @@ -286,11 +279,7 @@ impl<'s, T: Borrow> TokenizerI<'s, T> { if self.is_eof() { return None; } - let Position { - mut offset, - mut line, - mut column, - } = self.pos(); + let Position { mut offset, mut line, mut column } = self.pos(); if self.char() == '\n' { line = line.checked_add(1).unwrap(); @@ -300,11 +289,7 @@ impl<'s, T: Borrow> TokenizerI<'s, T> { } offset += self.char().len_utf8(); - self.tokenizer().pos.set(Position { - offset, - line, - column, - }); + self.tokenizer().pos.set(Position { offset, line, column }); self.text()[self.offset()..].chars().next() } @@ -340,9 +325,11 @@ impl<'s, T: Borrow> TokenizerI<'s, T> { } _ => { let token = self.scan_word()?; - let last_is_branch = tokens.last().is_some_and(Token::is_branch); + let last_is_branch = + tokens.last().is_some_and(Token::is_branch); if last_is_branch - && (token.kind == TokenKind::When || token.kind == TokenKind::Given) + && (token.kind == TokenKind::When + || token.kind == TokenKind::Given) { self.enter_identifier_mode(); }; @@ -378,13 +365,19 @@ impl<'s, T: Borrow> TokenizerI<'s, T> { let span_start = self.pos(); loop { - if self.is_identifier_mode() && !is_valid_identifier_char(self.char()) { - let invalid_identifier_error = - self.error(self.span(), ErrorKind::IdentifierCharInvalid(self.char())); + if self.is_identifier_mode() + && !is_valid_identifier_char(self.char()) + { + let invalid_identifier_error = self.error( + self.span(), + ErrorKind::IdentifierCharInvalid(self.char()), + ); return Err(invalid_identifier_error); }; - if self.peek().is_none() || self.peek().is_some_and(char::is_whitespace) { + if self.peek().is_none() + || self.peek().is_some_and(char::is_whitespace) + { lexeme.push(self.char()); let kind = match lexeme.to_lowercase().as_str() { "when" => TokenKind::When, @@ -418,11 +411,16 @@ fn is_valid_identifier_char(c: char) -> bool { mod tests { use pretty_assertions::assert_eq; - use crate::error::Result; - use crate::span::Span; - use crate::syntax::test_utils::{p, s, TestError}; - use crate::syntax::tokenizer::{ - self, ErrorKind::IdentifierCharInvalid, Token, TokenKind, Tokenizer, + use crate::{ + error::Result, + span::Span, + syntax::{ + test_utils::{p, s, TestError}, + tokenizer::{ + self, ErrorKind::IdentifierCharInvalid, Token, TokenKind, + Tokenizer, + }, + }, }; impl PartialEq for TestError { @@ -442,11 +440,7 @@ mod tests { } fn t(kind: TokenKind, lexeme: &str, span: Span) -> Token { - Token { - kind, - lexeme: lexeme.to_owned(), - span, - } + Token { kind, lexeme: lexeme.to_owned(), span } } fn tokenize(text: &str) -> tokenizer::Result> { @@ -572,7 +566,8 @@ mod tests { let starts_whitespace = String::from(" foo\n"); let ends_whitespace = String::from("foo \n"); - let expected = vec![t(TokenKind::Word, "foo", s(p(0, 1, 1), p(2, 1, 3)))]; + let expected = + vec![t(TokenKind::Word, "foo", s(p(0, 1, 1), p(2, 1, 3)))]; let mut tokenizer = Tokenizer::new(); assert_eq!(tokenizer.tokenize(&simple_name).unwrap(), expected); @@ -586,8 +581,9 @@ mod tests { #[test] fn one_child() { // Test parsing a when. - let file_contents = - String::from("Foo_Test\n└── when something bad happens\n └── it should revert"); + let file_contents = String::from( + "Foo_Test\n└── when something bad happens\n └── it should revert", + ); assert_eq!( tokenize(&file_contents).unwrap(), @@ -707,11 +703,7 @@ mod tests { t(TokenKind::Word, "is", s(p(360, 10, 26), p(361, 10, 27))), t(TokenKind::Word, "not", s(p(363, 10, 29), p(365, 10, 31))), t(TokenKind::Word, "a", s(p(367, 10, 33), p(367, 10, 33))), - t( - TokenKind::Word, - "contract", - s(p(369, 10, 35), p(376, 10, 42)), - ), + t(TokenKind::Word, "contract", s(p(369, 10, 35), p(376, 10, 42))), t(TokenKind::Corner, "└", s(p(389, 11, 10), p(389, 11, 10))), t(TokenKind::It, "it", s(p(399, 11, 14), p(400, 11, 15))), t(TokenKind::Word, "should", s(p(402, 11, 17), p(407, 11, 22))), @@ -722,11 +714,7 @@ mod tests { t(TokenKind::Word, "asset", s(p(441, 12, 20), p(445, 12, 24))), t(TokenKind::Word, "is", s(p(447, 12, 26), p(448, 12, 27))), t(TokenKind::Word, "a", s(p(450, 12, 29), p(450, 12, 29))), - t( - TokenKind::Word, - "contract", - s(p(452, 12, 31), p(459, 12, 38)), - ), + t(TokenKind::Word, "contract", s(p(452, 12, 31), p(459, 12, 38))), t(TokenKind::Tee, "├", s(p(471, 13, 11), p(471, 13, 11))), t(TokenKind::When, "when", s(p(481, 13, 15), p(484, 13, 18))), t(TokenKind::Word, "the", s(p(486, 13, 20), p(488, 13, 22))), @@ -745,18 +733,10 @@ mod tests { t(TokenKind::Tee, "├", s(p(594, 15, 14), p(594, 15, 14))), t(TokenKind::It, "it", s(p(604, 15, 18), p(605, 15, 19))), t(TokenKind::Word, "should", s(p(607, 15, 21), p(612, 15, 26))), - t( - TokenKind::Word, - "perform", - s(p(614, 15, 28), p(620, 15, 34)), - ), + t(TokenKind::Word, "perform", s(p(614, 15, 28), p(620, 15, 34))), t(TokenKind::Word, "the", s(p(622, 15, 36), p(624, 15, 38))), t(TokenKind::Word, "ERC-20", s(p(626, 15, 40), p(631, 15, 45))), - t( - TokenKind::Word, - "transfers", - s(p(633, 15, 47), p(641, 15, 55)), - ), + t(TokenKind::Word, "transfers", s(p(633, 15, 47), p(641, 15, 55))), t(TokenKind::Corner, "└", s(p(658, 16, 14), p(658, 16, 14))), t(TokenKind::It, "it", s(p(668, 16, 18), p(669, 16, 19))), t(TokenKind::Word, "should", s(p(671, 16, 21), p(676, 16, 26))), diff --git a/src/syntax/visitor.rs b/src/syntax/visitor.rs index 6b9a408..a764270 100644 --- a/src/syntax/visitor.rs +++ b/src/syntax/visitor.rs @@ -14,11 +14,20 @@ pub trait Visitor { type Error; /// This method is called on a root node. - fn visit_root(&mut self, root: &ast::Root) -> Result; + fn visit_root( + &mut self, + root: &ast::Root, + ) -> Result; /// This method is called on a condition node. - fn visit_condition(&mut self, condition: &ast::Condition) -> Result; + fn visit_condition( + &mut self, + condition: &ast::Condition, + ) -> Result; /// This method is called on an action node. - fn visit_action(&mut self, action: &ast::Action) -> Result; + fn visit_action( + &mut self, + action: &ast::Action, + ) -> Result; /// This method is called on an action description node. fn visit_description( &mut self, diff --git a/src/utils.rs b/src/utils.rs index ae30021..e510ad3 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,6 +1,7 @@ -use crate::constants::TREES_SEPARATOR; use unicode_xid::UnicodeXID; +use crate::constants::TREES_SEPARATOR; + pub(crate) fn capitalize_first_letter(s: &str) -> String { let mut c = s.chars(); match c.next() { @@ -31,17 +32,18 @@ pub(crate) fn sanitize(identifier: &str) -> String { /// in the title and removing the spaces. For example, the sentence /// `when only owner` is converted to the `WhenOnlyOwner` string. pub(crate) fn to_pascal_case(sentence: &str) -> String { - sentence - .split_whitespace() - .map(capitalize_first_letter) - .collect::() + sentence.split_whitespace().map(capitalize_first_letter).collect::() } pub(crate) fn repeat_str(s: &str, n: usize) -> String { s.repeat(n) } -pub(crate) fn pluralize<'a>(count: usize, singular: &'a str, plural: &'a str) -> &'a str { +pub(crate) fn pluralize<'a>( + count: usize, + singular: &'a str, + plural: &'a str, +) -> &'a str { if count == 1 { singular } else { diff --git a/tests/check.rs b/tests/check.rs index 8e3b0a8..e6dfee9 100644 --- a/tests/check.rs +++ b/tests/check.rs @@ -1,21 +1,17 @@ use std::env; +use common::{cmd, get_binary_path}; use owo_colors::OwoColorize; use pretty_assertions::assert_eq; -use common::cmd; -use common::get_binary_path; - mod common; #[test] fn checks_invalid_structural_match() { let binary_path = get_binary_path(); let cwd = env::current_dir().unwrap(); - let tree_path = cwd - .join("tests") - .join("check") - .join("invalid_sol_structure.tree"); + let tree_path = + cwd.join("tests").join("check").join("invalid_sol_structure.tree"); let output = cmd(&binary_path, "check", &tree_path, &[]); let stderr = String::from_utf8(output.stderr).unwrap(); @@ -38,17 +34,17 @@ warn: incorrect position for function "test_WhenThereIsReentrancy""# fn checks_valid_structural_match() { let cwd = env::current_dir().unwrap(); let binary_path = get_binary_path(); - let tree_path = cwd - .join("tests") - .join("check") - .join("extra_codegen_sol.tree"); + let tree_path = + cwd.join("tests").join("check").join("extra_codegen_sol.tree"); let output = cmd(&binary_path, "check", &tree_path, &[]); let stderr = String::from_utf8(output.stderr).unwrap(); let stdout = String::from_utf8(output.stdout).unwrap(); assert_eq!("", stderr); - assert!(stdout.contains("All checks completed successfully! No issues found.")); + assert!( + stdout.contains("All checks completed successfully! No issues found.") + ); } #[test] @@ -62,14 +58,17 @@ fn checks_modifiers_skipped() { let stdout = String::from_utf8(output.stdout).unwrap(); assert_eq!("", stderr); - assert!(stdout.contains("All checks completed successfully! No issues found.")); + assert!( + stdout.contains("All checks completed successfully! No issues found.") + ); } #[test] fn checks_missing_sol_file() { let cwd = env::current_dir().unwrap(); let binary_path = get_binary_path(); - let tree_path = cwd.join("tests").join("check").join("no_matching_sol.tree"); + let tree_path = + cwd.join("tests").join("check").join("no_matching_sol.tree"); let output = cmd(&binary_path, "check", &tree_path, &[]); let actual = String::from_utf8(output.stderr).unwrap(); @@ -87,18 +86,19 @@ fn checks_empty_contract() { let output = cmd(&binary_path, "check", &tree_path, &[]); let actual = String::from_utf8(output.stderr).unwrap(); - assert!(actual.contains(r#"function "test_ShouldNeverRevert" is missing in .sol"#)); - assert!(actual.contains(r#"function "test_ShouldNotFindTheSolidityFile" is missing in .sol"#)); + assert!(actual + .contains(r#"function "test_ShouldNeverRevert" is missing in .sol"#)); + assert!(actual.contains( + r#"function "test_ShouldNotFindTheSolidityFile" is missing in .sol"# + )); } #[test] fn checks_missing_contract() { let cwd = env::current_dir().unwrap(); let binary_path = get_binary_path(); - let tree_path = cwd - .join("tests") - .join("check") - .join("missing_contract.tree"); + let tree_path = + cwd.join("tests").join("check").join("missing_contract.tree"); let output = cmd(&binary_path, "check", &tree_path, &[]); let actual = String::from_utf8(output.stderr).unwrap(); @@ -132,10 +132,8 @@ fn checks_missing_contract_identifier() { fn checks_contract_name_mismatch() { let cwd = env::current_dir().unwrap(); let binary_path = get_binary_path(); - let tree_path = cwd - .join("tests") - .join("check") - .join("contract_names_mismatch.tree"); + let tree_path = + cwd.join("tests").join("check").join("contract_names_mismatch.tree"); let output = cmd(&binary_path, "check", &tree_path, &[]); let actual = String::from_utf8(output.stderr).unwrap(); @@ -176,17 +174,17 @@ fn checks_invalid_tree() { let output = cmd(&binary_path, "check", &tree_path, &[]); let actual = String::from_utf8(output.stderr).unwrap(); - assert!(actual.contains(r#"an error occurred while parsing the tree: unexpected token '├'"#)); + assert!(actual.contains( + r#"an error occurred while parsing the tree: unexpected token '├'"# + )); } #[test] fn fixes_non_matching_contract_names() { let cwd = env::current_dir().unwrap(); let binary_path = get_binary_path(); - let tree_path = cwd - .join("tests") - .join("check") - .join("contract_names_mismatch.tree"); + let tree_path = + cwd.join("tests").join("check").join("contract_names_mismatch.tree"); let output = cmd(&binary_path, "check", &tree_path, &["--fix", "--stdout"]); let expected = "// SPDX-License-Identifier: UNLICENSED @@ -208,10 +206,8 @@ contract ContractName { fn fixes_contract_missing() { let cwd = env::current_dir().unwrap(); let binary_path = get_binary_path(); - let tree_path = cwd - .join("tests") - .join("check") - .join("missing_contract.tree"); + let tree_path = + cwd.join("tests").join("check").join("missing_contract.tree"); let output = cmd(&binary_path, "check", &tree_path, &["--fix", "--stdout"]); let expected = "// SPDX-License-Identifier: UNLICENSED @@ -232,10 +228,8 @@ contract MissingContract { fn fixes_extra_codegen_tree() { let cwd = env::current_dir().unwrap(); let binary_path = get_binary_path(); - let tree_path = cwd - .join("tests") - .join("check") - .join("extra_codegen_tree.tree"); + let tree_path = + cwd.join("tests").join("check").join("extra_codegen_tree.tree"); let output = cmd(&binary_path, "check", &tree_path, &["--fix", "--stdout"]); let actual = String::from_utf8(output.stdout).unwrap(); @@ -248,10 +242,8 @@ fn fixes_extra_codegen_tree() { fn fixes_extra_fn_plus_wrong_order() { let cwd = env::current_dir().unwrap(); let binary_path = get_binary_path(); - let tree_path = cwd - .join("tests") - .join("check") - .join("fix_extra_fn_plus_order.tree"); + let tree_path = + cwd.join("tests").join("check").join("fix_extra_fn_plus_order.tree"); let output = cmd(&binary_path, "check", &tree_path, &["--fix", "--stdout"]); let actual = String::from_utf8(output.stdout).unwrap(); @@ -287,10 +279,8 @@ contract Foo { fn fixes_invalid_structural_match() { let binary_path = get_binary_path(); let cwd = env::current_dir().unwrap(); - let tree_path = cwd - .join("tests") - .join("check") - .join("invalid_sol_structure.tree"); + let tree_path = + cwd.join("tests").join("check").join("invalid_sol_structure.tree"); let output = cmd(&binary_path, "check", &tree_path, &["--fix", "--stdout"]); let actual = String::from_utf8(output.stdout).unwrap(); diff --git a/tests/common/mod.rs b/tests/common/mod.rs index 03d3025..df7c49f 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -14,7 +14,12 @@ pub fn get_binary_path() -> PathBuf { } /// Runs a command with the specified args. -pub fn cmd(binary_path: &PathBuf, command: &str, tree_path: &PathBuf, args: &[&str]) -> Output { +pub fn cmd( + binary_path: &PathBuf, + command: &str, + tree_path: &PathBuf, + args: &[&str], +) -> Output { Command::new(binary_path) .arg(command) .arg(tree_path) diff --git a/tests/scaffold.rs b/tests/scaffold.rs index 0bb49f1..41f6d1f 100644 --- a/tests/scaffold.rs +++ b/tests/scaffold.rs @@ -1,11 +1,9 @@ use std::{env, fs}; +use common::{cmd, get_binary_path}; use owo_colors::OwoColorize; use pretty_assertions::assert_eq; -use common::cmd; -use common::get_binary_path; - mod common; #[test] From e3b32ae34711baf924f3077fce7668e4e672c551 Mon Sep 17 00:00:00 2001 From: Alexander Gonzalez Date: Sat, 6 Jul 2024 21:29:34 +0200 Subject: [PATCH 2/2] lint: relax clippy checks a bit --- .github/workflows/ci.yml | 33 ++++++++++++--------------------- src/check/context.rs | 4 ++-- src/check/violation.rs | 3 ++- src/scaffold/emitter.rs | 6 +++--- src/span.rs | 2 +- 5 files changed, 20 insertions(+), 28 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a6ee57d..b720fc4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -53,30 +53,21 @@ jobs: clippy: runs-on: ubuntu-latest - name: ${{ matrix.toolchain }} / clippy permissions: - contents: read checks: write - strategy: - fail-fast: false - matrix: - # Get early warning of new lints which are regularly introduced in beta - # channels. - toolchain: [ stable, beta ] steps: - - uses: actions/checkout@v4 - with: - submodules: true - - name: Install ${{ matrix.toolchain }} - uses: dtolnay/rust-toolchain@master - with: - toolchain: ${{ matrix.toolchain }} - components: clippy - - name: cargo clippy - uses: giraffate/clippy-action@v1 - with: - reporter: 'github-pr-check' - github_token: ${{ secrets.GITHUB_TOKEN }} + - uses: actions/checkout@v1 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + components: clippy + - name: Run Clippy + uses: actions-rs/clippy-check@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + args: --all-features coverage: runs-on: ubuntu-latest diff --git a/src/check/context.rs b/src/check/context.rs index 2fd59ca..9124541 100644 --- a/src/check/context.rs +++ b/src/check/context.rs @@ -53,7 +53,7 @@ impl Context { pub(crate) fn new(tree: PathBuf, cfg: Config) -> Result { let tree_path_cow = tree.to_string_lossy(); let tree_contents = try_read_to_string(&tree)?; - let hir = crate::hir::translate(&tree_contents, &Default::default()) + let hir = crate::hir::translate(&tree_contents, &Config::default()) .map_err(|e| { Violation::new( ViolationKind::ParsingFailed(e), @@ -124,7 +124,7 @@ impl Context { function: &hir::FunctionDefinition, offset: usize, ) { - let cfg = &Default::default(); + let cfg = &Config::default(); let f = &Hir::FunctionDefinition(function.clone()); let function = Emitter::new(cfg).emit(f); self.src = format!( diff --git a/src/check/violation.rs b/src/check/violation.rs index 303b7b8..0ec8e1f 100644 --- a/src/check/violation.rs +++ b/src/check/violation.rs @@ -14,6 +14,7 @@ use solang_parser::{ use super::{context::Context, location::Location}; use crate::{ + config::Config, error, hir::{self, Hir}, sol::{self, find_contract, find_matching_fn}, @@ -193,7 +194,7 @@ impl ViolationKind { pub(crate) fn fix(&self, mut ctx: Context) -> Context { match self { ViolationKind::ContractMissing(_) => { - let pt = sol::Translator::new(&Default::default()) + let pt = sol::Translator::new(&Config::default()) .translate(&ctx.hir); let source = sol::Formatter::new().emit(pt.clone()); let parsed = diff --git a/src/scaffold/emitter.rs b/src/scaffold/emitter.rs index 94d4e40..52f5615 100644 --- a/src/scaffold/emitter.rs +++ b/src/scaffold/emitter.rs @@ -304,7 +304,7 @@ mod tests { }; fn scaffold(text: &str) -> Result { - let cfg = Default::default(); + let cfg = Config::default(); let hir = translate_and_combine_trees(text, &cfg)?; Ok(emitter::Emitter::new(&cfg).emit(&hir)) } @@ -465,7 +465,7 @@ contract FileTest { #[test] fn with_vm_skip() -> Result<()> { let file_contents = "FileTest\n└── when something bad happens\n └── it should not revert"; - let cfg: Config = Default::default(); + let cfg: Config = Config::default(); let cfg = cfg.with_vm_skip(true); let hir = translate_and_combine_trees(file_contents, &cfg)?; let emitted = emitter::Emitter::new(&cfg).emit(&hir); @@ -491,7 +491,7 @@ contract FileTest { fn with_vm_skip_top_level_statement() { let hir = Hir::Statement(Statement { ty: StatementType::VmSkip }); - let _ = emitter::Emitter::new(&Default::default()).emit(&hir); + let _ = emitter::Emitter::new(&Config::default()).emit(&hir); } #[test] diff --git a/src/span.rs b/src/span.rs index 656c736..4ef16bb 100644 --- a/src/span.rs +++ b/src/span.rs @@ -50,7 +50,7 @@ pub struct Position { impl Default for Position { fn default() -> Self { - Self { offset: Default::default(), line: 1, column: 1 } + Self { offset: usize::default(), line: 1, column: 1 } } }