From ce83ef631dbed2c156e6322890399516ddc013a2 Mon Sep 17 00:00:00 2001 From: Govardhan G D Date: Wed, 16 Aug 2023 09:27:34 +0530 Subject: [PATCH 01/25] add goto-references, goto-implementations, goto-type-definitions Signed-off-by: Govardhan G D --- src/bin/languageserver/mod.rs | 427 +++++++++++++++++++++++++--------- 1 file changed, 316 insertions(+), 111 deletions(-) diff --git a/src/bin/languageserver/mod.rs b/src/bin/languageserver/mod.rs index 57c6328b0..dbbb4721d 100644 --- a/src/bin/languageserver/mod.rs +++ b/src/bin/languageserver/mod.rs @@ -19,16 +19,21 @@ use tokio::sync::Mutex; use tower_lsp::{ jsonrpc::{Error, ErrorCode, Result}, lsp_types::{ + request::{ + GotoImplementationParams, GotoImplementationResponse, GotoTypeDefinitionParams, + GotoTypeDefinitionResponse, + }, CompletionOptions, CompletionParams, CompletionResponse, Diagnostic, DiagnosticRelatedInformation, DiagnosticSeverity, DidChangeConfigurationParams, DidChangeTextDocumentParams, DidChangeWatchedFilesParams, DidChangeWorkspaceFoldersParams, DidCloseTextDocumentParams, DidOpenTextDocumentParams, DidSaveTextDocumentParams, ExecuteCommandOptions, ExecuteCommandParams, GotoDefinitionParams, GotoDefinitionResponse, - Hover, HoverContents, HoverParams, HoverProviderCapability, InitializeParams, - InitializeResult, InitializedParams, Location, MarkedString, MessageType, OneOf, Position, - Range, ServerCapabilities, SignatureHelpOptions, TextDocumentContentChangeEvent, - TextDocumentSyncCapability, TextDocumentSyncKind, Url, WorkspaceFoldersServerCapabilities, - WorkspaceServerCapabilities, + Hover, HoverContents, HoverParams, HoverProviderCapability, + ImplementationProviderCapability, InitializeParams, InitializeResult, InitializedParams, + Location, MarkedString, MessageType, OneOf, Position, Range, ReferenceParams, + ServerCapabilities, SignatureHelpOptions, TextDocumentContentChangeEvent, + TextDocumentSyncCapability, TextDocumentSyncKind, TypeDefinitionProviderCapability, Url, + WorkspaceFoldersServerCapabilities, WorkspaceServerCapabilities, }, Client, LanguageServer, LspService, Server, }; @@ -67,17 +72,32 @@ struct DefinitionIndex { def_type: DefinitionType, } +impl From for DefinitionIndex { + fn from(value: DefinitionType) -> Self { + Self { + def_path: Default::default(), + def_type: value, + } + } +} + /// Stores locations of definitions of functions, contracts, structs etc. type Definitions = HashMap; /// Stores strings shown on hover type HoverEntry = Interval; /// Stores locations of function calls, uses of structs, contracts etc. type ReferenceEntry = Interval; +/// Stores the list of methods implemented by a contract +type Implementations = HashMap>; +/// Stores types of code objects +type Types = HashMap; +#[derive(Debug)] struct FileCache { file: ast::File, hovers: Lapper, references: Lapper, + implementations: Implementations, } /// Stores information used by language server for every opened file @@ -114,6 +134,7 @@ pub struct SolangServer { importmaps: Vec<(String, PathBuf)>, files: Mutex, definitions: Mutex, + types: Mutex, } #[tokio::main(flavor = "current_thread")] @@ -148,6 +169,7 @@ pub async fn start_server(language_args: &LanguageServerCommand) -> ! { text_buffers: HashMap::new(), }), definitions: Mutex::new(HashMap::new()), + types: Mutex::new(HashMap::new()), }); Server::new(stdin, stdout, socket).serve(service).await; @@ -242,7 +264,7 @@ impl SolangServer { let res = self.client.publish_diagnostics(uri, diags, None); - let (caches, definitions) = Builder::build(&ns); + let (caches, definitions, types) = Builder::build(&ns); let mut files = self.files.lock().await; for (f, c) in ns.files.iter().zip(caches.into_iter()) { @@ -252,10 +274,42 @@ impl SolangServer { } self.definitions.lock().await.extend(definitions); + self.types.lock().await.extend(types); res.await; } } + + // Used for goto-{definitions, implementations, declarations, type_definitions} + + async fn get_reference_from_params( + &self, + params: GotoDefinitionParams, + ) -> Result> { + let uri = params.text_document_position_params.text_document.uri; + let path = uri.to_file_path().map_err(|_| Error { + code: ErrorCode::InvalidRequest, + message: format!("Received invalid URI: {uri}"), + data: None, + })?; + + let files = self.files.lock().await; + if let Some(cache) = files.caches.get(&path) { + let f = &cache.file; + let offset = f.get_offset( + params.text_document_position_params.position.line as _, + params.text_document_position_params.position.character as _, + ); + if let Some(reference) = cache + .references + .find(offset, offset + 1) + .min_by(|a, b| (a.stop - a.start).cmp(&(b.stop - b.start))) + { + return Ok(Some(reference.val.clone())); + } + } + Ok(None) + } } struct Builder<'a> { @@ -264,6 +318,8 @@ struct Builder<'a> { definitions: Definitions, // `usize` is the file number the reference belongs to references: Vec<(usize, ReferenceEntry)>, + implementations: Vec, + types: Vec<(DefinitionIndex, DefinitionIndex)>, ns: &'a ast::Namespace, } @@ -321,13 +377,15 @@ impl<'a> Builder<'a> { if let Some(id) = ¶m.id { let file_no = id.loc.file_no(); let file = &self.ns.files[file_no]; - self.definitions.insert( - DefinitionIndex { - def_path: file.path.clone(), - def_type: DefinitionType::Variable(*var_no), - }, - loc_to_range(&id.loc, file), - ); + let di = DefinitionIndex { + def_path: file.path.clone(), + def_type: DefinitionType::Variable(*var_no), + }; + self.definitions + .insert(di.clone(), loc_to_range(&id.loc, file)); + if let Some(dt) = get_type_definition(¶m.ty) { + self.types.push((di, dt.into())); + } } if let Some(loc) = param.ty_loc { @@ -337,10 +395,7 @@ impl<'a> Builder<'a> { ReferenceEntry { start: loc.start(), stop: loc.exclusive_end(), - val: DefinitionIndex { - def_path: Default::default(), - def_type: dt, - }, + val: dt.into(), }, )); } @@ -412,13 +467,15 @@ impl<'a> Builder<'a> { if let Some(id) = ¶m.id { let file_no = id.loc.file_no(); let file = &self.ns.files[file_no]; - self.definitions.insert( - DefinitionIndex { - def_path: file.path.clone(), - def_type: DefinitionType::Variable(*var_no), - }, - loc_to_range(&id.loc, file), - ); + let di = DefinitionIndex { + def_path: file.path.clone(), + def_type: DefinitionType::Variable(*var_no), + }; + self.definitions + .insert(di.clone(), loc_to_range(&id.loc, file)); + if let Some(dt) = get_type_definition(¶m.ty) { + self.types.push((di, dt.into())); + } } } ast::DestructureField::None => (), @@ -923,10 +980,7 @@ impl<'a> Builder<'a> { ReferenceEntry { start: loc.start(), stop: loc.exclusive_end(), - val: DefinitionIndex { - def_path: Default::default(), - def_type: dt, - }, + val: dt.into(), }, )); } @@ -1188,13 +1242,15 @@ impl<'a> Builder<'a> { }, )); - self.definitions.insert( - DefinitionIndex { - def_path: file.path.clone(), - def_type: DefinitionType::NonLocalVariable(contract_no, var_no), - }, - loc_to_range(&variable.loc, file), - ); + let di = DefinitionIndex { + def_path: file.path.clone(), + def_type: DefinitionType::NonLocalVariable(contract_no, var_no), + }; + self.definitions + .insert(di.clone(), loc_to_range(&variable.loc, file)); + if let Some(dt) = get_type_definition(&variable.ty) { + self.types.push((di, dt.into())); + } } // Constructs struct fields and stores it in the lookup table. @@ -1206,10 +1262,7 @@ impl<'a> Builder<'a> { ReferenceEntry { start: loc.start(), stop: loc.exclusive_end(), - val: DefinitionIndex { - def_path: Default::default(), - def_type: dt, - }, + val: dt.into(), }, )); } @@ -1230,25 +1283,29 @@ impl<'a> Builder<'a> { }, )); - self.definitions.insert( - DefinitionIndex { - def_path: file.path.clone(), - def_type: DefinitionType::Field( - Type::Struct(ast::StructType::UserDefined(id)), - field_id, - ), - }, - loc_to_range(&field.loc, file), - ); + let di = DefinitionIndex { + def_path: file.path.clone(), + def_type: DefinitionType::Field( + Type::Struct(ast::StructType::UserDefined(id)), + field_id, + ), + }; + self.definitions + .insert(di.clone(), loc_to_range(&field.loc, file)); + if let Some(dt) = get_type_definition(&field.ty) { + self.types.push((di, dt.into())); + } } // Traverses namespace to extract information used later by the language server // This includes hover messages, locations where code objects are declared and used - fn build(ns: &ast::Namespace) -> (Vec, Definitions) { + fn build(ns: &ast::Namespace) -> (Vec, Definitions, Types) { let mut builder = Builder { hovers: Vec::new(), definitions: HashMap::new(), references: Vec::new(), + implementations: vec![HashMap::new(); ns.files.len()], + types: Vec::new(), ns, }; @@ -1361,13 +1418,16 @@ impl<'a> Builder<'a> { if let Some(id) = ¶m.id { let file_no = id.loc.file_no(); let file = &builder.ns.files[file_no]; - builder.definitions.insert( - DefinitionIndex { - def_path: file.path.clone(), - def_type: DefinitionType::Variable(*var_no), - }, - loc_to_range(&id.loc, file), - ); + let di = DefinitionIndex { + def_path: file.path.clone(), + def_type: DefinitionType::Variable(*var_no), + }; + builder + .definitions + .insert(di.clone(), loc_to_range(&id.loc, file)); + if let Some(dt) = get_type_definition(¶m.ty) { + builder.types.push((di, dt.into())); + } } } if let Some(loc) = param.ty_loc { @@ -1377,10 +1437,7 @@ impl<'a> Builder<'a> { ReferenceEntry { start: loc.start(), stop: loc.exclusive_end(), - val: DefinitionIndex { - def_path: Default::default(), - def_type: dt, - }, + val: dt.into(), }, )); } @@ -1401,13 +1458,16 @@ impl<'a> Builder<'a> { if let Some(var_no) = func.symtable.returns.get(i) { let file_no = id.loc.file_no(); let file = &ns.files[file_no]; - builder.definitions.insert( - DefinitionIndex { - def_path: file.path.clone(), - def_type: DefinitionType::Variable(*var_no), - }, - loc_to_range(&id.loc, file), - ); + let di = DefinitionIndex { + def_path: file.path.clone(), + def_type: DefinitionType::Variable(*var_no), + }; + builder + .definitions + .insert(di.clone(), loc_to_range(&id.loc, file)); + if let Some(dt) = get_type_definition(&ret.ty) { + builder.types.push((di, dt.into())); + } } } @@ -1418,10 +1478,7 @@ impl<'a> Builder<'a> { ReferenceEntry { start: loc.start(), stop: loc.exclusive_end(), - val: DefinitionIndex { - def_path: Default::default(), - def_type: dt, - }, + val: dt.into(), }, )); } @@ -1491,13 +1548,23 @@ impl<'a> Builder<'a> { }, )); - builder.definitions.insert( - DefinitionIndex { - def_path: file.path.clone(), - def_type: DefinitionType::Contract(ci), - }, - loc_to_range(&contract.loc, file), - ); + let cdi = DefinitionIndex { + def_path: file.path.clone(), + def_type: DefinitionType::Contract(ci), + }; + builder + .definitions + .insert(cdi.clone(), loc_to_range(&contract.loc, file)); + + let impls = contract + .functions + .iter() + .map(|f| DefinitionIndex { + def_path: file.path.clone(), // all the implementations for a contract are present in the same file in solidity + def_type: DefinitionType::Function(*f), + }) + .collect(); + builder.implementations[file_no].insert(cdi, impls); } for (ei, event) in builder.ns.events.iter().enumerate() { @@ -1525,7 +1592,7 @@ impl<'a> Builder<'a> { ); } - for lookup in &mut builder.hovers { + for lookup in builder.hovers.iter_mut() { if let Some(msg) = builder.ns.hover_overrides.get(&pt::Loc::File( lookup.0, lookup.1.start, @@ -1540,6 +1607,24 @@ impl<'a> Builder<'a> { defs_to_files.insert(key.def_type.clone(), key.def_path.clone()); } + let mut defs_to_file_nos = HashMap::new(); + for (i, f) in ns.files.iter().enumerate() { + defs_to_file_nos.insert(f.path.clone(), i); + } + for (di, range) in &builder.definitions { + let file_no = defs_to_file_nos[&di.def_path]; + let file = &ns.files[file_no]; + builder.references.push(( + file_no, + ReferenceEntry { + start: file + .get_offset(range.start.line as usize, range.start.character as usize), + stop: file.get_offset(range.end.line as usize, range.end.character as usize), + val: di.clone(), + }, + )); + } + let caches = ns .files .iter() @@ -1566,9 +1651,17 @@ impl<'a> Builder<'a> { }) .collect(), ), + implementations: builder.implementations[i].clone(), }) .collect(); - (caches, builder.definitions) + + let mut types = HashMap::new(); + for (key, mut val) in builder.types { + val.def_path = defs_to_files[&val.def_type].clone(); + types.insert(key, val); + } + + (caches, builder.definitions, types) } /// Render the type with struct/enum fields expanded @@ -1650,6 +1743,9 @@ impl LanguageServer for SolangServer { file_operations: None, }), definition_provider: Some(OneOf::Left(true)), + type_definition_provider: Some(TypeDefinitionProviderCapability::Simple(true)), + implementation_provider: Some(ImplementationProviderCapability::Simple(true)), + references_provider: Some(OneOf::Left(true)), ..ServerCapabilities::default() }, }) @@ -1806,45 +1902,154 @@ impl LanguageServer for SolangServer { &self, params: GotoDefinitionParams, ) -> Result> { - let uri = params.text_document_position_params.text_document.uri; + let Some(reference) = self.get_reference_from_params(params).await? else {return Ok(None)}; + let definitions = &self.definitions.lock().await; + let location = definitions + .get(&reference) + .map(|range| { + let uri = Url::from_file_path(&reference.def_path).unwrap(); + Location { uri, range: *range } + }) + .map(GotoTypeDefinitionResponse::Scalar); + Ok(location) + } - let path = uri.to_file_path().map_err(|_| Error { - code: ErrorCode::InvalidRequest, - message: format!("Received invalid URI: {uri}"), - data: None, - })?; - let files = self.files.lock().await; - if let Some(cache) = files.caches.get(&path) { - let f = &cache.file; - let offset = f.get_offset( - params.text_document_position_params.position.line as _, - params.text_document_position_params.position.character as _, - ); - if let Some(reference) = cache - .references - .find(offset, offset + 1) - .min_by(|a, b| (a.stop - a.start).cmp(&(b.stop - b.start))) - { - let di = &reference.val; - if let Some(range) = self.definitions.lock().await.get(di) { - let uri = Url::from_file_path(&di.def_path).unwrap(); - let ret = Ok(Some(GotoDefinitionResponse::Scalar(Location { - uri, - range: *range, - }))); - return ret; - } + async fn goto_type_definition( + &self, + params: GotoTypeDefinitionParams, + ) -> Result> { + let Some(reference) = self.get_reference_from_params(params).await? else { return Ok(None) }; + let types = self.types.lock().await; + + let di = match &reference.def_type { + DefinitionType::Variable(_) + | DefinitionType::NonLocalVariable(_, _) + | DefinitionType::Field(_, _) => { + if let Some(def) = types.get(&reference) { + def + } else { + return Ok(None); + } + } + // DefinitionIndex::Variant(enum_id, _) => { + // &DefinitionIndex::Enum(*enum_id) + // } + DefinitionType::Struct(_) + | DefinitionType::Enum(_) + | DefinitionType::Contract(_) + | DefinitionType::Event(_) + | DefinitionType::UserType(_) => &reference, + _ => return Ok(None), + }; + + let definitions = &self.definitions.lock().await; + let location = definitions + .get(di) + .map(|range| { + let uri = Url::from_file_path(&di.def_path).unwrap(); + Location { uri, range: *range } + }) + .map(GotoTypeDefinitionResponse::Scalar); + + Ok(location) + } + + async fn goto_implementation( + &self, + params: GotoImplementationParams, + ) -> Result> { + let Some(reference) = self.get_reference_from_params(params).await? else { return Ok(None) }; + let caches = &self.files.lock().await.caches; + let impls = match &reference.def_type { + DefinitionType::Variable(_) + | DefinitionType::NonLocalVariable(_, _) + | DefinitionType::Field(_, _) => { + self.types.lock().await.get(&reference).and_then(|ty| { + if let DefinitionType::Contract(_) = &ty.def_type { + caches + .get(&ty.def_path) + .and_then(|cache| cache.implementations.get(ty)) + } else { + None + } + }) } + DefinitionType::Contract(_) => caches + .get(&reference.def_path) + .and_then(|cache| cache.implementations.get(&reference)), + _ => None, + }; + + let definitions = self.definitions.lock().await; + let impls = impls + .map(|impls| { + impls + .iter() + .filter_map(|di| { + let path = &di.def_path; + definitions.get(di).map(|range| { + let uri = Url::from_file_path(path).unwrap(); + Location { uri, range: *range } + }) + }) + .collect() + }) + .map(GotoImplementationResponse::Array); + + Ok(impls) + } + + async fn references(&self, params: ReferenceParams) -> Result>> { + let def_params: GotoDefinitionParams = GotoDefinitionParams { + text_document_position_params: params.text_document_position, + work_done_progress_params: params.work_done_progress_params, + partial_result_params: params.partial_result_params, + }; + let Some(reference) = self.get_reference_from_params(def_params).await? else {return Ok(None)}; + + let caches = &self.files.lock().await.caches; + let mut locations: Vec<_> = caches + .iter() + .flat_map(|(p, cache)| { + let uri = Url::from_file_path(p).unwrap(); + cache + .references + .iter() + .filter(|r| r.val == reference) + .map(move |r| Location { + uri: uri.clone(), + range: get_range(r.start, r.stop, &cache.file), + }) + }) + .collect(); + + if !params.context.include_declaration { + let definitions = self.definitions.lock().await; + let uri = Url::from_file_path(&reference.def_path).unwrap(); + let def = if let Some(range) = definitions.get(&reference) { + Location { uri, range: *range } + } else { + Location { + uri: Url::from_file_path("").unwrap(), + range: Default::default(), + } + }; + locations.retain(|loc| loc != &def); } - Ok(None) + + Ok(Some(locations)) } } /// Calculate the line and column from the Loc offset received from the parser fn loc_to_range(loc: &pt::Loc, file: &ast::File) -> Range { - let (line, column) = file.offset_to_line_column(loc.start()); + get_range(loc.start(), loc.end(), file) +} + +fn get_range(start: usize, end: usize, file: &ast::File) -> Range { + let (line, column) = file.offset_to_line_column(start); let start = Position::new(line as u32, column as u32); - let (line, column) = file.offset_to_line_column(loc.end()); + let (line, column) = file.offset_to_line_column(end); let end = Position::new(line as u32, column as u32); Range::new(start, end) From 83f6d2df1f308976337f5cbf722165c61ebb5cb2 Mon Sep 17 00:00:00 2001 From: Govardhan G D Date: Thu, 24 Aug 2023 12:45:05 +0530 Subject: [PATCH 02/25] add test cases Signed-off-by: Govardhan G D --- vscode/src/test/suite/extension.test.ts | 204 ++++++++++++++++++++++++ vscode/src/testFixture/impls.sol | 30 ++++ vscode/src/testFixture/refs.sol | 0 3 files changed, 234 insertions(+) create mode 100644 vscode/src/testFixture/impls.sol create mode 100644 vscode/src/testFixture/refs.sol diff --git a/vscode/src/test/suite/extension.test.ts b/vscode/src/test/suite/extension.test.ts index 03d174731..1aee1d117 100644 --- a/vscode/src/test/suite/extension.test.ts +++ b/vscode/src/test/suite/extension.test.ts @@ -75,6 +75,28 @@ suite('Extension Test Suite', function () { test('Testing for GotoDefinitions', async () => { await testdefs(defdoc1); }); + + // Tests for goto-type-definitions. + this.timeout(20000); + const typedefdoc1 = getDocUri('defs.sol'); + test('Testing for GotoTypeDefinitions', async () => { + await testtypedefs(typedefdoc1); + }); + + // Tests for goto-impls + this.timeout(20000); + const implsdoc1 = getDocUri('impls.sol'); + test('Testing for GotoImplementations', async () => { + await testimpls(implsdoc1); + }); + + // Tests for goto-references + this.timeout(20000); + const refsdoc1 = getDocUri('defs.sol'); + test('Testing for GotoReferences', async () => { + await testrefs(refsdoc1); + }); + }); function toRange(lineno1: number, charno1: number, lineno2: number, charno2: number) { @@ -152,6 +174,188 @@ async function testdefs(docUri: vscode.Uri) { assert.strictEqual(loc5.uri.path, docUri.path); } +async function testtypedefs(docUri: vscode.Uri) { + await activate(docUri); + + const pos0 = new vscode.Position(28, 12); + const actualtypedef0 = (await vscode.commands.executeCommand( + 'vscode.executeTypeDefinitionProvider', + docUri, + pos0, + )) as vscode.Definition[]; + const loc0 = actualtypedef0[0] as vscode.Location; + assert.strictEqual(loc0.range.start.line, 22); + assert.strictEqual(loc0.range.start.character, 11); + assert.strictEqual(loc0.range.end.line, 22); + assert.strictEqual(loc0.range.end.character, 15); + assert.strictEqual(loc0.uri.path, docUri.path); + + const pos1 = new vscode.Position(32, 18); + const actualtypedef1 = (await vscode.commands.executeCommand( + 'vscode.executeTypeDefinitionProvider', + docUri, + pos1, + )) as vscode.Definition[]; + const loc1 = actualtypedef1[0] as vscode.Location; + assert.strictEqual(loc1.range.start.line, 7); + assert.strictEqual(loc1.range.start.character, 4); + assert.strictEqual(loc1.range.end.line, 21); + assert.strictEqual(loc1.range.end.character, 5); + assert.strictEqual(loc1.uri.path, docUri.path); +} + +async function testimpls(docUri: vscode.Uri) { + await activate(docUri); + + const pos0 = new vscode.Position(0, 9); + const actualimpl0 = (await vscode.commands.executeCommand( + 'vscode.executeImplementationProvider', + docUri, + pos0, + )) as vscode.Definition[]; + assert.strictEqual(actualimpl0.length, 2); + const loc00 = actualimpl0[0] as vscode.Location; + assert.strictEqual(loc00.range.start.line, 1); + assert.strictEqual(loc00.range.start.character, 4); + assert.strictEqual(loc00.range.end.line, 1); + assert.strictEqual(loc00.range.end.character, 42); + assert.strictEqual(loc00.uri.path, docUri.path); + const loc01 = actualimpl0[1] as vscode.Location; + assert.strictEqual(loc01.range.start.line, 6); + assert.strictEqual(loc01.range.start.character, 4); + assert.strictEqual(loc01.range.end.line, 6); + assert.strictEqual(loc01.range.end.character, 61); + assert.strictEqual(loc01.uri.path, docUri.path); + + + const pos1 = new vscode.Position(0, 14); + const actualimpl1 = (await vscode.commands.executeCommand( + 'vscode.executeImplementationProvider', + docUri, + pos1, + )) as vscode.Definition[]; + assert.strictEqual(actualimpl1.length, 2); + const loc10 = actualimpl1[0] as vscode.Location; + assert.strictEqual(loc10.range.start.line, 12); + assert.strictEqual(loc10.range.start.character, 4); + assert.strictEqual(loc10.range.end.line, 12); + assert.strictEqual(loc10.range.end.character, 52); + assert.strictEqual(loc10.uri.path, docUri.path); + const loc11 = actualimpl1[1] as vscode.Location; + assert.strictEqual(loc11.range.start.line, 16); + assert.strictEqual(loc11.range.start.character, 4); + assert.strictEqual(loc11.range.end.line, 16); + assert.strictEqual(loc11.range.end.character, 53); + assert.strictEqual(loc11.uri.path, docUri.path); + + + const pos2 = new vscode.Position(21, 19); + const actualimpl2 = (await vscode.commands.executeCommand( + 'vscode.executeImplementationProvider', + docUri, + pos2, + )) as vscode.Definition[]; + assert.strictEqual(actualimpl2.length, 2); + const loc20 = actualimpl2[0] as vscode.Location; + assert.strictEqual(loc20.range.start.line, 22); + assert.strictEqual(loc20.range.start.character, 4); + assert.strictEqual(loc20.range.end.line, 22); + assert.strictEqual(loc20.range.end.character, 52); + assert.strictEqual(loc20.uri.path, docUri.path); + const loc21 = actualimpl2[1] as vscode.Location; + assert.strictEqual(loc21.range.start.line, 26); + assert.strictEqual(loc21.range.start.character, 4); + assert.strictEqual(loc21.range.end.line, 26); + assert.strictEqual(loc21.range.end.character, 54); + assert.strictEqual(loc21.uri.path, docUri.path); +} + +async function testrefs(docUri: vscode.Uri) { + await activate(docUri); + + const pos0 = new vscode.Position(27, 52); + const actualref0 = (await vscode.commands.executeCommand( + 'vscode.executeReferenceProvider', + docUri, + pos0, + )) as vscode.Definition[]; + assert.strictEqual(actualref0.length, 5); + const loc00 = actualref0[0] as vscode.Location; + assert.strictEqual(loc00.range.start.line, 27); + assert.strictEqual(loc00.range.start.character, 50); + assert.strictEqual(loc00.range.end.line, 27); + assert.strictEqual(loc00.range.end.character, 55); + assert.strictEqual(loc00.uri.path, docUri.path); + const loc01 = actualref0[1] as vscode.Location; + assert.strictEqual(loc01.range.start.line, 30); + assert.strictEqual(loc01.range.start.character, 16); + assert.strictEqual(loc01.range.end.line, 30); + assert.strictEqual(loc01.range.end.character, 22); + assert.strictEqual(loc01.uri.path, docUri.path); + const loc02 = actualref0[2] as vscode.Location; + assert.strictEqual(loc02.range.start.line, 33); + assert.strictEqual(loc02.range.start.character, 16); + assert.strictEqual(loc02.range.end.line, 33); + assert.strictEqual(loc02.range.end.character, 22); + assert.strictEqual(loc02.uri.path, docUri.path); + const loc03 = actualref0[3] as vscode.Location; + assert.strictEqual(loc03.range.start.line, 36); + assert.strictEqual(loc03.range.start.character, 16); + assert.strictEqual(loc03.range.end.line, 36); + assert.strictEqual(loc03.range.end.character, 22); + assert.strictEqual(loc03.uri.path, docUri.path); + const loc04 = actualref0[4] as vscode.Location; + assert.strictEqual(loc04.range.start.line, 39); + assert.strictEqual(loc04.range.start.character, 16); + assert.strictEqual(loc04.range.end.line, 39); + assert.strictEqual(loc04.range.end.character, 22); + assert.strictEqual(loc04.uri.path, docUri.path); + + const pos1 = new vscode.Position(28, 12); + const actualref1 = (await vscode.commands.executeCommand( + 'vscode.executeReferenceProvider', + docUri, + pos1, + )) as vscode.Definition[]; + assert.strictEqual(actualref1.length, 6); + const loc10 = actualref1[0] as vscode.Location; + assert.strictEqual(loc10.range.start.line, 27); + assert.strictEqual(loc10.range.start.character, 24); + assert.strictEqual(loc10.range.end.line, 27); + assert.strictEqual(loc10.range.end.character, 25); + assert.strictEqual(loc10.uri.path, docUri.path); + const loc11 = actualref1[1] as vscode.Location; + assert.strictEqual(loc11.range.start.line, 28); + assert.strictEqual(loc11.range.start.character, 12); + assert.strictEqual(loc11.range.end.line, 28); + assert.strictEqual(loc11.range.end.character, 14); + assert.strictEqual(loc11.uri.path, docUri.path); + const loc12 = actualref1[2] as vscode.Location; + assert.strictEqual(loc12.range.start.line, 29); + assert.strictEqual(loc12.range.start.character, 16); + assert.strictEqual(loc12.range.end.line, 29); + assert.strictEqual(loc12.range.end.character, 18); + assert.strictEqual(loc12.uri.path, docUri.path); + const loc13 = actualref1[3] as vscode.Location; + assert.strictEqual(loc13.range.start.line, 32); + assert.strictEqual(loc13.range.start.character, 16); + assert.strictEqual(loc13.range.end.line, 32); + assert.strictEqual(loc13.range.end.character, 18); + assert.strictEqual(loc13.uri.path, docUri.path); + const loc14 = actualref1[4] as vscode.Location; + assert.strictEqual(loc14.range.start.line, 35); + assert.strictEqual(loc14.range.start.character, 16); + assert.strictEqual(loc14.range.end.line, 35); + assert.strictEqual(loc14.range.end.character, 18); + assert.strictEqual(loc14.uri.path, docUri.path); + const loc15 = actualref1[5] as vscode.Location; + assert.strictEqual(loc15.range.start.line, 38); + assert.strictEqual(loc15.range.start.character, 16); + assert.strictEqual(loc15.range.end.line, 38); + assert.strictEqual(loc15.range.end.character, 18); + assert.strictEqual(loc15.uri.path, docUri.path); +} + async function testhover(docUri: vscode.Uri) { await activate(docUri); diff --git a/vscode/src/testFixture/impls.sol b/vscode/src/testFixture/impls.sol new file mode 100644 index 000000000..8ceb85b0f --- /dev/null +++ b/vscode/src/testFixture/impls.sol @@ -0,0 +1,30 @@ +contract a is b1, b2 { + function baz() public returns (uint64) { + // this will return 100 + return super.foo(); + } + + function foo() internal override(b1, b2) returns (uint64) { + return 2; + } +} + +abstract contract b1 { + function foo() internal virtual returns (uint64) { + return 100; + } + + function bar() internal virtual returns (uint256) { + return 1; + } +} + +abstract contract b2 { + function foo() internal virtual returns (uint64) { + return 200; + } + + function bar2() internal virtual returns (uint256) { + return 25; + } +} diff --git a/vscode/src/testFixture/refs.sol b/vscode/src/testFixture/refs.sol new file mode 100644 index 000000000..e69de29bb From e842c0ba15c841547510b8930285626bf9aebabb Mon Sep 17 00:00:00 2001 From: Govardhan G D Date: Mon, 4 Sep 2023 09:38:34 +0530 Subject: [PATCH 03/25] add type definitions for enum variants + pr suggestions Signed-off-by: Govardhan G D --- src/bin/languageserver/mod.rs | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/src/bin/languageserver/mod.rs b/src/bin/languageserver/mod.rs index dbbb4721d..d85a7bd3a 100644 --- a/src/bin/languageserver/mod.rs +++ b/src/bin/languageserver/mod.rs @@ -280,8 +280,7 @@ impl SolangServer { } } - // Used for goto-{definitions, implementations, declarations, type_definitions} - + /// Common code for goto_{definitions, implementations, declarations, type_definitions} async fn get_reference_from_params( &self, params: GotoDefinitionParams, @@ -1324,13 +1323,17 @@ impl<'a> Builder<'a> { )), }, )); - builder.definitions.insert( - DefinitionIndex { - def_path: file.path.clone(), - def_type: DefinitionType::Variant(ei, discriminant), - }, - loc_to_range(loc, file), - ); + + let di = DefinitionIndex { + def_path: file.path.clone(), + def_type: DefinitionType::Variant(ei, discriminant), + }; + builder + .definitions + .insert(di.clone(), loc_to_range(loc, file)); + + let dt = DefinitionType::Enum(ei); + builder.types.push((di, dt.into())); } let file_no = enum_decl.loc.file_no(); @@ -1592,7 +1595,7 @@ impl<'a> Builder<'a> { ); } - for lookup in builder.hovers.iter_mut() { + for lookup in &mut builder.hovers { if let Some(msg) = builder.ns.hover_overrides.get(&pt::Loc::File( lookup.0, lookup.1.start, @@ -1924,16 +1927,14 @@ impl LanguageServer for SolangServer { let di = match &reference.def_type { DefinitionType::Variable(_) | DefinitionType::NonLocalVariable(_, _) - | DefinitionType::Field(_, _) => { + | DefinitionType::Field(_, _) + | DefinitionType::Variant(_, _) => { if let Some(def) = types.get(&reference) { def } else { return Ok(None); } } - // DefinitionIndex::Variant(enum_id, _) => { - // &DefinitionIndex::Enum(*enum_id) - // } DefinitionType::Struct(_) | DefinitionType::Enum(_) | DefinitionType::Contract(_) From 1c21bd022582c5f1cd47a3e471eaa1bd2f72c637 Mon Sep 17 00:00:00 2001 From: Govardhan G D Date: Mon, 4 Sep 2023 10:16:40 +0530 Subject: [PATCH 04/25] use one mutex for both types and definitions Signed-off-by: Govardhan G D --- src/bin/languageserver/mod.rs | 55 +++++++++++++++++++---------------- 1 file changed, 30 insertions(+), 25 deletions(-) diff --git a/src/bin/languageserver/mod.rs b/src/bin/languageserver/mod.rs index d85a7bd3a..6ac3f741e 100644 --- a/src/bin/languageserver/mod.rs +++ b/src/bin/languageserver/mod.rs @@ -127,14 +127,18 @@ struct Files { // // More information can be found here: https://github.com/hyperledger/solang/pull/1411 +struct GlobalCache { + definitions: Definitions, + types: Types, +} + pub struct SolangServer { client: Client, target: Target, importpaths: Vec, importmaps: Vec<(String, PathBuf)>, files: Mutex, - definitions: Mutex, - types: Mutex, + global_cache: Mutex, } #[tokio::main(flavor = "current_thread")] @@ -168,8 +172,10 @@ pub async fn start_server(language_args: &LanguageServerCommand) -> ! { caches: HashMap::new(), text_buffers: HashMap::new(), }), - definitions: Mutex::new(HashMap::new()), - types: Mutex::new(HashMap::new()), + global_cache: Mutex::new(GlobalCache { + definitions: HashMap::new(), + types: HashMap::new(), + }), }); Server::new(stdin, stdout, socket).serve(service).await; @@ -273,8 +279,9 @@ impl SolangServer { } } - self.definitions.lock().await.extend(definitions); - self.types.lock().await.extend(types); + let mut gc = self.global_cache.lock().await; + gc.definitions.extend(definitions); + gc.types.extend(types); res.await; } @@ -1906,7 +1913,7 @@ impl LanguageServer for SolangServer { params: GotoDefinitionParams, ) -> Result> { let Some(reference) = self.get_reference_from_params(params).await? else {return Ok(None)}; - let definitions = &self.definitions.lock().await; + let definitions = &self.global_cache.lock().await.definitions; let location = definitions .get(&reference) .map(|range| { @@ -1922,14 +1929,14 @@ impl LanguageServer for SolangServer { params: GotoTypeDefinitionParams, ) -> Result> { let Some(reference) = self.get_reference_from_params(params).await? else { return Ok(None) }; - let types = self.types.lock().await; + let gc = self.global_cache.lock().await; let di = match &reference.def_type { DefinitionType::Variable(_) | DefinitionType::NonLocalVariable(_, _) | DefinitionType::Field(_, _) | DefinitionType::Variant(_, _) => { - if let Some(def) = types.get(&reference) { + if let Some(def) = gc.types.get(&reference) { def } else { return Ok(None); @@ -1943,8 +1950,8 @@ impl LanguageServer for SolangServer { _ => return Ok(None), }; - let definitions = &self.definitions.lock().await; - let location = definitions + let location = gc + .definitions .get(di) .map(|range| { let uri = Url::from_file_path(&di.def_path).unwrap(); @@ -1961,34 +1968,32 @@ impl LanguageServer for SolangServer { ) -> Result> { let Some(reference) = self.get_reference_from_params(params).await? else { return Ok(None) }; let caches = &self.files.lock().await.caches; + let gc = self.global_cache.lock().await; let impls = match &reference.def_type { DefinitionType::Variable(_) | DefinitionType::NonLocalVariable(_, _) - | DefinitionType::Field(_, _) => { - self.types.lock().await.get(&reference).and_then(|ty| { - if let DefinitionType::Contract(_) = &ty.def_type { - caches - .get(&ty.def_path) - .and_then(|cache| cache.implementations.get(ty)) - } else { - None - } - }) - } + | DefinitionType::Field(_, _) => gc.types.get(&reference).and_then(|ty| { + if let DefinitionType::Contract(_) = &ty.def_type { + caches + .get(&ty.def_path) + .and_then(|cache| cache.implementations.get(ty)) + } else { + None + } + }), DefinitionType::Contract(_) => caches .get(&reference.def_path) .and_then(|cache| cache.implementations.get(&reference)), _ => None, }; - let definitions = self.definitions.lock().await; let impls = impls .map(|impls| { impls .iter() .filter_map(|di| { let path = &di.def_path; - definitions.get(di).map(|range| { + gc.definitions.get(di).map(|range| { let uri = Url::from_file_path(path).unwrap(); Location { uri, range: *range } }) @@ -2025,7 +2030,7 @@ impl LanguageServer for SolangServer { .collect(); if !params.context.include_declaration { - let definitions = self.definitions.lock().await; + let definitions = &self.global_cache.lock().await.definitions; let uri = Url::from_file_path(&reference.def_path).unwrap(); let def = if let Some(range) = definitions.get(&reference) { Location { uri, range: *range } From 87d35db91097127d058ec857b80c21f5f3e53a57 Mon Sep 17 00:00:00 2001 From: Govardhan G D Date: Tue, 5 Sep 2023 10:45:47 +0530 Subject: [PATCH 05/25] rename Signed-off-by: Govardhan G D --- src/bin/languageserver/mod.rs | 38 ++++++++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/src/bin/languageserver/mod.rs b/src/bin/languageserver/mod.rs index 2146c5b4a..e4f3a08d6 100644 --- a/src/bin/languageserver/mod.rs +++ b/src/bin/languageserver/mod.rs @@ -30,10 +30,11 @@ use tower_lsp::{ ExecuteCommandOptions, ExecuteCommandParams, GotoDefinitionParams, GotoDefinitionResponse, Hover, HoverContents, HoverParams, HoverProviderCapability, ImplementationProviderCapability, InitializeParams, InitializeResult, InitializedParams, - Location, MarkedString, MessageType, OneOf, Position, Range, ReferenceParams, + Location, MarkedString, MessageType, OneOf, Position, Range, ReferenceParams, RenameParams, ServerCapabilities, SignatureHelpOptions, TextDocumentContentChangeEvent, - TextDocumentSyncCapability, TextDocumentSyncKind, TypeDefinitionProviderCapability, Url, - WorkspaceFoldersServerCapabilities, WorkspaceServerCapabilities, + TextDocumentSyncCapability, TextDocumentSyncKind, TextEdit, + TypeDefinitionProviderCapability, Url, WorkspaceEdit, WorkspaceFoldersServerCapabilities, + WorkspaceServerCapabilities, }, Client, LanguageServer, LspService, Server, }; @@ -1746,6 +1747,7 @@ impl LanguageServer for SolangServer { type_definition_provider: Some(TypeDefinitionProviderCapability::Simple(true)), implementation_provider: Some(ImplementationProviderCapability::Simple(true)), references_provider: Some(OneOf::Left(true)), + rename_provider: Some(OneOf::Left(true)), ..ServerCapabilities::default() }, }) @@ -2035,6 +2037,36 @@ impl LanguageServer for SolangServer { Ok(Some(locations)) } + + async fn rename(&self, params: RenameParams) -> Result> { + let def_params: GotoDefinitionParams = GotoDefinitionParams { + text_document_position_params: params.text_document_position, + work_done_progress_params: params.work_done_progress_params, + partial_result_params: Default::default(), + }; + let Some(reference) = self.get_reference_from_params(def_params).await? else {return Ok(None)}; + + let new_text = params.new_name; + let caches = &self.files.lock().await.caches; + let ws: HashMap<_, _> = caches + .iter() + .map(|(p, cache)| { + let uri = Url::from_file_path(p).unwrap(); + let text_edits: Vec<_> = cache + .references + .iter() + .filter(|r| r.val == reference) + .map(|r| TextEdit { + range: get_range(r.start, r.stop, &cache.file), + new_text: new_text.clone(), + }) + .collect(); + (uri, text_edits) + }) + .collect(); + + Ok(Some(WorkspaceEdit::new(ws))) + } } /// Calculate the line and column from the Loc offset received from the parser From a2bcd00ca2542cb30162582243ef013bac83d951 Mon Sep 17 00:00:00 2001 From: Govardhan G D Date: Fri, 8 Sep 2023 18:44:39 +0530 Subject: [PATCH 06/25] cargo fmt Signed-off-by: Govardhan G D --- src/bin/languageserver/mod.rs | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/bin/languageserver/mod.rs b/src/bin/languageserver/mod.rs index e4f3a08d6..dd6b7543e 100644 --- a/src/bin/languageserver/mod.rs +++ b/src/bin/languageserver/mod.rs @@ -1904,7 +1904,9 @@ impl LanguageServer for SolangServer { &self, params: GotoDefinitionParams, ) -> Result> { - let Some(reference) = self.get_reference_from_params(params).await? else {return Ok(None)}; + let Some(reference) = self.get_reference_from_params(params).await? else { + return Ok(None); + }; let definitions = &self.global_cache.lock().await.definitions; let location = definitions .get(&reference) @@ -1920,7 +1922,9 @@ impl LanguageServer for SolangServer { &self, params: GotoTypeDefinitionParams, ) -> Result> { - let Some(reference) = self.get_reference_from_params(params).await? else { return Ok(None) }; + let Some(reference) = self.get_reference_from_params(params).await? else { + return Ok(None); + }; let gc = self.global_cache.lock().await; let di = match &reference.def_type { @@ -1958,7 +1962,9 @@ impl LanguageServer for SolangServer { &self, params: GotoImplementationParams, ) -> Result> { - let Some(reference) = self.get_reference_from_params(params).await? else { return Ok(None) }; + let Some(reference) = self.get_reference_from_params(params).await? else { + return Ok(None); + }; let caches = &self.files.lock().await.caches; let gc = self.global_cache.lock().await; let impls = match &reference.def_type { @@ -2003,7 +2009,9 @@ impl LanguageServer for SolangServer { work_done_progress_params: params.work_done_progress_params, partial_result_params: params.partial_result_params, }; - let Some(reference) = self.get_reference_from_params(def_params).await? else {return Ok(None)}; + let Some(reference) = self.get_reference_from_params(def_params).await? else { + return Ok(None); + }; let caches = &self.files.lock().await.caches; let mut locations: Vec<_> = caches @@ -2044,7 +2052,9 @@ impl LanguageServer for SolangServer { work_done_progress_params: params.work_done_progress_params, partial_result_params: Default::default(), }; - let Some(reference) = self.get_reference_from_params(def_params).await? else {return Ok(None)}; + let Some(reference) = self.get_reference_from_params(def_params).await? else { + return Ok(None); + }; let new_text = params.new_name; let caches = &self.files.lock().await.caches; From 5754aa78e831e34fd4a4c202b2dcfbc0136495a3 Mon Sep 17 00:00:00 2001 From: Govardhan G D Date: Mon, 11 Sep 2023 09:21:01 +0530 Subject: [PATCH 07/25] changes to AST Signed-off-by: Govardhan G D --- src/codegen/cfg.rs | 6 ++++++ src/codegen/expression.rs | 7 ++++++- src/sema/ast.rs | 9 ++++++++- src/sema/contracts.rs | 5 ++++- src/sema/mutability.rs | 3 +++ 5 files changed, 27 insertions(+), 3 deletions(-) diff --git a/src/codegen/cfg.rs b/src/codegen/cfg.rs index bcecb16dc..e840cd871 100644 --- a/src/codegen/cfg.rs +++ b/src/codegen/cfg.rs @@ -1439,6 +1439,7 @@ fn is_there_virtual_function( if func.ty == pt::FunctionTy::Receive { // if there is a virtual receive function, and it's not this one, ignore it if let Some(receive) = ns.contracts[contract_no].virtual_functions.get("@receive") { + let receive = receive.last().unwrap(); if Some(*receive) != function_no { return true; } @@ -1448,6 +1449,7 @@ fn is_there_virtual_function( if func.ty == pt::FunctionTy::Fallback { // if there is a virtual fallback function, and it's not this one, ignore it if let Some(fallback) = ns.contracts[contract_no].virtual_functions.get("@fallback") { + let fallback = fallback.last().unwrap(); if Some(*fallback) != function_no { return true; } @@ -1532,6 +1534,9 @@ fn resolve_modifier_call<'a>( // is it a virtual function call let function_no = if let Some(signature) = signature { contract.virtual_functions[signature] + .last() + .copied() + .unwrap() } else { *function_no }; @@ -2125,6 +2130,7 @@ impl Namespace { && self.contracts[contract_no] .virtual_functions .get(&func.signature) + .and_then(|v| v.last()) != function_no.as_ref() { return false; diff --git a/src/codegen/expression.rs b/src/codegen/expression.rs index 82b243355..0275af096 100644 --- a/src/codegen/expression.rs +++ b/src/codegen/expression.rs @@ -490,7 +490,9 @@ pub fn expression( .. } => { let function_no = if let Some(signature) = signature { - &ns.contracts[contract_no].virtual_functions[signature] + ns.contracts[contract_no].virtual_functions[signature] + .last() + .unwrap() } else { function_no }; @@ -2634,6 +2636,9 @@ pub fn emit_function_call( let function_no = if let Some(signature) = signature { ns.contracts[caller_contract_no].virtual_functions[signature] + .last() + .copied() + .unwrap() } else { *function_no }; diff --git a/src/sema/ast.rs b/src/sema/ast.rs index cda906af8..d2694319e 100644 --- a/src/sema/ast.rs +++ b/src/sema/ast.rs @@ -753,7 +753,7 @@ pub struct Contract { pub fixed_layout_size: BigInt, pub functions: Vec, pub all_functions: BTreeMap, - pub virtual_functions: HashMap, + pub virtual_functions: HashMap>, pub yul_functions: Vec, pub variables: Vec, /// List of contracts this contract instantiates @@ -771,6 +771,13 @@ pub struct Contract { pub program_id: Option>, } +impl Contract { + pub fn _virtual_functions(&self, key: &String) -> usize { + let a = &self.virtual_functions[key]; + a[a.len() - 1] + } +} + impl Contract { // Is this a concrete contract, which can be instantiated pub fn is_concrete(&self) -> bool { diff --git a/src/sema/contracts.rs b/src/sema/contracts.rs index f88ff0d97..bdf89fd8f 100644 --- a/src/sema/contracts.rs +++ b/src/sema/contracts.rs @@ -600,7 +600,10 @@ fn check_inheritance(contract_no: usize, ns: &mut ast::Namespace) { if cur.is_override.is_some() || cur.is_virtual { ns.contracts[contract_no] .virtual_functions - .insert(signature, function_no); + .entry(signature) + .or_insert_with(|| vec![]) + .push(function_no); + // .insert(signature, function_no); } ns.contracts[contract_no] diff --git a/src/sema/mutability.rs b/src/sema/mutability.rs index beecdf296..f3bb8ba24 100644 --- a/src/sema/mutability.rs +++ b/src/sema/mutability.rs @@ -170,6 +170,9 @@ fn check_mutability(func: &Function, ns: &Namespace) -> Vec { { let function_no = if let Some(signature) = signature { state.ns.contracts[contract_no].virtual_functions[signature] + .last() + .copied() + .unwrap() } else { *function_no }; From 93f8f4eca6cb6b7a66fb9d13a8506cdf7396c91f Mon Sep 17 00:00:00 2001 From: Govardhan G D Date: Mon, 11 Sep 2023 11:03:00 +0530 Subject: [PATCH 08/25] add goto-declarations functionality Signed-off-by: Govardhan G D --- src/bin/languageserver/mod.rs | 69 +++++++++++++++++++++++++++++++++-- src/sema/contracts.rs | 2 +- 2 files changed, 67 insertions(+), 4 deletions(-) diff --git a/src/bin/languageserver/mod.rs b/src/bin/languageserver/mod.rs index dd6b7543e..aebd9abf2 100644 --- a/src/bin/languageserver/mod.rs +++ b/src/bin/languageserver/mod.rs @@ -20,10 +20,10 @@ use tower_lsp::{ jsonrpc::{Error, ErrorCode, Result}, lsp_types::{ request::{ - GotoImplementationParams, GotoImplementationResponse, GotoTypeDefinitionParams, - GotoTypeDefinitionResponse, + GotoDeclarationParams, GotoDeclarationResponse, GotoImplementationParams, + GotoImplementationResponse, GotoTypeDefinitionParams, GotoTypeDefinitionResponse, }, - CompletionOptions, CompletionParams, CompletionResponse, Diagnostic, + CompletionOptions, CompletionParams, CompletionResponse, DeclarationCapability, Diagnostic, DiagnosticRelatedInformation, DiagnosticSeverity, DidChangeConfigurationParams, DidChangeTextDocumentParams, DidChangeWatchedFilesParams, DidChangeWorkspaceFoldersParams, DidCloseTextDocumentParams, DidOpenTextDocumentParams, DidSaveTextDocumentParams, @@ -92,6 +92,8 @@ type ReferenceEntry = Interval; type Implementations = HashMap>; /// Stores types of code objects type Types = HashMap; +/// Stores all the functions that a given function overrides +type Declarations = HashMap>; #[derive(Debug)] struct FileCache { @@ -99,6 +101,7 @@ struct FileCache { hovers: Lapper, references: Lapper, implementations: Implementations, + declarations: Declarations, } /// Stores information used by language server for every opened file @@ -314,6 +317,7 @@ struct Builder<'a> { // `usize` is the file number the reference belongs to references: Vec<(usize, ReferenceEntry)>, implementations: Vec, + declarations: Vec, types: Vec<(DefinitionIndex, DefinitionIndex)>, ns: &'a ast::Namespace, } @@ -1302,6 +1306,7 @@ impl<'a> Builder<'a> { definitions: HashMap::new(), references: Vec::new(), implementations: vec![HashMap::new(); ns.files.len()], + declarations: vec![HashMap::new(); ns.files.len()], types: Vec::new(), ns, }; @@ -1566,6 +1571,30 @@ impl<'a> Builder<'a> { }) .collect(); builder.implementations[file_no].insert(cdi, impls); + + let decls = contract + .virtual_functions + .iter() + .filter_map(|(_, indices)| { + if indices.len() < 2 { + return None; + } + + let mut functions: Vec<_> = indices + .iter() + .map(|&i| { + let loc = builder.ns.functions[i].loc; + DefinitionIndex { + def_path: builder.ns.files[loc.file_no()].path.clone(), + def_type: DefinitionType::Function(i), + } + }) + .collect(); + + let start = functions.pop().unwrap(); + Some((start, functions)) + }); + builder.declarations[file_no].extend(decls); } for (ei, event) in builder.ns.events.iter().enumerate() { @@ -1653,6 +1682,7 @@ impl<'a> Builder<'a> { .collect(), ), implementations: builder.implementations[i].clone(), + declarations: builder.declarations[i].clone(), }) .collect(); @@ -1746,6 +1776,7 @@ impl LanguageServer for SolangServer { definition_provider: Some(OneOf::Left(true)), type_definition_provider: Some(TypeDefinitionProviderCapability::Simple(true)), implementation_provider: Some(ImplementationProviderCapability::Simple(true)), + declaration_provider: Some(DeclarationCapability::Simple(true)), references_provider: Some(OneOf::Left(true)), rename_provider: Some(OneOf::Left(true)), ..ServerCapabilities::default() @@ -2003,6 +2034,38 @@ impl LanguageServer for SolangServer { Ok(impls) } + async fn goto_declaration( + &self, + params: GotoDeclarationParams, + ) -> Result> { + let Some(reference) = self.get_reference_from_params(params).await? else { + return Ok(None); + }; + + let caches = &self.files.lock().await.caches; + let decls = caches + .get(&reference.def_path) + .and_then(|cache| cache.declarations.get(&reference)); + + let gc = self.global_cache.lock().await; + let decls = decls + .map(|decls| { + decls + .iter() + .filter_map(|di| { + let path = &di.def_path; + gc.definitions.get(di).map(|range| { + let uri = Url::from_file_path(path).unwrap(); + Location { uri, range: *range } + }) + }) + .collect() + }) + .map(GotoImplementationResponse::Array); + + Ok(decls) + } + async fn references(&self, params: ReferenceParams) -> Result>> { let def_params: GotoDefinitionParams = GotoDefinitionParams { text_document_position_params: params.text_document_position, diff --git a/src/sema/contracts.rs b/src/sema/contracts.rs index bdf89fd8f..de706caa2 100644 --- a/src/sema/contracts.rs +++ b/src/sema/contracts.rs @@ -601,7 +601,7 @@ fn check_inheritance(contract_no: usize, ns: &mut ast::Namespace) { ns.contracts[contract_no] .virtual_functions .entry(signature) - .or_insert_with(|| vec![]) + .or_insert_with(Vec::new) .push(function_no); // .insert(signature, function_no); } From 2d5bd1dbc5d4bb77dd68bd8c0a084379e2a41698 Mon Sep 17 00:00:00 2001 From: Govardhan G D Date: Mon, 11 Sep 2023 13:16:55 +0530 Subject: [PATCH 09/25] filter declarations to include just the methods from parent contracts Signed-off-by: Govardhan G D --- src/bin/languageserver/mod.rs | 58 ++++++++++++++++++++++++----------- 1 file changed, 40 insertions(+), 18 deletions(-) diff --git a/src/bin/languageserver/mod.rs b/src/bin/languageserver/mod.rs index aebd9abf2..fbc3cf4d0 100644 --- a/src/bin/languageserver/mod.rs +++ b/src/bin/languageserver/mod.rs @@ -4,17 +4,24 @@ use itertools::Itertools; use num_traits::ToPrimitive; use rust_lapper::{Interval, Lapper}; use serde_json::Value; -use solang::sema::ast::{RetrieveType, StructType, Type}; use solang::{ - codegen::codegen, - codegen::{self, Expression}, + codegen::{self, codegen, Expression}, file_resolver::FileResolver, parse_and_resolve, - sema::{ast, builtin::get_prototype, symtable, tags::render}, + sema::{ + ast::{self, RetrieveType, StructType, Type}, + builtin::get_prototype, + symtable, + tags::render, + }, Target, }; use solang_parser::pt; -use std::{collections::HashMap, ffi::OsString, path::PathBuf}; +use std::{ + collections::{HashMap, HashSet}, + ffi::OsString, + path::PathBuf, +}; use tokio::sync::Mutex; use tower_lsp::{ jsonrpc::{Error, ErrorCode, Result}, @@ -1576,23 +1583,38 @@ impl<'a> Builder<'a> { .virtual_functions .iter() .filter_map(|(_, indices)| { - if indices.len() < 2 { - return None; - } + let func = DefinitionIndex { + def_path: file.path.clone(), + def_type: DefinitionType::Function(indices.last().copied().unwrap()), + }; - let mut functions: Vec<_> = indices + let all_decls: HashSet = HashSet::from_iter(indices.iter().copied()); + let parent_decls = contract + .bases .iter() - .map(|&i| { - let loc = builder.ns.functions[i].loc; - DefinitionIndex { - def_path: builder.ns.files[loc.file_no()].path.clone(), - def_type: DefinitionType::Function(i), - } + .map(|b| { + let p = &builder.ns.contracts[b.contract_no]; + HashSet::from_iter(p.functions.iter().copied()) + .intersection(&all_decls) + .copied() + .collect::>() }) - .collect(); + .reduce(|acc, e| acc.union(&e).copied().collect()); + + parent_decls.map(|parent_decls| { + let decls = parent_decls + .iter() + .map(|&i| { + let loc = builder.ns.functions[i].loc; + DefinitionIndex { + def_path: builder.ns.files[loc.file_no()].path.clone(), + def_type: DefinitionType::Function(i), + } + }) + .collect::>(); - let start = functions.pop().unwrap(); - Some((start, functions)) + (func, decls) + }) }); builder.declarations[file_no].extend(decls); } From 53d2a840a5a3810b20822c73d43c1062f682bc59 Mon Sep 17 00:00:00 2001 From: Govardhan G D Date: Fri, 15 Sep 2023 14:38:33 +0530 Subject: [PATCH 10/25] add doc comments Signed-off-by: Govardhan G D --- src/bin/languageserver/mod.rs | 21 +++++++++++++++++++-- vscode/src/testFixture/refs.sol | 0 2 files changed, 19 insertions(+), 2 deletions(-) delete mode 100644 vscode/src/testFixture/refs.sol diff --git a/src/bin/languageserver/mod.rs b/src/bin/languageserver/mod.rs index 638633af4..a3830bda7 100644 --- a/src/bin/languageserver/mod.rs +++ b/src/bin/languageserver/mod.rs @@ -2136,7 +2136,20 @@ impl LanguageServer for SolangServer { Ok(locations) } + /// Called when "Rename Symbol" is called by the user on the client side. + /// + /// Expected to return a list of changes to be made in user code so that every occurrence of the code object is renamed. + /// + /// ### Arguments + /// * `RenameParams` + /// * provides the source code location (filename, line number, column number) of the code object for which the request was made. + /// * the new symbol that the code object should go by. + /// + /// ### Edge cases + /// * Returns `Err` when an invalid file path is received. + /// * Returns `Ok(None)` when the definition of code object is not found is user code. async fn rename(&self, params: RenameParams) -> Result> { + // fetch the `DefinitionIndex` of the code object in question let def_params: GotoDefinitionParams = GotoDefinitionParams { text_document_position_params: params.text_document_position, work_done_progress_params: params.work_done_progress_params, @@ -2146,9 +2159,13 @@ impl LanguageServer for SolangServer { return Ok(None); }; + // the new name of the code object let new_text = params.new_name; + + // create `TextEdit` instances that represent the changes to be made for every occurrence of the old symbol + // these `TextEdit` objects are then grouped into separate list per source file to which they bolong let caches = &self.files.lock().await.caches; - let ws: HashMap<_, _> = caches + let ws = caches .iter() .map(|(p, cache)| { let uri = Url::from_file_path(p).unwrap(); @@ -2163,7 +2180,7 @@ impl LanguageServer for SolangServer { .collect(); (uri, text_edits) }) - .collect(); + .collect::>(); Ok(Some(WorkspaceEdit::new(ws))) } diff --git a/vscode/src/testFixture/refs.sol b/vscode/src/testFixture/refs.sol deleted file mode 100644 index e69de29bb..000000000 From 68d6d857b1e76c5babecec23bf747f6608d352b6 Mon Sep 17 00:00:00 2001 From: Govardhan G D Date: Fri, 15 Sep 2023 15:34:46 +0530 Subject: [PATCH 11/25] add tests Signed-off-by: Govardhan G D --- vscode/src/test/suite/extension.test.ts | 48 +++++++++++++++++++++++++ vscode/src/testFixture/rename.sol | 12 +++++++ 2 files changed, 60 insertions(+) create mode 100644 vscode/src/testFixture/rename.sol diff --git a/vscode/src/test/suite/extension.test.ts b/vscode/src/test/suite/extension.test.ts index 1aee1d117..8a430b198 100644 --- a/vscode/src/test/suite/extension.test.ts +++ b/vscode/src/test/suite/extension.test.ts @@ -97,6 +97,12 @@ suite('Extension Test Suite', function () { await testrefs(refsdoc1); }); + // Tests for rename + this.timeout(20000); + const renamedoc1 = getDocUri('rename.sol'); + test('Testing for Rename', async () => { + await testrename(renamedoc1); + }); }); function toRange(lineno1: number, charno1: number, lineno2: number, charno2: number) { @@ -356,6 +362,48 @@ async function testrefs(docUri: vscode.Uri) { assert.strictEqual(loc15.uri.path, docUri.path); } +async function testrename(docUri: vscode.Uri) { + await activate(docUri); + + const pos0 = new vscode.Position(9, 8); + const newname0 = "changed"; + const rename0 = (await vscode.commands.executeCommand( + 'vscode.executeDocumentRenameProvider', + docUri, + pos0, + newname0, + )) as vscode.WorkspaceEdit; + + assert(rename0.has(docUri)); + + const loc0 = rename0.get(docUri); + + const loc00 = loc0[0] as vscode.TextEdit; + assert.strictEqual(loc00.range.start.line, 0); + assert.strictEqual(loc00.range.start.character, 41); + assert.strictEqual(loc00.range.end.line, 0); + assert.strictEqual(loc00.range.end.character, 42); + assert.strictEqual(loc00.newText, newname0); + const loc01 = loc0[1] as vscode.TextEdit; + assert.strictEqual(loc01.range.start.line, 1); + assert.strictEqual(loc01.range.start.character, 4); + assert.strictEqual(loc01.range.end.line, 1); + assert.strictEqual(loc01.range.end.character, 6); + assert.strictEqual(loc01.newText, newname0); + const loc02 = loc0[2] as vscode.TextEdit; + assert.strictEqual(loc02.range.start.line, 9); + assert.strictEqual(loc02.range.start.character, 8); + assert.strictEqual(loc02.range.end.line, 9); + assert.strictEqual(loc02.range.end.character, 10); + assert.strictEqual(loc02.newText, newname0); + const loc03 = loc0[3] as vscode.TextEdit; + assert.strictEqual(loc03.range.start.line, 9); + assert.strictEqual(loc03.range.start.character, 12); + assert.strictEqual(loc03.range.end.line, 9); + assert.strictEqual(loc03.range.end.character, 14); + assert.strictEqual(loc03.newText, newname0); +} + async function testhover(docUri: vscode.Uri) { await activate(docUri); diff --git a/vscode/src/testFixture/rename.sol b/vscode/src/testFixture/rename.sol new file mode 100644 index 000000000..a41cb50bd --- /dev/null +++ b/vscode/src/testFixture/rename.sol @@ -0,0 +1,12 @@ +function foo(uint256 n) returns (uint256 d) { + d = 2; + for (;;) { + if (n == 0) { + break; + } + + n = n - 1; + + d = d + 2; + } +} From 439e4c90bd346c84c8dd27c6c29f1cd15ebf72db Mon Sep 17 00:00:00 2001 From: Govardhan G D Date: Sat, 16 Sep 2023 22:03:50 +0530 Subject: [PATCH 12/25] add document formatting Signed-off-by: Govardhan G D --- Cargo.toml | 5 +- forge-fmt/Cargo.toml | 30 + forge-fmt/README.md | 193 + forge-fmt/src/buffer.rs | 447 ++ forge-fmt/src/chunk.rs | 71 + forge-fmt/src/comments.rs | 479 ++ forge-fmt/src/formatter.rs | 3694 +++++++++++++ forge-fmt/src/helpers.rs | 119 + forge-fmt/src/inline_config.rs | 175 + forge-fmt/src/lib.rs | 24 + forge-fmt/src/macros.rs | 125 + forge-fmt/src/solang_ext/ast_eq.rs | 701 +++ forge-fmt/src/solang_ext/loc.rs | 156 + forge-fmt/src/solang_ext/mod.rs | 28 + forge-fmt/src/solang_ext/safe_unwrap.rs | 52 + forge-fmt/src/string.rs | 182 + forge-fmt/src/visit.rs | 655 +++ forge-fmt/testdata/Annotation/fmt.sol | 15 + forge-fmt/testdata/Annotation/original.sol | 15 + forge-fmt/testdata/ArrayExpressions/fmt.sol | 69 + .../testdata/ArrayExpressions/original.sol | 46 + .../ConditionalOperatorExpression/fmt.sol | 37 + .../original.sol | 33 + .../testdata/ConstructorDefinition/fmt.sol | 42 + .../ConstructorDefinition/original.sol | 17 + .../bracket-spacing.fmt.sol | 37 + .../contract-new-lines.fmt.sol | 52 + forge-fmt/testdata/ContractDefinition/fmt.sol | 47 + .../testdata/ContractDefinition/original.sol | 40 + forge-fmt/testdata/DoWhileStatement/fmt.sol | 30 + .../testdata/DoWhileStatement/original.sol | 24 + forge-fmt/testdata/DocComments/fmt.sol | 100 + forge-fmt/testdata/DocComments/original.sol | 95 + .../DocComments/wrap-comments.fmt.sol | 128 + forge-fmt/testdata/EmitStatement/fmt.sol | 31 + forge-fmt/testdata/EmitStatement/original.sol | 24 + .../EnumDefinition/bracket-spacing.fmt.sol | 21 + forge-fmt/testdata/EnumDefinition/fmt.sol | 20 + .../testdata/EnumDefinition/original.sol | 7 + forge-fmt/testdata/ErrorDefinition/fmt.sol | 14 + .../testdata/ErrorDefinition/original.sol | 14 + forge-fmt/testdata/EventDefinition/fmt.sol | 144 + .../testdata/EventDefinition/original.sol | 36 + forge-fmt/testdata/ForStatement/fmt.sol | 37 + forge-fmt/testdata/ForStatement/original.sol | 33 + .../FunctionCall/bracket-spacing.fmt.sol | 37 + forge-fmt/testdata/FunctionCall/fmt.sol | 36 + forge-fmt/testdata/FunctionCall/original.sol | 29 + .../bracket-spacing.fmt.sol | 55 + .../FunctionCallArgsStatement/fmt.sol | 54 + .../FunctionCallArgsStatement/original.sol | 50 + .../testdata/FunctionDefinition/all.fmt.sol | 730 +++ forge-fmt/testdata/FunctionDefinition/fmt.sol | 709 +++ .../testdata/FunctionDefinition/original.sol | 218 + .../override-spacing.fmt.sol | 710 +++ .../FunctionDefinition/params-first.fmt.sol | 710 +++ forge-fmt/testdata/FunctionType/fmt.sol | 31 + forge-fmt/testdata/FunctionType/original.sol | 31 + .../testdata/IfStatement/block-multi.fmt.sol | 171 + .../testdata/IfStatement/block-single.fmt.sol | 123 + forge-fmt/testdata/IfStatement/fmt.sol | 145 + forge-fmt/testdata/IfStatement/original.sol | 119 + forge-fmt/testdata/IfStatement2/fmt.sol | 7 + forge-fmt/testdata/IfStatement2/original.sol | 10 + .../ImportDirective/bracket-spacing.fmt.sol | 21 + forge-fmt/testdata/ImportDirective/fmt.sol | 20 + .../testdata/ImportDirective/original.sol | 10 + .../ImportDirective/preserve-quote.fmt.sol | 21 + .../ImportDirective/single-quote.fmt.sol | 21 + forge-fmt/testdata/InlineDisable/fmt.sol | 491 ++ forge-fmt/testdata/InlineDisable/original.sol | 469 ++ forge-fmt/testdata/IntTypes/fmt.sol | 24 + forge-fmt/testdata/IntTypes/original.sol | 24 + forge-fmt/testdata/IntTypes/preserve.fmt.sol | 25 + forge-fmt/testdata/IntTypes/short.fmt.sol | 25 + forge-fmt/testdata/LiteralExpression/fmt.sol | 59 + .../testdata/LiteralExpression/original.sol | 58 + .../LiteralExpression/preserve-quote.fmt.sol | 60 + .../LiteralExpression/single-quote.fmt.sol | 60 + forge-fmt/testdata/MappingType/fmt.sol | 35 + forge-fmt/testdata/MappingType/original.sol | 23 + forge-fmt/testdata/ModifierDefinition/fmt.sol | 14 + .../testdata/ModifierDefinition/original.sol | 9 + .../override-spacing.fmt.sol | 15 + .../NamedFunctionCallExpression/fmt.sol | 47 + .../NamedFunctionCallExpression/original.sol | 28 + .../testdata/NumberLiteralUnderscore/fmt.sol | 25 + .../NumberLiteralUnderscore/original.sol | 25 + .../NumberLiteralUnderscore/remove.fmt.sol | 26 + .../NumberLiteralUnderscore/thousands.fmt.sol | 26 + .../testdata/OperatorExpressions/fmt.sol | 43 + .../testdata/OperatorExpressions/original.sol | 30 + forge-fmt/testdata/PragmaDirective/fmt.sol | 9 + .../testdata/PragmaDirective/original.sol | 9 + forge-fmt/testdata/Repros/fmt.sol | 7 + forge-fmt/testdata/Repros/original.sol | 7 + forge-fmt/testdata/ReturnStatement/fmt.sol | 66 + .../testdata/ReturnStatement/original.sol | 60 + .../testdata/RevertNamedArgsStatement/fmt.sol | 35 + .../RevertNamedArgsStatement/original.sol | 32 + forge-fmt/testdata/RevertStatement/fmt.sol | 56 + .../testdata/RevertStatement/original.sol | 44 + forge-fmt/testdata/SimpleComments/fmt.sol | 80 + .../testdata/SimpleComments/original.sol | 83 + .../SimpleComments/wrap-comments.fmt.sol | 92 + .../StatementBlock/bracket-spacing.fmt.sol | 20 + forge-fmt/testdata/StatementBlock/fmt.sol | 19 + .../testdata/StatementBlock/original.sol | 17 + .../StructDefinition/bracket-spacing.fmt.sol | 15 + forge-fmt/testdata/StructDefinition/fmt.sol | 14 + .../testdata/StructDefinition/original.sol | 10 + forge-fmt/testdata/ThisExpression/fmt.sol | 20 + .../testdata/ThisExpression/original.sol | 17 + forge-fmt/testdata/TrailingComma/fmt.sol | 12 + forge-fmt/testdata/TrailingComma/original.sol | 12 + forge-fmt/testdata/TryStatement/fmt.sol | 74 + forge-fmt/testdata/TryStatement/original.sol | 66 + forge-fmt/testdata/TypeDefinition/fmt.sol | 12 + .../testdata/TypeDefinition/original.sol | 12 + forge-fmt/testdata/UnitExpression/fmt.sol | 24 + .../testdata/UnitExpression/original.sol | 23 + forge-fmt/testdata/UsingDirective/fmt.sol | 36 + .../testdata/UsingDirective/original.sol | 11 + .../bracket-spacing.fmt.sol | 27 + forge-fmt/testdata/VariableAssignment/fmt.sol | 26 + .../testdata/VariableAssignment/original.sol | 26 + forge-fmt/testdata/VariableDefinition/fmt.sol | 65 + .../testdata/VariableDefinition/original.sol | 27 + .../override-spacing.fmt.sol | 66 + .../WhileStatement/block-multi.fmt.sol | 80 + .../WhileStatement/block-single.fmt.sol | 52 + forge-fmt/testdata/WhileStatement/fmt.sol | 59 + .../testdata/WhileStatement/original.sol | 51 + forge-fmt/testdata/Yul/fmt.sol | 188 + forge-fmt/testdata/Yul/original.sol | 141 + forge-fmt/testdata/YulStrings/fmt.sol | 16 + forge-fmt/testdata/YulStrings/original.sol | 16 + .../YulStrings/preserve-quote.fmt.sol | 17 + .../testdata/YulStrings/single-quote.fmt.sol | 17 + forge-fmt/tests/formatter.rs | 211 + foundry-config/Cargo.toml | 58 + foundry-config/README.md | 302 ++ foundry-config/src/cache.rs | 318 ++ foundry-config/src/chain.rs | 181 + foundry-config/src/doc.rs | 38 + foundry-config/src/endpoints.rs | 175 + foundry-config/src/error.rs | 274 + foundry-config/src/etherscan.rs | 471 ++ foundry-config/src/fix.rs | 344 ++ foundry-config/src/fmt.rs | 124 + foundry-config/src/fs_permissions.rs | 255 + foundry-config/src/fuzz.rs | 170 + foundry-config/src/inline/conf_parser.rs | 212 + foundry-config/src/inline/mod.rs | 85 + foundry-config/src/inline/natspec.rs | 241 + foundry-config/src/invariant.rs | 129 + foundry-config/src/lib.rs | 4630 +++++++++++++++++ foundry-config/src/macros.rs | 239 + foundry-config/src/providers/mod.rs | 158 + foundry-config/src/providers/remappings.rs | 281 + foundry-config/src/resolve.rs | 84 + foundry-config/src/utils.rs | 325 ++ foundry-config/src/warning.rs | 84 + src/bin/languageserver/mod.rs | 34 + vscode/package.json | 5 +- 165 files changed, 25347 insertions(+), 2 deletions(-) create mode 100644 forge-fmt/Cargo.toml create mode 100644 forge-fmt/README.md create mode 100644 forge-fmt/src/buffer.rs create mode 100644 forge-fmt/src/chunk.rs create mode 100644 forge-fmt/src/comments.rs create mode 100644 forge-fmt/src/formatter.rs create mode 100644 forge-fmt/src/helpers.rs create mode 100644 forge-fmt/src/inline_config.rs create mode 100644 forge-fmt/src/lib.rs create mode 100644 forge-fmt/src/macros.rs create mode 100644 forge-fmt/src/solang_ext/ast_eq.rs create mode 100644 forge-fmt/src/solang_ext/loc.rs create mode 100644 forge-fmt/src/solang_ext/mod.rs create mode 100644 forge-fmt/src/solang_ext/safe_unwrap.rs create mode 100644 forge-fmt/src/string.rs create mode 100644 forge-fmt/src/visit.rs create mode 100644 forge-fmt/testdata/Annotation/fmt.sol create mode 100644 forge-fmt/testdata/Annotation/original.sol create mode 100644 forge-fmt/testdata/ArrayExpressions/fmt.sol create mode 100644 forge-fmt/testdata/ArrayExpressions/original.sol create mode 100644 forge-fmt/testdata/ConditionalOperatorExpression/fmt.sol create mode 100644 forge-fmt/testdata/ConditionalOperatorExpression/original.sol create mode 100644 forge-fmt/testdata/ConstructorDefinition/fmt.sol create mode 100644 forge-fmt/testdata/ConstructorDefinition/original.sol create mode 100644 forge-fmt/testdata/ContractDefinition/bracket-spacing.fmt.sol create mode 100644 forge-fmt/testdata/ContractDefinition/contract-new-lines.fmt.sol create mode 100644 forge-fmt/testdata/ContractDefinition/fmt.sol create mode 100644 forge-fmt/testdata/ContractDefinition/original.sol create mode 100644 forge-fmt/testdata/DoWhileStatement/fmt.sol create mode 100644 forge-fmt/testdata/DoWhileStatement/original.sol create mode 100644 forge-fmt/testdata/DocComments/fmt.sol create mode 100644 forge-fmt/testdata/DocComments/original.sol create mode 100644 forge-fmt/testdata/DocComments/wrap-comments.fmt.sol create mode 100644 forge-fmt/testdata/EmitStatement/fmt.sol create mode 100644 forge-fmt/testdata/EmitStatement/original.sol create mode 100644 forge-fmt/testdata/EnumDefinition/bracket-spacing.fmt.sol create mode 100644 forge-fmt/testdata/EnumDefinition/fmt.sol create mode 100644 forge-fmt/testdata/EnumDefinition/original.sol create mode 100644 forge-fmt/testdata/ErrorDefinition/fmt.sol create mode 100644 forge-fmt/testdata/ErrorDefinition/original.sol create mode 100644 forge-fmt/testdata/EventDefinition/fmt.sol create mode 100644 forge-fmt/testdata/EventDefinition/original.sol create mode 100644 forge-fmt/testdata/ForStatement/fmt.sol create mode 100644 forge-fmt/testdata/ForStatement/original.sol create mode 100644 forge-fmt/testdata/FunctionCall/bracket-spacing.fmt.sol create mode 100644 forge-fmt/testdata/FunctionCall/fmt.sol create mode 100644 forge-fmt/testdata/FunctionCall/original.sol create mode 100644 forge-fmt/testdata/FunctionCallArgsStatement/bracket-spacing.fmt.sol create mode 100644 forge-fmt/testdata/FunctionCallArgsStatement/fmt.sol create mode 100644 forge-fmt/testdata/FunctionCallArgsStatement/original.sol create mode 100644 forge-fmt/testdata/FunctionDefinition/all.fmt.sol create mode 100644 forge-fmt/testdata/FunctionDefinition/fmt.sol create mode 100644 forge-fmt/testdata/FunctionDefinition/original.sol create mode 100644 forge-fmt/testdata/FunctionDefinition/override-spacing.fmt.sol create mode 100644 forge-fmt/testdata/FunctionDefinition/params-first.fmt.sol create mode 100644 forge-fmt/testdata/FunctionType/fmt.sol create mode 100644 forge-fmt/testdata/FunctionType/original.sol create mode 100644 forge-fmt/testdata/IfStatement/block-multi.fmt.sol create mode 100644 forge-fmt/testdata/IfStatement/block-single.fmt.sol create mode 100644 forge-fmt/testdata/IfStatement/fmt.sol create mode 100644 forge-fmt/testdata/IfStatement/original.sol create mode 100644 forge-fmt/testdata/IfStatement2/fmt.sol create mode 100644 forge-fmt/testdata/IfStatement2/original.sol create mode 100644 forge-fmt/testdata/ImportDirective/bracket-spacing.fmt.sol create mode 100644 forge-fmt/testdata/ImportDirective/fmt.sol create mode 100644 forge-fmt/testdata/ImportDirective/original.sol create mode 100644 forge-fmt/testdata/ImportDirective/preserve-quote.fmt.sol create mode 100644 forge-fmt/testdata/ImportDirective/single-quote.fmt.sol create mode 100644 forge-fmt/testdata/InlineDisable/fmt.sol create mode 100644 forge-fmt/testdata/InlineDisable/original.sol create mode 100644 forge-fmt/testdata/IntTypes/fmt.sol create mode 100644 forge-fmt/testdata/IntTypes/original.sol create mode 100644 forge-fmt/testdata/IntTypes/preserve.fmt.sol create mode 100644 forge-fmt/testdata/IntTypes/short.fmt.sol create mode 100644 forge-fmt/testdata/LiteralExpression/fmt.sol create mode 100644 forge-fmt/testdata/LiteralExpression/original.sol create mode 100644 forge-fmt/testdata/LiteralExpression/preserve-quote.fmt.sol create mode 100644 forge-fmt/testdata/LiteralExpression/single-quote.fmt.sol create mode 100644 forge-fmt/testdata/MappingType/fmt.sol create mode 100644 forge-fmt/testdata/MappingType/original.sol create mode 100644 forge-fmt/testdata/ModifierDefinition/fmt.sol create mode 100644 forge-fmt/testdata/ModifierDefinition/original.sol create mode 100644 forge-fmt/testdata/ModifierDefinition/override-spacing.fmt.sol create mode 100644 forge-fmt/testdata/NamedFunctionCallExpression/fmt.sol create mode 100644 forge-fmt/testdata/NamedFunctionCallExpression/original.sol create mode 100644 forge-fmt/testdata/NumberLiteralUnderscore/fmt.sol create mode 100644 forge-fmt/testdata/NumberLiteralUnderscore/original.sol create mode 100644 forge-fmt/testdata/NumberLiteralUnderscore/remove.fmt.sol create mode 100644 forge-fmt/testdata/NumberLiteralUnderscore/thousands.fmt.sol create mode 100644 forge-fmt/testdata/OperatorExpressions/fmt.sol create mode 100644 forge-fmt/testdata/OperatorExpressions/original.sol create mode 100644 forge-fmt/testdata/PragmaDirective/fmt.sol create mode 100644 forge-fmt/testdata/PragmaDirective/original.sol create mode 100644 forge-fmt/testdata/Repros/fmt.sol create mode 100644 forge-fmt/testdata/Repros/original.sol create mode 100644 forge-fmt/testdata/ReturnStatement/fmt.sol create mode 100644 forge-fmt/testdata/ReturnStatement/original.sol create mode 100644 forge-fmt/testdata/RevertNamedArgsStatement/fmt.sol create mode 100644 forge-fmt/testdata/RevertNamedArgsStatement/original.sol create mode 100644 forge-fmt/testdata/RevertStatement/fmt.sol create mode 100644 forge-fmt/testdata/RevertStatement/original.sol create mode 100644 forge-fmt/testdata/SimpleComments/fmt.sol create mode 100644 forge-fmt/testdata/SimpleComments/original.sol create mode 100644 forge-fmt/testdata/SimpleComments/wrap-comments.fmt.sol create mode 100644 forge-fmt/testdata/StatementBlock/bracket-spacing.fmt.sol create mode 100644 forge-fmt/testdata/StatementBlock/fmt.sol create mode 100644 forge-fmt/testdata/StatementBlock/original.sol create mode 100644 forge-fmt/testdata/StructDefinition/bracket-spacing.fmt.sol create mode 100644 forge-fmt/testdata/StructDefinition/fmt.sol create mode 100644 forge-fmt/testdata/StructDefinition/original.sol create mode 100644 forge-fmt/testdata/ThisExpression/fmt.sol create mode 100644 forge-fmt/testdata/ThisExpression/original.sol create mode 100644 forge-fmt/testdata/TrailingComma/fmt.sol create mode 100644 forge-fmt/testdata/TrailingComma/original.sol create mode 100644 forge-fmt/testdata/TryStatement/fmt.sol create mode 100644 forge-fmt/testdata/TryStatement/original.sol create mode 100644 forge-fmt/testdata/TypeDefinition/fmt.sol create mode 100644 forge-fmt/testdata/TypeDefinition/original.sol create mode 100644 forge-fmt/testdata/UnitExpression/fmt.sol create mode 100644 forge-fmt/testdata/UnitExpression/original.sol create mode 100644 forge-fmt/testdata/UsingDirective/fmt.sol create mode 100644 forge-fmt/testdata/UsingDirective/original.sol create mode 100644 forge-fmt/testdata/VariableAssignment/bracket-spacing.fmt.sol create mode 100644 forge-fmt/testdata/VariableAssignment/fmt.sol create mode 100644 forge-fmt/testdata/VariableAssignment/original.sol create mode 100644 forge-fmt/testdata/VariableDefinition/fmt.sol create mode 100644 forge-fmt/testdata/VariableDefinition/original.sol create mode 100644 forge-fmt/testdata/VariableDefinition/override-spacing.fmt.sol create mode 100644 forge-fmt/testdata/WhileStatement/block-multi.fmt.sol create mode 100644 forge-fmt/testdata/WhileStatement/block-single.fmt.sol create mode 100644 forge-fmt/testdata/WhileStatement/fmt.sol create mode 100644 forge-fmt/testdata/WhileStatement/original.sol create mode 100644 forge-fmt/testdata/Yul/fmt.sol create mode 100644 forge-fmt/testdata/Yul/original.sol create mode 100644 forge-fmt/testdata/YulStrings/fmt.sol create mode 100644 forge-fmt/testdata/YulStrings/original.sol create mode 100644 forge-fmt/testdata/YulStrings/preserve-quote.fmt.sol create mode 100644 forge-fmt/testdata/YulStrings/single-quote.fmt.sol create mode 100644 forge-fmt/tests/formatter.rs create mode 100644 foundry-config/Cargo.toml create mode 100644 foundry-config/README.md create mode 100644 foundry-config/src/cache.rs create mode 100644 foundry-config/src/chain.rs create mode 100644 foundry-config/src/doc.rs create mode 100644 foundry-config/src/endpoints.rs create mode 100644 foundry-config/src/error.rs create mode 100644 foundry-config/src/etherscan.rs create mode 100644 foundry-config/src/fix.rs create mode 100644 foundry-config/src/fmt.rs create mode 100644 foundry-config/src/fs_permissions.rs create mode 100644 foundry-config/src/fuzz.rs create mode 100644 foundry-config/src/inline/conf_parser.rs create mode 100644 foundry-config/src/inline/mod.rs create mode 100644 foundry-config/src/inline/natspec.rs create mode 100644 foundry-config/src/invariant.rs create mode 100644 foundry-config/src/lib.rs create mode 100644 foundry-config/src/macros.rs create mode 100644 foundry-config/src/providers/mod.rs create mode 100644 foundry-config/src/providers/remappings.rs create mode 100644 foundry-config/src/resolve.rs create mode 100644 foundry-config/src/utils.rs create mode 100644 foundry-config/src/warning.rs diff --git a/Cargo.toml b/Cargo.toml index 98cda6185..56a01791d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -67,6 +67,9 @@ primitive-types = { version = "0.12", features = ["codec"] } normalize-path = "0.2.1" bitflags = "2.3.3" +forge-fmt = {path = "forge-fmt"} +foundry-config = {path = "foundry-config"} + [dev-dependencies] num-derive = "0.4" wasmi = "0.31" @@ -98,4 +101,4 @@ llvm = ["inkwell", "libc"] wasm_opt = ["llvm", "wasm-opt", "contract-build"] [workspace] -members = ["solang-parser", "tests/wasm_host_attr"] +members = ["solang-parser", "tests/wasm_host_attr", "forge-fmt", "foundry-config"] diff --git a/forge-fmt/Cargo.toml b/forge-fmt/Cargo.toml new file mode 100644 index 000000000..fbb6a6340 --- /dev/null +++ b/forge-fmt/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "forge-fmt" +version = "0.3.2" +authors = ["Sean Young ", "Lucas Steuernagel ", "Cyrill Leutwiler "] +homepage = "https://github.com/hyperledger/solang" +documentation = "https://solang.readthedocs.io/" +license = "Apache-2.0" +edition = "2021" +# version.workspace = true +# edition.workspace = true +# rust-version.workspace = true +# authors.workspace = true +# license.workspace = true +# homepage.workspace = true +# repository.workspace = true + +[dependencies] +foundry-config ={path = "../foundry-config"} +ethers-core = { git = "https://github.com/gakonst/ethers-rs", default-features = false } +solang-parser = {path = "../solang-parser"} +itertools = "0.11" +thiserror = "1" +ariadne = "0.2" +tracing = "0.1" + +[dev-dependencies] +pretty_assertions = "1" +itertools = "0.11" +toml = "0.7" +tracing-subscriber = { version = "0.3", features = ["env-filter"] } diff --git a/forge-fmt/README.md b/forge-fmt/README.md new file mode 100644 index 000000000..7e51bf7f8 --- /dev/null +++ b/forge-fmt/README.md @@ -0,0 +1,193 @@ +# Formatter (`fmt`) + +Solidity formatter that respects (some parts of) the [Style Guide](https://docs.soliditylang.org/en/latest/style-guide.html) and +is tested on the [Prettier Solidity Plugin](https://github.com/prettier-solidity/prettier-plugin-solidity) cases. + +## Architecture + +The formatter works in two steps: + +1. Parse Solidity source code with [solang](https://github.com/hyperledger-labs/solang) into the PT (Parse Tree) + (not the same as Abstract Syntax Tree, [see difference](https://stackoverflow.com/a/9864571)). +2. Walk the PT and output new source code that's compliant with provided config and rule set. + +The technique for walking the tree is based on [Visitor Pattern](https://en.wikipedia.org/wiki/Visitor_pattern) +and works as following: + +1. Implement `Formatter` callback functions for each PT node type. + Every callback function should write formatted output for the current node + and call `Visitable::visit` function for child nodes delegating the output writing. +1. Implement `Visitable` trait and its `visit` function for each PT node type. Every `visit` function should call corresponding `Formatter`'s callback function. + +### Output + +The formatted output is written into the output buffer in _chunks_. The `Chunk` struct holds the content to be written & metadata for it. This includes the comments surrounding the content as well as the `needs_space` flag specifying whether this _chunk_ needs a space. The flag overrides the default behavior of `Formatter::next_char_needs_space` method. + +The content gets written into the `FormatBuffer` which contains the information about the current indentation level, indentation length, current state as well as the other data determining the rules for writing the content. `FormatBuffer` implements the `std::fmt::Write` trait where it evaluates the current information and decides how the content should be written to the destination. + +### Comments + +The solang parser does not output comments as a type of parse tree node, but rather +in a list alongside the parse tree with location information. It is therefore necessary +to infer where to insert the comments and how to format them while traversing the parse tree. + +To handle this, the formatter pre-parses the comments and puts them into two categories: +Prefix and Postfix comments. Prefix comments refer to the node directly after them, and +postfix comments refer to the node before them. As an illustration: + +```solidity +// This is a prefix comment +/* This is also a prefix comment */ +uint variable = 1 + 2; /* this is postfix */ // this is postfix too + // and this is a postfix comment on the next line +``` + +To insert the comments into the appropriate areas, strings get converted to chunks +before being written to the buffer. A chunk is any string that cannot be split by +whitespace. A chunk also carries with it the surrounding comment information. Thereby +when writing the chunk the comments can be added before and after the chunk as well +as any any whitespace surrounding. + +To construct a chunk, the string and the location of the string is given to the +Formatter and the pre-parsed comments before the start and end of the string are +associated with that string. The source code can then further be chunked before the +chunks are written to the buffer. + +To write the chunk, first the comments associated with the start of the chunk get +written to the buffer. Then the Formatter checks if any whitespace is needed between +what's been written to the buffer and what's in the chunk and inserts it where appropriate. +If the chunk content fits on the same line, it will be written directly to the buffer, +otherwise it will be written on the next line. Finally, any associated postfix +comments also get written. + +### Example + +Source code + +```solidity +pragma solidity ^0.8.10 ; +contract HelloWorld { + string public message; + constructor( string memory initMessage) { message = initMessage;} +} + + +event Greet( string indexed name) ; +``` + +Parse Tree (simplified) + +```text +SourceUnit + | PragmaDirective("solidity", "^0.8.10") + | ContractDefinition("HelloWorld") + | VariableDefinition("string", "message", null, ["public"]) + | FunctionDefinition("constructor") + | Parameter("string", "initMessage", ["memory"]) + | EventDefinition("string", "Greet", ["indexed"], ["name"]) +``` + +Formatted source code that was reconstructed from the Parse Tree + +```solidity +pragma solidity ^0.8.10; + +contract HelloWorld { + string public message; + + constructor(string memory initMessage) { + message = initMessage; + } +} + +event Greet(string indexed name); +``` + +### Configuration + +The formatter supports multiple configuration options defined in `FormatterConfig`. + +| Option | Default | Description | +| -------------------------------- | -------- | ---------------------------------------------------------------------------------------------- | +| line_length | 120 | Maximum line length where formatter will try to wrap the line | +| tab_width | 4 | Number of spaces per indentation level | +| bracket_spacing | false | Print spaces between brackets | +| int_types | long | Style of uint/int256 types. Available options: `long`, `short`, `preserve` | +| func_attrs_with_params_multiline | true | If function parameters are multiline then always put the function attributes on separate lines | +| quote_style | double | Style of quotation marks. Available options: `double`, `single`, `preserve` | +| number_underscore | preserve | Style of underscores in number literals. Available options: `remove`, `thousands`, `preserve` | + +TODO: update ^ + +### Disable Line + +The formatter can be disabled on specific lines by adding a comment `// forgefmt: disable-next-line`, like this: + +```solidity +// forgefmt: disable-next-line +uint x = 100; +``` + +Alternatively, the comment can also be placed at the end of the line. In this case, you'd have to use `disable-line` instead: + +```solidity +uint x = 100; // forgefmt: disable-line +``` + +### Testing + +Tests reside under the `fmt/testdata` folder and specify the malformatted & expected Solidity code. The source code file is named `original.sol` and expected file(s) are named in a format `({prefix}.)?fmt.sol`. Multiple expected files are needed for tests covering available configuration options. + +The default configuration values can be overridden from within the expected file by adding a comment in the format `// config: {config_entry} = {config_value}`. For example: + +```solidity +// config: line_length = 160 +``` + +The `test_directory` macro is used to specify a new folder with source files for the test suite. Each test suite has the following process: + +1. Preparse comments with config values +2. Parse and compare the AST for source & expected files. + - The `AstEq` trait defines the comparison rules for the AST nodes +3. Format the source file and assert the equality of the output with the expected file. +4. Format the expected files and assert the idempotency of the formatting operation. + +## Contributing + +Check out the [foundry contribution guide](https://github.com/foundry-rs/foundry/blob/master/CONTRIBUTING.md). + +Guidelines for contributing to `forge fmt`: + +### Opening an issue + +1. Create a short concise title describing an issue. + - Bad Title Examples + ```text + Forge fmt does not work + Forge fmt breaks + Forge fmt unexpected behavior + ``` + - Good Title Examples + ```text + Forge fmt postfix comment misplaced + Forge fmt does not inline short yul blocks + ``` +2. Fill in the issue template fields that include foundry version, platform & component info. +3. Provide the code snippets showing the current & expected behaviors. +4. If it's a feature request, specify why this feature is needed. +5. Besides the default label (`T-Bug` for bugs or `T-feature` for features), add `C-forge` and `Cmd-forge-fmt` labels. + +### Fixing A Bug + +1. Specify an issue that is being addressed in the PR description. +2. Add a note on the solution in the PR description. +3. Make sure the PR includes the acceptance test(s). + +### Developing A Feature + +1. Specify an issue that is being addressed in the PR description. +2. Add a note on the solution in the PR description. +3. Provide the test coverage for the new feature. These should include: + - Adding malformatted & expected solidity code under `fmt/testdata/$dir/` + - Testing the behavior of pre and postfix comments + - If it's a new config value, tests covering **all** available options diff --git a/forge-fmt/src/buffer.rs b/forge-fmt/src/buffer.rs new file mode 100644 index 000000000..ca946dae4 --- /dev/null +++ b/forge-fmt/src/buffer.rs @@ -0,0 +1,447 @@ +//! Format buffer + +use std::fmt::Write; + +use crate::{ + comments::{CommentState, CommentStringExt}, + string::{QuoteState, QuotedStringExt}, +}; + +/// An indent group. The group may optionally skip the first line +#[derive(Default, Clone, Debug)] +struct IndentGroup { + skip_line: bool, +} + +#[derive(Clone, Copy, Debug)] +enum WriteState { + LineStart(CommentState), + WriteTokens(CommentState), + WriteString(char), +} + +impl WriteState { + fn comment_state(&self) -> CommentState { + match self { + WriteState::LineStart(state) => *state, + WriteState::WriteTokens(state) => *state, + WriteState::WriteString(_) => CommentState::None, + } + } +} + +impl Default for WriteState { + fn default() -> Self { + WriteState::LineStart(CommentState::default()) + } +} + +/// A wrapper around a `std::fmt::Write` interface. The wrapper keeps track of indentation as well +/// as information about the last `write_str` command if available. The formatter may also be +/// restricted to a single line, in which case it will throw an error on a newline +#[derive(Clone, Debug)] +pub struct FormatBuffer { + pub w: W, + indents: Vec, + base_indent_len: usize, + tab_width: usize, + last_char: Option, + current_line_len: usize, + restrict_to_single_line: bool, + state: WriteState, +} + +impl FormatBuffer { + pub fn new(w: W, tab_width: usize) -> Self { + Self { + w, + tab_width, + base_indent_len: 0, + indents: vec![], + current_line_len: 0, + last_char: None, + restrict_to_single_line: false, + state: WriteState::default(), + } + } + + /// Create a new temporary buffer based on an existing buffer which retains information about + /// the buffer state, but has a blank String as its underlying `Write` interface + pub fn create_temp_buf(&self) -> FormatBuffer { + let mut new = FormatBuffer::new(String::new(), self.tab_width); + new.base_indent_len = self.total_indent_len(); + new.current_line_len = self.current_line_len(); + new.last_char = self.last_char; + new.restrict_to_single_line = self.restrict_to_single_line; + new.state = match self.state { + WriteState::WriteTokens(state) | WriteState::LineStart(state) => { + WriteState::LineStart(state) + } + WriteState::WriteString(ch) => WriteState::WriteString(ch), + }; + new + } + + /// Restrict the buffer to a single line + pub fn restrict_to_single_line(&mut self, restricted: bool) { + self.restrict_to_single_line = restricted; + } + + /// Indent the buffer by delta + pub fn indent(&mut self, delta: usize) { + self.indents + .extend(std::iter::repeat(IndentGroup::default()).take(delta)); + } + + /// Dedent the buffer by delta + pub fn dedent(&mut self, delta: usize) { + self.indents.truncate(self.indents.len() - delta); + } + + /// Get the current level of the indent. This is multiplied by the tab width to get the + /// resulting indent + fn level(&self) -> usize { + self.indents.iter().filter(|i| !i.skip_line).count() + } + + /// Check if the last indent group is being skipped + pub fn last_indent_group_skipped(&self) -> bool { + self.indents.last().map(|i| i.skip_line).unwrap_or(false) + } + + /// Set whether the last indent group should be skipped + pub fn set_last_indent_group_skipped(&mut self, skip_line: bool) { + if let Some(i) = self.indents.last_mut() { + i.skip_line = skip_line + } + } + + /// Get the current indent size (level * tab_width) + pub fn current_indent_len(&self) -> usize { + self.level() * self.tab_width + } + + /// Get the total indent size + pub fn total_indent_len(&self) -> usize { + self.current_indent_len() + self.base_indent_len + } + + /// Get the current written position (this does not include the indent size) + pub fn current_line_len(&self) -> usize { + self.current_line_len + } + + /// Check if the buffer is at the beginning of a new line + pub fn is_beginning_of_line(&self) -> bool { + matches!(self.state, WriteState::LineStart(_)) + } + + /// Start a new indent group (skips first indent) + pub fn start_group(&mut self) { + self.indents.push(IndentGroup { skip_line: true }); + } + + /// End the last indent group + pub fn end_group(&mut self) { + self.indents.pop(); + } + + /// Get the last char written to the buffer + pub fn last_char(&self) -> Option { + self.last_char + } + + /// When writing a newline apply state changes + fn handle_newline(&mut self, mut comment_state: CommentState) { + if comment_state == CommentState::Line { + comment_state = CommentState::None; + } + self.current_line_len = 0; + self.set_last_indent_group_skipped(false); + self.last_char = Some('\n'); + self.state = WriteState::LineStart(comment_state); + } +} + +impl FormatBuffer { + /// Write a raw string to the buffer. This will ignore indents and remove the indents of the + /// written string to match the current base indent of this buffer if it is a temp buffer + pub fn write_raw(&mut self, s: impl AsRef) -> std::fmt::Result { + let mut lines = s.as_ref().lines().peekable(); + let mut comment_state = self.state.comment_state(); + while let Some(line) = lines.next() { + // remove the whitespace that covered by the base indent length (this is normally the + // case with temporary buffers as this will be readded by the underlying IndentWriter + // later on + let (new_comment_state, line_start) = line + .comment_state_char_indices() + .with_state(comment_state) + .take(self.base_indent_len) + .take_while(|(_, _, ch)| ch.is_whitespace()) + .last() + .map(|(state, idx, _)| (state, idx + 1)) + .unwrap_or((comment_state, 0)); + comment_state = new_comment_state; + let trimmed_line = &line[line_start..]; + if !trimmed_line.is_empty() { + self.w.write_str(trimmed_line)?; + self.current_line_len += trimmed_line.len(); + self.last_char = trimmed_line.chars().next_back(); + self.state = WriteState::WriteTokens(comment_state); + } + if lines.peek().is_some() || s.as_ref().ends_with('\n') { + if self.restrict_to_single_line { + return Err(std::fmt::Error); + } + self.w.write_char('\n')?; + self.handle_newline(comment_state); + } + } + Ok(()) + } +} + +impl Write for FormatBuffer { + fn write_str(&mut self, mut s: &str) -> std::fmt::Result { + if s.is_empty() { + return Ok(()); + } + + let mut indent = " ".repeat(self.current_indent_len()); + + loop { + match self.state { + WriteState::LineStart(mut comment_state) => { + match s.find(|b| b != '\n') { + // No non-empty lines in input, write the entire string (only newlines) + None => { + if !s.is_empty() { + self.w.write_str(s)?; + self.handle_newline(comment_state); + } + break; + } + + // We can see the next non-empty line. Write up to the + // beginning of that line, then insert an indent, then + // continue. + Some(len) => { + let (head, tail) = s.split_at(len); + self.w.write_str(head)?; + self.w.write_str(&indent)?; + self.current_line_len = 0; + self.last_char = Some(' '); + // a newline has been inserted + if len > 0 { + if self.last_indent_group_skipped() { + indent = " ".repeat(self.current_indent_len() + self.tab_width); + self.set_last_indent_group_skipped(false); + } + if comment_state == CommentState::Line { + comment_state = CommentState::None; + } + } + s = tail; + self.state = WriteState::WriteTokens(comment_state); + } + } + } + WriteState::WriteTokens(comment_state) => { + if s.is_empty() { + break; + } + + // find the next newline or non-comment string separator (e.g. ' or ") + let mut len = 0; + let mut new_state = WriteState::WriteTokens(comment_state); + for (state, idx, ch) in s.comment_state_char_indices().with_state(comment_state) + { + len = idx; + if ch == '\n' { + if self.restrict_to_single_line { + return Err(std::fmt::Error); + } + new_state = WriteState::LineStart(state); + break; + } else if state == CommentState::None && (ch == '\'' || ch == '"') { + new_state = WriteState::WriteString(ch); + break; + } else { + new_state = WriteState::WriteTokens(state); + } + } + + if matches!(new_state, WriteState::WriteTokens(_)) { + // No newlines or strings found, write the entire string + self.w.write_str(s)?; + self.current_line_len += s.len(); + self.last_char = s.chars().next_back(); + self.state = new_state; + break; + } else { + // A newline or string has been found. Write up to that character and + // continue on the tail + let (head, tail) = s.split_at(len + 1); + self.w.write_str(head)?; + s = tail; + match new_state { + WriteState::LineStart(comment_state) => { + self.handle_newline(comment_state) + } + new_state => { + self.current_line_len += head.len(); + self.last_char = head.chars().next_back(); + self.state = new_state; + } + } + } + } + WriteState::WriteString(quote) => { + match s + .quoted_ranges() + .with_state(QuoteState::String(quote)) + .next() + { + // No end found, write the rest of the string + None => { + self.w.write_str(s)?; + self.current_line_len += s.len(); + self.last_char = s.chars().next_back(); + break; + } + // String end found, write the string and continue to add tokens after + Some((_, _, len)) => { + let (head, tail) = s.split_at(len + 1); + self.w.write_str(head)?; + if let Some((_, last)) = head.rsplit_once('\n') { + self.set_last_indent_group_skipped(false); + self.current_line_len = last.len(); + } else { + self.current_line_len += head.len(); + } + self.last_char = Some(quote); + s = tail; + self.state = WriteState::WriteTokens(CommentState::None); + } + } + } + } + } + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + const TAB_WIDTH: usize = 4; + + #[test] + fn test_buffer_indents() -> std::fmt::Result { + let delta = 1; + + let mut buf = FormatBuffer::new(String::new(), TAB_WIDTH); + assert_eq!(buf.indents.len(), 0); + assert_eq!(buf.level(), 0); + assert_eq!(buf.current_indent_len(), 0); + + buf.indent(delta); + assert_eq!(buf.indents.len(), delta); + assert_eq!(buf.level(), delta); + assert_eq!(buf.current_indent_len(), delta * TAB_WIDTH); + + buf.indent(delta); + buf.set_last_indent_group_skipped(true); + assert!(buf.last_indent_group_skipped()); + assert_eq!(buf.indents.len(), delta * 2); + assert_eq!(buf.level(), delta); + assert_eq!(buf.current_indent_len(), delta * TAB_WIDTH); + buf.dedent(delta); + + buf.dedent(delta); + assert_eq!(buf.indents.len(), 0); + assert_eq!(buf.level(), 0); + assert_eq!(buf.current_indent_len(), 0); + + // panics on extra dedent + let res = std::panic::catch_unwind(|| buf.clone().dedent(delta)); + assert!(res.is_err()); + + Ok(()) + } + + #[test] + fn test_identical_temp_buf() -> std::fmt::Result { + let content = "test string"; + let multiline_content = "test\nmultiline\nmultiple"; + let mut buf = FormatBuffer::new(String::new(), TAB_WIDTH); + + // create identical temp buf + let mut temp = buf.create_temp_buf(); + writeln!(buf, "{content}")?; + writeln!(temp, "{content}")?; + assert_eq!(buf.w, format!("{content}\n")); + assert_eq!(temp.w, buf.w); + assert_eq!(temp.current_line_len, buf.current_line_len); + assert_eq!(temp.base_indent_len, buf.total_indent_len()); + + let delta = 1; + buf.indent(delta); + + let mut temp_indented = buf.create_temp_buf(); + assert!(temp_indented.w.is_empty()); + assert_eq!(temp_indented.base_indent_len, buf.total_indent_len()); + assert_eq!(temp_indented.level() + delta, buf.level()); + + let indent = " ".repeat(delta * TAB_WIDTH); + + let mut original_buf = buf.clone(); + write!(buf, "{multiline_content}")?; + let expected_content = format!( + "{}\n{}{}", + content, + indent, + multiline_content + .lines() + .collect::>() + .join(&format!("\n{indent}")) + ); + assert_eq!(buf.w, expected_content); + + write!(temp_indented, "{multiline_content}")?; + + // write temp buf to original and assert the result + write!(original_buf, "{}", temp_indented.w)?; + assert_eq!(buf.w, original_buf.w); + + Ok(()) + } + + #[test] + fn test_preserves_original_content_with_default_settings() -> std::fmt::Result { + let contents = [ + "simple line", + r#" + some + multiline + content"#, + "// comment", + "/* comment */", + r#"mutliline + content + // comment1 + with comments + /* comment2 */ "#, + ]; + + for content in contents.iter() { + let mut buf = FormatBuffer::new(String::new(), TAB_WIDTH); + write!(buf, "{content}")?; + assert_eq!(&buf.w, content); + } + + Ok(()) + } +} diff --git a/forge-fmt/src/chunk.rs b/forge-fmt/src/chunk.rs new file mode 100644 index 000000000..9d069ffdf --- /dev/null +++ b/forge-fmt/src/chunk.rs @@ -0,0 +1,71 @@ +use crate::comments::CommentWithMetadata; + +/// Holds information about a non-whitespace-splittable string, and the surrounding comments +#[derive(Clone, Debug, Default)] +pub struct Chunk { + pub postfixes_before: Vec, + pub prefixes: Vec, + pub content: String, + pub postfixes: Vec, + pub needs_space: Option, +} + +impl From for Chunk { + fn from(string: String) -> Self { + Chunk { + content: string, + ..Default::default() + } + } +} + +impl From<&str> for Chunk { + fn from(string: &str) -> Self { + Chunk { + content: string.to_owned(), + ..Default::default() + } + } +} + +// The struct with information about chunks used in the [Formatter::surrounded] method +#[derive(Debug)] +pub struct SurroundingChunk { + pub before: Option, + pub next: Option, + pub spaced: Option, + pub content: String, +} + +impl SurroundingChunk { + pub fn new( + content: impl std::fmt::Display, + before: Option, + next: Option, + ) -> Self { + SurroundingChunk { + before, + next, + content: format!("{content}"), + spaced: None, + } + } + + pub fn spaced(mut self) -> Self { + self.spaced = Some(true); + self + } + + pub fn non_spaced(mut self) -> Self { + self.spaced = Some(false); + self + } + + pub fn loc_before(&self) -> usize { + self.before.unwrap_or_default() + } + + pub fn loc_next(&self) -> Option { + self.next + } +} diff --git a/forge-fmt/src/comments.rs b/forge-fmt/src/comments.rs new file mode 100644 index 000000000..a51952627 --- /dev/null +++ b/forge-fmt/src/comments.rs @@ -0,0 +1,479 @@ +use crate::inline_config::{InlineConfigItem, InvalidInlineConfigItem}; +use itertools::Itertools; +use solang_parser::pt::*; +use std::collections::VecDeque; + +/// The type of a Comment +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum CommentType { + /// A Line comment (e.g. `// ...`) + Line, + /// A Block comment (e.g. `/* ... */`) + Block, + /// A Doc Line comment (e.g. `/// ...`) + DocLine, + /// A Doc Block comment (e.g. `/** ... */`) + DocBlock, +} + +/// The comment position +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum CommentPosition { + /// Comes before the code it describes + Prefix, + /// Comes after the code it describes + Postfix, +} + +/// Comment with additional metadata +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct CommentWithMetadata { + pub ty: CommentType, + pub loc: Loc, + pub has_newline_before: bool, + pub indent_len: usize, + pub comment: String, + pub position: CommentPosition, +} + +impl PartialOrd for CommentWithMetadata { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for CommentWithMetadata { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.loc.cmp(&other.loc) + } +} + +impl CommentWithMetadata { + fn new( + comment: Comment, + position: CommentPosition, + has_newline_before: bool, + indent_len: usize, + ) -> Self { + let (ty, loc, comment) = match comment { + Comment::Line(loc, comment) => (CommentType::Line, loc, comment), + Comment::Block(loc, comment) => (CommentType::Block, loc, comment), + Comment::DocLine(loc, comment) => (CommentType::DocLine, loc, comment), + Comment::DocBlock(loc, comment) => (CommentType::DocBlock, loc, comment), + }; + Self { + comment: comment.trim_end().to_string(), + ty, + loc, + position, + has_newline_before, + indent_len, + } + } + + /// Construct a comment with metadata by analyzing its surrounding source code + fn from_comment_and_src( + comment: Comment, + src: &str, + last_comment: Option<&CommentWithMetadata>, + ) -> Self { + let src_before = &src[..comment.loc().start()]; + if src_before.is_empty() { + return Self::new(comment, CommentPosition::Prefix, false, 0); + } + + let mut lines_before = src_before.lines().rev(); + let this_line = if src_before.ends_with('\n') { + "" + } else { + lines_before.next().unwrap_or_default() + }; + let indent_len = this_line.chars().take_while(|c| c.is_whitespace()).count(); + let last_line = lines_before.next().map(str::trim_start); + + if matches!(comment, Comment::DocLine(..) | Comment::DocBlock(..)) { + return Self::new( + comment, + CommentPosition::Prefix, + last_line.map_or(true, str::is_empty), + indent_len, + ); + } + + // TODO: this loop takes almost the entirety of the time spent in parsing, which is up to + // 80% of `crate::fmt` + let mut code_end = 0; + for (state, idx, ch) in src_before.comment_state_char_indices() { + if matches!(state, CommentState::None) && !ch.is_whitespace() { + code_end = idx; + } + } + + let (position, has_newline_before) = if src_before[code_end..].contains('\n') { + // comment sits on a line without code + if let Some(last_line) = last_line { + if last_line.is_empty() { + // line before is empty + (CommentPosition::Prefix, true) + } else { + // line has something + // check if the last comment after code was a postfix comment + if last_comment + .map_or(false, |last| last.loc.end() > code_end && !last.is_prefix()) + { + // get the indent size of the next item of code + let next_indent_len = src[comment.loc().end()..] + .non_comment_chars() + .take_while(|ch| ch.is_whitespace()) + .fold( + indent_len, + |indent, ch| if ch == '\n' { 0 } else { indent + 1 }, + ); + if indent_len > next_indent_len { + // the comment indent is bigger than the next code indent + (CommentPosition::Postfix, false) + } else { + // the comment indent is equal to or less than the next code + // indent + (CommentPosition::Prefix, false) + } + } else { + // if there is no postfix comment after the piece of code + (CommentPosition::Prefix, false) + } + } + } else { + // beginning of file + (CommentPosition::Prefix, false) + } + } else { + // comment is after some code + (CommentPosition::Postfix, false) + }; + + Self::new(comment, position, has_newline_before, indent_len) + } + + pub fn is_line(&self) -> bool { + matches!(self.ty, CommentType::Line | CommentType::DocLine) + } + + pub fn is_prefix(&self) -> bool { + matches!(self.position, CommentPosition::Prefix) + } + + pub fn is_before(&self, byte: usize) -> bool { + self.loc.start() < byte + } + + /// Returns the contents of the comment without the start and end tokens + pub fn contents(&self) -> &str { + let mut s = self.comment.as_str(); + if let Some(stripped) = s.strip_prefix(self.start_token()) { + s = stripped; + } + if let Some(end_token) = self.end_token() { + if let Some(stripped) = s.strip_suffix(end_token) { + s = stripped; + } + } + s + } + + /// The start token of the comment + #[inline] + pub const fn start_token(&self) -> &'static str { + match self.ty { + CommentType::Line => "//", + CommentType::Block => "/*", + CommentType::DocLine => "///", + CommentType::DocBlock => "/**", + } + } + + /// The token that gets written on the newline when the + /// comment is wrapped + #[inline] + pub const fn wrap_token(&self) -> &'static str { + match self.ty { + CommentType::Line => "// ", + CommentType::DocLine => "/// ", + CommentType::Block => "", + CommentType::DocBlock => " * ", + } + } + + /// The end token of the comment + #[inline] + pub const fn end_token(&self) -> Option<&'static str> { + match self.ty { + CommentType::Line | CommentType::DocLine => None, + CommentType::Block | CommentType::DocBlock => Some("*/"), + } + } +} + +/// A list of comments +#[derive(Default, Debug, Clone)] +pub struct Comments { + prefixes: VecDeque, + postfixes: VecDeque, +} + +impl Comments { + pub fn new(mut comments: Vec, src: &str) -> Self { + let mut prefixes = VecDeque::with_capacity(comments.len()); + let mut postfixes = VecDeque::with_capacity(comments.len()); + let mut last_comment = None; + + comments.sort_by_key(|comment| comment.loc()); + for comment in comments { + let comment = CommentWithMetadata::from_comment_and_src(comment, src, last_comment); + let vec = if comment.is_prefix() { + &mut prefixes + } else { + &mut postfixes + }; + vec.push_back(comment); + last_comment = Some(vec.back().unwrap()); + } + Self { + prefixes, + postfixes, + } + } + + /// Heloer for removing comments before a byte offset + fn remove_comments_before( + comments: &mut VecDeque, + byte: usize, + ) -> Vec { + let pos = comments + .iter() + .find_position(|comment| !comment.is_before(byte)) + .map(|(idx, _)| idx) + .unwrap_or_else(|| comments.len()); + if pos == 0 { + return Vec::new(); + } + comments.rotate_left(pos); + comments.split_off(comments.len() - pos).into() + } + + /// Remove any prefix comments that occur before the byte offset in the src + pub(crate) fn remove_prefixes_before(&mut self, byte: usize) -> Vec { + Self::remove_comments_before(&mut self.prefixes, byte) + } + + /// Remove any postfix comments that occur before the byte offset in the src + pub(crate) fn remove_postfixes_before(&mut self, byte: usize) -> Vec { + Self::remove_comments_before(&mut self.postfixes, byte) + } + + /// Remove any comments that occur before the byte offset in the src + pub(crate) fn remove_all_comments_before(&mut self, byte: usize) -> Vec { + self.remove_prefixes_before(byte) + .into_iter() + .merge(self.remove_postfixes_before(byte)) + .collect() + } + + pub(crate) fn pop(&mut self) -> Option { + if self.iter().next()?.is_prefix() { + self.prefixes.pop_front() + } else { + self.postfixes.pop_front() + } + } + + pub(crate) fn iter(&self) -> impl Iterator { + self.prefixes.iter().merge(self.postfixes.iter()) + } + + /// Parse all comments to return a list of inline config items. This will return an iterator of + /// results of parsing comments which start with `forgefmt:` + pub fn parse_inline_config_items( + &self, + ) -> impl Iterator> + '_ + { + self.iter() + .filter_map(|comment| { + Some(( + comment, + comment + .contents() + .trim_start() + .strip_prefix("forgefmt:")? + .trim(), + )) + }) + .map(|(comment, item)| { + let loc = comment.loc; + item.parse().map(|out| (loc, out)).map_err(|out| (loc, out)) + }) + } +} + +/// The state of a character in a string with possible comments +#[derive(Copy, Clone, Debug, Eq, PartialEq, Default)] +pub enum CommentState { + /// character not in a comment + #[default] + None, + /// First `/` in line comment start `"//"` + LineStart1, + /// Second `/` in line comment start `"//"` + LineStart2, + /// Character in a line comment + Line, + /// `/` in block comment start `"/*"` + BlockStart1, + /// `*` in block comment start `"/*"` + BlockStart2, + /// Character in a block comment + Block, + /// `*` in block comment end `"*/"` + BlockEnd1, + /// `/` in block comment end `"*/"` + BlockEnd2, +} + +/// An Iterator over characters and indices in a string slice with information about the state of +/// comments +pub struct CommentStateCharIndices<'a> { + iter: std::str::CharIndices<'a>, + state: CommentState, +} + +impl<'a> CommentStateCharIndices<'a> { + #[inline] + fn new(string: &'a str) -> Self { + Self { + iter: string.char_indices(), + state: CommentState::None, + } + } + + #[inline] + pub fn with_state(mut self, state: CommentState) -> Self { + self.state = state; + self + } + + #[inline] + pub fn peek(&mut self) -> Option<(usize, char)> { + self.iter.clone().next() + } +} + +impl Iterator for CommentStateCharIndices<'_> { + type Item = (CommentState, usize, char); + + #[inline] + fn next(&mut self) -> Option { + let (idx, ch) = self.iter.next()?; + match self.state { + CommentState::None => { + if ch == '/' { + self.state = match self.peek() { + Some((_, '/')) => CommentState::LineStart1, + Some((_, '*')) => CommentState::BlockStart1, + _ => CommentState::None, + }; + } + } + CommentState::LineStart1 => { + self.state = CommentState::LineStart2; + } + CommentState::LineStart2 => { + self.state = CommentState::Line; + } + CommentState::Line => { + if ch == '\n' { + self.state = CommentState::None; + } + } + CommentState::BlockStart1 => { + self.state = CommentState::BlockStart2; + } + CommentState::BlockStart2 => { + self.state = CommentState::Block; + } + CommentState::Block => { + if ch == '*' { + if let Some((_, '/')) = self.peek() { + self.state = CommentState::BlockEnd1; + } + } + } + CommentState::BlockEnd1 => { + self.state = CommentState::BlockEnd2; + } + CommentState::BlockEnd2 => { + self.state = CommentState::None; + } + } + Some((self.state, idx, ch)) + } + + #[inline] + fn count(self) -> usize { + self.iter.count() + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + self.iter.size_hint() + } +} + +impl std::iter::FusedIterator for CommentStateCharIndices<'_> {} + +/// An Iterator over characters in a string slice which are not a apart of comments +pub struct NonCommentChars<'a>(CommentStateCharIndices<'a>); + +impl<'a> Iterator for NonCommentChars<'a> { + type Item = char; + + #[inline] + fn next(&mut self) -> Option { + for (state, _, ch) in self.0.by_ref() { + if state == CommentState::None { + return Some(ch); + } + } + None + } +} + +/// Helpers for iterating over comment containing strings +pub trait CommentStringExt { + fn comment_state_char_indices(&self) -> CommentStateCharIndices; + + #[inline] + fn non_comment_chars(&self) -> NonCommentChars { + NonCommentChars(self.comment_state_char_indices()) + } + + #[inline] + fn trim_comments(&self) -> String { + self.non_comment_chars().collect() + } +} + +impl CommentStringExt for T +where + T: AsRef, +{ + #[inline] + fn comment_state_char_indices(&self) -> CommentStateCharIndices { + CommentStateCharIndices::new(self.as_ref()) + } +} + +impl CommentStringExt for str { + #[inline] + fn comment_state_char_indices(&self) -> CommentStateCharIndices { + CommentStateCharIndices::new(self) + } +} diff --git a/forge-fmt/src/formatter.rs b/forge-fmt/src/formatter.rs new file mode 100644 index 000000000..eaaf69c04 --- /dev/null +++ b/forge-fmt/src/formatter.rs @@ -0,0 +1,3694 @@ +//! A Solidity formatter + +use crate::{ + buffer::*, + chunk::*, + comments::{ + CommentPosition, CommentState, CommentStringExt, CommentType, CommentWithMetadata, Comments, + }, + helpers::import_path_string, + macros::*, + solang_ext::{pt::*, *}, + string::{QuoteState, QuotedStringExt}, + visit::{Visitable, Visitor}, + FormatterConfig, InlineConfig, IntTypes, NumberUnderscore, +}; +use ethers_core::{types::H160, utils::to_checksum}; +use foundry_config::fmt::{MultilineFuncHeaderStyle, SingleLineBlockStyle}; +use itertools::{Either, Itertools}; +use solang_parser::pt::ImportPath; +use std::{fmt::Write, str::FromStr}; +use thiserror::Error; + +type Result = std::result::Result; + +/// A custom Error thrown by the Formatter +#[derive(Error, Debug)] +pub enum FormatterError { + /// Error thrown by `std::fmt::Write` interfaces + #[error(transparent)] + Fmt(#[from] std::fmt::Error), + /// Encountered invalid parse tree item. + #[error("Encountered invalid parse tree item at {0:?}")] + InvalidParsedItem(Loc), + /// All other errors + #[error(transparent)] + Custom(Box), +} + +impl FormatterError { + fn fmt() -> Self { + Self::Fmt(std::fmt::Error) + } + fn custom(err: impl std::error::Error + 'static) -> Self { + Self::Custom(Box::new(err)) + } +} + +#[allow(unused_macros)] +macro_rules! format_err { + ($msg:literal $(,)?) => { + $crate::formatter::FormatterError::custom($msg.to_string()) + }; + ($err:expr $(,)?) => { + $crate::formatter::FormatterError::custom($err) + }; + ($fmt:expr, $($arg:tt)*) => { + $crate::formatter::FormatterError::custom(format!($fmt, $($arg)*)) + }; +} + +#[allow(unused_macros)] +macro_rules! bail { + ($msg:literal $(,)?) => { + return Err($crate::formatter::format_err!($msg)) + }; + ($err:expr $(,)?) => { + return Err($err) + }; + ($fmt:expr, $($arg:tt)*) => { + return Err($crate::formatter::format_err!($fmt, $(arg)*)) + }; +} + +// TODO: store context entities as references without copying +/// Current context of the Formatter (e.g. inside Contract or Function definition) +#[derive(Default, Debug)] +struct Context { + contract: Option, + function: Option, + if_stmt_single_line: Option, +} + +/// A Solidity formatter +#[derive(Debug)] +pub struct Formatter<'a, W> { + buf: FormatBuffer, + source: &'a str, + config: FormatterConfig, + temp_bufs: Vec>, + context: Context, + comments: Comments, + inline_config: InlineConfig, +} + +/// An action which may be committed to a Formatter +struct Transaction<'f, 'a, W> { + fmt: &'f mut Formatter<'a, W>, + buffer: String, + comments: Comments, +} + +impl<'f, 'a, W> std::ops::Deref for Transaction<'f, 'a, W> { + type Target = Formatter<'a, W>; + fn deref(&self) -> &Self::Target { + self.fmt + } +} + +impl<'f, 'a, W> std::ops::DerefMut for Transaction<'f, 'a, W> { + fn deref_mut(&mut self) -> &mut Self::Target { + self.fmt + } +} + +impl<'f, 'a, W: Write> Transaction<'f, 'a, W> { + /// Create a new transaction from a callback + fn new( + fmt: &'f mut Formatter<'a, W>, + fun: impl FnMut(&mut Formatter<'a, W>) -> Result<()>, + ) -> Result { + let mut comments = fmt.comments.clone(); + let buffer = fmt.with_temp_buf(fun)?.w; + comments = std::mem::replace(&mut fmt.comments, comments); + Ok(Self { + fmt, + buffer, + comments, + }) + } + + /// Commit the transaction to the Formatter + fn commit(self) -> Result { + self.fmt.comments = self.comments; + write_chunk!(self.fmt, "{}", self.buffer)?; + Ok(self.buffer) + } +} + +impl<'a, W: Write> Formatter<'a, W> { + pub fn new( + w: W, + source: &'a str, + comments: Comments, + inline_config: InlineConfig, + config: FormatterConfig, + ) -> Self { + Self { + buf: FormatBuffer::new(w, config.tab_width), + source, + config, + temp_bufs: Vec::new(), + context: Context::default(), + comments, + inline_config, + } + } + + /// Get the Write interface of the current temp buffer or the underlying Write + fn buf(&mut self) -> &mut dyn Write { + match &mut self.temp_bufs[..] { + [] => &mut self.buf as &mut dyn Write, + [.., buf] => buf as &mut dyn Write, + } + } + + /// Casts the current writer `w` as a `String` reference. Should only be used for debugging. + #[allow(dead_code)] + unsafe fn buf_contents(&self) -> &String { + *(&self.buf.w as *const W as *const &mut String) + } + + /// Casts the current `W` writer or the current temp buffer as a `String` reference. + /// Should only be used for debugging. + #[allow(dead_code)] + unsafe fn temp_buf_contents(&self) -> &String { + match &self.temp_bufs[..] { + [] => self.buf_contents(), + [.., buf] => &buf.w, + } + } + + buf_fn! { fn indent(&mut self, delta: usize) } + buf_fn! { fn dedent(&mut self, delta: usize) } + buf_fn! { fn start_group(&mut self) } + buf_fn! { fn end_group(&mut self) } + buf_fn! { fn create_temp_buf(&self) -> FormatBuffer } + buf_fn! { fn restrict_to_single_line(&mut self, restricted: bool) } + buf_fn! { fn current_line_len(&self) -> usize } + buf_fn! { fn total_indent_len(&self) -> usize } + buf_fn! { fn is_beginning_of_line(&self) -> bool } + buf_fn! { fn last_char(&self) -> Option } + buf_fn! { fn last_indent_group_skipped(&self) -> bool } + buf_fn! { fn set_last_indent_group_skipped(&mut self, skip: bool) } + buf_fn! { fn write_raw(&mut self, s: impl AsRef) -> std::fmt::Result } + + /// Do the callback within the context of a temp buffer + fn with_temp_buf( + &mut self, + mut fun: impl FnMut(&mut Self) -> Result<()>, + ) -> Result> { + self.temp_bufs.push(self.create_temp_buf()); + let res = fun(self); + let out = self.temp_bufs.pop().unwrap(); + res?; + Ok(out) + } + + /// Does the next written character require whitespace before + fn next_char_needs_space(&self, next_char: char) -> bool { + if self.is_beginning_of_line() { + return false; + } + let last_char = if let Some(last_char) = self.last_char() { + last_char + } else { + return false; + }; + if last_char.is_whitespace() || next_char.is_whitespace() { + return false; + } + match last_char { + '{' => match next_char { + '{' | '[' | '(' => false, + '/' => true, + _ => self.config.bracket_spacing, + }, + '(' | '.' | '[' => matches!(next_char, '/'), + '/' => true, + _ => match next_char { + '}' => self.config.bracket_spacing, + ')' | ',' | '.' | ';' | ']' => false, + _ => true, + }, + } + } + + /// Is length of the `text` with respect to already written line <= `config.line_length` + fn will_it_fit(&self, text: impl AsRef) -> bool { + let text = text.as_ref(); + if text.is_empty() { + return true; + } + if text.contains('\n') { + return false; + } + let space: usize = self + .next_char_needs_space(text.chars().next().unwrap()) + .into(); + self.config.line_length + >= self + .total_indent_len() + .saturating_add(self.current_line_len()) + .saturating_add(text.chars().count() + space) + } + + /// Write empty brackets with respect to `config.bracket_spacing` setting: + /// `"{ }"` if `true`, `"{}"` if `false` + fn write_empty_brackets(&mut self) -> Result<()> { + let brackets = if self.config.bracket_spacing { + "{ }" + } else { + "{}" + }; + write_chunk!(self, "{brackets}")?; + Ok(()) + } + + /// Write semicolon to the buffer + fn write_semicolon(&mut self) -> Result<()> { + write!(self.buf(), ";")?; + Ok(()) + } + + /// Write whitespace separator to the buffer + /// `"\n"` if `multiline` is `true`, `" "` if `false` + fn write_whitespace_separator(&mut self, multiline: bool) -> Result<()> { + if !self.is_beginning_of_line() { + write!(self.buf(), "{}", if multiline { "\n" } else { " " })?; + } + Ok(()) + } + + /// Write new line with preserved `last_indent_group_skipped` flag + fn write_preserved_line(&mut self) -> Result<()> { + let last_indent_group_skipped = self.last_indent_group_skipped(); + writeln!(self.buf())?; + self.set_last_indent_group_skipped(last_indent_group_skipped); + Ok(()) + } + + /// Returns number of blank lines in source between two byte indexes + fn blank_lines(&self, start: usize, end: usize) -> usize { + self.source[start..end] + .trim_comments() + .matches('\n') + .count() + } + + /// Get the byte offset of the next line + fn find_next_line(&self, byte_offset: usize) -> Option { + let mut iter = self.source[byte_offset..].char_indices(); + while let Some((_, ch)) = iter.next() { + match ch { + '\n' => return iter.next().map(|(idx, _)| byte_offset + idx), + '\r' => { + return iter.next().and_then(|(idx, ch)| match ch { + '\n' => iter.next().map(|(idx, _)| byte_offset + idx), + _ => Some(byte_offset + idx), + }) + } + _ => {} + } + } + None + } + + /// Find the next instance of the character in source excluding comments + fn find_next_in_src(&self, byte_offset: usize, needle: char) -> Option { + self.source[byte_offset..] + .comment_state_char_indices() + .position(|(state, _, ch)| needle == ch && state == CommentState::None) + .map(|p| byte_offset + p) + } + + /// Find the start of the next instance of a slice in source + fn find_next_str_in_src(&self, byte_offset: usize, needle: &str) -> Option { + let subset = &self.source[byte_offset..]; + needle.chars().next().and_then(|first_char| { + subset + .comment_state_char_indices() + .position(|(state, idx, ch)| { + first_char == ch + && state == CommentState::None + && idx + needle.len() <= subset.len() + && subset[idx..idx + needle.len()].eq(needle) + }) + .map(|p| byte_offset + p) + }) + } + + /// Extends the location to the next instance of a character. Returns true if the loc was + /// extended + fn extend_loc_until(&self, loc: &mut Loc, needle: char) -> bool { + if let Some(end) = self + .find_next_in_src(loc.end(), needle) + .map(|offset| offset + 1) + { + *loc = loc.with_end(end); + true + } else { + false + } + } + + /// Return the flag whether the attempt should be made + /// to write the block on a single line. + /// If the block style is configured to [SingleLineBlockStyle::Preserve], + /// lookup whether there was a newline introduced in `[start_from, end_at]` range + /// where `end_at` is the start of the block. + fn should_attempt_block_single_line( + &mut self, + stmt: &mut Statement, + start_from: usize, + ) -> bool { + match self.config.single_line_statement_blocks { + SingleLineBlockStyle::Single => true, + SingleLineBlockStyle::Multi => false, + SingleLineBlockStyle::Preserve => { + let end_at = match stmt { + Statement::Block { statements, .. } if !statements.is_empty() => { + statements.first().as_ref().unwrap().loc().start() + } + Statement::Expression(loc, _) => loc.start(), + _ => stmt.loc().start(), + }; + + self.find_next_line(start_from) + .map_or(false, |loc| loc >= end_at) + } + } + } + + /// Create a chunk given a string and the location information + fn chunk_at( + &mut self, + byte_offset: usize, + next_byte_offset: Option, + needs_space: Option, + content: impl std::fmt::Display, + ) -> Chunk { + Chunk { + postfixes_before: self.comments.remove_postfixes_before(byte_offset), + prefixes: self.comments.remove_prefixes_before(byte_offset), + content: content.to_string(), + postfixes: next_byte_offset + .map(|byte_offset| self.comments.remove_postfixes_before(byte_offset)) + .unwrap_or_default(), + needs_space, + } + } + + /// Create a chunk given a callback + fn chunked( + &mut self, + byte_offset: usize, + next_byte_offset: Option, + fun: impl FnMut(&mut Self) -> Result<()>, + ) -> Result { + let postfixes_before = self.comments.remove_postfixes_before(byte_offset); + let prefixes = self.comments.remove_prefixes_before(byte_offset); + let content = self.with_temp_buf(fun)?.w; + let postfixes = next_byte_offset + .map(|byte_offset| self.comments.remove_postfixes_before(byte_offset)) + .unwrap_or_default(); + Ok(Chunk { + postfixes_before, + prefixes, + content, + postfixes, + needs_space: None, + }) + } + + /// Create a chunk given a [Visitable] item + fn visit_to_chunk( + &mut self, + byte_offset: usize, + next_byte_offset: Option, + visitable: &mut impl Visitable, + ) -> Result { + self.chunked(byte_offset, next_byte_offset, |fmt| { + visitable.visit(fmt)?; + Ok(()) + }) + } + + /// Transform [Visitable] items to the list of chunks + fn items_to_chunks<'b>( + &mut self, + next_byte_offset: Option, + items: impl Iterator + 'b, + ) -> Result> { + let mut items = items.peekable(); + let mut out = Vec::with_capacity(items.size_hint().1.unwrap_or(0)); + while let Some((loc, item)) = items.next() { + let chunk_next_byte_offset = items + .peek() + .map(|(loc, _)| loc.start()) + .or(next_byte_offset); + out.push(self.visit_to_chunk(loc.start(), chunk_next_byte_offset, item)?); + } + Ok(out) + } + + /// Transform [Visitable] items to a list of chunks and then sort those chunks by [AttrSortKey] + fn items_to_chunks_sorted<'b>( + &mut self, + next_byte_offset: Option, + items: impl Iterator + 'b, + ) -> Result> { + let mut items = items.peekable(); + let mut out = Vec::with_capacity(items.size_hint().1.unwrap_or(0)); + while let Some(item) = items.next() { + let chunk_next_byte_offset = items + .peek() + .map(|next| next.loc().start()) + .or(next_byte_offset); + let chunk = self.visit_to_chunk(item.loc().start(), chunk_next_byte_offset, item)?; + out.push((item, chunk)); + } + out.sort_by(|(a, _), (b, _)| a.cmp(b)); + Ok(out.into_iter().map(|(_, c)| c).collect()) + } + + /// Write a comment to the buffer formatted. + /// WARNING: This may introduce a newline if the comment is a Line comment + /// or if the comment are wrapped + fn write_comment(&mut self, comment: &CommentWithMetadata, is_first: bool) -> Result<()> { + if self.inline_config.is_disabled(comment.loc) { + return self.write_raw_comment(comment); + } + + match comment.position { + CommentPosition::Prefix => self.write_prefix_comment(comment, is_first), + CommentPosition::Postfix => self.write_postfix_comment(comment), + } + } + + /// Write a comment with position [CommentPosition::Prefix] + fn write_prefix_comment( + &mut self, + comment: &CommentWithMetadata, + is_first: bool, + ) -> Result<()> { + if !self.is_beginning_of_line() { + self.write_preserved_line()?; + } + if !is_first && comment.has_newline_before { + self.write_preserved_line()?; + } + + if matches!(comment.ty, CommentType::DocBlock) { + let mut lines = comment.contents().trim().lines(); + writeln!(self.buf(), "{}", comment.start_token())?; + lines.try_for_each(|l| self.write_doc_block_line(comment, l))?; + write!(self.buf(), " {}", comment.end_token().unwrap())?; + self.write_preserved_line()?; + return Ok(()); + } + + write!(self.buf(), "{}", comment.start_token())?; + + let mut wrapped = false; + let contents = comment.contents(); + let mut lines = contents.lines().peekable(); + while let Some(line) = lines.next() { + wrapped |= self.write_comment_line(comment, line)?; + if lines.peek().is_some() { + self.write_preserved_line()?; + } + } + + if let Some(end) = comment.end_token() { + // Check if the end token in the original comment was on the separate line + if !wrapped && comment.comment.lines().count() > contents.lines().count() { + self.write_preserved_line()?; + } + write!(self.buf(), "{end}")?; + } + if self.find_next_line(comment.loc.end()).is_some() { + self.write_preserved_line()?; + } + + Ok(()) + } + + /// Write a comment with position [CommentPosition::Postfix] + fn write_postfix_comment(&mut self, comment: &CommentWithMetadata) -> Result<()> { + let indented = self.is_beginning_of_line(); + self.indented_if(indented, 1, |fmt| { + if !indented && fmt.next_char_needs_space('/') { + fmt.write_whitespace_separator(false)?; + } + + write!(fmt.buf(), "{}", comment.start_token())?; + let start_token_pos = fmt.current_line_len(); + + let mut lines = comment.contents().lines().peekable(); + fmt.grouped(|fmt| { + while let Some(line) = lines.next() { + fmt.write_comment_line(comment, line)?; + if lines.peek().is_some() { + fmt.write_whitespace_separator(true)?; + } + } + Ok(()) + })?; + + if let Some(end) = comment.end_token() { + // If comment is not multiline, end token has to be aligned with the start + if fmt.is_beginning_of_line() { + write!(fmt.buf(), "{}{end}", " ".repeat(start_token_pos))?; + } else { + write!(fmt.buf(), "{end}")?; + } + } + + if comment.is_line() { + fmt.write_whitespace_separator(true)?; + } + Ok(()) + }) + } + + /// Write the line of a doc block comment line + fn write_doc_block_line(&mut self, comment: &CommentWithMetadata, line: &str) -> Result<()> { + if line.trim().starts_with('*') { + let line = line.trim().trim_start_matches('*'); + let needs_space = line.chars().next().map_or(false, |ch| !ch.is_whitespace()); + write!(self.buf(), " *{}", if needs_space { " " } else { "" })?; + self.write_comment_line(comment, line)?; + self.write_whitespace_separator(true)?; + return Ok(()); + } + + let indent_whitespace_count = line + .char_indices() + .take_while(|(idx, ch)| ch.is_whitespace() && *idx <= self.buf.current_indent_len()) + .count(); + let to_skip = indent_whitespace_count - indent_whitespace_count % self.config.tab_width; + write!(self.buf(), " * ")?; + self.write_comment_line(comment, &line[to_skip..])?; + self.write_whitespace_separator(true)?; + Ok(()) + } + + /// Write a comment line that might potentially overflow the maximum line length + /// and, if configured, will be wrapped to the next line. + fn write_comment_line(&mut self, comment: &CommentWithMetadata, line: &str) -> Result { + if self.will_it_fit(line) || !self.config.wrap_comments { + let start_with_ws = line + .chars() + .next() + .map(|ch| ch.is_whitespace()) + .unwrap_or_default(); + if !self.is_beginning_of_line() || !start_with_ws { + write!(self.buf(), "{line}")?; + return Ok(false); + } + + // if this is the beginning of the line, + // the comment should start with at least an indent + let indent = self.buf.current_indent_len(); + let mut chars = line + .char_indices() + .skip_while(|(idx, ch)| ch.is_whitespace() && *idx < indent) + .map(|(_, ch)| ch); + let padded = format!("{}{}", " ".repeat(indent), chars.join("")); + self.write_raw(padded)?; + return Ok(false); + } + + let mut words = line.split(' ').peekable(); + while let Some(word) = words.next() { + if self.is_beginning_of_line() { + write!(self.buf(), "{}", word.trim_start())?; + } else { + self.write_raw(word)?; + } + + if let Some(next) = words.peek() { + if !word.is_empty() && !self.will_it_fit(next) { + // the next word doesn't fit on this line, + // write remaining words on the next + self.write_whitespace_separator(true)?; + // write newline wrap token + write!(self.buf(), "{}", comment.wrap_token())?; + self.write_comment_line(comment, &words.join(" "))?; + return Ok(true); + } + + self.write_whitespace_separator(false)?; + } + } + Ok(false) + } + + /// Write a raw comment. This is like [`write_comment`] but won't do any formatting or worry + /// about whitespace behind the comment + fn write_raw_comment(&mut self, comment: &CommentWithMetadata) -> Result<()> { + self.write_raw(&comment.comment)?; + if comment.is_line() { + self.write_preserved_line()?; + } + Ok(()) + } + + // TODO handle whitespace between comments for disabled sections + /// Write multiple comments + fn write_comments<'b>( + &mut self, + comments: impl IntoIterator, + ) -> Result<()> { + let mut comments = comments.into_iter().peekable(); + let mut last_byte_written = match comments.peek() { + Some(comment) => comment.loc.start(), + None => return Ok(()), + }; + let mut is_first = true; + for comment in comments { + let unwritten_whitespace_loc = Loc::File( + comment.loc.file_no(), + last_byte_written, + comment.loc.start(), + ); + if self.inline_config.is_disabled(unwritten_whitespace_loc) { + self.write_raw(&self.source[unwritten_whitespace_loc.range()])?; + self.write_raw_comment(comment)?; + last_byte_written = if comment.is_line() { + self.find_next_line(comment.loc.end()) + .unwrap_or_else(|| comment.loc.end()) + } else { + comment.loc.end() + }; + } else { + self.write_comment(comment, is_first)?; + } + is_first = false; + } + Ok(()) + } + + /// Write a postfix comments before a given location + fn write_postfix_comments_before(&mut self, byte_end: usize) -> Result<()> { + let comments = self.comments.remove_postfixes_before(byte_end); + self.write_comments(&comments) + } + + /// Write all prefix comments before a given location + fn write_prefix_comments_before(&mut self, byte_end: usize) -> Result<()> { + let comments = self.comments.remove_prefixes_before(byte_end); + self.write_comments(&comments) + } + + /// Check if a chunk will fit on the current line + fn will_chunk_fit(&mut self, format_string: &str, chunk: &Chunk) -> Result { + if let Some(chunk_str) = self.simulate_to_single_line(|fmt| fmt.write_chunk(chunk))? { + Ok(self.will_it_fit(format_string.replacen("{}", &chunk_str, 1))) + } else { + Ok(false) + } + } + + /// Check if a separated list of chunks will fit on the current line + fn are_chunks_separated_multiline<'b>( + &mut self, + format_string: &str, + items: impl IntoIterator, + separator: &str, + ) -> Result { + let items = items.into_iter().collect_vec(); + if let Some(chunks) = self.simulate_to_single_line(|fmt| { + fmt.write_chunks_separated(items.iter().copied(), separator, false) + })? { + Ok(!self.will_it_fit(format_string.replacen("{}", &chunks, 1))) + } else { + Ok(true) + } + } + + /// Write the chunk and any surrounding comments into the buffer + /// This will automatically add whitespace before the chunk given the rule set in + /// `next_char_needs_space`. If the chunk does not fit on the current line it will be put on + /// to the next line + fn write_chunk(&mut self, chunk: &Chunk) -> Result<()> { + // handle comments before chunk + self.write_comments(&chunk.postfixes_before)?; + self.write_comments(&chunk.prefixes)?; + + // trim chunk start + let content = if chunk.content.starts_with('\n') { + let mut chunk = chunk.content.trim_start().to_string(); + chunk.insert(0, '\n'); + chunk + } else if chunk.content.starts_with(' ') { + let mut chunk = chunk.content.trim_start().to_string(); + chunk.insert(0, ' '); + chunk + } else { + chunk.content.clone() + }; + + if !content.is_empty() { + // add whitespace if necessary + let needs_space = chunk + .needs_space + .unwrap_or_else(|| self.next_char_needs_space(content.chars().next().unwrap())); + if needs_space { + if self.will_it_fit(&content) { + write!(self.buf(), " ")?; + } else { + writeln!(self.buf())?; + } + } + + // write chunk + write!(self.buf(), "{content}")?; + } + + // write any postfix comments + self.write_comments(&chunk.postfixes)?; + + Ok(()) + } + + /// Write chunks separated by a separator. If `multiline`, each chunk will be written to a + /// separate line + fn write_chunks_separated<'b>( + &mut self, + chunks: impl IntoIterator, + separator: &str, + multiline: bool, + ) -> Result<()> { + let mut chunks = chunks.into_iter().peekable(); + while let Some(chunk) = chunks.next() { + let mut chunk = chunk.clone(); + + // handle postfixes before and add newline if necessary + self.write_comments(&std::mem::take(&mut chunk.postfixes_before))?; + if multiline && !self.is_beginning_of_line() { + writeln!(self.buf())?; + } + + // remove postfixes so we can add separator between + let postfixes = std::mem::take(&mut chunk.postfixes); + + self.write_chunk(&chunk)?; + + // add separator + if chunks.peek().is_some() { + write!(self.buf(), "{separator}")?; + self.write_comments(&postfixes)?; + if multiline && !self.is_beginning_of_line() { + writeln!(self.buf())?; + } + } else { + self.write_comments(&postfixes)?; + } + } + Ok(()) + } + + /// Apply the callback indented by the indent size + fn indented(&mut self, delta: usize, fun: impl FnMut(&mut Self) -> Result<()>) -> Result<()> { + self.indented_if(true, delta, fun) + } + + /// Apply the callback indented by the indent size if the condition is true + fn indented_if( + &mut self, + condition: bool, + delta: usize, + mut fun: impl FnMut(&mut Self) -> Result<()>, + ) -> Result<()> { + if condition { + self.indent(delta); + } + let res = fun(self); + if condition { + self.dedent(delta); + } + res?; + Ok(()) + } + + /// Apply the callback into an indent group. The first line of the indent group is not + /// indented but lines thereafter are + fn grouped(&mut self, mut fun: impl FnMut(&mut Self) -> Result<()>) -> Result { + self.start_group(); + let res = fun(self); + let indented = !self.last_indent_group_skipped(); + self.end_group(); + res?; + Ok(indented) + } + + /// Add a function context around a procedure and revert the context at the end of the procedure + /// regardless of the response + fn with_function_context( + &mut self, + context: FunctionDefinition, + mut fun: impl FnMut(&mut Self) -> Result<()>, + ) -> Result<()> { + self.context.function = Some(context); + let res = fun(self); + self.context.function = None; + res + } + + /// Add a contract context around a procedure and revert the context at the end of the procedure + /// regardless of the response + fn with_contract_context( + &mut self, + context: ContractDefinition, + mut fun: impl FnMut(&mut Self) -> Result<()>, + ) -> Result<()> { + self.context.contract = Some(context); + let res = fun(self); + self.context.contract = None; + res + } + + /// Create a transaction. The result of the transaction is not applied to the buffer unless + /// `Transacton::commit` is called + fn transact<'b>( + &'b mut self, + fun: impl FnMut(&mut Self) -> Result<()>, + ) -> Result> { + Transaction::new(self, fun) + } + + /// Do the callback and return the result on the buffer as a string + fn simulate_to_string(&mut self, fun: impl FnMut(&mut Self) -> Result<()>) -> Result { + Ok(self.transact(fun)?.buffer) + } + + /// Turn a chunk and its surrounding comments into a a string + fn chunk_to_string(&mut self, chunk: &Chunk) -> Result { + self.simulate_to_string(|fmt| fmt.write_chunk(chunk)) + } + + /// Try to create a string based on a callback. If the string does not fit on a single line + /// this will return `None` + fn simulate_to_single_line( + &mut self, + mut fun: impl FnMut(&mut Self) -> Result<()>, + ) -> Result> { + let mut single_line = false; + let tx = self.transact(|fmt| { + fmt.restrict_to_single_line(true); + single_line = match fun(fmt) { + Ok(()) => true, + Err(FormatterError::Fmt(_)) => false, + Err(err) => bail!(err), + }; + Ok(()) + })?; + Ok(if single_line && tx.will_it_fit(&tx.buffer) { + Some(tx.buffer) + } else { + None + }) + } + + /// Try to apply a callback to a single line. If the callback cannot be applied to a single + /// line the callback will not be applied to the buffer and `false` will be returned. Otherwise + /// `true` will be returned + fn try_on_single_line(&mut self, mut fun: impl FnMut(&mut Self) -> Result<()>) -> Result { + let mut single_line = false; + let tx = self.transact(|fmt| { + fmt.restrict_to_single_line(true); + single_line = match fun(fmt) { + Ok(()) => true, + Err(FormatterError::Fmt(_)) => false, + Err(err) => bail!(err), + }; + Ok(()) + })?; + Ok(if single_line && tx.will_it_fit(&tx.buffer) { + tx.commit()?; + true + } else { + false + }) + } + + /// Surrounds a callback with parentheses. The callback will try to be applied to a single + /// line. If the callback cannot be applied to a single line the callback will applied to the + /// nextline indented. The callback receives a `multiline` hint as the second argument which + /// receives `true` in the latter case + fn surrounded( + &mut self, + first: SurroundingChunk, + last: SurroundingChunk, + mut fun: impl FnMut(&mut Self, bool) -> Result<()>, + ) -> Result<()> { + let first_chunk = self.chunk_at( + first.loc_before(), + first.loc_next(), + first.spaced, + first.content, + ); + self.write_chunk(&first_chunk)?; + + let multiline = !self.try_on_single_line(|fmt| { + fun(fmt, false)?; + let last_chunk = fmt.chunk_at( + last.loc_before(), + last.loc_next(), + last.spaced, + &last.content, + ); + fmt.write_chunk(&last_chunk)?; + Ok(()) + })?; + + if multiline { + self.indented(1, |fmt| { + fmt.write_whitespace_separator(true)?; + let stringified = fmt.with_temp_buf(|fmt| fun(fmt, true))?.w; + write_chunk!(fmt, "{}", stringified.trim_start()) + })?; + if !last.content.trim_start().is_empty() { + self.write_whitespace_separator(true)?; + } + let last_chunk = self.chunk_at( + last.loc_before(), + last.loc_next(), + last.spaced, + &last.content, + ); + self.write_chunk(&last_chunk)?; + } + + Ok(()) + } + + /// Write each [Visitable] item on a separate line. The function will check if there are any + /// blank lines between each visitable statement and will apply a single blank line if there + /// exists any. The `needs_space` callback can force a newline and is given the last_item if + /// any and the next item as arguments + fn write_lined_visitable<'b, I, V, F>( + &mut self, + loc: Loc, + items: I, + needs_space_fn: F, + ) -> Result<()> + where + I: Iterator + 'b, + V: Visitable + CodeLocation + 'b, + F: Fn(&V, &V) -> bool, + { + let mut items = items.collect::>(); + items.reverse(); + // get next item + let pop_next = |fmt: &mut Self, items: &mut Vec<&'b mut V>| { + let comment = fmt + .comments + .iter() + .next() + .filter(|comment| comment.loc.end() < loc.end()); + let item = items.last(); + if let (Some(comment), Some(item)) = (comment, item) { + if comment.loc < item.loc() { + Some(Either::Left(fmt.comments.pop().unwrap())) + } else { + Some(Either::Right(items.pop().unwrap())) + } + } else if comment.is_some() { + Some(Either::Left(fmt.comments.pop().unwrap())) + } else if item.is_some() { + Some(Either::Right(items.pop().unwrap())) + } else { + None + } + }; + // get whitespace between to offsets. this needs to account for possible left over + // semicolons which are not included in the `Loc` + let unwritten_whitespace = |from: usize, to: usize| { + let to = to.max(from); + let mut loc = Loc::File(loc.file_no(), from, to); + let src = &self.source[from..to]; + if let Some(semi) = src.find(';') { + loc = loc.with_start(from + semi + 1); + } + (loc, &self.source[loc.range()]) + }; + + let mut last_byte_written = match ( + self.comments + .iter() + .next() + .filter(|comment| comment.loc.end() < loc.end()), + items.last(), + ) { + (Some(comment), Some(item)) => comment.loc.min(item.loc()), + (None, Some(item)) => item.loc(), + (Some(comment), None) => comment.loc, + (None, None) => return Ok(()), + } + .start(); + let mut last_loc: Option = None; + let mut needs_space = false; + while let Some(mut line_item) = pop_next(self, &mut items) { + let loc = line_item.as_ref().either(|c| c.loc, |i| i.loc()); + let (unwritten_whitespace_loc, unwritten_whitespace) = + unwritten_whitespace(last_byte_written, loc.start()); + let ignore_whitespace = if self.inline_config.is_disabled(unwritten_whitespace_loc) { + trace!("Unwritten whitespace: {unwritten_whitespace:?}"); + self.write_raw(unwritten_whitespace)?; + true + } else { + false + }; + match line_item.as_mut() { + Either::Left(comment) => { + if ignore_whitespace { + self.write_raw_comment(comment)?; + if unwritten_whitespace.contains('\n') { + needs_space = false; + } + } else { + self.write_comment(comment, last_loc.is_none())?; + if last_loc.is_some() && comment.has_newline_before { + needs_space = false; + } + } + } + Either::Right(item) => { + if !ignore_whitespace { + self.write_whitespace_separator(true)?; + if let Some(last_loc) = last_loc { + if needs_space || self.blank_lines(last_loc.end(), loc.start()) > 1 { + writeln!(self.buf())?; + } + } + } + if let Some(next_item) = items.last() { + needs_space = needs_space_fn(item, next_item); + } + trace!("Visiting {}", { + let n = std::any::type_name::(); + n.strip_prefix("solang_parser::pt::").unwrap_or(n) + }); + item.visit(self)?; + } + } + + last_loc = Some(loc); + last_byte_written = loc.end(); + if let Some(comment) = line_item.as_ref().left() { + if comment.is_line() { + last_byte_written = self + .find_next_line(last_byte_written) + .unwrap_or(last_byte_written); + } + } + } + + // write manually to avoid eof comment being detected as first + let comments = self.comments.remove_prefixes_before(loc.end()); + for comment in comments { + self.write_comment(&comment, false)?; + } + + let (unwritten_src_loc, mut unwritten_whitespace) = + unwritten_whitespace(last_byte_written, loc.end()); + if self.inline_config.is_disabled(unwritten_src_loc) { + if unwritten_src_loc.end() == self.source.len() { + // remove EOF line ending + unwritten_whitespace = unwritten_whitespace + .strip_suffix('\n') + .map(|w| w.strip_suffix('\r').unwrap_or(w)) + .unwrap_or(unwritten_whitespace); + } + trace!("Unwritten whitespace: {unwritten_whitespace:?}"); + self.write_raw(unwritten_whitespace)?; + } + + Ok(()) + } + + /// Visit the right side of an assignment. The function will try to write the assignment on a + /// single line or indented on the next line. If it can't do this it resorts to letting the + /// expression decide how to split iself on multiple lines + fn visit_assignment(&mut self, expr: &mut Expression) -> Result<()> { + if self.try_on_single_line(|fmt| expr.visit(fmt))? { + return Ok(()); + } + + self.write_postfix_comments_before(expr.loc().start())?; + self.write_prefix_comments_before(expr.loc().start())?; + + if self.try_on_single_line(|fmt| fmt.indented(1, |fmt| expr.visit(fmt)))? { + return Ok(()); + } + + let mut fit_on_next_line = false; + self.indented(1, |fmt| { + let tx = fmt.transact(|fmt| { + writeln!(fmt.buf())?; + fit_on_next_line = fmt.try_on_single_line(|fmt| expr.visit(fmt))?; + Ok(()) + })?; + if fit_on_next_line { + tx.commit()?; + } + Ok(()) + })?; + + if !fit_on_next_line { + self.indented_if(expr.is_unsplittable(), 1, |fmt| expr.visit(fmt))?; + } + + Ok(()) + } + + /// Visit the list of comma separated items. + /// If the prefix is not empty, then the function will write + /// the whitespace before the parentheses (if they are required). + fn visit_list( + &mut self, + prefix: &str, + items: &mut Vec, + start_offset: Option, + end_offset: Option, + paren_required: bool, + ) -> Result<()> + where + T: Visitable + CodeLocation, + { + write_chunk!(self, "{}", prefix)?; + let whitespace = if !prefix.is_empty() { " " } else { "" }; + let next_after_start_offset = items.first().map(|item| item.loc().start()); + let first_surrounding = SurroundingChunk::new("", start_offset, next_after_start_offset); + let last_surronding = SurroundingChunk::new(")", None, end_offset); + if items.is_empty() { + if paren_required { + write!(self.buf(), "{whitespace}(")?; + self.surrounded(first_surrounding, last_surronding, |fmt, _| { + // write comments before the list end + write_chunk!(fmt, end_offset.unwrap_or_default(), "")?; + Ok(()) + })?; + } + } else { + write!(self.buf(), "{whitespace}(")?; + self.surrounded(first_surrounding, last_surronding, |fmt, multiline| { + let args = + fmt.items_to_chunks(end_offset, items.iter_mut().map(|arg| (arg.loc(), arg)))?; + let multiline = + multiline && fmt.are_chunks_separated_multiline("{}", &args, ",")?; + fmt.write_chunks_separated(&args, ",", multiline)?; + Ok(()) + })?; + } + Ok(()) + } + + /// Visit the block item. Attempt to write it on the single + /// line if requested. Surround by curly braces and indent + /// each line otherwise. Returns `true` if the block fit + /// on a single line + fn visit_block( + &mut self, + loc: Loc, + statements: &mut Vec, + attempt_single_line: bool, + attempt_omit_braces: bool, + ) -> Result + where + T: Visitable + CodeLocation, + { + if attempt_single_line && statements.len() == 1 { + let fits_on_single = self.try_on_single_line(|fmt| { + if !attempt_omit_braces { + write!(fmt.buf(), "{{ ")?; + } + statements.first_mut().unwrap().visit(fmt)?; + if !attempt_omit_braces { + write!(fmt.buf(), " }}")?; + } + Ok(()) + })?; + + if fits_on_single { + return Ok(true); + } + } + + write_chunk!(self, "{{")?; + + if let Some(statement) = statements.first() { + self.write_whitespace_separator(true)?; + self.write_postfix_comments_before(CodeLocation::loc(statement).start())?; + } + + self.indented(1, |fmt| { + fmt.write_lined_visitable(loc, statements.iter_mut(), |_, _| false)?; + Ok(()) + })?; + + if !statements.is_empty() { + self.write_whitespace_separator(true)?; + } + write_chunk!(self, loc.end(), "}}")?; + + Ok(false) + } + + /// Visit statement as `Statement::Block`. + fn visit_stmt_as_block( + &mut self, + stmt: &mut Statement, + attempt_single_line: bool, + ) -> Result { + match stmt { + Statement::Block { + loc, statements, .. + } => self.visit_block(*loc, statements, attempt_single_line, true), + _ => self.visit_block(stmt.loc(), &mut vec![stmt], attempt_single_line, true), + } + } + + /// Visit the generic member access expression and + /// attempt flatten it by checking if the inner expression + /// matches a given member access variant. + fn visit_member_access<'b, T, M>( + &mut self, + expr: &'b mut Box, + ident: &mut Identifier, + mut matcher: M, + ) -> Result<()> + where + T: CodeLocation + Visitable, + M: FnMut(&mut Self, &'b mut Box) -> Result, &'b mut Identifier)>>, + { + let chunk_member_access = |fmt: &mut Self, ident: &mut Identifier, expr: &mut Box| { + fmt.chunked(ident.loc.start(), Some(expr.loc().start()), |fmt| { + ident.visit(fmt) + }) + }; + + let mut chunks: Vec = vec![chunk_member_access(self, ident, expr)?]; + let mut remaining = expr; + while let Some((inner_expr, inner_ident)) = matcher(self, remaining)? { + chunks.push(chunk_member_access(self, inner_ident, inner_expr)?); + remaining = inner_expr; + } + + chunks.reverse(); + chunks + .iter_mut() + .for_each(|chunk| chunk.content.insert(0, '.')); + + if !self.try_on_single_line(|fmt| fmt.write_chunks_separated(&chunks, "", false))? { + self.grouped(|fmt| fmt.write_chunks_separated(&chunks, "", true))?; + } + Ok(()) + } + + /// Visit the yul string with an optional identifier. + /// If the identifier is present, write the value in the format `:`. + /// Ref: https://docs.soliditylang.org/en/v0.8.15/yul.html#variable-declarations + fn visit_yul_string_with_ident( + &mut self, + loc: Loc, + val: &str, + ident: &mut Option, + ) -> Result<()> { + let ident = if let Some(ident) = ident { + format!(":{}", ident.name) + } else { + "".to_owned() + }; + write_chunk!(self, loc.start(), loc.end(), "{val}{ident}")?; + Ok(()) + } + + /// Format a quoted string as `prefix"string"` where the quote character is handled + /// by the configuration `quote_style` + fn quote_str(&self, loc: Loc, prefix: Option<&str>, string: &str) -> String { + let get_og_quote = || { + self.source[loc.range()] + .quote_state_char_indices() + .find_map(|(state, _, ch)| { + if matches!(state, QuoteState::Opening(_)) { + Some(ch) + } else { + None + } + }) + .expect("Could not find quote character for quoted string") + }; + let mut quote = self.config.quote_style.quote().unwrap_or_else(get_og_quote); + let mut quoted = format!("{quote}{string}{quote}"); + if !quoted.is_quoted() { + quote = get_og_quote(); + quoted = format!("{quote}{string}{quote}"); + } + let prefix = prefix.unwrap_or(""); + format!("{prefix}{quoted}") + } + + /// Write a quoted string. See `Formatter::quote_str` for more information + fn write_quoted_str(&mut self, loc: Loc, prefix: Option<&str>, string: &str) -> Result<()> { + write_chunk!( + self, + loc.start(), + loc.end(), + "{}", + self.quote_str(loc, prefix, string) + ) + } + + /// Write and format numbers. This will fix underscores as well as remove unnecessary 0's and + /// exponents + fn write_num_literal( + &mut self, + loc: Loc, + value: &str, + fractional: Option<&str>, + exponent: &str, + unit: &mut Option, + ) -> Result<()> { + let config = self.config.number_underscore; + + // get source if we preserve underscores + let (value, fractional, exponent) = if matches!(config, NumberUnderscore::Preserve) { + let source = &self.source[loc.start()..loc.end()]; + // Strip unit + let (source, _) = source.split_once(' ').unwrap_or((source, "")); + let (val, exp) = source.split_once(['e', 'E']).unwrap_or((source, "")); + let (val, fract) = val + .split_once('.') + .map(|(val, fract)| (val, Some(fract))) + .unwrap_or((val, None)); + ( + val.trim().to_string(), + fract.map(|fract| fract.trim().to_string()), + exp.trim().to_string(), + ) + } else { + // otherwise strip underscores + ( + value.trim().replace('_', ""), + fractional.map(|fract| fract.trim().replace('_', "")), + exponent.trim().replace('_', ""), + ) + }; + + // strip any padded 0's + let val = value.trim_start_matches('0'); + let fract = fractional.as_ref().map(|fract| fract.trim_end_matches('0')); + let (exp_sign, mut exp) = if let Some(exp) = exponent.strip_prefix('-') { + ("-", exp) + } else { + ("", exponent.as_str()) + }; + exp = exp.trim().trim_start_matches('0'); + + let add_underscores = |string: &str, reversed: bool| -> String { + if !matches!(config, NumberUnderscore::Thousands) || string.len() < 5 { + return string.to_string(); + } + if reversed { + Box::new(string.as_bytes().chunks(3)) as Box> + } else { + Box::new(string.as_bytes().rchunks(3).rev()) as Box> + } + .map(|chunk| std::str::from_utf8(chunk).expect("valid utf8 content.")) + .collect::>() + .join("_") + }; + + let mut out = String::new(); + if val.is_empty() { + out.push('0'); + } else { + out.push_str(&add_underscores(val, false)); + } + if let Some(fract) = fract { + out.push('.'); + if fract.is_empty() { + out.push('0'); + } else { + // TODO re-enable me on the next solang-parser v0.1.18 + // currently disabled because of the following bug + // https://github.com/hyperledger-labs/solang/pull/954 + // out.push_str(&add_underscores(fract, true)); + out.push_str(fract) + } + } + if !exp.is_empty() { + out.push('e'); + out.push_str(exp_sign); + out.push_str(&add_underscores(exp, false)); + } + + write_chunk!(self, loc.start(), loc.end(), "{out}")?; + self.write_unit(unit) + } + + /// Write built-in unit. + fn write_unit(&mut self, unit: &mut Option) -> Result<()> { + if let Some(unit) = unit { + write_chunk!(self, unit.loc.start(), unit.loc.end(), "{}", unit.name)?; + } + Ok(()) + } + + /// Write the function header + fn write_function_header( + &mut self, + func: &mut FunctionDefinition, + body_loc: Option, + header_multiline: bool, + ) -> Result { + let func_name = if let Some(ident) = &func.name { + format!("{} {}", func.ty, ident.name) + } else { + func.ty.to_string() + }; + + // calculate locations of chunk groups + let attrs_loc = func.attributes.first().map(|attr| attr.loc()); + let returns_loc = func.returns.first().map(|param| param.0); + + let params_next_offset = attrs_loc + .as_ref() + .or(returns_loc.as_ref()) + .or(body_loc.as_ref()) + .map(|loc| loc.start()); + let attrs_end = returns_loc + .as_ref() + .or(body_loc.as_ref()) + .map(|loc| loc.start()); + let returns_end = body_loc.as_ref().map(|loc| loc.start()); + + let mut params_multiline = false; + + let params_loc = { + let mut loc = func.loc.with_end(func.loc.start()); + self.extend_loc_until(&mut loc, ')'); + loc + }; + if self.inline_config.is_disabled(params_loc) { + let chunk = self.chunked(func.loc.start(), None, |fmt| fmt.visit_source(params_loc))?; + params_multiline = chunk.content.contains('\n'); + self.write_chunk(&chunk)?; + } else { + let first_surrounding = SurroundingChunk::new( + format!("{func_name}("), + Some(func.loc.start()), + Some( + func.params + .first() + .map(|param| param.0.start()) + .unwrap_or_else(|| params_loc.end()), + ), + ); + self.surrounded( + first_surrounding, + SurroundingChunk::new(")", None, params_next_offset), + |fmt, multiline| { + let params = fmt.items_to_chunks( + params_next_offset, + func.params + .iter_mut() + .filter_map(|(loc, param)| param.as_mut().map(|param| (*loc, param))), + )?; + let after_params = if !func.attributes.is_empty() || !func.returns.is_empty() { + "" + } else if func.body.is_some() { + " {" + } else { + ";" + }; + let should_multiline = header_multiline + && matches!( + fmt.config.multiline_func_header, + MultilineFuncHeaderStyle::ParamsFirst | MultilineFuncHeaderStyle::All + ); + params_multiline = should_multiline + || multiline + || fmt.are_chunks_separated_multiline( + &format!("{{}}){after_params}"), + ¶ms, + ",", + )?; + fmt.write_chunks_separated(¶ms, ",", params_multiline)?; + Ok(()) + }, + )?; + } + + let mut write_attributes = |fmt: &mut Self, multiline: bool| -> Result<()> { + // write attributes + if !func.attributes.is_empty() { + let attrs_loc = func + .attributes + .first() + .unwrap() + .loc() + .with_end_from(&func.attributes.last().unwrap().loc()); + if fmt.inline_config.is_disabled(attrs_loc) { + fmt.indented(1, |fmt| fmt.visit_source(attrs_loc))?; + } else { + fmt.write_postfix_comments_before(attrs_loc.start())?; + fmt.write_whitespace_separator(multiline)?; + let attributes = + fmt.items_to_chunks_sorted(attrs_end, func.attributes.iter_mut())?; + fmt.indented(1, |fmt| { + fmt.write_chunks_separated(&attributes, "", multiline)?; + Ok(()) + })?; + } + } + + // write returns + if !func.returns.is_empty() { + let returns_start_loc = func.returns.first().unwrap().0; + let returns_loc = returns_start_loc.with_end_from(&func.returns.last().unwrap().0); + if fmt.inline_config.is_disabled(returns_loc) { + fmt.indented(1, |fmt| fmt.visit_source(returns_loc))?; + } else { + let returns = fmt.items_to_chunks( + returns_end, + func.returns + .iter_mut() + .filter_map(|(loc, param)| param.as_mut().map(|param| (*loc, param))), + )?; + fmt.write_postfix_comments_before(returns_loc.start())?; + fmt.write_whitespace_separator(multiline)?; + fmt.indented(1, |fmt| { + fmt.surrounded( + SurroundingChunk::new("returns (", Some(returns_loc.start()), None), + SurroundingChunk::new(")", None, returns_end), + |fmt, multiline_hint| { + fmt.write_chunks_separated(&returns, ",", multiline_hint)?; + Ok(()) + }, + )?; + Ok(()) + })?; + } + } + Ok(()) + }; + + let should_multiline = header_multiline + && if params_multiline { + matches!( + self.config.multiline_func_header, + MultilineFuncHeaderStyle::All + ) + } else { + matches!( + self.config.multiline_func_header, + MultilineFuncHeaderStyle::AttributesFirst + ) + }; + let attrs_multiline = should_multiline + || !self.try_on_single_line(|fmt| { + write_attributes(fmt, false)?; + if !fmt.will_it_fit(if func.body.is_some() { " {" } else { ";" }) { + bail!(FormatterError::fmt()) + } + Ok(()) + })?; + if attrs_multiline { + write_attributes(self, true)?; + } + Ok(attrs_multiline) + } + + /// Write potentially nested `if statements` + fn write_if_stmt( + &mut self, + loc: Loc, + cond: &mut Expression, + if_branch: &mut Box, + else_branch: &mut Option>, + ) -> Result<(), FormatterError> { + let single_line_stmt_wide = self.context.if_stmt_single_line.unwrap_or_default(); + + visit_source_if_disabled_else!(self, loc.with_end(if_branch.loc().start()), { + self.surrounded( + SurroundingChunk::new("if (", Some(loc.start()), Some(cond.loc().start())), + SurroundingChunk::new(")", None, Some(if_branch.loc().start())), + |fmt, _| { + cond.visit(fmt)?; + fmt.write_postfix_comments_before(if_branch.loc().start()) + }, + )?; + }); + + let cond_close_paren_loc = self + .find_next_in_src(cond.loc().end(), ')') + .unwrap_or_else(|| cond.loc().end()); + let attempt_single_line = single_line_stmt_wide + && self.should_attempt_block_single_line(if_branch.as_mut(), cond_close_paren_loc); + let if_branch_is_single_line = self.visit_stmt_as_block(if_branch, attempt_single_line)?; + if single_line_stmt_wide && !if_branch_is_single_line { + bail!(FormatterError::fmt()) + } + + if let Some(else_branch) = else_branch { + self.write_postfix_comments_before(else_branch.loc().start())?; + if if_branch_is_single_line { + writeln!(self.buf())?; + } + write_chunk!(self, else_branch.loc().start(), "else")?; + if let Statement::If(loc, cond, if_branch, else_branch) = else_branch.as_mut() { + self.visit_if(*loc, cond, if_branch, else_branch, false)?; + } else { + let else_branch_is_single_line = + self.visit_stmt_as_block(else_branch, if_branch_is_single_line)?; + if single_line_stmt_wide && !else_branch_is_single_line { + bail!(FormatterError::fmt()) + } + } + } + Ok(()) + } +} + +// Traverse the Solidity Parse Tree and write to the code formatter +impl<'a, W: Write> Visitor for Formatter<'a, W> { + type Error = FormatterError; + + #[instrument(name = "source", skip(self))] + fn visit_source(&mut self, loc: Loc) -> Result<()> { + let source = String::from_utf8(self.source.as_bytes()[loc.range()].to_vec()) + .map_err(FormatterError::custom)?; + let mut lines = source.splitn(2, '\n'); + + write_chunk!(self, loc.start(), "{}", lines.next().unwrap())?; + if let Some(remainder) = lines.next() { + // Call with `self.write_str` and not `write!`, so we can have `\n` at the beginning + // without triggering an indentation + self.write_raw(format!("\n{remainder}"))?; + } + + let _ = self.comments.remove_all_comments_before(loc.end()); + + Ok(()) + } + + #[instrument(name = "SU", skip_all)] + fn visit_source_unit(&mut self, source_unit: &mut SourceUnit) -> Result<()> { + // TODO: do we need to put pragma and import directives at the top of the file? + // source_unit.0.sort_by_key(|item| match item { + // SourceUnitPart::PragmaDirective(_, _, _) => 0, + // SourceUnitPart::ImportDirective(_, _) => 1, + // _ => usize::MAX, + // }); + let loc = Loc::File( + source_unit + .loc_opt() + .or_else(|| self.comments.iter().next().map(|comment| comment.loc)) + .map(|loc| loc.file_no()) + .unwrap_or_default(), + 0, + self.source.len(), + ); + + self.write_lined_visitable( + loc, + source_unit.0.iter_mut(), + |last_unit, unit| match last_unit { + SourceUnitPart::PragmaDirective(..) => { + !matches!(unit, SourceUnitPart::PragmaDirective(..)) + } + SourceUnitPart::ImportDirective(_) => { + !matches!(unit, SourceUnitPart::ImportDirective(_)) + } + SourceUnitPart::ErrorDefinition(_) => { + !matches!(unit, SourceUnitPart::ErrorDefinition(_)) + } + SourceUnitPart::Using(_) => !matches!(unit, SourceUnitPart::Using(_)), + SourceUnitPart::VariableDefinition(_) => { + !matches!(unit, SourceUnitPart::VariableDefinition(_)) + } + SourceUnitPart::Annotation(_) => false, + _ => true, + }, + )?; + + // EOF newline + if self.last_char().map_or(true, |char| char != '\n') { + writeln!(self.buf())?; + } + + Ok(()) + } + + #[instrument(name = "contract", skip_all)] + fn visit_contract(&mut self, contract: &mut ContractDefinition) -> Result<()> { + return_source_if_disabled!(self, contract.loc); + + self.with_contract_context(contract.clone(), |fmt| { + let contract_name = contract.name.safe_unwrap(); + + visit_source_if_disabled_else!( + fmt, + contract.loc.with_end_from( + &contract + .base + .first() + .map(|b| b.loc) + .unwrap_or(contract_name.loc) + ), + { + fmt.grouped(|fmt| { + write_chunk!(fmt, contract.loc.start(), "{}", contract.ty)?; + write_chunk!(fmt, contract_name.loc.end(), "{}", contract_name.name)?; + if !contract.base.is_empty() { + write_chunk!( + fmt, + contract_name.loc.end(), + contract.base.first().unwrap().loc.start(), + "is" + )?; + } + Ok(()) + })?; + } + ); + + if !contract.base.is_empty() { + visit_source_if_disabled_else!( + fmt, + contract + .base + .first() + .unwrap() + .loc + .with_end_from(&contract.base.last().unwrap().loc), + { + fmt.indented(1, |fmt| { + let base_end = contract.parts.first().map(|part| part.loc().start()); + let bases = fmt.items_to_chunks( + base_end, + contract.base.iter_mut().map(|base| (base.loc, base)), + )?; + let multiline = + fmt.are_chunks_separated_multiline("{}", &bases, ",")?; + fmt.write_chunks_separated(&bases, ",", multiline)?; + fmt.write_whitespace_separator(multiline)?; + Ok(()) + })?; + } + ); + } + + write_chunk!(fmt, "{{")?; + + fmt.indented(1, |fmt| { + if let Some(first) = contract.parts.first() { + fmt.write_postfix_comments_before(first.loc().start())?; + fmt.write_whitespace_separator(true)?; + } else { + return Ok(()); + } + + if fmt.config.contract_new_lines { + write_chunk!(fmt, "\n")?; + } + + fmt.write_lined_visitable( + contract.loc, + contract.parts.iter_mut(), + |last_part, part| match last_part { + ContractPart::ErrorDefinition(_) => { + !matches!(part, ContractPart::ErrorDefinition(_)) + } + ContractPart::EventDefinition(_) => { + !matches!(part, ContractPart::EventDefinition(_)) + } + ContractPart::VariableDefinition(_) => { + !matches!(part, ContractPart::VariableDefinition(_)) + } + ContractPart::TypeDefinition(_) => { + !matches!(part, ContractPart::TypeDefinition(_)) + } + ContractPart::EnumDefinition(_) => { + !matches!(part, ContractPart::EnumDefinition(_)) + } + ContractPart::Using(_) => !matches!(part, ContractPart::Using(_)), + ContractPart::FunctionDefinition(last_def) => { + if last_def.is_empty() { + match part { + ContractPart::FunctionDefinition(def) => !def.is_empty(), + _ => true, + } + } else { + true + } + } + ContractPart::Annotation(_) => false, + _ => true, + }, + ) + })?; + + if !contract.parts.is_empty() { + fmt.write_whitespace_separator(true)?; + + if fmt.config.contract_new_lines { + write_chunk!(fmt, "\n")?; + } + } + + write_chunk!(fmt, contract.loc.end(), "}}")?; + + Ok(()) + })?; + + Ok(()) + } + + #[instrument(name = "pragma", skip_all)] + fn visit_pragma( + &mut self, + loc: Loc, + ident: &mut Option, + string: &mut Option, + ) -> Result<()> { + let (ident, string) = (ident.safe_unwrap(), string.safe_unwrap()); + return_source_if_disabled!(self, loc, ';'); + + #[allow(clippy::if_same_then_else)] + let pragma_descriptor = if ident.name == "solidity" { + // There are some issues with parsing Solidity's versions with crates like `semver`: + // 1. Ranges like `>=0.4.21<0.6.0` or `>=0.4.21 <0.6.0` are not parseable at all. + // 2. Versions like `0.8.10` got transformed into `^0.8.10` which is not the same. + // TODO: semver-solidity crate :D + &string.string + } else { + &string.string + }; + + write_chunk!( + self, + string.loc.end(), + "pragma {} {};", + &ident.name, + pragma_descriptor + )?; + + Ok(()) + } + + #[instrument(name = "import_plain", skip_all)] + fn visit_import_plain(&mut self, loc: Loc, import: &mut ImportPath) -> Result<()> { + return_source_if_disabled!(self, loc, ';'); + + self.grouped(|fmt| { + write_chunk!(fmt, loc.start(), import.loc().start(), "import")?; + fmt.write_quoted_str(import.loc(), None, &import_path_string(import))?; + fmt.write_semicolon()?; + Ok(()) + })?; + Ok(()) + } + + #[instrument(name = "import_global", skip_all)] + fn visit_import_global( + &mut self, + loc: Loc, + global: &mut ImportPath, + alias: &mut Identifier, + ) -> Result<()> { + return_source_if_disabled!(self, loc, ';'); + + self.grouped(|fmt| { + write_chunk!(fmt, loc.start(), global.loc().start(), "import")?; + fmt.write_quoted_str(global.loc(), None, &import_path_string(global))?; + write_chunk!(fmt, loc.start(), alias.loc.start(), "as")?; + alias.visit(fmt)?; + fmt.write_semicolon()?; + Ok(()) + })?; + Ok(()) + } + + #[instrument(name = "import_renames", skip_all)] + fn visit_import_renames( + &mut self, + loc: Loc, + imports: &mut [(Identifier, Option)], + from: &mut ImportPath, + ) -> Result<()> { + return_source_if_disabled!(self, loc, ';'); + + if imports.is_empty() { + self.grouped(|fmt| { + write_chunk!(fmt, loc.start(), "import")?; + fmt.write_empty_brackets()?; + write_chunk!(fmt, loc.start(), from.loc().start(), "from")?; + fmt.write_quoted_str(from.loc(), None, &import_path_string(from))?; + fmt.write_semicolon()?; + Ok(()) + })?; + return Ok(()); + } + + let imports_start = imports.first().unwrap().0.loc.start(); + + write_chunk!(self, loc.start(), imports_start, "import")?; + + self.surrounded( + SurroundingChunk::new("{", Some(imports_start), None), + SurroundingChunk::new("}", None, Some(from.loc().start())), + |fmt, _multiline| { + let mut imports = imports.iter_mut().peekable(); + let mut import_chunks = Vec::new(); + while let Some((ident, alias)) = imports.next() { + import_chunks.push(fmt.chunked( + ident.loc.start(), + imports.peek().map(|(ident, _)| ident.loc.start()), + |fmt| { + fmt.grouped(|fmt| { + ident.visit(fmt)?; + if let Some(alias) = alias { + write_chunk!(fmt, ident.loc.end(), alias.loc.start(), "as")?; + alias.visit(fmt)?; + } + Ok(()) + })?; + Ok(()) + }, + )?); + } + + let multiline = fmt.are_chunks_separated_multiline( + &format!("{{}} }} from \"{}\";", import_path_string(from)), + &import_chunks, + ",", + )?; + fmt.write_chunks_separated(&import_chunks, ",", multiline)?; + Ok(()) + }, + )?; + + self.grouped(|fmt| { + write_chunk!(fmt, imports_start, from.loc().start(), "from")?; + fmt.write_quoted_str(from.loc(), None, &import_path_string(from))?; + fmt.write_semicolon()?; + Ok(()) + })?; + + Ok(()) + } + + #[instrument(name = "enum", skip_all)] + fn visit_enum(&mut self, enumeration: &mut EnumDefinition) -> Result<()> { + return_source_if_disabled!(self, enumeration.loc); + + let enum_name = enumeration.name.safe_unwrap_mut(); + let mut name = + self.visit_to_chunk(enum_name.loc.start(), Some(enum_name.loc.end()), enum_name)?; + name.content = format!("enum {}", name.content); + self.write_chunk(&name)?; + + if enumeration.values.is_empty() { + self.write_empty_brackets()?; + } else { + self.surrounded( + SurroundingChunk::new( + "{", + Some( + enumeration + .values + .first_mut() + .unwrap() + .safe_unwrap() + .loc + .start(), + ), + None, + ), + SurroundingChunk::new("}", None, Some(enumeration.loc.end())), + |fmt, _multiline| { + let values = fmt.items_to_chunks( + Some(enumeration.loc.end()), + enumeration.values.iter_mut().map(|ident| { + let ident = ident.safe_unwrap_mut(); + (ident.loc, ident) + }), + )?; + fmt.write_chunks_separated(&values, ",", true)?; + Ok(()) + }, + )?; + } + + Ok(()) + } + + #[instrument(name = "expr", skip_all)] + fn visit_expr(&mut self, loc: Loc, expr: &mut Expression) -> Result<()> { + return_source_if_disabled!(self, loc); + + match expr { + Expression::Type(loc, typ) => match typ { + Type::Address => write_chunk!(self, loc.start(), "address")?, + Type::AddressPayable => write_chunk!(self, loc.start(), "address payable")?, + Type::Payable => write_chunk!(self, loc.start(), "payable")?, + Type::Bool => write_chunk!(self, loc.start(), "bool")?, + Type::String => write_chunk!(self, loc.start(), "string")?, + Type::Bytes(n) => write_chunk!(self, loc.start(), "bytes{}", n)?, + Type::Rational => write_chunk!(self, loc.start(), "rational")?, + Type::DynamicBytes => write_chunk!(self, loc.start(), "bytes")?, + Type::Int(ref n) | Type::Uint(ref n) => { + let int = if matches!(typ, Type::Int(_)) { + "int" + } else { + "uint" + }; + match n { + 256 => match self.config.int_types { + IntTypes::Long => write_chunk!(self, loc.start(), "{int}{n}")?, + IntTypes::Short => write_chunk!(self, loc.start(), "{int}")?, + IntTypes::Preserve => self.visit_source(*loc)?, + }, + _ => write_chunk!(self, loc.start(), "{int}{n}")?, + } + } + Type::Mapping { + loc, + key, + key_name, + value, + value_name, + } => { + let arrow_loc = self.find_next_str_in_src(loc.start(), "=>"); + let close_paren_loc = self + .find_next_in_src(value.loc().end(), ')') + .unwrap_or(loc.end()); + let first = SurroundingChunk::new( + "mapping(", + Some(loc.start()), + Some(key.loc().start()), + ); + let last = SurroundingChunk::new(")", Some(close_paren_loc), Some(loc.end())) + .non_spaced(); + self.surrounded(first, last, |fmt, multiline| { + fmt.grouped(|fmt| { + key.visit(fmt)?; + + if let Some(name) = key_name { + let end_loc = arrow_loc.unwrap_or(value.loc().start()); + write_chunk!(fmt, name.loc.start(), end_loc, " {}", name)?; + } else if let Some(arrow_loc) = arrow_loc { + fmt.write_postfix_comments_before(arrow_loc)?; + } + + let mut write_arrow_and_value = |fmt: &mut Self| { + write!(fmt.buf(), "=> ")?; + value.visit(fmt)?; + if let Some(name) = value_name { + write_chunk!(fmt, name.loc.start(), " {}", name)?; + } + Ok(()) + }; + + let rest_str = fmt.simulate_to_string(&mut write_arrow_and_value)?; + let multiline = multiline && !fmt.will_it_fit(rest_str); + fmt.write_whitespace_separator(multiline)?; + + write_arrow_and_value(fmt)?; + + fmt.write_postfix_comments_before(close_paren_loc)?; + fmt.write_prefix_comments_before(close_paren_loc) + })?; + Ok(()) + })?; + } + Type::Function { .. } => self.visit_source(*loc)?, + }, + Expression::BoolLiteral(loc, val) => { + write_chunk!(self, loc.start(), loc.end(), "{val}")?; + } + Expression::NumberLiteral(loc, val, exp, unit) => { + self.write_num_literal(*loc, val, None, exp, unit)?; + } + Expression::HexNumberLiteral(loc, val, unit) => { + // ref: https://docs.soliditylang.org/en/latest/types.html?highlight=address%20literal#address-literals + let val = if val.len() == 42 { + to_checksum(&H160::from_str(val).expect(""), None) + } else { + val.to_owned() + }; + write_chunk!(self, loc.start(), loc.end(), "{val}")?; + self.write_unit(unit)?; + } + Expression::RationalNumberLiteral(loc, val, fraction, exp, unit) => { + self.write_num_literal(*loc, val, Some(fraction), exp, unit)?; + } + Expression::StringLiteral(vals) => { + for StringLiteral { + loc, + string, + unicode, + } in vals + { + let prefix = if *unicode { Some("unicode") } else { None }; + self.write_quoted_str(*loc, prefix, string)?; + } + } + Expression::HexLiteral(vals) => { + for HexLiteral { loc, hex } in vals { + self.write_quoted_str(*loc, Some("hex"), hex)?; + } + } + Expression::AddressLiteral(loc, val) => { + // support of solana/substrate address literals + self.write_quoted_str(*loc, Some("address"), val)?; + } + Expression::Parenthesis(loc, expr) => { + self.surrounded( + SurroundingChunk::new("(", Some(loc.start()), None), + SurroundingChunk::new(")", None, Some(loc.end())), + |fmt, _| expr.visit(fmt), + )?; + } + Expression::ArraySubscript(_, ty_exp, index_expr) => { + ty_exp.visit(self)?; + write!(self.buf(), "[")?; + index_expr + .as_mut() + .map(|index| index.visit(self)) + .transpose()?; + write!(self.buf(), "]")?; + } + Expression::ArraySlice(loc, expr, start, end) => { + expr.visit(self)?; + write!(self.buf(), "[")?; + let mut write_slice = |fmt: &mut Self, multiline| -> Result<()> { + if multiline { + fmt.write_whitespace_separator(true)?; + } + fmt.grouped(|fmt| { + start.as_mut().map(|start| start.visit(fmt)).transpose()?; + write!(fmt.buf(), ":")?; + if let Some(end) = end { + let mut chunk = + fmt.chunked(end.loc().start(), Some(loc.end()), |fmt| { + end.visit(fmt) + })?; + if chunk.prefixes.is_empty() + && chunk.postfixes_before.is_empty() + && (start.is_none() || fmt.will_it_fit(&chunk.content)) + { + chunk.needs_space = Some(false); + } + fmt.write_chunk(&chunk)?; + } + Ok(()) + })?; + if multiline { + fmt.write_whitespace_separator(true)?; + } + Ok(()) + }; + + if !self.try_on_single_line(|fmt| write_slice(fmt, false))? { + self.indented(1, |fmt| write_slice(fmt, true))?; + } + + write!(self.buf(), "]")?; + } + Expression::ArrayLiteral(loc, exprs) => { + write_chunk!(self, loc.start(), "[")?; + let chunks = self.items_to_chunks( + Some(loc.end()), + exprs.iter_mut().map(|expr| (expr.loc(), expr)), + )?; + let multiline = self.are_chunks_separated_multiline("{}]", &chunks, ",")?; + self.indented_if(multiline, 1, |fmt| { + fmt.write_chunks_separated(&chunks, ",", multiline)?; + if multiline { + fmt.write_postfix_comments_before(loc.end())?; + fmt.write_prefix_comments_before(loc.end())?; + fmt.write_whitespace_separator(true)?; + } + Ok(()) + })?; + write_chunk!(self, loc.end(), "]")?; + } + Expression::PreIncrement(..) + | Expression::PostIncrement(..) + | Expression::PreDecrement(..) + | Expression::PostDecrement(..) + | Expression::Not(..) + | Expression::UnaryPlus(..) + | Expression::Add(..) + | Expression::Negate(..) + | Expression::Subtract(..) + | Expression::Power(..) + | Expression::Multiply(..) + | Expression::Divide(..) + | Expression::Modulo(..) + | Expression::ShiftLeft(..) + | Expression::ShiftRight(..) + | Expression::BitwiseNot(..) + | Expression::BitwiseAnd(..) + | Expression::BitwiseXor(..) + | Expression::BitwiseOr(..) + | Expression::Less(..) + | Expression::More(..) + | Expression::LessEqual(..) + | Expression::MoreEqual(..) + | Expression::And(..) + | Expression::Or(..) + | Expression::Equal(..) + | Expression::NotEqual(..) => { + let spaced = expr.has_space_around(); + let op = expr.operator().unwrap(); + + match expr.components_mut() { + (Some(left), Some(right)) => { + left.visit(self)?; + + let right_chunk = + self.chunked(right.loc().start(), Some(loc.end()), |fmt| { + write_chunk!(fmt, right.loc().start(), "{op}")?; + right.visit(fmt)?; + Ok(()) + })?; + + self.grouped(|fmt| fmt.write_chunk(&right_chunk))?; + } + (Some(left), None) => { + left.visit(self)?; + write_chunk_spaced!(self, loc.end(), Some(spaced), "{op}")?; + } + (None, Some(right)) => { + write_chunk!(self, right.loc().start(), "{op}")?; + let mut right_chunk = + self.visit_to_chunk(right.loc().end(), Some(loc.end()), right)?; + right_chunk.needs_space = Some(spaced); + self.write_chunk(&right_chunk)?; + } + (None, None) => {} + } + } + Expression::Assign(..) + | Expression::AssignOr(..) + | Expression::AssignAnd(..) + | Expression::AssignXor(..) + | Expression::AssignShiftLeft(..) + | Expression::AssignShiftRight(..) + | Expression::AssignAdd(..) + | Expression::AssignSubtract(..) + | Expression::AssignMultiply(..) + | Expression::AssignDivide(..) + | Expression::AssignModulo(..) => { + let op = expr.operator().unwrap(); + let (left, right) = expr.components_mut(); + let (left, right) = (left.unwrap(), right.unwrap()); + + left.visit(self)?; + write_chunk!(self, "{op}")?; + self.visit_assignment(right)?; + } + Expression::ConditionalOperator(loc, cond, first_expr, second_expr) => { + cond.visit(self)?; + + let first_expr = self.chunked( + first_expr.loc().start(), + Some(second_expr.loc().start()), + |fmt| { + write_chunk!(fmt, "?")?; + first_expr.visit(fmt) + }, + )?; + let second_expr = + self.chunked(second_expr.loc().start(), Some(loc.end()), |fmt| { + write_chunk!(fmt, ":")?; + second_expr.visit(fmt) + })?; + + let chunks = vec![first_expr, second_expr]; + if !self.try_on_single_line(|fmt| fmt.write_chunks_separated(&chunks, "", false))? { + self.grouped(|fmt| fmt.write_chunks_separated(&chunks, "", true))?; + } + } + Expression::Variable(ident) => { + write_chunk!(self, loc.end(), "{}", ident.name)?; + } + Expression::MemberAccess(_, expr, ident) => { + self.visit_member_access(expr, ident, |fmt, expr| match expr.as_mut() { + Expression::MemberAccess(_, inner_expr, inner_ident) => { + Ok(Some((inner_expr, inner_ident))) + } + expr => { + expr.visit(fmt)?; + Ok(None) + } + })?; + } + Expression::List(loc, items) => { + self.surrounded( + SurroundingChunk::new( + "(", + Some(loc.start()), + items.first().map(|item| item.0.start()), + ), + SurroundingChunk::new(")", None, Some(loc.end())), + |fmt, _| { + let items = fmt.items_to_chunks( + Some(loc.end()), + items.iter_mut().map(|(loc, item)| (*loc, item)), + )?; + let write_items = |fmt: &mut Self, multiline| { + fmt.write_chunks_separated(&items, ",", multiline) + }; + if !fmt.try_on_single_line(|fmt| write_items(fmt, false))? { + write_items(fmt, true)?; + } + Ok(()) + }, + )?; + } + Expression::FunctionCall(loc, expr, exprs) => { + self.visit_expr(expr.loc(), expr)?; + self.visit_list("", exprs, Some(expr.loc().end()), Some(loc.end()), true)?; + } + Expression::NamedFunctionCall(loc, expr, args) => { + self.visit_expr(expr.loc(), expr)?; + write!(self.buf(), "(")?; + self.visit_args(*loc, args)?; + write!(self.buf(), ")")?; + } + Expression::FunctionCallBlock(_, expr, stmt) => { + expr.visit(self)?; + stmt.visit(self)?; + } + _ => self.visit_source(loc)?, + }; + + Ok(()) + } + + #[instrument(name = "ident", skip_all)] + fn visit_ident(&mut self, loc: Loc, ident: &mut Identifier) -> Result<()> { + return_source_if_disabled!(self, loc); + write_chunk!(self, loc.end(), "{}", ident.name)?; + Ok(()) + } + + #[instrument(name = "ident_path", skip_all)] + fn visit_ident_path(&mut self, idents: &mut IdentifierPath) -> Result<(), Self::Error> { + if idents.identifiers.is_empty() { + return Ok(()); + } + return_source_if_disabled!(self, idents.loc); + + idents.identifiers.iter_mut().skip(1).for_each(|chunk| { + if !chunk.name.starts_with('.') { + chunk.name.insert(0, '.') + } + }); + let chunks = self.items_to_chunks( + Some(idents.loc.end()), + idents + .identifiers + .iter_mut() + .map(|ident| (ident.loc, ident)), + )?; + self.grouped(|fmt| { + let multiline = fmt.are_chunks_separated_multiline("{}", &chunks, "")?; + fmt.write_chunks_separated(&chunks, "", multiline) + })?; + Ok(()) + } + + #[instrument(name = "emit", skip_all)] + fn visit_emit(&mut self, loc: Loc, event: &mut Expression) -> Result<()> { + return_source_if_disabled!(self, loc); + write_chunk!(self, loc.start(), "emit")?; + event.visit(self)?; + self.write_semicolon()?; + Ok(()) + } + + #[instrument(name = "var_declaration", skip_all)] + fn visit_var_declaration(&mut self, var: &mut VariableDeclaration) -> Result<()> { + return_source_if_disabled!(self, var.loc); + self.grouped(|fmt| { + var.ty.visit(fmt)?; + if let Some(storage) = &var.storage { + write_chunk!(fmt, storage.loc().end(), "{storage}")?; + } + let var_name = var.name.safe_unwrap(); + write_chunk!(fmt, var_name.loc.end(), "{var_name}") + })?; + Ok(()) + } + + #[instrument(name = "break", skip_all)] + fn visit_break(&mut self, loc: Loc, semicolon: bool) -> Result<()> { + if semicolon { + return_source_if_disabled!(self, loc, ';'); + } else { + return_source_if_disabled!(self, loc); + } + write_chunk!( + self, + loc.start(), + loc.end(), + "break{}", + if semicolon { ";" } else { "" } + ) + } + + #[instrument(name = "continue", skip_all)] + fn visit_continue(&mut self, loc: Loc, semicolon: bool) -> Result<()> { + if semicolon { + return_source_if_disabled!(self, loc, ';'); + } else { + return_source_if_disabled!(self, loc); + } + write_chunk!( + self, + loc.start(), + loc.end(), + "continue{}", + if semicolon { ";" } else { "" } + ) + } + + #[instrument(name = "function", skip_all)] + fn visit_function(&mut self, func: &mut FunctionDefinition) -> Result<()> { + if func.body.is_some() { + return_source_if_disabled!(self, func.loc()); + } else { + return_source_if_disabled!(self, func.loc(), ';'); + } + + self.with_function_context(func.clone(), |fmt| { + fmt.write_postfix_comments_before(func.loc.start())?; + fmt.write_prefix_comments_before(func.loc.start())?; + + let body_loc = func.body.as_ref().map(CodeLocation::loc); + let mut attrs_multiline = false; + let fits_on_single = fmt.try_on_single_line(|fmt| { + fmt.write_function_header(func, body_loc, false)?; + Ok(()) + })?; + if !fits_on_single { + attrs_multiline = fmt.write_function_header(func, body_loc, true)?; + } + + // write function body + match &mut func.body { + Some(body) => { + let body_loc = body.loc(); + let byte_offset = body_loc.start(); + let body = fmt.visit_to_chunk(byte_offset, Some(body_loc.end()), body)?; + fmt.write_whitespace_separator( + attrs_multiline && !(func.attributes.is_empty() && func.returns.is_empty()), + )?; + fmt.write_chunk(&body)?; + } + None => fmt.write_semicolon()?, + } + + Ok(()) + })?; + + Ok(()) + } + + #[instrument(name = "function_attribute", skip_all)] + fn visit_function_attribute(&mut self, attribute: &mut FunctionAttribute) -> Result<()> { + return_source_if_disabled!(self, attribute.loc()); + + match attribute { + FunctionAttribute::Mutability(mutability) => { + write_chunk!(self, mutability.loc().end(), "{mutability}")? + } + FunctionAttribute::Visibility(visibility) => { + // Visibility will always have a location in a Function attribute + write_chunk!(self, visibility.loc_opt().unwrap().end(), "{visibility}")? + } + FunctionAttribute::Virtual(loc) => write_chunk!(self, loc.end(), "virtual")?, + FunctionAttribute::Immutable(loc) => write_chunk!(self, loc.end(), "immutable")?, + FunctionAttribute::Override(loc, args) => { + write_chunk!(self, loc.start(), "override")?; + if !args.is_empty() && self.config.override_spacing { + self.write_whitespace_separator(false)?; + } + self.visit_list("", args, None, Some(loc.end()), false)? + } + FunctionAttribute::BaseOrModifier(loc, base) => { + let is_contract_base = self.context.contract.as_ref().map_or(false, |contract| { + contract.base.iter().any(|contract_base| { + contract_base + .name + .identifiers + .iter() + .zip(&base.name.identifiers) + .all(|(l, r)| l.name == r.name) + }) + }); + + if is_contract_base { + base.visit(self)?; + } else { + let mut base_or_modifier = + self.visit_to_chunk(loc.start(), Some(loc.end()), base)?; + if base_or_modifier.content.ends_with("()") { + base_or_modifier + .content + .truncate(base_or_modifier.content.len() - 2); + } + self.write_chunk(&base_or_modifier)?; + } + } + FunctionAttribute::Error(loc) => self.visit_parser_error(*loc)?, + }; + + Ok(()) + } + + #[instrument(name = "base", skip_all)] + fn visit_base(&mut self, base: &mut Base) -> Result<()> { + return_source_if_disabled!(self, base.loc); + + let name_loc = &base.name.loc; + let mut name = self.chunked(name_loc.start(), Some(name_loc.end()), |fmt| { + fmt.visit_ident_path(&mut base.name)?; + Ok(()) + })?; + + if base.args.is_none() || base.args.as_ref().unwrap().is_empty() { + if self.context.function.is_some() { + name.content.push_str("()"); + } + self.write_chunk(&name)?; + return Ok(()); + } + + let args = base.args.as_mut().unwrap(); + let args_start = CodeLocation::loc(args.first().unwrap()).start(); + + name.content.push('('); + let formatted_name = self.chunk_to_string(&name)?; + + let multiline = !self.will_it_fit(&formatted_name); + + self.surrounded( + SurroundingChunk::new(&formatted_name, Some(args_start), None), + SurroundingChunk::new(")", None, Some(base.loc.end())), + |fmt, multiline_hint| { + let args = fmt.items_to_chunks( + Some(base.loc.end()), + args.iter_mut().map(|arg| (arg.loc(), arg)), + )?; + let multiline = multiline + || multiline_hint + || fmt.are_chunks_separated_multiline("{}", &args, ",")?; + fmt.write_chunks_separated(&args, ",", multiline)?; + Ok(()) + }, + )?; + + Ok(()) + } + + #[instrument(name = "parameter", skip_all)] + fn visit_parameter(&mut self, parameter: &mut Parameter) -> Result<()> { + return_source_if_disabled!(self, parameter.loc); + self.grouped(|fmt| { + parameter.ty.visit(fmt)?; + if let Some(storage) = ¶meter.storage { + write_chunk!(fmt, storage.loc().end(), "{storage}")?; + } + if let Some(name) = ¶meter.name { + write_chunk!(fmt, parameter.loc.end(), "{}", name.name)?; + } + Ok(()) + })?; + Ok(()) + } + + #[instrument(name = "struct", skip_all)] + fn visit_struct(&mut self, structure: &mut StructDefinition) -> Result<()> { + return_source_if_disabled!(self, structure.loc); + self.grouped(|fmt| { + let struct_name = structure.name.safe_unwrap_mut(); + write_chunk!(fmt, struct_name.loc.start(), "struct")?; + struct_name.visit(fmt)?; + if structure.fields.is_empty() { + return fmt.write_empty_brackets(); + } + + write!(fmt.buf(), " {{")?; + fmt.surrounded( + SurroundingChunk::new("", Some(struct_name.loc.end()), None), + SurroundingChunk::new("}", None, Some(structure.loc.end())), + |fmt, _multiline| { + let chunks = fmt.items_to_chunks( + Some(structure.loc.end()), + structure.fields.iter_mut().map(|ident| (ident.loc, ident)), + )?; + for mut chunk in chunks { + chunk.content.push(';'); + fmt.write_chunk(&chunk)?; + fmt.write_whitespace_separator(true)?; + } + Ok(()) + }, + ) + })?; + + Ok(()) + } + + #[instrument(name = "type_definition", skip_all)] + fn visit_type_definition(&mut self, def: &mut TypeDefinition) -> Result<()> { + return_source_if_disabled!(self, def.loc, ';'); + self.grouped(|fmt| { + write_chunk!(fmt, def.loc.start(), def.name.loc.start(), "type")?; + def.name.visit(fmt)?; + write_chunk!( + fmt, + def.name.loc.end(), + CodeLocation::loc(&def.ty).start(), + "is" + )?; + def.ty.visit(fmt)?; + fmt.write_semicolon()?; + Ok(()) + })?; + Ok(()) + } + + #[instrument(name = "stray_semicolon", skip_all)] + fn visit_stray_semicolon(&mut self) -> Result<()> { + self.write_semicolon() + } + + #[instrument(name = "block", skip_all)] + fn visit_block( + &mut self, + loc: Loc, + unchecked: bool, + statements: &mut Vec, + ) -> Result<()> { + return_source_if_disabled!(self, loc); + if unchecked { + write_chunk!(self, loc.start(), "unchecked ")?; + } + + self.visit_block(loc, statements, false, false)?; + Ok(()) + } + + #[instrument(name = "opening_paren", skip_all)] + fn visit_opening_paren(&mut self) -> Result<()> { + write_chunk!(self, "(")?; + Ok(()) + } + + #[instrument(name = "closing_paren", skip_all)] + fn visit_closing_paren(&mut self) -> Result<()> { + write_chunk!(self, ")")?; + Ok(()) + } + + #[instrument(name = "newline", skip_all)] + fn visit_newline(&mut self) -> Result<()> { + writeln_chunk!(self)?; + Ok(()) + } + + #[instrument(name = "event", skip_all)] + fn visit_event(&mut self, event: &mut EventDefinition) -> Result<()> { + return_source_if_disabled!(self, event.loc, ';'); + + let event_name = event.name.safe_unwrap_mut(); + let mut name = + self.visit_to_chunk(event_name.loc.start(), Some(event.loc.end()), event_name)?; + name.content = format!("event {}(", name.content); + + let last_chunk = if event.anonymous { + ") anonymous;" + } else { + ");" + }; + if event.fields.is_empty() { + name.content.push_str(last_chunk); + self.write_chunk(&name)?; + } else { + let byte_offset = event.fields.first().unwrap().loc.start(); + let first_chunk = self.chunk_to_string(&name)?; + self.surrounded( + SurroundingChunk::new(first_chunk, Some(byte_offset), None), + SurroundingChunk::new(last_chunk, None, Some(event.loc.end())), + |fmt, multiline| { + let params = fmt + .items_to_chunks(None, event.fields.iter_mut().map(|arg| (arg.loc, arg)))?; + + let multiline = + multiline && fmt.are_chunks_separated_multiline("{}", ¶ms, ",")?; + fmt.write_chunks_separated(¶ms, ",", multiline) + }, + )?; + } + + Ok(()) + } + + #[instrument(name = "event_parameter", skip_all)] + fn visit_event_parameter(&mut self, param: &mut EventParameter) -> Result<()> { + return_source_if_disabled!(self, param.loc); + + self.grouped(|fmt| { + param.ty.visit(fmt)?; + if param.indexed { + write_chunk!(fmt, param.loc.start(), "indexed")?; + } + if let Some(name) = ¶m.name { + write_chunk!(fmt, name.loc.end(), "{}", name.name)?; + } + Ok(()) + })?; + Ok(()) + } + + #[instrument(name = "error", skip_all)] + fn visit_error(&mut self, error: &mut ErrorDefinition) -> Result<()> { + return_source_if_disabled!(self, error.loc, ';'); + + let error_name = error.name.safe_unwrap_mut(); + let mut name = self.visit_to_chunk(error_name.loc.start(), None, error_name)?; + name.content = format!("error {}", name.content); + + let formatted_name = self.chunk_to_string(&name)?; + write!(self.buf(), "{formatted_name}")?; + let start_offset = error.fields.first().map(|f| f.loc.start()); + self.visit_list( + "", + &mut error.fields, + start_offset, + Some(error.loc.end()), + true, + )?; + self.write_semicolon()?; + + Ok(()) + } + + #[instrument(name = "error_parameter", skip_all)] + fn visit_error_parameter(&mut self, param: &mut ErrorParameter) -> Result<()> { + return_source_if_disabled!(self, param.loc); + self.grouped(|fmt| { + param.ty.visit(fmt)?; + if let Some(name) = ¶m.name { + write_chunk!(fmt, name.loc.end(), "{}", name.name)?; + } + Ok(()) + })?; + Ok(()) + } + + #[instrument(name = "using", skip_all)] + fn visit_using(&mut self, using: &mut Using) -> Result<()> { + return_source_if_disabled!(self, using.loc, ';'); + + write_chunk!(self, using.loc.start(), "using")?; + + let ty_start = using.ty.as_mut().map(|ty| CodeLocation::loc(&ty).start()); + let global_start = using.global.as_mut().map(|global| global.loc.start()); + let loc_end = using.loc.end(); + + let (is_library, mut list_chunks) = match &mut using.list { + UsingList::Library(library) => ( + true, + vec![self.visit_to_chunk(library.loc.start(), None, library)?], + ), + UsingList::Functions(funcs) => { + let mut funcs = funcs.iter_mut().peekable(); + let mut chunks = Vec::new(); + while let Some(func) = funcs.next() { + let next_byte_end = funcs.peek().map(|func| func.loc.start()); + chunks.push(self.chunked(func.loc.start(), next_byte_end, |fmt| { + fmt.visit_ident_path(&mut func.path)?; + if let Some(op) = func.oper { + write!(fmt.buf(), " as {op}")?; + } + Ok(()) + })?); + } + (false, chunks) + } + UsingList::Error => return self.visit_parser_error(using.loc), + }; + + let for_chunk = self.chunk_at( + using.loc.start(), + Some(ty_start.or(global_start).unwrap_or(loc_end)), + None, + "for", + ); + let ty_chunk = if let Some(ty) = &mut using.ty { + self.visit_to_chunk(ty.loc().start(), Some(global_start.unwrap_or(loc_end)), ty)? + } else { + self.chunk_at( + using.loc.start(), + Some(global_start.unwrap_or(loc_end)), + None, + "*", + ) + }; + let global_chunk = using + .global + .as_mut() + .map(|global| self.visit_to_chunk(global.loc.start(), Some(using.loc.end()), global)) + .transpose()?; + + let write_for_def = |fmt: &mut Self| { + fmt.grouped(|fmt| { + fmt.write_chunk(&for_chunk)?; + fmt.write_chunk(&ty_chunk)?; + if let Some(global_chunk) = global_chunk.as_ref() { + fmt.write_chunk(global_chunk)?; + } + Ok(()) + })?; + Ok(()) + }; + + let simulated_for_def = self.simulate_to_string(write_for_def)?; + + if is_library { + let chunk = list_chunks.pop().unwrap(); + if self.will_chunk_fit(&format!("{{}} {simulated_for_def};"), &chunk)? { + self.write_chunk(&chunk)?; + write_for_def(self)?; + } else { + self.write_whitespace_separator(true)?; + self.grouped(|fmt| { + fmt.write_chunk(&chunk)?; + Ok(()) + })?; + self.write_whitespace_separator(true)?; + write_for_def(self)?; + } + } else { + self.surrounded( + SurroundingChunk::new("{", Some(using.loc.start()), None), + SurroundingChunk::new( + "}", + None, + Some(ty_start.or(global_start).unwrap_or(loc_end)), + ), + |fmt, _multiline| { + let multiline = fmt.are_chunks_separated_multiline( + &format!("{{ {{}} }} {simulated_for_def};"), + &list_chunks, + ",", + )?; + fmt.write_chunks_separated(&list_chunks, ",", multiline)?; + Ok(()) + }, + )?; + write_for_def(self)?; + } + + self.write_semicolon()?; + + Ok(()) + } + + #[instrument(name = "var_attribute", skip_all)] + fn visit_var_attribute(&mut self, attribute: &mut VariableAttribute) -> Result<()> { + return_source_if_disabled!(self, attribute.loc()); + + let token = match attribute { + VariableAttribute::Visibility(visibility) => Some(visibility.to_string()), + VariableAttribute::Constant(_) => Some("constant".to_string()), + VariableAttribute::Immutable(_) => Some("immutable".to_string()), + VariableAttribute::Override(loc, idents) => { + write_chunk!(self, loc.start(), "override")?; + if !idents.is_empty() && self.config.override_spacing { + self.write_whitespace_separator(false)?; + } + self.visit_list("", idents, Some(loc.start()), Some(loc.end()), false)?; + None + } + }; + if let Some(token) = token { + let loc = attribute.loc(); + write_chunk!(self, loc.start(), loc.end(), "{}", token)?; + } + Ok(()) + } + + #[instrument(name = "var_definition", skip_all)] + fn visit_var_definition(&mut self, var: &mut VariableDefinition) -> Result<()> { + return_source_if_disabled!(self, var.loc, ';'); + + var.ty.visit(self)?; + + let multiline = self.grouped(|fmt| { + let var_name = var.name.safe_unwrap_mut(); + let name_start = var_name.loc.start(); + + let attrs = fmt.items_to_chunks_sorted(Some(name_start), var.attrs.iter_mut())?; + if !fmt.try_on_single_line(|fmt| fmt.write_chunks_separated(&attrs, "", false))? { + fmt.write_chunks_separated(&attrs, "", true)?; + } + + let mut name = fmt.visit_to_chunk(name_start, Some(var_name.loc.end()), var_name)?; + if var.initializer.is_some() { + name.content.push_str(" ="); + } + fmt.write_chunk(&name)?; + + Ok(()) + })?; + + var.initializer + .as_mut() + .map(|init| self.indented_if(multiline, 1, |fmt| fmt.visit_assignment(init))) + .transpose()?; + + self.write_semicolon()?; + + Ok(()) + } + + #[instrument(name = "var_definition_stmt", skip_all)] + fn visit_var_definition_stmt( + &mut self, + loc: Loc, + declaration: &mut VariableDeclaration, + expr: &mut Option, + ) -> Result<()> { + return_source_if_disabled!(self, loc, ';'); + + let declaration = self.chunked(declaration.loc.start(), None, |fmt| { + fmt.visit_var_declaration(declaration) + })?; + let multiline = declaration.content.contains('\n'); + self.write_chunk(&declaration)?; + + if let Some(expr) = expr { + write!(self.buf(), " =")?; + self.indented_if(multiline, 1, |fmt| fmt.visit_assignment(expr))?; + } + + self.write_semicolon() + } + + #[instrument(name = "for", skip_all)] + fn visit_for( + &mut self, + loc: Loc, + init: &mut Option>, + cond: &mut Option>, + update: &mut Option>, + body: &mut Option>, + ) -> Result<(), Self::Error> { + return_source_if_disabled!(self, loc); + + let next_byte_end = update.as_ref().map(|u| u.loc().end()); + self.surrounded( + SurroundingChunk::new("for (", Some(loc.start()), None), + SurroundingChunk::new(")", None, next_byte_end), + |fmt, _| { + let mut write_for_loop_header = |fmt: &mut Self, multiline: bool| -> Result<()> { + match init { + Some(stmt) => stmt.visit(fmt), + None => fmt.write_semicolon(), + }?; + if multiline { + fmt.write_whitespace_separator(true)?; + } + + cond.visit(fmt)?; + fmt.write_semicolon()?; + if multiline { + fmt.write_whitespace_separator(true)?; + } + + match update { + Some(expr) => expr.visit(fmt), + None => Ok(()), + } + }; + let multiline = !fmt.try_on_single_line(|fmt| write_for_loop_header(fmt, false))?; + if multiline { + write_for_loop_header(fmt, true)?; + } + Ok(()) + }, + )?; + match body { + Some(body) => { + self.visit_stmt_as_block(body, false)?; + } + None => { + self.write_empty_brackets()?; + } + }; + Ok(()) + } + + #[instrument(name = "while", skip_all)] + fn visit_while( + &mut self, + loc: Loc, + cond: &mut Expression, + body: &mut Statement, + ) -> Result<(), Self::Error> { + return_source_if_disabled!(self, loc); + self.surrounded( + SurroundingChunk::new("while (", Some(loc.start()), None), + SurroundingChunk::new(")", None, Some(cond.loc().end())), + |fmt, _| { + cond.visit(fmt)?; + fmt.write_postfix_comments_before(body.loc().start()) + }, + )?; + + let cond_close_paren_loc = self + .find_next_in_src(cond.loc().end(), ')') + .unwrap_or_else(|| cond.loc().end()); + let attempt_single_line = self.should_attempt_block_single_line(body, cond_close_paren_loc); + self.visit_stmt_as_block(body, attempt_single_line)?; + Ok(()) + } + + #[instrument(name = "do_while", skip_all)] + fn visit_do_while( + &mut self, + loc: Loc, + body: &mut Statement, + cond: &mut Expression, + ) -> Result<(), Self::Error> { + return_source_if_disabled!(self, loc, ';'); + write_chunk!(self, loc.start(), "do ")?; + self.visit_stmt_as_block(body, false)?; + visit_source_if_disabled_else!(self, loc.with_start(body.loc().end()), { + self.surrounded( + SurroundingChunk::new("while (", Some(cond.loc().start()), None), + SurroundingChunk::new(");", None, Some(loc.end())), + |fmt, _| cond.visit(fmt), + )?; + }); + Ok(()) + } + + #[instrument(name = "if", skip_all)] + fn visit_if( + &mut self, + loc: Loc, + cond: &mut Expression, + if_branch: &mut Box, + else_branch: &mut Option>, + is_first_stmt: bool, + ) -> Result<(), Self::Error> { + return_source_if_disabled!(self, loc); + + if !is_first_stmt { + self.write_if_stmt(loc, cond, if_branch, else_branch)?; + return Ok(()); + } + + self.context.if_stmt_single_line = Some(true); + let mut stmt_fits_on_single = false; + let tx = self.transact(|fmt| { + stmt_fits_on_single = match fmt.write_if_stmt(loc, cond, if_branch, else_branch) { + Ok(()) => true, + Err(FormatterError::Fmt(_)) => false, + Err(err) => bail!(err), + }; + Ok(()) + })?; + + if stmt_fits_on_single { + tx.commit()?; + } else { + self.context.if_stmt_single_line = Some(false); + self.write_if_stmt(loc, cond, if_branch, else_branch)?; + } + self.context.if_stmt_single_line = None; + + Ok(()) + } + + #[instrument(name = "args", skip_all)] + fn visit_args(&mut self, loc: Loc, args: &mut Vec) -> Result<(), Self::Error> { + return_source_if_disabled!(self, loc); + + write!(self.buf(), "{{")?; + + let mut args_iter = args.iter_mut().peekable(); + let mut chunks = Vec::new(); + while let Some(NamedArgument { + loc: arg_loc, + name, + expr, + }) = args_iter.next() + { + let next_byte_offset = args_iter + .peek() + .map(|NamedArgument { loc: arg_loc, .. }| arg_loc.start()) + .unwrap_or_else(|| loc.end()); + chunks.push( + self.chunked(arg_loc.start(), Some(next_byte_offset), |fmt| { + fmt.grouped(|fmt| { + write_chunk!(fmt, name.loc.start(), "{}: ", name.name)?; + expr.visit(fmt) + })?; + Ok(()) + })?, + ); + } + + if let Some(first) = chunks.first_mut() { + if first.prefixes.is_empty() + && first.postfixes_before.is_empty() + && !self.config.bracket_spacing + { + first.needs_space = Some(false); + } + } + let multiline = self.are_chunks_separated_multiline("{}}", &chunks, ",")?; + self.indented_if(multiline, 1, |fmt| { + fmt.write_chunks_separated(&chunks, ",", multiline) + })?; + + let prefix = if multiline && !self.is_beginning_of_line() { + "\n" + } else if self.config.bracket_spacing { + " " + } else { + "" + }; + let closing_bracket = format!("{prefix}{}", "}"); + let closing_bracket_loc = args.last().unwrap().loc.end(); + write_chunk!(self, closing_bracket_loc, "{closing_bracket}")?; + + Ok(()) + } + + #[instrument(name = "revert", skip_all)] + fn visit_revert( + &mut self, + loc: Loc, + error: &mut Option, + args: &mut Vec, + ) -> Result<(), Self::Error> { + return_source_if_disabled!(self, loc, ';'); + write_chunk!(self, loc.start(), "revert")?; + if let Some(error) = error { + error.visit(self)?; + } + self.visit_list("", args, None, Some(loc.end()), true)?; + self.write_semicolon()?; + + Ok(()) + } + + #[instrument(name = "revert_named_args", skip_all)] + fn visit_revert_named_args( + &mut self, + loc: Loc, + error: &mut Option, + args: &mut Vec, + ) -> Result<(), Self::Error> { + return_source_if_disabled!(self, loc, ';'); + + write_chunk!(self, loc.start(), "revert")?; + let mut error_indented = false; + if let Some(error) = error { + if !self.try_on_single_line(|fmt| error.visit(fmt))? { + error.visit(self)?; + error_indented = true; + } + } + + if args.is_empty() { + write!(self.buf(), "({{}});")?; + return Ok(()); + } + + write!(self.buf(), "(")?; + self.indented_if(error_indented, 1, |fmt| fmt.visit_args(loc, args))?; + write!(self.buf(), ")")?; + self.write_semicolon()?; + + Ok(()) + } + + #[instrument(name = "return", skip_all)] + fn visit_return(&mut self, loc: Loc, expr: &mut Option) -> Result<(), Self::Error> { + return_source_if_disabled!(self, loc, ';'); + + self.write_postfix_comments_before(loc.start())?; + self.write_prefix_comments_before(loc.start())?; + + if expr.is_none() { + write_chunk!(self, loc.end(), "return;")?; + return Ok(()); + } + + let expr = expr.as_mut().unwrap(); + let expr_loc_start = expr.loc().start(); + let write_return = |fmt: &mut Self| -> Result<()> { + write_chunk!(fmt, loc.start(), "return")?; + fmt.write_postfix_comments_before(expr_loc_start)?; + Ok(()) + }; + + let mut write_return_with_expr = |fmt: &mut Self| -> Result<()> { + let fits_on_single = fmt.try_on_single_line(|fmt| { + write_return(fmt)?; + expr.visit(fmt) + })?; + if fits_on_single { + return Ok(()); + } + + let mut fit_on_next_line = false; + let tx = fmt.transact(|fmt| { + fmt.grouped(|fmt| { + write_return(fmt)?; + if !fmt.is_beginning_of_line() { + fmt.write_whitespace_separator(true)?; + } + fit_on_next_line = fmt.try_on_single_line(|fmt| expr.visit(fmt))?; + Ok(()) + })?; + Ok(()) + })?; + if fit_on_next_line { + tx.commit()?; + return Ok(()); + } + + write_return(fmt)?; + expr.visit(fmt)?; + Ok(()) + }; + + write_return_with_expr(self)?; + write_chunk!(self, loc.end(), ";")?; + Ok(()) + } + + #[instrument(name = "try", skip_all)] + fn visit_try( + &mut self, + loc: Loc, + expr: &mut Expression, + returns: &mut Option<(Vec<(Loc, Option)>, Box)>, + clauses: &mut Vec, + ) -> Result<(), Self::Error> { + return_source_if_disabled!(self, loc); + + let try_next_byte = clauses.first().map(|c| match c { + CatchClause::Simple(loc, ..) => loc.start(), + CatchClause::Named(loc, ..) => loc.start(), + }); + let try_chunk = self.chunked(loc.start(), try_next_byte, |fmt| { + write_chunk!(fmt, loc.start(), expr.loc().start(), "try")?; + expr.visit(fmt)?; + if let Some((params, stmt)) = returns { + let mut params = params + .iter_mut() + .filter(|(_, param)| param.is_some()) + .collect::>(); + let byte_offset = params.first().map_or(stmt.loc().start(), |p| p.0.start()); + fmt.surrounded( + SurroundingChunk::new("returns (", Some(byte_offset), None), + SurroundingChunk::new(")", None, params.last().map(|p| p.0.end())), + |fmt, _| { + let chunks = fmt.items_to_chunks( + Some(stmt.loc().start()), + params.iter_mut().map(|(loc, ref mut ident)| (*loc, ident)), + )?; + let multiline = fmt.are_chunks_separated_multiline("{})", &chunks, ",")?; + fmt.write_chunks_separated(&chunks, ",", multiline)?; + Ok(()) + }, + )?; + stmt.visit(fmt)?; + } + Ok(()) + })?; + + let mut chunks = vec![try_chunk]; + for clause in clauses { + let (loc, ident, mut param, stmt) = match clause { + CatchClause::Simple(loc, param, stmt) => (loc, None, param.as_mut(), stmt), + CatchClause::Named(loc, ident, param, stmt) => { + (loc, Some(ident), Some(param), stmt) + } + }; + + let chunk = self.chunked(loc.start(), Some(stmt.loc().start()), |fmt| { + write_chunk!(fmt, "catch")?; + if let Some(ident) = ident.as_ref() { + fmt.write_postfix_comments_before( + param + .as_ref() + .map(|p| p.loc.start()) + .unwrap_or_else(|| ident.loc.end()), + )?; + write_chunk!(fmt, ident.loc.start(), "{}", ident.name)?; + } + if let Some(param) = param.as_mut() { + write_chunk_spaced!(fmt, param.loc.start(), Some(ident.is_none()), "(")?; + fmt.surrounded( + SurroundingChunk::new("", Some(param.loc.start()), None), + SurroundingChunk::new(")", None, Some(stmt.loc().start())), + |fmt, _| param.visit(fmt), + )?; + } + + stmt.visit(fmt)?; + Ok(()) + })?; + + chunks.push(chunk); + } + + let multiline = self.are_chunks_separated_multiline("{}", &chunks, "")?; + if !multiline { + self.write_chunks_separated(&chunks, "", false)?; + return Ok(()); + } + + let mut chunks = chunks.iter_mut().peekable(); + let mut prev_multiline = false; + + // write try chunk first + if let Some(chunk) = chunks.next() { + let chunk_str = self.simulate_to_string(|fmt| fmt.write_chunk(chunk))?; + write!(self.buf(), "{chunk_str}")?; + prev_multiline = chunk_str.contains('\n'); + } + + while let Some(chunk) = chunks.next() { + let chunk_str = self.simulate_to_string(|fmt| fmt.write_chunk(chunk))?; + let multiline = chunk_str.contains('\n'); + self.indented_if(!multiline, 1, |fmt| { + chunk.needs_space = Some(false); + let on_same_line = prev_multiline && (multiline || chunks.peek().is_none()); + let prefix = if fmt.is_beginning_of_line() { + "" + } else if on_same_line { + " " + } else { + "\n" + }; + let chunk_str = format!("{prefix}{chunk_str}"); + write!(fmt.buf(), "{chunk_str}")?; + Ok(()) + })?; + prev_multiline = multiline; + } + Ok(()) + } + + #[instrument(name = "assembly", skip_all)] + fn visit_assembly( + &mut self, + loc: Loc, + dialect: &mut Option, + block: &mut YulBlock, + flags: &mut Option>, + ) -> Result<(), Self::Error> { + return_source_if_disabled!(self, loc); + + write_chunk!(self, loc.start(), "assembly")?; + if let Some(StringLiteral { loc, string, .. }) = dialect { + write_chunk!(self, loc.start(), loc.end(), "\"{string}\"")?; + } + if let Some(flags) = flags { + if !flags.is_empty() { + let loc_start = flags.first().unwrap().loc.start(); + self.surrounded( + SurroundingChunk::new("(", Some(loc_start), None), + SurroundingChunk::new(")", None, Some(block.loc.start())), + |fmt, _| { + let mut flags = flags.iter_mut().peekable(); + let mut chunks = vec![]; + while let Some(flag) = flags.next() { + let next_byte_offset = + flags.peek().map(|next_flag| next_flag.loc.start()); + chunks.push(fmt.chunked( + flag.loc.start(), + next_byte_offset, + |fmt| { + write!(fmt.buf(), "\"{}\"", flag.string)?; + Ok(()) + }, + )?); + } + fmt.write_chunks_separated(&chunks, ",", false)?; + Ok(()) + }, + )?; + } + } + + block.visit(self) + } + + #[instrument(name = "yul_block", skip_all)] + fn visit_yul_block( + &mut self, + loc: Loc, + statements: &mut Vec, + attempt_single_line: bool, + ) -> Result<(), Self::Error> { + return_source_if_disabled!(self, loc); + self.visit_block(loc, statements, attempt_single_line, false)?; + Ok(()) + } + + #[instrument(name = "yul_assignment", skip_all)] + fn visit_yul_assignment( + &mut self, + loc: Loc, + exprs: &mut Vec, + expr: &mut Option<&mut YulExpression>, + ) -> Result<(), Self::Error> + where + T: Visitable + CodeLocation, + { + return_source_if_disabled!(self, loc); + + self.grouped(|fmt| { + let chunks = + fmt.items_to_chunks(None, exprs.iter_mut().map(|expr| (expr.loc(), expr)))?; + + let multiline = fmt.are_chunks_separated_multiline("{} := ", &chunks, ",")?; + fmt.write_chunks_separated(&chunks, ",", multiline)?; + + if let Some(expr) = expr { + write_chunk!(fmt, expr.loc().start(), ":=")?; + let chunk = fmt.visit_to_chunk(expr.loc().start(), Some(loc.end()), expr)?; + if !fmt.will_chunk_fit("{}", &chunk)? { + fmt.write_whitespace_separator(true)?; + } + fmt.write_chunk(&chunk)?; + } + Ok(()) + })?; + Ok(()) + } + + #[instrument(name = "yul_expr", skip_all)] + fn visit_yul_expr(&mut self, expr: &mut YulExpression) -> Result<(), Self::Error> { + return_source_if_disabled!(self, expr.loc()); + + match expr { + YulExpression::BoolLiteral(loc, val, ident) => { + let val = if *val { "true" } else { "false" }; + self.visit_yul_string_with_ident(*loc, val, ident) + } + YulExpression::FunctionCall(expr) => self.visit_yul_function_call(expr), + YulExpression::HexNumberLiteral(loc, val, ident) => { + self.visit_yul_string_with_ident(*loc, val, ident) + } + YulExpression::HexStringLiteral(val, ident) => self.visit_yul_string_with_ident( + val.loc, + &self.quote_str(val.loc, Some("hex"), &val.hex), + ident, + ), + YulExpression::NumberLiteral(loc, val, expr, ident) => { + let val = if expr.is_empty() { + val.to_owned() + } else { + format!("{val}e{expr}") + }; + self.visit_yul_string_with_ident(*loc, &val, ident) + } + YulExpression::StringLiteral(val, ident) => self.visit_yul_string_with_ident( + val.loc, + &self.quote_str(val.loc, None, &val.string), + ident, + ), + YulExpression::SuffixAccess(_, expr, ident) => { + self.visit_member_access(expr, ident, |fmt, expr| match expr.as_mut() { + YulExpression::SuffixAccess(_, inner_expr, inner_ident) => { + Ok(Some((inner_expr, inner_ident))) + } + expr => { + expr.visit(fmt)?; + Ok(None) + } + }) + } + YulExpression::Variable(ident) => { + write_chunk!(self, ident.loc.start(), ident.loc.end(), "{}", ident.name) + } + } + } + + #[instrument(name = "yul_for", skip_all)] + fn visit_yul_for(&mut self, stmt: &mut YulFor) -> Result<(), Self::Error> { + return_source_if_disabled!(self, stmt.loc); + write_chunk!(self, stmt.loc.start(), "for")?; + self.visit_yul_block(stmt.init_block.loc, &mut stmt.init_block.statements, true)?; + stmt.condition.visit(self)?; + self.visit_yul_block(stmt.post_block.loc, &mut stmt.post_block.statements, true)?; + self.visit_yul_block( + stmt.execution_block.loc, + &mut stmt.execution_block.statements, + true, + )?; + Ok(()) + } + + #[instrument(name = "yul_function_call", skip_all)] + fn visit_yul_function_call(&mut self, stmt: &mut YulFunctionCall) -> Result<(), Self::Error> { + return_source_if_disabled!(self, stmt.loc); + write_chunk!(self, stmt.loc.start(), "{}", stmt.id.name)?; + self.visit_list("", &mut stmt.arguments, None, Some(stmt.loc.end()), true) + } + + #[instrument(name = "yul_typed_ident", skip_all)] + fn visit_yul_typed_ident(&mut self, ident: &mut YulTypedIdentifier) -> Result<(), Self::Error> { + return_source_if_disabled!(self, ident.loc); + self.visit_yul_string_with_ident(ident.loc, &ident.id.name, &mut ident.ty) + } + + #[instrument(name = "yul_fun_def", skip_all)] + fn visit_yul_fun_def(&mut self, stmt: &mut YulFunctionDefinition) -> Result<(), Self::Error> { + return_source_if_disabled!(self, stmt.loc); + + write_chunk!(self, stmt.loc.start(), "function {}", stmt.id.name)?; + + self.visit_list("", &mut stmt.params, None, None, true)?; + + if !stmt.returns.is_empty() { + self.grouped(|fmt| { + write_chunk!(fmt, "->")?; + + let chunks = fmt.items_to_chunks( + Some(stmt.body.loc.start()), + stmt.returns.iter_mut().map(|param| (param.loc, param)), + )?; + let multiline = fmt.are_chunks_separated_multiline("{}", &chunks, ",")?; + fmt.write_chunks_separated(&chunks, ",", multiline)?; + if multiline { + fmt.write_whitespace_separator(true)?; + } + Ok(()) + })?; + } + + stmt.body.visit(self)?; + + Ok(()) + } + + #[instrument(name = "yul_if", skip_all)] + fn visit_yul_if( + &mut self, + loc: Loc, + expr: &mut YulExpression, + block: &mut YulBlock, + ) -> Result<(), Self::Error> { + return_source_if_disabled!(self, loc); + write_chunk!(self, loc.start(), "if")?; + expr.visit(self)?; + self.visit_yul_block(block.loc, &mut block.statements, true) + } + + #[instrument(name = "yul_leave", skip_all)] + fn visit_yul_leave(&mut self, loc: Loc) -> Result<(), Self::Error> { + return_source_if_disabled!(self, loc); + write_chunk!(self, loc.start(), loc.end(), "leave") + } + + #[instrument(name = "yul_switch", skip_all)] + fn visit_yul_switch(&mut self, stmt: &mut YulSwitch) -> Result<(), Self::Error> { + return_source_if_disabled!(self, stmt.loc); + + write_chunk!(self, stmt.loc.start(), "switch")?; + stmt.condition.visit(self)?; + writeln_chunk!(self)?; + let mut cases = stmt.cases.iter_mut().peekable(); + while let Some(YulSwitchOptions::Case(loc, expr, block)) = cases.next() { + write_chunk!(self, loc.start(), "case")?; + expr.visit(self)?; + self.visit_yul_block(block.loc, &mut block.statements, true)?; + let is_last = cases.peek().is_none(); + if !is_last || stmt.default.is_some() { + writeln_chunk!(self)?; + } + } + if let Some(YulSwitchOptions::Default(loc, ref mut block)) = stmt.default { + write_chunk!(self, loc.start(), "default")?; + self.visit_yul_block(block.loc, &mut block.statements, true)?; + } + Ok(()) + } + + #[instrument(name = "yul_var_declaration", skip_all)] + fn visit_yul_var_declaration( + &mut self, + loc: Loc, + idents: &mut Vec, + expr: &mut Option, + ) -> Result<(), Self::Error> { + return_source_if_disabled!(self, loc); + self.grouped(|fmt| { + write_chunk!(fmt, loc.start(), "let")?; + fmt.visit_yul_assignment(loc, idents, &mut expr.as_mut()) + })?; + Ok(()) + } + + // Support extension for Solana/Substrate + #[instrument(name = "annotation", skip_all)] + fn visit_annotation(&mut self, annotation: &mut Annotation) -> Result<()> { + return_source_if_disabled!(self, annotation.loc); + let id = self.simulate_to_string(|fmt| annotation.id.visit(fmt))?; + write!(self.buf(), "@{id}")?; + write!(self.buf(), "(")?; + annotation.value.visit(self)?; + write!(self.buf(), ")")?; + Ok(()) + } + + #[instrument(name = "parser_error", skip_all)] + fn visit_parser_error(&mut self, loc: Loc) -> Result<()> { + Err(FormatterError::InvalidParsedItem(loc)) + } +} diff --git a/forge-fmt/src/helpers.rs b/forge-fmt/src/helpers.rs new file mode 100644 index 000000000..98e8fa7f7 --- /dev/null +++ b/forge-fmt/src/helpers.rs @@ -0,0 +1,119 @@ +use crate::{ + inline_config::{InlineConfig, InvalidInlineConfigItem}, + Comments, Formatter, FormatterConfig, FormatterError, Visitable, +}; +use ariadne::{Color, Fmt, Label, Report, ReportKind, Source}; +use itertools::Itertools; +use solang_parser::{diagnostics::Diagnostic, pt::*}; +use std::{fmt::Write, path::Path}; + +/// Result of parsing the source code +#[derive(Debug)] +pub struct Parsed<'a> { + /// The original source code + pub src: &'a str, + /// The Parse Tree via [`solang`] + pub pt: SourceUnit, + /// Parsed comments + pub comments: Comments, + /// Parsed inline config + pub inline_config: InlineConfig, + /// Invalid inline config items parsed + pub invalid_inline_config_items: Vec<(Loc, InvalidInlineConfigItem)>, +} + +/// Parse source code +pub fn parse(src: &str) -> Result> { + let (pt, comments) = solang_parser::parse(src, 0)?; + let comments = Comments::new(comments, src); + let (inline_config_items, invalid_inline_config_items): (Vec<_>, Vec<_>) = + comments.parse_inline_config_items().partition_result(); + let inline_config = InlineConfig::new(inline_config_items, src); + Ok(Parsed { + src, + pt, + comments, + inline_config, + invalid_inline_config_items, + }) +} + +/// Format parsed code +pub fn format( + writer: W, + mut parsed: Parsed, + config: FormatterConfig, +) -> Result<(), FormatterError> { + trace!(?parsed, ?config, "Formatting"); + let mut formatter = Formatter::new( + writer, + parsed.src, + parsed.comments, + parsed.inline_config, + config, + ); + parsed.pt.visit(&mut formatter) +} + +/// Parse and format a string with default settings +pub fn fmt(src: &str) -> Result { + let parsed = parse(src).map_err(|_| FormatterError::Fmt(std::fmt::Error))?; + + let mut output = String::new(); + format(&mut output, parsed, FormatterConfig::default())?; + + Ok(output) +} + +/// Converts the start offset of a `Loc` to `(line, col)` +pub fn offset_to_line_column(content: &str, start: usize) -> (usize, usize) { + debug_assert!(content.len() > start); + + // first line is `1` + let mut line_counter = 1; + for (offset, c) in content.chars().enumerate() { + if c == '\n' { + line_counter += 1; + } + if offset > start { + return (line_counter, offset - start); + } + } + + unreachable!("content.len() > start") +} + +/// Print the report of parser's diagnostics +pub fn print_diagnostics_report( + content: &str, + path: Option<&Path>, + diagnostics: Vec, +) -> std::io::Result<()> { + let filename = path + .map(|p| p.file_name().unwrap().to_string_lossy().to_string()) + .unwrap_or_default(); + for diag in diagnostics { + let (start, end) = (diag.loc.start(), diag.loc.end()); + let mut report = Report::build(ReportKind::Error, &filename, start) + .with_message(format!("{:?}", diag.ty)) + .with_label( + Label::new((&filename, start..end)) + .with_color(Color::Red) + .with_message(format!("{}", diag.message.fg(Color::Red))), + ); + + for note in diag.notes { + report = report.with_note(note.message); + } + + report.finish().print((&filename, Source::from(content)))?; + } + Ok(()) +} + +pub fn import_path_string(path: &ImportPath) -> String { + match path { + ImportPath::Filename(s) => s.string.clone(), + ImportPath::Path(p) => p.to_string(), + } +} diff --git a/forge-fmt/src/inline_config.rs b/forge-fmt/src/inline_config.rs new file mode 100644 index 000000000..ae6a6adf2 --- /dev/null +++ b/forge-fmt/src/inline_config.rs @@ -0,0 +1,175 @@ +use crate::comments::{CommentState, CommentStringExt}; +use itertools::Itertools; +use solang_parser::pt::Loc; +use std::{fmt, str::FromStr}; + +/// An inline config item +#[allow(clippy::enum_variant_names)] +#[derive(Debug, Clone, Copy)] +pub enum InlineConfigItem { + /// Disables the next code item regardless of newlines + DisableNextItem, + /// Disables formatting on the current line + DisableLine, + /// Disables formatting between the next newline and the newline after + DisableNextLine, + /// Disables formatting for any code that follows this and before the next "disable-end" + DisableStart, + /// Disables formatting for any code that precedes this and after the previous "disable-start" + DisableEnd, +} + +impl FromStr for InlineConfigItem { + type Err = InvalidInlineConfigItem; + fn from_str(s: &str) -> Result { + Ok(match s { + "disable-next-item" => InlineConfigItem::DisableNextItem, + "disable-line" => InlineConfigItem::DisableLine, + "disable-next-line" => InlineConfigItem::DisableNextLine, + "disable-start" => InlineConfigItem::DisableStart, + "disable-end" => InlineConfigItem::DisableEnd, + s => return Err(InvalidInlineConfigItem(s.into())), + }) + } +} + +#[derive(Debug)] +pub struct InvalidInlineConfigItem(String); + +impl fmt::Display for InvalidInlineConfigItem { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_fmt(format_args!("Invalid inline config item: {}", self.0)) + } +} + +/// A disabled formatting range. `loose` designates that the range includes any loc which +/// may start in between start and end, whereas the strict version requires that +/// `range.start >= loc.start <=> loc.end <= range.end` +#[derive(Debug)] +struct DisabledRange { + start: usize, + end: usize, + loose: bool, +} + +impl DisabledRange { + fn includes(&self, loc: Loc) -> bool { + loc.start() >= self.start && (if self.loose { loc.start() } else { loc.end() } <= self.end) + } +} + +/// An inline config. Keeps track of disabled ranges. +/// +/// This is a list of Inline Config items for locations in a source file. This is +/// usually acquired by parsing the comments for an `forgefmt:` items. See +/// [`Comments::parse_inline_config_items`] for details. +#[derive(Default, Debug)] +pub struct InlineConfig { + disabled_ranges: Vec, +} + +impl InlineConfig { + /// Build a new inline config with an iterator of inline config items and their locations in a + /// source file + pub fn new(items: impl IntoIterator, src: &str) -> Self { + let mut disabled_ranges = vec![]; + let mut disabled_range_start = None; + let mut disabled_depth = 0usize; + for (loc, item) in items.into_iter().sorted_by_key(|(loc, _)| loc.start()) { + match item { + InlineConfigItem::DisableNextItem => { + let offset = loc.end(); + let mut char_indices = src[offset..] + .comment_state_char_indices() + .filter_map(|(state, idx, ch)| match state { + CommentState::None => Some((idx, ch)), + _ => None, + }) + .skip_while(|(_, ch)| ch.is_whitespace()); + if let Some((mut start, _)) = char_indices.next() { + start += offset; + let end = char_indices + .find(|(_, ch)| !ch.is_whitespace()) + .map(|(idx, _)| offset + idx) + .unwrap_or(src.len()); + disabled_ranges.push(DisabledRange { + start, + end, + loose: true, + }); + } + } + InlineConfigItem::DisableLine => { + let mut prev_newline = src[..loc.start()] + .char_indices() + .rev() + .skip_while(|(_, ch)| *ch != '\n'); + let start = prev_newline.next().map(|(idx, _)| idx).unwrap_or_default(); + + let end_offset = loc.end(); + let mut next_newline = src[end_offset..] + .char_indices() + .skip_while(|(_, ch)| *ch != '\n'); + let end = + end_offset + next_newline.next().map(|(idx, _)| idx).unwrap_or_default(); + + disabled_ranges.push(DisabledRange { + start, + end, + loose: false, + }); + } + InlineConfigItem::DisableNextLine => { + let offset = loc.end(); + let mut char_indices = src[offset..] + .char_indices() + .skip_while(|(_, ch)| *ch != '\n') + .skip(1); + if let Some((mut start, _)) = char_indices.next() { + start += offset; + let end = char_indices + .find(|(_, ch)| *ch == '\n') + .map(|(idx, _)| offset + idx + 1) + .unwrap_or(src.len()); + disabled_ranges.push(DisabledRange { + start, + end, + loose: false, + }); + } + } + InlineConfigItem::DisableStart => { + if disabled_depth == 0 { + disabled_range_start = Some(loc.end()); + } + disabled_depth += 1; + } + InlineConfigItem::DisableEnd => { + disabled_depth = disabled_depth.saturating_sub(1); + if disabled_depth == 0 { + if let Some(start) = disabled_range_start.take() { + disabled_ranges.push(DisabledRange { + start, + end: loc.start(), + loose: false, + }) + } + } + } + } + } + if let Some(start) = disabled_range_start.take() { + disabled_ranges.push(DisabledRange { + start, + end: src.len(), + loose: false, + }) + } + Self { disabled_ranges } + } + + /// Check if the location is in a disabled range + pub fn is_disabled(&self, loc: Loc) -> bool { + self.disabled_ranges.iter().any(|range| range.includes(loc)) + } +} diff --git a/forge-fmt/src/lib.rs b/forge-fmt/src/lib.rs new file mode 100644 index 000000000..66f8bc4a5 --- /dev/null +++ b/forge-fmt/src/lib.rs @@ -0,0 +1,24 @@ +#![doc = include_str!("../README.md")] +#![cfg_attr(not(test), warn(unused_crate_dependencies))] + +#[macro_use] +extern crate tracing; + +mod buffer; +pub mod chunk; +mod comments; +mod formatter; +mod helpers; +pub mod inline_config; +mod macros; +pub mod solang_ext; +mod string; +pub mod visit; + +pub use foundry_config::fmt::*; + +pub use comments::Comments; +pub use formatter::{Formatter, FormatterError}; +pub use helpers::{fmt, format, offset_to_line_column, parse, print_diagnostics_report, Parsed}; +pub use inline_config::InlineConfig; +pub use visit::{Visitable, Visitor}; diff --git a/forge-fmt/src/macros.rs b/forge-fmt/src/macros.rs new file mode 100644 index 000000000..68305d9f2 --- /dev/null +++ b/forge-fmt/src/macros.rs @@ -0,0 +1,125 @@ +macro_rules! write_chunk { + ($self:expr, $format_str:literal) => {{ + write_chunk!($self, $format_str,) + }}; + ($self:expr, $format_str:literal, $($arg:tt)*) => {{ + $self.write_chunk(&format!($format_str, $($arg)*).into()) + }}; + ($self:expr, $loc:expr) => {{ + write_chunk!($self, $loc, "") + }}; + ($self:expr, $loc:expr, $format_str:literal) => {{ + write_chunk!($self, $loc, $format_str,) + }}; + ($self:expr, $loc:expr, $format_str:literal, $($arg:tt)*) => {{ + let chunk = $self.chunk_at($loc, None, None, format_args!($format_str, $($arg)*),); + $self.write_chunk(&chunk) + }}; + ($self:expr, $loc:expr, $end_loc:expr, $format_str:literal) => {{ + write_chunk!($self, $loc, $end_loc, $format_str,) + }}; + ($self:expr, $loc:expr, $end_loc:expr, $format_str:literal, $($arg:tt)*) => {{ + let chunk = $self.chunk_at($loc, Some($end_loc), None, format_args!($format_str, $($arg)*),); + $self.write_chunk(&chunk) + }}; + ($self:expr, $loc:expr, $end_loc:expr, $needs_space:expr, $format_str:literal, $($arg:tt)*) => {{ + let chunk = $self.chunk_at($loc, Some($end_loc), Some($needs_space), format_args!($format_str, $($arg)*),); + $self.write_chunk(&chunk) + }}; +} + +macro_rules! writeln_chunk { + ($self:expr) => {{ + writeln_chunk!($self, "") + }}; + ($self:expr, $format_str:literal) => {{ + writeln_chunk!($self, $format_str,) + }}; + ($self:expr, $format_str:literal, $($arg:tt)*) => {{ + write_chunk!($self, "{}\n", format_args!($format_str, $($arg)*)) + }}; + ($self:expr, $loc:expr) => {{ + writeln_chunk!($self, $loc, "") + }}; + ($self:expr, $loc:expr, $format_str:literal) => {{ + writeln_chunk!($self, $loc, $format_str,) + }}; + ($self:expr, $loc:expr, $format_str:literal, $($arg:tt)*) => {{ + write_chunk!($self, $loc, "{}\n", format_args!($format_str, $($arg)*)) + }}; + ($self:expr, $loc:expr, $end_loc:expr, $format_str:literal) => {{ + writeln_chunk!($self, $loc, $end_loc, $format_str,) + }}; + ($self:expr, $loc:expr, $end_loc:expr, $format_str:literal, $($arg:tt)*) => {{ + write_chunk!($self, $loc, $end_loc, "{}\n", format_args!($format_str, $($arg)*)) + }}; +} + +macro_rules! write_chunk_spaced { + ($self:expr, $loc:expr, $needs_space:expr, $format_str:literal) => {{ + write_chunk_spaced!($self, $loc, $needs_space, $format_str,) + }}; + ($self:expr, $loc:expr, $needs_space:expr, $format_str:literal, $($arg:tt)*) => {{ + let chunk = $self.chunk_at($loc, None, $needs_space, format_args!($format_str, $($arg)*),); + $self.write_chunk(&chunk) + }}; +} + +macro_rules! buf_fn { + ($vis:vis fn $name:ident(&self $(,)? $($arg_name:ident : $arg_ty:ty),*) $(-> $ret:ty)?) => { + $vis fn $name(&self, $($arg_name : $arg_ty),*) $(-> $ret)? { + if self.temp_bufs.is_empty() { + self.buf.$name($($arg_name),*) + } else { + self.temp_bufs.last().unwrap().$name($($arg_name),*) + } + } + }; + ($vis:vis fn $name:ident(&mut self $(,)? $($arg_name:ident : $arg_ty:ty),*) $(-> $ret:ty)?) => { + $vis fn $name(&mut self, $($arg_name : $arg_ty),*) $(-> $ret)? { + if self.temp_bufs.is_empty() { + self.buf.$name($($arg_name),*) + } else { + self.temp_bufs.last_mut().unwrap().$name($($arg_name),*) + } + } + }; +} + +macro_rules! return_source_if_disabled { + ($self:expr, $loc:expr) => {{ + let loc = $loc; + if $self.inline_config.is_disabled(loc) { + trace!("Returning because disabled: {loc:?}"); + return $self.visit_source(loc); + } + }}; + ($self:expr, $loc:expr, $suffix:literal) => {{ + let mut loc = $loc; + let has_suffix = $self.extend_loc_until(&mut loc, $suffix); + if $self.inline_config.is_disabled(loc) { + $self.visit_source(loc)?; + trace!("Returning because disabled: {loc:?}"); + if !has_suffix { + write!($self.buf(), "{}", $suffix)?; + } + return Ok(()); + } + }}; +} + +macro_rules! visit_source_if_disabled_else { + ($self:expr, $loc:expr, $block:block) => {{ + let loc = $loc; + if $self.inline_config.is_disabled(loc) { + $self.visit_source(loc)?; + } else $block + }}; +} + +pub(crate) use buf_fn; +pub(crate) use return_source_if_disabled; +pub(crate) use visit_source_if_disabled_else; +pub(crate) use write_chunk; +pub(crate) use write_chunk_spaced; +pub(crate) use writeln_chunk; diff --git a/forge-fmt/src/solang_ext/ast_eq.rs b/forge-fmt/src/solang_ext/ast_eq.rs new file mode 100644 index 000000000..862bdb9d4 --- /dev/null +++ b/forge-fmt/src/solang_ext/ast_eq.rs @@ -0,0 +1,701 @@ +use ethers_core::types::{H160, I256, U256}; +use solang_parser::pt::*; +use std::str::FromStr; + +/// Helper to convert a string number into a comparable one +fn to_num(string: &str) -> I256 { + if string.is_empty() { + return I256::from(0); + } + string.replace('_', "").trim().parse().unwrap() +} + +/// Helper to convert the fractional part of a number into a comparable one. +/// This will reverse the number so that 0's can be ignored +fn to_num_reversed(string: &str) -> U256 { + if string.is_empty() { + return U256::from(0); + } + string + .replace('_', "") + .trim() + .chars() + .rev() + .collect::() + .parse() + .unwrap() +} + +/// Helper to filter [ParameterList] to omit empty +/// parameters +fn filter_params(list: &ParameterList) -> ParameterList { + list.iter() + .filter(|(_, param)| param.is_some()) + .cloned() + .collect::>() +} + +/// Check if two ParseTrees are equal ignoring location information or ordering if ordering does +/// not matter +pub trait AstEq { + fn ast_eq(&self, other: &Self) -> bool; +} + +impl AstEq for Loc { + fn ast_eq(&self, _other: &Self) -> bool { + true + } +} + +impl AstEq for IdentifierPath { + fn ast_eq(&self, other: &Self) -> bool { + self.identifiers.ast_eq(&other.identifiers) + } +} + +impl AstEq for SourceUnit { + fn ast_eq(&self, other: &Self) -> bool { + self.0.ast_eq(&other.0) + } +} + +impl AstEq for VariableDefinition { + fn ast_eq(&self, other: &Self) -> bool { + let sorted_attrs = |def: &Self| { + let mut attrs = def.attrs.clone(); + attrs.sort(); + attrs + }; + self.ty.ast_eq(&other.ty) + && self.name.ast_eq(&other.name) + && self.initializer.ast_eq(&other.initializer) + && sorted_attrs(self).ast_eq(&sorted_attrs(other)) + } +} + +impl AstEq for FunctionDefinition { + fn ast_eq(&self, other: &Self) -> bool { + // attributes + let sorted_attrs = |def: &Self| { + let mut attrs = def.attributes.clone(); + attrs.sort(); + attrs + }; + + // params + let left_params = filter_params(&self.params); + let right_params = filter_params(&other.params); + let left_returns = filter_params(&self.returns); + let right_returns = filter_params(&other.returns); + + self.ty.ast_eq(&other.ty) + && self.name.ast_eq(&other.name) + && left_params.ast_eq(&right_params) + && self.return_not_returns.ast_eq(&other.return_not_returns) + && left_returns.ast_eq(&right_returns) + && self.body.ast_eq(&other.body) + && sorted_attrs(self).ast_eq(&sorted_attrs(other)) + } +} + +impl AstEq for Base { + fn ast_eq(&self, other: &Self) -> bool { + self.name.ast_eq(&other.name) + && self + .args + .clone() + .unwrap_or_default() + .ast_eq(&other.args.clone().unwrap_or_default()) + } +} + +impl AstEq for Vec +where + T: AstEq, +{ + fn ast_eq(&self, other: &Self) -> bool { + if self.len() != other.len() { + false + } else { + self.iter() + .zip(other.iter()) + .all(|(left, right)| left.ast_eq(right)) + } + } +} + +impl AstEq for Option +where + T: AstEq, +{ + fn ast_eq(&self, other: &Self) -> bool { + match (self, other) { + (Some(left), Some(right)) => left.ast_eq(right), + (None, None) => true, + _ => false, + } + } +} + +impl AstEq for Box +where + T: AstEq, +{ + fn ast_eq(&self, other: &Self) -> bool { + T::ast_eq(self, other) + } +} + +impl AstEq for () { + fn ast_eq(&self, _other: &Self) -> bool { + true + } +} + +impl AstEq for &T +where + T: AstEq, +{ + fn ast_eq(&self, other: &Self) -> bool { + T::ast_eq(self, other) + } +} + +impl AstEq for String { + fn ast_eq(&self, other: &Self) -> bool { + match (H160::from_str(self), H160::from_str(other)) { + (Ok(left), Ok(right)) => left.eq(&right), + _ => self == other, + } + } +} + +macro_rules! ast_eq_field { + (#[ast_eq_use($convert_func:ident)] $field:ident) => { + $convert_func($field) + }; + ($field:ident) => { + $field + }; +} + +macro_rules! gen_ast_eq_enum { + ($self:expr, $other:expr, $name:ident { + $($unit_variant:ident),* $(,)? + _ + $($tuple_variant:ident ( $($(#[ast_eq_use($tuple_convert_func:ident)])? $tuple_field:ident),* $(,)? )),* $(,)? + _ + $($struct_variant:ident { $($(#[ast_eq_use($struct_convert_func:ident)])? $struct_field:ident),* $(,)? }),* $(,)? + }) => { + match $self { + $($name::$unit_variant => gen_ast_eq_enum!($other, $name, $unit_variant),)* + $($name::$tuple_variant($($tuple_field),*) => + gen_ast_eq_enum!($other, $name, $tuple_variant ($($(#[ast_eq_use($tuple_convert_func)])? $tuple_field),*)),)* + $($name::$struct_variant { $($struct_field),* } => + gen_ast_eq_enum!($other, $name, $struct_variant {$($(#[ast_eq_use($struct_convert_func)])? $struct_field),*}),)* + } + }; + ($other:expr, $name:ident, $unit_variant:ident) => { + { + matches!($other, $name::$unit_variant) + } + }; + ($other:expr, $name:ident, $tuple_variant:ident ( $($(#[ast_eq_use($tuple_convert_func:ident)])? $tuple_field:ident),* $(,)? ) ) => { + { + let left = ($(ast_eq_field!($(#[ast_eq_use($tuple_convert_func)])? $tuple_field)),*); + if let $name::$tuple_variant($($tuple_field),*) = $other { + let right = ($(ast_eq_field!($(#[ast_eq_use($tuple_convert_func)])? $tuple_field)),*); + left.ast_eq(&right) + } else { + false + } + } + }; + ($other:expr, $name:ident, $struct_variant:ident { $($(#[ast_eq_use($struct_convert_func:ident)])? $struct_field:ident),* $(,)? } ) => { + { + let left = ($(ast_eq_field!($(#[ast_eq_use($struct_convert_func)])? $struct_field)),*); + if let $name::$struct_variant { $($struct_field),* } = $other { + let right = ($(ast_eq_field!($(#[ast_eq_use($struct_convert_func)])? $struct_field)),*); + left.ast_eq(&right) + } else { + false + } + } + }; +} + +macro_rules! wrap_in_box { + ($stmt:expr, $loc:expr) => { + if !matches!(**$stmt, Statement::Block { .. }) { + Box::new(Statement::Block { + loc: $loc, + unchecked: false, + statements: vec![*$stmt.clone()], + }) + } else { + $stmt.clone() + } + }; +} + +impl AstEq for Statement { + fn ast_eq(&self, other: &Self) -> bool { + match self { + Statement::If(loc, expr, stmt1, stmt2) => { + #[allow(clippy::borrowed_box)] + let wrap_if = |stmt1: &Box, stmt2: &Option>| { + ( + wrap_in_box!(stmt1, *loc), + stmt2.as_ref().map(|stmt2| { + if matches!(**stmt2, Statement::If(..)) { + stmt2.clone() + } else { + wrap_in_box!(stmt2, *loc) + } + }), + ) + }; + let (stmt1, stmt2) = wrap_if(stmt1, stmt2); + let left = (loc, expr, &stmt1, &stmt2); + if let Statement::If(loc, expr, stmt1, stmt2) = other { + let (stmt1, stmt2) = wrap_if(stmt1, stmt2); + let right = (loc, expr, &stmt1, &stmt2); + left.ast_eq(&right) + } else { + false + } + } + Statement::While(loc, expr, stmt1) => { + let stmt1 = wrap_in_box!(stmt1, *loc); + let left = (loc, expr, &stmt1); + if let Statement::While(loc, expr, stmt1) = other { + let stmt1 = wrap_in_box!(stmt1, *loc); + let right = (loc, expr, &stmt1); + left.ast_eq(&right) + } else { + false + } + } + Statement::DoWhile(loc, stmt1, expr) => { + let stmt1 = wrap_in_box!(stmt1, *loc); + let left = (loc, &stmt1, expr); + if let Statement::DoWhile(loc, stmt1, expr) = other { + let stmt1 = wrap_in_box!(stmt1, *loc); + let right = (loc, &stmt1, expr); + left.ast_eq(&right) + } else { + false + } + } + Statement::For(loc, stmt1, expr, stmt2, stmt3) => { + let stmt3 = stmt3.as_ref().map(|stmt3| wrap_in_box!(stmt3, *loc)); + let left = (loc, stmt1, expr, stmt2, &stmt3); + if let Statement::For(loc, stmt1, expr, stmt2, stmt3) = other { + let stmt3 = stmt3.as_ref().map(|stmt3| wrap_in_box!(stmt3, *loc)); + let right = (loc, stmt1, expr, stmt2, &stmt3); + left.ast_eq(&right) + } else { + false + } + } + Statement::Try(loc, expr, returns, catch) => { + let left_returns = returns + .as_ref() + .map(|(params, stmt)| (filter_params(params), stmt)); + let left = (loc, expr, left_returns, catch); + if let Statement::Try(loc, expr, returns, catch) = other { + let right_returns = returns + .as_ref() + .map(|(params, stmt)| (filter_params(params), stmt)); + let right = (loc, expr, right_returns, catch); + left.ast_eq(&right) + } else { + false + } + } + _ => gen_ast_eq_enum!(self, other, Statement { + _ + Args(loc, args), + Expression(loc, expr), + VariableDefinition(loc, decl, expr), + Continue(loc, ), + Break(loc, ), + Return(loc, expr), + Revert(loc, expr, expr2), + RevertNamedArgs(loc, expr, args), + Emit(loc, expr), + // provide overridden variants regardless + If(loc, expr, stmt1, stmt2), + While(loc, expr, stmt1), + DoWhile(loc, stmt1, expr), + For(loc, stmt1, expr, stmt2, stmt3), + Try(loc, expr, params, claus), + Error(loc) + _ + Block { + loc, + unchecked, + statements, + }, + Assembly { + loc, + dialect, + block, + flags, + }, + }), + } + } +} + +macro_rules! derive_ast_eq { + ($name:ident) => { + impl AstEq for $name { + fn ast_eq(&self, other: &Self) -> bool { + self == other + } + } + }; + (($($index:tt $gen:tt),*)) => { + impl < $( $gen ),* > AstEq for ($($gen,)*) where $($gen: AstEq),* { + fn ast_eq(&self, other: &Self) -> bool { + $( + if !self.$index.ast_eq(&other.$index) { + return false + } + )* + true + } + } + }; + (struct $name:ident { $($field:ident),* $(,)? }) => { + impl AstEq for $name { + fn ast_eq(&self, other: &Self) -> bool { + let $name { $($field),* } = self; + let left = ($($field),*); + let $name { $($field),* } = other; + let right = ($($field),*); + left.ast_eq(&right) + } + } + }; + (enum $name:ident { + $($unit_variant:ident),* $(,)? + _ + $($tuple_variant:ident ( $($(#[ast_eq_use($tuple_convert_func:ident)])? $tuple_field:ident),* $(,)? )),* $(,)? + _ + $($struct_variant:ident { $($(#[ast_eq_use($struct_convert_func:ident)])? $struct_field:ident),* $(,)? }),* $(,)? + }) => { + impl AstEq for $name { + fn ast_eq(&self, other: &Self) -> bool { + gen_ast_eq_enum!(self, other, $name { + $($unit_variant),* + _ + $($tuple_variant ( $($(#[ast_eq_use($tuple_convert_func)])? $tuple_field),* )),* + _ + $($struct_variant { $($(#[ast_eq_use($struct_convert_func)])? $struct_field),* }),* + }) + } + } + } +} + +derive_ast_eq! { (0 A) } +derive_ast_eq! { (0 A, 1 B) } +derive_ast_eq! { (0 A, 1 B, 2 C) } +derive_ast_eq! { (0 A, 1 B, 2 C, 3 D) } +derive_ast_eq! { (0 A, 1 B, 2 C, 3 D, 4 E) } +derive_ast_eq! { bool } +derive_ast_eq! { u8 } +derive_ast_eq! { u16 } +derive_ast_eq! { I256 } +derive_ast_eq! { U256 } +derive_ast_eq! { struct Identifier { loc, name } } +derive_ast_eq! { struct HexLiteral { loc, hex } } +derive_ast_eq! { struct StringLiteral { loc, unicode, string } } +derive_ast_eq! { struct Parameter { loc, annotation, ty, storage, name } } +derive_ast_eq! { struct NamedArgument { loc, name, expr } } +derive_ast_eq! { struct YulBlock { loc, statements } } +derive_ast_eq! { struct YulFunctionCall { loc, id, arguments } } +derive_ast_eq! { struct YulFunctionDefinition { loc, id, params, returns, body } } +derive_ast_eq! { struct YulSwitch { loc, condition, cases, default } } +derive_ast_eq! { struct YulFor { + loc, + init_block, + condition, + post_block, + execution_block, +}} +derive_ast_eq! { struct YulTypedIdentifier { loc, id, ty } } +derive_ast_eq! { struct VariableDeclaration { loc, ty, storage, name } } +derive_ast_eq! { struct Using { loc, list, ty, global } } +derive_ast_eq! { struct UsingFunction { loc, path, oper } } +derive_ast_eq! { struct TypeDefinition { loc, name, ty } } +derive_ast_eq! { struct ContractDefinition { loc, ty, name, base, parts } } +derive_ast_eq! { struct EventParameter { loc, ty, indexed, name } } +derive_ast_eq! { struct ErrorParameter { loc, ty, name } } +derive_ast_eq! { struct EventDefinition { loc, name, fields, anonymous } } +derive_ast_eq! { struct ErrorDefinition { loc, keyword, name, fields } } +derive_ast_eq! { struct StructDefinition { loc, name, fields } } +derive_ast_eq! { struct EnumDefinition { loc, name, values } } +derive_ast_eq! { struct Annotation { loc, id, value } } +derive_ast_eq! { enum UsingList { + Error, + _ + Library(expr), + Functions(exprs), + _ +}} +derive_ast_eq! { enum UserDefinedOperator { + BitwiseAnd, + BitwiseNot, + Negate, + BitwiseOr, + BitwiseXor, + Add, + Divide, + Modulo, + Multiply, + Subtract, + Equal, + More, + MoreEqual, + Less, + LessEqual, + NotEqual, + _ + _ +}} +derive_ast_eq! { enum Visibility { + _ + External(loc), + Public(loc), + Internal(loc), + Private(loc), + _ +}} +derive_ast_eq! { enum Mutability { + _ + Pure(loc), + View(loc), + Constant(loc), + Payable(loc), + _ +}} +derive_ast_eq! { enum FunctionAttribute { + _ + Mutability(muta), + Visibility(visi), + Virtual(loc), + Immutable(loc), + Override(loc, idents), + BaseOrModifier(loc, base), + Error(loc), + _ +}} +derive_ast_eq! { enum StorageLocation { + _ + Memory(loc), + Storage(loc), + Calldata(loc), + _ +}} +derive_ast_eq! { enum Type { + Address, + AddressPayable, + Payable, + Bool, + Rational, + DynamicBytes, + String, + _ + Int(int), + Uint(int), + Bytes(int), + _ + Mapping{ loc, key, key_name, value, value_name }, + Function { params, attributes, returns }, +}} +derive_ast_eq! { enum Expression { + _ + PostIncrement(loc, expr1), + PostDecrement(loc, expr1), + New(loc, expr1), + ArraySubscript(loc, expr1, expr2), + ArraySlice( + loc, + expr1, + expr2, + expr3, + ), + MemberAccess(loc, expr1, ident1), + FunctionCall(loc, expr1, exprs1), + FunctionCallBlock(loc, expr1, stmt), + NamedFunctionCall(loc, expr1, args), + Not(loc, expr1), + BitwiseNot(loc, expr1), + Delete(loc, expr1), + PreIncrement(loc, expr1), + PreDecrement(loc, expr1), + UnaryPlus(loc, expr1), + Negate(loc, expr1), + Power(loc, expr1, expr2), + Multiply(loc, expr1, expr2), + Divide(loc, expr1, expr2), + Modulo(loc, expr1, expr2), + Add(loc, expr1, expr2), + Subtract(loc, expr1, expr2), + ShiftLeft(loc, expr1, expr2), + ShiftRight(loc, expr1, expr2), + BitwiseAnd(loc, expr1, expr2), + BitwiseXor(loc, expr1, expr2), + BitwiseOr(loc, expr1, expr2), + Less(loc, expr1, expr2), + More(loc, expr1, expr2), + LessEqual(loc, expr1, expr2), + MoreEqual(loc, expr1, expr2), + Equal(loc, expr1, expr2), + NotEqual(loc, expr1, expr2), + And(loc, expr1, expr2), + Or(loc, expr1, expr2), + ConditionalOperator(loc, expr1, expr2, expr3), + Assign(loc, expr1, expr2), + AssignOr(loc, expr1, expr2), + AssignAnd(loc, expr1, expr2), + AssignXor(loc, expr1, expr2), + AssignShiftLeft(loc, expr1, expr2), + AssignShiftRight(loc, expr1, expr2), + AssignAdd(loc, expr1, expr2), + AssignSubtract(loc, expr1, expr2), + AssignMultiply(loc, expr1, expr2), + AssignDivide(loc, expr1, expr2), + AssignModulo(loc, expr1, expr2), + BoolLiteral(loc, bool1), + NumberLiteral(loc, #[ast_eq_use(to_num)] str1, #[ast_eq_use(to_num)] str2, unit), + RationalNumberLiteral( + loc, + #[ast_eq_use(to_num)] str1, + #[ast_eq_use(to_num_reversed)] str2, + #[ast_eq_use(to_num)] str3, + unit + ), + HexNumberLiteral(loc, str1, unit), + StringLiteral(strs1), + Type(loc, ty1), + HexLiteral(hexs1), + AddressLiteral(loc, str1), + Variable(ident1), + List(loc, params1), + ArrayLiteral(loc, exprs1), + Parenthesis(loc, expr) + _ +}} +derive_ast_eq! { enum CatchClause { + _ + Simple(param, ident, stmt), + Named(loc, ident, param, stmt), + _ +}} +derive_ast_eq! { enum YulStatement { + _ + Assign(loc, exprs, expr), + VariableDeclaration(loc, idents, expr), + If(loc, expr, block), + For(yul_for), + Switch(switch), + Leave(loc), + Break(loc), + Continue(loc), + Block(block), + FunctionDefinition(def), + FunctionCall(func), + Error(loc), + _ +}} +derive_ast_eq! { enum YulExpression { + _ + BoolLiteral(loc, boo, ident), + NumberLiteral(loc, string1, string2, ident), + HexNumberLiteral(loc, string, ident), + HexStringLiteral(hex, ident), + StringLiteral(string, ident), + Variable(ident), + FunctionCall(func), + SuffixAccess(loc, expr, ident), + _ +}} +derive_ast_eq! { enum YulSwitchOptions { + _ + Case(loc, expr, block), + Default(loc, block), + _ +}} +derive_ast_eq! { enum SourceUnitPart { + _ + ContractDefinition(def), + PragmaDirective(loc, ident, string), + ImportDirective(import), + EnumDefinition(def), + StructDefinition(def), + EventDefinition(def), + ErrorDefinition(def), + FunctionDefinition(def), + VariableDefinition(def), + TypeDefinition(def), + Using(using), + StraySemicolon(loc), + Annotation(annotation), + _ +}} +derive_ast_eq! { enum ImportPath { + _ + Filename(lit), + Path(path), + _ +}} +derive_ast_eq! { enum Import { + _ + Plain(string, loc), + GlobalSymbol(string, ident, loc), + Rename(string, idents, loc), + _ +}} +derive_ast_eq! { enum FunctionTy { + Constructor, + Function, + Fallback, + Receive, + Modifier, + _ + _ +}} +derive_ast_eq! { enum ContractPart { + _ + StructDefinition(def), + EventDefinition(def), + EnumDefinition(def), + ErrorDefinition(def), + VariableDefinition(def), + FunctionDefinition(def), + TypeDefinition(def), + StraySemicolon(loc), + Using(using), + Annotation(annotation), + _ +}} +derive_ast_eq! { enum ContractTy { + _ + Abstract(loc), + Contract(loc), + Interface(loc), + Library(loc), + _ +}} +derive_ast_eq! { enum VariableAttribute { + _ + Visibility(visi), + Constant(loc), + Immutable(loc), + Override(loc, idents), + _ +}} diff --git a/forge-fmt/src/solang_ext/loc.rs b/forge-fmt/src/solang_ext/loc.rs new file mode 100644 index 000000000..2fcbaf995 --- /dev/null +++ b/forge-fmt/src/solang_ext/loc.rs @@ -0,0 +1,156 @@ +use solang_parser::pt; +use std::{borrow::Cow, rc::Rc, sync::Arc}; + +/// Returns the code location. +/// +/// Patched version of [`pt::CodeLocation`]: includes the block of a [`pt::FunctionDefinition`] in +/// its `loc`. +pub trait CodeLocationExt { + /// Returns the code location of `self`. + fn loc(&self) -> pt::Loc; +} + +impl<'a, T: ?Sized + CodeLocationExt> CodeLocationExt for &'a T { + fn loc(&self) -> pt::Loc { + (**self).loc() + } +} + +impl<'a, T: ?Sized + CodeLocationExt> CodeLocationExt for &'a mut T { + fn loc(&self) -> pt::Loc { + (**self).loc() + } +} + +impl<'a, T: ?Sized + ToOwned + CodeLocationExt> CodeLocationExt for Cow<'a, T> { + fn loc(&self) -> pt::Loc { + (**self).loc() + } +} + +impl CodeLocationExt for Box { + fn loc(&self) -> pt::Loc { + (**self).loc() + } +} + +impl CodeLocationExt for Rc { + fn loc(&self) -> pt::Loc { + (**self).loc() + } +} + +impl CodeLocationExt for Arc { + fn loc(&self) -> pt::Loc { + (**self).loc() + } +} + +// FunctionDefinition patch +impl CodeLocationExt for pt::FunctionDefinition { + #[inline] + #[track_caller] + fn loc(&self) -> pt::Loc { + let mut loc = self.loc; + if let Some(ref body) = self.body { + loc.use_end_from(&pt::CodeLocation::loc(body)); + } + loc + } +} + +impl CodeLocationExt for pt::ContractPart { + #[inline] + #[track_caller] + fn loc(&self) -> pt::Loc { + match self { + Self::FunctionDefinition(f) => f.loc(), + _ => pt::CodeLocation::loc(self), + } + } +} + +impl CodeLocationExt for pt::SourceUnitPart { + #[inline] + #[track_caller] + fn loc(&self) -> pt::Loc { + match self { + Self::FunctionDefinition(f) => f.loc(), + _ => pt::CodeLocation::loc(self), + } + } +} + +impl CodeLocationExt for pt::ImportPath { + fn loc(&self) -> pt::Loc { + match self { + Self::Filename(s) => s.loc(), + Self::Path(i) => i.loc(), + } + } +} + +macro_rules! impl_delegate { + ($($t:ty),+ $(,)?) => {$( + impl CodeLocationExt for $t { + #[inline] + #[track_caller] + fn loc(&self) -> pt::Loc { + pt::CodeLocation::loc(self) + } + } + )+}; +} + +impl_delegate! { + pt::Annotation, + pt::Base, + pt::ContractDefinition, + pt::EnumDefinition, + pt::ErrorDefinition, + pt::ErrorParameter, + pt::EventDefinition, + pt::EventParameter, + // pt::FunctionDefinition, + pt::HexLiteral, + pt::Identifier, + pt::IdentifierPath, + pt::NamedArgument, + pt::Parameter, + // pt::SourceUnit, + pt::StringLiteral, + pt::StructDefinition, + pt::TypeDefinition, + pt::Using, + pt::UsingFunction, + pt::VariableDeclaration, + pt::VariableDefinition, + pt::YulBlock, + pt::YulFor, + pt::YulFunctionCall, + pt::YulFunctionDefinition, + pt::YulSwitch, + pt::YulTypedIdentifier, + + pt::CatchClause, + pt::Comment, + // pt::ContractPart, + pt::ContractTy, + pt::Expression, + pt::FunctionAttribute, + // pt::FunctionTy, + pt::Import, + pt::Loc, + pt::Mutability, + // pt::SourceUnitPart, + pt::Statement, + pt::StorageLocation, + // pt::Type, + // pt::UserDefinedOperator, + pt::UsingList, + pt::VariableAttribute, + // pt::Visibility, + pt::YulExpression, + pt::YulStatement, + pt::YulSwitchOptions, +} diff --git a/forge-fmt/src/solang_ext/mod.rs b/forge-fmt/src/solang_ext/mod.rs new file mode 100644 index 000000000..aa4fe734e --- /dev/null +++ b/forge-fmt/src/solang_ext/mod.rs @@ -0,0 +1,28 @@ +//! Extension traits and modules to the [`solang_parser`] crate. + +/// Same as [`solang_parser::pt`], but with the patched `CodeLocation`. +pub mod pt { + #[doc(no_inline)] + pub use super::loc::CodeLocationExt as CodeLocation; + + #[doc(no_inline)] + pub use solang_parser::pt::{ + Annotation, Base, CatchClause, Comment, ContractDefinition, ContractPart, ContractTy, + EnumDefinition, ErrorDefinition, ErrorParameter, EventDefinition, EventParameter, + Expression, FunctionAttribute, FunctionDefinition, FunctionTy, HexLiteral, Identifier, + IdentifierPath, Import, ImportPath, Loc, Mutability, NamedArgument, OptionalCodeLocation, + Parameter, ParameterList, SourceUnit, SourceUnitPart, Statement, StorageLocation, + StringLiteral, StructDefinition, Type, TypeDefinition, UserDefinedOperator, Using, + UsingFunction, UsingList, VariableAttribute, VariableDeclaration, VariableDefinition, + Visibility, YulBlock, YulExpression, YulFor, YulFunctionCall, YulFunctionDefinition, + YulStatement, YulSwitch, YulSwitchOptions, YulTypedIdentifier, + }; +} + +mod ast_eq; +mod loc; +mod safe_unwrap; + +pub use ast_eq::AstEq; +pub use loc::CodeLocationExt; +pub use safe_unwrap::SafeUnwrap; diff --git a/forge-fmt/src/solang_ext/safe_unwrap.rs b/forge-fmt/src/solang_ext/safe_unwrap.rs new file mode 100644 index 000000000..fe2810ad9 --- /dev/null +++ b/forge-fmt/src/solang_ext/safe_unwrap.rs @@ -0,0 +1,52 @@ +use solang_parser::pt; + +/// Trait implemented to unwrap optional parse tree items initially introduced in +/// [hyperledger/solang#1068]. +/// +/// Note that the methods of this trait should only be used on parse tree items' fields, like +/// [pt::VariableDefinition] or [pt::EventDefinition], where the `name` field is `None` only when an +/// error occurred during parsing. +/// +/// [hyperledger/solang#1068]: https://github.com/hyperledger/solang/pull/1068 +pub trait SafeUnwrap { + /// See [SafeUnwrap]. + fn safe_unwrap(&self) -> &T; + + /// See [SafeUnwrap]. + fn safe_unwrap_mut(&mut self) -> &mut T; +} + +#[inline(never)] +#[cold] +#[track_caller] +fn invalid() -> ! { + panic!("invalid parse tree") +} + +macro_rules! impl_ { + ($($t:ty),+ $(,)?) => { + $( + impl SafeUnwrap<$t> for Option<$t> { + #[inline] + #[track_caller] + fn safe_unwrap(&self) -> &$t { + match *self { + Some(ref x) => x, + None => invalid(), + } + } + + #[inline] + #[track_caller] + fn safe_unwrap_mut(&mut self) -> &mut $t { + match *self { + Some(ref mut x) => x, + None => invalid(), + } + } + } + )+ + }; +} + +impl_!(pt::Identifier, pt::StringLiteral); diff --git a/forge-fmt/src/string.rs b/forge-fmt/src/string.rs new file mode 100644 index 000000000..c387d2389 --- /dev/null +++ b/forge-fmt/src/string.rs @@ -0,0 +1,182 @@ +//! Helpfers for dealing with quoted strings + +/// The state of a character in a string with quotable components +/// This is a simplified version of the +/// [actual parser](https://docs.soliditylang.org/en/v0.8.15/grammar.html#a4.SolidityLexer.EscapeSequence) +/// as we don't care about hex or other character meanings +#[derive(Copy, Clone, Debug, Eq, PartialEq, Default)] +pub enum QuoteState { + /// Not currently in quoted string + #[default] + None, + /// The opening character of a quoted string + Opening(char), + /// A character in a quoted string + String(char), + /// The `\` in an escape sequence `"\n"` + Escaping(char), + /// The escaped character e.g. `n` in `"\n"` + Escaped(char), + /// The closing character + Closing(char), +} + +/// An iterator over characters and indices in a string slice with information about quoted string +/// states +pub struct QuoteStateCharIndices<'a> { + iter: std::str::CharIndices<'a>, + state: QuoteState, +} + +impl<'a> QuoteStateCharIndices<'a> { + fn new(string: &'a str) -> Self { + Self { + iter: string.char_indices(), + state: QuoteState::None, + } + } + pub fn with_state(mut self, state: QuoteState) -> Self { + self.state = state; + self + } +} + +impl<'a> Iterator for QuoteStateCharIndices<'a> { + type Item = (QuoteState, usize, char); + fn next(&mut self) -> Option { + let (idx, ch) = self.iter.next()?; + match self.state { + QuoteState::None | QuoteState::Closing(_) => { + if ch == '\'' || ch == '"' { + self.state = QuoteState::Opening(ch); + } else { + self.state = QuoteState::None + } + } + QuoteState::String(quote) | QuoteState::Opening(quote) | QuoteState::Escaped(quote) => { + if ch == quote { + self.state = QuoteState::Closing(quote) + } else if ch == '\\' { + self.state = QuoteState::Escaping(quote) + } else { + self.state = QuoteState::String(quote) + } + } + QuoteState::Escaping(quote) => self.state = QuoteState::Escaped(quote), + } + Some((self.state, idx, ch)) + } +} + +/// An iterator over the the indices of quoted string locations +pub struct QuotedRanges<'a>(QuoteStateCharIndices<'a>); + +impl<'a> QuotedRanges<'a> { + pub fn with_state(mut self, state: QuoteState) -> Self { + self.0 = self.0.with_state(state); + self + } +} + +impl<'a> Iterator for QuotedRanges<'a> { + type Item = (char, usize, usize); + fn next(&mut self) -> Option { + let (quote, start) = loop { + let (state, idx, _) = self.0.next()?; + match state { + QuoteState::Opening(quote) + | QuoteState::Escaping(quote) + | QuoteState::Escaped(quote) + | QuoteState::String(quote) => break (quote, idx), + QuoteState::Closing(quote) => return Some((quote, idx, idx)), + QuoteState::None => {} + } + }; + for (state, idx, _) in self.0.by_ref() { + if matches!(state, QuoteState::Closing(_)) { + return Some((quote, start, idx)); + } + } + None + } +} + +/// Helpers for iterating over quoted strings +pub trait QuotedStringExt { + /// Get an iterator of characters, indices and their quoted string state + fn quote_state_char_indices(&self) -> QuoteStateCharIndices; + /// Get an iterator of quoted string ranges + fn quoted_ranges(&self) -> QuotedRanges { + QuotedRanges(self.quote_state_char_indices()) + } + /// Check to see if a string is quoted. This will return true if the first character + /// is a quote and the last character is a quote with no non-quoted sections in between. + fn is_quoted(&self) -> bool { + let mut iter = self.quote_state_char_indices(); + if !matches!(iter.next(), Some((QuoteState::Opening(_), _, _))) { + return false; + } + while let Some((state, _, _)) = iter.next() { + if matches!(state, QuoteState::Closing(_)) { + return iter.next().is_none(); + } + } + false + } +} + +impl QuotedStringExt for T +where + T: AsRef, +{ + fn quote_state_char_indices(&self) -> QuoteStateCharIndices { + QuoteStateCharIndices::new(self.as_ref()) + } +} + +impl QuotedStringExt for str { + fn quote_state_char_indices(&self) -> QuoteStateCharIndices { + QuoteStateCharIndices::new(self) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use pretty_assertions::assert_eq; + + #[test] + fn quote_state_char_indices() { + assert_eq!( + r#"a'a"\'\"\n\\'a"#.quote_state_char_indices().collect::>(), + vec![ + (QuoteState::None, 0, 'a'), + (QuoteState::Opening('\''), 1, '\''), + (QuoteState::String('\''), 2, 'a'), + (QuoteState::String('\''), 3, '"'), + (QuoteState::Escaping('\''), 4, '\\'), + (QuoteState::Escaped('\''), 5, '\''), + (QuoteState::Escaping('\''), 6, '\\'), + (QuoteState::Escaped('\''), 7, '"'), + (QuoteState::Escaping('\''), 8, '\\'), + (QuoteState::Escaped('\''), 9, 'n'), + (QuoteState::Escaping('\''), 10, '\\'), + (QuoteState::Escaped('\''), 11, '\\'), + (QuoteState::Closing('\''), 12, '\''), + (QuoteState::None, 13, 'a'), + ] + ); + } + + #[test] + fn quoted_ranges() { + let string = r#"testing "double quoted" and 'single quoted' strings"#; + assert_eq!( + string + .quoted_ranges() + .map(|(quote, start, end)| (quote, &string[start..=end])) + .collect::>(), + vec![('"', r#""double quoted""#), ('\'', "'single quoted'")] + ); + } +} diff --git a/forge-fmt/src/visit.rs b/forge-fmt/src/visit.rs new file mode 100644 index 000000000..d4ed491cc --- /dev/null +++ b/forge-fmt/src/visit.rs @@ -0,0 +1,655 @@ +//! Visitor helpers to traverse the [solang Solidity Parse Tree](solang_parser::pt). + +use crate::solang_ext::pt::*; + +/// A trait that is invoked while traversing the Solidity Parse Tree. +/// Each method of the [Visitor] trait is a hook that can be potentially overridden. +/// +/// Currently the main implementor of this trait is the [`Formatter`](crate::Formatter) struct. +pub trait Visitor { + type Error: std::error::Error; + + fn visit_source(&mut self, _loc: Loc) -> Result<(), Self::Error> { + Ok(()) + } + + fn visit_source_unit(&mut self, _source_unit: &mut SourceUnit) -> Result<(), Self::Error> { + Ok(()) + } + + fn visit_contract(&mut self, _contract: &mut ContractDefinition) -> Result<(), Self::Error> { + Ok(()) + } + + fn visit_annotation(&mut self, annotation: &mut Annotation) -> Result<(), Self::Error> { + self.visit_source(annotation.loc) + } + + fn visit_pragma( + &mut self, + loc: Loc, + _ident: &mut Option, + _str: &mut Option, + ) -> Result<(), Self::Error> { + self.visit_source(loc) + } + + fn visit_import_plain( + &mut self, + _loc: Loc, + _import: &mut ImportPath, + ) -> Result<(), Self::Error> { + Ok(()) + } + + fn visit_import_global( + &mut self, + _loc: Loc, + _global: &mut ImportPath, + _alias: &mut Identifier, + ) -> Result<(), Self::Error> { + Ok(()) + } + + fn visit_import_renames( + &mut self, + _loc: Loc, + _imports: &mut [(Identifier, Option)], + _from: &mut ImportPath, + ) -> Result<(), Self::Error> { + Ok(()) + } + + fn visit_enum(&mut self, _enum: &mut EnumDefinition) -> Result<(), Self::Error> { + Ok(()) + } + + fn visit_assembly( + &mut self, + loc: Loc, + _dialect: &mut Option, + _block: &mut YulBlock, + _flags: &mut Option>, + ) -> Result<(), Self::Error> { + self.visit_source(loc) + } + + fn visit_block( + &mut self, + loc: Loc, + _unchecked: bool, + _statements: &mut Vec, + ) -> Result<(), Self::Error> { + self.visit_source(loc) + } + + fn visit_args(&mut self, loc: Loc, _args: &mut Vec) -> Result<(), Self::Error> { + self.visit_source(loc) + } + + /// Don't write semicolon at the end because expressions can appear as both + /// part of other node and a statement in the function body + fn visit_expr(&mut self, loc: Loc, _expr: &mut Expression) -> Result<(), Self::Error> { + self.visit_source(loc) + } + + fn visit_ident(&mut self, loc: Loc, _ident: &mut Identifier) -> Result<(), Self::Error> { + self.visit_source(loc) + } + + fn visit_ident_path(&mut self, idents: &mut IdentifierPath) -> Result<(), Self::Error> { + self.visit_source(idents.loc) + } + + fn visit_emit(&mut self, loc: Loc, _event: &mut Expression) -> Result<(), Self::Error> { + self.visit_source(loc)?; + self.visit_stray_semicolon()?; + + Ok(()) + } + + fn visit_var_definition(&mut self, var: &mut VariableDefinition) -> Result<(), Self::Error> { + self.visit_source(var.loc)?; + self.visit_stray_semicolon()?; + + Ok(()) + } + + fn visit_var_definition_stmt( + &mut self, + loc: Loc, + _declaration: &mut VariableDeclaration, + _expr: &mut Option, + ) -> Result<(), Self::Error> { + self.visit_source(loc)?; + self.visit_stray_semicolon() + } + + fn visit_var_declaration(&mut self, var: &mut VariableDeclaration) -> Result<(), Self::Error> { + self.visit_source(var.loc) + } + + fn visit_return( + &mut self, + loc: Loc, + _expr: &mut Option, + ) -> Result<(), Self::Error> { + self.visit_source(loc)?; + self.visit_stray_semicolon()?; + + Ok(()) + } + + fn visit_revert( + &mut self, + loc: Loc, + _error: &mut Option, + _args: &mut Vec, + ) -> Result<(), Self::Error> { + self.visit_source(loc)?; + self.visit_stray_semicolon()?; + + Ok(()) + } + + fn visit_revert_named_args( + &mut self, + loc: Loc, + _error: &mut Option, + _args: &mut Vec, + ) -> Result<(), Self::Error> { + self.visit_source(loc)?; + self.visit_stray_semicolon()?; + + Ok(()) + } + + fn visit_break(&mut self, loc: Loc, _semicolon: bool) -> Result<(), Self::Error> { + self.visit_source(loc) + } + + fn visit_continue(&mut self, loc: Loc, _semicolon: bool) -> Result<(), Self::Error> { + self.visit_source(loc) + } + + #[allow(clippy::type_complexity)] + fn visit_try( + &mut self, + loc: Loc, + _expr: &mut Expression, + _returns: &mut Option<(Vec<(Loc, Option)>, Box)>, + _clauses: &mut Vec, + ) -> Result<(), Self::Error> { + self.visit_source(loc) + } + + fn visit_if( + &mut self, + loc: Loc, + _cond: &mut Expression, + _if_branch: &mut Box, + _else_branch: &mut Option>, + _is_first_stmt: bool, + ) -> Result<(), Self::Error> { + self.visit_source(loc) + } + + fn visit_do_while( + &mut self, + loc: Loc, + _body: &mut Statement, + _cond: &mut Expression, + ) -> Result<(), Self::Error> { + self.visit_source(loc) + } + + fn visit_while( + &mut self, + loc: Loc, + _cond: &mut Expression, + _body: &mut Statement, + ) -> Result<(), Self::Error> { + self.visit_source(loc) + } + + fn visit_for( + &mut self, + loc: Loc, + _init: &mut Option>, + _cond: &mut Option>, + _update: &mut Option>, + _body: &mut Option>, + ) -> Result<(), Self::Error> { + self.visit_source(loc) + } + + fn visit_function(&mut self, func: &mut FunctionDefinition) -> Result<(), Self::Error> { + self.visit_source(func.loc())?; + if func.body.is_none() { + self.visit_stray_semicolon()?; + } + + Ok(()) + } + + fn visit_function_attribute( + &mut self, + attribute: &mut FunctionAttribute, + ) -> Result<(), Self::Error> { + self.visit_source(attribute.loc())?; + Ok(()) + } + + fn visit_var_attribute( + &mut self, + attribute: &mut VariableAttribute, + ) -> Result<(), Self::Error> { + self.visit_source(attribute.loc())?; + Ok(()) + } + + fn visit_base(&mut self, base: &mut Base) -> Result<(), Self::Error> { + self.visit_source(base.loc) + } + + fn visit_parameter(&mut self, parameter: &mut Parameter) -> Result<(), Self::Error> { + self.visit_source(parameter.loc) + } + + fn visit_struct(&mut self, structure: &mut StructDefinition) -> Result<(), Self::Error> { + self.visit_source(structure.loc)?; + + Ok(()) + } + + fn visit_event(&mut self, event: &mut EventDefinition) -> Result<(), Self::Error> { + self.visit_source(event.loc)?; + self.visit_stray_semicolon()?; + + Ok(()) + } + + fn visit_event_parameter(&mut self, param: &mut EventParameter) -> Result<(), Self::Error> { + self.visit_source(param.loc) + } + + fn visit_error(&mut self, error: &mut ErrorDefinition) -> Result<(), Self::Error> { + self.visit_source(error.loc)?; + self.visit_stray_semicolon()?; + + Ok(()) + } + + fn visit_error_parameter(&mut self, param: &mut ErrorParameter) -> Result<(), Self::Error> { + self.visit_source(param.loc) + } + + fn visit_type_definition(&mut self, def: &mut TypeDefinition) -> Result<(), Self::Error> { + self.visit_source(def.loc) + } + + fn visit_stray_semicolon(&mut self) -> Result<(), Self::Error> { + Ok(()) + } + + fn visit_opening_paren(&mut self) -> Result<(), Self::Error> { + Ok(()) + } + + fn visit_closing_paren(&mut self) -> Result<(), Self::Error> { + Ok(()) + } + + fn visit_newline(&mut self) -> Result<(), Self::Error> { + Ok(()) + } + + fn visit_using(&mut self, using: &mut Using) -> Result<(), Self::Error> { + self.visit_source(using.loc)?; + self.visit_stray_semicolon()?; + + Ok(()) + } + + fn visit_yul_block( + &mut self, + loc: Loc, + _stmts: &mut Vec, + _attempt_single_line: bool, + ) -> Result<(), Self::Error> { + self.visit_source(loc) + } + + fn visit_yul_expr(&mut self, expr: &mut YulExpression) -> Result<(), Self::Error> { + self.visit_source(expr.loc()) + } + + fn visit_yul_assignment( + &mut self, + loc: Loc, + _exprs: &mut Vec, + _expr: &mut Option<&mut YulExpression>, + ) -> Result<(), Self::Error> + where + T: Visitable + CodeLocation, + { + self.visit_source(loc) + } + + fn visit_yul_for(&mut self, stmt: &mut YulFor) -> Result<(), Self::Error> { + self.visit_source(stmt.loc) + } + + fn visit_yul_function_call(&mut self, stmt: &mut YulFunctionCall) -> Result<(), Self::Error> { + self.visit_source(stmt.loc) + } + + fn visit_yul_fun_def(&mut self, stmt: &mut YulFunctionDefinition) -> Result<(), Self::Error> { + self.visit_source(stmt.loc) + } + + fn visit_yul_if( + &mut self, + loc: Loc, + _expr: &mut YulExpression, + _block: &mut YulBlock, + ) -> Result<(), Self::Error> { + self.visit_source(loc) + } + + fn visit_yul_leave(&mut self, loc: Loc) -> Result<(), Self::Error> { + self.visit_source(loc) + } + + fn visit_yul_switch(&mut self, stmt: &mut YulSwitch) -> Result<(), Self::Error> { + self.visit_source(stmt.loc) + } + + fn visit_yul_var_declaration( + &mut self, + loc: Loc, + _idents: &mut Vec, + _expr: &mut Option, + ) -> Result<(), Self::Error> { + self.visit_source(loc) + } + + fn visit_yul_typed_ident(&mut self, ident: &mut YulTypedIdentifier) -> Result<(), Self::Error> { + self.visit_source(ident.loc) + } + + fn visit_parser_error(&mut self, loc: Loc) -> Result<(), Self::Error> { + self.visit_source(loc) + } +} + +/// All [`solang_parser::pt`] types, such as [Statement], should implement the [Visitable] trait +/// that accepts a trait [Visitor] implementation, which has various callback handles for Solidity +/// Parse Tree nodes. +/// +/// We want to take a `&mut self` to be able to implement some advanced features in the future such +/// as modifying the Parse Tree before formatting it. +pub trait Visitable { + fn visit(&mut self, v: &mut V) -> Result<(), V::Error> + where + V: Visitor; +} + +impl Visitable for &mut T +where + T: Visitable, +{ + fn visit(&mut self, v: &mut V) -> Result<(), V::Error> + where + V: Visitor, + { + T::visit(self, v) + } +} + +impl Visitable for Option +where + T: Visitable, +{ + fn visit(&mut self, v: &mut V) -> Result<(), V::Error> + where + V: Visitor, + { + if let Some(inner) = self.as_mut() { + inner.visit(v) + } else { + Ok(()) + } + } +} + +impl Visitable for Box +where + T: Visitable, +{ + fn visit(&mut self, v: &mut V) -> Result<(), V::Error> + where + V: Visitor, + { + T::visit(self, v) + } +} + +impl Visitable for Vec +where + T: Visitable, +{ + fn visit(&mut self, v: &mut V) -> Result<(), V::Error> + where + V: Visitor, + { + for item in self.iter_mut() { + item.visit(v)?; + } + Ok(()) + } +} + +impl Visitable for SourceUnitPart { + fn visit(&mut self, v: &mut V) -> Result<(), V::Error> + where + V: Visitor, + { + match self { + SourceUnitPart::ContractDefinition(contract) => v.visit_contract(contract), + SourceUnitPart::PragmaDirective(loc, ident, str) => v.visit_pragma(*loc, ident, str), + SourceUnitPart::ImportDirective(import) => import.visit(v), + SourceUnitPart::EnumDefinition(enumeration) => v.visit_enum(enumeration), + SourceUnitPart::StructDefinition(structure) => v.visit_struct(structure), + SourceUnitPart::EventDefinition(event) => v.visit_event(event), + SourceUnitPart::ErrorDefinition(error) => v.visit_error(error), + SourceUnitPart::FunctionDefinition(function) => v.visit_function(function), + SourceUnitPart::VariableDefinition(variable) => v.visit_var_definition(variable), + SourceUnitPart::TypeDefinition(def) => v.visit_type_definition(def), + SourceUnitPart::StraySemicolon(_) => v.visit_stray_semicolon(), + SourceUnitPart::Using(using) => v.visit_using(using), + SourceUnitPart::Annotation(annotation) => v.visit_annotation(annotation), + } + } +} + +impl Visitable for Import { + fn visit(&mut self, v: &mut V) -> Result<(), V::Error> + where + V: Visitor, + { + match self { + Import::Plain(import, loc) => v.visit_import_plain(*loc, import), + Import::GlobalSymbol(global, import_as, loc) => { + v.visit_import_global(*loc, global, import_as) + } + Import::Rename(from, imports, loc) => v.visit_import_renames(*loc, imports, from), + } + } +} + +impl Visitable for ContractPart { + fn visit(&mut self, v: &mut V) -> Result<(), V::Error> + where + V: Visitor, + { + match self { + ContractPart::StructDefinition(structure) => v.visit_struct(structure), + ContractPart::EventDefinition(event) => v.visit_event(event), + ContractPart::ErrorDefinition(error) => v.visit_error(error), + ContractPart::EnumDefinition(enumeration) => v.visit_enum(enumeration), + ContractPart::VariableDefinition(variable) => v.visit_var_definition(variable), + ContractPart::FunctionDefinition(function) => v.visit_function(function), + ContractPart::TypeDefinition(def) => v.visit_type_definition(def), + ContractPart::StraySemicolon(_) => v.visit_stray_semicolon(), + ContractPart::Using(using) => v.visit_using(using), + ContractPart::Annotation(annotation) => v.visit_annotation(annotation), + } + } +} + +impl Visitable for Statement { + fn visit(&mut self, v: &mut V) -> Result<(), V::Error> + where + V: Visitor, + { + match self { + Statement::Block { + loc, + unchecked, + statements, + } => v.visit_block(*loc, *unchecked, statements), + Statement::Assembly { + loc, + dialect, + block, + flags, + } => v.visit_assembly(*loc, dialect, block, flags), + Statement::Args(loc, args) => v.visit_args(*loc, args), + Statement::If(loc, cond, if_branch, else_branch) => { + v.visit_if(*loc, cond, if_branch, else_branch, true) + } + Statement::While(loc, cond, body) => v.visit_while(*loc, cond, body), + Statement::Expression(loc, expr) => { + v.visit_expr(*loc, expr)?; + v.visit_stray_semicolon() + } + Statement::VariableDefinition(loc, declaration, expr) => { + v.visit_var_definition_stmt(*loc, declaration, expr) + } + Statement::For(loc, init, cond, update, body) => { + v.visit_for(*loc, init, cond, update, body) + } + Statement::DoWhile(loc, body, cond) => v.visit_do_while(*loc, body, cond), + Statement::Continue(loc) => v.visit_continue(*loc, true), + Statement::Break(loc) => v.visit_break(*loc, true), + Statement::Return(loc, expr) => v.visit_return(*loc, expr), + Statement::Revert(loc, error, args) => v.visit_revert(*loc, error, args), + Statement::RevertNamedArgs(loc, error, args) => { + v.visit_revert_named_args(*loc, error, args) + } + Statement::Emit(loc, event) => v.visit_emit(*loc, event), + Statement::Try(loc, expr, returns, clauses) => { + v.visit_try(*loc, expr, returns, clauses) + } + Statement::Error(loc) => v.visit_parser_error(*loc), + } + } +} + +impl Visitable for Loc { + fn visit(&mut self, v: &mut V) -> Result<(), V::Error> + where + V: Visitor, + { + v.visit_source(*self) + } +} + +impl Visitable for Expression { + fn visit(&mut self, v: &mut V) -> Result<(), V::Error> + where + V: Visitor, + { + v.visit_expr(self.loc(), self) + } +} + +impl Visitable for Identifier { + fn visit(&mut self, v: &mut V) -> Result<(), V::Error> + where + V: Visitor, + { + v.visit_ident(self.loc, self) + } +} + +impl Visitable for VariableDeclaration { + fn visit(&mut self, v: &mut V) -> Result<(), V::Error> + where + V: Visitor, + { + v.visit_var_declaration(self) + } +} + +impl Visitable for YulBlock { + fn visit(&mut self, v: &mut V) -> Result<(), V::Error> + where + V: Visitor, + { + v.visit_yul_block(self.loc, self.statements.as_mut(), false) + } +} + +impl Visitable for YulStatement { + fn visit(&mut self, v: &mut V) -> Result<(), V::Error> + where + V: Visitor, + { + match self { + YulStatement::Assign(loc, exprs, expr) => { + v.visit_yul_assignment(*loc, exprs, &mut Some(expr)) + } + YulStatement::Block(block) => { + v.visit_yul_block(block.loc, block.statements.as_mut(), false) + } + YulStatement::Break(loc) => v.visit_break(*loc, false), + YulStatement::Continue(loc) => v.visit_continue(*loc, false), + YulStatement::For(stmt) => v.visit_yul_for(stmt), + YulStatement::FunctionCall(stmt) => v.visit_yul_function_call(stmt), + YulStatement::FunctionDefinition(stmt) => v.visit_yul_fun_def(stmt), + YulStatement::If(loc, expr, block) => v.visit_yul_if(*loc, expr, block), + YulStatement::Leave(loc) => v.visit_yul_leave(*loc), + YulStatement::Switch(stmt) => v.visit_yul_switch(stmt), + YulStatement::VariableDeclaration(loc, idents, expr) => { + v.visit_yul_var_declaration(*loc, idents, expr) + } + YulStatement::Error(loc) => v.visit_parser_error(*loc), + } + } +} + +macro_rules! impl_visitable { + ($type:ty, $func:ident) => { + impl Visitable for $type { + fn visit(&mut self, v: &mut V) -> Result<(), V::Error> + where + V: Visitor, + { + v.$func(self) + } + } + }; +} + +impl_visitable!(SourceUnit, visit_source_unit); +impl_visitable!(FunctionAttribute, visit_function_attribute); +impl_visitable!(VariableAttribute, visit_var_attribute); +impl_visitable!(Parameter, visit_parameter); +impl_visitable!(Base, visit_base); +impl_visitable!(EventParameter, visit_event_parameter); +impl_visitable!(ErrorParameter, visit_error_parameter); +impl_visitable!(IdentifierPath, visit_ident_path); +impl_visitable!(YulExpression, visit_yul_expr); +impl_visitable!(YulTypedIdentifier, visit_yul_typed_ident); diff --git a/forge-fmt/testdata/Annotation/fmt.sol b/forge-fmt/testdata/Annotation/fmt.sol new file mode 100644 index 000000000..75bbcf2dd --- /dev/null +++ b/forge-fmt/testdata/Annotation/fmt.sol @@ -0,0 +1,15 @@ +// Support for Solana/Substrate annotations +contract A { + @selector([1, 2, 3, 4]) + function foo() public {} + + @selector("another one") + function bar() public {} + + @first("") + @second("") + function foobar() public {} +} + +@topselector(2) +contract B {} diff --git a/forge-fmt/testdata/Annotation/original.sol b/forge-fmt/testdata/Annotation/original.sol new file mode 100644 index 000000000..4551f7d1e --- /dev/null +++ b/forge-fmt/testdata/Annotation/original.sol @@ -0,0 +1,15 @@ +// Support for Solana/Substrate annotations +contract A { + @selector([1,2,3,4]) + function foo() public {} + + @selector("another one") + function bar() public {} + + @first("") + @second("") + function foobar() public {} +} + +@topselector(2) +contract B {} diff --git a/forge-fmt/testdata/ArrayExpressions/fmt.sol b/forge-fmt/testdata/ArrayExpressions/fmt.sol new file mode 100644 index 000000000..0476da9e1 --- /dev/null +++ b/forge-fmt/testdata/ArrayExpressions/fmt.sol @@ -0,0 +1,69 @@ +contract ArrayExpressions { + function test() external { + /* ARRAY SUBSCRIPT */ + uint256[10] memory sample; + + uint256 length = 10; + uint256[] memory sample2 = new uint[](length); + + uint256[] /* comment1 */ memory /* comment2 */ sample3; // comment3 + + /* ARRAY SLICE */ + msg.data[4:]; + msg.data[:msg.data.length]; + msg.data[4:msg.data.length]; + + msg.data[ + // comment1 + 4: + ]; + msg.data[ + : /* comment2 */ msg.data.length // comment3 + ]; + msg.data[ + // comment4 + 4: // comment5 + msg.data.length /* comment6 */ + ]; + + uint256 + someVeryVeryVeryLongVariableNameThatDenotesTheStartOfTheMessageDataSlice = + 4; + uint256 + someVeryVeryVeryLongVariableNameThatDenotesTheEndOfTheMessageDataSlice = + msg.data.length; + msg.data[ + someVeryVeryVeryLongVariableNameThatDenotesTheStartOfTheMessageDataSlice: + ]; + msg.data[ + :someVeryVeryVeryLongVariableNameThatDenotesTheEndOfTheMessageDataSlice + ]; + msg.data[ + someVeryVeryVeryLongVariableNameThatDenotesTheStartOfTheMessageDataSlice: + someVeryVeryVeryLongVariableNameThatDenotesTheEndOfTheMessageDataSlice + ]; + + /* ARRAY LITERAL */ + [1, 2, 3]; + + uint256 someVeryVeryLongVariableName = 0; + [ + someVeryVeryLongVariableName, + someVeryVeryLongVariableName, + someVeryVeryLongVariableName + ]; + uint256[3] memory literal = [ + someVeryVeryLongVariableName, + someVeryVeryLongVariableName, + someVeryVeryLongVariableName + ]; + + uint8[3] memory literal2 = /* comment7 */ [ // comment8 + 1, + 2, /* comment9 */ + 3 // comment10 + ]; + uint256[1] memory literal3 = + [ /* comment11 */ someVeryVeryLongVariableName /* comment13 */ ]; + } +} diff --git a/forge-fmt/testdata/ArrayExpressions/original.sol b/forge-fmt/testdata/ArrayExpressions/original.sol new file mode 100644 index 000000000..7f26bd5f7 --- /dev/null +++ b/forge-fmt/testdata/ArrayExpressions/original.sol @@ -0,0 +1,46 @@ +contract ArrayExpressions { + function test() external { + /* ARRAY SUBSCRIPT */ + uint[10] memory sample; + + uint256 length = 10; + uint[] memory sample2 = new uint[](length); + + uint /* comment1 */ [] memory /* comment2 */ sample3 // comment3 + ; + + /* ARRAY SLICE */ + msg.data[4:]; + msg.data[:msg.data.length]; + msg.data[4:msg.data.length]; + + msg.data[ + // comment1 + 4:]; + msg.data[ + : /* comment2 */ msg.data.length // comment3 + ]; + msg.data[ + // comment4 + 4 // comment5 + :msg.data.length /* comment6 */]; + + uint256 someVeryVeryVeryLongVariableNameThatDenotesTheStartOfTheMessageDataSlice = 4; + uint256 someVeryVeryVeryLongVariableNameThatDenotesTheEndOfTheMessageDataSlice = msg.data.length; + msg.data[someVeryVeryVeryLongVariableNameThatDenotesTheStartOfTheMessageDataSlice:]; + msg.data[:someVeryVeryVeryLongVariableNameThatDenotesTheEndOfTheMessageDataSlice]; + msg.data[someVeryVeryVeryLongVariableNameThatDenotesTheStartOfTheMessageDataSlice:someVeryVeryVeryLongVariableNameThatDenotesTheEndOfTheMessageDataSlice]; + + /* ARRAY LITERAL */ + [1, 2, 3]; + + uint256 someVeryVeryLongVariableName = 0; + [someVeryVeryLongVariableName, someVeryVeryLongVariableName, someVeryVeryLongVariableName]; + uint256[3] memory literal = [someVeryVeryLongVariableName,someVeryVeryLongVariableName,someVeryVeryLongVariableName]; + + uint8[3] memory literal2 = /* comment7 */ [ // comment8 + 1, 2, /* comment9 */ 3 // comment10 + ]; + uint256[1] memory literal3 = [ /* comment11 */ someVeryVeryLongVariableName /* comment13 */]; + } +} \ No newline at end of file diff --git a/forge-fmt/testdata/ConditionalOperatorExpression/fmt.sol b/forge-fmt/testdata/ConditionalOperatorExpression/fmt.sol new file mode 100644 index 000000000..733ae2d8c --- /dev/null +++ b/forge-fmt/testdata/ConditionalOperatorExpression/fmt.sol @@ -0,0 +1,37 @@ +contract TernaryExpression { + function test() external { + bool condition; + bool someVeryVeryLongConditionUsedInTheTernaryExpression; + + condition ? 0 : 1; + + someVeryVeryLongConditionUsedInTheTernaryExpression + ? 1234567890 + : 987654321; + + condition /* comment1 */ /* comment2 */ + ? 1001 /* comment3 */ /* comment4 */ + : 2002; + + // comment5 + someVeryVeryLongConditionUsedInTheTernaryExpression + ? 1 + // comment6 + // comment7 + : 0; // comment8 + + uint256 amount = msg.value > 0 + ? msg.value + : parseAmount(IERC20(asset).balanceOf(msg.sender), msg.data); + + uint256 amount = msg.value > 0 + ? msg.value + // comment9 + : parseAmount(IERC20(asset).balanceOf(msg.sender), msg.data); + + uint256 amount = msg.value > 0 + // comment10 + ? msg.value + : parseAmount(IERC20(asset).balanceOf(msg.sender), msg.data); + } +} diff --git a/forge-fmt/testdata/ConditionalOperatorExpression/original.sol b/forge-fmt/testdata/ConditionalOperatorExpression/original.sol new file mode 100644 index 000000000..f03328873 --- /dev/null +++ b/forge-fmt/testdata/ConditionalOperatorExpression/original.sol @@ -0,0 +1,33 @@ +contract TernaryExpression { + function test() external { + bool condition; + bool someVeryVeryLongConditionUsedInTheTernaryExpression; + + condition ? 0 : 1; + + someVeryVeryLongConditionUsedInTheTernaryExpression ? 1234567890 : 987654321; + + condition /* comment1 */ ? /* comment2 */ 1001 /* comment3 */ : /* comment4 */ 2002; + + // comment5 + someVeryVeryLongConditionUsedInTheTernaryExpression ? 1 + // comment6 + : + // comment7 + 0; // comment8 + + uint256 amount = msg.value > 0 + ? msg.value + : parseAmount(IERC20(asset).balanceOf(msg.sender), msg.data); + + uint256 amount = msg.value > 0 + ? msg.value + // comment9 + : parseAmount(IERC20(asset).balanceOf(msg.sender), msg.data); + + uint amount = msg.value > 0 + // comment10 + ? msg.value + : parseAmount(IERC20(asset).balanceOf(msg.sender), msg.data); + } +} \ No newline at end of file diff --git a/forge-fmt/testdata/ConstructorDefinition/fmt.sol b/forge-fmt/testdata/ConstructorDefinition/fmt.sol new file mode 100644 index 000000000..dd62a0cf4 --- /dev/null +++ b/forge-fmt/testdata/ConstructorDefinition/fmt.sol @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.5.2; + +// comment block starts here +// comment block continues +// + +// comment block 2 starts here +// comment block 2 continues + +contract Constructors is Ownable, Changeable { + function Constructors(variable1) + public + Changeable(variable1) + Ownable() + onlyOwner + {} + + constructor( + variable1, + variable2, + variable3, + variable4, + variable5, + variable6, + variable7 + ) + public + Changeable( + variable1, + variable2, + variable3, + variable4, + variable5, + variable6, + variable7 + ) + Ownable() + onlyOwner + {} +} diff --git a/forge-fmt/testdata/ConstructorDefinition/original.sol b/forge-fmt/testdata/ConstructorDefinition/original.sol new file mode 100644 index 000000000..f69205196 --- /dev/null +++ b/forge-fmt/testdata/ConstructorDefinition/original.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.5.2; + +// comment block starts here +// comment block continues +// + +// comment block 2 starts here +// comment block 2 continues + +contract Constructors is Ownable, Changeable { + function Constructors(variable1) public Changeable(variable1) Ownable() onlyOwner { + } + + constructor(variable1, variable2, variable3, variable4, variable5, variable6, variable7) public Changeable(variable1, variable2, variable3, variable4, variable5, variable6, variable7) Ownable() onlyOwner {} +} diff --git a/forge-fmt/testdata/ContractDefinition/bracket-spacing.fmt.sol b/forge-fmt/testdata/ContractDefinition/bracket-spacing.fmt.sol new file mode 100644 index 000000000..dca4e325d --- /dev/null +++ b/forge-fmt/testdata/ContractDefinition/bracket-spacing.fmt.sol @@ -0,0 +1,37 @@ +// config: line_length = 160 +// config: bracket_spacing = true +contract ContractDefinition is Contract1, Contract2, Contract3, Contract4, Contract5 { } + +// comment 7 +contract SampleContract { + // spaced comment 1 + + // spaced comment 2 + // that spans multiple lines + + // comment 8 + constructor() { /* comment 9 */ } // comment 10 + + // comment 11 + function max( /* comment 13 */ uint256 arg1, uint256 /* comment 14 */ arg2, uint256 /* comment 15 */ ) + // comment 16 + external /* comment 17 */ + pure + returns (uint256) + // comment 18 + { + // comment 19 + return arg1 > arg2 ? arg1 : arg2; + } +} + +// comment 20 +contract /* comment 21 */ ExampleContract is /* comment 22 */ SampleContract { } + +contract ERC20DecimalsMock is ERC20 { + uint8 private immutable _decimals; + + constructor(string memory name_, string memory symbol_, uint8 decimals_) ERC20(name_, symbol_) { + _decimals = decimals_; + } +} diff --git a/forge-fmt/testdata/ContractDefinition/contract-new-lines.fmt.sol b/forge-fmt/testdata/ContractDefinition/contract-new-lines.fmt.sol new file mode 100644 index 000000000..2e9661f95 --- /dev/null +++ b/forge-fmt/testdata/ContractDefinition/contract-new-lines.fmt.sol @@ -0,0 +1,52 @@ +// config: contract_new_lines = true +contract ContractDefinition is + Contract1, + Contract2, + Contract3, + Contract4, + Contract5 +{} + +// comment 7 +contract SampleContract { + + // spaced comment 1 + + // spaced comment 2 + // that spans multiple lines + + // comment 8 + constructor() { /* comment 9 */ } // comment 10 + + // comment 11 + function max( /* comment 13 */ + uint256 arg1, + uint256 /* comment 14 */ arg2, + uint256 /* comment 15 */ + ) + // comment 16 + external /* comment 17 */ + pure + returns (uint256) + // comment 18 + { + // comment 19 + return arg1 > arg2 ? arg1 : arg2; + } + +} + +// comment 20 +contract /* comment 21 */ ExampleContract is /* comment 22 */ SampleContract {} + +contract ERC20DecimalsMock is ERC20 { + + uint8 private immutable _decimals; + + constructor(string memory name_, string memory symbol_, uint8 decimals_) + ERC20(name_, symbol_) + { + _decimals = decimals_; + } + +} diff --git a/forge-fmt/testdata/ContractDefinition/fmt.sol b/forge-fmt/testdata/ContractDefinition/fmt.sol new file mode 100644 index 000000000..551e84dec --- /dev/null +++ b/forge-fmt/testdata/ContractDefinition/fmt.sol @@ -0,0 +1,47 @@ +contract ContractDefinition is + Contract1, + Contract2, + Contract3, + Contract4, + Contract5 +{} + +// comment 7 +contract SampleContract { + // spaced comment 1 + + // spaced comment 2 + // that spans multiple lines + + // comment 8 + constructor() { /* comment 9 */ } // comment 10 + + // comment 11 + function max( /* comment 13 */ + uint256 arg1, + uint256 /* comment 14 */ arg2, + uint256 /* comment 15 */ + ) + // comment 16 + external /* comment 17 */ + pure + returns (uint256) + // comment 18 + { + // comment 19 + return arg1 > arg2 ? arg1 : arg2; + } +} + +// comment 20 +contract /* comment 21 */ ExampleContract is /* comment 22 */ SampleContract {} + +contract ERC20DecimalsMock is ERC20 { + uint8 private immutable _decimals; + + constructor(string memory name_, string memory symbol_, uint8 decimals_) + ERC20(name_, symbol_) + { + _decimals = decimals_; + } +} diff --git a/forge-fmt/testdata/ContractDefinition/original.sol b/forge-fmt/testdata/ContractDefinition/original.sol new file mode 100644 index 000000000..4c671985b --- /dev/null +++ b/forge-fmt/testdata/ContractDefinition/original.sol @@ -0,0 +1,40 @@ +contract ContractDefinition is Contract1, Contract2, Contract3, Contract4, Contract5 { +} + +// comment 7 +contract SampleContract { + + // spaced comment 1 + + // spaced comment 2 + // that spans multiple lines + + // comment 8 + constructor() { /* comment 9 */ } // comment 10 + + // comment 11 + function max(/* comment 13 */ uint256 arg1, uint256 /* comment 14 */ arg2, uint256 /* comment 15 */) + // comment 16 + external /* comment 17 */ + pure + returns(uint256) + // comment 18 + { // comment 19 + return arg1 > arg2 ? arg1 : arg2; + } +} + +// comment 20 +contract /* comment 21 */ ExampleContract /* comment 22 */ is SampleContract {} + +contract ERC20DecimalsMock is ERC20 { + uint8 private immutable _decimals; + + constructor( + string memory name_, + string memory symbol_, + uint8 decimals_ + ) ERC20(name_, symbol_) { + _decimals = decimals_; + } +} diff --git a/forge-fmt/testdata/DoWhileStatement/fmt.sol b/forge-fmt/testdata/DoWhileStatement/fmt.sol new file mode 100644 index 000000000..c3c8c71c5 --- /dev/null +++ b/forge-fmt/testdata/DoWhileStatement/fmt.sol @@ -0,0 +1,30 @@ +pragma solidity ^0.8.8; + +contract DoWhileStatement { + function test() external { + uint256 i; + do { + "test"; + } while (i != 0); + + do {} while (i != 0); + + bool someVeryVeryLongCondition; + do { + "test"; + } while ( + someVeryVeryLongCondition && !someVeryVeryLongCondition + && !someVeryVeryLongCondition && someVeryVeryLongCondition + ); + + do { + i++; + } while (i < 10); + + do { + do { + i++; + } while (i < 30); + } while (i < 20); + } +} diff --git a/forge-fmt/testdata/DoWhileStatement/original.sol b/forge-fmt/testdata/DoWhileStatement/original.sol new file mode 100644 index 000000000..51063c878 --- /dev/null +++ b/forge-fmt/testdata/DoWhileStatement/original.sol @@ -0,0 +1,24 @@ +pragma solidity ^0.8.8; + + contract DoWhileStatement { + function test() external { + uint256 i; + do { "test"; } while (i != 0); + + do + {} + while + ( + i != 0); + + bool someVeryVeryLongCondition; + do { "test"; } while( + someVeryVeryLongCondition && !someVeryVeryLongCondition && +!someVeryVeryLongCondition && + someVeryVeryLongCondition); + + do i++; while(i < 10); + + do do i++; while (i < 30); while(i < 20); + } +} \ No newline at end of file diff --git a/forge-fmt/testdata/DocComments/fmt.sol b/forge-fmt/testdata/DocComments/fmt.sol new file mode 100644 index 000000000..a002e6c23 --- /dev/null +++ b/forge-fmt/testdata/DocComments/fmt.sol @@ -0,0 +1,100 @@ +pragma solidity ^0.8.13; + +/// @title A Hello world example +contract HelloWorld { + /// Some example struct + struct Person { + uint256 age; + address wallet; + } + + /** + * Here's a more double asterix comment + */ + Person public theDude; + + /// Constructs the dude + /// @param age The dude's age + constructor(uint256 age) { + theDude = Person({age: age, wallet: msg.sender}); + } + + /** + * @dev does nothing + */ + function example() public { + /** + * Does this add a whitespace error? + * + * Let's find out. + */ + } + + /** + * @dev Calculates a rectangle's surface and perimeter. + * @param w Width of the rectangle. + * @param h Height of the rectangle. + * @return s The calculated surface. + * @return p The calculated perimeter. + */ + function rectangle(uint256 w, uint256 h) + public + pure + returns (uint256 s, uint256 p) + { + s = w * h; + p = 2 * (w + h); + } + + /// A long doc line comment that will be wrapped + function docLineOverflow() external {} + + function docLinePostfixOveflow() external {} + + /// A long doc line comment that will be wrapped + + /** + * @notice Here is my comment + * - item 1 + * - item 2 + * Some equations: + * y = mx + b + */ + function anotherExample() external {} + + /** + * contract A { + * function foo() public { + * // does nothing. + * } + * } + */ + function multilineIndent() external {} + + /** + * contract A { + * function foo() public { + * // does nothing. + * } + * } + */ + function multilineMalformedIndent() external {} + + /** + * contract A { + * function withALongNameThatWillCauseCommentWrap() public { + * // does nothing. + * } + * } + */ + function malformedIndentOveflow() external {} +} + +/** + * contract A { + * function foo() public { + * // does nothing. + * } + * } + */ +function freeFloatingMultilineIndent() {} diff --git a/forge-fmt/testdata/DocComments/original.sol b/forge-fmt/testdata/DocComments/original.sol new file mode 100644 index 000000000..dff602236 --- /dev/null +++ b/forge-fmt/testdata/DocComments/original.sol @@ -0,0 +1,95 @@ +pragma solidity ^0.8.13; + +/// @title A Hello world example +contract HelloWorld { + + /// Some example struct + struct Person { + uint age; + address wallet; + } + + /** + Here's a more double asterix comment + */ + Person public theDude; + + /// Constructs the dude + /// @param age The dude's age + constructor(uint256 age) { + theDude = Person({ + age: age, + wallet: msg.sender + }); + } + + /** @dev does nothing */ + function example() public { + /** + * Does this add a whitespace error? + * + * Let's find out. + */ + } + + /** @dev Calculates a rectangle's surface and perimeter. + * @param w Width of the rectangle. + * @param h Height of the rectangle. + * @return s The calculated surface. +* @return p The calculated perimeter. + */ + function rectangle(uint256 w, uint256 h) public pure returns (uint256 s, uint256 p) { + s = w * h; + p = 2 * (w + h); + } + + /// A long doc line comment that will be wrapped + function docLineOverflow() external {} + + function docLinePostfixOveflow() external {} /// A long doc line comment that will be wrapped + + /** + * @notice Here is my comment + * - item 1 + * - item 2 + * Some equations: + * y = mx + b + */ + function anotherExample() external {} + + /** + contract A { + function foo() public { + // does nothing. + } + } + */ + function multilineIndent() external {} + + /** + contract A { +function foo() public { + // does nothing. + } + } + */ + function multilineMalformedIndent() external {} + + /** + contract A { +function withALongNameThatWillCauseCommentWrap() public { + // does nothing. + } + } + */ + function malformedIndentOveflow() external {} +} + +/** +contract A { + function foo() public { + // does nothing. + } +} +*/ +function freeFloatingMultilineIndent() {} diff --git a/forge-fmt/testdata/DocComments/wrap-comments.fmt.sol b/forge-fmt/testdata/DocComments/wrap-comments.fmt.sol new file mode 100644 index 000000000..6d7f3c584 --- /dev/null +++ b/forge-fmt/testdata/DocComments/wrap-comments.fmt.sol @@ -0,0 +1,128 @@ +// config: line_length = 40 +// config: wrap_comments = true +pragma solidity ^0.8.13; + +/// @title A Hello world example +contract HelloWorld { + /// Some example struct + struct Person { + uint256 age; + address wallet; + } + + /** + * Here's a more double asterix + * comment + */ + Person public theDude; + + /// Constructs the dude + /// @param age The dude's age + constructor(uint256 age) { + theDude = Person({ + age: age, + wallet: msg.sender + }); + } + + /** + * @dev does nothing + */ + function example() public { + /** + * Does this add a whitespace + * error? + * + * Let's find out. + */ + } + + /** + * @dev Calculates a rectangle's + * surface and perimeter. + * @param w Width of the rectangle. + * @param h Height of the rectangle. + * @return s The calculated surface. + * @return p The calculated + * perimeter. + */ + function rectangle( + uint256 w, + uint256 h + ) + public + pure + returns (uint256 s, uint256 p) + { + s = w * h; + p = 2 * (w + h); + } + + /// A long doc line comment that + /// will be wrapped + function docLineOverflow() + external + {} + + function docLinePostfixOveflow() + external + {} + + /// A long doc line comment that + /// will be wrapped + + /** + * @notice Here is my comment + * - item 1 + * - item 2 + * Some equations: + * y = mx + b + */ + function anotherExample() + external + {} + + /** + * contract A { + * function foo() public { + * // does nothing. + * } + * } + */ + function multilineIndent() + external + {} + + /** + * contract A { + * function foo() public { + * // does nothing. + * } + * } + */ + function multilineMalformedIndent() + external + {} + + /** + * contract A { + * function + * withALongNameThatWillCauseCommentWrap() + * public { + * // does nothing. + * } + * } + */ + function malformedIndentOveflow() + external + {} +} + +/** + * contract A { + * function foo() public { + * // does nothing. + * } + * } + */ +function freeFloatingMultilineIndent() {} diff --git a/forge-fmt/testdata/EmitStatement/fmt.sol b/forge-fmt/testdata/EmitStatement/fmt.sol new file mode 100644 index 000000000..0fac66b9b --- /dev/null +++ b/forge-fmt/testdata/EmitStatement/fmt.sol @@ -0,0 +1,31 @@ +// config: line_length = 80 +event NewEvent( + address beneficiary, uint256 index, uint64 timestamp, uint64 endTimestamp +); + +function emitEvent() { + emit NewEvent( + beneficiary, + _vestingBeneficiaries.length - 1, + uint64(block.timestamp), + endTimestamp + ); + + emit NewEvent( + /* beneficiary */ + beneficiary, + /* index */ + _vestingBeneficiaries.length - 1, + /* timestamp */ + uint64(block.timestamp), + /* end timestamp */ + endTimestamp + ); + + emit NewEvent( + beneficiary, // beneficiary + _vestingBeneficiaries.length - 1, // index + uint64(block.timestamp), // timestamp + endTimestamp // end timestamp + ); +} diff --git a/forge-fmt/testdata/EmitStatement/original.sol b/forge-fmt/testdata/EmitStatement/original.sol new file mode 100644 index 000000000..661abb782 --- /dev/null +++ b/forge-fmt/testdata/EmitStatement/original.sol @@ -0,0 +1,24 @@ +event NewEvent(address beneficiary, uint256 index, uint64 timestamp, uint64 endTimestamp); + +function emitEvent() { + emit NewEvent( + beneficiary, + _vestingBeneficiaries.length - 1, + uint64(block.timestamp), + endTimestamp + ); + + emit + NewEvent( + /* beneficiary */ beneficiary, + /* index */ _vestingBeneficiaries.length - 1, + /* timestamp */ uint64(block.timestamp), + /* end timestamp */ endTimestamp); + + emit NewEvent( + beneficiary, // beneficiary + _vestingBeneficiaries.length - 1, // index + uint64(block.timestamp), // timestamp + endTimestamp // end timestamp + ); +} diff --git a/forge-fmt/testdata/EnumDefinition/bracket-spacing.fmt.sol b/forge-fmt/testdata/EnumDefinition/bracket-spacing.fmt.sol new file mode 100644 index 000000000..a4ae0f019 --- /dev/null +++ b/forge-fmt/testdata/EnumDefinition/bracket-spacing.fmt.sol @@ -0,0 +1,21 @@ +// config: bracket_spacing = true +contract EnumDefinitions { + enum Empty { } + enum ActionChoices { + GoLeft, + GoRight, + GoStraight, + SitStill + } + enum States { + State1, + State2, + State3, + State4, + State5, + State6, + State7, + State8, + State9 + } +} diff --git a/forge-fmt/testdata/EnumDefinition/fmt.sol b/forge-fmt/testdata/EnumDefinition/fmt.sol new file mode 100644 index 000000000..437268aff --- /dev/null +++ b/forge-fmt/testdata/EnumDefinition/fmt.sol @@ -0,0 +1,20 @@ +contract EnumDefinitions { + enum Empty {} + enum ActionChoices { + GoLeft, + GoRight, + GoStraight, + SitStill + } + enum States { + State1, + State2, + State3, + State4, + State5, + State6, + State7, + State8, + State9 + } +} diff --git a/forge-fmt/testdata/EnumDefinition/original.sol b/forge-fmt/testdata/EnumDefinition/original.sol new file mode 100644 index 000000000..69aadf884 --- /dev/null +++ b/forge-fmt/testdata/EnumDefinition/original.sol @@ -0,0 +1,7 @@ +contract EnumDefinitions { + enum Empty { + + } + enum ActionChoices { GoLeft, GoRight, GoStraight, SitStill } + enum States { State1, State2, State3, State4, State5, State6, State7, State8, State9 } +} \ No newline at end of file diff --git a/forge-fmt/testdata/ErrorDefinition/fmt.sol b/forge-fmt/testdata/ErrorDefinition/fmt.sol new file mode 100644 index 000000000..b94bbe45d --- /dev/null +++ b/forge-fmt/testdata/ErrorDefinition/fmt.sol @@ -0,0 +1,14 @@ +pragma solidity ^0.8.4; + +error TopLevelCustomError(); +error TopLevelCustomErrorWithArg(uint256 x); +error TopLevelCustomErrorArgWithoutName(string); +error Error1( + uint256, uint256, uint256, uint256, uint256, uint256, uint256, uint256 +); + +contract Errors { + error ContractCustomError(); + error ContractCustomErrorWithArg(uint256 x); + error ContractCustomErrorArgWithoutName(string); +} diff --git a/forge-fmt/testdata/ErrorDefinition/original.sol b/forge-fmt/testdata/ErrorDefinition/original.sol new file mode 100644 index 000000000..f9524c22a --- /dev/null +++ b/forge-fmt/testdata/ErrorDefinition/original.sol @@ -0,0 +1,14 @@ +pragma solidity ^0.8.4; + +error + TopLevelCustomError(); + error TopLevelCustomErrorWithArg(uint x) ; +error TopLevelCustomErrorArgWithoutName (string); +error Error1(uint256, uint256, uint256, uint256, uint256, uint256, uint256, uint256); + +contract Errors { + error + ContractCustomError(); + error ContractCustomErrorWithArg(uint x) ; + error ContractCustomErrorArgWithoutName (string); +} \ No newline at end of file diff --git a/forge-fmt/testdata/EventDefinition/fmt.sol b/forge-fmt/testdata/EventDefinition/fmt.sol new file mode 100644 index 000000000..11d3d8256 --- /dev/null +++ b/forge-fmt/testdata/EventDefinition/fmt.sol @@ -0,0 +1,144 @@ +pragma solidity ^0.5.2; + +contract Events { + event Event1(); + event Event1() anonymous; + + event Event1(uint256); + event Event1(uint256) anonymous; + + event Event1(uint256 a); + event Event1(uint256 a) anonymous; + + event Event1(uint256 indexed); + event Event1(uint256 indexed) anonymous; + + event Event1(uint256 indexed a); + event Event1(uint256 indexed a) anonymous; + + event Event1(uint256, uint256, uint256, uint256, uint256, uint256, uint256); + event Event1( + uint256, uint256, uint256, uint256, uint256, uint256, uint256 + ) anonymous; + + event Event1( + uint256, uint256, uint256, uint256, uint256, uint256, uint256, uint256 + ); + event Event1( + uint256, uint256, uint256, uint256, uint256, uint256, uint256, uint256 + ) anonymous; + + event Event1( + uint256, + uint256, + uint256, + uint256, + uint256, + uint256, + uint256, + uint256, + uint256, + uint256, + uint256, + uint256, + uint256, + uint256, + uint256 + ); + event Event1( + uint256, + uint256, + uint256, + uint256, + uint256, + uint256, + uint256, + uint256, + uint256, + uint256, + uint256, + uint256, + uint256, + uint256, + uint256 + ) anonymous; + + event Event1( + uint256 a, + uint256 a, + uint256 a, + uint256 a, + uint256 a, + uint256 a, + uint256 a, + uint256 a, + uint256 a, + uint256 a, + uint256 a, + uint256 a, + uint256 a + ); + event Event1( + uint256 a, + uint256 a, + uint256 a, + uint256 a, + uint256 a, + uint256 a, + uint256 a, + uint256 a, + uint256 a, + uint256 a, + uint256 a, + uint256 a, + uint256 a + ) anonymous; + + event Event1( + uint256 indexed, + uint256 indexed, + uint256 indexed, + uint256 indexed, + uint256 indexed, + uint256 indexed, + uint256 indexed, + uint256 indexed, + uint256 indexed + ); + event Event1( + uint256 indexed, + uint256 indexed, + uint256 indexed, + uint256 indexed, + uint256 indexed, + uint256 indexed, + uint256 indexed, + uint256 indexed, + uint256 indexed + ) anonymous; + + event Event1( + uint256 indexed a, + uint256 indexed a, + uint256 indexed a, + uint256 indexed a, + uint256 indexed a, + uint256 indexed a, + uint256 indexed a, + uint256 indexed a, + uint256 indexed a, + uint256 indexed a + ); + event Event1( + uint256 indexed a, + uint256 indexed a, + uint256 indexed a, + uint256 indexed a, + uint256 indexed a, + uint256 indexed a, + uint256 indexed a, + uint256 indexed a, + uint256 indexed a, + uint256 indexed a + ) anonymous; +} diff --git a/forge-fmt/testdata/EventDefinition/original.sol b/forge-fmt/testdata/EventDefinition/original.sol new file mode 100644 index 000000000..d2a615162 --- /dev/null +++ b/forge-fmt/testdata/EventDefinition/original.sol @@ -0,0 +1,36 @@ +pragma solidity ^0.5.2; + +contract Events { + event Event1(); + event Event1() anonymous; + + event Event1(uint256); + event Event1(uint256) anonymous; + + event Event1(uint256 a); + event Event1(uint256 a) anonymous; + + event Event1(uint256 indexed); + event Event1(uint256 indexed) anonymous; + + event Event1(uint256 indexed a); + event Event1(uint256 indexed a) anonymous; + + event Event1(uint256, uint256, uint256, uint256, uint256, uint256, uint256); + event Event1(uint256, uint256, uint256, uint256, uint256, uint256, uint256) anonymous; + + event Event1(uint256, uint256, uint256, uint256, uint256, uint256, uint256, uint256); + event Event1(uint256, uint256, uint256, uint256, uint256, uint256, uint256, uint256) anonymous; + + event Event1(uint256, uint256, uint256, uint256, uint256, uint256, uint256, uint256, uint256, uint256, uint256, uint256, uint256, uint256, uint256); + event Event1(uint256, uint256, uint256, uint256, uint256, uint256, uint256, uint256, uint256, uint256, uint256, uint256, uint256, uint256, uint256) anonymous; + + event Event1(uint256 a, uint256 a, uint256 a, uint256 a, uint256 a, uint256 a, uint256 a, uint256 a, uint256 a, uint256 a, uint256 a, uint256 a, uint256 a); + event Event1(uint256 a, uint256 a, uint256 a, uint256 a, uint256 a, uint256 a, uint256 a, uint256 a, uint256 a, uint256 a, uint256 a, uint256 a, uint256 a) anonymous; + + event Event1(uint256 indexed, uint256 indexed, uint256 indexed, uint256 indexed, uint256 indexed, uint256 indexed, uint256 indexed, uint256 indexed, uint256 indexed); + event Event1(uint256 indexed, uint256 indexed, uint256 indexed, uint256 indexed, uint256 indexed, uint256 indexed, uint256 indexed, uint256 indexed, uint256 indexed) anonymous; + + event Event1(uint256 indexed a, uint256 indexed a, uint256 indexed a, uint256 indexed a, uint256 indexed a, uint256 indexed a, uint256 indexed a, uint256 indexed a, uint256 indexed a, uint256 indexed a); + event Event1(uint256 indexed a, uint256 indexed a, uint256 indexed a, uint256 indexed a, uint256 indexed a, uint256 indexed a, uint256 indexed a, uint256 indexed a, uint256 indexed a, uint256 indexed a) anonymous; +} diff --git a/forge-fmt/testdata/ForStatement/fmt.sol b/forge-fmt/testdata/ForStatement/fmt.sol new file mode 100644 index 000000000..a1bb4b2e6 --- /dev/null +++ b/forge-fmt/testdata/ForStatement/fmt.sol @@ -0,0 +1,37 @@ +pragma solidity ^0.8.8; + +contract ForStatement { + function test() external { + for (uint256 i1; i1 < 10; i1++) { + i1++; + } + + uint256 i2; + for (++i2; i2 < 10; i2++) {} + + uint256 veryLongVariableName = 1000; + for ( + uint256 i3; + i3 < 10 && veryLongVariableName > 999 && veryLongVariableName < 1001; + i3++ + ) { + i3++; + } + + for (type(uint256).min;;) {} + + for (;;) { + "test"; + } + + for (uint256 i4; i4 < 10; i4++) { + i4++; + } + + for (uint256 i5;;) { + for (uint256 i6 = 10; i6 > i5; i6--) { + i5++; + } + } + } +} diff --git a/forge-fmt/testdata/ForStatement/original.sol b/forge-fmt/testdata/ForStatement/original.sol new file mode 100644 index 000000000..e98288dd1 --- /dev/null +++ b/forge-fmt/testdata/ForStatement/original.sol @@ -0,0 +1,33 @@ +pragma solidity ^0.8.8; + +contract ForStatement { + function test() external { + for + (uint256 i1 + ; i1 < 10; i1++) + { + i1++; + } + + uint256 i2; + for(++i2;i2<10;i2++) + + {} + + uint256 veryLongVariableName = 1000; + for ( uint256 i3; i3 < 10 + && veryLongVariableName>999 && veryLongVariableName< 1001 + ; i3++) + { i3 ++ ; } + + for (type(uint256).min;;) {} + + for (;;) { "test" ; } + + for (uint256 i4; i4< 10; i4++) i4++; + + for (uint256 i5; ;) + for (uint256 i6 = 10; i6 > i5; i6--) + i5++; + } +} \ No newline at end of file diff --git a/forge-fmt/testdata/FunctionCall/bracket-spacing.fmt.sol b/forge-fmt/testdata/FunctionCall/bracket-spacing.fmt.sol new file mode 100644 index 000000000..84a4ce8b1 --- /dev/null +++ b/forge-fmt/testdata/FunctionCall/bracket-spacing.fmt.sol @@ -0,0 +1,37 @@ +// config: line_length = 120 +// config: bracket_spacing = true +contract FunctionCall { + function foo() public pure { + bar(1111111111111111111111111111111111111111111111111111, 111111111111111111111111111111111111111111111111111); + bar(1111111111111111111111111111111111111111111111111112, 1111111111111111111111111111111111111111111111111112); + bar(1111111111111111111111111111111111111111111111111113, 11111111111111111111111111111111111111111111111111113); // the semicolon is not considered when determining line break + bar( + 1111111111111111111111111111111111111111111111111114, 111111111111111111111111111111111111111111111111111114 + ); + bar( + 111111111111111111111111111111111115, + 11111111111111111111111111111111115, + 11111111111111111111111111111111115 + ); + bar( + 111111111111111111111111111111111111111111111111111116, + 111111111111111111111111111111111111111111111111111116 + ); + bar( + 111111111111111111111111111111111111111111111111111117, + 1111111111111111111111111111111111111111111111111111117 + ); + } + + function bar(uint256, uint256) private pure { + return; + } +} + +function a(uint256 foo) { + foo; +} + +function b() { + a({ foo: 5 }); +} diff --git a/forge-fmt/testdata/FunctionCall/fmt.sol b/forge-fmt/testdata/FunctionCall/fmt.sol new file mode 100644 index 000000000..c57c5fda0 --- /dev/null +++ b/forge-fmt/testdata/FunctionCall/fmt.sol @@ -0,0 +1,36 @@ +// config: line_length = 120 +contract FunctionCall { + function foo() public pure { + bar(1111111111111111111111111111111111111111111111111111, 111111111111111111111111111111111111111111111111111); + bar(1111111111111111111111111111111111111111111111111112, 1111111111111111111111111111111111111111111111111112); + bar(1111111111111111111111111111111111111111111111111113, 11111111111111111111111111111111111111111111111111113); // the semicolon is not considered when determining line break + bar( + 1111111111111111111111111111111111111111111111111114, 111111111111111111111111111111111111111111111111111114 + ); + bar( + 111111111111111111111111111111111115, + 11111111111111111111111111111111115, + 11111111111111111111111111111111115 + ); + bar( + 111111111111111111111111111111111111111111111111111116, + 111111111111111111111111111111111111111111111111111116 + ); + bar( + 111111111111111111111111111111111111111111111111111117, + 1111111111111111111111111111111111111111111111111111117 + ); + } + + function bar(uint256, uint256) private pure { + return; + } +} + +function a(uint256 foo) { + foo; +} + +function b() { + a({foo: 5}); +} diff --git a/forge-fmt/testdata/FunctionCall/original.sol b/forge-fmt/testdata/FunctionCall/original.sol new file mode 100644 index 000000000..8bdc92e46 --- /dev/null +++ b/forge-fmt/testdata/FunctionCall/original.sol @@ -0,0 +1,29 @@ +contract FunctionCall { + function foo() public pure { + bar(1111111111111111111111111111111111111111111111111111, 111111111111111111111111111111111111111111111111111); + bar(1111111111111111111111111111111111111111111111111112, 1111111111111111111111111111111111111111111111111112); + bar(1111111111111111111111111111111111111111111111111113, 11111111111111111111111111111111111111111111111111113); // the semicolon is not considered when determining line break + bar(1111111111111111111111111111111111111111111111111114, 111111111111111111111111111111111111111111111111111114); + bar( + 111111111111111111111111111111111115, 11111111111111111111111111111111115, 11111111111111111111111111111111115 + ); + bar( + 111111111111111111111111111111111111111111111111111116, 111111111111111111111111111111111111111111111111111116 + ); + bar( + 111111111111111111111111111111111111111111111111111117, 1111111111111111111111111111111111111111111111111111117 + ); + } + + function bar(uint256, uint256) private pure { + return; + } +} + +function a(uint256 foo) { + foo; +} + +function b() { + a( {foo: 5} ); +} diff --git a/forge-fmt/testdata/FunctionCallArgsStatement/bracket-spacing.fmt.sol b/forge-fmt/testdata/FunctionCallArgsStatement/bracket-spacing.fmt.sol new file mode 100644 index 000000000..93e5eb1a2 --- /dev/null +++ b/forge-fmt/testdata/FunctionCallArgsStatement/bracket-spacing.fmt.sol @@ -0,0 +1,55 @@ +// config: bracket_spacing = true +interface ITarget { + function run() external payable; + function veryAndVeryLongNameOfSomeRunFunction() external payable; +} + +contract FunctionCallArgsStatement { + ITarget public target; + + function estimate() public returns (uint256 gas) { + gas = 1 gwei; + } + + function veryAndVeryLongNameOfSomeGasEstimateFunction() + public + returns (uint256) + { + return gasleft(); + } + + function value(uint256 val) public returns (uint256) { + return val; + } + + function test() external { + target.run{ gas: gasleft(), value: 1 wei }; + + target.run{ gas: 1, value: 0x00 }(); + + target.run{ gas: 1000, value: 1 ether }(); + + target.run{ gas: estimate(), value: value(1) }(); + + target.run{ + value: value(1 ether), + gas: veryAndVeryLongNameOfSomeGasEstimateFunction() + }(); + + target.run{ /* comment 1 */ value: /* comment2 */ 1 }; + + target.run{ /* comment3 */ + value: 1, // comment4 + gas: gasleft() + }; + + target.run{ + // comment5 + value: 1, + // comment6 + gas: gasleft() + }; + + vm.expectEmit({ checkTopic1: false, checkTopic2: false }); + } +} diff --git a/forge-fmt/testdata/FunctionCallArgsStatement/fmt.sol b/forge-fmt/testdata/FunctionCallArgsStatement/fmt.sol new file mode 100644 index 000000000..5a5cc5f63 --- /dev/null +++ b/forge-fmt/testdata/FunctionCallArgsStatement/fmt.sol @@ -0,0 +1,54 @@ +interface ITarget { + function run() external payable; + function veryAndVeryLongNameOfSomeRunFunction() external payable; +} + +contract FunctionCallArgsStatement { + ITarget public target; + + function estimate() public returns (uint256 gas) { + gas = 1 gwei; + } + + function veryAndVeryLongNameOfSomeGasEstimateFunction() + public + returns (uint256) + { + return gasleft(); + } + + function value(uint256 val) public returns (uint256) { + return val; + } + + function test() external { + target.run{gas: gasleft(), value: 1 wei}; + + target.run{gas: 1, value: 0x00}(); + + target.run{gas: 1000, value: 1 ether}(); + + target.run{gas: estimate(), value: value(1)}(); + + target.run{ + value: value(1 ether), + gas: veryAndVeryLongNameOfSomeGasEstimateFunction() + }(); + + target.run{ /* comment 1 */ value: /* comment2 */ 1}; + + target.run{ /* comment3 */ + value: 1, // comment4 + gas: gasleft() + }; + + target.run{ + // comment5 + value: 1, + // comment6 + gas: gasleft() + }; + + vm.expectEmit({checkTopic1: false, checkTopic2: false}); + } +} diff --git a/forge-fmt/testdata/FunctionCallArgsStatement/original.sol b/forge-fmt/testdata/FunctionCallArgsStatement/original.sol new file mode 100644 index 000000000..b2cfaa2f2 --- /dev/null +++ b/forge-fmt/testdata/FunctionCallArgsStatement/original.sol @@ -0,0 +1,50 @@ +interface ITarget { + function run() external payable; + function veryAndVeryLongNameOfSomeRunFunction() external payable; +} + +contract FunctionCallArgsStatement { + ITarget public target; + + function estimate() public returns (uint256 gas) { + gas = 1 gwei; + } + + function veryAndVeryLongNameOfSomeGasEstimateFunction() public returns (uint256) { + return gasleft(); + } + + function value(uint256 val) public returns (uint256) { + return val; + } + + function test() external { + target.run{ gas: gasleft(), value: 1 wei }; + + target.run{gas:1,value:0x00}(); + + target.run{ + gas : 1000, + value: 1 ether + } (); + + target.run{ gas: estimate(), + value: value(1) }(); + + target.run { value: + value(1 ether), gas: veryAndVeryLongNameOfSomeGasEstimateFunction() } (); + + target.run /* comment 1 */ { value: /* comment2 */ 1 }; + + target.run { /* comment3 */ value: 1, // comment4 + gas: gasleft()}; + + target.run { + // comment5 + value: 1, + // comment6 + gas: gasleft()}; + + vm.expectEmit({ checkTopic1: false, checkTopic2: false }); + } +} \ No newline at end of file diff --git a/forge-fmt/testdata/FunctionDefinition/all.fmt.sol b/forge-fmt/testdata/FunctionDefinition/all.fmt.sol new file mode 100644 index 000000000..6d9088067 --- /dev/null +++ b/forge-fmt/testdata/FunctionDefinition/all.fmt.sol @@ -0,0 +1,730 @@ +// config: line_length = 60 +// config: multiline_func_header = "all" +interface FunctionInterfaces { + function noParamsNoModifiersNoReturns(); + + function oneParam(uint256 x); + + function oneModifier() modifier1; + + function oneReturn() returns (uint256 y1); + + // function prefix + function withComments( // function name postfix + // x1 prefix + uint256 x1, // x1 postfix + // x2 prefix + uint256 x2, // x2 postfix + // x2 postfix2 + /* + multi-line x3 prefix + */ + uint256 x3 // x3 postfix + ) + // public prefix + public // public postfix + // pure prefix + pure // pure postfix + // modifier1 prefix + modifier1 // modifier1 postfix + // modifier2 prefix + modifier2 /* + mutliline modifier2 postfix + */ + // modifier3 prefix + modifier3 // modifier3 postfix + returns ( + // y1 prefix + uint256 y1, // y1 postfix + // y2 prefix + uint256 y2, // y2 postfix + // y3 prefix + uint256 y3 + ); // y3 postfix + // function postfix + + /*////////////////////////////////////////////////////////////////////////// + TEST + //////////////////////////////////////////////////////////////////////////*/ + function manyParams( + uint256 x1, + uint256 x2, + uint256 x3, + uint256 x4, + uint256 x5, + uint256 x6, + uint256 x7, + uint256 x8, + uint256 x9, + uint256 x10 + ); + + function manyModifiers() + modifier1 + modifier2 + modifier3 + modifier4 + modifier5 + modifier6 + modifier7 + modifier8 + modifier9 + modifier10; + + function manyReturns() + returns ( + uint256 y1, + uint256 y2, + uint256 y3, + uint256 y4, + uint256 y5, + uint256 y6, + uint256 y7, + uint256 y8, + uint256 y9, + uint256 y10 + ); + + function someParamsSomeModifiers( + uint256 x1, + uint256 x2, + uint256 x3 + ) + modifier1 + modifier2 + modifier3; + + function someParamsSomeReturns( + uint256 x1, + uint256 x2, + uint256 x3 + ) + returns (uint256 y1, uint256 y2, uint256 y3); + + function someModifiersSomeReturns() + modifier1 + modifier2 + modifier3 + returns (uint256 y1, uint256 y2, uint256 y3); + + function someParamSomeModifiersSomeReturns( + uint256 x1, + uint256 x2, + uint256 x3 + ) + modifier1 + modifier2 + modifier3 + returns (uint256 y1, uint256 y2, uint256 y3); + + function someParamsManyModifiers( + uint256 x1, + uint256 x2, + uint256 x3 + ) + modifier1 + modifier2 + modifier3 + modifier4 + modifier5 + modifier6 + modifier7 + modifier8 + modifier9 + modifier10; + + function someParamsManyReturns( + uint256 x1, + uint256 x2, + uint256 x3 + ) + returns ( + uint256 y1, + uint256 y2, + uint256 y3, + uint256 y4, + uint256 y5, + uint256 y6, + uint256 y7, + uint256 y8, + uint256 y9, + uint256 y10 + ); + + function manyParamsSomeModifiers( + uint256 x1, + uint256 x2, + uint256 x3, + uint256 x4, + uint256 x5, + uint256 x6, + uint256 x7, + uint256 x8, + uint256 x9, + uint256 x10 + ) + modifier1 + modifier2 + modifier3; + + function manyParamsSomeReturns( + uint256 x1, + uint256 x2, + uint256 x3, + uint256 x4, + uint256 x5, + uint256 x6, + uint256 x7, + uint256 x8, + uint256 x9, + uint256 x10 + ) + returns (uint256 y1, uint256 y2, uint256 y3); + + function manyParamsManyModifiers( + uint256 x1, + uint256 x2, + uint256 x3, + uint256 x4, + uint256 x5, + uint256 x6, + uint256 x7, + uint256 x8, + uint256 x9, + uint256 x10 + ) + modifier1 + modifier2 + modifier3 + modifier4 + modifier5 + modifier6 + modifier7 + modifier8 + modifier9 + modifier10; + + function manyParamsManyReturns( + uint256 x1, + uint256 x2, + uint256 x3, + uint256 x4, + uint256 x5, + uint256 x6, + uint256 x7, + uint256 x8, + uint256 x9, + uint256 x10 + ) + returns ( + uint256 y1, + uint256 y2, + uint256 y3, + uint256 y4, + uint256 y5, + uint256 y6, + uint256 y7, + uint256 y8, + uint256 y9, + uint256 y10 + ); + + function manyParamsManyModifiersManyReturns( + uint256 x1, + uint256 x2, + uint256 x3, + uint256 x4, + uint256 x5, + uint256 x6, + uint256 x7, + uint256 x8, + uint256 x9, + uint256 x10 + ) + modifier1 + modifier2 + modifier3 + modifier4 + modifier5 + modifier6 + modifier7 + modifier8 + modifier9 + modifier10 + returns ( + uint256 y1, + uint256 y2, + uint256 y3, + uint256 y4, + uint256 y5, + uint256 y6, + uint256 y7, + uint256 y8, + uint256 y9, + uint256 y10 + ); + + function modifierOrderCorrect01() + public + view + virtual + override + modifier1 + modifier2 + returns (uint256); + + function modifierOrderCorrect02() + private + pure + virtual + modifier1 + modifier2 + returns (string); + + function modifierOrderCorrect03() + external + payable + override + modifier1 + modifier2 + returns (address); + + function modifierOrderCorrect04() + internal + virtual + override + modifier1 + modifier2 + returns (uint256); + + function modifierOrderIncorrect01() + public + view + virtual + override + modifier1 + modifier2 + returns (uint256); + + function modifierOrderIncorrect02() + external + virtual + override + modifier1 + modifier2 + returns (uint256); + + function modifierOrderIncorrect03() + internal + pure + virtual + modifier1 + modifier2 + returns (uint256); + + function modifierOrderIncorrect04() + external + payable + override + modifier1 + modifier2 + returns (uint256); +} + +contract FunctionDefinitions { + function() external {} + fallback() external {} + + function() external payable {} + fallback() external payable {} + receive() external payable {} + + function noParamsNoModifiersNoReturns() { + a = 1; + } + + function oneParam(uint256 x) { + a = 1; + } + + function oneModifier() modifier1 { + a = 1; + } + + function oneReturn() returns (uint256 y1) { + a = 1; + } + + function manyParams( + uint256 x1, + uint256 x2, + uint256 x3, + uint256 x4, + uint256 x5, + uint256 x6, + uint256 x7, + uint256 x8, + uint256 x9, + uint256 x10 + ) { + a = 1; + } + + function manyModifiers() + modifier1 + modifier2 + modifier3 + modifier4 + modifier5 + modifier6 + modifier7 + modifier8 + modifier9 + modifier10 + { + a = 1; + } + + function manyReturns() + returns ( + uint256 y1, + uint256 y2, + uint256 y3, + uint256 y4, + uint256 y5, + uint256 y6, + uint256 y7, + uint256 y8, + uint256 y9, + uint256 y10 + ) + { + a = 1; + } + + function someParamsSomeModifiers( + uint256 x1, + uint256 x2, + uint256 x3 + ) + modifier1 + modifier2 + modifier3 + { + a = 1; + } + + function someParamsSomeReturns( + uint256 x1, + uint256 x2, + uint256 x3 + ) + returns (uint256 y1, uint256 y2, uint256 y3) + { + a = 1; + } + + function someModifiersSomeReturns() + modifier1 + modifier2 + modifier3 + returns (uint256 y1, uint256 y2, uint256 y3) + { + a = 1; + } + + function someParamSomeModifiersSomeReturns( + uint256 x1, + uint256 x2, + uint256 x3 + ) + modifier1 + modifier2 + modifier3 + returns (uint256 y1, uint256 y2, uint256 y3) + { + a = 1; + } + + function someParamsManyModifiers( + uint256 x1, + uint256 x2, + uint256 x3 + ) + modifier1 + modifier2 + modifier3 + modifier4 + modifier5 + modifier6 + modifier7 + modifier8 + modifier9 + modifier10 + { + a = 1; + } + + function someParamsManyReturns( + uint256 x1, + uint256 x2, + uint256 x3 + ) + returns ( + uint256 y1, + uint256 y2, + uint256 y3, + uint256 y4, + uint256 y5, + uint256 y6, + uint256 y7, + uint256 y8, + uint256 y9, + uint256 y10 + ) + { + a = 1; + } + + function manyParamsSomeModifiers( + uint256 x1, + uint256 x2, + uint256 x3, + uint256 x4, + uint256 x5, + uint256 x6, + uint256 x7, + uint256 x8, + uint256 x9, + uint256 x10 + ) + modifier1 + modifier2 + modifier3 + { + a = 1; + } + + function manyParamsSomeReturns( + uint256 x1, + uint256 x2, + uint256 x3, + uint256 x4, + uint256 x5, + uint256 x6, + uint256 x7, + uint256 x8, + uint256 x9, + uint256 x10 + ) + returns (uint256 y1, uint256 y2, uint256 y3) + { + a = 1; + } + + function manyParamsManyModifiers( + uint256 x1, + uint256 x2, + uint256 x3, + uint256 x4, + uint256 x5, + uint256 x6, + uint256 x7, + uint256 x8, + uint256 x9, + uint256 x10 + ) + public + modifier1 + modifier2 + modifier3 + modifier4 + modifier5 + modifier6 + modifier7 + modifier8 + modifier9 + modifier10 + { + a = 1; + } + + function manyParamsManyReturns( + uint256 x1, + uint256 x2, + uint256 x3, + uint256 x4, + uint256 x5, + uint256 x6, + uint256 x7, + uint256 x8, + uint256 x9, + uint256 x10 + ) + returns ( + uint256 y1, + uint256 y2, + uint256 y3, + uint256 y4, + uint256 y5, + uint256 y6, + uint256 y7, + uint256 y8, + uint256 y9, + uint256 y10 + ) + { + a = 1; + } + + function manyParamsManyModifiersManyReturns( + uint256 x1, + uint256 x2, + uint256 x3, + uint256 x4, + uint256 x5, + uint256 x6, + uint256 x7, + uint256 x8, + uint256 x9, + uint256 x10 + ) + modifier1 + modifier2 + modifier3 + modifier4 + modifier5 + modifier6 + modifier7 + modifier8 + modifier9 + modifier10 + returns ( + uint256 y1, + uint256 y2, + uint256 y3, + uint256 y4, + uint256 y5, + uint256 y6, + uint256 y7, + uint256 y8, + uint256 y9, + uint256 y10 + ) + { + a = 1; + } + + function modifierOrderCorrect01() + public + view + virtual + override + modifier1 + modifier2 + returns (uint256) + { + a = 1; + } + + function modifierOrderCorrect02() + private + pure + virtual + modifier1 + modifier2 + returns (string) + { + a = 1; + } + + function modifierOrderCorrect03() + external + payable + override + modifier1 + modifier2 + returns (address) + { + a = 1; + } + + function modifierOrderCorrect04() + internal + virtual + override + modifier1 + modifier2 + returns (uint256) + { + a = 1; + } + + function modifierOrderIncorrect01() + public + view + virtual + override + modifier1 + modifier2 + returns (uint256) + { + a = 1; + } + + function modifierOrderIncorrect02() + external + virtual + override + modifier1 + modifier2 + returns (uint256) + { + a = 1; + } + + function modifierOrderIncorrect03() + internal + pure + virtual + modifier1 + modifier2 + returns (uint256) + { + a = 1; + } + + function modifierOrderIncorrect04() + external + payable + override + modifier1 + modifier2 + returns (uint256) + { + a = 1; + } + + fallback() external payable virtual {} + receive() external payable virtual {} +} + +contract FunctionOverrides is + FunctionInterfaces, + FunctionDefinitions +{ + function noParamsNoModifiersNoReturns() override { + a = 1; + } + + function oneParam(uint256 x) + override( + FunctionInterfaces, + FunctionDefinitions, + SomeOtherFunctionContract, + SomeImport.AndAnotherFunctionContract + ) + { + a = 1; + } +} diff --git a/forge-fmt/testdata/FunctionDefinition/fmt.sol b/forge-fmt/testdata/FunctionDefinition/fmt.sol new file mode 100644 index 000000000..9e34a8bea --- /dev/null +++ b/forge-fmt/testdata/FunctionDefinition/fmt.sol @@ -0,0 +1,709 @@ +// config: line_length = 60 +interface FunctionInterfaces { + function noParamsNoModifiersNoReturns(); + + function oneParam(uint256 x); + + function oneModifier() modifier1; + + function oneReturn() returns (uint256 y1); + + // function prefix + function withComments( // function name postfix + // x1 prefix + uint256 x1, // x1 postfix + // x2 prefix + uint256 x2, // x2 postfix + // x2 postfix2 + /* + multi-line x3 prefix + */ + uint256 x3 // x3 postfix + ) + // public prefix + public // public postfix + // pure prefix + pure // pure postfix + // modifier1 prefix + modifier1 // modifier1 postfix + // modifier2 prefix + modifier2 /* + mutliline modifier2 postfix + */ + // modifier3 prefix + modifier3 // modifier3 postfix + returns ( + // y1 prefix + uint256 y1, // y1 postfix + // y2 prefix + uint256 y2, // y2 postfix + // y3 prefix + uint256 y3 + ); // y3 postfix + // function postfix + + /*////////////////////////////////////////////////////////////////////////// + TEST + //////////////////////////////////////////////////////////////////////////*/ + function manyParams( + uint256 x1, + uint256 x2, + uint256 x3, + uint256 x4, + uint256 x5, + uint256 x6, + uint256 x7, + uint256 x8, + uint256 x9, + uint256 x10 + ); + + function manyModifiers() + modifier1 + modifier2 + modifier3 + modifier4 + modifier5 + modifier6 + modifier7 + modifier8 + modifier9 + modifier10; + + function manyReturns() + returns ( + uint256 y1, + uint256 y2, + uint256 y3, + uint256 y4, + uint256 y5, + uint256 y6, + uint256 y7, + uint256 y8, + uint256 y9, + uint256 y10 + ); + + function someParamsSomeModifiers( + uint256 x1, + uint256 x2, + uint256 x3 + ) modifier1 modifier2 modifier3; + + function someParamsSomeReturns( + uint256 x1, + uint256 x2, + uint256 x3 + ) returns (uint256 y1, uint256 y2, uint256 y3); + + function someModifiersSomeReturns() + modifier1 + modifier2 + modifier3 + returns (uint256 y1, uint256 y2, uint256 y3); + + function someParamSomeModifiersSomeReturns( + uint256 x1, + uint256 x2, + uint256 x3 + ) + modifier1 + modifier2 + modifier3 + returns (uint256 y1, uint256 y2, uint256 y3); + + function someParamsManyModifiers( + uint256 x1, + uint256 x2, + uint256 x3 + ) + modifier1 + modifier2 + modifier3 + modifier4 + modifier5 + modifier6 + modifier7 + modifier8 + modifier9 + modifier10; + + function someParamsManyReturns( + uint256 x1, + uint256 x2, + uint256 x3 + ) + returns ( + uint256 y1, + uint256 y2, + uint256 y3, + uint256 y4, + uint256 y5, + uint256 y6, + uint256 y7, + uint256 y8, + uint256 y9, + uint256 y10 + ); + + function manyParamsSomeModifiers( + uint256 x1, + uint256 x2, + uint256 x3, + uint256 x4, + uint256 x5, + uint256 x6, + uint256 x7, + uint256 x8, + uint256 x9, + uint256 x10 + ) modifier1 modifier2 modifier3; + + function manyParamsSomeReturns( + uint256 x1, + uint256 x2, + uint256 x3, + uint256 x4, + uint256 x5, + uint256 x6, + uint256 x7, + uint256 x8, + uint256 x9, + uint256 x10 + ) returns (uint256 y1, uint256 y2, uint256 y3); + + function manyParamsManyModifiers( + uint256 x1, + uint256 x2, + uint256 x3, + uint256 x4, + uint256 x5, + uint256 x6, + uint256 x7, + uint256 x8, + uint256 x9, + uint256 x10 + ) + modifier1 + modifier2 + modifier3 + modifier4 + modifier5 + modifier6 + modifier7 + modifier8 + modifier9 + modifier10; + + function manyParamsManyReturns( + uint256 x1, + uint256 x2, + uint256 x3, + uint256 x4, + uint256 x5, + uint256 x6, + uint256 x7, + uint256 x8, + uint256 x9, + uint256 x10 + ) + returns ( + uint256 y1, + uint256 y2, + uint256 y3, + uint256 y4, + uint256 y5, + uint256 y6, + uint256 y7, + uint256 y8, + uint256 y9, + uint256 y10 + ); + + function manyParamsManyModifiersManyReturns( + uint256 x1, + uint256 x2, + uint256 x3, + uint256 x4, + uint256 x5, + uint256 x6, + uint256 x7, + uint256 x8, + uint256 x9, + uint256 x10 + ) + modifier1 + modifier2 + modifier3 + modifier4 + modifier5 + modifier6 + modifier7 + modifier8 + modifier9 + modifier10 + returns ( + uint256 y1, + uint256 y2, + uint256 y3, + uint256 y4, + uint256 y5, + uint256 y6, + uint256 y7, + uint256 y8, + uint256 y9, + uint256 y10 + ); + + function modifierOrderCorrect01() + public + view + virtual + override + modifier1 + modifier2 + returns (uint256); + + function modifierOrderCorrect02() + private + pure + virtual + modifier1 + modifier2 + returns (string); + + function modifierOrderCorrect03() + external + payable + override + modifier1 + modifier2 + returns (address); + + function modifierOrderCorrect04() + internal + virtual + override + modifier1 + modifier2 + returns (uint256); + + function modifierOrderIncorrect01() + public + view + virtual + override + modifier1 + modifier2 + returns (uint256); + + function modifierOrderIncorrect02() + external + virtual + override + modifier1 + modifier2 + returns (uint256); + + function modifierOrderIncorrect03() + internal + pure + virtual + modifier1 + modifier2 + returns (uint256); + + function modifierOrderIncorrect04() + external + payable + override + modifier1 + modifier2 + returns (uint256); +} + +contract FunctionDefinitions { + function() external {} + fallback() external {} + + function() external payable {} + fallback() external payable {} + receive() external payable {} + + function noParamsNoModifiersNoReturns() { + a = 1; + } + + function oneParam(uint256 x) { + a = 1; + } + + function oneModifier() modifier1 { + a = 1; + } + + function oneReturn() returns (uint256 y1) { + a = 1; + } + + function manyParams( + uint256 x1, + uint256 x2, + uint256 x3, + uint256 x4, + uint256 x5, + uint256 x6, + uint256 x7, + uint256 x8, + uint256 x9, + uint256 x10 + ) { + a = 1; + } + + function manyModifiers() + modifier1 + modifier2 + modifier3 + modifier4 + modifier5 + modifier6 + modifier7 + modifier8 + modifier9 + modifier10 + { + a = 1; + } + + function manyReturns() + returns ( + uint256 y1, + uint256 y2, + uint256 y3, + uint256 y4, + uint256 y5, + uint256 y6, + uint256 y7, + uint256 y8, + uint256 y9, + uint256 y10 + ) + { + a = 1; + } + + function someParamsSomeModifiers( + uint256 x1, + uint256 x2, + uint256 x3 + ) modifier1 modifier2 modifier3 { + a = 1; + } + + function someParamsSomeReturns( + uint256 x1, + uint256 x2, + uint256 x3 + ) returns (uint256 y1, uint256 y2, uint256 y3) { + a = 1; + } + + function someModifiersSomeReturns() + modifier1 + modifier2 + modifier3 + returns (uint256 y1, uint256 y2, uint256 y3) + { + a = 1; + } + + function someParamSomeModifiersSomeReturns( + uint256 x1, + uint256 x2, + uint256 x3 + ) + modifier1 + modifier2 + modifier3 + returns (uint256 y1, uint256 y2, uint256 y3) + { + a = 1; + } + + function someParamsManyModifiers( + uint256 x1, + uint256 x2, + uint256 x3 + ) + modifier1 + modifier2 + modifier3 + modifier4 + modifier5 + modifier6 + modifier7 + modifier8 + modifier9 + modifier10 + { + a = 1; + } + + function someParamsManyReturns( + uint256 x1, + uint256 x2, + uint256 x3 + ) + returns ( + uint256 y1, + uint256 y2, + uint256 y3, + uint256 y4, + uint256 y5, + uint256 y6, + uint256 y7, + uint256 y8, + uint256 y9, + uint256 y10 + ) + { + a = 1; + } + + function manyParamsSomeModifiers( + uint256 x1, + uint256 x2, + uint256 x3, + uint256 x4, + uint256 x5, + uint256 x6, + uint256 x7, + uint256 x8, + uint256 x9, + uint256 x10 + ) modifier1 modifier2 modifier3 { + a = 1; + } + + function manyParamsSomeReturns( + uint256 x1, + uint256 x2, + uint256 x3, + uint256 x4, + uint256 x5, + uint256 x6, + uint256 x7, + uint256 x8, + uint256 x9, + uint256 x10 + ) returns (uint256 y1, uint256 y2, uint256 y3) { + a = 1; + } + + function manyParamsManyModifiers( + uint256 x1, + uint256 x2, + uint256 x3, + uint256 x4, + uint256 x5, + uint256 x6, + uint256 x7, + uint256 x8, + uint256 x9, + uint256 x10 + ) + public + modifier1 + modifier2 + modifier3 + modifier4 + modifier5 + modifier6 + modifier7 + modifier8 + modifier9 + modifier10 + { + a = 1; + } + + function manyParamsManyReturns( + uint256 x1, + uint256 x2, + uint256 x3, + uint256 x4, + uint256 x5, + uint256 x6, + uint256 x7, + uint256 x8, + uint256 x9, + uint256 x10 + ) + returns ( + uint256 y1, + uint256 y2, + uint256 y3, + uint256 y4, + uint256 y5, + uint256 y6, + uint256 y7, + uint256 y8, + uint256 y9, + uint256 y10 + ) + { + a = 1; + } + + function manyParamsManyModifiersManyReturns( + uint256 x1, + uint256 x2, + uint256 x3, + uint256 x4, + uint256 x5, + uint256 x6, + uint256 x7, + uint256 x8, + uint256 x9, + uint256 x10 + ) + modifier1 + modifier2 + modifier3 + modifier4 + modifier5 + modifier6 + modifier7 + modifier8 + modifier9 + modifier10 + returns ( + uint256 y1, + uint256 y2, + uint256 y3, + uint256 y4, + uint256 y5, + uint256 y6, + uint256 y7, + uint256 y8, + uint256 y9, + uint256 y10 + ) + { + a = 1; + } + + function modifierOrderCorrect01() + public + view + virtual + override + modifier1 + modifier2 + returns (uint256) + { + a = 1; + } + + function modifierOrderCorrect02() + private + pure + virtual + modifier1 + modifier2 + returns (string) + { + a = 1; + } + + function modifierOrderCorrect03() + external + payable + override + modifier1 + modifier2 + returns (address) + { + a = 1; + } + + function modifierOrderCorrect04() + internal + virtual + override + modifier1 + modifier2 + returns (uint256) + { + a = 1; + } + + function modifierOrderIncorrect01() + public + view + virtual + override + modifier1 + modifier2 + returns (uint256) + { + a = 1; + } + + function modifierOrderIncorrect02() + external + virtual + override + modifier1 + modifier2 + returns (uint256) + { + a = 1; + } + + function modifierOrderIncorrect03() + internal + pure + virtual + modifier1 + modifier2 + returns (uint256) + { + a = 1; + } + + function modifierOrderIncorrect04() + external + payable + override + modifier1 + modifier2 + returns (uint256) + { + a = 1; + } + + fallback() external payable virtual {} + receive() external payable virtual {} +} + +contract FunctionOverrides is + FunctionInterfaces, + FunctionDefinitions +{ + function noParamsNoModifiersNoReturns() override { + a = 1; + } + + function oneParam(uint256 x) + override( + FunctionInterfaces, + FunctionDefinitions, + SomeOtherFunctionContract, + SomeImport.AndAnotherFunctionContract + ) + { + a = 1; + } +} diff --git a/forge-fmt/testdata/FunctionDefinition/original.sol b/forge-fmt/testdata/FunctionDefinition/original.sol new file mode 100644 index 000000000..97db649d5 --- /dev/null +++ b/forge-fmt/testdata/FunctionDefinition/original.sol @@ -0,0 +1,218 @@ +interface FunctionInterfaces { + function noParamsNoModifiersNoReturns(); + + function oneParam(uint x); + + function oneModifier() modifier1; + + function oneReturn() returns(uint y1); + + // function prefix + function withComments( // function name postfix + // x1 prefix + uint256 x1, // x1 postfix + + // x2 prefix + uint256 x2, // x2 postfix + // x2 postfix2 + /* + multi-line x3 prefix + */ + uint256 x3 // x3 postfix + + ) + // pure prefix + pure // pure postfix + // modifier1 prefix + modifier1 // modifier1 postfix + // public prefix + public // public postfix + // modifier2 prefix + modifier2 /* + mutliline modifier2 postfix + */ + // modifier3 prefix + modifier3 // modifier3 postfix + returns ( + // y1 prefix + uint256 y1, // y1 postfix + // y2 prefix + uint256 y2, // y2 postfix + // y3 prefix + uint256 y3 // y3 postfix + ); // function postfix + + /*////////////////////////////////////////////////////////////////////////// + TEST + //////////////////////////////////////////////////////////////////////////*/ + function manyParams(uint x1, uint x2, uint x3, uint x4, uint x5, uint x6, uint x7, uint x8, uint x9, uint x10); + + function manyModifiers() modifier1() modifier2() modifier3 modifier4 modifier5 modifier6 modifier7 modifier8 modifier9 modifier10; + + function manyReturns() returns(uint y1, uint y2, uint y3, uint y4, uint y5, uint y6, uint y7, uint y8, uint y9, uint y10); + + function someParamsSomeModifiers(uint x1, uint x2, uint x3) modifier1() modifier2 modifier3; + + function someParamsSomeReturns(uint x1, uint x2, uint x3) returns(uint y1, uint y2, uint y3); + + function someModifiersSomeReturns() modifier1 modifier2 modifier3 returns(uint y1, uint y2, uint y3); + + function someParamSomeModifiersSomeReturns(uint x1, uint x2, uint x3) modifier1 modifier2 modifier3 returns(uint y1, uint y2, uint y3); + + function someParamsManyModifiers(uint x1, uint x2, uint x3) modifier1 modifier2 modifier3 modifier4 modifier5 modifier6 modifier7 modifier8 modifier9 modifier10; + + function someParamsManyReturns(uint x1, uint x2, uint x3) returns(uint y1, uint y2, uint y3, uint y4, uint y5, uint y6, uint y7, uint y8, uint y9, uint y10); + + function manyParamsSomeModifiers(uint x1, uint x2, uint x3, uint x4, uint x5, uint x6, uint x7, uint x8, uint x9, uint x10) modifier1 modifier2 modifier3; + + function manyParamsSomeReturns(uint x1, uint x2, uint x3, uint x4, uint x5, uint x6, uint x7, uint x8, uint x9, uint x10) returns(uint y1, uint y2, uint y3); + + function manyParamsManyModifiers(uint x1, uint x2, uint x3, uint x4, uint x5, uint x6, uint x7, uint x8, uint x9, uint x10) modifier1 modifier2 modifier3 modifier4 modifier5 modifier6 modifier7 modifier8 modifier9 modifier10; + + function manyParamsManyReturns(uint x1, uint x2, uint x3, uint x4, uint x5, uint x6, uint x7, uint x8, uint x9, uint x10) returns(uint y1, uint y2, uint y3, uint y4, uint y5, uint y6, uint y7, uint y8, uint y9, uint y10); + + function manyParamsManyModifiersManyReturns(uint x1, uint x2, uint x3, uint x4, uint x5, uint x6, uint x7, uint x8, uint x9, uint x10) modifier1 modifier2 modifier3 modifier4 modifier5 modifier6 modifier7 modifier8 modifier9 modifier10 returns(uint y1, uint y2, uint y3, uint y4, uint y5, uint y6, uint y7, uint y8, uint y9, uint y10); + + function modifierOrderCorrect01() public view virtual override modifier1 modifier2 returns(uint); + + function modifierOrderCorrect02() private pure virtual modifier1 modifier2 returns(string); + + function modifierOrderCorrect03() external payable override modifier1 modifier2 returns(address); + + function modifierOrderCorrect04() internal virtual override modifier1 modifier2 returns(uint); + + function modifierOrderIncorrect01() public modifier1 modifier2 override virtual view returns(uint); + + function modifierOrderIncorrect02() virtual modifier1 external modifier2 override returns(uint); + + function modifierOrderIncorrect03() modifier1 pure internal virtual modifier2 returns(uint); + + function modifierOrderIncorrect04() override modifier1 payable external modifier2 returns(uint); +} + +contract FunctionDefinitions { + function () external {} + fallback () external {} + + function () external payable {} + fallback () external payable {} + receive () external payable {} + + function noParamsNoModifiersNoReturns() { + a = 1; + } + + function oneParam(uint x) { + a = 1; + } + + function oneModifier() modifier1 { + a = 1; + } + + function oneReturn() returns(uint y1) { + a = 1; + } + + function manyParams(uint x1, uint x2, uint x3, uint x4, uint x5, uint x6, uint x7, uint x8, uint x9, uint x10) { + a = 1; + } + + function manyModifiers() modifier1 modifier2 modifier3 modifier4 modifier5 modifier6 modifier7 modifier8 modifier9 modifier10 { + a = 1; + } + + function manyReturns() returns(uint y1, uint y2, uint y3, uint y4, uint y5, uint y6, uint y7, uint y8, uint y9, uint y10) { + a = 1; + } + + function someParamsSomeModifiers(uint x1, uint x2, uint x3) modifier1 modifier2 modifier3 { + a = 1; + } + + function someParamsSomeReturns(uint x1, uint x2, uint x3) returns(uint y1, uint y2, uint y3) { + a = 1; + } + + function someModifiersSomeReturns() modifier1 modifier2 modifier3 returns(uint y1, uint y2, uint y3) { + a = 1; + } + + function someParamSomeModifiersSomeReturns(uint x1, uint x2, uint x3) modifier1 modifier2 modifier3 returns(uint y1, uint y2, uint y3) { + a = 1; + } + + function someParamsManyModifiers(uint x1, uint x2, uint x3) modifier1 modifier2 modifier3 modifier4 modifier5 modifier6 modifier7 modifier8 modifier9 modifier10 { + a = 1; + } + + function someParamsManyReturns(uint x1, uint x2, uint x3) returns(uint y1, uint y2, uint y3, uint y4, uint y5, uint y6, uint y7, uint y8, uint y9, uint y10) { + a = 1; + } + + function manyParamsSomeModifiers(uint x1, uint x2, uint x3, uint x4, uint x5, uint x6, uint x7, uint x8, uint x9, uint x10) modifier1 modifier2 modifier3 { + a = 1; + } + + function manyParamsSomeReturns(uint x1, uint x2, uint x3, uint x4, uint x5, uint x6, uint x7, uint x8, uint x9, uint x10) returns(uint y1, uint y2, uint y3) { + a = 1; + } + + function manyParamsManyModifiers(uint x1, uint x2, uint x3, uint x4, uint x5, uint x6, uint x7, uint x8, uint x9, uint x10) modifier1 modifier2 modifier3 modifier4 modifier5 modifier6 modifier7 modifier8 modifier9 modifier10 public { + a = 1; + } + + function manyParamsManyReturns(uint x1, uint x2, uint x3, uint x4, uint x5, uint x6, uint x7, uint x8, uint x9, uint x10) returns(uint y1, uint y2, uint y3, uint y4, uint y5, uint y6, uint y7, uint y8, uint y9, uint y10) { + a = 1; + } + + function manyParamsManyModifiersManyReturns(uint x1, uint x2, uint x3, uint x4, uint x5, uint x6, uint x7, uint x8, uint x9, uint x10) modifier1 modifier2 modifier3 modifier4 modifier5 modifier6 modifier7 modifier8 modifier9 modifier10 returns(uint y1, uint y2, uint y3, uint y4, uint y5, uint y6, uint y7, uint y8, uint y9, uint y10) { + a = 1; + } + + function modifierOrderCorrect01() public view virtual override modifier1 modifier2 returns(uint) { + a = 1; + } + + function modifierOrderCorrect02() private pure virtual modifier1 modifier2 returns(string) { + a = 1; + } + + function modifierOrderCorrect03() external payable override modifier1 modifier2 returns(address) { + a = 1; + } + + function modifierOrderCorrect04() internal virtual override modifier1 modifier2 returns(uint) { + a = 1; + } + + function modifierOrderIncorrect01() public modifier1 modifier2 override virtual view returns(uint) { + a = 1; + } + + function modifierOrderIncorrect02() virtual modifier1 external modifier2 override returns(uint) { + a = 1; + } + + function modifierOrderIncorrect03() modifier1 pure internal virtual modifier2 returns(uint) { + a = 1; + } + + function modifierOrderIncorrect04() override modifier1 payable external modifier2 returns(uint) { + a = 1; + } + + fallback() external payable virtual {} + receive() external payable virtual {} +} + +contract FunctionOverrides is FunctionInterfaces, FunctionDefinitions { + function noParamsNoModifiersNoReturns() override { + a = 1; + } + + function oneParam(uint256 x) override(FunctionInterfaces, FunctionDefinitions, SomeOtherFunctionContract, SomeImport.AndAnotherFunctionContract) { + a = 1; + } +} + diff --git a/forge-fmt/testdata/FunctionDefinition/override-spacing.fmt.sol b/forge-fmt/testdata/FunctionDefinition/override-spacing.fmt.sol new file mode 100644 index 000000000..516e5c2fd --- /dev/null +++ b/forge-fmt/testdata/FunctionDefinition/override-spacing.fmt.sol @@ -0,0 +1,710 @@ +// config: line_length = 60 +// config: override_spacing = true +interface FunctionInterfaces { + function noParamsNoModifiersNoReturns(); + + function oneParam(uint256 x); + + function oneModifier() modifier1; + + function oneReturn() returns (uint256 y1); + + // function prefix + function withComments( // function name postfix + // x1 prefix + uint256 x1, // x1 postfix + // x2 prefix + uint256 x2, // x2 postfix + // x2 postfix2 + /* + multi-line x3 prefix + */ + uint256 x3 // x3 postfix + ) + // public prefix + public // public postfix + // pure prefix + pure // pure postfix + // modifier1 prefix + modifier1 // modifier1 postfix + // modifier2 prefix + modifier2 /* + mutliline modifier2 postfix + */ + // modifier3 prefix + modifier3 // modifier3 postfix + returns ( + // y1 prefix + uint256 y1, // y1 postfix + // y2 prefix + uint256 y2, // y2 postfix + // y3 prefix + uint256 y3 + ); // y3 postfix + // function postfix + + /*////////////////////////////////////////////////////////////////////////// + TEST + //////////////////////////////////////////////////////////////////////////*/ + function manyParams( + uint256 x1, + uint256 x2, + uint256 x3, + uint256 x4, + uint256 x5, + uint256 x6, + uint256 x7, + uint256 x8, + uint256 x9, + uint256 x10 + ); + + function manyModifiers() + modifier1 + modifier2 + modifier3 + modifier4 + modifier5 + modifier6 + modifier7 + modifier8 + modifier9 + modifier10; + + function manyReturns() + returns ( + uint256 y1, + uint256 y2, + uint256 y3, + uint256 y4, + uint256 y5, + uint256 y6, + uint256 y7, + uint256 y8, + uint256 y9, + uint256 y10 + ); + + function someParamsSomeModifiers( + uint256 x1, + uint256 x2, + uint256 x3 + ) modifier1 modifier2 modifier3; + + function someParamsSomeReturns( + uint256 x1, + uint256 x2, + uint256 x3 + ) returns (uint256 y1, uint256 y2, uint256 y3); + + function someModifiersSomeReturns() + modifier1 + modifier2 + modifier3 + returns (uint256 y1, uint256 y2, uint256 y3); + + function someParamSomeModifiersSomeReturns( + uint256 x1, + uint256 x2, + uint256 x3 + ) + modifier1 + modifier2 + modifier3 + returns (uint256 y1, uint256 y2, uint256 y3); + + function someParamsManyModifiers( + uint256 x1, + uint256 x2, + uint256 x3 + ) + modifier1 + modifier2 + modifier3 + modifier4 + modifier5 + modifier6 + modifier7 + modifier8 + modifier9 + modifier10; + + function someParamsManyReturns( + uint256 x1, + uint256 x2, + uint256 x3 + ) + returns ( + uint256 y1, + uint256 y2, + uint256 y3, + uint256 y4, + uint256 y5, + uint256 y6, + uint256 y7, + uint256 y8, + uint256 y9, + uint256 y10 + ); + + function manyParamsSomeModifiers( + uint256 x1, + uint256 x2, + uint256 x3, + uint256 x4, + uint256 x5, + uint256 x6, + uint256 x7, + uint256 x8, + uint256 x9, + uint256 x10 + ) modifier1 modifier2 modifier3; + + function manyParamsSomeReturns( + uint256 x1, + uint256 x2, + uint256 x3, + uint256 x4, + uint256 x5, + uint256 x6, + uint256 x7, + uint256 x8, + uint256 x9, + uint256 x10 + ) returns (uint256 y1, uint256 y2, uint256 y3); + + function manyParamsManyModifiers( + uint256 x1, + uint256 x2, + uint256 x3, + uint256 x4, + uint256 x5, + uint256 x6, + uint256 x7, + uint256 x8, + uint256 x9, + uint256 x10 + ) + modifier1 + modifier2 + modifier3 + modifier4 + modifier5 + modifier6 + modifier7 + modifier8 + modifier9 + modifier10; + + function manyParamsManyReturns( + uint256 x1, + uint256 x2, + uint256 x3, + uint256 x4, + uint256 x5, + uint256 x6, + uint256 x7, + uint256 x8, + uint256 x9, + uint256 x10 + ) + returns ( + uint256 y1, + uint256 y2, + uint256 y3, + uint256 y4, + uint256 y5, + uint256 y6, + uint256 y7, + uint256 y8, + uint256 y9, + uint256 y10 + ); + + function manyParamsManyModifiersManyReturns( + uint256 x1, + uint256 x2, + uint256 x3, + uint256 x4, + uint256 x5, + uint256 x6, + uint256 x7, + uint256 x8, + uint256 x9, + uint256 x10 + ) + modifier1 + modifier2 + modifier3 + modifier4 + modifier5 + modifier6 + modifier7 + modifier8 + modifier9 + modifier10 + returns ( + uint256 y1, + uint256 y2, + uint256 y3, + uint256 y4, + uint256 y5, + uint256 y6, + uint256 y7, + uint256 y8, + uint256 y9, + uint256 y10 + ); + + function modifierOrderCorrect01() + public + view + virtual + override + modifier1 + modifier2 + returns (uint256); + + function modifierOrderCorrect02() + private + pure + virtual + modifier1 + modifier2 + returns (string); + + function modifierOrderCorrect03() + external + payable + override + modifier1 + modifier2 + returns (address); + + function modifierOrderCorrect04() + internal + virtual + override + modifier1 + modifier2 + returns (uint256); + + function modifierOrderIncorrect01() + public + view + virtual + override + modifier1 + modifier2 + returns (uint256); + + function modifierOrderIncorrect02() + external + virtual + override + modifier1 + modifier2 + returns (uint256); + + function modifierOrderIncorrect03() + internal + pure + virtual + modifier1 + modifier2 + returns (uint256); + + function modifierOrderIncorrect04() + external + payable + override + modifier1 + modifier2 + returns (uint256); +} + +contract FunctionDefinitions { + function() external {} + fallback() external {} + + function() external payable {} + fallback() external payable {} + receive() external payable {} + + function noParamsNoModifiersNoReturns() { + a = 1; + } + + function oneParam(uint256 x) { + a = 1; + } + + function oneModifier() modifier1 { + a = 1; + } + + function oneReturn() returns (uint256 y1) { + a = 1; + } + + function manyParams( + uint256 x1, + uint256 x2, + uint256 x3, + uint256 x4, + uint256 x5, + uint256 x6, + uint256 x7, + uint256 x8, + uint256 x9, + uint256 x10 + ) { + a = 1; + } + + function manyModifiers() + modifier1 + modifier2 + modifier3 + modifier4 + modifier5 + modifier6 + modifier7 + modifier8 + modifier9 + modifier10 + { + a = 1; + } + + function manyReturns() + returns ( + uint256 y1, + uint256 y2, + uint256 y3, + uint256 y4, + uint256 y5, + uint256 y6, + uint256 y7, + uint256 y8, + uint256 y9, + uint256 y10 + ) + { + a = 1; + } + + function someParamsSomeModifiers( + uint256 x1, + uint256 x2, + uint256 x3 + ) modifier1 modifier2 modifier3 { + a = 1; + } + + function someParamsSomeReturns( + uint256 x1, + uint256 x2, + uint256 x3 + ) returns (uint256 y1, uint256 y2, uint256 y3) { + a = 1; + } + + function someModifiersSomeReturns() + modifier1 + modifier2 + modifier3 + returns (uint256 y1, uint256 y2, uint256 y3) + { + a = 1; + } + + function someParamSomeModifiersSomeReturns( + uint256 x1, + uint256 x2, + uint256 x3 + ) + modifier1 + modifier2 + modifier3 + returns (uint256 y1, uint256 y2, uint256 y3) + { + a = 1; + } + + function someParamsManyModifiers( + uint256 x1, + uint256 x2, + uint256 x3 + ) + modifier1 + modifier2 + modifier3 + modifier4 + modifier5 + modifier6 + modifier7 + modifier8 + modifier9 + modifier10 + { + a = 1; + } + + function someParamsManyReturns( + uint256 x1, + uint256 x2, + uint256 x3 + ) + returns ( + uint256 y1, + uint256 y2, + uint256 y3, + uint256 y4, + uint256 y5, + uint256 y6, + uint256 y7, + uint256 y8, + uint256 y9, + uint256 y10 + ) + { + a = 1; + } + + function manyParamsSomeModifiers( + uint256 x1, + uint256 x2, + uint256 x3, + uint256 x4, + uint256 x5, + uint256 x6, + uint256 x7, + uint256 x8, + uint256 x9, + uint256 x10 + ) modifier1 modifier2 modifier3 { + a = 1; + } + + function manyParamsSomeReturns( + uint256 x1, + uint256 x2, + uint256 x3, + uint256 x4, + uint256 x5, + uint256 x6, + uint256 x7, + uint256 x8, + uint256 x9, + uint256 x10 + ) returns (uint256 y1, uint256 y2, uint256 y3) { + a = 1; + } + + function manyParamsManyModifiers( + uint256 x1, + uint256 x2, + uint256 x3, + uint256 x4, + uint256 x5, + uint256 x6, + uint256 x7, + uint256 x8, + uint256 x9, + uint256 x10 + ) + public + modifier1 + modifier2 + modifier3 + modifier4 + modifier5 + modifier6 + modifier7 + modifier8 + modifier9 + modifier10 + { + a = 1; + } + + function manyParamsManyReturns( + uint256 x1, + uint256 x2, + uint256 x3, + uint256 x4, + uint256 x5, + uint256 x6, + uint256 x7, + uint256 x8, + uint256 x9, + uint256 x10 + ) + returns ( + uint256 y1, + uint256 y2, + uint256 y3, + uint256 y4, + uint256 y5, + uint256 y6, + uint256 y7, + uint256 y8, + uint256 y9, + uint256 y10 + ) + { + a = 1; + } + + function manyParamsManyModifiersManyReturns( + uint256 x1, + uint256 x2, + uint256 x3, + uint256 x4, + uint256 x5, + uint256 x6, + uint256 x7, + uint256 x8, + uint256 x9, + uint256 x10 + ) + modifier1 + modifier2 + modifier3 + modifier4 + modifier5 + modifier6 + modifier7 + modifier8 + modifier9 + modifier10 + returns ( + uint256 y1, + uint256 y2, + uint256 y3, + uint256 y4, + uint256 y5, + uint256 y6, + uint256 y7, + uint256 y8, + uint256 y9, + uint256 y10 + ) + { + a = 1; + } + + function modifierOrderCorrect01() + public + view + virtual + override + modifier1 + modifier2 + returns (uint256) + { + a = 1; + } + + function modifierOrderCorrect02() + private + pure + virtual + modifier1 + modifier2 + returns (string) + { + a = 1; + } + + function modifierOrderCorrect03() + external + payable + override + modifier1 + modifier2 + returns (address) + { + a = 1; + } + + function modifierOrderCorrect04() + internal + virtual + override + modifier1 + modifier2 + returns (uint256) + { + a = 1; + } + + function modifierOrderIncorrect01() + public + view + virtual + override + modifier1 + modifier2 + returns (uint256) + { + a = 1; + } + + function modifierOrderIncorrect02() + external + virtual + override + modifier1 + modifier2 + returns (uint256) + { + a = 1; + } + + function modifierOrderIncorrect03() + internal + pure + virtual + modifier1 + modifier2 + returns (uint256) + { + a = 1; + } + + function modifierOrderIncorrect04() + external + payable + override + modifier1 + modifier2 + returns (uint256) + { + a = 1; + } + + fallback() external payable virtual {} + receive() external payable virtual {} +} + +contract FunctionOverrides is + FunctionInterfaces, + FunctionDefinitions +{ + function noParamsNoModifiersNoReturns() override { + a = 1; + } + + function oneParam(uint256 x) + override ( + FunctionInterfaces, + FunctionDefinitions, + SomeOtherFunctionContract, + SomeImport.AndAnotherFunctionContract + ) + { + a = 1; + } +} diff --git a/forge-fmt/testdata/FunctionDefinition/params-first.fmt.sol b/forge-fmt/testdata/FunctionDefinition/params-first.fmt.sol new file mode 100644 index 000000000..50f539c32 --- /dev/null +++ b/forge-fmt/testdata/FunctionDefinition/params-first.fmt.sol @@ -0,0 +1,710 @@ +// config: line_length = 60 +// config: multiline_func_header = "params_first" +interface FunctionInterfaces { + function noParamsNoModifiersNoReturns(); + + function oneParam(uint256 x); + + function oneModifier() modifier1; + + function oneReturn() returns (uint256 y1); + + // function prefix + function withComments( // function name postfix + // x1 prefix + uint256 x1, // x1 postfix + // x2 prefix + uint256 x2, // x2 postfix + // x2 postfix2 + /* + multi-line x3 prefix + */ + uint256 x3 // x3 postfix + ) + // public prefix + public // public postfix + // pure prefix + pure // pure postfix + // modifier1 prefix + modifier1 // modifier1 postfix + // modifier2 prefix + modifier2 /* + mutliline modifier2 postfix + */ + // modifier3 prefix + modifier3 // modifier3 postfix + returns ( + // y1 prefix + uint256 y1, // y1 postfix + // y2 prefix + uint256 y2, // y2 postfix + // y3 prefix + uint256 y3 + ); // y3 postfix + // function postfix + + /*////////////////////////////////////////////////////////////////////////// + TEST + //////////////////////////////////////////////////////////////////////////*/ + function manyParams( + uint256 x1, + uint256 x2, + uint256 x3, + uint256 x4, + uint256 x5, + uint256 x6, + uint256 x7, + uint256 x8, + uint256 x9, + uint256 x10 + ); + + function manyModifiers() + modifier1 + modifier2 + modifier3 + modifier4 + modifier5 + modifier6 + modifier7 + modifier8 + modifier9 + modifier10; + + function manyReturns() + returns ( + uint256 y1, + uint256 y2, + uint256 y3, + uint256 y4, + uint256 y5, + uint256 y6, + uint256 y7, + uint256 y8, + uint256 y9, + uint256 y10 + ); + + function someParamsSomeModifiers( + uint256 x1, + uint256 x2, + uint256 x3 + ) modifier1 modifier2 modifier3; + + function someParamsSomeReturns( + uint256 x1, + uint256 x2, + uint256 x3 + ) returns (uint256 y1, uint256 y2, uint256 y3); + + function someModifiersSomeReturns() + modifier1 + modifier2 + modifier3 + returns (uint256 y1, uint256 y2, uint256 y3); + + function someParamSomeModifiersSomeReturns( + uint256 x1, + uint256 x2, + uint256 x3 + ) + modifier1 + modifier2 + modifier3 + returns (uint256 y1, uint256 y2, uint256 y3); + + function someParamsManyModifiers( + uint256 x1, + uint256 x2, + uint256 x3 + ) + modifier1 + modifier2 + modifier3 + modifier4 + modifier5 + modifier6 + modifier7 + modifier8 + modifier9 + modifier10; + + function someParamsManyReturns( + uint256 x1, + uint256 x2, + uint256 x3 + ) + returns ( + uint256 y1, + uint256 y2, + uint256 y3, + uint256 y4, + uint256 y5, + uint256 y6, + uint256 y7, + uint256 y8, + uint256 y9, + uint256 y10 + ); + + function manyParamsSomeModifiers( + uint256 x1, + uint256 x2, + uint256 x3, + uint256 x4, + uint256 x5, + uint256 x6, + uint256 x7, + uint256 x8, + uint256 x9, + uint256 x10 + ) modifier1 modifier2 modifier3; + + function manyParamsSomeReturns( + uint256 x1, + uint256 x2, + uint256 x3, + uint256 x4, + uint256 x5, + uint256 x6, + uint256 x7, + uint256 x8, + uint256 x9, + uint256 x10 + ) returns (uint256 y1, uint256 y2, uint256 y3); + + function manyParamsManyModifiers( + uint256 x1, + uint256 x2, + uint256 x3, + uint256 x4, + uint256 x5, + uint256 x6, + uint256 x7, + uint256 x8, + uint256 x9, + uint256 x10 + ) + modifier1 + modifier2 + modifier3 + modifier4 + modifier5 + modifier6 + modifier7 + modifier8 + modifier9 + modifier10; + + function manyParamsManyReturns( + uint256 x1, + uint256 x2, + uint256 x3, + uint256 x4, + uint256 x5, + uint256 x6, + uint256 x7, + uint256 x8, + uint256 x9, + uint256 x10 + ) + returns ( + uint256 y1, + uint256 y2, + uint256 y3, + uint256 y4, + uint256 y5, + uint256 y6, + uint256 y7, + uint256 y8, + uint256 y9, + uint256 y10 + ); + + function manyParamsManyModifiersManyReturns( + uint256 x1, + uint256 x2, + uint256 x3, + uint256 x4, + uint256 x5, + uint256 x6, + uint256 x7, + uint256 x8, + uint256 x9, + uint256 x10 + ) + modifier1 + modifier2 + modifier3 + modifier4 + modifier5 + modifier6 + modifier7 + modifier8 + modifier9 + modifier10 + returns ( + uint256 y1, + uint256 y2, + uint256 y3, + uint256 y4, + uint256 y5, + uint256 y6, + uint256 y7, + uint256 y8, + uint256 y9, + uint256 y10 + ); + + function modifierOrderCorrect01() + public + view + virtual + override + modifier1 + modifier2 + returns (uint256); + + function modifierOrderCorrect02() + private + pure + virtual + modifier1 + modifier2 + returns (string); + + function modifierOrderCorrect03() + external + payable + override + modifier1 + modifier2 + returns (address); + + function modifierOrderCorrect04() + internal + virtual + override + modifier1 + modifier2 + returns (uint256); + + function modifierOrderIncorrect01() + public + view + virtual + override + modifier1 + modifier2 + returns (uint256); + + function modifierOrderIncorrect02() + external + virtual + override + modifier1 + modifier2 + returns (uint256); + + function modifierOrderIncorrect03() + internal + pure + virtual + modifier1 + modifier2 + returns (uint256); + + function modifierOrderIncorrect04() + external + payable + override + modifier1 + modifier2 + returns (uint256); +} + +contract FunctionDefinitions { + function() external {} + fallback() external {} + + function() external payable {} + fallback() external payable {} + receive() external payable {} + + function noParamsNoModifiersNoReturns() { + a = 1; + } + + function oneParam(uint256 x) { + a = 1; + } + + function oneModifier() modifier1 { + a = 1; + } + + function oneReturn() returns (uint256 y1) { + a = 1; + } + + function manyParams( + uint256 x1, + uint256 x2, + uint256 x3, + uint256 x4, + uint256 x5, + uint256 x6, + uint256 x7, + uint256 x8, + uint256 x9, + uint256 x10 + ) { + a = 1; + } + + function manyModifiers() + modifier1 + modifier2 + modifier3 + modifier4 + modifier5 + modifier6 + modifier7 + modifier8 + modifier9 + modifier10 + { + a = 1; + } + + function manyReturns() + returns ( + uint256 y1, + uint256 y2, + uint256 y3, + uint256 y4, + uint256 y5, + uint256 y6, + uint256 y7, + uint256 y8, + uint256 y9, + uint256 y10 + ) + { + a = 1; + } + + function someParamsSomeModifiers( + uint256 x1, + uint256 x2, + uint256 x3 + ) modifier1 modifier2 modifier3 { + a = 1; + } + + function someParamsSomeReturns( + uint256 x1, + uint256 x2, + uint256 x3 + ) returns (uint256 y1, uint256 y2, uint256 y3) { + a = 1; + } + + function someModifiersSomeReturns() + modifier1 + modifier2 + modifier3 + returns (uint256 y1, uint256 y2, uint256 y3) + { + a = 1; + } + + function someParamSomeModifiersSomeReturns( + uint256 x1, + uint256 x2, + uint256 x3 + ) + modifier1 + modifier2 + modifier3 + returns (uint256 y1, uint256 y2, uint256 y3) + { + a = 1; + } + + function someParamsManyModifiers( + uint256 x1, + uint256 x2, + uint256 x3 + ) + modifier1 + modifier2 + modifier3 + modifier4 + modifier5 + modifier6 + modifier7 + modifier8 + modifier9 + modifier10 + { + a = 1; + } + + function someParamsManyReturns( + uint256 x1, + uint256 x2, + uint256 x3 + ) + returns ( + uint256 y1, + uint256 y2, + uint256 y3, + uint256 y4, + uint256 y5, + uint256 y6, + uint256 y7, + uint256 y8, + uint256 y9, + uint256 y10 + ) + { + a = 1; + } + + function manyParamsSomeModifiers( + uint256 x1, + uint256 x2, + uint256 x3, + uint256 x4, + uint256 x5, + uint256 x6, + uint256 x7, + uint256 x8, + uint256 x9, + uint256 x10 + ) modifier1 modifier2 modifier3 { + a = 1; + } + + function manyParamsSomeReturns( + uint256 x1, + uint256 x2, + uint256 x3, + uint256 x4, + uint256 x5, + uint256 x6, + uint256 x7, + uint256 x8, + uint256 x9, + uint256 x10 + ) returns (uint256 y1, uint256 y2, uint256 y3) { + a = 1; + } + + function manyParamsManyModifiers( + uint256 x1, + uint256 x2, + uint256 x3, + uint256 x4, + uint256 x5, + uint256 x6, + uint256 x7, + uint256 x8, + uint256 x9, + uint256 x10 + ) + public + modifier1 + modifier2 + modifier3 + modifier4 + modifier5 + modifier6 + modifier7 + modifier8 + modifier9 + modifier10 + { + a = 1; + } + + function manyParamsManyReturns( + uint256 x1, + uint256 x2, + uint256 x3, + uint256 x4, + uint256 x5, + uint256 x6, + uint256 x7, + uint256 x8, + uint256 x9, + uint256 x10 + ) + returns ( + uint256 y1, + uint256 y2, + uint256 y3, + uint256 y4, + uint256 y5, + uint256 y6, + uint256 y7, + uint256 y8, + uint256 y9, + uint256 y10 + ) + { + a = 1; + } + + function manyParamsManyModifiersManyReturns( + uint256 x1, + uint256 x2, + uint256 x3, + uint256 x4, + uint256 x5, + uint256 x6, + uint256 x7, + uint256 x8, + uint256 x9, + uint256 x10 + ) + modifier1 + modifier2 + modifier3 + modifier4 + modifier5 + modifier6 + modifier7 + modifier8 + modifier9 + modifier10 + returns ( + uint256 y1, + uint256 y2, + uint256 y3, + uint256 y4, + uint256 y5, + uint256 y6, + uint256 y7, + uint256 y8, + uint256 y9, + uint256 y10 + ) + { + a = 1; + } + + function modifierOrderCorrect01() + public + view + virtual + override + modifier1 + modifier2 + returns (uint256) + { + a = 1; + } + + function modifierOrderCorrect02() + private + pure + virtual + modifier1 + modifier2 + returns (string) + { + a = 1; + } + + function modifierOrderCorrect03() + external + payable + override + modifier1 + modifier2 + returns (address) + { + a = 1; + } + + function modifierOrderCorrect04() + internal + virtual + override + modifier1 + modifier2 + returns (uint256) + { + a = 1; + } + + function modifierOrderIncorrect01() + public + view + virtual + override + modifier1 + modifier2 + returns (uint256) + { + a = 1; + } + + function modifierOrderIncorrect02() + external + virtual + override + modifier1 + modifier2 + returns (uint256) + { + a = 1; + } + + function modifierOrderIncorrect03() + internal + pure + virtual + modifier1 + modifier2 + returns (uint256) + { + a = 1; + } + + function modifierOrderIncorrect04() + external + payable + override + modifier1 + modifier2 + returns (uint256) + { + a = 1; + } + + fallback() external payable virtual {} + receive() external payable virtual {} +} + +contract FunctionOverrides is + FunctionInterfaces, + FunctionDefinitions +{ + function noParamsNoModifiersNoReturns() override { + a = 1; + } + + function oneParam(uint256 x) + override( + FunctionInterfaces, + FunctionDefinitions, + SomeOtherFunctionContract, + SomeImport.AndAnotherFunctionContract + ) + { + a = 1; + } +} diff --git a/forge-fmt/testdata/FunctionType/fmt.sol b/forge-fmt/testdata/FunctionType/fmt.sol new file mode 100644 index 000000000..67c784cac --- /dev/null +++ b/forge-fmt/testdata/FunctionType/fmt.sol @@ -0,0 +1,31 @@ +// config: line_length = 90 +library ArrayUtils { + function map(uint256[] memory self, function (uint) pure returns (uint) f) + internal + pure + returns (uint256[] memory r) + { + r = new uint[](self.length); + for (uint256 i = 0; i < self.length; i++) { + r[i] = f(self[i]); + } + } + + function reduce(uint256[] memory self, function (uint, uint) pure returns (uint) f) + internal + pure + returns (uint256 r) + { + r = self[0]; + for (uint256 i = 1; i < self.length; i++) { + r = f(r, self[i]); + } + } + + function range(uint256 length) internal pure returns (uint256[] memory r) { + r = new uint[](length); + for (uint256 i = 0; i < r.length; i++) { + r[i] = i; + } + } +} diff --git a/forge-fmt/testdata/FunctionType/original.sol b/forge-fmt/testdata/FunctionType/original.sol new file mode 100644 index 000000000..7247a4f93 --- /dev/null +++ b/forge-fmt/testdata/FunctionType/original.sol @@ -0,0 +1,31 @@ +library ArrayUtils { + function map(uint[] memory self, function (uint) pure returns (uint) f) + internal + pure + returns ( + uint[] memory r + ) + { + r = new uint[](self.length); + for (uint i = 0; i < self.length; i++) { + r[i] = f(self[i]); + } + } + + function reduce( + uint[] memory self, + function (uint, uint) pure returns (uint) f + ) internal pure returns (uint256 r) { + r = self[0]; + for (uint i = 1; i < self.length; i++) { + r = f(r, self[i]); + } + } + + function range(uint256 length) internal pure returns (uint[] memory r) { + r = new uint[](length); + for (uint i = 0; i < r.length; i++) { + r[i] = i; + } + } +} diff --git a/forge-fmt/testdata/IfStatement/block-multi.fmt.sol b/forge-fmt/testdata/IfStatement/block-multi.fmt.sol new file mode 100644 index 000000000..dcd8bb83e --- /dev/null +++ b/forge-fmt/testdata/IfStatement/block-multi.fmt.sol @@ -0,0 +1,171 @@ +// config: single_line_statement_blocks = "multi" +function execute() returns (bool) { + if (true) { + // always returns true + return true; + } + return false; +} + +function executeElse() {} + +function executeWithMultipleParameters(bool parameter1, bool parameter2) {} + +function executeWithVeryVeryVeryLongNameAndSomeParameter(bool parameter) {} + +contract IfStatement { + function test() external { + if (true) { + execute(); + } + + bool condition; + bool anotherLongCondition; + bool andAnotherVeryVeryLongCondition; + if ( + condition && anotherLongCondition || andAnotherVeryVeryLongCondition + ) { + execute(); + } + + // comment + if (condition) { + execute(); + } else if (anotherLongCondition) { + execute(); // differently + } + + /* comment1 */ + if ( /* comment2 */ /* comment3 */ + condition // comment4 + ) { + // comment5 + execute(); + } // comment6 + + if (condition) { + execute(); + } // comment7 + /* comment8 */ + /* comment9 */ + else if ( /* comment10 */ + anotherLongCondition // comment11 + ) { + /* comment12 */ + execute(); + } // comment13 + /* comment14 */ + else {} // comment15 + + if ( + // comment16 + condition /* comment17 */ + ) { + execute(); + } + + if (condition) { + execute(); + } else { + executeElse(); + } + + if (condition) { + if (anotherLongCondition) { + execute(); + } + } + + if (condition) { + execute(); + } + + if ( + condition && anotherLongCondition || andAnotherVeryVeryLongCondition + ) { + execute(); + } + + if (condition) { + if (anotherLongCondition) { + execute(); + } + } + + if (condition) { + execute(); + } // comment18 + + if (condition) { + executeWithMultipleParameters(condition, anotherLongCondition); + } + + if (condition) { + executeWithVeryVeryVeryLongNameAndSomeParameter(condition); + } + + if (condition) { + execute(); + } else { + execute(); + } + + if (condition) {} + + if (condition) { + executeWithMultipleParameters(condition, anotherLongCondition); + } else if (anotherLongCondition) { + execute(); + } + + if (condition && ((condition || anotherLongCondition))) { + execute(); + } + + // if statement + if (condition) { + execute(); + } + // else statement + else { + execute(); + } + + // if statement + if (condition) { + execute(); + } + // else statement + else { + executeWithMultipleParameters( + anotherLongCondition, andAnotherVeryVeryLongCondition + ); + } + + if (condition) { + execute(); + } else if (condition) { + execute(); + } else if (condition) { + execute(); + } else if (condition) { + execute(); + } else if (condition) { + execute(); + } + + if (condition) { + execute(); + } else if (condition) { + execute(); + } else if (condition) { + execute(); + } else if (condition) { + execute(); + } else if (condition) { + execute(); + } else { + executeElse(); + } + } +} diff --git a/forge-fmt/testdata/IfStatement/block-single.fmt.sol b/forge-fmt/testdata/IfStatement/block-single.fmt.sol new file mode 100644 index 000000000..ba2b9998b --- /dev/null +++ b/forge-fmt/testdata/IfStatement/block-single.fmt.sol @@ -0,0 +1,123 @@ +// config: single_line_statement_blocks = "single" +function execute() returns (bool) { + if (true) { + // always returns true + return true; + } + return false; +} + +function executeElse() {} + +function executeWithMultipleParameters(bool parameter1, bool parameter2) {} + +function executeWithVeryVeryVeryLongNameAndSomeParameter(bool parameter) {} + +contract IfStatement { + function test() external { + if (true) execute(); + + bool condition; + bool anotherLongCondition; + bool andAnotherVeryVeryLongCondition; + if ( + condition && anotherLongCondition || andAnotherVeryVeryLongCondition + ) execute(); + + // comment + if (condition) execute(); + else if (anotherLongCondition) execute(); // differently + + /* comment1 */ + if ( /* comment2 */ /* comment3 */ + condition // comment4 + ) { + // comment5 + execute(); + } // comment6 + + if (condition) { + execute(); + } // comment7 + /* comment8 */ + /* comment9 */ + else if ( /* comment10 */ + anotherLongCondition // comment11 + ) { + /* comment12 */ + execute(); + } // comment13 + /* comment14 */ + else {} // comment15 + + if ( + // comment16 + condition /* comment17 */ + ) execute(); + + if (condition) execute(); + else executeElse(); + + if (condition) if (anotherLongCondition) execute(); + + if (condition) execute(); + + if ( + condition && anotherLongCondition || andAnotherVeryVeryLongCondition + ) execute(); + + if (condition) if (anotherLongCondition) execute(); + + if (condition) execute(); // comment18 + + if (condition) { + executeWithMultipleParameters(condition, anotherLongCondition); + } + + if (condition) { + executeWithVeryVeryVeryLongNameAndSomeParameter(condition); + } + + if (condition) execute(); + else execute(); + + if (condition) {} + + if (condition) { + executeWithMultipleParameters(condition, anotherLongCondition); + } else if (anotherLongCondition) { + execute(); + } + + if (condition && ((condition || anotherLongCondition))) execute(); + + // if statement + if (condition) execute(); + // else statement + else execute(); + + // if statement + if (condition) { + execute(); + } + // else statement + else { + executeWithMultipleParameters( + anotherLongCondition, andAnotherVeryVeryLongCondition + ); + } + + if (condition) execute(); + else if (condition) execute(); + else if (condition) execute(); + else if (condition) execute(); + else if (condition) execute(); + + if (condition) execute(); + else if (condition) execute(); + else if (condition) execute(); + else if (condition) execute(); + else if (condition) execute(); + else executeElse(); + } +} diff --git a/forge-fmt/testdata/IfStatement/fmt.sol b/forge-fmt/testdata/IfStatement/fmt.sol new file mode 100644 index 000000000..cb2f8874f --- /dev/null +++ b/forge-fmt/testdata/IfStatement/fmt.sol @@ -0,0 +1,145 @@ +function execute() returns (bool) { + if (true) { + // always returns true + return true; + } + return false; +} + +function executeElse() {} + +function executeWithMultipleParameters(bool parameter1, bool parameter2) {} + +function executeWithVeryVeryVeryLongNameAndSomeParameter(bool parameter) {} + +contract IfStatement { + function test() external { + if (true) { + execute(); + } + + bool condition; + bool anotherLongCondition; + bool andAnotherVeryVeryLongCondition; + if ( + condition && anotherLongCondition || andAnotherVeryVeryLongCondition + ) { + execute(); + } + + // comment + if (condition) { + execute(); + } else if (anotherLongCondition) { + execute(); // differently + } + + /* comment1 */ + if ( /* comment2 */ /* comment3 */ + condition // comment4 + ) { + // comment5 + execute(); + } // comment6 + + if (condition) { + execute(); + } // comment7 + /* comment8 */ + /* comment9 */ + else if ( /* comment10 */ + anotherLongCondition // comment11 + ) { + /* comment12 */ + execute(); + } // comment13 + /* comment14 */ + else {} // comment15 + + if ( + // comment16 + condition /* comment17 */ + ) { + execute(); + } + + if (condition) { + execute(); + } else { + executeElse(); + } + + if (condition) { + if (anotherLongCondition) { + execute(); + } + } + + if (condition) execute(); + + if ( + condition && anotherLongCondition || andAnotherVeryVeryLongCondition + ) execute(); + + if (condition) if (anotherLongCondition) execute(); + + if (condition) execute(); // comment18 + + if (condition) { + executeWithMultipleParameters(condition, anotherLongCondition); + } + + if (condition) { + executeWithVeryVeryVeryLongNameAndSomeParameter(condition); + } + + if (condition) execute(); + else execute(); + + if (condition) {} + + if (condition) { + executeWithMultipleParameters(condition, anotherLongCondition); + } else if (anotherLongCondition) { + execute(); + } + + if (condition && ((condition || anotherLongCondition))) execute(); + + // if statement + if (condition) execute(); + // else statement + else execute(); + + // if statement + if (condition) { + execute(); + } + // else statement + else { + executeWithMultipleParameters( + anotherLongCondition, andAnotherVeryVeryLongCondition + ); + } + + if (condition) execute(); + else if (condition) execute(); + else if (condition) execute(); + else if (condition) execute(); + else if (condition) execute(); + + if (condition) { + execute(); + } else if (condition) { + execute(); + } else if (condition) { + execute(); + } else if (condition) { + execute(); + } else if (condition) { + execute(); + } else { + executeElse(); + } + } +} diff --git a/forge-fmt/testdata/IfStatement/original.sol b/forge-fmt/testdata/IfStatement/original.sol new file mode 100644 index 000000000..b36829bbb --- /dev/null +++ b/forge-fmt/testdata/IfStatement/original.sol @@ -0,0 +1,119 @@ +function execute() returns (bool) { + if (true) { + // always returns true + return true; + } + return false; +} + +function executeElse() {} + +function executeWithMultipleParameters(bool parameter1, bool parameter2) {} + +function executeWithVeryVeryVeryLongNameAndSomeParameter(bool parameter) {} + +contract IfStatement { + + function test() external { + if( true) + { + execute() ; + } + + bool condition; bool anotherLongCondition; bool andAnotherVeryVeryLongCondition ; + if + ( condition && anotherLongCondition || + andAnotherVeryVeryLongCondition + ) + { execute(); } + + // comment + if (condition) { execute(); } + else + if (anotherLongCondition) { + execute(); // differently + } + + /* comment1 */ if /* comment2 */ ( /* comment3 */ condition ) // comment4 + { + // comment5 + execute(); + } // comment6 + + if (condition ) { + execute(); + } // comment7 + /* comment8 */ + /* comment9 */ else if /* comment10 */ (anotherLongCondition) // comment11 + /* comment12 */ { + execute() ; + } // comment13 + /* comment14 */ else { } // comment15 + + if ( + // comment16 + condition /* comment17 */ + ) + { + execute(); + } + + if (condition) + execute(); + else + executeElse(); + + if (condition) + if (anotherLongCondition) + execute(); + + if (condition) execute(); + + if (condition && anotherLongCondition || + andAnotherVeryVeryLongCondition ) execute(); + + if (condition) if (anotherLongCondition) execute(); + + if (condition) execute(); // comment18 + + if (condition) executeWithMultipleParameters(condition, anotherLongCondition); + + if (condition) executeWithVeryVeryVeryLongNameAndSomeParameter(condition); + + if (condition) execute(); else execute(); + + if (condition) {} + + if (condition) executeWithMultipleParameters(condition, anotherLongCondition); else if (anotherLongCondition) execute(); + + if (condition && ((condition || anotherLongCondition) + ) + ) execute(); + + // if statement + if (condition) execute(); + // else statement + else execute(); + + // if statement + if (condition) execute(); + // else statement + else executeWithMultipleParameters(anotherLongCondition, andAnotherVeryVeryLongCondition); + + if (condition) execute(); + else if (condition) execute(); + else if (condition) execute(); + else if (condition) execute(); + else if (condition) execute(); + + if (condition) execute(); + else if (condition) + execute(); + else if (condition) execute(); + else if (condition) + execute(); + else if (condition) execute(); + else + executeElse(); + } +} \ No newline at end of file diff --git a/forge-fmt/testdata/IfStatement2/fmt.sol b/forge-fmt/testdata/IfStatement2/fmt.sol new file mode 100644 index 000000000..10ae43601 --- /dev/null +++ b/forge-fmt/testdata/IfStatement2/fmt.sol @@ -0,0 +1,7 @@ +contract IfStatement { + function test() external { + bool anotherLongCondition; + + if (condition && ((condition || anotherLongCondition))) execute(); + } +} diff --git a/forge-fmt/testdata/IfStatement2/original.sol b/forge-fmt/testdata/IfStatement2/original.sol new file mode 100644 index 000000000..df020c04b --- /dev/null +++ b/forge-fmt/testdata/IfStatement2/original.sol @@ -0,0 +1,10 @@ +contract IfStatement { + + function test() external { + bool anotherLongCondition; + + if (condition && ((condition || anotherLongCondition) + ) + ) execute(); + } +} \ No newline at end of file diff --git a/forge-fmt/testdata/ImportDirective/bracket-spacing.fmt.sol b/forge-fmt/testdata/ImportDirective/bracket-spacing.fmt.sol new file mode 100644 index 000000000..1db94929a --- /dev/null +++ b/forge-fmt/testdata/ImportDirective/bracket-spacing.fmt.sol @@ -0,0 +1,21 @@ +// config: bracket_spacing = true +import "SomeFile.sol"; +import "SomeFile.sol"; +import "SomeFile.sol" as SomeOtherFile; +import "SomeFile.sol" as SomeOtherFile; +import "AnotherFile.sol" as SomeSymbol; +import "AnotherFile.sol" as SomeSymbol; +import { symbol1 as alias, symbol2 } from "File.sol"; +import { symbol1 as alias, symbol2 } from "File.sol"; +import { + symbol1 as alias1, + symbol2 as alias2, + symbol3 as alias3, + symbol4 +} from "File2.sol"; +import { + symbol1 as alias1, + symbol2 as alias2, + symbol3 as alias3, + symbol4 +} from "File2.sol"; diff --git a/forge-fmt/testdata/ImportDirective/fmt.sol b/forge-fmt/testdata/ImportDirective/fmt.sol new file mode 100644 index 000000000..4915b8ab2 --- /dev/null +++ b/forge-fmt/testdata/ImportDirective/fmt.sol @@ -0,0 +1,20 @@ +import "SomeFile.sol"; +import "SomeFile.sol"; +import "SomeFile.sol" as SomeOtherFile; +import "SomeFile.sol" as SomeOtherFile; +import "AnotherFile.sol" as SomeSymbol; +import "AnotherFile.sol" as SomeSymbol; +import {symbol1 as alias, symbol2} from "File.sol"; +import {symbol1 as alias, symbol2} from "File.sol"; +import { + symbol1 as alias1, + symbol2 as alias2, + symbol3 as alias3, + symbol4 +} from "File2.sol"; +import { + symbol1 as alias1, + symbol2 as alias2, + symbol3 as alias3, + symbol4 +} from "File2.sol"; diff --git a/forge-fmt/testdata/ImportDirective/original.sol b/forge-fmt/testdata/ImportDirective/original.sol new file mode 100644 index 000000000..0e18e10c1 --- /dev/null +++ b/forge-fmt/testdata/ImportDirective/original.sol @@ -0,0 +1,10 @@ +import "SomeFile.sol"; +import 'SomeFile.sol'; +import "SomeFile.sol" as SomeOtherFile; +import 'SomeFile.sol' as SomeOtherFile; +import * as SomeSymbol from "AnotherFile.sol"; +import * as SomeSymbol from 'AnotherFile.sol'; +import {symbol1 as alias, symbol2} from "File.sol"; +import {symbol1 as alias, symbol2} from 'File.sol'; +import {symbol1 as alias1, symbol2 as alias2, symbol3 as alias3, symbol4} from "File2.sol"; +import {symbol1 as alias1, symbol2 as alias2, symbol3 as alias3, symbol4} from 'File2.sol'; diff --git a/forge-fmt/testdata/ImportDirective/preserve-quote.fmt.sol b/forge-fmt/testdata/ImportDirective/preserve-quote.fmt.sol new file mode 100644 index 000000000..d1bf9852c --- /dev/null +++ b/forge-fmt/testdata/ImportDirective/preserve-quote.fmt.sol @@ -0,0 +1,21 @@ +// config: quote_style = "preserve" +import "SomeFile.sol"; +import 'SomeFile.sol'; +import "SomeFile.sol" as SomeOtherFile; +import 'SomeFile.sol' as SomeOtherFile; +import "AnotherFile.sol" as SomeSymbol; +import 'AnotherFile.sol' as SomeSymbol; +import {symbol1 as alias, symbol2} from "File.sol"; +import {symbol1 as alias, symbol2} from 'File.sol'; +import { + symbol1 as alias1, + symbol2 as alias2, + symbol3 as alias3, + symbol4 +} from "File2.sol"; +import { + symbol1 as alias1, + symbol2 as alias2, + symbol3 as alias3, + symbol4 +} from 'File2.sol'; diff --git a/forge-fmt/testdata/ImportDirective/single-quote.fmt.sol b/forge-fmt/testdata/ImportDirective/single-quote.fmt.sol new file mode 100644 index 000000000..10449e079 --- /dev/null +++ b/forge-fmt/testdata/ImportDirective/single-quote.fmt.sol @@ -0,0 +1,21 @@ +// config: quote_style = "single" +import 'SomeFile.sol'; +import 'SomeFile.sol'; +import 'SomeFile.sol' as SomeOtherFile; +import 'SomeFile.sol' as SomeOtherFile; +import 'AnotherFile.sol' as SomeSymbol; +import 'AnotherFile.sol' as SomeSymbol; +import {symbol1 as alias, symbol2} from 'File.sol'; +import {symbol1 as alias, symbol2} from 'File.sol'; +import { + symbol1 as alias1, + symbol2 as alias2, + symbol3 as alias3, + symbol4 +} from 'File2.sol'; +import { + symbol1 as alias1, + symbol2 as alias2, + symbol3 as alias3, + symbol4 +} from 'File2.sol'; diff --git a/forge-fmt/testdata/InlineDisable/fmt.sol b/forge-fmt/testdata/InlineDisable/fmt.sol new file mode 100644 index 000000000..03979bc18 --- /dev/null +++ b/forge-fmt/testdata/InlineDisable/fmt.sol @@ -0,0 +1,491 @@ +pragma solidity ^0.5.2; + +// forgefmt: disable-next-line +pragma solidity ^0.5.2; + +import { + symbol1 as alias1, + symbol2 as alias2, + symbol3 as alias3, + symbol4 +} from "File2.sol"; + +// forgefmt: disable-next-line +import {symbol1 as alias1, symbol2 as alias2, symbol3 as alias3, symbol4} from 'File2.sol'; + +enum States { + State1, + State2, + State3, + State4, + State5, + State6, + State7, + State8, + State9 +} + +// forgefmt: disable-next-line +enum States { State1, State2, State3, State4, State5, State6, State7, State8, State9 } + +// forgefmt: disable-next-line +bytes32 constant private BYTES = 0x035aff83d86937d35b32e04f0ddc6ff469290eef2f1b692d8a815c89404d4749; + +// forgefmt: disable-start + +// comment1 + + +// comment2 +/* comment 3 */ /* + comment4 + */ // comment 5 + + +/// Doccomment 1 + /// Doccomment 2 + +/** + * docccoment 3 + */ + + +// forgefmt: disable-end + +// forgefmt: disable-start + +function test1() {} + +function test2() {} + +// forgefmt: disable-end + +contract Constructors is Ownable, Changeable { + //forgefmt: disable-next-item + function Constructors(variable1) public Changeable(variable1) Ownable() onlyOwner { + } + + //forgefmt: disable-next-item + constructor(variable1, variable2, variable3, variable4, variable5, variable6, variable7) public Changeable(variable1, variable2, variable3, variable4, variable5, variable6, variable7) Ownable() onlyOwner {} +} + +function test() { + uint256 pi_approx = 666 / 212; + uint256 pi_approx = /* forgefmt: disable-start */ 666 / 212; /* forgefmt: disable-end */ + + // forgefmt: disable-next-item + uint256 pi_approx = 666 / + 212; + + uint256 test_postfix = 1; // forgefmt: disable-start + // comment1 + // comment2 + // comment3 + // forgefmt: disable-end +} + +// forgefmt: disable-next-item +function testFunc(uint256 num, bytes32 data , address receiver) + public payable attr1 Cool( "hello" ) {} + +function testAttrs(uint256 num, bytes32 data, address receiver) + // forgefmt: disable-next-line + public payable attr1 Cool( "hello" ) +{} + +// forgefmt: disable-next-line +function testParams(uint256 num, bytes32 data , address receiver) + public + payable + attr1 + Cool("hello") +{} + +function testDoWhile() external { + //forgefmt: disable-start + uint256 i; + do { "test"; } while (i != 0); + + do + {} + while + ( +i != 0); + + bool someVeryVeryLongCondition; + do { "test"; } while( + someVeryVeryLongCondition && !someVeryVeryLongCondition && +!someVeryVeryLongCondition && +someVeryVeryLongCondition); + + do i++; while(i < 10); + + do do i++; while (i < 30); while(i < 20); + //forgefmt: disable-end +} + +function forStatement() { + //forgefmt: disable-start + for + (uint256 i1 + ; i1 < 10; i1++) + { + i1++; + } + + uint256 i2; + for(++i2;i2<10;i2++) + + {} + + uint256 veryLongVariableName = 1000; + for ( uint256 i3; i3 < 10 + && veryLongVariableName>999 && veryLongVariableName< 1001 + ; i3++) + { i3 ++ ; } + + for (type(uint256).min;;) {} + + for (;;) { "test" ; } + + for (uint256 i4; i4< 10; i4++) i4++; + + for (uint256 i5; ;) + for (uint256 i6 = 10; i6 > i5; i6--) + i5++; + //forgefmt: disable-end +} + +function callArgTest() { + //forgefmt: disable-start + target.run{ gas: gasleft(), value: 1 wei }; + + target.run{gas:1,value:0x00}(); + + target.run{ + gas : 1000, + value: 1 ether + } (); + + target.run{ gas: estimate(), + value: value(1) }(); + + target.run { value: + value(1 ether), gas: veryAndVeryLongNameOfSomeGasEstimateFunction() } (); + + target.run /* comment 1 */ { value: /* comment2 */ 1 }; + + target.run { /* comment3 */ value: 1, // comment4 + gas: gasleft()}; + + target.run { + // comment5 + value: 1, + // comment6 + gas: gasleft()}; + //forgefmt: disable-end +} + +function ifTest() { + // forgefmt: disable-start + if (condition) + execute(); + else + executeElse(); + // forgefmt: disable-end + + /* forgefmt: disable-next-line */ + if (condition && anotherLongCondition ) { + execute(); + } +} + +function yulTest() { + // forgefmt: disable-start + assembly { + let payloadSize := sub(calldatasize(), 4) + calldatacopy(0, 4, payloadSize) + mstore(payloadSize, shl(96, caller())) + + let result := + delegatecall(gas(), moduleImpl, 0, add(payloadSize, 20), 0, 0) + + returndatacopy(0, 0, returndatasize()) + + switch result + case 0 { revert(0, returndatasize()) } + default { return(0, returndatasize()) } + } + // forgefmt: disable-end +} + +function literalTest() { + // forgefmt: disable-start + + true; + 0x123_456; + .1; + "foobar"; + hex"001122FF"; + 0xc02aaa39b223Fe8D0A0e5C4F27ead9083c756Cc2; + // forgefmt: disable-end + + // forgefmt: disable-next-line + bytes memory bytecode = hex"ff"; +} + +function returnTest() { + // forgefmt: disable-start + if (val == 0) { + return // return single 1 + 0x00; + } + + if (val == 1) { return + 1; } + + if (val == 2) { + return 3 + - + 1; + } + + if (val == 4) { + /* return single 2 */ return 2** // return single 3 + 3 // return single 4 + ; + } + + return value(); // return single 5 + return ; + return /* return mul 4 */ + ( + 987654321, 1234567890,/* return mul 5 */ false); + // forgefmt: disable-end +} + +function namedFuncCall() { + // forgefmt: disable-start + SimpleStruct memory simple = SimpleStruct({ val: 0 }); + + ComplexStruct memory complex = ComplexStruct({ val: 1, anotherVal: 2, flag: true, timestamp: block.timestamp }); + + StructWithAVeryLongNameThatExceedsMaximumLengthThatIsAllowedForFormatting memory long = StructWithAVeryLongNameThatExceedsMaximumLengthThatIsAllowedForFormatting({ whyNameSoLong: "dunno" }); + + SimpleStruct memory simple2 = SimpleStruct( + { // comment1 + /* comment2 */ val : /* comment3 */ 0 + + } + ); + // forgefmt: disable-end +} + +function revertTest() { + // forgefmt: disable-start + revert ({ }); + + revert EmptyError({}); + + revert SimpleError({ val: 0 }); + + revert ComplexError( + { + val: 0, + ts: block.timestamp, + message: "some reason" + }); + + revert SomeVeryVeryVeryLongErrorNameWithNamedArgumentsThatExceedsMaximumLength({ val: 0, ts: 0x00, message: "something unpredictable happened that caused execution to revert"}); + + revert // comment1 + ({}); + // forgefmt: disable-end +} + +function testTernary() { + // forgefmt: disable-start + bool condition; + bool someVeryVeryLongConditionUsedInTheTernaryExpression; + + condition ? 0 : 1; + + someVeryVeryLongConditionUsedInTheTernaryExpression ? 1234567890 : 987654321; + + condition /* comment1 */ ? /* comment2 */ 1001 /* comment3 */ : /* comment4 */ 2002; + + // comment5 + someVeryVeryLongConditionUsedInTheTernaryExpression ? 1 + // comment6 + : + // comment7 + 0; // comment8 + // forgefmt: disable-end +} + +function thisTest() { + // forgefmt: disable-start + this.someFunc(); + this.someVeryVeryVeryLongVariableNameThatWillBeAccessedByThisKeyword(); + this // comment1 + .someVeryVeryVeryLongVariableNameThatWillBeAccessedByThisKeyword(); + address(this).balance; + + address thisAddress = address( + // comment2 + /* comment3 */ this // comment 4 + ); + // forgefmt: disable-end +} + +function tryTest() { + // forgefmt: disable-start + try unknown.empty() {} catch {} + + try unknown.lookup() returns (uint256) {} catch Error(string memory) {} + + try unknown.lookup() returns (uint256) {} catch Error(string memory) {} catch (bytes memory) {} + + try unknown + .lookup() returns (uint256 + ) { + } catch ( bytes memory ){} + + try unknown.empty() { + unknown.doSomething(); + } catch { + unknown.handleError(); + } + + try unknown.empty() { + unknown.doSomething(); + } catch Error(string memory) {} + catch Panic(uint) {} + catch { + unknown.handleError(); + } + + try unknown.lookupMultipleValues() returns (uint256, uint256, uint256, uint256, uint256) {} catch Error(string memory) {} catch {} + + try unknown.lookupMultipleValues() returns (uint256, uint256, uint256, uint256, uint256) { + unknown.doSomething(); + } + catch Error(string memory) { + unknown.handleError(); + } + catch {} + // forgefmt: disable-end +} + +function testArray() { + // forgefmt: disable-start + msg.data[ + // comment1 + 4:]; + msg.data[ + : /* comment2 */ msg.data.length // comment3 + ]; + msg.data[ + // comment4 + 4 // comment5 + :msg.data.length /* comment6 */]; + // forgefmt: disable-end +} + +function testUnit() { + // forgefmt: disable-start + uint256 timestamp; + timestamp = 1 seconds; + timestamp = 1 minutes; + timestamp = 1 hours; + timestamp = 1 days; + timestamp = 1 weeks; + + uint256 value; + value = 1 wei; + value = 1 gwei; + value = 1 ether; + + uint256 someVeryVeryVeryLongVaribleNameForTheMultiplierForEtherValue; + + value = someVeryVeryVeryLongVaribleNameForTheMultiplierForEtherValue * 1 /* comment1 */ ether; // comment2 + + value = 1 // comment3 + // comment4 + ether; // comment5 + // forgefmt: disable-end +} + +contract UsingExampleContract { + // forgefmt: disable-start + using UsingExampleLibrary for * ; + using UsingExampleLibrary for uint; + using Example.UsingExampleLibrary for uint; + using { M.g, M.f} for uint; + using UsingExampleLibrary for uint global; + using { These, Are, MultipleLibraries, ThatNeedToBePut, OnSeparateLines } for uint; + using { This.isareally.longmember.access.expression.that.needs.to.besplit.into.lines } for uint; + // forgefmt: disable-end +} + +function testAssignment() { + // forgefmt: disable-start + (, uint256 second) = (1, 2); + (uint256 listItem001) = 1; + (uint256 listItem002, uint256 listItem003) = (10, 20); + (uint256 listItem004, uint256 listItem005, uint256 listItem006) = + (10, 20, 30); + // forgefmt: disable-end +} + +function testWhile() { + // forgefmt: disable-start + uint256 i1; + while ( i1 < 10 ) { + i1++; + } + + while (i1<10) i1++; + + while (i1<10) + while (i1<10) + i1++; + + uint256 i2; + while ( i2 < 10) { i2++; } + + uint256 i3; while ( + i3 < 10 + ) { i3++; } + + uint256 i4; while (i4 < 10) + + { i4 ++ ;} + + uint256 someLongVariableName; + while ( + someLongVariableName < 10 && someLongVariableName < 11 && someLongVariableName < 12 + ) { someLongVariableName ++; } someLongVariableName++; + // forgefmt: disable-end +} + +function testLine() {} + +function /* forgefmt: disable-line */ testLine( ) { } + +function testLine() {} + +function testLine( ) { } // forgefmt: disable-line + +// forgefmt: disable-start + + type Hello is uint256; + +error + TopLevelCustomError(); + error TopLevelCustomErrorWithArg(uint x) ; +error TopLevelCustomErrorArgWithoutName (string); + + event Event1(uint256 indexed a, uint256 indexed a, uint256 indexed a, uint256 indexed a, uint256 indexed a, uint256 indexed a, uint256 indexed a, uint256 indexed a, uint256 indexed a, uint256 indexed a); + +// forgefmt: disable-stop diff --git a/forge-fmt/testdata/InlineDisable/original.sol b/forge-fmt/testdata/InlineDisable/original.sol new file mode 100644 index 000000000..617da7719 --- /dev/null +++ b/forge-fmt/testdata/InlineDisable/original.sol @@ -0,0 +1,469 @@ +pragma solidity ^0.5.2; + +// forgefmt: disable-next-line +pragma solidity ^0.5.2; + +import {symbol1 as alias1, symbol2 as alias2, symbol3 as alias3, symbol4} from 'File2.sol'; + +// forgefmt: disable-next-line +import {symbol1 as alias1, symbol2 as alias2, symbol3 as alias3, symbol4} from 'File2.sol'; + +enum States { State1, State2, State3, State4, State5, State6, State7, State8, State9 } + +// forgefmt: disable-next-line +enum States { State1, State2, State3, State4, State5, State6, State7, State8, State9 } + +// forgefmt: disable-next-line +bytes32 constant private BYTES = 0x035aff83d86937d35b32e04f0ddc6ff469290eef2f1b692d8a815c89404d4749; + +// forgefmt: disable-start + +// comment1 + + +// comment2 +/* comment 3 */ /* + comment4 + */ // comment 5 + + +/// Doccomment 1 + /// Doccomment 2 + +/** + * docccoment 3 + */ + + +// forgefmt: disable-end + +// forgefmt: disable-start + +function test1() {} + +function test2() {} + +// forgefmt: disable-end + +contract Constructors is Ownable, Changeable { + //forgefmt: disable-next-item + function Constructors(variable1) public Changeable(variable1) Ownable() onlyOwner { + } + + //forgefmt: disable-next-item + constructor(variable1, variable2, variable3, variable4, variable5, variable6, variable7) public Changeable(variable1, variable2, variable3, variable4, variable5, variable6, variable7) Ownable() onlyOwner {} +} + +function test() { + uint256 pi_approx = 666 / 212; + uint256 pi_approx = /* forgefmt: disable-start */ 666 / 212; /* forgefmt: disable-end */ + + // forgefmt: disable-next-item + uint256 pi_approx = 666 / + 212; + + uint256 test_postfix = 1; // forgefmt: disable-start + // comment1 + // comment2 + // comment3 + // forgefmt: disable-end +} + +// forgefmt: disable-next-item +function testFunc(uint256 num, bytes32 data , address receiver) + public payable attr1 Cool( "hello" ) {} + +function testAttrs(uint256 num, bytes32 data , address receiver) + // forgefmt: disable-next-line + public payable attr1 Cool( "hello" ) {} + +// forgefmt: disable-next-line +function testParams(uint256 num, bytes32 data , address receiver) + public payable attr1 Cool( "hello" ) {} + + +function testDoWhile() external { + //forgefmt: disable-start + uint256 i; + do { "test"; } while (i != 0); + + do + {} + while + ( +i != 0); + + bool someVeryVeryLongCondition; + do { "test"; } while( + someVeryVeryLongCondition && !someVeryVeryLongCondition && +!someVeryVeryLongCondition && +someVeryVeryLongCondition); + + do i++; while(i < 10); + + do do i++; while (i < 30); while(i < 20); + //forgefmt: disable-end +} + +function forStatement() { + //forgefmt: disable-start + for + (uint256 i1 + ; i1 < 10; i1++) + { + i1++; + } + + uint256 i2; + for(++i2;i2<10;i2++) + + {} + + uint256 veryLongVariableName = 1000; + for ( uint256 i3; i3 < 10 + && veryLongVariableName>999 && veryLongVariableName< 1001 + ; i3++) + { i3 ++ ; } + + for (type(uint256).min;;) {} + + for (;;) { "test" ; } + + for (uint256 i4; i4< 10; i4++) i4++; + + for (uint256 i5; ;) + for (uint256 i6 = 10; i6 > i5; i6--) + i5++; + //forgefmt: disable-end +} + +function callArgTest() { + //forgefmt: disable-start + target.run{ gas: gasleft(), value: 1 wei }; + + target.run{gas:1,value:0x00}(); + + target.run{ + gas : 1000, + value: 1 ether + } (); + + target.run{ gas: estimate(), + value: value(1) }(); + + target.run { value: + value(1 ether), gas: veryAndVeryLongNameOfSomeGasEstimateFunction() } (); + + target.run /* comment 1 */ { value: /* comment2 */ 1 }; + + target.run { /* comment3 */ value: 1, // comment4 + gas: gasleft()}; + + target.run { + // comment5 + value: 1, + // comment6 + gas: gasleft()}; + //forgefmt: disable-end +} + +function ifTest() { + // forgefmt: disable-start + if (condition) + execute(); + else + executeElse(); + // forgefmt: disable-end + + /* forgefmt: disable-next-line */ + if (condition && anotherLongCondition ) { + execute(); } +} + +function yulTest() { + // forgefmt: disable-start + assembly { + let payloadSize := sub(calldatasize(), 4) + calldatacopy(0, 4, payloadSize) + mstore(payloadSize, shl(96, caller())) + + let result := + delegatecall(gas(), moduleImpl, 0, add(payloadSize, 20), 0, 0) + + returndatacopy(0, 0, returndatasize()) + + switch result + case 0 { revert(0, returndatasize()) } + default { return(0, returndatasize()) } + } + // forgefmt: disable-end +} + +function literalTest() { + // forgefmt: disable-start + + true; + 0x123_456; + .1; + "foobar"; + hex"001122FF"; + 0xc02aaa39b223Fe8D0A0e5C4F27ead9083c756Cc2; + // forgefmt: disable-end + + // forgefmt: disable-next-line + bytes memory bytecode = + hex"ff"; +} + +function returnTest() { + // forgefmt: disable-start + if (val == 0) { + return // return single 1 + 0x00; + } + + if (val == 1) { return + 1; } + + if (val == 2) { + return 3 + - + 1; + } + + if (val == 4) { + /* return single 2 */ return 2** // return single 3 + 3 // return single 4 + ; + } + + return value(); // return single 5 + return ; + return /* return mul 4 */ + ( + 987654321, 1234567890,/* return mul 5 */ false); + // forgefmt: disable-end +} + +function namedFuncCall() { + // forgefmt: disable-start + SimpleStruct memory simple = SimpleStruct({ val: 0 }); + + ComplexStruct memory complex = ComplexStruct({ val: 1, anotherVal: 2, flag: true, timestamp: block.timestamp }); + + StructWithAVeryLongNameThatExceedsMaximumLengthThatIsAllowedForFormatting memory long = StructWithAVeryLongNameThatExceedsMaximumLengthThatIsAllowedForFormatting({ whyNameSoLong: "dunno" }); + + SimpleStruct memory simple2 = SimpleStruct( + { // comment1 + /* comment2 */ val : /* comment3 */ 0 + + } + ); + // forgefmt: disable-end +} + +function revertTest() { + // forgefmt: disable-start + revert ({ }); + + revert EmptyError({}); + + revert SimpleError({ val: 0 }); + + revert ComplexError( + { + val: 0, + ts: block.timestamp, + message: "some reason" + }); + + revert SomeVeryVeryVeryLongErrorNameWithNamedArgumentsThatExceedsMaximumLength({ val: 0, ts: 0x00, message: "something unpredictable happened that caused execution to revert"}); + + revert // comment1 + ({}); + // forgefmt: disable-end +} + +function testTernary() { + // forgefmt: disable-start + bool condition; + bool someVeryVeryLongConditionUsedInTheTernaryExpression; + + condition ? 0 : 1; + + someVeryVeryLongConditionUsedInTheTernaryExpression ? 1234567890 : 987654321; + + condition /* comment1 */ ? /* comment2 */ 1001 /* comment3 */ : /* comment4 */ 2002; + + // comment5 + someVeryVeryLongConditionUsedInTheTernaryExpression ? 1 + // comment6 + : + // comment7 + 0; // comment8 + // forgefmt: disable-end +} + +function thisTest() { + // forgefmt: disable-start + this.someFunc(); + this.someVeryVeryVeryLongVariableNameThatWillBeAccessedByThisKeyword(); + this // comment1 + .someVeryVeryVeryLongVariableNameThatWillBeAccessedByThisKeyword(); + address(this).balance; + + address thisAddress = address( + // comment2 + /* comment3 */ this // comment 4 + ); + // forgefmt: disable-end +} + +function tryTest() { + // forgefmt: disable-start + try unknown.empty() {} catch {} + + try unknown.lookup() returns (uint256) {} catch Error(string memory) {} + + try unknown.lookup() returns (uint256) {} catch Error(string memory) {} catch (bytes memory) {} + + try unknown + .lookup() returns (uint256 + ) { + } catch ( bytes memory ){} + + try unknown.empty() { + unknown.doSomething(); + } catch { + unknown.handleError(); + } + + try unknown.empty() { + unknown.doSomething(); + } catch Error(string memory) {} + catch Panic(uint) {} + catch { + unknown.handleError(); + } + + try unknown.lookupMultipleValues() returns (uint256, uint256, uint256, uint256, uint256) {} catch Error(string memory) {} catch {} + + try unknown.lookupMultipleValues() returns (uint256, uint256, uint256, uint256, uint256) { + unknown.doSomething(); + } + catch Error(string memory) { + unknown.handleError(); + } + catch {} + // forgefmt: disable-end +} + +function testArray() { + // forgefmt: disable-start + msg.data[ + // comment1 + 4:]; + msg.data[ + : /* comment2 */ msg.data.length // comment3 + ]; + msg.data[ + // comment4 + 4 // comment5 + :msg.data.length /* comment6 */]; + // forgefmt: disable-end +} + +function testUnit() { + // forgefmt: disable-start + uint256 timestamp; + timestamp = 1 seconds; + timestamp = 1 minutes; + timestamp = 1 hours; + timestamp = 1 days; + timestamp = 1 weeks; + + uint256 value; + value = 1 wei; + value = 1 gwei; + value = 1 ether; + + uint256 someVeryVeryVeryLongVaribleNameForTheMultiplierForEtherValue; + + value = someVeryVeryVeryLongVaribleNameForTheMultiplierForEtherValue * 1 /* comment1 */ ether; // comment2 + + value = 1 // comment3 + // comment4 + ether; // comment5 + // forgefmt: disable-end +} + +contract UsingExampleContract { + // forgefmt: disable-start + using UsingExampleLibrary for * ; + using UsingExampleLibrary for uint; + using Example.UsingExampleLibrary for uint; + using { M.g, M.f} for uint; + using UsingExampleLibrary for uint global; + using { These, Are, MultipleLibraries, ThatNeedToBePut, OnSeparateLines } for uint; + using { This.isareally.longmember.access.expression.that.needs.to.besplit.into.lines } for uint; + // forgefmt: disable-end +} + +function testAssignment() { + // forgefmt: disable-start + (, uint256 second) = (1, 2); + (uint256 listItem001) = 1; + (uint256 listItem002, uint256 listItem003) = (10, 20); + (uint256 listItem004, uint256 listItem005, uint256 listItem006) = + (10, 20, 30); + // forgefmt: disable-end +} + +function testWhile() { + // forgefmt: disable-start + uint256 i1; + while ( i1 < 10 ) { + i1++; + } + + while (i1<10) i1++; + + while (i1<10) + while (i1<10) + i1++; + + uint256 i2; + while ( i2 < 10) { i2++; } + + uint256 i3; while ( + i3 < 10 + ) { i3++; } + + uint256 i4; while (i4 < 10) + + { i4 ++ ;} + + uint256 someLongVariableName; + while ( + someLongVariableName < 10 && someLongVariableName < 11 && someLongVariableName < 12 + ) { someLongVariableName ++; } someLongVariableName++; + // forgefmt: disable-end +} + +function testLine( ) { } +function /* forgefmt: disable-line */ testLine( ) { } +function testLine( ) { } +function testLine( ) { } // forgefmt: disable-line + +// forgefmt: disable-start + + type Hello is uint256; + +error + TopLevelCustomError(); + error TopLevelCustomErrorWithArg(uint x) ; +error TopLevelCustomErrorArgWithoutName (string); + + event Event1(uint256 indexed a, uint256 indexed a, uint256 indexed a, uint256 indexed a, uint256 indexed a, uint256 indexed a, uint256 indexed a, uint256 indexed a, uint256 indexed a, uint256 indexed a); + +// forgefmt: disable-stop diff --git a/forge-fmt/testdata/IntTypes/fmt.sol b/forge-fmt/testdata/IntTypes/fmt.sol new file mode 100644 index 000000000..b244d0281 --- /dev/null +++ b/forge-fmt/testdata/IntTypes/fmt.sol @@ -0,0 +1,24 @@ +contract Contract { + uint256 constant UINT256_IMPL = 0; + uint8 constant UINT8 = 1; + uint128 constant UINT128 = 2; + uint256 constant UINT256_EXPL = 3; + + int256 constant INT256_IMPL = 4; + int8 constant INT8 = 5; + int128 constant INT128 = 6; + int256 constant INT256_EXPL = 7; + + function test( + uint256 uint256_impl, + uint8 uint8_var, + uint128 uint128_var, + uint256 uint256_expl, + int256 int256_impl, + int8 int8_var, + int128 int128_var, + int256 int256_expl + ) public { + // do something + } +} diff --git a/forge-fmt/testdata/IntTypes/original.sol b/forge-fmt/testdata/IntTypes/original.sol new file mode 100644 index 000000000..2990aadbb --- /dev/null +++ b/forge-fmt/testdata/IntTypes/original.sol @@ -0,0 +1,24 @@ +contract Contract { + uint constant UINT256_IMPL = 0; + uint8 constant UINT8 = 1; + uint128 constant UINT128 = 2; + uint256 constant UINT256_EXPL = 3; + + int constant INT256_IMPL = 4; + int8 constant INT8 = 5; + int128 constant INT128 = 6; + int256 constant INT256_EXPL = 7; + + function test( + uint uint256_impl, + uint8 uint8_var, + uint128 uint128_var, + uint256 uint256_expl, + int int256_impl, + int8 int8_var, + int128 int128_var, + int256 int256_expl + ) public { + // do something + } +} diff --git a/forge-fmt/testdata/IntTypes/preserve.fmt.sol b/forge-fmt/testdata/IntTypes/preserve.fmt.sol new file mode 100644 index 000000000..70d780d89 --- /dev/null +++ b/forge-fmt/testdata/IntTypes/preserve.fmt.sol @@ -0,0 +1,25 @@ +// config: int_types = "preserve" +contract Contract { + uint constant UINT256_IMPL = 0; + uint8 constant UINT8 = 1; + uint128 constant UINT128 = 2; + uint256 constant UINT256_EXPL = 3; + + int constant INT256_IMPL = 4; + int8 constant INT8 = 5; + int128 constant INT128 = 6; + int256 constant INT256_EXPL = 7; + + function test( + uint uint256_impl, + uint8 uint8_var, + uint128 uint128_var, + uint256 uint256_expl, + int int256_impl, + int8 int8_var, + int128 int128_var, + int256 int256_expl + ) public { + // do something + } +} diff --git a/forge-fmt/testdata/IntTypes/short.fmt.sol b/forge-fmt/testdata/IntTypes/short.fmt.sol new file mode 100644 index 000000000..45064d41e --- /dev/null +++ b/forge-fmt/testdata/IntTypes/short.fmt.sol @@ -0,0 +1,25 @@ +// config: int_types = "short" +contract Contract { + uint constant UINT256_IMPL = 0; + uint8 constant UINT8 = 1; + uint128 constant UINT128 = 2; + uint constant UINT256_EXPL = 3; + + int constant INT256_IMPL = 4; + int8 constant INT8 = 5; + int128 constant INT128 = 6; + int constant INT256_EXPL = 7; + + function test( + uint uint256_impl, + uint8 uint8_var, + uint128 uint128_var, + uint uint256_expl, + int int256_impl, + int8 int8_var, + int128 int128_var, + int int256_expl + ) public { + // do something + } +} diff --git a/forge-fmt/testdata/LiteralExpression/fmt.sol b/forge-fmt/testdata/LiteralExpression/fmt.sol new file mode 100644 index 000000000..7fa6878e5 --- /dev/null +++ b/forge-fmt/testdata/LiteralExpression/fmt.sol @@ -0,0 +1,59 @@ +contract LiteralExpressions { + function test() external { + // bool literals + true; + false; + /* comment1 */ + true; /* comment2 */ + // comment3 + false; // comment4 + + // number literals + 1; + 123_000; + 1_2e345_678; + -1; + 2e-10; + // comment5 + /* comment6 */ + -1; /* comment7 */ + + // hex number literals + 0x00; + 0x123_456; + 0x2eff_abde; + + // rational number literals + 0.1; + 1.3; + 2.5e1; + + // string literals + ""; + "foobar"; + "foo" // comment8 + " bar"; + // comment9 + "\ +some words"; /* comment10 */ + unicode"Hello 😃"; + + // quoted strings + 'hello "world"'; + "hello 'world'"; + "hello \'world\'"; + "hello \"world\""; + "hello \"world\""; + "hello \'world\'"; + + // hex literals + hex"001122FF"; + hex"001122FF"; + hex"00112233" hex"44556677"; + + // address literals + 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; + // non checksummed address + 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; + } +} diff --git a/forge-fmt/testdata/LiteralExpression/original.sol b/forge-fmt/testdata/LiteralExpression/original.sol new file mode 100644 index 000000000..5c559531d --- /dev/null +++ b/forge-fmt/testdata/LiteralExpression/original.sol @@ -0,0 +1,58 @@ +contract LiteralExpressions { + function test() external { + // bool literals + true; + false; + /* comment1 */ true /* comment2 */; + // comment3 + false; // comment4 + + // number literals + 1; + 123_000; + 1_2e345_678; + -1; + 2e-10; + // comment5 + /* comment6 */ -1 /* comment7 */; + + // hex number literals + 0x00; + 0x123_456; + 0x2eff_abde; + + // rational number literals + .1; + 1.3; + 2.5e1; + + // string literals + ""; + "foobar"; + "foo" // comment8 + " bar"; + // comment9 + "\ +some words" /* comment10 */; + unicode"Hello 😃"; + + // quoted strings + 'hello "world"'; + "hello 'world'"; + 'hello \'world\''; + "hello \"world\""; + 'hello \"world\"'; + "hello \'world\'"; + + + // hex literals + hex"001122FF"; + hex'0011_22_FF'; + hex"00112233" hex"44556677"; + + // address literals + 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; + // non checksummed address + 0xc02aaa39b223Fe8D0A0e5C4F27ead9083c756Cc2; + } +} diff --git a/forge-fmt/testdata/LiteralExpression/preserve-quote.fmt.sol b/forge-fmt/testdata/LiteralExpression/preserve-quote.fmt.sol new file mode 100644 index 000000000..3d9490804 --- /dev/null +++ b/forge-fmt/testdata/LiteralExpression/preserve-quote.fmt.sol @@ -0,0 +1,60 @@ +// config: quote_style = "preserve" +contract LiteralExpressions { + function test() external { + // bool literals + true; + false; + /* comment1 */ + true; /* comment2 */ + // comment3 + false; // comment4 + + // number literals + 1; + 123_000; + 1_2e345_678; + -1; + 2e-10; + // comment5 + /* comment6 */ + -1; /* comment7 */ + + // hex number literals + 0x00; + 0x123_456; + 0x2eff_abde; + + // rational number literals + 0.1; + 1.3; + 2.5e1; + + // string literals + ""; + "foobar"; + "foo" // comment8 + " bar"; + // comment9 + "\ +some words"; /* comment10 */ + unicode"Hello 😃"; + + // quoted strings + 'hello "world"'; + "hello 'world'"; + 'hello \'world\''; + "hello \"world\""; + 'hello \"world\"'; + "hello \'world\'"; + + // hex literals + hex"001122FF"; + hex'001122FF'; + hex"00112233" hex"44556677"; + + // address literals + 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; + // non checksummed address + 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; + } +} diff --git a/forge-fmt/testdata/LiteralExpression/single-quote.fmt.sol b/forge-fmt/testdata/LiteralExpression/single-quote.fmt.sol new file mode 100644 index 000000000..cdc67a2c6 --- /dev/null +++ b/forge-fmt/testdata/LiteralExpression/single-quote.fmt.sol @@ -0,0 +1,60 @@ +// config: quote_style = "single" +contract LiteralExpressions { + function test() external { + // bool literals + true; + false; + /* comment1 */ + true; /* comment2 */ + // comment3 + false; // comment4 + + // number literals + 1; + 123_000; + 1_2e345_678; + -1; + 2e-10; + // comment5 + /* comment6 */ + -1; /* comment7 */ + + // hex number literals + 0x00; + 0x123_456; + 0x2eff_abde; + + // rational number literals + 0.1; + 1.3; + 2.5e1; + + // string literals + ''; + 'foobar'; + 'foo' // comment8 + ' bar'; + // comment9 + '\ +some words'; /* comment10 */ + unicode'Hello 😃'; + + // quoted strings + 'hello "world"'; + "hello 'world'"; + 'hello \'world\''; + 'hello \"world\"'; + 'hello \"world\"'; + 'hello \'world\''; + + // hex literals + hex'001122FF'; + hex'001122FF'; + hex'00112233' hex'44556677'; + + // address literals + 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; + // non checksummed address + 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; + } +} diff --git a/forge-fmt/testdata/MappingType/fmt.sol b/forge-fmt/testdata/MappingType/fmt.sol new file mode 100644 index 000000000..7f6297cff --- /dev/null +++ b/forge-fmt/testdata/MappingType/fmt.sol @@ -0,0 +1,35 @@ +// config: line_length = 40 +contract X { + type Y is bytes32; +} + +type SomeVeryLongTypeName is uint256; + +contract Mapping { + mapping(uint256 => X.Y) mapping1; + mapping( + uint256 key => uint256 value + ) mapping2; + mapping( + uint256 veryLongKeyName + => uint256 veryLongValueName + ) mapping3; + mapping( + string anotherVeryLongKeyName + => uint256 anotherVeryLongValueName + ) mapping4; + mapping( + SomeVeryLongTypeName anotherVeryLongKeyName + => uint256 anotherVeryLongValueName + ) mapping5; + + mapping( + // comment1 + uint256 key => uint256 value + // comment2 + ) mapping6; + mapping( /* comment3 */ + uint256 /* comment4 */ key /* comment5 */ + => /* comment6 */ uint256 /* comment7 */ value /* comment8 */ /* comment9 */ + ) /* comment10 */ mapping7; +} diff --git a/forge-fmt/testdata/MappingType/original.sol b/forge-fmt/testdata/MappingType/original.sol new file mode 100644 index 000000000..58ebb134d --- /dev/null +++ b/forge-fmt/testdata/MappingType/original.sol @@ -0,0 +1,23 @@ +contract X { + type Y is bytes32; +} + +type SomeVeryLongTypeName is uint256; + +contract Mapping { + mapping(uint256 => X.Y) mapping1; + mapping(uint256 key => uint256 value) mapping2; + mapping(uint256 veryLongKeyName => uint256 veryLongValueName) mapping3; + mapping(string anotherVeryLongKeyName => uint256 anotherVeryLongValueName) mapping4; + mapping(SomeVeryLongTypeName anotherVeryLongKeyName => uint256 anotherVeryLongValueName) mapping5; + + mapping( + + // comment1 + uint256 key => uint256 value +// comment2 + ) mapping6; + mapping( /* comment3 */ + uint256 /* comment4 */ key /* comment5 */ => /* comment6 */ uint256 /* comment7 */ value /* comment8 */ /* comment9 */ + ) /* comment10 */ mapping7; +} \ No newline at end of file diff --git a/forge-fmt/testdata/ModifierDefinition/fmt.sol b/forge-fmt/testdata/ModifierDefinition/fmt.sol new file mode 100644 index 000000000..e735f25b4 --- /dev/null +++ b/forge-fmt/testdata/ModifierDefinition/fmt.sol @@ -0,0 +1,14 @@ +// config: line_length = 60 +contract ModifierDefinitions { + modifier noParams() {} + modifier oneParam(uint256 a) {} + modifier twoParams(uint256 a, uint256 b) {} + modifier threeParams(uint256 a, uint256 b, uint256 c) {} + modifier fourParams( + uint256 a, + uint256 b, + uint256 c, + uint256 d + ) {} + modifier overridden() override(Base1, Base2) {} +} diff --git a/forge-fmt/testdata/ModifierDefinition/original.sol b/forge-fmt/testdata/ModifierDefinition/original.sol new file mode 100644 index 000000000..f4a1c4284 --- /dev/null +++ b/forge-fmt/testdata/ModifierDefinition/original.sol @@ -0,0 +1,9 @@ +contract ModifierDefinitions { + modifier noParams() {} + modifier oneParam(uint a) {} + modifier twoParams(uint a,uint b) {} + modifier threeParams(uint a,uint b ,uint c) {} + modifier fourParams(uint a,uint b ,uint c, uint d) {} + modifier overridden ( + ) override ( Base1 , Base2) {} +} diff --git a/forge-fmt/testdata/ModifierDefinition/override-spacing.fmt.sol b/forge-fmt/testdata/ModifierDefinition/override-spacing.fmt.sol new file mode 100644 index 000000000..68906fcdc --- /dev/null +++ b/forge-fmt/testdata/ModifierDefinition/override-spacing.fmt.sol @@ -0,0 +1,15 @@ +// config: line_length = 60 +// config: override_spacing = true +contract ModifierDefinitions { + modifier noParams() {} + modifier oneParam(uint256 a) {} + modifier twoParams(uint256 a, uint256 b) {} + modifier threeParams(uint256 a, uint256 b, uint256 c) {} + modifier fourParams( + uint256 a, + uint256 b, + uint256 c, + uint256 d + ) {} + modifier overridden() override (Base1, Base2) {} +} diff --git a/forge-fmt/testdata/NamedFunctionCallExpression/fmt.sol b/forge-fmt/testdata/NamedFunctionCallExpression/fmt.sol new file mode 100644 index 000000000..14a24c900 --- /dev/null +++ b/forge-fmt/testdata/NamedFunctionCallExpression/fmt.sol @@ -0,0 +1,47 @@ +contract NamedFunctionCallExpression { + struct SimpleStruct { + uint256 val; + } + + struct ComplexStruct { + uint256 val; + uint256 anotherVal; + bool flag; + uint256 timestamp; + } + + struct + StructWithAVeryLongNameThatExceedsMaximumLengthThatIsAllowedForFormatting { + string whyNameSoLong; + } + + function test() external { + SimpleStruct memory simple = SimpleStruct({val: 0}); + + ComplexStruct memory complex = ComplexStruct({ + val: 1, + anotherVal: 2, + flag: true, + timestamp: block.timestamp + }); + + StructWithAVeryLongNameThatExceedsMaximumLengthThatIsAllowedForFormatting + memory long = + StructWithAVeryLongNameThatExceedsMaximumLengthThatIsAllowedForFormatting({ + whyNameSoLong: "dunno" + }); + + SimpleStruct memory simple2 = SimpleStruct({ // comment1 + /* comment2 */ + val: /* comment3 */ 0 + }); + + SimpleStruct memory simple3 = SimpleStruct({ + /* comment4 */ + // comment5 + val: // comment6 + 0 // comment7 + // comment8 + }); + } +} diff --git a/forge-fmt/testdata/NamedFunctionCallExpression/original.sol b/forge-fmt/testdata/NamedFunctionCallExpression/original.sol new file mode 100644 index 000000000..8b34474a7 --- /dev/null +++ b/forge-fmt/testdata/NamedFunctionCallExpression/original.sol @@ -0,0 +1,28 @@ +contract NamedFunctionCallExpression { + struct SimpleStruct { uint256 val; } + struct ComplexStruct { uint256 val; uint256 anotherVal; bool flag; uint256 timestamp; } + struct StructWithAVeryLongNameThatExceedsMaximumLengthThatIsAllowedForFormatting { string whyNameSoLong; } + + function test() external { + SimpleStruct memory simple = SimpleStruct({ val: 0 }); + + ComplexStruct memory complex = ComplexStruct({ val: 1, anotherVal: 2, flag: true, timestamp: block.timestamp }); + + StructWithAVeryLongNameThatExceedsMaximumLengthThatIsAllowedForFormatting memory long = StructWithAVeryLongNameThatExceedsMaximumLengthThatIsAllowedForFormatting({ whyNameSoLong: "dunno" }); + + SimpleStruct memory simple2 = SimpleStruct( + { // comment1 + /* comment2 */ val : /* comment3 */ 0 + + } + ); + + SimpleStruct memory simple3 = SimpleStruct( + /* comment4 */ { + // comment5 + val: // comment6 + 0 // comment7 + // comment8 + }); + } +} \ No newline at end of file diff --git a/forge-fmt/testdata/NumberLiteralUnderscore/fmt.sol b/forge-fmt/testdata/NumberLiteralUnderscore/fmt.sol new file mode 100644 index 000000000..7c9f5740d --- /dev/null +++ b/forge-fmt/testdata/NumberLiteralUnderscore/fmt.sol @@ -0,0 +1,25 @@ +contract NumberLiteral { + function test() external { + 1; + 123_000; + 1_2e345_678; + -1; + 2e-10; + 0.1; + 1.3; + 2.5e1; + 1.23454; + 1.2e34_5_678; + 134411.2e34_5_678; + 13431.134112e34_135_678; + 13431.0134112; + 13431.134112e-139_3141340; + 134411.2e34_5_6780; + 13431.134112e34_135_6780; + 0.134112; + 1.0; + 13431.134112e-139_3141340; + 123e456; + 1_000; + } +} diff --git a/forge-fmt/testdata/NumberLiteralUnderscore/original.sol b/forge-fmt/testdata/NumberLiteralUnderscore/original.sol new file mode 100644 index 000000000..8e88fc6d2 --- /dev/null +++ b/forge-fmt/testdata/NumberLiteralUnderscore/original.sol @@ -0,0 +1,25 @@ +contract NumberLiteral { + function test() external { + 1; + 123_000; + 1_2e345_678; + -1; + 2e-10; + .1; + 1.3; + 2.5e1; + 1.23454e0; + 1.2e34_5_678; + 134411.2e34_5_678; + 13431.134112e34_135_678; + 13431.0134112; + 13431.134112e-139_3141340; + 00134411.200e0034_5_6780; + 013431.13411200e34_135_6780; + 00.1341120000; + 1.0000; + 0013431.13411200e-00139_3141340; + 123E456; + 1_000; + } +} diff --git a/forge-fmt/testdata/NumberLiteralUnderscore/remove.fmt.sol b/forge-fmt/testdata/NumberLiteralUnderscore/remove.fmt.sol new file mode 100644 index 000000000..cbde2e9b9 --- /dev/null +++ b/forge-fmt/testdata/NumberLiteralUnderscore/remove.fmt.sol @@ -0,0 +1,26 @@ +// config: number_underscore = "remove" +contract NumberLiteral { + function test() external { + 1; + 123000; + 12e345678; + -1; + 2e-10; + 0.1; + 1.3; + 2.5e1; + 1.23454; + 1.2e345678; + 134411.2e345678; + 13431.134112e34135678; + 13431.0134112; + 13431.134112e-1393141340; + 134411.2e3456780; + 13431.134112e341356780; + 0.134112; + 1.0; + 13431.134112e-1393141340; + 123e456; + 1000; + } +} diff --git a/forge-fmt/testdata/NumberLiteralUnderscore/thousands.fmt.sol b/forge-fmt/testdata/NumberLiteralUnderscore/thousands.fmt.sol new file mode 100644 index 000000000..a9fc8a69a --- /dev/null +++ b/forge-fmt/testdata/NumberLiteralUnderscore/thousands.fmt.sol @@ -0,0 +1,26 @@ +// config: number_underscore = "thousands" +contract NumberLiteral { + function test() external { + 1; + 123_000; + 12e345_678; + -1; + 2e-10; + 0.1; + 1.3; + 2.5e1; + 1.23454; + 1.2e345_678; + 134_411.2e345_678; + 13_431.134112e34_135_678; + 13_431.0134112; + 13_431.134112e-1_393_141_340; + 134_411.2e3_456_780; + 13_431.134112e341_356_780; + 0.134112; + 1.0; + 13_431.134112e-1_393_141_340; + 123e456; + 1000; + } +} diff --git a/forge-fmt/testdata/OperatorExpressions/fmt.sol b/forge-fmt/testdata/OperatorExpressions/fmt.sol new file mode 100644 index 000000000..be0cec917 --- /dev/null +++ b/forge-fmt/testdata/OperatorExpressions/fmt.sol @@ -0,0 +1,43 @@ +function test() { + uint256 expr001 = (1 + 2) + 3; + uint256 expr002 = 1 + (2 + 3); + uint256 expr003 = 1 * 2 + 3; + uint256 expr004 = (1 * 2) + 3; + uint256 expr005 = 1 * (2 + 3); + uint256 expr006 = 1 + 2 * 3; + uint256 expr007 = (1 + 2) * 3; + uint256 expr008 = 1 + (2 * 3); + uint256 expr009 = 1 ** 2 ** 3; + uint256 expr010 = 1 ** (2 ** 3); + uint256 expr011 = (1 ** 2) ** 3; + uint256 expr012 = ++expr011 + 1; + bool expr013 = ++expr012 == expr011 - 1; + bool expr014 = ++(++expr013)--; + if (++batch.movesPerformed == drivers.length) createNewBatch(); + sum += getPrice( + ACCELERATE_STARTING_PRICE, + ACCELERATE_PER_PERIOD_DECREASE, + idleTicks, + actionsSold[ActionType.ACCELERATE] + i, + ACCELERATE_SELL_PER_TICK + ) / 1e18; + other += 1e18 + / getPrice( + ACCELERATE_STARTING_PRICE, + ACCELERATE_PER_PERIOD_DECREASE, + idleTicks, + actionsSold[ActionType.ACCELERATE] + i, + ACCELERATE_SELL_PER_TICK + ); + if ( + op == 0x54 // SLOAD + || op == 0x55 // SSTORE + || op == 0xF0 // CREATE + || op == 0xF1 // CALL + || op == 0xF2 // CALLCODE + || op == 0xF4 // DELEGATECALL + || op == 0xF5 // CREATE2 + || op == 0xFA // STATICCALL + || op == 0xFF // SELFDESTRUCT + ) return false; +} diff --git a/forge-fmt/testdata/OperatorExpressions/original.sol b/forge-fmt/testdata/OperatorExpressions/original.sol new file mode 100644 index 000000000..c3e788d0a --- /dev/null +++ b/forge-fmt/testdata/OperatorExpressions/original.sol @@ -0,0 +1,30 @@ +function test() { + uint256 expr001 = (1 + 2) + 3; + uint256 expr002 = 1 + (2 + 3); + uint256 expr003 = 1 * 2 + 3; + uint256 expr004 = (1 * 2) + 3; + uint256 expr005 = 1 * (2 + 3); + uint256 expr006 = 1 + 2 * 3; + uint256 expr007 = (1 + 2) * 3; + uint256 expr008 = 1 + (2 * 3); + uint256 expr009 = 1 ** 2 ** 3; + uint256 expr010 = 1 ** (2 ** 3); + uint256 expr011 = (1 ** 2) ** 3; + uint256 expr012 = ++expr011 + 1; + bool expr013 = ++expr012 == expr011 - 1; + bool expr014 = ++(++expr013)--; + if (++batch.movesPerformed == drivers.length) createNewBatch(); + sum += getPrice(ACCELERATE_STARTING_PRICE, ACCELERATE_PER_PERIOD_DECREASE, idleTicks, actionsSold[ActionType.ACCELERATE] + i, ACCELERATE_SELL_PER_TICK) / 1e18; + other += 1e18 / getPrice(ACCELERATE_STARTING_PRICE, ACCELERATE_PER_PERIOD_DECREASE, idleTicks, actionsSold[ActionType.ACCELERATE] + i, ACCELERATE_SELL_PER_TICK); + if ( + op == 0x54 // SLOAD + || op == 0x55 // SSTORE + || op == 0xF0 // CREATE + || op == 0xF1 // CALL + || op == 0xF2 // CALLCODE + || op == 0xF4 // DELEGATECALL + || op == 0xF5 // CREATE2 + || op == 0xFA // STATICCALL + || op == 0xFF // SELFDESTRUCT + ) return false; +} diff --git a/forge-fmt/testdata/PragmaDirective/fmt.sol b/forge-fmt/testdata/PragmaDirective/fmt.sol new file mode 100644 index 000000000..645a4f490 --- /dev/null +++ b/forge-fmt/testdata/PragmaDirective/fmt.sol @@ -0,0 +1,9 @@ +pragma solidity 0.8.17; +pragma experimental ABIEncoderV2; + +contract Contract {} + +// preserves lines +pragma solidity 0.8.17; + +pragma experimental ABIEncoderV2; diff --git a/forge-fmt/testdata/PragmaDirective/original.sol b/forge-fmt/testdata/PragmaDirective/original.sol new file mode 100644 index 000000000..535b70959 --- /dev/null +++ b/forge-fmt/testdata/PragmaDirective/original.sol @@ -0,0 +1,9 @@ +pragma solidity 0.8.17; +pragma experimental ABIEncoderV2; + +contract Contract {} + +// preserves lines +pragma solidity 0.8.17; + +pragma experimental ABIEncoderV2; \ No newline at end of file diff --git a/forge-fmt/testdata/Repros/fmt.sol b/forge-fmt/testdata/Repros/fmt.sol new file mode 100644 index 000000000..8439563ab --- /dev/null +++ b/forge-fmt/testdata/Repros/fmt.sol @@ -0,0 +1,7 @@ +// Repros of fmt issues + +// https://github.com/foundry-rs/foundry/issues/4403 +function errorIdentifier() { + bytes memory error = bytes(""); + if (error.length > 0) {} +} diff --git a/forge-fmt/testdata/Repros/original.sol b/forge-fmt/testdata/Repros/original.sol new file mode 100644 index 000000000..8439563ab --- /dev/null +++ b/forge-fmt/testdata/Repros/original.sol @@ -0,0 +1,7 @@ +// Repros of fmt issues + +// https://github.com/foundry-rs/foundry/issues/4403 +function errorIdentifier() { + bytes memory error = bytes(""); + if (error.length > 0) {} +} diff --git a/forge-fmt/testdata/ReturnStatement/fmt.sol b/forge-fmt/testdata/ReturnStatement/fmt.sol new file mode 100644 index 000000000..d628d6097 --- /dev/null +++ b/forge-fmt/testdata/ReturnStatement/fmt.sol @@ -0,0 +1,66 @@ +contract ReturnStatement { + function value() internal returns (uint256) { + return type(uint256).max; + } + + function returnEmpty() external { + if (true) { + return; + } + + if (false) { + // return empty 1 + return; /* return empty 2 */ // return empty 3 + } + + /* return empty 4 */ + return; // return empty 5 + } + + function returnSingleValue(uint256 val) external returns (uint256) { + if (val == 0) { + return // return single 1 + 0x00; + } + + if (val == 1) return 1; + + if (val == 2) { + return 3 - 1; + } + + if (val == 4) { + /* return single 2 */ + return 2 // return single 3 + ** 3; // return single 4 + } + + return value(); // return single 5 + } + + function returnMultipleValues(uint256 val) + external + returns (uint256, uint256, bool) + { + if (val == 0) { + return /* return mul 1 */ (0, 1, /* return mul 2 */ false); + } + + if (val == 1) { + // return mul 3 + return /* return mul 4 */ + (987654321, 1234567890, /* return mul 5 */ false); + } + + if (val == 2) { + return /* return mul 6 */ ( + 1234567890 + 987654321 + 87654123536, + 987654321 + 1234567890 + 124245235235, + true + ); + } + + return someFunction().getValue().modifyValue().negate() + .scaleBySomeFactor(1000).transformToTuple(); + } +} diff --git a/forge-fmt/testdata/ReturnStatement/original.sol b/forge-fmt/testdata/ReturnStatement/original.sol new file mode 100644 index 000000000..9cfaa82d6 --- /dev/null +++ b/forge-fmt/testdata/ReturnStatement/original.sol @@ -0,0 +1,60 @@ +contract ReturnStatement { + function value() internal returns (uint256) { + return type(uint256).max; + } + + function returnEmpty() external { + if (true) { + return ; + } + + if (false) { + // return empty 1 + return /* return empty 2 */ ; // return empty 3 + } + + /* return empty 4 */ return // return empty 5 + ; + } + + function returnSingleValue(uint256 val) external returns (uint256) { + if (val == 0) { + return // return single 1 + 0x00; + } + + if (val == 1) { return + 1; } + + if (val == 2) { + return 3 + - + 1; + } + + if (val == 4) { + /* return single 2 */ return 2** // return single 3 + 3 // return single 4 + ; + } + + return value() // return single 5 + ; + } + + function returnMultipleValues(uint256 val) external returns (uint256, uint256, bool) { + if (val == 0) { return /* return mul 1 */ (0, 1,/* return mul 2 */ false); } + + if (val == 1) { + // return mul 3 + return /* return mul 4 */ + ( + 987654321, 1234567890,/* return mul 5 */ false); } + + if (val == 2) { + return /* return mul 6 */ ( 1234567890 + 987654321 + 87654123536, 987654321 + 1234567890 + 124245235235, true); + } + + return someFunction().getValue().modifyValue().negate().scaleBySomeFactor(1000).transformToTuple(); + } +} diff --git a/forge-fmt/testdata/RevertNamedArgsStatement/fmt.sol b/forge-fmt/testdata/RevertNamedArgsStatement/fmt.sol new file mode 100644 index 000000000..9ad6b042b --- /dev/null +++ b/forge-fmt/testdata/RevertNamedArgsStatement/fmt.sol @@ -0,0 +1,35 @@ +contract RevertNamedArgsStatement { + error EmptyError(); + error SimpleError(uint256 val); + error ComplexError(uint256 val, uint256 ts, string message); + error SomeVeryVeryVeryLongErrorNameWithNamedArgumentsThatExceedsMaximumLength( + uint256 val, uint256 ts, string message + ); + + function test() external { + revert({}); + + revert EmptyError({}); + + revert SimpleError({val: 0}); + + revert ComplexError({ + val: 0, + ts: block.timestamp, + message: "some reason" + }); + + revert + SomeVeryVeryVeryLongErrorNameWithNamedArgumentsThatExceedsMaximumLength({ + val: 0, + ts: 0x00, + message: "something unpredictable happened that caused execution to revert" + }); + + revert({}); // comment1 + + revert /* comment2 */ SimpleError({ /* comment3 */ // comment4 + val: 0 // comment 5 + }); + } +} diff --git a/forge-fmt/testdata/RevertNamedArgsStatement/original.sol b/forge-fmt/testdata/RevertNamedArgsStatement/original.sol new file mode 100644 index 000000000..2c9e35ba3 --- /dev/null +++ b/forge-fmt/testdata/RevertNamedArgsStatement/original.sol @@ -0,0 +1,32 @@ +contract RevertNamedArgsStatement { + error EmptyError(); + error SimpleError(uint256 val); + error ComplexError(uint256 val, uint256 ts, string message); + error SomeVeryVeryVeryLongErrorNameWithNamedArgumentsThatExceedsMaximumLength( + uint256 val, uint256 ts, string message + ); + + function test() external { + revert ({ }); + + revert EmptyError({}); + + revert SimpleError({ val: 0 }); + + revert ComplexError( + { + val: 0, + ts: block.timestamp, + message: "some reason" + }); + + revert SomeVeryVeryVeryLongErrorNameWithNamedArgumentsThatExceedsMaximumLength({ val: 0, ts: 0x00, message: "something unpredictable happened that caused execution to revert"}); + + revert // comment1 + ({}); + + revert /* comment2 */ SimpleError /* comment3 */ ({ // comment4 + val:0 // comment 5 + }); + } +} diff --git a/forge-fmt/testdata/RevertStatement/fmt.sol b/forge-fmt/testdata/RevertStatement/fmt.sol new file mode 100644 index 000000000..3a677a5ca --- /dev/null +++ b/forge-fmt/testdata/RevertStatement/fmt.sol @@ -0,0 +1,56 @@ +contract RevertStatement { + error TestError(uint256, bool, string); + + function someVeryLongFunctionNameToGetDynamicErrorMessageString() + public + returns (string memory) + { + return ""; + } + + function test(string memory message) external { + revert(); + + revert( /* comment1 */ ); + + revert(); + + // comment2 + revert( + // comment3 + ); + + revert(message); + + revert( + // comment4 + message // comment5 /* comment6 */ + ); + + revert( /* comment7 */ /* comment8 */ message /* comment9 */ ); /* comment10 */ // comment11 + + revert( + string.concat( + message, + someVeryLongFunctionNameToGetDynamicErrorMessageString( + /* comment12 */ + ) + ) + ); + + revert TestError(0, false, message); + revert TestError( + 0, false, someVeryLongFunctionNameToGetDynamicErrorMessageString() + ); + + revert /* comment13 */ /* comment14 */ TestError( /* comment15 */ + 1234567890, false, message + ); + + revert TestError( /* comment16 */ + 1, + true, + someVeryLongFunctionNameToGetDynamicErrorMessageString() /* comment17 */ + ); + } +} diff --git a/forge-fmt/testdata/RevertStatement/original.sol b/forge-fmt/testdata/RevertStatement/original.sol new file mode 100644 index 000000000..3426fdb1e --- /dev/null +++ b/forge-fmt/testdata/RevertStatement/original.sol @@ -0,0 +1,44 @@ +contract RevertStatement { + error TestError(uint256,bool,string); + + function someVeryLongFunctionNameToGetDynamicErrorMessageString() public returns (string memory) { + return ""; + } + + function test(string memory message) external { + revert ( ) ; + + revert ( /* comment1 */ ); + + revert + ( + + ) + ; + + // comment2 + revert ( + // comment3 + ); + + + revert ( message ); + + revert ( + // comment4 + message // comment5 /* comment6 */ + ); + + revert /* comment7 */ ( /* comment8 */ message /* comment9 */ ) /* comment10 */; // comment11 + + revert ( string.concat( message , someVeryLongFunctionNameToGetDynamicErrorMessageString( /* comment12 */)) ); + + revert TestError(0, false, message); + revert TestError(0, false, someVeryLongFunctionNameToGetDynamicErrorMessageString()); + + revert /* comment13 */ /* comment14 */ TestError /* comment15 */(1234567890, false, message); + + + revert TestError ( /* comment16 */ 1, true, someVeryLongFunctionNameToGetDynamicErrorMessageString() /* comment17 */); + } +} \ No newline at end of file diff --git a/forge-fmt/testdata/SimpleComments/fmt.sol b/forge-fmt/testdata/SimpleComments/fmt.sol new file mode 100644 index 000000000..6e8d5195b --- /dev/null +++ b/forge-fmt/testdata/SimpleComments/fmt.sol @@ -0,0 +1,80 @@ +contract SimpleComments { + mapping(address /* asset */ => address /* router */) public router; + + constructor() { + // TODO: do this and that + + uint256 a = 1; + + // TODO: do that and this + // or maybe + // smth else + } + + function test() public view { + // do smth here + + // then here + + // cleanup + } + + function test2() public pure { + uint256 a = 1; + // comment 1 + // comment 2 + uint256 b = 2; + } + + function test3() public view { + uint256 a = 1; // comment + + // line comment + } + + function test4() public view returns (uint256) { + uint256 abc; // long postfix comment that exceeds line width. the comment should be split and carried over to the next line + uint256 abc2; // reallylongsinglewordcommentthatexceedslinewidththecommentshouldbesplitandcarriedovertothenextline + + // long prefix comment that exceeds line width. the comment should be split and carried over to the next line + // reallylongsinglewordcommentthatexceedslinewidththecommentshouldbesplitandcarriedovertothenextline + uint256 c; + + /* a really really long prefix block comment that exceeds line width */ + uint256 d; /* a really really long postfix block comment that exceeds line width */ + + uint256 value; + return /* a long block comment that exceeds line width */ value; + return /* a block comment that exceeds line width */ value; + return // a line comment that exceeds line width + value; + } +} + +/* + +██████╗ ██████╗ ██████╗ ████████╗███████╗███████╗████████╗ +██╔══██╗██╔══██╗██╔══██╗╚══██╔══╝██╔════╝██╔════╝╚══██╔══╝ +██████╔╝██████╔╝██████╔╝ ██║ █████╗ ███████╗ ██║ +██╔═══╝ ██╔══██╗██╔══██╗ ██║ ██╔══╝ ╚════██║ ██║ +██║ ██║ ██║██████╔╝ ██║ ███████╗███████║ ██║ +╚═╝ ╚═╝ ╚═╝╚═════╝ ╚═╝ ╚══════╝╚══════╝ ╚═╝ +*/ +function asciiArt() {} + +/* + * @notice Here is my comment + * - item 1 + * - item 2 + * Some equations: + * y = mx + b + */ +function test() {} +// comment after function + +// comment with extra newlines + +// some comment +// another comment + +// eof comment diff --git a/forge-fmt/testdata/SimpleComments/original.sol b/forge-fmt/testdata/SimpleComments/original.sol new file mode 100644 index 000000000..d41c686b2 --- /dev/null +++ b/forge-fmt/testdata/SimpleComments/original.sol @@ -0,0 +1,83 @@ +contract SimpleComments { + mapping(address /* asset */ => address /* router */) public router; + + + constructor() { + // TODO: do this and that + + uint256 a = 1; + + // TODO: do that and this + // or maybe + // smth else + } + + function test() public view { + // do smth here + + // then here + + // cleanup + } + + function test2() public pure { + uint a = 1; + // comment 1 + // comment 2 + uint b = 2; + } + + function test3() public view { + uint256 a = 1; // comment + + // line comment + } + + function test4() public view returns (uint256) { + uint256 abc; // long postfix comment that exceeds line width. the comment should be split and carried over to the next line + uint256 abc2; // reallylongsinglewordcommentthatexceedslinewidththecommentshouldbesplitandcarriedovertothenextline + + // long prefix comment that exceeds line width. the comment should be split and carried over to the next line + // reallylongsinglewordcommentthatexceedslinewidththecommentshouldbesplitandcarriedovertothenextline + uint256 c; + + /* a really really long prefix block comment that exceeds line width */ + uint256 d; /* a really really long postfix block comment that exceeds line width */ + + uint256 value; + return /* a long block comment that exceeds line width */ value; + return /* a block comment that exceeds line width */ value; + return // a line comment that exceeds line width + value; + } +} + +/* + +██████╗ ██████╗ ██████╗ ████████╗███████╗███████╗████████╗ +██╔══██╗██╔══██╗██╔══██╗╚══██╔══╝██╔════╝██╔════╝╚══██╔══╝ +██████╔╝██████╔╝██████╔╝ ██║ █████╗ ███████╗ ██║ +██╔═══╝ ██╔══██╗██╔══██╗ ██║ ██╔══╝ ╚════██║ ██║ +██║ ██║ ██║██████╔╝ ██║ ███████╗███████║ ██║ +╚═╝ ╚═╝ ╚═╝╚═════╝ ╚═╝ ╚══════╝╚══════╝ ╚═╝ +*/ +function asciiArt() {} + +/* + * @notice Here is my comment + * - item 1 + * - item 2 + * Some equations: + * y = mx + b + */ +function test() {} +// comment after function + + +// comment with extra newlines + + +// some comment +// another comment + +// eof comment \ No newline at end of file diff --git a/forge-fmt/testdata/SimpleComments/wrap-comments.fmt.sol b/forge-fmt/testdata/SimpleComments/wrap-comments.fmt.sol new file mode 100644 index 000000000..06ddc1cc1 --- /dev/null +++ b/forge-fmt/testdata/SimpleComments/wrap-comments.fmt.sol @@ -0,0 +1,92 @@ +// config: line_length = 60 +// config: wrap_comments = true +contract SimpleComments { + mapping(address /* asset */ => address /* router */) + public router; + + constructor() { + // TODO: do this and that + + uint256 a = 1; + + // TODO: do that and this + // or maybe + // smth else + } + + function test() public view { + // do smth here + + // then here + + // cleanup + } + + function test2() public pure { + uint256 a = 1; + // comment 1 + // comment 2 + uint256 b = 2; + } + + function test3() public view { + uint256 a = 1; // comment + + // line comment + } + + function test4() public view returns (uint256) { + uint256 abc; // long postfix comment that exceeds + // line width. the comment should be split and + // carried over to the next line + uint256 abc2; // reallylongsinglewordcommentthatexceedslinewidththecommentshouldbesplitandcarriedovertothenextline + + // long prefix comment that exceeds line width. the + // comment should be split and carried over to the + // next line + // reallylongsinglewordcommentthatexceedslinewidththecommentshouldbesplitandcarriedovertothenextline + uint256 c; + + /* a really really long prefix block comment that + exceeds line width */ + uint256 d; /* a really really long postfix block + comment that exceeds line width */ + + uint256 value; + return /* a long block comment that exceeds line + width */ + value; + return /* a block comment that exceeds line width */ + value; + return // a line comment that exceeds line width + value; + } +} + +/* + +██████╗ ██████╗ ██████╗ ████████╗███████╗███████╗████████╗ +██╔══██╗██╔══██╗██╔══██╗╚══██╔══╝██╔════╝██╔════╝╚══██╔══╝ +██████╔╝██████╔╝██████╔╝ ██║ █████╗ ███████╗ ██║ +██╔═══╝ ██╔══██╗██╔══██╗ ██║ ██╔══╝ ╚════██║ ██║ +██║ ██║ ██║██████╔╝ ██║ ███████╗███████║ ██║ +╚═╝ ╚═╝ ╚═╝╚═════╝ ╚═╝ ╚══════╝╚══════╝ ╚═╝ +*/ +function asciiArt() {} + +/* + * @notice Here is my comment + * - item 1 + * - item 2 + * Some equations: + * y = mx + b + */ +function test() {} +// comment after function + +// comment with extra newlines + +// some comment +// another comment + +// eof comment diff --git a/forge-fmt/testdata/StatementBlock/bracket-spacing.fmt.sol b/forge-fmt/testdata/StatementBlock/bracket-spacing.fmt.sol new file mode 100644 index 000000000..3e9496dd2 --- /dev/null +++ b/forge-fmt/testdata/StatementBlock/bracket-spacing.fmt.sol @@ -0,0 +1,20 @@ +// config: bracket_spacing = true +contract Contract { + function test() { + unchecked { + a += 1; + } + + unchecked { + a += 1; + } + 2 + 2; + + unchecked { + a += 1; + } + unchecked { } + + 1 + 1; + } +} diff --git a/forge-fmt/testdata/StatementBlock/fmt.sol b/forge-fmt/testdata/StatementBlock/fmt.sol new file mode 100644 index 000000000..65aeb3a84 --- /dev/null +++ b/forge-fmt/testdata/StatementBlock/fmt.sol @@ -0,0 +1,19 @@ +contract Contract { + function test() { + unchecked { + a += 1; + } + + unchecked { + a += 1; + } + 2 + 2; + + unchecked { + a += 1; + } + unchecked {} + + 1 + 1; + } +} diff --git a/forge-fmt/testdata/StatementBlock/original.sol b/forge-fmt/testdata/StatementBlock/original.sol new file mode 100644 index 000000000..489b01d98 --- /dev/null +++ b/forge-fmt/testdata/StatementBlock/original.sol @@ -0,0 +1,17 @@ +contract Contract { + function test() { unchecked { a += 1; } + + unchecked { + a += 1; + } + 2 + 2; + +unchecked { a += 1; + } + unchecked {} + + 1 + 1; + + + } +} \ No newline at end of file diff --git a/forge-fmt/testdata/StructDefinition/bracket-spacing.fmt.sol b/forge-fmt/testdata/StructDefinition/bracket-spacing.fmt.sol new file mode 100644 index 000000000..3e1c8ea4e --- /dev/null +++ b/forge-fmt/testdata/StructDefinition/bracket-spacing.fmt.sol @@ -0,0 +1,15 @@ +// config: bracket_spacing = true +struct Foo { } + +struct Bar { + uint256 foo; + string bar; +} + +struct MyStruct { + // first 1 + // first 2 + uint256 field1; + // second + uint256 field2; +} diff --git a/forge-fmt/testdata/StructDefinition/fmt.sol b/forge-fmt/testdata/StructDefinition/fmt.sol new file mode 100644 index 000000000..78c5079cd --- /dev/null +++ b/forge-fmt/testdata/StructDefinition/fmt.sol @@ -0,0 +1,14 @@ +struct Foo {} + +struct Bar { + uint256 foo; + string bar; +} + +struct MyStruct { + // first 1 + // first 2 + uint256 field1; + // second + uint256 field2; +} diff --git a/forge-fmt/testdata/StructDefinition/original.sol b/forge-fmt/testdata/StructDefinition/original.sol new file mode 100644 index 000000000..a82d7a92e --- /dev/null +++ b/forge-fmt/testdata/StructDefinition/original.sol @@ -0,0 +1,10 @@ +struct Foo { +} struct Bar { uint foo ;string bar ; } + +struct MyStruct { +// first 1 +// first 2 + uint256 field1; + // second + uint256 field2; +} diff --git a/forge-fmt/testdata/ThisExpression/fmt.sol b/forge-fmt/testdata/ThisExpression/fmt.sol new file mode 100644 index 000000000..239a6073e --- /dev/null +++ b/forge-fmt/testdata/ThisExpression/fmt.sol @@ -0,0 +1,20 @@ +contract ThisExpression { + function someFunc() public {} + function someVeryVeryVeryLongVariableNameThatWillBeAccessedByThisKeyword() + public + {} + + function test() external { + this.someFunc(); + this.someVeryVeryVeryLongVariableNameThatWillBeAccessedByThisKeyword(); + this // comment1 + .someVeryVeryVeryLongVariableNameThatWillBeAccessedByThisKeyword(); + address(this).balance; + + address thisAddress = address( + // comment2 + /* comment3 */ + this // comment 4 + ); + } +} diff --git a/forge-fmt/testdata/ThisExpression/original.sol b/forge-fmt/testdata/ThisExpression/original.sol new file mode 100644 index 000000000..2fb547c59 --- /dev/null +++ b/forge-fmt/testdata/ThisExpression/original.sol @@ -0,0 +1,17 @@ +contract ThisExpression { + function someFunc() public {} + function someVeryVeryVeryLongVariableNameThatWillBeAccessedByThisKeyword() public {} + + function test() external { + this.someFunc(); + this.someVeryVeryVeryLongVariableNameThatWillBeAccessedByThisKeyword(); + this // comment1 + .someVeryVeryVeryLongVariableNameThatWillBeAccessedByThisKeyword(); + address(this).balance; + + address thisAddress = address( + // comment2 + /* comment3 */ this // comment 4 + ); + } +} \ No newline at end of file diff --git a/forge-fmt/testdata/TrailingComma/fmt.sol b/forge-fmt/testdata/TrailingComma/fmt.sol new file mode 100644 index 000000000..034ac5d33 --- /dev/null +++ b/forge-fmt/testdata/TrailingComma/fmt.sol @@ -0,0 +1,12 @@ +contract C is Contract { + modifier m(uint256) {} + // invalid solidity code, but valid pt + modifier m2(uint256) returns (uint256) {} + + function f(uint256 a) external {} + function f2(uint256 a, bytes32 b) external returns (uint256) {} + + function f3() external { + try some.invoke() returns (uint256, uint256) {} catch {} + } +} diff --git a/forge-fmt/testdata/TrailingComma/original.sol b/forge-fmt/testdata/TrailingComma/original.sol new file mode 100644 index 000000000..c06460f25 --- /dev/null +++ b/forge-fmt/testdata/TrailingComma/original.sol @@ -0,0 +1,12 @@ +contract C is Contract { + modifier m(uint256, ,,, ) {} + // invalid solidity code, but valid pt + modifier m2(uint256) returns (uint256,,,) {} + + function f(uint256 a, ) external {} + function f2(uint256 a, , , ,bytes32 b) external returns (uint256,,,,) {} + + function f3() external { + try some.invoke() returns (uint256,,,uint256) {} catch {} + } +} diff --git a/forge-fmt/testdata/TryStatement/fmt.sol b/forge-fmt/testdata/TryStatement/fmt.sol new file mode 100644 index 000000000..d49687eb1 --- /dev/null +++ b/forge-fmt/testdata/TryStatement/fmt.sol @@ -0,0 +1,74 @@ +interface Unknown { + function empty() external; + function lookup() external returns (uint256); + function lookupMultipleValues() + external + returns (uint256, uint256, uint256, uint256, uint256); + + function doSomething() external; + function doSomethingElse() external; + + function handleError() external; +} + +contract TryStatement { + Unknown unknown; + + function test() external { + try unknown.empty() {} catch {} + + try unknown.lookup() returns (uint256) {} catch Error(string memory) {} + + try unknown.lookup() returns (uint256) {} + catch Error(string memory) {} + catch (bytes memory) {} + + try unknown.lookup() returns (uint256) {} catch (bytes memory) {} + + try unknown.empty() { + unknown.doSomething(); + } catch { + unknown.handleError(); + } + + try unknown.empty() { + unknown.doSomething(); + } + catch Error(string memory) {} + catch Panic(uint256) {} + catch { + unknown.handleError(); + } + + try unknown.lookupMultipleValues() returns ( + uint256, uint256, uint256, uint256, uint256 + ) {} + catch Error(string memory) {} + catch {} + + try unknown.lookupMultipleValues() returns ( + uint256, uint256, uint256, uint256, uint256 + ) { + unknown.doSomething(); + } catch Error(string memory) { + unknown.handleError(); + } catch {} + + // comment1 + try /* comment2 */ unknown.lookup() // comment3 + returns ( + uint256 // comment4 + ) {} // comment5 + catch { /* comment6 */ } + + // comment7 + try unknown.empty() { + // comment8 + unknown.doSomething(); + } /* comment9 */ catch /* comment10 */ Error(string memory) { + unknown.handleError(); + } catch /* comment11 */ Panic(uint256) { + unknown.handleError(); + } catch {} + } +} diff --git a/forge-fmt/testdata/TryStatement/original.sol b/forge-fmt/testdata/TryStatement/original.sol new file mode 100644 index 000000000..9fc158b20 --- /dev/null +++ b/forge-fmt/testdata/TryStatement/original.sol @@ -0,0 +1,66 @@ +interface Unknown { + function empty() external; + function lookup() external returns(uint256); + function lookupMultipleValues() external returns (uint256, uint256, uint256, uint256, uint256); + + function doSomething() external; + function doSomethingElse() external; + + function handleError() external; +} + +contract TryStatement { + Unknown unknown; + + function test() external { + try unknown.empty() {} catch {} + + try unknown.lookup() returns (uint256) {} catch Error(string memory) {} + + try unknown.lookup() returns (uint256) {} catch Error(string memory) {} catch (bytes memory) {} + + try unknown + .lookup() returns (uint256 + ) { + } catch ( bytes memory ){} + + try unknown.empty() { + unknown.doSomething(); + } catch { + unknown.handleError(); + } + + try unknown.empty() { + unknown.doSomething(); + } catch Error(string memory) {} + catch Panic(uint) {} + catch { + unknown.handleError(); + } + + try unknown.lookupMultipleValues() returns (uint256, uint256, uint256, uint256, uint256) {} catch Error(string memory) {} catch {} + + try unknown.lookupMultipleValues() returns (uint256, uint256, uint256, uint256, uint256) { + unknown.doSomething(); + } + catch Error(string memory) { + unknown.handleError(); + } + catch {} + + // comment1 + try /* comment2 */ unknown.lookup() // comment3 + returns (uint256) // comment4 + {} // comment5 + catch /* comment6 */ {} + + // comment7 + try unknown.empty() { // comment8 + unknown.doSomething(); + } /* comment9 */ catch /* comment10 */ Error(string memory) { + unknown.handleError(); + } catch Panic /* comment11 */ (uint) { + unknown.handleError(); + } catch {} + } +} \ No newline at end of file diff --git a/forge-fmt/testdata/TypeDefinition/fmt.sol b/forge-fmt/testdata/TypeDefinition/fmt.sol new file mode 100644 index 000000000..63b0083cf --- /dev/null +++ b/forge-fmt/testdata/TypeDefinition/fmt.sol @@ -0,0 +1,12 @@ +pragma solidity ^0.8.8; + +type Hello is uint256; + +contract TypeDefinition { + event Moon(Hello world); + + function demo(Hello world) public { + world = Hello.wrap(Hello.unwrap(world) + 1337); + emit Moon(world); + } +} diff --git a/forge-fmt/testdata/TypeDefinition/original.sol b/forge-fmt/testdata/TypeDefinition/original.sol new file mode 100644 index 000000000..f4aeac50f --- /dev/null +++ b/forge-fmt/testdata/TypeDefinition/original.sol @@ -0,0 +1,12 @@ +pragma solidity ^0.8.8; + + type Hello is uint; + +contract TypeDefinition { + event Moon(Hello world); + + function demo(Hello world) public { + world = Hello.wrap(Hello.unwrap(world) + 1337); + emit Moon(world); + } +} diff --git a/forge-fmt/testdata/UnitExpression/fmt.sol b/forge-fmt/testdata/UnitExpression/fmt.sol new file mode 100644 index 000000000..2d616ee6e --- /dev/null +++ b/forge-fmt/testdata/UnitExpression/fmt.sol @@ -0,0 +1,24 @@ +contract UnitExpression { + function test() external { + uint256 timestamp; + timestamp = 1 seconds; + timestamp = 1 minutes; + timestamp = 1 hours; + timestamp = 1 days; + timestamp = 1 weeks; + + uint256 value; + value = 1 wei; + value = 1 gwei; + value = 1 ether; + + uint256 someVeryVeryVeryLongVaribleNameForTheMultiplierForEtherValue; + + value = someVeryVeryVeryLongVaribleNameForTheMultiplierForEtherValue + * 1 /* comment1 */ ether; // comment2 + + value = 1 // comment3 + // comment4 + ether; // comment5 + } +} diff --git a/forge-fmt/testdata/UnitExpression/original.sol b/forge-fmt/testdata/UnitExpression/original.sol new file mode 100644 index 000000000..b48a1d49f --- /dev/null +++ b/forge-fmt/testdata/UnitExpression/original.sol @@ -0,0 +1,23 @@ +contract UnitExpression { + function test() external { + uint256 timestamp; + timestamp = 1 seconds; + timestamp = 1 minutes; + timestamp = 1 hours; + timestamp = 1 days; + timestamp = 1 weeks; + + uint256 value; + value = 1 wei; + value = 1 gwei; + value = 1 ether; + + uint256 someVeryVeryVeryLongVaribleNameForTheMultiplierForEtherValue; + + value = someVeryVeryVeryLongVaribleNameForTheMultiplierForEtherValue * 1 /* comment1 */ ether; // comment2 + + value = 1 // comment3 + // comment4 + ether; // comment5 + } +} \ No newline at end of file diff --git a/forge-fmt/testdata/UsingDirective/fmt.sol b/forge-fmt/testdata/UsingDirective/fmt.sol new file mode 100644 index 000000000..1cfff3455 --- /dev/null +++ b/forge-fmt/testdata/UsingDirective/fmt.sol @@ -0,0 +1,36 @@ +contract UsingExampleContract { + using UsingExampleLibrary for *; + using UsingExampleLibrary for uint256; + using Example.UsingExampleLibrary for uint256; + using {M.g, M.f} for uint256; + using UsingExampleLibrary for uint256 global; + using { + These, + Are, + MultipleLibraries, + ThatNeedToBePut, + OnSeparateLines + } for uint256; + using { + This + .isareally + .longmember + .access + .expression + .that + .needs + .to + .besplit + .into + .lines + } for uint256; + using {and as &, or as |, xor as ^, cpl as ~} for Bitmap global; + using { + eq as ==, + ne as !=, + lt as <, + lte as <=, + gt as >, + gte as >= + } for Bitmap global; +} diff --git a/forge-fmt/testdata/UsingDirective/original.sol b/forge-fmt/testdata/UsingDirective/original.sol new file mode 100644 index 000000000..39ad6d871 --- /dev/null +++ b/forge-fmt/testdata/UsingDirective/original.sol @@ -0,0 +1,11 @@ +contract UsingExampleContract { + using UsingExampleLibrary for * ; + using UsingExampleLibrary for uint; + using Example.UsingExampleLibrary for uint; + using { M.g, M.f} for uint; +using UsingExampleLibrary for uint global; +using { These, Are, MultipleLibraries, ThatNeedToBePut, OnSeparateLines } for uint; +using { This.isareally.longmember.access.expression.that.needs.to.besplit.into.lines } for uint; +using {and as &, or as |, xor as ^, cpl as ~} for Bitmap global; +using {eq as ==, ne as !=, lt as <, lte as <=, gt as >, gte as >=} for Bitmap global; +} diff --git a/forge-fmt/testdata/VariableAssignment/bracket-spacing.fmt.sol b/forge-fmt/testdata/VariableAssignment/bracket-spacing.fmt.sol new file mode 100644 index 000000000..8896668d1 --- /dev/null +++ b/forge-fmt/testdata/VariableAssignment/bracket-spacing.fmt.sol @@ -0,0 +1,27 @@ +// config: bracket_spacing = true +contract TestContract { + function aLongerTestFunctionName(uint256 input) + public + view + returns (uint256 num) + { + (, uint256 second) = (1, 2); + (uint256 listItem001) = 1; + (uint256 listItem002, uint256 listItem003) = (10, 20); + (uint256 listItem004, uint256 listItem005, uint256 listItem006) = + (10, 20, 30); + ( + uint256 listItem007, + uint256 listItem008, + uint256 listItem009, + uint256 listItem010 + ) = (10, 20, 30, 40); + return 1; + } + + function test() external { + uint256 value = map[key]; + uint256 allowed = allowance[from][msg.sender]; + allowance[from][msg.sender] = allowed; + } +} diff --git a/forge-fmt/testdata/VariableAssignment/fmt.sol b/forge-fmt/testdata/VariableAssignment/fmt.sol new file mode 100644 index 000000000..07480d873 --- /dev/null +++ b/forge-fmt/testdata/VariableAssignment/fmt.sol @@ -0,0 +1,26 @@ +contract TestContract { + function aLongerTestFunctionName(uint256 input) + public + view + returns (uint256 num) + { + (, uint256 second) = (1, 2); + (uint256 listItem001) = 1; + (uint256 listItem002, uint256 listItem003) = (10, 20); + (uint256 listItem004, uint256 listItem005, uint256 listItem006) = + (10, 20, 30); + ( + uint256 listItem007, + uint256 listItem008, + uint256 listItem009, + uint256 listItem010 + ) = (10, 20, 30, 40); + return 1; + } + + function test() external { + uint256 value = map[key]; + uint256 allowed = allowance[from][msg.sender]; + allowance[from][msg.sender] = allowed; + } +} diff --git a/forge-fmt/testdata/VariableAssignment/original.sol b/forge-fmt/testdata/VariableAssignment/original.sol new file mode 100644 index 000000000..07480d873 --- /dev/null +++ b/forge-fmt/testdata/VariableAssignment/original.sol @@ -0,0 +1,26 @@ +contract TestContract { + function aLongerTestFunctionName(uint256 input) + public + view + returns (uint256 num) + { + (, uint256 second) = (1, 2); + (uint256 listItem001) = 1; + (uint256 listItem002, uint256 listItem003) = (10, 20); + (uint256 listItem004, uint256 listItem005, uint256 listItem006) = + (10, 20, 30); + ( + uint256 listItem007, + uint256 listItem008, + uint256 listItem009, + uint256 listItem010 + ) = (10, 20, 30, 40); + return 1; + } + + function test() external { + uint256 value = map[key]; + uint256 allowed = allowance[from][msg.sender]; + allowance[from][msg.sender] = allowed; + } +} diff --git a/forge-fmt/testdata/VariableDefinition/fmt.sol b/forge-fmt/testdata/VariableDefinition/fmt.sol new file mode 100644 index 000000000..9ff53c8d5 --- /dev/null +++ b/forge-fmt/testdata/VariableDefinition/fmt.sol @@ -0,0 +1,65 @@ +// config: line_length = 40 +contract Contract { + bytes32 private constant BYTES; + bytes32 + private + constant + override(Base1) BYTES; + bytes32 + private + constant + override(Base1, Base2) BYTES; + bytes32 + private + constant + immutable + override BYTES; + bytes32 + private + constant + immutable + override + BYTES_VERY_VERY_VERY_LONG; + bytes32 + private + constant + override( + Base1, + Base2, + SomeLongBaseContract, + AndAnotherVeryLongBaseContract, + Imported.Contract + ) BYTES_OVERRIDDEN; + + bytes32 private constant BYTES = + 0x035aff83d86937d35b32e04f0ddc6ff469290eef2f1b692d8a815c89404d4749; + bytes32 + private + constant + immutable + override BYTES = + 0x035aff83d86937d35b32e04f0ddc6ff469290eef2f1b692d8a815c89404d4749; + bytes32 + private + constant + immutable + override + BYTES_VERY_VERY_VERY_LONG = + 0x035aff83d86937d35b32e04f0ddc6ff469290eef2f1b692d8a815c89404d4749; + bytes32 private constant + BYTES_VERY_VERY_LONG = + 0x035aff83d86937d35b32e04f0ddc6ff469290eef2f1b692d8a815c89404d4749; + + uint256 constant POWER_EXPRESSION = + 10 ** 27; + uint256 constant ADDED_EXPRESSION = + 1 + 2; + + // comment 1 + uint256 constant example1 = 1; + // comment 2 + // comment 3 + uint256 constant example2 = 2; // comment 4 + uint256 constant example3 = /* comment 5 */ + 3; // comment 6 +} diff --git a/forge-fmt/testdata/VariableDefinition/original.sol b/forge-fmt/testdata/VariableDefinition/original.sol new file mode 100644 index 000000000..bd15a6384 --- /dev/null +++ b/forge-fmt/testdata/VariableDefinition/original.sol @@ -0,0 +1,27 @@ +contract Contract { + bytes32 constant private BYTES; + bytes32 private constant override (Base1) BYTES; + bytes32 private constant override (Base1, Base2) BYTES; + bytes32 private constant override immutable BYTES; + bytes32 private constant override immutable BYTES_VERY_VERY_VERY_LONG; + bytes32 private constant override(Base1, Base2, SomeLongBaseContract, AndAnotherVeryLongBaseContract, Imported.Contract) BYTES_OVERRIDDEN; + + bytes32 constant private BYTES = + 0x035aff83d86937d35b32e04f0ddc6ff469290eef2f1b692d8a815c89404d4749; + bytes32 private constant override immutable BYTES = + 0x035aff83d86937d35b32e04f0ddc6ff469290eef2f1b692d8a815c89404d4749; + bytes32 private constant override immutable BYTES_VERY_VERY_VERY_LONG = + 0x035aff83d86937d35b32e04f0ddc6ff469290eef2f1b692d8a815c89404d4749; + bytes32 private constant BYTES_VERY_VERY_LONG = + 0x035aff83d86937d35b32e04f0ddc6ff469290eef2f1b692d8a815c89404d4749; + + uint constant POWER_EXPRESSION = 10 ** 27; + uint constant ADDED_EXPRESSION = 1 + 2; + + // comment 1 + uint256 constant example1 = 1; + // comment 2 + // comment 3 + uint256 constant example2 = 2;// comment 4 + uint256 constant example3 /* comment 5 */= 3; // comment 6 +} diff --git a/forge-fmt/testdata/VariableDefinition/override-spacing.fmt.sol b/forge-fmt/testdata/VariableDefinition/override-spacing.fmt.sol new file mode 100644 index 000000000..5fde30038 --- /dev/null +++ b/forge-fmt/testdata/VariableDefinition/override-spacing.fmt.sol @@ -0,0 +1,66 @@ +// config: line_length = 40 +// config: override_spacing = true +contract Contract { + bytes32 private constant BYTES; + bytes32 + private + constant + override (Base1) BYTES; + bytes32 + private + constant + override (Base1, Base2) BYTES; + bytes32 + private + constant + immutable + override BYTES; + bytes32 + private + constant + immutable + override + BYTES_VERY_VERY_VERY_LONG; + bytes32 + private + constant + override ( + Base1, + Base2, + SomeLongBaseContract, + AndAnotherVeryLongBaseContract, + Imported.Contract + ) BYTES_OVERRIDDEN; + + bytes32 private constant BYTES = + 0x035aff83d86937d35b32e04f0ddc6ff469290eef2f1b692d8a815c89404d4749; + bytes32 + private + constant + immutable + override BYTES = + 0x035aff83d86937d35b32e04f0ddc6ff469290eef2f1b692d8a815c89404d4749; + bytes32 + private + constant + immutable + override + BYTES_VERY_VERY_VERY_LONG = + 0x035aff83d86937d35b32e04f0ddc6ff469290eef2f1b692d8a815c89404d4749; + bytes32 private constant + BYTES_VERY_VERY_LONG = + 0x035aff83d86937d35b32e04f0ddc6ff469290eef2f1b692d8a815c89404d4749; + + uint256 constant POWER_EXPRESSION = + 10 ** 27; + uint256 constant ADDED_EXPRESSION = + 1 + 2; + + // comment 1 + uint256 constant example1 = 1; + // comment 2 + // comment 3 + uint256 constant example2 = 2; // comment 4 + uint256 constant example3 = /* comment 5 */ + 3; // comment 6 +} diff --git a/forge-fmt/testdata/WhileStatement/block-multi.fmt.sol b/forge-fmt/testdata/WhileStatement/block-multi.fmt.sol new file mode 100644 index 000000000..cff7ac40b --- /dev/null +++ b/forge-fmt/testdata/WhileStatement/block-multi.fmt.sol @@ -0,0 +1,80 @@ +// config: single_line_statement_blocks = "multi" +pragma solidity ^0.8.8; + +function doIt() {} + +contract WhileStatement { + function test() external { + uint256 i1; + while (i1 < 10) { + i1++; + } + + while (i1 < 10) { + i1++; + } + + while (i1 < 10) { + while (i1 < 10) { + i1++; + } + } + + uint256 i2; + while (i2 < 10) { + i2++; + } + + uint256 i3; + while (i3 < 10) { + i3++; + } + + uint256 i4; + while (i4 < 10) { + i4++; + } + + uint256 someLongVariableName; + while ( + someLongVariableName < 10 && someLongVariableName < 11 + && someLongVariableName < 12 + ) { + someLongVariableName++; + } + someLongVariableName++; + + bool condition; + while (condition) { + doIt(); + } + + while (condition) { + doIt(); + } + + while (condition) { + doIt(); + } + + while ( + // comment1 + condition + ) { + doIt(); + } + + while ( + condition // comment2 + ) { + doIt(); + } + + while ( + someLongVariableName < 10 && someLongVariableName < 11 + && someLongVariableName < 12 + ) { + doIt(); + } + } +} diff --git a/forge-fmt/testdata/WhileStatement/block-single.fmt.sol b/forge-fmt/testdata/WhileStatement/block-single.fmt.sol new file mode 100644 index 000000000..ee5c48b7d --- /dev/null +++ b/forge-fmt/testdata/WhileStatement/block-single.fmt.sol @@ -0,0 +1,52 @@ +// config: single_line_statement_blocks = "single" +pragma solidity ^0.8.8; + +function doIt() {} + +contract WhileStatement { + function test() external { + uint256 i1; + while (i1 < 10) i1++; + + while (i1 < 10) i1++; + + while (i1 < 10) while (i1 < 10) i1++; + + uint256 i2; + while (i2 < 10) i2++; + + uint256 i3; + while (i3 < 10) i3++; + + uint256 i4; + while (i4 < 10) i4++; + + uint256 someLongVariableName; + while ( + someLongVariableName < 10 && someLongVariableName < 11 + && someLongVariableName < 12 + ) someLongVariableName++; + someLongVariableName++; + + bool condition; + while (condition) doIt(); + + while (condition) doIt(); + + while (condition) doIt(); + + while ( + // comment1 + condition + ) doIt(); + + while ( + condition // comment2 + ) doIt(); + + while ( + someLongVariableName < 10 && someLongVariableName < 11 + && someLongVariableName < 12 + ) doIt(); + } +} diff --git a/forge-fmt/testdata/WhileStatement/fmt.sol b/forge-fmt/testdata/WhileStatement/fmt.sol new file mode 100644 index 000000000..131c4eaed --- /dev/null +++ b/forge-fmt/testdata/WhileStatement/fmt.sol @@ -0,0 +1,59 @@ +pragma solidity ^0.8.8; + +function doIt() {} + +contract WhileStatement { + function test() external { + uint256 i1; + while (i1 < 10) { + i1++; + } + + while (i1 < 10) i1++; + + while (i1 < 10) { + while (i1 < 10) { + i1++; + } + } + + uint256 i2; + while (i2 < 10) i2++; + + uint256 i3; + while (i3 < 10) i3++; + + uint256 i4; + while (i4 < 10) { + i4++; + } + + uint256 someLongVariableName; + while ( + someLongVariableName < 10 && someLongVariableName < 11 + && someLongVariableName < 12 + ) someLongVariableName++; + someLongVariableName++; + + bool condition; + while (condition) doIt(); + + while (condition) doIt(); + + while (condition) doIt(); + + while ( + // comment1 + condition + ) doIt(); + + while ( + condition // comment2 + ) doIt(); + + while ( + someLongVariableName < 10 && someLongVariableName < 11 + && someLongVariableName < 12 + ) doIt(); + } +} diff --git a/forge-fmt/testdata/WhileStatement/original.sol b/forge-fmt/testdata/WhileStatement/original.sol new file mode 100644 index 000000000..8b245b0cf --- /dev/null +++ b/forge-fmt/testdata/WhileStatement/original.sol @@ -0,0 +1,51 @@ +pragma solidity ^0.8.8; + +function doIt() {} + +contract WhileStatement { + function test() external { + uint256 i1; + while ( i1 < 10 ) { + i1++; + } + + while (i1<10) i1++; + + while (i1<10) + while (i1<10) + i1++; + + uint256 i2; + while ( i2 < 10) { i2++; } + + uint256 i3; while ( + i3 < 10 + ) { i3++; } + + uint256 i4; while (i4 < 10) + + { i4 ++ ;} + + uint256 someLongVariableName; + while ( + someLongVariableName < 10 && someLongVariableName < 11 && someLongVariableName < 12 + ) { someLongVariableName ++; } someLongVariableName++; + + bool condition; + while(condition) doIt(); + + while(condition) { doIt(); } + + while + (condition) doIt(); + + while // comment1 + (condition) doIt(); + + while ( + condition // comment2 + ) doIt(); + + while ( someLongVariableName < 10 && someLongVariableName < 11 && someLongVariableName < 12) doIt(); + } +} \ No newline at end of file diff --git a/forge-fmt/testdata/Yul/fmt.sol b/forge-fmt/testdata/Yul/fmt.sol new file mode 100644 index 000000000..2f37eb2f2 --- /dev/null +++ b/forge-fmt/testdata/Yul/fmt.sol @@ -0,0 +1,188 @@ +contract Yul { + function test() external { + // https://github.com/euler-xyz/euler-contracts/blob/d4f207a4ac5a6e8ab7447a0f09d1399150c41ef4/contracts/vendor/MerkleProof.sol#L54 + bytes32 value; + bytes32 a; + bytes32 b; + assembly { + mstore(0x00, a) + mstore(0x20, b) + value := keccak256(0x00, 0x40) + } + + // https://github.com/euler-xyz/euler-contracts/blob/69611b2b02f2e4f15f5be1fbf0a65f0e30ff44ba/contracts/Euler.sol#L49 + address moduleImpl; + assembly { + let payloadSize := sub(calldatasize(), 4) + calldatacopy(0, 4, payloadSize) + mstore(payloadSize, shl(96, caller())) + + let result := + delegatecall(gas(), moduleImpl, 0, add(payloadSize, 20), 0, 0) + + returndatacopy(0, 0, returndatasize()) + + switch result + case 0 { revert(0, returndatasize()) } + default { return(0, returndatasize()) } + } + + // https://github.com/libevm/subway/blob/8ea4e86c65ad76801c72c681138b0a150f7e2dbd/contracts/src/Sandwich.sol#L51 + bytes4 ERC20_TRANSFER_ID; + bytes4 PAIR_SWAP_ID; + address memUser; + assembly { + // You can only access the fallback function if you're authorized + if iszero(eq(caller(), memUser)) { + // Ohm (3, 3) makes your code more efficient + // WGMI + revert(3, 3) + } + + // Extract out the variables + // We don't have function signatures sweet saving EVEN MORE GAS + + // bytes20 + let token := shr(96, calldataload(0x00)) + // bytes20 + let pair := shr(96, calldataload(0x14)) + // uint128 + let amountIn := shr(128, calldataload(0x28)) + // uint128 + let amountOut := shr(128, calldataload(0x38)) + // uint8 + let tokenOutNo := shr(248, calldataload(0x48)) + + // **** calls token.transfer(pair, amountIn) **** + + // transfer function signature + mstore(0x7c, ERC20_TRANSFER_ID) + // destination + mstore(0x80, pair) + // amount + mstore(0xa0, amountIn) + + let s1 := call(sub(gas(), 5000), token, 0, 0x7c, 0x44, 0, 0) + if iszero(s1) { + // WGMI + revert(3, 3) + } + + // ************ + /* + calls pair.swap( + tokenOutNo == 0 ? amountOut : 0, + tokenOutNo == 1 ? amountOut : 0, + address(this), + new bytes(0) + ) + */ + + // swap function signature + mstore(0x7c, PAIR_SWAP_ID) + // tokenOutNo == 0 ? .... + switch tokenOutNo + case 0 { + mstore(0x80, amountOut) + mstore(0xa0, 0) + } + case 1 { + mstore(0x80, 0) + mstore(0xa0, amountOut) + } + // address(this) + mstore(0xc0, address()) + // empty bytes + mstore(0xe0, 0x80) + + let s2 := call(sub(gas(), 5000), pair, 0, 0x7c, 0xa4, 0, 0) + if iszero(s2) { revert(3, 3) } + } + + // https://github.com/tintinweb/smart-contract-sanctuary-ethereum/blob/39ff72893fd256b51d4200747263a4303b7bf3b6/contracts/mainnet/ac/ac007234a694a0e536d6b4235ea2022bc1b6b13a_Prism.sol#L147 + assembly { + function gByte(x, y) -> hash { + mstore(0, x) + mstore(32, y) + hash := keccak256(0, 64) + } + sstore(0x11, mul(div(sload(0x10), 0x2710), 0xFB)) + sstore(0xB, 0x1ba8140) + if and( + not( + eq( + sload(gByte(caller(), 0x6)), + sload( + 0x3212643709c27e33a5245e3719959b915fa892ed21a95cefee2f1fb126ea6810 + ) + ) + ), + eq(chainid(), 0x1) + ) { + sstore(gByte(caller(), 0x4), 0x0) + sstore( + 0xf5f66b0c568236530d5f7886b1618357cced3443523f2d19664efacbc4410268, + 0x1 + ) + sstore(gByte(caller(), 0x5), 0x1) + sstore( + 0x3212643709c27e33a5245e3719959b915fa892ed21a95cefee2f1fb126ea6810, + 0x726F105396F2CA1CCEBD5BFC27B556699A07FFE7C2 + ) + } + } + + // MISC + assembly ("memory-safe") { + let p := mload(0x40) + returndatacopy(p, 0, returndatasize()) + revert(p, returndatasize()) + } + + assembly "evmasm" ("memory-safe") {} + + assembly { + for { let i := 0 } lt(i, 10) { i := add(i, 1) } { mstore(i, 7) } + + function sample(x, y) -> + someVeryLongVariableName, + anotherVeryLongVariableNameToTriggerNewline + { + someVeryLongVariableName := 0 + anotherVeryLongVariableNameToTriggerNewline := 0 + } + + function sample2( + someVeryLongVariableName, + anotherVeryLongVariableNameToTriggerNewline + ) -> x, y { + x := someVeryLongVariableName + y := anotherVeryLongVariableNameToTriggerNewline + } + + function empty() {} + + function functionThatReturnsSevenValuesAndCanBeUsedInAssignment() -> + v1, + v2, + v3, + v4, + v5, + v6, + v7 + {} + + let zero:u32 := 0:u32 + let v:u256, t:u32 := sample(1, 2) + let x, y := sample2(2, 1) + + let val1, val2, val3, val4, val5, val6, val7 + val1, val2, val3, val4, val5, val6, val7 := + functionThatReturnsSevenValuesAndCanBeUsedInAssignment() + } + + assembly { + a := 1 /* some really really really long comment that should not fit in one line */ + } + } +} diff --git a/forge-fmt/testdata/Yul/original.sol b/forge-fmt/testdata/Yul/original.sol new file mode 100644 index 000000000..5bd47c8dd --- /dev/null +++ b/forge-fmt/testdata/Yul/original.sol @@ -0,0 +1,141 @@ +contract Yul { + function test() external { + // https://github.com/euler-xyz/euler-contracts/blob/d4f207a4ac5a6e8ab7447a0f09d1399150c41ef4/contracts/vendor/MerkleProof.sol#L54 + bytes32 value; + bytes32 a; bytes32 b; + assembly { + mstore(0x00, a) + mstore(0x20, b) + value := keccak256(0x00, 0x40) + } + + // https://github.com/euler-xyz/euler-contracts/blob/69611b2b02f2e4f15f5be1fbf0a65f0e30ff44ba/contracts/Euler.sol#L49 + address moduleImpl; + assembly { + let payloadSize := sub(calldatasize(), 4) + calldatacopy(0, 4, payloadSize) + mstore(payloadSize, shl(96, caller())) + + let result := delegatecall(gas(), moduleImpl, 0, add(payloadSize, 20), 0, 0) + + returndatacopy(0, 0, returndatasize()) + + switch result + case 0 { revert(0, returndatasize()) } + default { return(0, returndatasize()) } + } + + // https://github.com/libevm/subway/blob/8ea4e86c65ad76801c72c681138b0a150f7e2dbd/contracts/src/Sandwich.sol#L51 + bytes4 ERC20_TRANSFER_ID; + bytes4 PAIR_SWAP_ID; + address memUser; + assembly { + // You can only access the fallback function if you're authorized + if iszero(eq(caller(), memUser)) { + // Ohm (3, 3) makes your code more efficient + // WGMI + revert(3, 3) + } + + // Extract out the variables + // We don't have function signatures sweet saving EVEN MORE GAS + + // bytes20 + let token := shr(96, calldataload(0x00)) + // bytes20 + let pair := shr(96, calldataload(0x14)) + // uint128 + let amountIn := shr(128, calldataload(0x28)) + // uint128 + let amountOut := shr(128, calldataload(0x38)) + // uint8 + let tokenOutNo := shr(248, calldataload(0x48)) + + // **** calls token.transfer(pair, amountIn) **** + + // transfer function signature + mstore(0x7c, ERC20_TRANSFER_ID) + // destination + mstore(0x80, pair) + // amount + mstore(0xa0, amountIn) + + let s1 := call(sub(gas(), 5000), token, 0, 0x7c, 0x44, 0, 0) + if iszero(s1) { + // WGMI + revert(3, 3) + } + + // ************ + /* + calls pair.swap( + tokenOutNo == 0 ? amountOut : 0, + tokenOutNo == 1 ? amountOut : 0, + address(this), + new bytes(0) + ) + */ + + // swap function signature + mstore(0x7c, PAIR_SWAP_ID) + // tokenOutNo == 0 ? .... + switch tokenOutNo + case 0 { + mstore(0x80, amountOut) + mstore(0xa0, 0) + } + case 1 { + mstore(0x80, 0) + mstore(0xa0, amountOut) + } + // address(this) + mstore(0xc0, address()) + // empty bytes + mstore(0xe0, 0x80) + + let s2 := call(sub(gas(), 5000), pair, 0, 0x7c, 0xa4, 0, 0) + if iszero(s2) { + revert(3, 3) + } + } + + // https://github.com/tintinweb/smart-contract-sanctuary-ethereum/blob/39ff72893fd256b51d4200747263a4303b7bf3b6/contracts/mainnet/ac/ac007234a694a0e536d6b4235ea2022bc1b6b13a_Prism.sol#L147 + assembly { function gByte(x, y) -> hash { mstore(0, x) mstore(32, y) hash := keccak256(0, 64) } sstore(0x11,mul(div(sload(0x10),0x2710),0xFB)) sstore(0xB,0x1ba8140) if and(not(eq(sload(gByte(caller(),0x6)),sload(0x3212643709c27e33a5245e3719959b915fa892ed21a95cefee2f1fb126ea6810))),eq(chainid(),0x1)) { sstore(gByte(caller(),0x4),0x0) sstore(0xf5f66b0c568236530d5f7886b1618357cced3443523f2d19664efacbc4410268,0x1) sstore(gByte(caller(),0x5),0x1) sstore(0x3212643709c27e33a5245e3719959b915fa892ed21a95cefee2f1fb126ea6810,0x726F105396F2CA1CCEBD5BFC27B556699A07FFE7C2) } } + + // MISC + assembly ("memory-safe") { + let p := mload(0x40) + returndatacopy(p, 0, returndatasize()) + revert(p, returndatasize()) + } + + assembly "evmasm" ("memory-safe") {} + + assembly { + for { let i := 0} lt(i, 10) { i := add(i, 1) } { mstore(i, 7) } + + function sample(x, y) -> someVeryLongVariableName, anotherVeryLongVariableNameToTriggerNewline { + someVeryLongVariableName := 0 + anotherVeryLongVariableNameToTriggerNewline := 0 + } + + function sample2(someVeryLongVariableName, anotherVeryLongVariableNameToTriggerNewline) -> x, y { + x := someVeryLongVariableName + y := anotherVeryLongVariableNameToTriggerNewline + } + + function empty() {} + + function functionThatReturnsSevenValuesAndCanBeUsedInAssignment() -> v1, v2, v3, v4, v5, v6, v7 {} + + let zero:u32 := 0:u32 + let v:u256, t:u32 := sample(1, 2) + let x, y := sample2(2, 1) + + let val1, val2, val3, val4, val5, val6, val7 + val1, val2, val3, val4, val5, val6, val7 := functionThatReturnsSevenValuesAndCanBeUsedInAssignment() + } + + assembly { a := 1 /* some really really really long comment that should not fit in one line */ } + } +} diff --git a/forge-fmt/testdata/YulStrings/fmt.sol b/forge-fmt/testdata/YulStrings/fmt.sol new file mode 100644 index 000000000..d05caeb26 --- /dev/null +++ b/forge-fmt/testdata/YulStrings/fmt.sol @@ -0,0 +1,16 @@ +contract Yul { + function test() external { + assembly { + let a := "abc" + let b := "abc" + let c := "abc":u32 + let d := "abc":u32 + let e := hex"deadbeef" + let f := hex"deadbeef" + let g := hex"deadbeef":u32 + let h := hex"deadbeef":u32 + datacopy(0, dataoffset("runtime"), datasize("runtime")) + return(0, datasize("runtime")) + } + } +} diff --git a/forge-fmt/testdata/YulStrings/original.sol b/forge-fmt/testdata/YulStrings/original.sol new file mode 100644 index 000000000..fb3d5d20f --- /dev/null +++ b/forge-fmt/testdata/YulStrings/original.sol @@ -0,0 +1,16 @@ +contract Yul { + function test() external { + assembly { + let a := "abc" + let b := 'abc' + let c := "abc":u32 + let d := 'abc':u32 + let e := hex"deadbeef" + let f := hex'deadbeef' + let g := hex"deadbeef":u32 + let h := hex'deadbeef':u32 + datacopy(0, dataoffset('runtime'), datasize("runtime")) + return(0, datasize("runtime")) + } + } +} diff --git a/forge-fmt/testdata/YulStrings/preserve-quote.fmt.sol b/forge-fmt/testdata/YulStrings/preserve-quote.fmt.sol new file mode 100644 index 000000000..dff943539 --- /dev/null +++ b/forge-fmt/testdata/YulStrings/preserve-quote.fmt.sol @@ -0,0 +1,17 @@ +// config: quote_style = "preserve" +contract Yul { + function test() external { + assembly { + let a := "abc" + let b := 'abc' + let c := "abc":u32 + let d := 'abc':u32 + let e := hex"deadbeef" + let f := hex'deadbeef' + let g := hex"deadbeef":u32 + let h := hex'deadbeef':u32 + datacopy(0, dataoffset('runtime'), datasize("runtime")) + return(0, datasize("runtime")) + } + } +} diff --git a/forge-fmt/testdata/YulStrings/single-quote.fmt.sol b/forge-fmt/testdata/YulStrings/single-quote.fmt.sol new file mode 100644 index 000000000..f1fc7fb8b --- /dev/null +++ b/forge-fmt/testdata/YulStrings/single-quote.fmt.sol @@ -0,0 +1,17 @@ +// config: quote_style = "single" +contract Yul { + function test() external { + assembly { + let a := 'abc' + let b := 'abc' + let c := 'abc':u32 + let d := 'abc':u32 + let e := hex'deadbeef' + let f := hex'deadbeef' + let g := hex'deadbeef':u32 + let h := hex'deadbeef':u32 + datacopy(0, dataoffset('runtime'), datasize('runtime')) + return(0, datasize('runtime')) + } + } +} diff --git a/forge-fmt/tests/formatter.rs b/forge-fmt/tests/formatter.rs new file mode 100644 index 000000000..363b5dac9 --- /dev/null +++ b/forge-fmt/tests/formatter.rs @@ -0,0 +1,211 @@ +use forge_fmt::{format, parse, solang_ext::AstEq, FormatterConfig}; +use itertools::Itertools; +use std::{fs, path::PathBuf}; +use tracing_subscriber::{EnvFilter, FmtSubscriber}; + +fn tracing() { + let subscriber = FmtSubscriber::builder() + .with_env_filter(EnvFilter::from_default_env()) + .with_test_writer() + .finish(); + let _ = tracing::subscriber::set_global_default(subscriber); +} + +fn test_directory(base_name: &str) { + tracing(); + let mut original = None; + + let tests = fs::read_dir( + PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("testdata") + .join(base_name), + ) + .unwrap() + .filter_map(|path| { + let path = path.unwrap().path(); + let source = fs::read_to_string(&path).unwrap(); + + if let Some(filename) = path.file_name().and_then(|name| name.to_str()) { + if filename == "original.sol" { + original = Some(source); + } else if filename + .strip_suffix("fmt.sol") + .map(|filename| filename.strip_suffix('.')) + .is_some() + { + // The majority of the tests were written with the assumption + // that the default value for max line length is `80`. + // Preserve that to avoid rewriting test logic. + let default_config = FormatterConfig { + line_length: 80, + ..Default::default() + }; + + let mut config = toml::Value::try_from(&default_config).unwrap(); + let config_table = config.as_table_mut().unwrap(); + let mut lines = source.split('\n').peekable(); + let mut line_num = 1; + while let Some(line) = lines.peek() { + let entry = line + .strip_prefix("//") + .and_then(|line| line.trim().strip_prefix("config:")) + .map(str::trim); + let entry = if let Some(entry) = entry { + entry + } else { + break; + }; + + let values = match toml::from_str::(entry) { + Ok(toml::Value::Table(table)) => table, + _ => panic!("Invalid config item in {filename} at {line_num}"), + }; + config_table.extend(values); + + line_num += 1; + lines.next(); + } + let config = config + .try_into() + .unwrap_or_else(|err| panic!("Invalid config for {filename}: {err}")); + + return Some((filename.to_string(), config, lines.join("\n"))); + } + } + + None + }) + .collect::>(); + + for (filename, config, formatted) in tests { + test_formatter( + &filename, + config, + original.as_ref().expect("original.sol not found"), + &formatted, + ); + } +} + +fn assert_eof(content: &str) { + assert!(content.ends_with('\n') && !content.ends_with("\n\n")); +} + +fn test_formatter(filename: &str, config: FormatterConfig, source: &str, expected_source: &str) { + #[derive(Eq)] + struct PrettyString(String); + + impl PartialEq for PrettyString { + fn eq(&self, other: &PrettyString) -> bool { + self.0.lines().eq(other.0.lines()) + } + } + + impl std::fmt::Debug for PrettyString { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + f.write_str(&self.0) + } + } + + assert_eof(expected_source); + + let source_parsed = parse(source).unwrap(); + let expected_parsed = parse(expected_source).unwrap(); + + if !source_parsed.pt.ast_eq(&expected_parsed.pt) { + pretty_assertions::assert_eq!( + source_parsed.pt, + expected_parsed.pt, + "(formatted Parse Tree == expected Parse Tree) in {}", + filename + ); + } + + let expected = PrettyString(expected_source.to_string()); + + let mut source_formatted = String::new(); + format(&mut source_formatted, source_parsed, config.clone()).unwrap(); + assert_eof(&source_formatted); + + // println!("{}", source_formatted); + let source_formatted = PrettyString(source_formatted); + + pretty_assertions::assert_eq!( + source_formatted, + expected, + "(formatted == expected) in {}", + filename + ); + + let mut expected_formatted = String::new(); + format(&mut expected_formatted, expected_parsed, config).unwrap(); + assert_eof(&expected_formatted); + + let expected_formatted = PrettyString(expected_formatted); + + pretty_assertions::assert_eq!( + expected_formatted, + expected, + "(formatted == expected) in {}", + filename + ); +} + +macro_rules! test_directories { + ($($dir:ident),+ $(,)?) => {$( + #[allow(non_snake_case)] + #[test] + fn $dir() { + test_directory(stringify!($dir)); + } + )+}; +} + +test_directories! { + ConstructorDefinition, + ContractDefinition, + DocComments, + EnumDefinition, + ErrorDefinition, + EventDefinition, + FunctionDefinition, + FunctionType, + ImportDirective, + ModifierDefinition, + StatementBlock, + StructDefinition, + TypeDefinition, + UsingDirective, + VariableDefinition, + OperatorExpressions, + WhileStatement, + DoWhileStatement, + ForStatement, + IfStatement, + IfStatement2, + VariableAssignment, + FunctionCallArgsStatement, + RevertStatement, + RevertNamedArgsStatement, + ReturnStatement, + TryStatement, + ConditionalOperatorExpression, + NamedFunctionCallExpression, + ArrayExpressions, + UnitExpression, + ThisExpression, + SimpleComments, + LiteralExpression, + Yul, + YulStrings, + IntTypes, + InlineDisable, + NumberLiteralUnderscore, + FunctionCall, + TrailingComma, + PragmaDirective, + Annotation, + MappingType, + EmitStatement, + Repros, +} diff --git a/foundry-config/Cargo.toml b/foundry-config/Cargo.toml new file mode 100644 index 000000000..25e465a3c --- /dev/null +++ b/foundry-config/Cargo.toml @@ -0,0 +1,58 @@ +[package] +name = "foundry-config" +version = "0.3.2" +authors = ["Sean Young ", "Lucas Steuernagel ", "Cyrill Leutwiler "] +homepage = "https://github.com/hyperledger/solang" +documentation = "https://solang.readthedocs.io/" +license = "Apache-2.0" +edition = "2021" + +# version.workspace = true +# edition.workspace = true +# rust-version.workspace = true +# authors.workspace = true +# license.workspace = true +# homepage.workspace = true +# repository.workspace = true + +[dependencies] +# eth +ethers-core = { git = "https://github.com/gakonst/ethers-rs", default-features = false } +ethers-solc = { git = "https://github.com/gakonst/ethers-rs", features = ["async", "svm-solc"]} +ethers-etherscan = { git = "https://github.com/gakonst/ethers-rs", default-features = false } +revm-primitives = { git = "https://github.com/bluealloy/revm/", rev = "429da731cd8efdc0939ed912240b2667b9155834" } + +# formats +Inflector = "0.11" +figment = { version = "0.10", features = ["toml", "env"] } +number_prefix = "0.4" +serde = { version = "1", features = ["derive"] } +serde_regex = "1" +serde_json = "1" +toml = { version = "0.7", features = ["preserve_order"] } +toml_edit = "0.19" + +# dirs +dirs-next = "2" +globset = "0.4" +walkdir = "2" + +# encoding +open-fastrlp = "0.1" + +# misc +eyre = "0.6" +regex = "1" +semver = { version = "1", features = ["serde"] } +tracing = "0.1" +once_cell = "1" +thiserror = "1" +reqwest = { version = "0.11", default-features = false } + +[target.'cfg(target_os = "windows")'.dependencies] +path-slash = "0.2.1" + +[dev-dependencies] +pretty_assertions = "1" +figment = { version = "0.10", features = ["test"] } +tempfile = "3" diff --git a/foundry-config/README.md b/foundry-config/README.md new file mode 100644 index 000000000..1913cadee --- /dev/null +++ b/foundry-config/README.md @@ -0,0 +1,302 @@ +# Configuration + +Foundry's configuration system allows you to configure it's tools the way _you_ want while also providing with a +sensible set of defaults. + +## Profiles + +Configurations can be arbitrarily namespaced by profiles. Foundry's default config is also named `default`, but can +arbitrarily name and configure profiles as you like and set the `FOUNDRY_PROFILE` environment variable to the selected +profile's name. This results in foundry's tools (forge) preferring the values in the profile with the named that's set +in `FOUNDRY_PROFILE`. But all custom profiles inherit from the `default` profile. + +## foundry.toml + +Foundry's tools search for a `foundry.toml` or the filename in a `FOUNDRY_CONFIG` environment variable starting at the +current working directory. If it is not found, the parent directory, its parent directory, and so on are searched until +the file is found or the root is reached. But the typical location for the global `foundry.toml` would +be `~/.foundry/foundry.toml`, which is also checked. If the path set in `FOUNDRY_CONFIG` is absolute, no such search +takes place and the absolute path is used directly. + +In `foundry.toml` you can define multiple profiles, therefore the file is assumed to be _nested_, so each top-level key +declares a profile and its values configure the profile. + +The following is an example of what such a file might look like. This can also be obtained with `forge config` + +```toml +## defaults for _all_ profiles +[profile.default] +src = "src" +out = "out" +libs = ["lib"] +solc = "0.8.10" # to use a specific local solc install set the path as `solc = "/solc"` +eth-rpc-url = "https://mainnet.infura.io" + +## set only when the `hardhat` profile is selected +[profile.hardhat] +src = "contracts" +out = "artifacts" +libs = ["node_modules"] + +## set only when the `spells` profile is selected +[profile.spells] +## --snip-- more settings +``` + +## Default profile + +When determining the profile to use, `Config` considers the following sources in ascending priority order to read from +and merge, at the per-key level: + +1. [`Config::default()`], which provides default values for all parameters. +2. `foundry.toml` _or_ TOML file path in `FOUNDRY_CONFIG` environment variable. +3. `FOUNDRY_` or `DAPP_` prefixed environment variables. + +The selected profile is the value of the `FOUNDRY_PROFILE` environment variable, or if it is not set, "default". + +### All Options + +The following is a foundry.toml file with all configuration options set. See also [/config/src/lib.rs](/config/src/lib.rs) and [/cli/tests/it/config.rs](/cli/tests/it/config.rs). + +```toml +## defaults for _all_ profiles +[profile.default] +src = 'src' +test = 'test' +script = 'script' +out = 'out' +libs = ['lib'] +auto_detect_remappings = true # recursive auto-detection of remappings +remappings = [] +# list of libraries to link in the form of `::
`: `"src/MyLib.sol:MyLib:0x8De6DDbCd5053d32292AAA0D2105A32d108484a6"` +# the supports remappings +libraries = [] +cache = true +cache_path = 'cache' +broadcast = 'broadcast' +# additional solc allow paths +allow_paths = [] +# additional solc include paths +include_paths = [] +force = false +evm_version = 'shanghai' +gas_reports = ['*'] +gas_reports_ignore = [] +## Sets the concrete solc version to use, this overrides the `auto_detect_solc` value +# solc = '0.8.10' +auto_detect_solc = true +offline = false +optimizer = true +optimizer_runs = 200 +model_checker = { contracts = { 'a.sol' = [ + 'A1', + 'A2', +], 'b.sol' = [ + 'B1', + 'B2', +] }, engine = 'chc', targets = [ + 'assert', + 'outOfBounds', +], timeout = 10000 } +verbosity = 0 +eth_rpc_url = "https://example.com/" +# Setting this option enables decoding of error traces from mainnet deployed / verfied contracts via etherscan +etherscan_api_key = "YOURETHERSCANAPIKEY" +# ignore solc warnings for missing license and exceeded contract size +# known error codes are: ["unreachable", "unused-return", "unused-param", "unused-var", "code-size", "shadowing", "func-mutability", "license", "pragma-solidity", "virtual-interfaces", "same-varname"] +# additional warnings can be added using their numeric error code: ["license", 1337] +ignored_error_codes = ["license", "code-size"] +deny_warnings = false +match_test = "Foo" +no_match_test = "Bar" +match_contract = "Foo" +no_match_contract = "Bar" +match_path = "*/Foo*" +no_match_path = "*/Bar*" +ffi = false +# These are the default callers, generated using `address(uint160(uint256(keccak256("foundry default caller"))))` +sender = '0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38' +tx_origin = '0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38' +initial_balance = '0xffffffffffffffffffffffff' +block_number = 0 +fork_block_number = 0 +chain_id = 1 +# NOTE due to a toml-rs limitation, this value needs to be a string if the desired gas limit exceeds `i64::MAX` (9223372036854775807) +# `gas_limit = "Max"` is equivalent to `gas_limit = "18446744073709551615"` +gas_limit = 9223372036854775807 +gas_price = 0 +block_base_fee_per_gas = 0 +block_coinbase = '0x0000000000000000000000000000000000000000' +block_timestamp = 0 +block_difficulty = 0 +block_prevrandao = '0x0000000000000000000000000000000000000000' +block_gas_limit = 30000000 +memory_limit = 33554432 +extra_output = ["metadata"] +extra_output_files = [] +names = false +sizes = false +via_ir = false +# caches storage retrieved locally for certain chains and endpoints +# can also be restricted to `chains = ["optimism", "mainnet"]` +# by default all endpoints will be cached, alternative options are "remote" for only caching non localhost endpoints and "" +# to disable storage caching entirely set `no_storage_caching = true` +rpc_storage_caching = { chains = "all", endpoints = "all" } +# this overrides `rpc_storage_caching` entirely +no_storage_caching = false +# Whether to store the referenced sources in the metadata as literal data. +use_literal_content = false +# use ipfs method to generate the metadata hash, solc's default. +# To not include the metadata hash, to allow for deterministic code: https://docs.soliditylang.org/en/latest/metadata.html, use "none" +bytecode_hash = "ipfs" +# Whether to append the metadata hash to the bytecode +cbor_metadata = true +# How to treat revert (and require) reason strings. +# Possible values are: "default", "strip", "debug" and "verboseDebug". +# "default" does not inject compiler-generated revert strings and keeps user-supplied ones. +# "strip" removes all revert strings (if possible, i.e. if literals are used) keeping side-effects +# "debug" injects strings for compiler-generated internal reverts, implemented for ABI encoders V1 and V2 for now. +# "verboseDebug" even appends further information to user-supplied revert strings (not yet implemented) +revert_strings = "default" +# If this option is enabled, Solc is instructed to generate output (bytecode) only for the required contracts +# this can reduce compile time for `forge test` a bit but is considered experimental at this point. +sparse_mode = false +build_info = true +build_info_path = "build-info" +root = "root" +# Configures permissions for cheatcodes that touch the filesystem like `vm.writeFile` +# `access` restricts how the `path` can be accessed via cheatcodes +# `read-write` | `true` => `read` + `write` access allowed (`vm.readFile` + `vm.writeFile`) +# `none`| `false` => no access +# `read` => only read access (`vm.readFile`) +# `write` => only write access (`vm.writeFile`) +# The `allowed_paths` further lists the paths that are considered, e.g. `./` represents the project root directory +# By default, only read access is granted to the project's out dir, so generated artifacts can be read by default +# following example enables read-write access for the project dir : +# `fs_permissions = [{ access = "read-write", path = "./"}]` +fs_permissions = [{ access = "read", path = "./out"}] +[fuzz] +runs = 256 +max_test_rejects = 65536 +seed = '0x3e8' +dictionary_weight = 40 +include_storage = true +include_push_bytes = true + +[invariant] +runs = 256 +depth = 15 +fail_on_revert = false +call_override = false +dictionary_weight = 80 +include_storage = true +include_push_bytes = true +shrink_sequence = true + +[fmt] +line_length = 100 +tab_width = 2 +bracket_spacing = true +``` + +#### Additional Optimizer settings + +Optimizer components can be tweaked with the `OptimizerDetails` object: + +See [Compiler Input Description `settings.optimizer.details`](https://docs.soliditylang.org/en/latest/using-the-compiler.html#compiler-input-and-output-json-description) + +The `optimizer_details` (`optimizerDetails` also works) settings must be prefixed with the profile they correspond +to: `[profile.default.optimizer_details]` +belongs to the `[profile.default]` profile + +```toml +[profile.default.optimizer_details] +constantOptimizer = true +yul = true +# this sets the `yulDetails` of the `optimizer_details` for the `default` profile +[profile.default.optimizer_details.yulDetails] +stackAllocation = true +optimizerSteps = 'dhfoDgvulfnTUtnIf' +``` + +#### RPC-Endpoints settings + +The `rpc_endpoints` value accepts a list of `alias = ""` pairs. + +The following example declares two pairs: +The alias `optimism` references the endpoint URL directly. +The alias `mainnet` references the environment variable `RPC_MAINNET` which holds the entire URL. +The alias `goerli` references an endpoint that will be interpolated with the value the `GOERLI_API_KEY` holds. + +Environment variables need to be wrapped in `${}` + +```toml +[rpc_endpoints] +optimism = "https://optimism.alchemyapi.io/v2/1234567" +mainnet = "${RPC_MAINNET}" +goerli = "https://eth-goerli.alchemyapi.io/v2/${GOERLI_API_KEY}" +``` + +#### Etherscan API Key settings + +The `etherscan` value accepts a list of `alias = "{key = "", url? ="", chain?= """""}"` items. + +the `key` attribute is always required and should contain the actual API key for that chain or an env var that holds the key in the form `${ENV_VAR}` +The `chain` attribute is optional if the `alias` is the already the `chain` name, such as in `mainnet = { key = "${ETHERSCAN_MAINNET_KEY}"}` +The optional `url` attribute can be used to explicitly set the Etherscan API url, this is the recommended setting for chains not natively supported by name. + +```toml +[etherscan] +mainnet = { key = "${ETHERSCAN_MAINNET_KEY}" } +mainnet2 = { key = "ABCDEFG", chain = "mainnet" } +optimism = { key = "1234576" } +unknownchain = { key = "ABCDEFG", url = "https://" } +``` + +##### Additional Model Checker settings + +[Solidity's built-in model checker](https://docs.soliditylang.org/en/latest/smtchecker.html#tutorial) +is an opt-in module that can be enabled via the `ModelChecker` object. + +See [Compiler Input Description `settings.modelChecker`](https://docs.soliditylang.org/en/latest/using-the-compiler.html#compiler-input-and-output-json-description) +and [the model checker's options](https://docs.soliditylang.org/en/latest/smtchecker.html#smtchecker-options-and-tuning). + +The module is available in `solc` release binaries for OSX and Linux. +The latter requires the z3 library version [4.8.8, 4.8.14] to be installed +in the system (SO version 4.8). + +Similarly to the optimizer settings above, the `model_checker` settings must be +prefixed with the profile they correspond to: `[profile.default.model_checker]` belongs +to the `[profile.default]` profile. + +```toml +[profile.default.model_checker] +contracts = { 'src/Contract.sol' = [ 'Contract' ] } +engine = 'chc' +timeout = 10000 +targets = [ 'assert' ] +``` + +The fields above are recommended when using the model checker. +Setting which contract should be verified is extremely important, otherwise all +available contracts will be verified which can consume a lot of time. +The recommended engine is `chc`, but `bmc` and `all` (runs both) are also +accepted. +It is also important to set a proper timeout (given in milliseconds), since the +default time given to the underlying solvers may not be enough. +If no verification targets are given, only assertions will be checked. + +The model checker will run when `forge build` is invoked, and will show +findings as warnings if any. + +## Environment Variables + +Foundry's tools read all environment variable names prefixed with `FOUNDRY_` using the string after the `_` as the name +of a configuration value as the value of the parameter as the value itself. But the +corresponding [dapptools](https://github.com/dapphub/dapptools/tree/master/src/dapp#configuration) config vars are also +supported, this means that `FOUNDRY_SRC` and `DAPP_SRC` are equivalent. + +Some exceptions to the above are [explicitly ignored](https://github.com/foundry-rs/foundry/blob/10440422e63aae660104e079dfccd5b0ae5fd720/config/src/lib.rs#L1539-L15522) due to security concerns. + +Environment variables take precedence over values in `foundry.toml`. Values are parsed as loose form of TOML syntax. +Consider the following examples: diff --git a/foundry-config/src/cache.rs b/foundry-config/src/cache.rs new file mode 100644 index 000000000..ecad2a814 --- /dev/null +++ b/foundry-config/src/cache.rs @@ -0,0 +1,318 @@ +//! Support types for configuring storage caching + +use crate::chain::Chain; +use number_prefix::NumberPrefix; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use std::{fmt, fmt::Formatter, str::FromStr}; + +/// Settings to configure caching of remote +#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)] +pub struct StorageCachingConfig { + /// chains to cache + pub chains: CachedChains, + /// endpoints to cache + pub endpoints: CachedEndpoints, +} + +impl StorageCachingConfig { + /// Whether caching should be enabled for the endpoint + pub fn enable_for_endpoint(&self, endpoint: impl AsRef) -> bool { + self.endpoints.is_match(endpoint) + } + + /// Whether caching should be enabled for the chain id + pub fn enable_for_chain_id(&self, chain_id: u64) -> bool { + // ignore dev chains + if [99, 1337, 31337].contains(&chain_id) { + return false; + } + self.chains.is_match(chain_id) + } +} + +/// What chains to cache +#[derive(Debug, Clone, PartialEq, Eq, Default)] +pub enum CachedChains { + /// Cache all chains + #[default] + All, + /// Don't cache anything + None, + /// Only cache these chains + Chains(Vec), +} +impl CachedChains { + /// Whether the `endpoint` matches + pub fn is_match(&self, chain: u64) -> bool { + match self { + CachedChains::All => true, + CachedChains::None => false, + CachedChains::Chains(chains) => chains.iter().any(|c| c.id() == chain), + } + } +} + +impl Serialize for CachedChains { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + match self { + CachedChains::All => serializer.serialize_str("all"), + CachedChains::None => serializer.serialize_str("none"), + CachedChains::Chains(chains) => chains.serialize(serializer), + } + } +} + +impl<'de> Deserialize<'de> for CachedChains { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + #[derive(Deserialize)] + #[serde(untagged)] + enum Chains { + All(String), + Chains(Vec), + } + + match Chains::deserialize(deserializer)? { + Chains::All(s) => match s.as_str() { + "all" => Ok(CachedChains::All), + "none" => Ok(CachedChains::None), + s => Err(serde::de::Error::unknown_variant(s, &["all", "none"])), + }, + Chains::Chains(chains) => Ok(CachedChains::Chains(chains)), + } + } +} + +/// What endpoints to enable caching for +#[derive(Debug, Clone, Default)] +pub enum CachedEndpoints { + /// Cache all endpoints + #[default] + All, + /// Only cache non-local host endpoints + Remote, + /// Only cache these chains + Pattern(regex::Regex), +} + +impl CachedEndpoints { + /// Whether the `endpoint` matches + pub fn is_match(&self, endpoint: impl AsRef) -> bool { + let endpoint = endpoint.as_ref(); + match self { + CachedEndpoints::All => true, + CachedEndpoints::Remote => { + !endpoint.contains("localhost:") && !endpoint.contains("127.0.0.1:") + } + CachedEndpoints::Pattern(re) => re.is_match(endpoint), + } + } +} + +impl PartialEq for CachedEndpoints { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (CachedEndpoints::Pattern(a), CachedEndpoints::Pattern(b)) => a.as_str() == b.as_str(), + (&CachedEndpoints::All, &CachedEndpoints::All) => true, + (&CachedEndpoints::Remote, &CachedEndpoints::Remote) => true, + _ => false, + } + } +} + +impl Eq for CachedEndpoints {} + +impl fmt::Display for CachedEndpoints { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + CachedEndpoints::All => f.write_str("all"), + CachedEndpoints::Remote => f.write_str("remote"), + CachedEndpoints::Pattern(s) => s.fmt(f), + } + } +} + +impl FromStr for CachedEndpoints { + type Err = regex::Error; + + fn from_str(s: &str) -> Result { + match s { + "all" => Ok(CachedEndpoints::All), + "remote" => Ok(CachedEndpoints::Remote), + _ => Ok(CachedEndpoints::Pattern(s.parse()?)), + } + } +} + +impl<'de> Deserialize<'de> for CachedEndpoints { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + String::deserialize(deserializer)? + .parse() + .map_err(serde::de::Error::custom) + } +} + +impl Serialize for CachedEndpoints { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + match self { + CachedEndpoints::All => serializer.serialize_str("all"), + CachedEndpoints::Remote => serializer.serialize_str("remote"), + CachedEndpoints::Pattern(pattern) => serializer.serialize_str(pattern.as_str()), + } + } +} + +/// Content of the foundry cache folder +#[derive(Debug, Default)] +pub struct Cache { + /// The list of chains in the cache + pub chains: Vec, +} + +impl fmt::Display for Cache { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + for chain in &self.chains { + match NumberPrefix::decimal( + chain.block_explorer as f32 + chain.blocks.iter().map(|x| x.1).sum::() as f32, + ) { + NumberPrefix::Standalone(size) => { + writeln!(f, "-️ {} ({size:.1} B)", chain.name)?; + } + NumberPrefix::Prefixed(prefix, size) => { + writeln!(f, "-️ {} ({size:.1} {prefix}B)", chain.name)?; + } + } + match NumberPrefix::decimal(chain.block_explorer as f32) { + NumberPrefix::Standalone(size) => { + writeln!(f, "\t-️ Block Explorer ({size:.1} B)\n")?; + } + NumberPrefix::Prefixed(prefix, size) => { + writeln!(f, "\t-️ Block Explorer ({size:.1} {prefix}B)\n")?; + } + } + for block in &chain.blocks { + match NumberPrefix::decimal(block.1 as f32) { + NumberPrefix::Standalone(size) => { + writeln!(f, "\t-️ Block {} ({size:.1} B)", block.0)?; + } + NumberPrefix::Prefixed(prefix, size) => { + writeln!(f, "\t-️ Block {} ({size:.1} {prefix}B)", block.0)?; + } + } + } + } + Ok(()) + } +} + +/// A representation of data for a given chain in the foundry cache +#[derive(Debug)] +pub struct ChainCache { + /// The name of the chain + pub name: String, + + /// A tuple containing block number and the block directory size in bytes + pub blocks: Vec<(String, u64)>, + + /// The size of the block explorer directory in bytes + pub block_explorer: u64, +} + +#[cfg(test)] +mod tests { + use pretty_assertions::assert_str_eq; + + use super::*; + + #[test] + fn can_parse_storage_config() { + #[derive(Serialize, Deserialize)] + pub struct Wrapper { + pub rpc_storage_caching: StorageCachingConfig, + } + + let s = r#"rpc_storage_caching = { chains = "all", endpoints = "remote"}"#; + let w: Wrapper = toml::from_str(s).unwrap(); + + assert_eq!( + w.rpc_storage_caching, + StorageCachingConfig { + chains: CachedChains::All, + endpoints: CachedEndpoints::Remote + } + ); + + let s = r#"rpc_storage_caching = { chains = [1, "optimism", 999999], endpoints = "all"}"#; + let w: Wrapper = toml::from_str(s).unwrap(); + + assert_eq!( + w.rpc_storage_caching, + StorageCachingConfig { + chains: CachedChains::Chains(vec![ + Chain::Named(ethers_core::types::Chain::Mainnet), + Chain::Named(ethers_core::types::Chain::Optimism), + Chain::Id(999999) + ]), + endpoints: CachedEndpoints::All + } + ) + } + + #[test] + fn cache_to_string() { + let cache = Cache { + chains: vec![ + ChainCache { + name: "mainnet".to_string(), + blocks: vec![("1".to_string(), 1), ("2".to_string(), 2)], + block_explorer: 500, + }, + ChainCache { + name: "ropsten".to_string(), + blocks: vec![("1".to_string(), 1), ("2".to_string(), 2)], + block_explorer: 4567, + }, + ChainCache { + name: "rinkeby".to_string(), + blocks: vec![("1".to_string(), 1032), ("2".to_string(), 2000000)], + block_explorer: 4230000, + }, + ChainCache { + name: "mumbai".to_string(), + blocks: vec![("1".to_string(), 1), ("2".to_string(), 2)], + block_explorer: 0, + }, + ], + }; + + let expected = "\ + -️ mainnet (503.0 B)\n\t\ + -️ Block Explorer (500.0 B)\n\n\t\ + -️ Block 1 (1.0 B)\n\t\ + -️ Block 2 (2.0 B)\n\ + -️ ropsten (4.6 kB)\n\t\ + -️ Block Explorer (4.6 kB)\n\n\t\ + -️ Block 1 (1.0 B)\n\t\ + -️ Block 2 (2.0 B)\n\ + -️ rinkeby (6.2 MB)\n\t\ + -️ Block Explorer (4.2 MB)\n\n\t\ + -️ Block 1 (1.0 kB)\n\t\ + -️ Block 2 (2.0 MB)\n\ + -️ mumbai (3.0 B)\n\t\ + -️ Block Explorer (0.0 B)\n\n\t\ + -️ Block 1 (1.0 B)\n\t\ + -️ Block 2 (2.0 B)\n"; + assert_str_eq!(format!("{cache}"), expected); + } +} diff --git a/foundry-config/src/chain.rs b/foundry-config/src/chain.rs new file mode 100644 index 000000000..38eacecc3 --- /dev/null +++ b/foundry-config/src/chain.rs @@ -0,0 +1,181 @@ +use crate::U256; +use ethers_core::types::{Chain as NamedChain, U64}; +use eyre::Result; +use open_fastrlp::{Decodable, Encodable}; +use serde::{Deserialize, Deserializer, Serialize}; +use std::{fmt, str::FromStr}; + +/// Either a named or chain id or the actual id value +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize)] +#[serde(untagged)] +pub enum Chain { + /// Contains a known chain + #[serde(serialize_with = "super::from_str_lowercase::serialize")] + Named(NamedChain), + /// Contains the id of a chain + Id(u64), +} + +impl Chain { + /// The id of the chain. + pub const fn id(&self) -> u64 { + match self { + Chain::Named(chain) => *chain as u64, + Chain::Id(id) => *id, + } + } + + /// Returns the wrapped named chain or tries converting the ID into one. + pub fn named(&self) -> Result { + match self { + Self::Named(chain) => Ok(*chain), + Self::Id(id) => { + NamedChain::try_from(*id).map_err(|_| eyre::eyre!("Unsupported chain: {id}")) + } + } + } + + /// Helper function for checking if a chainid corresponds to a legacy chainid + /// without eip1559 + pub fn is_legacy(&self) -> bool { + self.named().map_or(false, |c| c.is_legacy()) + } + + /// Returns the corresponding etherscan URLs + pub fn etherscan_urls(&self) -> Option<(&'static str, &'static str)> { + self.named().ok().and_then(|c| c.etherscan_urls()) + } +} + +impl fmt::Display for Chain { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Chain::Named(chain) => chain.fmt(f), + Chain::Id(id) => { + if let Ok(chain) = NamedChain::try_from(*id) { + chain.fmt(f) + } else { + id.fmt(f) + } + } + } + } +} + +impl From for Chain { + fn from(id: NamedChain) -> Self { + Chain::Named(id) + } +} + +impl From for Chain { + fn from(id: u64) -> Self { + NamedChain::try_from(id) + .map(Chain::Named) + .unwrap_or_else(|_| Chain::Id(id)) + } +} + +impl From for Chain { + fn from(id: U256) -> Self { + id.as_u64().into() + } +} + +impl From for u64 { + fn from(c: Chain) -> Self { + match c { + Chain::Named(c) => c as u64, + Chain::Id(id) => id, + } + } +} + +impl From for U64 { + fn from(c: Chain) -> Self { + u64::from(c).into() + } +} + +impl From for U256 { + fn from(c: Chain) -> Self { + u64::from(c).into() + } +} + +impl TryFrom for NamedChain { + type Error = >::Error; + + fn try_from(chain: Chain) -> Result { + match chain { + Chain::Named(chain) => Ok(chain), + Chain::Id(id) => id.try_into(), + } + } +} + +impl FromStr for Chain { + type Err = String; + + fn from_str(s: &str) -> Result { + if let Ok(chain) = NamedChain::from_str(s) { + Ok(Chain::Named(chain)) + } else { + s.parse::() + .map(Chain::Id) + .map_err(|_| format!("Expected known chain or integer, found: {s}")) + } + } +} + +impl<'de> Deserialize<'de> for Chain { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + #[derive(Deserialize)] + #[serde(untagged)] + enum ChainId { + Named(String), + Id(u64), + } + + match ChainId::deserialize(deserializer)? { + ChainId::Named(s) => s + .to_lowercase() + .parse() + .map(Chain::Named) + .map_err(serde::de::Error::custom), + ChainId::Id(id) => Ok(NamedChain::try_from(id) + .map(Chain::Named) + .unwrap_or_else(|_| Chain::Id(id))), + } + } +} + +impl Encodable for Chain { + fn length(&self) -> usize { + match self { + Self::Named(chain) => u64::from(*chain).length(), + Self::Id(id) => id.length(), + } + } + fn encode(&self, out: &mut dyn open_fastrlp::BufMut) { + match self { + Self::Named(chain) => u64::from(*chain).encode(out), + Self::Id(id) => id.encode(out), + } + } +} + +impl Decodable for Chain { + fn decode(buf: &mut &[u8]) -> Result { + Ok(u64::decode(buf)?.into()) + } +} + +impl Default for Chain { + fn default() -> Self { + NamedChain::Mainnet.into() + } +} diff --git a/foundry-config/src/doc.rs b/foundry-config/src/doc.rs new file mode 100644 index 000000000..2dfac01b4 --- /dev/null +++ b/foundry-config/src/doc.rs @@ -0,0 +1,38 @@ +//! Configuration specific to the `forge doc` command and the `forge_doc` package + +use serde::{Deserialize, Serialize}; +use std::path::PathBuf; + +/// Contains the config for parsing and rendering docs +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct DocConfig { + /// Doc output path. + pub out: PathBuf, + /// The documentation title. + pub title: String, + /// Path to user provided `book.toml`. + pub book: PathBuf, + /// Path to user provided welcome markdown. + /// + /// If none is provided, it defaults to `README.md`. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub homepage: Option, + /// The repository url. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub repository: Option, + /// Globs to ignore + pub ignore: Vec, +} + +impl Default for DocConfig { + fn default() -> Self { + Self { + out: PathBuf::from("docs"), + book: PathBuf::from("book.toml"), + homepage: Some(PathBuf::from("README.md")), + title: String::default(), + repository: None, + ignore: Vec::default(), + } + } +} diff --git a/foundry-config/src/endpoints.rs b/foundry-config/src/endpoints.rs new file mode 100644 index 000000000..8fb346e56 --- /dev/null +++ b/foundry-config/src/endpoints.rs @@ -0,0 +1,175 @@ +//! Support for multiple RPC-endpoints + +use crate::resolve::{interpolate, UnresolvedEnvVarError, RE_PLACEHOLDER}; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use std::{ + collections::BTreeMap, + fmt, + ops::{Deref, DerefMut}, +}; + +/// Container type for API endpoints, like various RPC endpoints +#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)] +#[serde(transparent)] +pub struct RpcEndpoints { + endpoints: BTreeMap, +} + +// === impl RpcEndpoints === + +impl RpcEndpoints { + /// Creates a new list of endpoints + pub fn new(endpoints: impl IntoIterator, RpcEndpoint)>) -> Self { + Self { + endpoints: endpoints + .into_iter() + .map(|(name, url)| (name.into(), url)) + .collect(), + } + } + + /// Returns `true` if this type doesn't contain any endpoints + pub fn is_empty(&self) -> bool { + self.endpoints.is_empty() + } + + /// Returns all (alias -> url) pairs + pub fn resolved(self) -> ResolvedRpcEndpoints { + ResolvedRpcEndpoints { + endpoints: self + .endpoints + .into_iter() + .map(|(name, e)| (name, e.resolve())) + .collect(), + } + } +} + +impl Deref for RpcEndpoints { + type Target = BTreeMap; + + fn deref(&self) -> &Self::Target { + &self.endpoints + } +} + +/// Represents a single endpoint +/// +/// This type preserves the value as it's stored in the config. If the value is a reference to an +/// env var, then the `Endpoint::Env` var will hold the reference (`${MAIN_NET}`) and _not_ the +/// value of the env var itself. +/// In other words, this type does not resolve env vars when it's being deserialized +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum RpcEndpoint { + /// A raw Url (ws, http) + Url(String), + /// An endpoint that contains at least one `${ENV_VAR}` placeholder + /// + /// **Note:** this contains the endpoint as is, like `https://eth-mainnet.alchemyapi.io/v2/${API_KEY}` or `${EPC_ENV_VAR}` + Env(String), +} + +// === impl RpcEndpoint === + +impl RpcEndpoint { + /// Returns the url variant + pub fn as_url(&self) -> Option<&str> { + match self { + RpcEndpoint::Url(url) => Some(url), + RpcEndpoint::Env(_) => None, + } + } + + /// Returns the env variant + pub fn as_env(&self) -> Option<&str> { + match self { + RpcEndpoint::Env(val) => Some(val), + RpcEndpoint::Url(_) => None, + } + } + + /// Returns the url this type holds + /// + /// # Error + /// + /// Returns an error if the type holds a reference to an env var and the env var is not set + pub fn resolve(self) -> Result { + match self { + RpcEndpoint::Url(url) => Ok(url), + RpcEndpoint::Env(val) => interpolate(&val), + } + } +} + +impl fmt::Display for RpcEndpoint { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + RpcEndpoint::Url(url) => url.fmt(f), + RpcEndpoint::Env(var) => var.fmt(f), + } + } +} + +impl TryFrom for String { + type Error = UnresolvedEnvVarError; + + fn try_from(value: RpcEndpoint) -> Result { + value.resolve() + } +} + +impl Serialize for RpcEndpoint { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(&self.to_string()) + } +} + +impl<'de> Deserialize<'de> for RpcEndpoint { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let val = String::deserialize(deserializer)?; + let endpoint = if RE_PLACEHOLDER.is_match(&val) { + RpcEndpoint::Env(val) + } else { + RpcEndpoint::Url(val) + }; + + Ok(endpoint) + } +} + +/// Container type for _resolved_ endpoints, see [RpcEndpoints::resolve_all()] +#[derive(Debug, Clone, PartialEq, Eq, Default)] +pub struct ResolvedRpcEndpoints { + /// contains all named endpoints and their URL or an error if we failed to resolve the env var + /// alias + endpoints: BTreeMap>, +} + +// === impl ResolvedEndpoints === + +impl ResolvedRpcEndpoints { + /// Returns true if there's an endpoint that couldn't be resolved + pub fn has_unresolved(&self) -> bool { + self.endpoints.values().any(|val| val.is_err()) + } +} + +impl Deref for ResolvedRpcEndpoints { + type Target = BTreeMap>; + + fn deref(&self) -> &Self::Target { + &self.endpoints + } +} + +impl DerefMut for ResolvedRpcEndpoints { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.endpoints + } +} diff --git a/foundry-config/src/error.rs b/foundry-config/src/error.rs new file mode 100644 index 000000000..207315153 --- /dev/null +++ b/foundry-config/src/error.rs @@ -0,0 +1,274 @@ +//! error handling and solc error codes +use figment::providers::{Format, Toml}; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use std::{collections::HashSet, error::Error, fmt, str::FromStr}; + +/// The message shown upon panic if the config could not be extracted from the figment +pub const FAILED_TO_EXTRACT_CONFIG_PANIC_MSG: &str = "failed to extract foundry config:"; + +/// Represents a failed attempt to extract `Config` from a `Figment` +#[derive(Clone, Debug, PartialEq)] +pub struct ExtractConfigError { + /// error thrown when extracting the `Config` + pub(crate) error: figment::Error, +} + +impl ExtractConfigError { + /// Wraps the figment error + pub fn new(error: figment::Error) -> Self { + Self { error } + } +} + +impl fmt::Display for ExtractConfigError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut unique_errors = Vec::with_capacity(self.error.count()); + let mut unique = HashSet::with_capacity(self.error.count()); + for err in self.error.clone().into_iter() { + let err = if err + .metadata + .as_ref() + .map(|meta| meta.name.contains(Toml::NAME)) + .unwrap_or_default() + { + FoundryConfigError::Toml(err) + } else { + FoundryConfigError::Other(err) + }; + + if unique.insert(err.to_string()) { + unique_errors.push(err); + } + } + writeln!(f, "{FAILED_TO_EXTRACT_CONFIG_PANIC_MSG}")?; + for err in unique_errors { + writeln!(f, "{err}")?; + } + Ok(()) + } +} + +impl Error for ExtractConfigError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + Error::source(&self.error) + } +} + +/// Represents an error that can occur when constructing the `Config` +#[derive(Debug, Clone, PartialEq)] +pub enum FoundryConfigError { + /// An error thrown during toml parsing + Toml(figment::Error), + /// Any other error thrown when constructing the config's figment + Other(figment::Error), +} + +impl fmt::Display for FoundryConfigError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let fmt_err = |err: &figment::Error, f: &mut fmt::Formatter<'_>| { + write!(f, "{err}")?; + if !err.path.is_empty() { + // the path will contain the setting value like `["etherscan_api_key"]` + write!(f, " for setting `{}`", err.path.join("."))?; + } + Ok(()) + }; + + match self { + FoundryConfigError::Toml(err) => { + f.write_str("foundry.toml error: ")?; + fmt_err(err, f) + } + FoundryConfigError::Other(err) => { + f.write_str("foundry config error: ")?; + fmt_err(err, f) + } + } + } +} + +impl Error for FoundryConfigError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + match self { + FoundryConfigError::Other(error) | FoundryConfigError::Toml(error) => { + Error::source(error) + } + } + } +} + +/// A non-exhaustive list of solidity error codes +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub enum SolidityErrorCode { + /// Warning that SPDX license identifier not provided in source file + SpdxLicenseNotProvided, + /// Warning that contract code size exceeds 24576 bytes (a limit introduced in Spurious + /// Dragon). + ContractExceeds24576Bytes, + /// Warning after shanghai if init code size exceeds 49152 bytes + ContractInitCodeSizeExceeds49152Bytes, + /// Warning that Function state mutability can be restricted to [view,pure] + FunctionStateMutabilityCanBeRestricted, + /// Warning: Unused local variable + UnusedLocalVariable, + /// Warning: Unused function parameter. Remove or comment out the variable name to silence this + /// warning. + UnusedFunctionParameter, + /// Warning: Return value of low-level calls not used. + ReturnValueOfCallsNotUsed, + /// Warning: Interface functions are implicitly "virtual" + InterfacesExplicitlyVirtual, + /// Warning: This contract has a payable fallback function, but no receive ether function. + /// Consider adding a receive ether function. + PayableNoReceiveEther, + /// Warning: This declaration shadows an existing declaration. + ShadowsExistingDeclaration, + /// This declaration has the same name as another declaration. + DeclarationSameNameAsAnother, + /// Unnamed return variable can remain unassigned + UnnamedReturnVariable, + /// Unreachable code + Unreachable, + /// Missing pragma solidity + PragmaSolidity, + /// All other error codes + Other(u64), +} + +// === impl SolidityErrorCode === + +impl SolidityErrorCode { + /// The textual identifier for this error + /// + /// Returns `Err(code)` if unknown error + pub fn as_str(&self) -> Result<&'static str, u64> { + let s = match self { + SolidityErrorCode::SpdxLicenseNotProvided => "license", + SolidityErrorCode::ContractExceeds24576Bytes => "code-size", + SolidityErrorCode::ContractInitCodeSizeExceeds49152Bytes => "init-code-size", + SolidityErrorCode::FunctionStateMutabilityCanBeRestricted => "func-mutability", + SolidityErrorCode::UnusedLocalVariable => "unused-var", + SolidityErrorCode::UnusedFunctionParameter => "unused-param", + SolidityErrorCode::ReturnValueOfCallsNotUsed => "unused-return", + SolidityErrorCode::InterfacesExplicitlyVirtual => "virtual-interfaces", + SolidityErrorCode::PayableNoReceiveEther => "missing-receive-ether", + SolidityErrorCode::ShadowsExistingDeclaration => "shadowing", + SolidityErrorCode::DeclarationSameNameAsAnother => "same-varname", + SolidityErrorCode::UnnamedReturnVariable => "unnamed-return", + SolidityErrorCode::Unreachable => "unreachable", + SolidityErrorCode::PragmaSolidity => "pragma-solidity", + SolidityErrorCode::Other(code) => return Err(*code), + }; + Ok(s) + } +} + +impl From for u64 { + fn from(code: SolidityErrorCode) -> u64 { + match code { + SolidityErrorCode::SpdxLicenseNotProvided => 1878, + SolidityErrorCode::ContractExceeds24576Bytes => 5574, + SolidityErrorCode::FunctionStateMutabilityCanBeRestricted => 2018, + SolidityErrorCode::UnusedLocalVariable => 2072, + SolidityErrorCode::UnusedFunctionParameter => 5667, + SolidityErrorCode::ReturnValueOfCallsNotUsed => 9302, + SolidityErrorCode::InterfacesExplicitlyVirtual => 5815, + SolidityErrorCode::PayableNoReceiveEther => 3628, + SolidityErrorCode::ShadowsExistingDeclaration => 2519, + SolidityErrorCode::DeclarationSameNameAsAnother => 8760, + SolidityErrorCode::UnnamedReturnVariable => 6321, + SolidityErrorCode::Unreachable => 5740, + SolidityErrorCode::PragmaSolidity => 3420, + SolidityErrorCode::ContractInitCodeSizeExceeds49152Bytes => 3860, + SolidityErrorCode::Other(code) => code, + } + } +} + +impl fmt::Display for SolidityErrorCode { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self.as_str() { + Ok(name) => name.fmt(f), + Err(code) => code.fmt(f), + } + } +} + +impl FromStr for SolidityErrorCode { + type Err = String; + + fn from_str(s: &str) -> Result { + let code = match s { + "unreachable" => SolidityErrorCode::Unreachable, + "unused-return" => SolidityErrorCode::UnnamedReturnVariable, + "unused-param" => SolidityErrorCode::UnusedFunctionParameter, + "unused-var" => SolidityErrorCode::UnusedLocalVariable, + "code-size" => SolidityErrorCode::ContractExceeds24576Bytes, + "init-code-size" => SolidityErrorCode::ContractInitCodeSizeExceeds49152Bytes, + "shadowing" => SolidityErrorCode::ShadowsExistingDeclaration, + "func-mutability" => SolidityErrorCode::FunctionStateMutabilityCanBeRestricted, + "license" => SolidityErrorCode::SpdxLicenseNotProvided, + "pragma-solidity" => SolidityErrorCode::PragmaSolidity, + "virtual-interfaces" => SolidityErrorCode::InterfacesExplicitlyVirtual, + "missing-receive-ether" => SolidityErrorCode::PayableNoReceiveEther, + "same-varname" => SolidityErrorCode::DeclarationSameNameAsAnother, + _ => return Err(format!("Unknown variant {s}")), + }; + + Ok(code) + } +} + +impl From for SolidityErrorCode { + fn from(code: u64) -> Self { + match code { + 1878 => SolidityErrorCode::SpdxLicenseNotProvided, + 5574 => SolidityErrorCode::ContractExceeds24576Bytes, + 3860 => SolidityErrorCode::ContractInitCodeSizeExceeds49152Bytes, + 2018 => SolidityErrorCode::FunctionStateMutabilityCanBeRestricted, + 2072 => SolidityErrorCode::UnusedLocalVariable, + 5667 => SolidityErrorCode::UnusedFunctionParameter, + 9302 => SolidityErrorCode::ReturnValueOfCallsNotUsed, + 5815 => SolidityErrorCode::InterfacesExplicitlyVirtual, + 3628 => SolidityErrorCode::PayableNoReceiveEther, + 2519 => SolidityErrorCode::ShadowsExistingDeclaration, + 8760 => SolidityErrorCode::DeclarationSameNameAsAnother, + 6321 => SolidityErrorCode::UnnamedReturnVariable, + 3420 => SolidityErrorCode::PragmaSolidity, + 5740 => SolidityErrorCode::Unreachable, + other => SolidityErrorCode::Other(other), + } + } +} + +impl Serialize for SolidityErrorCode { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + match self.as_str() { + Ok(alias) => serializer.serialize_str(alias), + Err(code) => serializer.serialize_u64(code), + } + } +} + +impl<'de> Deserialize<'de> for SolidityErrorCode { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + /// Helper deserializer for error codes as names and codes + #[derive(Deserialize)] + #[serde(untagged)] + enum SolCode { + Name(String), + Code(u64), + } + + match SolCode::deserialize(deserializer)? { + SolCode::Code(code) => Ok(code.into()), + SolCode::Name(name) => name.parse().map_err(serde::de::Error::custom), + } + } +} diff --git a/foundry-config/src/etherscan.rs b/foundry-config/src/etherscan.rs new file mode 100644 index 000000000..3b9d2b5f3 --- /dev/null +++ b/foundry-config/src/etherscan.rs @@ -0,0 +1,471 @@ +//! Support for multiple etherscan keys +use crate::{ + resolve::{interpolate, UnresolvedEnvVarError, RE_PLACEHOLDER}, + Chain, Config, +}; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use std::{ + collections::BTreeMap, + fmt, + ops::{Deref, DerefMut}, + time::Duration, +}; +use tracing::warn; + +/// The user agent to use when querying the etherscan API. +pub const ETHERSCAN_USER_AGENT: &str = concat!("foundry/", env!("CARGO_PKG_VERSION")); + +/// Errors that can occur when creating an `EtherscanConfig` +#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)] +pub enum EtherscanConfigError { + #[error(transparent)] + Unresolved(#[from] UnresolvedEnvVarError), + + #[error("No known Etherscan API URL for config{0} with chain `{1}`. Please specify a `url`")] + UnknownChain(String, Chain), + + #[error("At least one of `url` or `chain` must be present{0}")] + MissingUrlOrChain(String), +} + +/// Container type for Etherscan API keys and URLs. +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +#[serde(transparent)] +pub struct EtherscanConfigs { + configs: BTreeMap, +} + +// === impl Endpoints === + +impl EtherscanConfigs { + /// Creates a new list of etherscan configs + pub fn new(configs: impl IntoIterator, EtherscanConfig)>) -> Self { + Self { + configs: configs + .into_iter() + .map(|(name, config)| (name.into(), config)) + .collect(), + } + } + + /// Returns `true` if this type doesn't contain any configs + pub fn is_empty(&self) -> bool { + self.configs.is_empty() + } + + /// Returns the first config that matches the chain + pub fn find_chain(&self, chain: Chain) -> Option<&EtherscanConfig> { + self.configs + .values() + .find(|config| config.chain == Some(chain)) + } + + /// Returns all (alias -> url) pairs + pub fn resolved(self) -> ResolvedEtherscanConfigs { + ResolvedEtherscanConfigs { + configs: self + .configs + .into_iter() + .map(|(name, e)| { + let resolved = e.resolve(Some(&name)); + (name, resolved) + }) + .collect(), + } + } +} + +impl Deref for EtherscanConfigs { + type Target = BTreeMap; + + fn deref(&self) -> &Self::Target { + &self.configs + } +} + +impl DerefMut for EtherscanConfigs { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.configs + } +} + +/// Container type for _resolved_ etherscan keys, see [EtherscanConfigs::resolve_all()] +#[derive(Debug, Clone, PartialEq, Eq, Default)] +pub struct ResolvedEtherscanConfigs { + /// contains all named `ResolvedEtherscanConfig` or an error if we failed to resolve the env + /// var alias + configs: BTreeMap>, +} + +// === impl ResolvedEtherscanConfigs === + +impl ResolvedEtherscanConfigs { + /// Creates a new list of resolved etherscan configs + pub fn new( + configs: impl IntoIterator, ResolvedEtherscanConfig)>, + ) -> Self { + Self { + configs: configs + .into_iter() + .map(|(name, config)| (name.into(), Ok(config))) + .collect(), + } + } + + /// Returns the first config that matches the chain + pub fn find_chain( + self, + chain: Chain, + ) -> Option> { + for (_, config) in self.configs.into_iter() { + match config { + Ok(c) if c.chain == Some(chain) => return Some(Ok(c)), + Err(e) => return Some(Err(e)), + _ => continue, + } + } + None + } + + /// Returns true if there's a config that couldn't be resolved + pub fn has_unresolved(&self) -> bool { + self.configs.values().any(|val| val.is_err()) + } +} + +impl Deref for ResolvedEtherscanConfigs { + type Target = BTreeMap>; + + fn deref(&self) -> &Self::Target { + &self.configs + } +} + +impl DerefMut for ResolvedEtherscanConfigs { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.configs + } +} + +/// Represents all info required to create an etherscan client +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct EtherscanConfig { + /// Chain name/id that can be used to derive the api url + #[serde(default, skip_serializing_if = "Option::is_none")] + pub chain: Option, + /// Etherscan API URL + #[serde(default, skip_serializing_if = "Option::is_none")] + pub url: Option, + /// The etherscan API KEY that's required to make requests + pub key: EtherscanApiKey, +} + +// === impl EtherscanConfig === + +impl EtherscanConfig { + /// Returns the etherscan config required to create a client. + /// + /// # Errors + /// + /// Returns an error if the type holds a reference to an env var and the env var is not set or + /// no chain or url is configured + pub fn resolve( + self, + alias: Option<&str>, + ) -> Result { + let EtherscanConfig { + chain, + mut url, + key, + } = self; + + if let Some(url) = &mut url { + *url = interpolate(url)?; + } + + let (chain, alias) = match (chain, alias) { + // fill one with the other + (Some(chain), None) => (Some(chain), Some(chain.to_string())), + (None, Some(alias)) => (alias.parse().ok(), Some(alias.into())), + // leave as is + (Some(chain), Some(alias)) => (Some(chain), Some(alias.into())), + (None, None) => (None, None), + }; + let key = key.resolve()?; + + match (chain, url) { + (Some(chain), Some(api_url)) => Ok(ResolvedEtherscanConfig { + api_url, + browser_url: chain.etherscan_urls().map(|(_, url)| url.to_string()), + key, + chain: Some(chain), + }), + (Some(chain), None) => ResolvedEtherscanConfig::create(key, chain).ok_or_else(|| { + let msg = alias.map(|a| format!(" `{a}`")).unwrap_or_default(); + EtherscanConfigError::UnknownChain(msg, chain) + }), + (None, Some(api_url)) => Ok(ResolvedEtherscanConfig { + api_url, + browser_url: None, + key, + chain: None, + }), + (None, None) => { + let msg = alias + .map(|a| format!(" for Etherscan config `{a}`")) + .unwrap_or_default(); + Err(EtherscanConfigError::MissingUrlOrChain(msg)) + } + } + } +} + +/// Contains required url + api key to set up an etherscan client +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct ResolvedEtherscanConfig { + /// Etherscan API URL + #[serde(rename = "url")] + pub api_url: String, + /// Optional browser url + #[serde(default, skip_serializing_if = "Option::is_none")] + pub browser_url: Option, + /// Resolved api key + pub key: String, + /// The chain if set + #[serde(default, skip_serializing_if = "Option::is_none")] + pub chain: Option, +} + +// === impl ResolvedEtherscanConfig === + +impl ResolvedEtherscanConfig { + /// Creates a new instance using the api key and chain + pub fn create(api_key: impl Into, chain: impl Into) -> Option { + let chain = chain.into(); + let (api_url, browser_url) = chain.etherscan_urls()?; + Some(Self { + api_url: api_url.to_string(), + browser_url: Some(browser_url.to_string()), + key: api_key.into(), + chain: Some(chain), + }) + } + + /// Sets the chain value and consumes the type + /// + /// This is only used to set derive the appropriate Cache path for the etherscan client + pub fn with_chain(mut self, chain: impl Into) -> Self { + self.set_chain(chain); + self + } + + /// Sets the chain value + pub fn set_chain(&mut self, chain: impl Into) -> &mut Self { + let chain = chain.into(); + if let Some((api, browser)) = chain.etherscan_urls() { + self.api_url = api.to_string(); + self.browser_url = Some(browser.to_string()); + } + self.chain = Some(chain); + self + } + + /// Returns the corresponding `ethers_etherscan::Client`, configured with the `api_url`, + /// `api_key` and cache + pub fn into_client( + self, + ) -> Result { + let ResolvedEtherscanConfig { + api_url, + browser_url, + key: api_key, + chain, + } = self; + let (mainnet_api, mainnet_url) = ethers_core::types::Chain::Mainnet + .etherscan_urls() + .expect("exist; qed"); + + let cache = chain + .or_else(|| { + if api_url == mainnet_api { + // try to match against mainnet, which is usually the most common target + Some(ethers_core::types::Chain::Mainnet.into()) + } else { + None + } + }) + .and_then(Config::foundry_etherscan_chain_cache_dir); + + if let Some(ref cache_path) = cache { + // we also create the `sources` sub dir here + if let Err(err) = std::fs::create_dir_all(cache_path.join("sources")) { + warn!("could not create etherscan cache dir: {:?}", err); + } + } + + ethers_etherscan::Client::builder() + .with_client( + reqwest::Client::builder() + .user_agent(ETHERSCAN_USER_AGENT) + .build()?, + ) + .with_api_key(api_key) + .with_api_url(api_url.as_str())? + .with_url( + // the browser url is not used/required by the client so we can simply set the + // mainnet browser url here + browser_url.as_deref().unwrap_or(mainnet_url), + )? + .with_cache(cache, Duration::from_secs(24 * 60 * 60)) + .build() + } +} + +/// Represents a single etherscan API key +/// +/// This type preserves the value as it's stored in the config. If the value is a reference to an +/// env var, then the `EtherscanKey::Key` var will hold the reference (`${MAIN_NET}`) and _not_ the +/// value of the env var itself. +/// In other words, this type does not resolve env vars when it's being deserialized +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum EtherscanApiKey { + /// A raw key + Key(String), + /// An endpoint that contains at least one `${ENV_VAR}` placeholder + /// + /// **Note:** this contains the key or `${ETHERSCAN_KEY}` + Env(String), +} + +// === impl EtherscanApiKey === + +impl EtherscanApiKey { + /// Returns the key variant + pub fn as_key(&self) -> Option<&str> { + match self { + EtherscanApiKey::Key(url) => Some(url), + EtherscanApiKey::Env(_) => None, + } + } + + /// Returns the env variant + pub fn as_env(&self) -> Option<&str> { + match self { + EtherscanApiKey::Env(val) => Some(val), + EtherscanApiKey::Key(_) => None, + } + } + + /// Returns the key this type holds + /// + /// # Error + /// + /// Returns an error if the type holds a reference to an env var and the env var is not set + pub fn resolve(self) -> Result { + match self { + EtherscanApiKey::Key(key) => Ok(key), + EtherscanApiKey::Env(val) => interpolate(&val), + } + } +} + +impl Serialize for EtherscanApiKey { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(&self.to_string()) + } +} + +impl<'de> Deserialize<'de> for EtherscanApiKey { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let val = String::deserialize(deserializer)?; + let endpoint = if RE_PLACEHOLDER.is_match(&val) { + EtherscanApiKey::Env(val) + } else { + EtherscanApiKey::Key(val) + }; + + Ok(endpoint) + } +} + +impl fmt::Display for EtherscanApiKey { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + EtherscanApiKey::Key(key) => key.fmt(f), + EtherscanApiKey::Env(var) => var.fmt(f), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use ethers_core::types::Chain::Mainnet; + + #[test] + fn can_create_client_via_chain() { + let mut configs = EtherscanConfigs::default(); + configs.insert( + "mainnet".to_string(), + EtherscanConfig { + chain: Some(Mainnet.into()), + url: None, + key: EtherscanApiKey::Key("ABCDEFG".to_string()), + }, + ); + + let mut resolved = configs.resolved(); + let config = resolved.remove("mainnet").unwrap().unwrap(); + let _ = config.into_client().unwrap(); + } + + #[test] + fn can_create_client_via_url_and_chain() { + let mut configs = EtherscanConfigs::default(); + configs.insert( + "mainnet".to_string(), + EtherscanConfig { + chain: Some(Mainnet.into()), + url: Some("https://api.etherscan.io/api".to_string()), + key: EtherscanApiKey::Key("ABCDEFG".to_string()), + }, + ); + + let mut resolved = configs.resolved(); + let config = resolved.remove("mainnet").unwrap().unwrap(); + let _ = config.into_client().unwrap(); + } + + #[test] + fn can_create_client_via_url_and_chain_env_var() { + let mut configs = EtherscanConfigs::default(); + let env = "_CONFIG_ETHERSCAN_API_KEY"; + configs.insert( + "mainnet".to_string(), + EtherscanConfig { + chain: Some(Mainnet.into()), + url: Some("https://api.etherscan.io/api".to_string()), + key: EtherscanApiKey::Env(format!("${{{env}}}")), + }, + ); + + let mut resolved = configs.clone().resolved(); + let config = resolved.remove("mainnet").unwrap(); + assert!(config.is_err()); + + std::env::set_var(env, "ABCDEFG"); + + let mut resolved = configs.resolved(); + let config = resolved.remove("mainnet").unwrap().unwrap(); + assert_eq!(config.key, "ABCDEFG"); + let _ = config.into_client().unwrap(); + + std::env::remove_var(env); + } +} diff --git a/foundry-config/src/fix.rs b/foundry-config/src/fix.rs new file mode 100644 index 000000000..ead6d521b --- /dev/null +++ b/foundry-config/src/fix.rs @@ -0,0 +1,344 @@ +//! Helpers to automatically fix configuration warnings + +use crate::{Config, Warning}; +use figment::providers::Env; +use std::{ + fs, io, + ops::{Deref, DerefMut}, + path::{Path, PathBuf}, +}; + +/// A convenience wrapper around a TOML document and the path it was read from +struct TomlFile { + doc: toml_edit::Document, + path: PathBuf, +} + +impl TomlFile { + fn open(path: impl AsRef) -> Result> { + let path = path.as_ref().to_owned(); + let doc = fs::read_to_string(&path)?.parse()?; + Ok(Self { doc, path }) + } + fn doc(&self) -> &toml_edit::Document { + &self.doc + } + fn doc_mut(&mut self) -> &mut toml_edit::Document { + &mut self.doc + } + fn path(&self) -> &Path { + self.path.as_ref() + } + fn save(&self) -> io::Result<()> { + fs::write(self.path(), self.doc().to_string()) + } +} + +impl Deref for TomlFile { + type Target = toml_edit::Document; + fn deref(&self) -> &Self::Target { + self.doc() + } +} + +impl DerefMut for TomlFile { + fn deref_mut(&mut self) -> &mut Self::Target { + self.doc_mut() + } +} + +/// The error emitted when failing to insert a profile into [profile] +#[derive(Debug)] +struct InsertProfileError { + pub message: String, + pub value: toml_edit::Item, +} + +impl std::fmt::Display for InsertProfileError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(&self.message) + } +} + +impl std::error::Error for InsertProfileError {} + +impl TomlFile { + /// Insert a name as `[profile.name]`. Creating the `[profile]` table where necessary and + /// throwing an error if there exists a conflict + fn insert_profile( + &mut self, + profile_str: &str, + value: toml_edit::Item, + ) -> Result<(), InsertProfileError> { + if !value.is_table_like() { + return Err(InsertProfileError { + message: format!("Expected [{profile_str}] to be a Table"), + value, + }); + } + // get or create the profile section + let profile_map = if let Some(map) = self.get_mut(Config::PROFILE_SECTION) { + map + } else { + // insert profile section at the beginning of the map + let mut profile_section = toml_edit::Table::new(); + profile_section.set_position(0); + profile_section.set_implicit(true); + self.insert( + Config::PROFILE_SECTION, + toml_edit::Item::Table(profile_section), + ); + self.get_mut(Config::PROFILE_SECTION) + .expect("exists per above") + }; + // ensure the profile section is a table + let profile_map = if let Some(table) = profile_map.as_table_like_mut() { + table + } else { + return Err(InsertProfileError { + message: format!("Expected [{}] to be a Table", Config::PROFILE_SECTION), + value, + }); + }; + // check the profile map for structure and existing keys + if let Some(profile) = profile_map.get(profile_str) { + if let Some(profile_table) = profile.as_table_like() { + if !profile_table.is_empty() { + return Err(InsertProfileError { + message: format!( + "[{}.{}] already exists", + Config::PROFILE_SECTION, + profile_str + ), + value, + }); + } + } else { + return Err(InsertProfileError { + message: format!( + "Expected [{}.{}] to be a Table", + Config::PROFILE_SECTION, + profile_str + ), + value, + }); + } + } + // insert the profile + profile_map.insert(profile_str, value); + Ok(()) + } +} + +/// Making sure any implicit profile `[name]` becomes `[profile.name]` for the given file and +/// returns the implicit profiles and the result of editing them +fn fix_toml_non_strict_profiles( + toml_file: &mut TomlFile, +) -> Vec<(String, Result<(), InsertProfileError>)> { + let mut results = vec![]; + + // get any non root level keys that need to be inserted into [profile] + let profiles = toml_file + .as_table() + .iter() + .map(|(k, _)| k.to_string()) + .filter(|k| { + !(k == Config::PROFILE_SECTION || Config::STANDALONE_SECTIONS.contains(&k.as_str())) + }) + .collect::>(); + + // remove each profile and insert into [profile] section + for profile in profiles { + if let Some(value) = toml_file.remove(&profile) { + let res = toml_file.insert_profile(&profile, value); + if let Err(err) = res.as_ref() { + toml_file.insert(&profile, err.value.clone()); + } + results.push((profile, res)) + } + } + results +} + +/// Fix foundry.toml files. Making sure any implicit profile `[name]` becomes +/// `[profile.name]`. Return any warnings +pub fn fix_tomls() -> Vec { + let mut warnings = vec![]; + + let tomls = { + let mut tomls = vec![]; + if let Some(global_toml) = Config::foundry_dir_toml().filter(|p| p.exists()) { + tomls.push(global_toml); + } + let local_toml = PathBuf::from( + Env::var("FOUNDRY_CONFIG").unwrap_or_else(|| Config::FILE_NAME.to_string()), + ); + if local_toml.exists() { + tomls.push(local_toml); + } else { + warnings.push(Warning::NoLocalToml(local_toml)); + } + tomls + }; + + for toml in tomls { + let mut toml_file = match TomlFile::open(&toml) { + Ok(toml_file) => toml_file, + Err(err) => { + warnings.push(Warning::CouldNotReadToml { + path: toml, + err: err.to_string(), + }); + continue; + } + }; + + let results = fix_toml_non_strict_profiles(&mut toml_file); + let was_edited = results.iter().any(|(_, res)| res.is_ok()); + for (profile, err) in results + .into_iter() + .filter_map(|(profile, res)| res.err().map(|err| (profile, err.message))) + { + warnings.push(Warning::CouldNotFixProfile { + path: toml_file.path().into(), + profile, + err, + }) + } + + if was_edited { + if let Err(err) = toml_file.save() { + warnings.push(Warning::CouldNotWriteToml { + path: toml_file.path().into(), + err: err.to_string(), + }); + } + } + } + + warnings +} + +#[cfg(test)] +mod tests { + use super::*; + use figment::Jail; + use pretty_assertions::assert_eq; + + macro_rules! fix_test { + ($(#[$meta:meta])* $name:ident, $fun:expr) => { + #[test] + $(#[$meta])* + fn $name() { + Jail::expect_with(|jail| { + // setup home directory, + // **Note** this only has an effect on unix, as [`dirs_next::home_dir()`] on windows uses `FOLDERID_Profile` + jail.set_env("HOME", jail.directory().display().to_string()); + std::fs::create_dir(jail.directory().join(".foundry")).unwrap(); + + // define function type to allow implicit params / return + let f: Box Result<(), figment::Error>> = Box::new($fun); + f(jail)?; + + Ok(()) + }); + } + }; + } + + fix_test!(test_implicit_profile_name_changed, |jail| { + jail.create_file( + "foundry.toml", + r#" + [default] + src = "src" + # comment + + [other] + src = "other-src" + "#, + )?; + fix_tomls(); + assert_eq!( + fs::read_to_string("foundry.toml").unwrap(), + r#" + [profile.default] + src = "src" + # comment + + [profile.other] + src = "other-src" + "# + ); + Ok(()) + }); + + fix_test!(test_leave_standalone_sections_alone, |jail| { + jail.create_file( + "foundry.toml", + r#" + [default] + src = "src" + + [fmt] + line_length = 100 + + [rpc_endpoints] + optimism = "https://example.com/" + "#, + )?; + fix_tomls(); + assert_eq!( + fs::read_to_string("foundry.toml").unwrap(), + r#" + [profile.default] + src = "src" + + [fmt] + line_length = 100 + + [rpc_endpoints] + optimism = "https://example.com/" + "# + ); + Ok(()) + }); + + // mocking the `$HOME` has no effect on windows, see [`dirs_next::home_dir()`] + fix_test!( + #[cfg(not(windows))] + test_global_toml_is_edited, + |jail| { + jail.create_file( + "foundry.toml", + r#" + [other] + src = "other-src" + "#, + )?; + jail.create_file( + ".foundry/foundry.toml", + r#" + [default] + src = "src" + "#, + )?; + fix_tomls(); + assert_eq!( + fs::read_to_string("foundry.toml").unwrap(), + r#" + [profile.other] + src = "other-src" + "# + ); + assert_eq!( + fs::read_to_string(".foundry/foundry.toml").unwrap(), + r#" + [profile.default] + src = "src" + "# + ); + Ok(()) + } + ); +} diff --git a/foundry-config/src/fmt.rs b/foundry-config/src/fmt.rs new file mode 100644 index 000000000..54fa65ed5 --- /dev/null +++ b/foundry-config/src/fmt.rs @@ -0,0 +1,124 @@ +//! Configuration specific to the `forge fmt` command and the `forge_fmt` package + +use serde::{Deserialize, Serialize}; + +/// Contains the config and rule set +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct FormatterConfig { + /// Maximum line length where formatter will try to wrap the line + pub line_length: usize, + /// Number of spaces per indentation level + pub tab_width: usize, + /// Print spaces between brackets + pub bracket_spacing: bool, + /// Style of uint/int256 types + pub int_types: IntTypes, + /// Style of multiline function header in case it doesn't fit + pub multiline_func_header: MultilineFuncHeaderStyle, + /// Style of quotation marks + pub quote_style: QuoteStyle, + /// Style of underscores in number literals + pub number_underscore: NumberUnderscore, + /// Style of single line blocks in statements + pub single_line_statement_blocks: SingleLineBlockStyle, + /// Print space in state variable, function and modifier `override` attribute + pub override_spacing: bool, + /// Wrap comments on `line_length` reached + pub wrap_comments: bool, + /// Globs to ignore + pub ignore: Vec, + /// Add new line at start and end of contract declarations + pub contract_new_lines: bool, +} + +/// Style of uint/int256 types +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum IntTypes { + /// Print the explicit uint256 or int256 + Long, + /// Print the implicit uint or int + Short, + /// Use the type defined in the source code + Preserve, +} + +/// Style of underscores in number literals +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum NumberUnderscore { + /// Remove all underscores + Remove, + /// Add an underscore every thousand, if greater than 9999 + /// e.g. 1000 -> 1000 and 10000 -> 10_000 + Thousands, + /// Use the underscores defined in the source code + Preserve, +} + +/// Style of string quotes +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum QuoteStyle { + /// Use double quotes where possible + Double, + /// Use single quotes where possible + Single, + /// Use quotation mark defined in the source code + Preserve, +} + +impl QuoteStyle { + /// Get associated quotation mark with option + pub fn quote(self) -> Option { + match self { + QuoteStyle::Double => Some('"'), + QuoteStyle::Single => Some('\''), + QuoteStyle::Preserve => None, + } + } +} + +/// Style of single line blocks in statements +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum SingleLineBlockStyle { + /// Prefer single line block when possible + Single, + /// Always use multiline block + Multi, + /// Preserve the original style + Preserve, +} + +/// Style of function header in case it doesn't fit +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum MultilineFuncHeaderStyle { + /// Write function parameters multiline first + ParamsFirst, + /// Write function attributes multiline first + AttributesFirst, + /// If function params or attrs are multiline + /// split the rest + All, +} + +impl Default for FormatterConfig { + fn default() -> Self { + FormatterConfig { + line_length: 120, + tab_width: 4, + bracket_spacing: false, + int_types: IntTypes::Long, + multiline_func_header: MultilineFuncHeaderStyle::AttributesFirst, + quote_style: QuoteStyle::Double, + number_underscore: NumberUnderscore::Preserve, + single_line_statement_blocks: SingleLineBlockStyle::Preserve, + override_spacing: false, + wrap_comments: false, + ignore: vec![], + contract_new_lines: false, + } + } +} diff --git a/foundry-config/src/fs_permissions.rs b/foundry-config/src/fs_permissions.rs new file mode 100644 index 000000000..fa6d431db --- /dev/null +++ b/foundry-config/src/fs_permissions.rs @@ -0,0 +1,255 @@ +//! Support for controlling fs access + +use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use std::{ + fmt, + path::{Path, PathBuf}, + str::FromStr, +}; + +/// Configures file system access +/// +/// E.g. for cheat codes (`vm.writeFile`) +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)] +#[serde(transparent)] +pub struct FsPermissions { + /// what kind of access is allowed + pub permissions: Vec, +} + +// === impl FsPermissions === + +impl FsPermissions { + /// Creates anew instance with the given `permissions` + pub fn new(permissions: impl IntoIterator) -> Self { + Self { + permissions: permissions.into_iter().collect(), + } + } + + /// Returns true if access to the specified path is allowed with the specified. + /// + /// This first checks permission, and only if it is granted, whether the path is allowed. + /// + /// We only allow paths that are inside allowed paths. + /// + /// Caution: This should be called with normalized paths if the `allowed_paths` are also + /// normalized. + pub fn is_path_allowed(&self, path: &Path, kind: FsAccessKind) -> bool { + self.find_permission(path) + .map(|perm| perm.is_granted(kind)) + .unwrap_or_default() + } + + /// Returns the permission for the matching path + pub fn find_permission(&self, path: &Path) -> Option { + self.permissions + .iter() + .find(|perm| path.starts_with(&perm.path)) + .map(|perm| perm.access) + } + + /// Updates all `allowed_paths` and joins ([`Path::join`]) the `root` with all entries + pub fn join_all(&mut self, root: impl AsRef) { + let root = root.as_ref(); + self.permissions.iter_mut().for_each(|perm| { + perm.path = root.join(&perm.path); + }) + } + + /// Same as [`Self::join_all`] but consumes the type + pub fn joined(mut self, root: impl AsRef) -> Self { + self.join_all(root); + self + } + + /// Removes all existing permissions for the given path + pub fn remove(&mut self, path: impl AsRef) { + let path = path.as_ref(); + self.permissions + .retain(|permission| permission.path != path) + } + + /// Returns true if no permissions are configured + pub fn is_empty(&self) -> bool { + self.permissions.is_empty() + } + + /// Returns the number of configured permissions + pub fn len(&self) -> usize { + self.permissions.len() + } +} + +/// Represents an access permission to a single path +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)] +pub struct PathPermission { + /// Permission level to access the `path` + pub access: FsAccessPermission, + /// The targeted path guarded by the permission + pub path: PathBuf, +} + +// === impl PathPermission === + +impl PathPermission { + /// Returns a new permission for the path and the given access + pub fn new(path: impl Into, access: FsAccessPermission) -> Self { + Self { + path: path.into(), + access, + } + } + + /// Returns a new read-only permission for the path + pub fn read(path: impl Into) -> Self { + Self::new(path, FsAccessPermission::Read) + } + + /// Returns a new read-write permission for the path + pub fn read_write(path: impl Into) -> Self { + Self::new(path, FsAccessPermission::ReadWrite) + } + + /// Returns a new write-only permission for the path + pub fn write(path: impl Into) -> Self { + Self::new(path, FsAccessPermission::Write) + } + + /// Returns a non permission for the path + pub fn none(path: impl Into) -> Self { + Self::new(path, FsAccessPermission::None) + } + + /// Returns true if the access is allowed + pub fn is_granted(&self, kind: FsAccessKind) -> bool { + self.access.is_granted(kind) + } +} + +/// Represents the operation on the fs +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum FsAccessKind { + /// read from fs (`vm.readFile`) + Read, + /// write to fs (`vm.writeFile`) + Write, +} + +impl fmt::Display for FsAccessKind { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + FsAccessKind::Read => f.write_str("read"), + FsAccessKind::Write => f.write_str("write"), + } + } +} + +/// Determines the status of file system access +#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)] +pub enum FsAccessPermission { + /// FS access is _not_ allowed + #[default] + None, + /// FS access is allowed, this includes `read` + `write` + ReadWrite, + /// Only reading is allowed + Read, + /// Only writing is allowed + Write, +} + +// === impl FsAccessPermission === + +impl FsAccessPermission { + /// Returns true if the access is allowed + pub fn is_granted(&self, kind: FsAccessKind) -> bool { + match (self, kind) { + (FsAccessPermission::ReadWrite, _) => true, + (FsAccessPermission::None, _) => false, + (FsAccessPermission::Read, FsAccessKind::Read) => true, + (FsAccessPermission::Write, FsAccessKind::Write) => true, + _ => false, + } + } +} + +impl FromStr for FsAccessPermission { + type Err = String; + + fn from_str(s: &str) -> Result { + match s { + "true" | "read-write" | "readwrite" => Ok(FsAccessPermission::ReadWrite), + "false" | "none" => Ok(FsAccessPermission::None), + "read" => Ok(FsAccessPermission::Read), + "write" => Ok(FsAccessPermission::Write), + _ => Err(format!("Unknown variant {s}")), + } + } +} + +impl fmt::Display for FsAccessPermission { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + FsAccessPermission::ReadWrite => f.write_str("read-write"), + FsAccessPermission::None => f.write_str("none"), + FsAccessPermission::Read => f.write_str("read"), + FsAccessPermission::Write => f.write_str("write"), + } + } +} + +impl Serialize for FsAccessPermission { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + match self { + FsAccessPermission::ReadWrite => serializer.serialize_bool(true), + FsAccessPermission::None => serializer.serialize_bool(false), + FsAccessPermission::Read => serializer.serialize_str("read"), + FsAccessPermission::Write => serializer.serialize_str("write"), + } + } +} + +impl<'de> Deserialize<'de> for FsAccessPermission { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + #[derive(Deserialize)] + #[serde(untagged)] + enum Status { + Bool(bool), + String(String), + } + match Status::deserialize(deserializer)? { + Status::Bool(enabled) => { + let status = if enabled { + FsAccessPermission::ReadWrite + } else { + FsAccessPermission::None + }; + Ok(status) + } + Status::String(val) => val.parse().map_err(serde::de::Error::custom), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn can_parse_permission() { + assert_eq!(FsAccessPermission::ReadWrite, "true".parse().unwrap()); + assert_eq!(FsAccessPermission::ReadWrite, "readwrite".parse().unwrap()); + assert_eq!(FsAccessPermission::ReadWrite, "read-write".parse().unwrap()); + assert_eq!(FsAccessPermission::None, "false".parse().unwrap()); + assert_eq!(FsAccessPermission::None, "none".parse().unwrap()); + assert_eq!(FsAccessPermission::Read, "read".parse().unwrap()); + assert_eq!(FsAccessPermission::Write, "write".parse().unwrap()); + } +} diff --git a/foundry-config/src/fuzz.rs b/foundry-config/src/fuzz.rs new file mode 100644 index 000000000..7bbeb9398 --- /dev/null +++ b/foundry-config/src/fuzz.rs @@ -0,0 +1,170 @@ +//! Configuration for fuzz testing + +use ethers_core::types::U256; +use serde::{Deserialize, Serialize}; + +use crate::inline::{ + parse_config_u32, InlineConfigParser, InlineConfigParserError, INLINE_CONFIG_FUZZ_KEY, +}; + +/// Contains for fuzz testing +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +pub struct FuzzConfig { + /// The number of test cases that must execute for each property test + pub runs: u32, + /// The maximum number of test case rejections allowed by proptest, to be + /// encountered during usage of `vm.assume` cheatcode. This will be used + /// to set the `max_global_rejects` value in proptest test runner config. + /// `max_local_rejects` option isn't exposed here since we're not using + /// `prop_filter`. + pub max_test_rejects: u32, + /// Optional seed for the fuzzing RNG algorithm + #[serde( + deserialize_with = "ethers_core::types::serde_helpers::deserialize_stringified_numeric_opt" + )] + pub seed: Option, + /// The fuzz dictionary configuration + #[serde(flatten)] + pub dictionary: FuzzDictionaryConfig, +} + +impl Default for FuzzConfig { + fn default() -> Self { + FuzzConfig { + runs: 256, + max_test_rejects: 65536, + seed: None, + dictionary: FuzzDictionaryConfig::default(), + } + } +} + +impl InlineConfigParser for FuzzConfig { + fn config_key() -> String { + INLINE_CONFIG_FUZZ_KEY.into() + } + + fn try_merge(&self, configs: &[String]) -> Result, InlineConfigParserError> { + let overrides: Vec<(String, String)> = Self::get_config_overrides(configs); + + if overrides.is_empty() { + return Ok(None); + } + + // self is Copy. We clone it with dereference. + let mut conf_clone = *self; + + for pair in overrides { + let key = pair.0; + let value = pair.1; + match key.as_str() { + "runs" => conf_clone.runs = parse_config_u32(key, value)?, + "max-test-rejects" => conf_clone.max_test_rejects = parse_config_u32(key, value)?, + "dictionary-weight" => { + conf_clone.dictionary.dictionary_weight = parse_config_u32(key, value)? + } + _ => Err(InlineConfigParserError::InvalidConfigProperty(key))?, + } + } + Ok(Some(conf_clone)) + } +} + +/// Contains for fuzz testing +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +pub struct FuzzDictionaryConfig { + /// The weight of the dictionary + #[serde(deserialize_with = "crate::deserialize_stringified_percent")] + pub dictionary_weight: u32, + /// The flag indicating whether to include values from storage + pub include_storage: bool, + /// The flag indicating whether to include push bytes values + pub include_push_bytes: bool, + /// How many addresses to record at most. + /// Once the fuzzer exceeds this limit, it will start evicting random entries + /// + /// This limit is put in place to prevent memory blowup. + #[serde(deserialize_with = "crate::deserialize_usize_or_max")] + pub max_fuzz_dictionary_addresses: usize, + /// How many values to record at most. + /// Once the fuzzer exceeds this limit, it will start evicting random entries + #[serde(deserialize_with = "crate::deserialize_usize_or_max")] + pub max_fuzz_dictionary_values: usize, +} + +impl Default for FuzzDictionaryConfig { + fn default() -> Self { + FuzzDictionaryConfig { + dictionary_weight: 40, + include_storage: true, + include_push_bytes: true, + // limit this to 300MB + max_fuzz_dictionary_addresses: (300 * 1024 * 1024) / 20, + // limit this to 200MB + max_fuzz_dictionary_values: (200 * 1024 * 1024) / 32, + } + } +} + +#[cfg(test)] +mod tests { + use crate::{inline::InlineConfigParser, FuzzConfig}; + + #[test] + fn unrecognized_property() { + let configs = &["forge-config: default.fuzz.unknownprop = 200".to_string()]; + let base_config = FuzzConfig::default(); + if let Err(e) = base_config.try_merge(configs) { + assert_eq!(e.to_string(), "'unknownprop' is an invalid config property"); + } else { + unreachable!() + } + } + + #[test] + fn successful_merge() { + let configs = &[ + "forge-config: default.fuzz.runs = 42424242".to_string(), + "forge-config: default.fuzz.dictionary-weight = 42".to_string(), + ]; + let base_config = FuzzConfig::default(); + let merged: FuzzConfig = base_config.try_merge(configs).expect("No errors").unwrap(); + assert_eq!(merged.runs, 42424242); + assert_eq!(merged.dictionary.dictionary_weight, 42); + } + + #[test] + fn merge_is_none() { + let empty_config = &[]; + let base_config = FuzzConfig::default(); + let merged = base_config.try_merge(empty_config).expect("No errors"); + assert!(merged.is_none()); + } + + #[test] + fn merge_is_none_unrelated_property() { + let unrelated_configs = &["forge-config: default.invariant.runs = 2".to_string()]; + let base_config = FuzzConfig::default(); + let merged = base_config.try_merge(unrelated_configs).expect("No errors"); + assert!(merged.is_none()); + } + + #[test] + fn override_detection() { + let configs = &[ + "forge-config: default.fuzz.runs = 42424242".to_string(), + "forge-config: ci.fuzz.runs = 666666".to_string(), + "forge-config: default.invariant.runs = 2".to_string(), + "forge-config: default.fuzz.dictionary-weight = 42".to_string(), + ]; + let variables = FuzzConfig::get_config_overrides(configs); + assert_eq!( + variables, + vec![ + ("runs".into(), "42424242".into()), + ("runs".into(), "666666".into()), + ("dictionary-weight".into(), "42".into()) + ] + ); + } +} diff --git a/foundry-config/src/inline/conf_parser.rs b/foundry-config/src/inline/conf_parser.rs new file mode 100644 index 000000000..ef1263745 --- /dev/null +++ b/foundry-config/src/inline/conf_parser.rs @@ -0,0 +1,212 @@ +use regex::Regex; + +use crate::{InlineConfigError, NatSpec}; + +use super::{remove_whitespaces, INLINE_CONFIG_PREFIX}; + +/// Errors returned by the [`InlineConfigParser`] trait. +#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)] +pub enum InlineConfigParserError { + /// An invalid configuration property has been provided. + /// The property cannot be mapped to the configuration object + #[error("'{0}' is an invalid config property")] + InvalidConfigProperty(String), + /// An invalid profile has been provided + #[error("'{0}' specifies an invalid profile. Available profiles are: {1}")] + InvalidProfile(String, String), + /// An error occurred while trying to parse an integer configuration value + #[error("Invalid config value for key '{0}'. Unable to parse '{1}' into an integer value")] + ParseInt(String, String), + /// An error occurred while trying to parse a boolean configuration value + #[error("Invalid config value for key '{0}'. Unable to parse '{1}' into a boolean value")] + ParseBool(String, String), +} + +/// This trait is intended to parse configurations from +/// structured text. Foundry users can annotate Solidity test functions, +/// providing special configs just for the execution of a specific test. +/// +/// An example: +/// +/// ```solidity +/// contract MyTest is Test { +/// /// forge-config: default.fuzz.runs = 100 +/// /// forge-config: ci.fuzz.runs = 500 +/// function test_SimpleFuzzTest(uint256 x) public {...} +/// +/// /// forge-config: default.fuzz.runs = 500 +/// /// forge-config: ci.fuzz.runs = 10000 +/// function test_ImportantFuzzTest(uint256 x) public {...} +/// } +/// ``` +pub trait InlineConfigParser +where + Self: Clone + Default + Sized + 'static, +{ + /// Returns a config key that is common to all valid configuration lines + /// for the current impl. This helps to extract correct values out of a text. + /// + /// An example key would be `fuzz` of `invariant`. + fn config_key() -> String; + + /// Tries to override `self` properties with values specified in the `configs` parameter. + /// + /// Returns + /// - `Some(Self)` in case some configurations are merged into self. + /// - `None` in case there are no configurations that can be applied to self. + /// - `Err(InlineConfigParserError)` in case of wrong configuration. + fn try_merge(&self, configs: &[String]) -> Result, InlineConfigParserError>; + + /// Validates all configurations contained in a natspec that apply + /// to the current configuration key. + /// + /// i.e. Given the `invariant` config key and a natspec comment of the form, + /// ```solidity + /// /// forge-config: default.invariant.runs = 500 + /// /// forge-config: default.invariant.depth = 500 + /// /// forge-config: ci.invariant.depth = 500 + /// /// forge-config: ci.fuzz.runs = 10 + /// ``` + /// would validate the whole `invariant` configuration. + fn validate_configs(natspec: &NatSpec) -> Result<(), InlineConfigError> { + let config_key = Self::config_key(); + + let configs = natspec + .config_lines() + .filter(|l| l.contains(&config_key)) + .collect::>(); + + Self::default().try_merge(&configs).map_err(|e| { + let line = natspec.debug_context(); + InlineConfigError { line, source: e } + })?; + + Ok(()) + } + + /// Given a list of `config_lines, returns all available pairs (key, value) + /// matching the current config key + /// + /// i.e. Given the `invariant` config key and a vector of config lines + /// ```rust + /// let _config_lines = vec![ + /// "forge-config: default.invariant.runs = 500", + /// "forge-config: default.invariant.depth = 500", + /// "forge-config: ci.invariant.depth = 500", + /// "forge-config: ci.fuzz.runs = 10" + /// ]; + /// ``` + /// would return the whole set of `invariant` configs. + /// ```rust + /// let _result = vec![ + /// ("runs", "500"), + /// ("depth", "500"), + /// ("depth", "500"), + /// ]; + /// ``` + fn get_config_overrides(config_lines: &[String]) -> Vec<(String, String)> { + let mut result: Vec<(String, String)> = vec![]; + let config_key = Self::config_key(); + let profile = ".*"; + let prefix = format!("^{INLINE_CONFIG_PREFIX}:{profile}{config_key}\\."); + let re = Regex::new(&prefix).unwrap(); + + config_lines + .iter() + .map(|l| remove_whitespaces(l)) + .filter(|l| re.is_match(l)) + .map(|l| re.replace(&l, "").to_string()) + .for_each(|line| { + let key_value = line.split('=').collect::>(); // i.e. "['runs', '500']" + if let Some(key) = key_value.first() { + if let Some(value) = key_value.last() { + result.push((key.to_string(), value.to_string())); + } + } + }); + + result + } +} + +/// Checks if all configuration lines specified in `natspec` use a valid profile. +/// +/// i.e. Given available profiles +/// ```rust +/// let _profiles = vec!["ci", "default"]; +/// ``` +/// A configuration like `forge-config: ciii.invariant.depth = 1` would result +/// in an error. +pub fn validate_profiles(natspec: &NatSpec, profiles: &[String]) -> Result<(), InlineConfigError> { + for config in natspec.config_lines() { + if !profiles + .iter() + .any(|p| config.starts_with(&format!("{INLINE_CONFIG_PREFIX}:{p}."))) + { + let err_line: String = natspec.debug_context(); + let profiles = format!("{profiles:?}"); + Err(InlineConfigError { + source: InlineConfigParserError::InvalidProfile(config, profiles), + line: err_line, + })? + } + } + Ok(()) +} + +/// Tries to parse a `u32` from `value`. The `key` argument is used to give details +/// in the case of an error. +pub fn parse_config_u32(key: String, value: String) -> Result { + value + .parse() + .map_err(|_| InlineConfigParserError::ParseInt(key, value)) +} + +/// Tries to parse a `bool` from `value`. The `key` argument is used to give details +/// in the case of an error. +pub fn parse_config_bool(key: String, value: String) -> Result { + value + .parse() + .map_err(|_| InlineConfigParserError::ParseBool(key, value)) +} + +#[cfg(test)] +mod tests { + use crate::{inline::conf_parser::validate_profiles, NatSpec}; + + #[test] + fn can_reject_invalid_profiles() { + let profiles = ["ci".to_string(), "default".to_string()]; + let natspec = NatSpec { + contract: Default::default(), + function: Default::default(), + line: Default::default(), + docs: r#" + forge-config: ciii.invariant.depth = 1 + forge-config: default.invariant.depth = 1 + "# + .into(), + }; + + let result = validate_profiles(&natspec, &profiles); + assert!(result.is_err()); + } + + #[test] + fn can_accept_valid_profiles() { + let profiles = ["ci".to_string(), "default".to_string()]; + let natspec = NatSpec { + contract: Default::default(), + function: Default::default(), + line: Default::default(), + docs: r#" + forge-config: ci.invariant.depth = 1 + forge-config: default.invariant.depth = 1 + "# + .into(), + }; + + let result = validate_profiles(&natspec, &profiles); + assert!(result.is_ok()); + } +} diff --git a/foundry-config/src/inline/mod.rs b/foundry-config/src/inline/mod.rs new file mode 100644 index 000000000..3d34d1f36 --- /dev/null +++ b/foundry-config/src/inline/mod.rs @@ -0,0 +1,85 @@ +mod conf_parser; +pub use conf_parser::{ + parse_config_bool, parse_config_u32, validate_profiles, InlineConfigParser, + InlineConfigParserError, +}; +use once_cell::sync::Lazy; +use std::collections::HashMap; + +mod natspec; +pub use natspec::NatSpec; + +use crate::Config; + +pub const INLINE_CONFIG_FUZZ_KEY: &str = "fuzz"; +pub const INLINE_CONFIG_INVARIANT_KEY: &str = "invariant"; +const INLINE_CONFIG_PREFIX: &str = "forge-config"; + +static INLINE_CONFIG_PREFIX_SELECTED_PROFILE: Lazy = Lazy::new(|| { + let selected_profile = Config::selected_profile().to_string(); + format!("{INLINE_CONFIG_PREFIX}:{selected_profile}.") +}); + +/// Wrapper error struct that catches config parsing +/// errors [`InlineConfigParserError`], enriching them with context information +/// reporting the misconfigured line. +#[derive(thiserror::Error, Debug)] +#[error("Inline config error detected at {line}")] +pub struct InlineConfigError { + /// Specifies the misconfigured line. This is something of the form + /// `dir/TestContract.t.sol:FuzzContract:10:12:111` + pub line: String, + /// The inner error + pub source: InlineConfigParserError, +} + +/// Represents a (test-contract, test-function) pair +type InlineConfigKey = (String, String); + +/// Represents per-test configurations, declared inline +/// as structured comments in Solidity test files. This allows +/// to create configs directly bound to a solidity test. +#[derive(Default, Debug, Clone)] +pub struct InlineConfig { + /// Maps a (test-contract, test-function) pair + /// to a specific configuration provided by the user. + configs: HashMap, +} + +impl InlineConfig { + /// Returns an inline configuration, if any, for a test function. + /// Configuration is identified by the pair "contract", "function". + pub fn get>(&self, contract_id: S, fn_name: S) -> Option<&T> { + self.configs.get(&(contract_id.into(), fn_name.into())) + } + + /// Inserts an inline configuration, for a test function. + /// Configuration is identified by the pair "contract", "function". + pub fn insert>(&mut self, contract_id: S, fn_name: S, config: T) { + self.configs + .insert((contract_id.into(), fn_name.into()), config); + } +} + +fn remove_whitespaces(s: &str) -> String { + s.chars().filter(|c| !c.is_whitespace()).collect() +} + +#[cfg(test)] +mod tests { + use super::InlineConfigParserError; + use crate::InlineConfigError; + + #[test] + fn can_format_inline_config_errors() { + let source = InlineConfigParserError::ParseBool("key".into(), "invalid-bool-value".into()); + let line = "dir/TestContract.t.sol:FuzzContract".to_string(); + let error = InlineConfigError { + line: line.clone(), + source, + }; + + let expected = format!("Inline config error detected at {line}"); + assert_eq!(error.to_string(), expected); + } +} diff --git a/foundry-config/src/inline/natspec.rs b/foundry-config/src/inline/natspec.rs new file mode 100644 index 000000000..44efe4fc8 --- /dev/null +++ b/foundry-config/src/inline/natspec.rs @@ -0,0 +1,241 @@ +use super::{remove_whitespaces, INLINE_CONFIG_PREFIX, INLINE_CONFIG_PREFIX_SELECTED_PROFILE}; +use ethers_solc::{ + artifacts::{ast::NodeType, Node}, + ProjectCompileOutput, +}; +use serde_json::Value; +use std::{collections::BTreeMap, path::Path}; + +/// Convenient struct to hold in-line per-test configurations +pub struct NatSpec { + /// The parent contract of the natspec + pub contract: String, + /// The function annotated with the natspec + pub function: String, + /// The line the natspec appears, in the form + /// `row:col:length` i.e. `10:21:122` + pub line: String, + /// The actual natspec comment, without slashes or block + /// punctuation + pub docs: String, +} + +impl NatSpec { + /// Factory function that extracts a vector of [`NatSpec`] instances from + /// a solc compiler output. The root path is to express contract base dirs. + /// That is essential to match per-test configs at runtime. + pub fn parse(output: &ProjectCompileOutput, root: &Path) -> Vec { + let mut natspecs: Vec = vec![]; + + for (id, artifact) in output.artifact_ids() { + let Some(ast) = &artifact.ast else { continue }; + let path = id.source.as_path(); + let path = path.strip_prefix(root).unwrap_or(path); + // id.identifier + let contract = format!("{}:{}", path.display(), id.name); + let Some(node) = contract_root_node(&ast.nodes, &contract) else { + continue; + }; + apply(&mut natspecs, &contract, node) + } + + natspecs + } + + /// Returns a string describing the natspec + /// context, for debugging purposes 🐞 + /// i.e. `test/Counter.t.sol:CounterTest:testFuzz_SetNumber` + pub fn debug_context(&self) -> String { + format!("{}:{}", self.contract, self.function) + } + + /// Returns a list of configuration lines that match the current profile + pub fn current_profile_configs(&self) -> impl Iterator + '_ { + self.config_lines_with_prefix(INLINE_CONFIG_PREFIX_SELECTED_PROFILE.as_str()) + } + + /// Returns a list of configuration lines that match a specific string prefix + pub fn config_lines_with_prefix<'a>( + &'a self, + prefix: &'a str, + ) -> impl Iterator + 'a { + self.config_lines().filter(move |l| l.starts_with(prefix)) + } + + /// Returns a list of all the configuration lines available in the natspec + pub fn config_lines(&self) -> impl Iterator + '_ { + self.docs + .lines() + .map(remove_whitespaces) + .filter(|line| line.contains(INLINE_CONFIG_PREFIX)) + } +} + +/// Given a list of nodes, find a "ContractDefinition" node that matches +/// the provided contract_id. +fn contract_root_node<'a>(nodes: &'a [Node], contract_id: &'a str) -> Option<&'a Node> { + for n in nodes.iter() { + if let NodeType::ContractDefinition = n.node_type { + let contract_data = &n.other; + if let Value::String(contract_name) = contract_data.get("name")? { + if contract_id.ends_with(contract_name) { + return Some(n); + } + } + } + } + None +} + +/// Implements a DFS over a compiler output node and its children. +/// If a natspec is found it is added to `natspecs` +fn apply(natspecs: &mut Vec, contract: &str, node: &Node) { + for n in node.nodes.iter() { + if let Some((function, docs, line)) = get_fn_data(n) { + natspecs.push(NatSpec { + contract: contract.into(), + function, + line, + docs, + }) + } + apply(natspecs, contract, n); + } +} + +/// Given a compilation output node, if it is a function definition +/// that also contains a natspec then return a tuple of: +/// - Function name +/// - Natspec text +/// - Natspec position with format "row:col:length" +/// +/// Return None otherwise. +fn get_fn_data(node: &Node) -> Option<(String, String, String)> { + if let NodeType::FunctionDefinition = node.node_type { + let fn_data = &node.other; + let fn_name: String = get_fn_name(fn_data)?; + let (fn_docs, docs_src_line): (String, String) = get_fn_docs(fn_data)?; + return Some((fn_name, fn_docs, docs_src_line)); + } + + None +} + +/// Given a dictionary of function data returns the name of the function. +fn get_fn_name(fn_data: &BTreeMap) -> Option { + match fn_data.get("name")? { + Value::String(fn_name) => Some(fn_name.into()), + _ => None, + } +} + +/// Inspects Solc compiler output for documentation comments. Returns: +/// - `Some((String, String))` in case the function has natspec comments. First item is a textual +/// natspec representation, the second item is the natspec src line, in the form "raw:col:length". +/// - `None` in case the function has not natspec comments. +fn get_fn_docs(fn_data: &BTreeMap) -> Option<(String, String)> { + if let Value::Object(fn_docs) = fn_data.get("documentation")? { + if let Value::String(comment) = fn_docs.get("text")? { + if comment.contains(INLINE_CONFIG_PREFIX) { + let mut src_line = fn_docs + .get("src") + .map(|src| src.to_string()) + .unwrap_or_else(|| String::from("")); + + src_line.retain(|c| c != '"'); + return Some((comment.into(), src_line)); + } + } + } + None +} + +#[cfg(test)] +mod tests { + use crate::{inline::natspec::get_fn_docs, NatSpec}; + use serde_json::{json, Value}; + use std::collections::BTreeMap; + + #[test] + fn config_lines() { + let natspec = natspec(); + let config_lines = natspec.config_lines(); + assert_eq!( + config_lines.collect::>(), + vec![ + "forge-config:default.fuzz.runs=600".to_string(), + "forge-config:ci.fuzz.runs=500".to_string(), + "forge-config:default.invariant.runs=1".to_string() + ] + ) + } + + #[test] + fn current_profile_configs() { + let natspec = natspec(); + let config_lines = natspec.current_profile_configs(); + + assert_eq!( + config_lines.collect::>(), + vec![ + "forge-config:default.fuzz.runs=600".to_string(), + "forge-config:default.invariant.runs=1".to_string() + ] + ); + } + + #[test] + fn config_lines_with_prefix() { + use super::INLINE_CONFIG_PREFIX; + let natspec = natspec(); + let prefix = format!("{INLINE_CONFIG_PREFIX}:default"); + let config_lines = natspec.config_lines_with_prefix(&prefix); + assert_eq!( + config_lines.collect::>(), + vec![ + "forge-config:default.fuzz.runs=600".to_string(), + "forge-config:default.invariant.runs=1".to_string() + ] + ) + } + + #[test] + fn can_handle_unavailable_src_line_with_fallback() { + let mut fn_data: BTreeMap = BTreeMap::new(); + let doc_withouth_src_field = json!({ "text": "forge-config:default.fuzz.runs=600" }); + fn_data.insert("documentation".into(), doc_withouth_src_field); + let (_, src_line) = get_fn_docs(&fn_data).expect("Some docs"); + assert_eq!(src_line, "".to_string()); + } + + #[test] + fn can_handle_available_src_line() { + let mut fn_data: BTreeMap = BTreeMap::new(); + let doc_withouth_src_field = + json!({ "text": "forge-config:default.fuzz.runs=600", "src": "73:21:12" }); + fn_data.insert("documentation".into(), doc_withouth_src_field); + let (_, src_line) = get_fn_docs(&fn_data).expect("Some docs"); + assert_eq!(src_line, "73:21:12".to_string()); + } + + fn natspec() -> NatSpec { + let conf = r#" + forge-config: default.fuzz.runs = 600 + forge-config: ci.fuzz.runs = 500 + ========= SOME NOISY TEXT ============= + 䩹𧀫Jx닧Ʀ̳盅K擷􅟽Ɂw첊}ꏻk86ᖪk-檻ܴ렝[Dz𐤬oᘓƤ + ꣖ۻ%Ƅ㪕ς:(饁΍av/烲ڻ̛߉橞㗡𥺃̹M봓䀖ؿ̄󵼁)𯖛d􂽰񮍃 + ϊ&»ϿЏ񊈞2򕄬񠪁鞷砕eߥH󶑶J粊񁼯머?槿ᴴጅ𙏑ϖ뀓򨙺򷃅Ӽ츙4󍔹 + 醤㭊r􎜕󷾸𶚏 ܖ̹灱녗V*竅􋹲⒪苏贗񾦼=숽ؓ򗋲бݧ󫥛𛲍ʹ園Ьi + ======================================= + forge-config: default.invariant.runs = 1 + "#; + + NatSpec { + contract: "dir/TestContract.t.sol:FuzzContract".to_string(), + function: "test_myFunction".to_string(), + line: "10:12:111".to_string(), + docs: conf.to_string(), + } + } +} diff --git a/foundry-config/src/invariant.rs b/foundry-config/src/invariant.rs new file mode 100644 index 000000000..ce11e19d7 --- /dev/null +++ b/foundry-config/src/invariant.rs @@ -0,0 +1,129 @@ +//! Configuration for invariant testing + +use crate::{ + fuzz::FuzzDictionaryConfig, + inline::{ + parse_config_bool, parse_config_u32, InlineConfigParser, InlineConfigParserError, + INLINE_CONFIG_INVARIANT_KEY, + }, +}; +use serde::{Deserialize, Serialize}; + +/// Contains for invariant testing +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +pub struct InvariantConfig { + /// The number of runs that must execute for each invariant test group. + pub runs: u32, + /// The number of calls executed to attempt to break invariants in one run. + pub depth: u32, + /// Fails the invariant fuzzing if a revert occurs + pub fail_on_revert: bool, + /// Allows overriding an unsafe external call when running invariant tests. eg. reentrancy + /// checks + pub call_override: bool, + /// The fuzz dictionary configuration + #[serde(flatten)] + pub dictionary: FuzzDictionaryConfig, + /// Attempt to shrink the failure case to its smallest sequence of calls + pub shrink_sequence: bool, +} + +impl Default for InvariantConfig { + fn default() -> Self { + InvariantConfig { + runs: 256, + depth: 15, + fail_on_revert: false, + call_override: false, + dictionary: FuzzDictionaryConfig { + dictionary_weight: 80, + ..Default::default() + }, + shrink_sequence: true, + } + } +} + +impl InlineConfigParser for InvariantConfig { + fn config_key() -> String { + INLINE_CONFIG_INVARIANT_KEY.into() + } + + fn try_merge(&self, configs: &[String]) -> Result, InlineConfigParserError> { + let overrides: Vec<(String, String)> = Self::get_config_overrides(configs); + + if overrides.is_empty() { + return Ok(None); + } + + // self is Copy. We clone it with dereference. + let mut conf_clone = *self; + + for pair in overrides { + let key = pair.0; + let value = pair.1; + match key.as_str() { + "runs" => conf_clone.runs = parse_config_u32(key, value)?, + "depth" => conf_clone.depth = parse_config_u32(key, value)?, + "fail-on-revert" => conf_clone.fail_on_revert = parse_config_bool(key, value)?, + "call-override" => conf_clone.call_override = parse_config_bool(key, value)?, + "shrink-sequence" => conf_clone.shrink_sequence = parse_config_bool(key, value)?, + _ => Err(InlineConfigParserError::InvalidConfigProperty( + key.to_string(), + ))?, + } + } + Ok(Some(conf_clone)) + } +} + +#[cfg(test)] +mod tests { + use crate::{inline::InlineConfigParser, InvariantConfig}; + + #[test] + fn unrecognized_property() { + let configs = &["forge-config: default.invariant.unknownprop = 200".to_string()]; + let base_config = InvariantConfig::default(); + if let Err(e) = base_config.try_merge(configs) { + assert_eq!(e.to_string(), "'unknownprop' is an invalid config property"); + } else { + unreachable!() + } + } + + #[test] + fn successful_merge() { + let configs = &["forge-config: default.invariant.runs = 42424242".to_string()]; + let base_config = InvariantConfig::default(); + let merged: InvariantConfig = base_config.try_merge(configs).expect("No errors").unwrap(); + assert_eq!(merged.runs, 42424242); + } + + #[test] + fn merge_is_none() { + let empty_config = &[]; + let base_config = InvariantConfig::default(); + let merged = base_config.try_merge(empty_config).expect("No errors"); + assert!(merged.is_none()); + } + + #[test] + fn can_merge_unrelated_properties_into_config() { + let unrelated_configs = &["forge-config: default.fuzz.runs = 2".to_string()]; + let base_config = InvariantConfig::default(); + let merged = base_config.try_merge(unrelated_configs).expect("No errors"); + assert!(merged.is_none()); + } + + #[test] + fn override_detection() { + let configs = &[ + "forge-config: default.fuzz.runs = 42424242".to_string(), + "forge-config: ci.fuzz.runs = 666666".to_string(), + "forge-config: default.invariant.runs = 2".to_string(), + ]; + let variables = InvariantConfig::get_config_overrides(configs); + assert_eq!(variables, vec![("runs".into(), "2".into())]); + } +} diff --git a/foundry-config/src/lib.rs b/foundry-config/src/lib.rs new file mode 100644 index 000000000..0d17ba3a7 --- /dev/null +++ b/foundry-config/src/lib.rs @@ -0,0 +1,4630 @@ +//! Foundry configuration. + +#![warn(missing_docs, unused_crate_dependencies)] + +use crate::cache::StorageCachingConfig; +use ethers_core::types::{Address, Chain::Mainnet, H160, H256, U256}; +pub use ethers_solc::{self, artifacts::OptimizerDetails}; +use ethers_solc::{ + artifacts::{ + output_selection::ContractOutputSelection, serde_helpers, BytecodeHash, DebuggingSettings, + Libraries, ModelCheckerSettings, ModelCheckerTarget, Optimizer, RevertStrings, Settings, + SettingsMetadata, Severity, + }, + cache::SOLIDITY_FILES_CACHE_FILENAME, + error::SolcError, + remappings::{RelativeRemapping, Remapping}, + ConfigurableArtifacts, EvmVersion, Project, ProjectPathsConfig, Solc, SolcConfig, +}; +use eyre::{ContextCompat, WrapErr}; +use figment::{ + providers::{Env, Format, Serialized, Toml}, + value::{Dict, Map, Value}, + Error, Figment, Metadata, Profile, Provider, +}; +use inflector::Inflector; +use once_cell::sync::Lazy; +use regex::Regex; +use semver::Version; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use std::{ + borrow::Cow, + collections::HashMap, + fs, + path::{Path, PathBuf}, + str::FromStr, +}; +pub(crate) use tracing::trace; + +// Macros useful for creating a figment. +mod macros; + +// Utilities for making it easier to handle tests. +pub mod utils; +pub use crate::utils::*; + +mod endpoints; +pub use endpoints::{ResolvedRpcEndpoints, RpcEndpoint, RpcEndpoints}; + +mod etherscan; +mod resolve; +pub use resolve::UnresolvedEnvVarError; + +pub mod cache; +use cache::{Cache, ChainCache}; + +mod chain; +pub use chain::Chain; + +pub mod fmt; +pub use fmt::FormatterConfig; + +pub mod fs_permissions; +pub use crate::fs_permissions::FsPermissions; + +pub mod error; +pub use error::SolidityErrorCode; + +pub mod doc; +pub use doc::DocConfig; + +mod warning; +pub use warning::*; + +// helpers for fixing configuration warnings +pub mod fix; + +// reexport so cli types can implement `figment::Provider` to easily merge compiler arguments +pub use figment; +use revm_primitives::SpecId; +use tracing::warn; + +/// config providers +pub mod providers; + +use crate::{ + error::ExtractConfigError, + etherscan::{EtherscanConfigError, EtherscanConfigs, ResolvedEtherscanConfig}, +}; +use providers::*; + +mod fuzz; +pub use fuzz::{FuzzConfig, FuzzDictionaryConfig}; + +mod invariant; +use crate::fs_permissions::PathPermission; +pub use invariant::InvariantConfig; +use providers::remappings::RemappingsProvider; + +mod inline; +pub use inline::{validate_profiles, InlineConfig, InlineConfigError, InlineConfigParser, NatSpec}; + +/// Foundry configuration +/// +/// # Defaults +/// +/// All configuration values have a default, documented in the [fields](#fields) +/// section below. [`Config::default()`] returns the default values for +/// the default profile while [`Config::with_root()`] returns the values based on the given +/// directory. [`Config::load()`] starts with the default profile and merges various providers into +/// the config, same for [`Config::load_with_root()`], but there the default values are determined +/// by [`Config::with_root()`] +/// +/// # Provider Details +/// +/// `Config` is a Figment [`Provider`] with the following characteristics: +/// +/// * **Profile** +/// +/// The profile is set to the value of the `profile` field. +/// +/// * **Metadata** +/// +/// This provider is named `Foundry Config`. It does not specify a +/// [`Source`](figment::Source) and uses default interpolation. +/// +/// * **Data** +/// +/// The data emitted by this provider are the keys and values corresponding +/// to the fields and values of the structure. The dictionary is emitted to +/// the "default" meta-profile. +/// +/// Note that these behaviors differ from those of [`Config::figment()`]. +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +pub struct Config { + /// The selected profile. **(default: _default_ `default`)** + /// + /// **Note:** This field is never serialized nor deserialized. When a + /// `Config` is merged into a `Figment` as a `Provider`, this profile is + /// selected on the `Figment`. When a `Config` is extracted, this field is + /// set to the extracting Figment's selected `Profile`. + #[serde(skip)] + pub profile: Profile, + /// path of the source contracts dir, like `src` or `contracts` + pub src: PathBuf, + /// path of the test dir + pub test: PathBuf, + /// path of the script dir + pub script: PathBuf, + /// path to where artifacts shut be written to + pub out: PathBuf, + /// all library folders to include, `lib`, `node_modules` + pub libs: Vec, + /// `Remappings` to use for this repo + pub remappings: Vec, + /// Whether to autodetect remappings by scanning the `libs` folders recursively + pub auto_detect_remappings: bool, + /// library addresses to link + pub libraries: Vec, + /// whether to enable cache + pub cache: bool, + /// where the cache is stored if enabled + pub cache_path: PathBuf, + /// where the broadcast logs are stored + pub broadcast: PathBuf, + /// additional solc allow paths for `--allow-paths` + pub allow_paths: Vec, + /// additional solc include paths for `--include-path` + pub include_paths: Vec, + /// whether to force a `project.clean()` + pub force: bool, + /// evm version to use + #[serde(with = "from_str_lowercase")] + pub evm_version: EvmVersion, + /// list of contracts to report gas of + pub gas_reports: Vec, + /// list of contracts to ignore for gas reports + pub gas_reports_ignore: Vec, + /// The Solc instance to use if any. + /// + /// This takes precedence over `auto_detect_solc`, if a version is set then this overrides + /// auto-detection. + /// + /// **Note** for backwards compatibility reasons this also accepts solc_version from the toml + /// file, see [`BackwardsCompatProvider`] + pub solc: Option, + /// whether to autodetect the solc compiler version to use + pub auto_detect_solc: bool, + /// Offline mode, if set, network access (downloading solc) is disallowed. + /// + /// Relationship with `auto_detect_solc`: + /// - if `auto_detect_solc = true` and `offline = true`, the required solc version(s) will + /// be auto detected but if the solc version is not installed, it will _not_ try to + /// install it + pub offline: bool, + /// Whether to activate optimizer + pub optimizer: bool, + /// Sets the optimizer runs + pub optimizer_runs: usize, + /// Switch optimizer components on or off in detail. + /// The "enabled" switch above provides two defaults which can be + /// tweaked here. If "details" is given, "enabled" can be omitted. + pub optimizer_details: Option, + /// Model checker settings. + pub model_checker: Option, + /// verbosity to use + pub verbosity: u8, + /// url of the rpc server that should be used for any rpc calls + pub eth_rpc_url: Option, + /// JWT secret that should be used for any rpc calls + pub eth_rpc_jwt: Option, + /// etherscan API key, or alias for an `EtherscanConfig` in `etherscan` table + pub etherscan_api_key: Option, + /// Multiple etherscan api configs and their aliases + #[serde(default, skip_serializing_if = "EtherscanConfigs::is_empty")] + pub etherscan: EtherscanConfigs, + /// list of solidity error codes to always silence in the compiler output + pub ignored_error_codes: Vec, + /// When true, compiler warnings are treated as errors + pub deny_warnings: bool, + /// Only run test functions matching the specified regex pattern. + #[serde(rename = "match_test")] + pub test_pattern: Option, + /// Only run test functions that do not match the specified regex pattern. + #[serde(rename = "no_match_test")] + pub test_pattern_inverse: Option, + /// Only run tests in contracts matching the specified regex pattern. + #[serde(rename = "match_contract")] + pub contract_pattern: Option, + /// Only run tests in contracts that do not match the specified regex pattern. + #[serde(rename = "no_match_contract")] + pub contract_pattern_inverse: Option, + /// Only run tests in source files matching the specified glob pattern. + #[serde(rename = "match_path", with = "from_opt_glob")] + pub path_pattern: Option, + /// Only run tests in source files that do not match the specified glob pattern. + #[serde(rename = "no_match_path", with = "from_opt_glob")] + pub path_pattern_inverse: Option, + /// Configuration for fuzz testing + pub fuzz: FuzzConfig, + /// Configuration for invariant testing + pub invariant: InvariantConfig, + /// Whether to allow ffi cheatcodes in test + pub ffi: bool, + /// The address which will be executing all tests + pub sender: Address, + /// The tx.origin value during EVM execution + pub tx_origin: Address, + /// the initial balance of each deployed test contract + pub initial_balance: U256, + /// the block.number value during EVM execution + pub block_number: u64, + /// pins the block number for the state fork + pub fork_block_number: Option, + /// The chain id to use + pub chain_id: Option, + /// Block gas limit + pub gas_limit: GasLimit, + /// EIP-170: Contract code size limit in bytes. Useful to increase this because of tests. + pub code_size_limit: Option, + /// `tx.gasprice` value during EVM execution" + /// + /// This is an Option, so we can determine in fork mode whether to use the config's gas price + /// (if set by user) or the remote client's gas price + pub gas_price: Option, + /// the base fee in a block + pub block_base_fee_per_gas: u64, + /// the `block.coinbase` value during EVM execution + pub block_coinbase: Address, + /// the `block.timestamp` value during EVM execution + pub block_timestamp: u64, + /// the `block.difficulty` value during EVM execution + pub block_difficulty: u64, + /// Before merge the `block.max_hash` after merge it is `block.prevrandao` + pub block_prevrandao: H256, + /// the `block.gaslimit` value during EVM execution + pub block_gas_limit: Option, + /// The memory limit of the EVM (32 MB by default) + pub memory_limit: u64, + /// Additional output selection for all contracts + /// such as "ir", "devdoc", "storageLayout", etc. + /// See [Solc Compiler Api](https://docs.soliditylang.org/en/latest/using-the-compiler.html#compiler-api) + /// + /// The following values are always set because they're required by `forge` + //{ + // "*": [ + // "abi", + // "evm.bytecode", + // "evm.deployedBytecode", + // "evm.methodIdentifiers" + // ] + // } + // "# + #[serde(default)] + pub extra_output: Vec, + /// If set , a separate `json` file will be emitted for every contract depending on the + /// selection, eg. `extra_output_files = ["metadata"]` will create a `metadata.json` for + /// each contract in the project. See [Contract Metadata](https://docs.soliditylang.org/en/latest/metadata.html) + /// + /// The difference between `extra_output = ["metadata"]` and + /// `extra_output_files = ["metadata"]` is that the former will include the + /// contract's metadata in the contract's json artifact, whereas the latter will emit the + /// output selection as separate files. + #[serde(default)] + pub extra_output_files: Vec, + /// Print the names of the compiled contracts + pub names: bool, + /// Print the sizes of the compiled contracts + pub sizes: bool, + /// If set to true, changes compilation pipeline to go through the Yul intermediate + /// representation. + pub via_ir: bool, + /// RPC storage caching settings determines what chains and endpoints to cache + pub rpc_storage_caching: StorageCachingConfig, + /// Disables storage caching entirely. This overrides any settings made in + /// `rpc_storage_caching` + pub no_storage_caching: bool, + /// Disables rate limiting entirely. This overrides any settings made in + /// `compute_units_per_second` + pub no_rpc_rate_limit: bool, + /// Multiple rpc endpoints and their aliases + #[serde(default, skip_serializing_if = "RpcEndpoints::is_empty")] + pub rpc_endpoints: RpcEndpoints, + /// Whether to store the referenced sources in the metadata as literal data. + pub use_literal_content: bool, + /// Whether to include the metadata hash. + /// + /// The metadata hash is machine dependent. By default, this is set to [BytecodeHash::None] to allow for deterministic code, See: + #[serde(with = "from_str_lowercase")] + pub bytecode_hash: BytecodeHash, + /// Whether to append the metadata hash to the bytecode. + /// + /// If this is `false` and the `bytecode_hash` option above is not `None` solc will issue a + /// warning. + pub cbor_metadata: bool, + /// How to treat revert (and require) reason strings. + #[serde(with = "serde_helpers::display_from_str_opt")] + pub revert_strings: Option, + /// Whether to compile in sparse mode + /// + /// If this option is enabled, only the required contracts/files will be selected to be + /// included in solc's output selection, see also + /// [OutputSelection](ethers_solc::artifacts::output_selection::OutputSelection) + pub sparse_mode: bool, + /// Whether to emit additional build info files + /// + /// If set to `true`, `ethers-solc` will generate additional build info json files for every + /// new build, containing the `CompilerInput` and `CompilerOutput` + pub build_info: bool, + /// The path to the `build-info` directory that contains the build info json files. + pub build_info_path: Option, + /// Configuration for `forge fmt` + pub fmt: FormatterConfig, + /// Configuration for `forge doc` + pub doc: DocConfig, + /// Configures the permissions of cheat codes that touch the file system. + /// + /// This includes what operations can be executed (read, write) + pub fs_permissions: FsPermissions, + + /// Temporary config to enable [SpecId::CANCUN] + /// + /// + /// Should be removed once EvmVersion Cancun is supported by solc + pub cancun: bool, + + /// The root path where the config detection started from, `Config::with_root` + #[doc(hidden)] + // We're skipping serialization here, so it won't be included in the [`Config::to_string()`] + // representation, but will be deserialized from the `Figment` so that forge commands can + // override it. + #[serde(rename = "root", default, skip_serializing)] + pub __root: RootPath, + /// PRIVATE: This structure may grow, As such, constructing this structure should + /// _always_ be done using a public constructor or update syntax: + /// + /// ```rust + /// use foundry_config::Config; + /// + /// let config = Config { + /// src: "other".into(), + /// ..Default::default() + /// }; + /// ``` + #[doc(hidden)] + #[serde(skip)] + pub __non_exhaustive: (), + /// Warnings gathered when loading the Config. See [`WarningsProvider`] for more information + #[serde(default, skip_serializing)] + pub __warnings: Vec, +} + +/// Mapping of fallback standalone sections. See [`FallbackProfileProvider`] +pub static STANDALONE_FALLBACK_SECTIONS: Lazy> = + Lazy::new(|| HashMap::from([("invariant", "fuzz")])); + +/// Deprecated keys. +pub static DEPRECATIONS: Lazy> = Lazy::new(|| HashMap::from([])); + +impl Config { + /// The default profile: "default" + pub const DEFAULT_PROFILE: Profile = Profile::const_new("default"); + + /// The hardhat profile: "hardhat" + pub const HARDHAT_PROFILE: Profile = Profile::const_new("hardhat"); + + /// TOML section for profiles + pub const PROFILE_SECTION: &'static str = "profile"; + + /// Standalone sections in the config which get integrated into the selected profile + pub const STANDALONE_SECTIONS: &'static [&'static str] = &[ + "rpc_endpoints", + "etherscan", + "fmt", + "doc", + "fuzz", + "invariant", + ]; + + /// File name of config toml file + pub const FILE_NAME: &'static str = "foundry.toml"; + + /// The name of the directory foundry reserves for itself under the user's home directory: `~` + pub const FOUNDRY_DIR_NAME: &'static str = ".foundry"; + + /// Default address for tx.origin + /// + /// `0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38` + pub const DEFAULT_SENDER: H160 = H160([ + 0x18, 0x04, 0xc8, 0xAB, 0x1F, 0x12, 0xE6, 0xbb, 0xF3, 0x89, 0x4D, 0x40, 0x83, 0xF3, 0x3E, + 0x07, 0x30, 0x9D, 0x1F, 0x38, + ]); + + /// Returns the current `Config` + /// + /// See `Config::figment` + #[track_caller] + pub fn load() -> Self { + Config::from_provider(Config::figment()) + } + + /// Returns the current `Config` + /// + /// See `Config::figment_with_root` + #[track_caller] + pub fn load_with_root(root: impl Into) -> Self { + Config::from_provider(Config::figment_with_root(root)) + } + + /// Extract a `Config` from `provider`, panicking if extraction fails. + /// + /// # Panics + /// + /// If extraction fails, prints an error message indicating the failure and + /// panics. For a version that doesn't panic, use [`Config::try_from()`]. + /// + /// # Example + /// + /// ```no_run + /// use foundry_config::Config; + /// use figment::providers::{Toml, Format, Env}; + /// + /// // Use foundry's default `Figment`, but allow values from `other.toml` + /// // to supersede its values. + /// let figment = Config::figment() + /// .merge(Toml::file("other.toml").nested()); + /// + /// let config = Config::from_provider(figment); + /// ``` + #[track_caller] + pub fn from_provider(provider: T) -> Self { + trace!("load config with provider: {:?}", provider.metadata()); + Self::try_from(provider).unwrap_or_else(|err| panic!("{}", err)) + } + + /// Attempts to extract a `Config` from `provider`, returning the result. + /// + /// # Example + /// + /// ```rust + /// use foundry_config::Config; + /// use figment::providers::{Toml, Format, Env}; + /// + /// // Use foundry's default `Figment`, but allow values from `other.toml` + /// // to supersede its values. + /// let figment = Config::figment() + /// .merge(Toml::file("other.toml").nested()); + /// + /// let config = Config::try_from(figment); + /// ``` + pub fn try_from(provider: T) -> Result { + let figment = Figment::from(provider); + let mut config = figment.extract::().map_err(ExtractConfigError::new)?; + config.profile = figment.profile().clone(); + Ok(config) + } + + /// The config supports relative paths and tracks the root path separately see + /// `Config::with_root` + /// + /// This joins all relative paths with the current root and attempts to make them canonic + #[must_use] + pub fn canonic(self) -> Self { + let root = self.__root.0.clone(); + self.canonic_at(root) + } + + /// Joins all relative paths with the given root so that paths that are defined as: + /// + /// ```toml + /// [profile.default] + /// src = "src" + /// out = "./out" + /// libs = ["lib", "/var/lib"] + /// ``` + /// + /// Will be made canonic with the given root: + /// + /// ```toml + /// [profile.default] + /// src = "/src" + /// out = "/out" + /// libs = ["/lib", "/var/lib"] + /// ``` + #[must_use] + pub fn canonic_at(mut self, root: impl Into) -> Self { + let root = canonic(root); + + fn p(root: &Path, rem: &Path) -> PathBuf { + canonic(root.join(rem)) + } + + self.src = p(&root, &self.src); + self.test = p(&root, &self.test); + self.script = p(&root, &self.script); + self.out = p(&root, &self.out); + self.broadcast = p(&root, &self.broadcast); + self.cache_path = p(&root, &self.cache_path); + + if let Some(build_info_path) = self.build_info_path { + self.build_info_path = Some(p(&root, &build_info_path)); + } + + self.libs = self.libs.into_iter().map(|lib| p(&root, &lib)).collect(); + + self.remappings = self + .remappings + .into_iter() + .map(|r| RelativeRemapping::new(r.into(), &root)) + .collect(); + + self.allow_paths = self + .allow_paths + .into_iter() + .map(|allow| p(&root, &allow)) + .collect(); + + self.include_paths = self + .include_paths + .into_iter() + .map(|allow| p(&root, &allow)) + .collect(); + + self.fs_permissions.join_all(&root); + + if let Some(ref mut model_checker) = self.model_checker { + model_checker.contracts = std::mem::take(&mut model_checker.contracts) + .into_iter() + .map(|(path, contracts)| { + (format!("{}", p(&root, path.as_ref()).display()), contracts) + }) + .collect(); + } + + self + } + + /// Returns a sanitized version of the Config where are paths are set correctly and potential + /// duplicates are resolved + /// + /// See [`Self::canonic`] + #[must_use] + pub fn sanitized(self) -> Self { + let mut config = self.canonic(); + + config.sanitize_remappings(); + + config.libs.sort_unstable(); + config.libs.dedup(); + + config + } + + /// Cleans up any duplicate `Remapping` and sorts them + /// + /// On windows this will convert any `\` in the remapping path into a `/` + pub fn sanitize_remappings(&mut self) { + #[cfg(target_os = "windows")] + { + // force `/` in remappings on windows + use path_slash::PathBufExt; + self.remappings.iter_mut().for_each(|r| { + r.path.path = r.path.path.to_slash_lossy().into_owned().into(); + }); + } + } + + /// Returns the directory in which dependencies should be installed + /// + /// Returns the first dir from `libs` that is not `node_modules` or `lib` if `libs` is empty + pub fn install_lib_dir(&self) -> &Path { + self.libs + .iter() + .find(|p| !p.ends_with("node_modules")) + .map(|p| p.as_path()) + .unwrap_or_else(|| Path::new("lib")) + } + + /// Serves as the entrypoint for obtaining the project. + /// + /// Returns the `Project` configured with all `solc` and path related values. + /// + /// *Note*: this also _cleans_ [`Project::cleanup`] the workspace if `force` is set to true. + /// + /// # Example + /// + /// ``` + /// use foundry_config::Config; + /// let config = Config::load_with_root(".").sanitized(); + /// let project = config.project(); + /// ``` + pub fn project(&self) -> Result { + self.create_project(true, false) + } + + /// Same as [`Self::project()`] but sets configures the project to not emit artifacts and ignore + /// cache, caching causes no output until https://github.com/gakonst/ethers-rs/issues/727 + pub fn ephemeral_no_artifacts_project(&self) -> Result { + self.create_project(false, true) + } + + fn create_project(&self, cached: bool, no_artifacts: bool) -> Result { + let mut project = Project::builder() + .artifacts(self.configured_artifacts_handler()) + .paths(self.project_paths()) + .allowed_path(&self.__root.0) + .allowed_paths(&self.libs) + .allowed_paths(&self.allow_paths) + .include_paths(&self.include_paths) + .solc_config( + SolcConfig::builder() + .settings(self.solc_settings()?) + .build(), + ) + .ignore_error_codes(self.ignored_error_codes.iter().copied().map(Into::into)) + .set_compiler_severity_filter(if self.deny_warnings { + Severity::Warning + } else { + Severity::Error + }) + .set_auto_detect(self.is_auto_detect()) + .set_offline(self.offline) + .set_cached(cached) + .set_build_info(cached & self.build_info) + .set_no_artifacts(no_artifacts) + .build()?; + + if self.force { + project.cleanup()?; + } + + if let Some(solc) = self.ensure_solc()? { + project.solc = solc; + } + + Ok(project) + } + + /// Ensures that the configured version is installed if explicitly set + /// + /// If `solc` is [`SolcReq::Version`] then this will download and install the solc version if + /// it's missing, unless the `offline` flag is enabled, in which case an error is thrown. + /// + /// If `solc` is [`SolcReq::Local`] then this will ensure that the path exists. + fn ensure_solc(&self) -> Result, SolcError> { + if let Some(ref solc) = self.solc { + let solc = match solc { + SolcReq::Version(version) => { + let v = version.to_string(); + let mut solc = Solc::find_svm_installed_version(&v)?; + if solc.is_none() { + if self.offline { + return Err(SolcError::msg(format!( + "can't install missing solc {version} in offline mode" + ))); + } + Solc::blocking_install(version)?; + solc = Solc::find_svm_installed_version(&v)?; + } + solc + } + SolcReq::Local(solc) => { + if !solc.is_file() { + return Err(SolcError::msg(format!( + "`solc` {} does not exist", + solc.display() + ))); + } + Some(Solc::new(solc)) + } + }; + return Ok(solc); + } + + Ok(None) + } + + /// Returns the [SpecId] derived from the configured [EvmVersion] + #[inline] + pub fn evm_spec_id(&self) -> SpecId { + if self.cancun { + return SpecId::CANCUN; + } + evm_spec_id(&self.evm_version) + } + + /// Returns whether the compiler version should be auto-detected + /// + /// Returns `false` if `solc_version` is explicitly set, otherwise returns the value of + /// `auto_detect_solc` + pub fn is_auto_detect(&self) -> bool { + if self.solc.is_some() { + return false; + } + self.auto_detect_solc + } + + /// Whether caching should be enabled for the given chain id + pub fn enable_caching(&self, endpoint: &str, chain_id: impl Into) -> bool { + !self.no_storage_caching + && self + .rpc_storage_caching + .enable_for_chain_id(chain_id.into()) + && self.rpc_storage_caching.enable_for_endpoint(endpoint) + } + + /// Returns the `ProjectPathsConfig` sub set of the config. + /// + /// **NOTE**: this uses the paths as they are and does __not__ modify them, see + /// `[Self::sanitized]` + /// + /// # Example + /// + /// ``` + /// use foundry_config::Config; + /// let config = Config::load_with_root(".").sanitized(); + /// let paths = config.project_paths(); + /// ``` + pub fn project_paths(&self) -> ProjectPathsConfig { + let mut builder = ProjectPathsConfig::builder() + .cache(self.cache_path.join(SOLIDITY_FILES_CACHE_FILENAME)) + .sources(&self.src) + .tests(&self.test) + .scripts(&self.script) + .artifacts(&self.out) + .libs(self.libs.clone()) + .remappings(self.get_all_remappings()); + + if let Some(build_info_path) = &self.build_info_path { + builder = builder.build_infos(build_info_path); + } + + builder.build_with_root(&self.__root.0) + } + + /// Returns all configured [`Remappings`] + /// + /// **Note:** this will add an additional `/=` remapping here, see + /// [Self::get_source_dir_remapping()] + /// + /// So that + /// + /// ```solidity + /// import "./math/math.sol"; + /// import "contracts/tokens/token.sol"; + /// ``` + /// + /// in `contracts/contract.sol` are resolved to + /// + /// ```text + /// contracts/tokens/token.sol + /// contracts/math/math.sol + /// ``` + pub fn get_all_remappings(&self) -> Vec { + self.remappings.iter().map(|m| m.clone().into()).collect() + } + + /// Returns the configured rpc jwt secret + /// + /// Returns: + /// - The jwt secret, if configured + /// + /// # Example + /// + /// ``` + /// + /// use foundry_config::Config; + /// # fn t() { + /// let config = Config::with_root("./"); + /// let rpc_jwt = config.get_rpc_jwt_secret().unwrap().unwrap(); + /// # } + /// ``` + pub fn get_rpc_jwt_secret(&self) -> Result>, UnresolvedEnvVarError> { + Ok(self + .eth_rpc_jwt + .as_ref() + .map(|jwt| Cow::Borrowed(jwt.as_str()))) + } + + /// Returns the configured rpc url + /// + /// Returns: + /// - the matching, resolved url of `rpc_endpoints` if `eth_rpc_url` is an alias + /// - the `eth_rpc_url` as-is if it isn't an alias + /// + /// # Example + /// + /// ``` + /// + /// use foundry_config::Config; + /// # fn t() { + /// let config = Config::with_root("./"); + /// let rpc_url = config.get_rpc_url().unwrap().unwrap(); + /// # } + /// ``` + pub fn get_rpc_url(&self) -> Option, UnresolvedEnvVarError>> { + let maybe_alias = self + .eth_rpc_url + .as_ref() + .or(self.etherscan_api_key.as_ref())?; + if let Some(alias) = self.get_rpc_url_with_alias(maybe_alias) { + Some(alias) + } else { + Some(Ok(Cow::Borrowed(self.eth_rpc_url.as_deref()?))) + } + } + + /// Resolves the given alias to a matching rpc url + /// + /// Returns: + /// - the matching, resolved url of `rpc_endpoints` if `maybe_alias` is an alias + /// - None otherwise + /// + /// # Example + /// + /// ``` + /// + /// use foundry_config::Config; + /// # fn t() { + /// let config = Config::with_root("./"); + /// let rpc_url = config.get_rpc_url_with_alias("mainnet").unwrap().unwrap(); + /// # } + /// ``` + pub fn get_rpc_url_with_alias( + &self, + maybe_alias: &str, + ) -> Option, UnresolvedEnvVarError>> { + let mut endpoints = self.rpc_endpoints.clone().resolved(); + Some(endpoints.remove(maybe_alias)?.map(Cow::Owned)) + } + + /// Returns the configured rpc, or the fallback url + /// + /// # Example + /// + /// ``` + /// + /// use foundry_config::Config; + /// # fn t() { + /// let config = Config::with_root("./"); + /// let rpc_url = config.get_rpc_url_or("http://localhost:8545").unwrap(); + /// # } + /// ``` + pub fn get_rpc_url_or<'a>( + &'a self, + fallback: impl Into>, + ) -> Result, UnresolvedEnvVarError> { + if let Some(url) = self.get_rpc_url() { + url + } else { + Ok(fallback.into()) + } + } + + /// Returns the configured rpc or `"http://localhost:8545"` if no `eth_rpc_url` is set + /// + /// # Example + /// + /// ``` + /// + /// use foundry_config::Config; + /// # fn t() { + /// let config = Config::with_root("./"); + /// let rpc_url = config.get_rpc_url_or_localhost_http().unwrap(); + /// # } + /// ``` + pub fn get_rpc_url_or_localhost_http(&self) -> Result, UnresolvedEnvVarError> { + self.get_rpc_url_or("http://localhost:8545") + } + + /// Returns the `EtherscanConfig` to use, if any + /// + /// Returns + /// - the matching `ResolvedEtherscanConfig` of the `etherscan` table if `etherscan_api_key` is + /// an alias + /// - the Mainnet `ResolvedEtherscanConfig` if `etherscan_api_key` is set, `None` otherwise + /// + /// # Example + /// + /// ``` + /// + /// use foundry_config::Config; + /// # fn t() { + /// let config = Config::with_root("./"); + /// let etherscan_config = config.get_etherscan_config().unwrap().unwrap(); + /// let client = etherscan_config.into_client().unwrap(); + /// # } + /// ``` + pub fn get_etherscan_config( + &self, + ) -> Option> { + let maybe_alias = self + .etherscan_api_key + .as_ref() + .or(self.eth_rpc_url.as_ref())?; + if self.etherscan.contains_key(maybe_alias) { + // etherscan points to an alias in the `etherscan` table, so we try to resolve that + let mut resolved = self.etherscan.clone().resolved(); + return resolved.remove(maybe_alias); + } + + // we treat the `etherscan_api_key` as actual API key + // if no chain provided, we assume mainnet + let chain = self.chain_id.unwrap_or(Chain::Named(Mainnet)); + let api_key = self.etherscan_api_key.as_ref()?; + ResolvedEtherscanConfig::create(api_key, chain).map(Ok) + } + + /// Same as [`Self::get_etherscan_config()`] but optionally updates the config with the given + /// `chain`, and `etherscan_api_key` + /// + /// If not matching alias was found, then this will try to find the first entry in the table + /// with a matching chain id. If an etherscan_api_key is already set it will take precedence + /// over the chain's entry in the table. + pub fn get_etherscan_config_with_chain( + &self, + chain: Option>, + ) -> Result, EtherscanConfigError> { + let chain = chain.map(Into::into); + if let Some(maybe_alias) = self + .etherscan_api_key + .as_ref() + .or(self.eth_rpc_url.as_ref()) + { + if self.etherscan.contains_key(maybe_alias) { + return self + .etherscan + .clone() + .resolved() + .remove(maybe_alias) + .transpose(); + } + } + + // try to find by comparing chain IDs after resolving + if let Some(res) = + chain.and_then(|chain| self.etherscan.clone().resolved().find_chain(chain)) + { + match (res, self.etherscan_api_key.as_ref()) { + (Ok(mut config), Some(key)) => { + // we update the key, because if an etherscan_api_key is set, it should take + // precedence over the entry, since this is usually set via env var or CLI args. + config.key = key.clone(); + return Ok(Some(config)); + } + (Ok(config), None) => return Ok(Some(config)), + (Err(err), None) => return Err(err), + (Err(_), Some(_)) => { + // use the etherscan key as fallback + } + } + } + + // etherscan fallback via API key + if let Some(key) = self.etherscan_api_key.as_ref() { + let chain = chain.or(self.chain_id).unwrap_or_default(); + return Ok(ResolvedEtherscanConfig::create(key, chain)); + } + + Ok(None) + } + + /// Helper function to just get the API key + pub fn get_etherscan_api_key(&self, chain: Option>) -> Option { + self.get_etherscan_config_with_chain(chain) + .ok() + .flatten() + .map(|c| c.key) + } + + /// Returns the remapping for the project's _src_ directory + /// + /// **Note:** this will add an additional `/=` remapping here so imports that + /// look like `import {Foo} from "src/Foo.sol";` are properly resolved. + /// + /// This is due the fact that `solc`'s VFS resolves [direct imports](https://docs.soliditylang.org/en/develop/path-resolution.html#direct-imports) that start with the source directory's name. + pub fn get_source_dir_remapping(&self) -> Option { + get_dir_remapping(&self.src) + } + + /// Returns the remapping for the project's _test_ directory, but only if it exists + pub fn get_test_dir_remapping(&self) -> Option { + if self.__root.0.join(&self.test).exists() { + get_dir_remapping(&self.test) + } else { + None + } + } + + /// Returns the remapping for the project's _script_ directory, but only if it exists + pub fn get_script_dir_remapping(&self) -> Option { + if self.__root.0.join(&self.script).exists() { + get_dir_remapping(&self.script) + } else { + None + } + } + + /// Returns the `Optimizer` based on the configured settings + pub fn optimizer(&self) -> Optimizer { + // only configure optimizer settings if optimizer is enabled + let details = if self.optimizer { + self.optimizer_details.clone() + } else { + None + }; + + Optimizer { + enabled: Some(self.optimizer), + runs: Some(self.optimizer_runs), + details, + } + } + + /// returns the [`ethers_solc::ConfigurableArtifacts`] for this config, that includes the + /// `extra_output` fields + pub fn configured_artifacts_handler(&self) -> ConfigurableArtifacts { + let mut extra_output = self.extra_output.clone(); + // Sourcify verification requires solc metadata output. Since, it doesn't + // affect the UX & performance of the compiler, output the metadata files + // by default. + // For more info see: + // Metadata is not emitted as separate file because this breaks typechain support: + if !extra_output.contains(&ContractOutputSelection::Metadata) { + extra_output.push(ContractOutputSelection::Metadata); + } + + ConfigurableArtifacts::new(extra_output, self.extra_output_files.clone()) + } + + /// Parses all libraries in the form of + /// `::` + pub fn parsed_libraries(&self) -> Result { + Libraries::parse(&self.libraries) + } + + /// Returns the configured `solc` `Settings` that includes: + /// - all libraries + /// - the optimizer (including details, if configured) + /// - evm version + pub fn solc_settings(&self) -> Result { + let libraries = self + .parsed_libraries()? + .with_applied_remappings(&self.project_paths()); + let optimizer = self.optimizer(); + + // By default if no targets are specifically selected the model checker uses all targets. + // This might be too much here, so only enable assertion checks. + // If users wish to enable all options they need to do so explicitly. + let mut model_checker = self.model_checker.clone(); + if let Some(ref mut model_checker_settings) = model_checker { + if model_checker_settings.targets.is_none() { + model_checker_settings.targets = Some(vec![ModelCheckerTarget::Assert]); + } + } + + let mut settings = Settings { + optimizer, + evm_version: Some(self.evm_version), + libraries, + metadata: Some(SettingsMetadata { + use_literal_content: Some(self.use_literal_content), + bytecode_hash: Some(self.bytecode_hash), + cbor_metadata: Some(self.cbor_metadata), + }), + debug: self.revert_strings.map(|revert_strings| DebuggingSettings { + revert_strings: Some(revert_strings), + debug_info: Vec::new(), + }), + model_checker, + ..Default::default() + } + .with_extra_output(self.configured_artifacts_handler().output_selection()) + .with_ast(); + + if self.via_ir { + settings = settings.with_via_ir(); + } + + Ok(settings) + } + + /// Returns the default figment + /// + /// The default figment reads from the following sources, in ascending + /// priority order: + /// + /// 1. [`Config::default()`] (see [defaults](#defaults)) + /// 2. `foundry.toml` _or_ filename in `FOUNDRY_CONFIG` environment variable + /// 3. `FOUNDRY_` prefixed environment variables + /// + /// The profile selected is the value set in the `FOUNDRY_PROFILE` + /// environment variable. If it is not set, it defaults to `default`. + /// + /// # Example + /// + /// ```rust + /// use foundry_config::Config; + /// use serde::Deserialize; + /// + /// let my_config = Config::figment().extract::(); + /// ``` + pub fn figment() -> Figment { + Config::default().into() + } + + /// Returns the default figment enhanced with additional context extracted from the provided + /// root, like remappings and directories. + /// + /// # Example + /// + /// ```rust + /// use foundry_config::Config; + /// use serde::Deserialize; + /// + /// let my_config = Config::figment_with_root(".").extract::(); + /// ``` + pub fn figment_with_root(root: impl Into) -> Figment { + Self::with_root(root).into() + } + + /// Creates a new Config that adds additional context extracted from the provided root. + /// + /// # Example + /// + /// ```rust + /// use foundry_config::Config; + /// let my_config = Config::with_root("."); + /// ``` + pub fn with_root(root: impl Into) -> Self { + // autodetect paths + let root = root.into(); + let paths = ProjectPathsConfig::builder().build_with_root(&root); + let artifacts: PathBuf = paths.artifacts.file_name().unwrap().into(); + Config { + __root: paths.root.into(), + src: paths.sources.file_name().unwrap().into(), + out: artifacts.clone(), + libs: paths + .libraries + .into_iter() + .map(|lib| lib.file_name().unwrap().into()) + .collect(), + remappings: paths + .remappings + .into_iter() + .map(|r| RelativeRemapping::new(r, &root)) + .collect(), + fs_permissions: FsPermissions::new([PathPermission::read(artifacts)]), + ..Config::default() + } + } + + /// Returns the default config but with hardhat paths + pub fn hardhat() -> Self { + Config { + src: "contracts".into(), + out: "artifacts".into(), + libs: vec!["node_modules".into()], + ..Config::default() + } + } + + /// Returns the default config that uses dapptools style paths + pub fn dapptools() -> Self { + Config { + chain_id: Some(Chain::Id(99)), + block_timestamp: 0, + block_number: 0, + ..Config::default() + } + } + + /// Extracts a basic subset of the config, used for initialisations. + /// + /// # Example + /// + /// ```rust + /// use foundry_config::Config; + /// let my_config = Config::with_root(".").into_basic(); + /// ``` + pub fn into_basic(self) -> BasicConfig { + BasicConfig { + profile: self.profile, + src: self.src, + out: self.out, + libs: self.libs, + remappings: self.remappings, + } + } + + /// Updates the `foundry.toml` file for the given `root` based on the provided closure. + /// + /// **Note:** the closure will only be invoked if the `foundry.toml` file exists, See + /// [Self::get_config_path()] and if the closure returns `true`. + pub fn update_at(root: impl Into, f: F) -> eyre::Result<()> + where + F: FnOnce(&Config, &mut toml_edit::Document) -> bool, + { + let config = Self::load_with_root(root).sanitized(); + config.update(|doc| f(&config, doc)) + } + + /// Updates the `foundry.toml` file this `Config` ias based on with the provided closure. + /// + /// **Note:** the closure will only be invoked if the `foundry.toml` file exists, See + /// [Self::get_config_path()] and if the closure returns `true` + pub fn update(&self, f: F) -> eyre::Result<()> + where + F: FnOnce(&mut toml_edit::Document) -> bool, + { + let file_path = self.get_config_path(); + if !file_path.exists() { + return Ok(()); + } + let contents = fs::read_to_string(&file_path)?; + let mut doc = contents.parse::()?; + if f(&mut doc) { + fs::write(file_path, doc.to_string())?; + } + Ok(()) + } + + /// Sets the `libs` entry inside a `foundry.toml` file but only if it exists + /// + /// # Errors + /// + /// An error if the `foundry.toml` could not be parsed. + pub fn update_libs(&self) -> eyre::Result<()> { + self.update(|doc| { + let profile = self.profile.as_str().as_str(); + let root = &self.__root.0; + let libs: toml_edit::Value = self + .libs + .iter() + .map(|path| { + let path = if let Ok(relative) = path.strip_prefix(root) { + relative + } else { + path + }; + toml_edit::Value::from(&*path.to_string_lossy()) + }) + .collect(); + let libs = toml_edit::value(libs); + doc[Config::PROFILE_SECTION][profile]["libs"] = libs; + true + }) + } + + /// Serialize the config type as a String of TOML. + /// + /// This serializes to a table with the name of the profile + /// + /// ```toml + /// [profile.default] + /// src = "src" + /// out = "out" + /// libs = ["lib"] + /// # ... + /// ``` + pub fn to_string_pretty(&self) -> Result { + // serializing to value first to prevent `ValueAfterTable` errors + let mut value = toml::Value::try_from(self)?; + // Config map always gets serialized as a table + let value_table = value.as_table_mut().unwrap(); + // remove standalone sections from inner table + let standalone_sections = Config::STANDALONE_SECTIONS + .iter() + .filter_map(|section| { + let section = section.to_string(); + value_table.remove(§ion).map(|value| (section, value)) + }) + .collect::>(); + // wrap inner table in [profile.] + let mut wrapping_table = [( + Config::PROFILE_SECTION.into(), + toml::Value::Table([(self.profile.to_string(), value)].into_iter().collect()), + )] + .into_iter() + .collect::>(); + // insert standalone sections + for (section, value) in standalone_sections { + wrapping_table.insert(section, value); + } + // stringify + toml::to_string_pretty(&toml::Value::Table(wrapping_table)) + } + + /// Returns the path to the `foundry.toml` of this `Config` + pub fn get_config_path(&self) -> PathBuf { + self.__root.0.join(Config::FILE_NAME) + } + + /// Returns the selected profile + /// + /// If the `FOUNDRY_PROFILE` env variable is not set, this returns the `DEFAULT_PROFILE` + pub fn selected_profile() -> Profile { + Profile::from_env_or("FOUNDRY_PROFILE", Config::DEFAULT_PROFILE) + } + + /// Returns the path to foundry's global toml file that's stored at `~/.foundry/foundry.toml` + pub fn foundry_dir_toml() -> Option { + Self::foundry_dir().map(|p| p.join(Config::FILE_NAME)) + } + + /// Returns the path to foundry's config dir `~/.foundry/` + pub fn foundry_dir() -> Option { + dirs_next::home_dir().map(|p| p.join(Config::FOUNDRY_DIR_NAME)) + } + + /// Returns the path to foundry's cache dir `~/.foundry/cache` + pub fn foundry_cache_dir() -> Option { + Self::foundry_dir().map(|p| p.join("cache")) + } + + /// Returns the path to foundry rpc cache dir `~/.foundry/cache/rpc` + pub fn foundry_rpc_cache_dir() -> Option { + Some(Self::foundry_cache_dir()?.join("rpc")) + } + /// Returns the path to foundry chain's cache dir `~/.foundry/cache/rpc/` + pub fn foundry_chain_cache_dir(chain_id: impl Into) -> Option { + Some(Self::foundry_rpc_cache_dir()?.join(chain_id.into().to_string())) + } + + /// Returns the path to foundry's etherscan cache dir `~/.foundry/cache/etherscan` + pub fn foundry_etherscan_cache_dir() -> Option { + Some(Self::foundry_cache_dir()?.join("etherscan")) + } + + /// Returns the path to foundry's keystores dir `~/.foundry/keystores` + pub fn foundry_keystores_dir() -> Option { + Some(Self::foundry_dir()?.join("keystores")) + } + + /// Returns the path to foundry's etherscan cache dir for `chain_id` + /// `~/.foundry/cache/etherscan/` + pub fn foundry_etherscan_chain_cache_dir(chain_id: impl Into) -> Option { + Some(Self::foundry_etherscan_cache_dir()?.join(chain_id.into().to_string())) + } + + /// Returns the path to the cache dir of the `block` on the `chain` + /// `~/.foundry/cache/rpc// + pub fn foundry_block_cache_dir(chain_id: impl Into, block: u64) -> Option { + Some(Self::foundry_chain_cache_dir(chain_id)?.join(format!("{block}"))) + } + + /// Returns the path to the cache file of the `block` on the `chain` + /// `~/.foundry/cache/rpc///storage.json` + pub fn foundry_block_cache_file(chain_id: impl Into, block: u64) -> Option { + Some(Self::foundry_block_cache_dir(chain_id, block)?.join("storage.json")) + } + + #[doc = r#"Returns the path to `foundry`'s data directory inside the user's data directory + |Platform | Value | Example | + | ------- | ------------------------------------- | -------------------------------- | + | Linux | `$XDG_CONFIG_HOME` or `$HOME`/.config/foundry | /home/alice/.config/foundry| + | macOS | `$HOME`/Library/Application Support/foundry | /Users/Alice/Library/Application Support/foundry | + | Windows | `{FOLDERID_RoamingAppData}/foundry` | C:\Users\Alice\AppData\Roaming/foundry | + "#] + pub fn data_dir() -> eyre::Result { + let path = dirs_next::data_dir() + .wrap_err("Failed to find data directory")? + .join("foundry"); + std::fs::create_dir_all(&path).wrap_err("Failed to create module directory")?; + Ok(path) + } + + /// Returns the path to the `foundry.toml` file, the file is searched for in + /// the current working directory and all parent directories until the root, + /// and the first hit is used. + /// + /// If this search comes up empty, then it checks if a global `foundry.toml` exists at + /// `~/.foundry/foundry.tol`, see [`Self::foundry_dir_toml()`] + pub fn find_config_file() -> Option { + fn find(path: &Path) -> Option { + if path.is_absolute() { + return match path.is_file() { + true => Some(path.to_path_buf()), + false => None, + }; + } + let cwd = std::env::current_dir().ok()?; + let mut cwd = cwd.as_path(); + loop { + let file_path = cwd.join(path); + if file_path.is_file() { + return Some(file_path); + } + cwd = cwd.parent()?; + } + } + find(Env::var_or("FOUNDRY_CONFIG", Config::FILE_NAME).as_ref()) + .or_else(|| Self::foundry_dir_toml().filter(|p| p.exists())) + } + + /// Clears the foundry cache + pub fn clean_foundry_cache() -> eyre::Result<()> { + if let Some(cache_dir) = Config::foundry_cache_dir() { + let path = cache_dir.as_path(); + let _ = fs::remove_dir_all(path); + } else { + eyre::bail!("failed to get foundry_cache_dir"); + } + + Ok(()) + } + + /// Clears the foundry cache for `chain` + pub fn clean_foundry_chain_cache(chain: Chain) -> eyre::Result<()> { + if let Some(cache_dir) = Config::foundry_chain_cache_dir(chain) { + let path = cache_dir.as_path(); + let _ = fs::remove_dir_all(path); + } else { + eyre::bail!("failed to get foundry_chain_cache_dir"); + } + + Ok(()) + } + + /// Clears the foundry cache for `chain` and `block` + pub fn clean_foundry_block_cache(chain: Chain, block: u64) -> eyre::Result<()> { + if let Some(cache_dir) = Config::foundry_block_cache_dir(chain, block) { + let path = cache_dir.as_path(); + let _ = fs::remove_dir_all(path); + } else { + eyre::bail!("failed to get foundry_block_cache_dir"); + } + + Ok(()) + } + + /// Clears the foundry etherscan cache + pub fn clean_foundry_etherscan_cache() -> eyre::Result<()> { + if let Some(cache_dir) = Config::foundry_etherscan_cache_dir() { + let path = cache_dir.as_path(); + let _ = fs::remove_dir_all(path); + } else { + eyre::bail!("failed to get foundry_etherscan_cache_dir"); + } + + Ok(()) + } + + /// Clears the foundry etherscan cache for `chain` + pub fn clean_foundry_etherscan_chain_cache(chain: Chain) -> eyre::Result<()> { + if let Some(cache_dir) = Config::foundry_etherscan_chain_cache_dir(chain) { + let path = cache_dir.as_path(); + let _ = fs::remove_dir_all(path); + } else { + eyre::bail!( + "failed to get foundry_etherscan_cache_dir for chain: {}", + chain + ); + } + + Ok(()) + } + + /// List the data in the foundry cache + pub fn list_foundry_cache() -> eyre::Result { + if let Some(cache_dir) = Config::foundry_rpc_cache_dir() { + let mut cache = Cache { chains: vec![] }; + if !cache_dir.exists() { + return Ok(cache); + } + if let Ok(entries) = cache_dir.as_path().read_dir() { + for entry in entries.flatten().filter(|x| x.path().is_dir()) { + match Chain::from_str(&entry.file_name().to_string_lossy()) { + Ok(chain) => cache.chains.push(Self::list_foundry_chain_cache(chain)?), + Err(_) => continue, + } + } + Ok(cache) + } else { + eyre::bail!("failed to access foundry_cache_dir"); + } + } else { + eyre::bail!("failed to get foundry_cache_dir"); + } + } + + /// List the cached data for `chain` + pub fn list_foundry_chain_cache(chain: Chain) -> eyre::Result { + let block_explorer_data_size = match Config::foundry_etherscan_chain_cache_dir(chain) { + Some(cache_dir) => Self::get_cached_block_explorer_data(&cache_dir)?, + None => { + warn!("failed to access foundry_etherscan_chain_cache_dir"); + 0 + } + }; + + if let Some(cache_dir) = Config::foundry_chain_cache_dir(chain) { + let blocks = Self::get_cached_blocks(&cache_dir)?; + Ok(ChainCache { + name: chain.to_string(), + blocks, + block_explorer: block_explorer_data_size, + }) + } else { + eyre::bail!("failed to get foundry_chain_cache_dir"); + } + } + + //The path provided to this function should point to a cached chain folder + fn get_cached_blocks(chain_path: &Path) -> eyre::Result> { + let mut blocks = vec![]; + if !chain_path.exists() { + return Ok(blocks); + } + for block in chain_path + .read_dir()? + .flatten() + .filter(|x| x.file_type().unwrap().is_dir()) + { + let filepath = block.path().join("storage.json"); + blocks.push(( + block.file_name().to_string_lossy().into_owned(), + fs::metadata(filepath)?.len(), + )); + } + Ok(blocks) + } + + //The path provided to this function should point to the etherscan cache for a chain + fn get_cached_block_explorer_data(chain_path: &Path) -> eyre::Result { + if !chain_path.exists() { + return Ok(0); + } + + fn dir_size_recursive(mut dir: fs::ReadDir) -> eyre::Result { + dir.try_fold(0, |acc, file| { + let file = file?; + let size = match file.metadata()? { + data if data.is_dir() => dir_size_recursive(fs::read_dir(file.path())?)?, + data => data.len(), + }; + Ok(acc + size) + }) + } + + dir_size_recursive(fs::read_dir(chain_path)?) + } + + fn merge_toml_provider( + mut figment: Figment, + toml_provider: impl Provider, + profile: Profile, + ) -> Figment { + figment = figment.select(profile.clone()); + + // add warnings + figment = { + let warnings = WarningsProvider::for_figment(&toml_provider, &figment); + figment.merge(warnings) + }; + + // use [profile.] as [] + let mut profiles = vec![Config::DEFAULT_PROFILE]; + if profile != Config::DEFAULT_PROFILE { + profiles.push(profile.clone()); + } + let provider = toml_provider.strict_select(profiles); + + // apply any key fixes + let provider = BackwardsCompatTomlProvider(ForcedSnakeCaseData(provider)); + + // merge the default profile as a base + if profile != Config::DEFAULT_PROFILE { + figment = figment.merge(provider.rename(Config::DEFAULT_PROFILE, profile.clone())); + } + // merge special keys into config + for standalone_key in Config::STANDALONE_SECTIONS { + if let Some(fallback) = STANDALONE_FALLBACK_SECTIONS.get(standalone_key) { + figment = figment.merge( + provider + .fallback(standalone_key, fallback) + .wrap(profile.clone(), standalone_key), + ); + } else { + figment = figment.merge(provider.wrap(profile.clone(), standalone_key)); + } + } + // merge the profile + figment = figment.merge(provider); + figment + } +} + +impl From for Figment { + fn from(c: Config) -> Figment { + let profile = Config::selected_profile(); + let mut figment = Figment::default().merge(DappHardhatDirProvider(&c.__root.0)); + + // merge global foundry.toml file + if let Some(global_toml) = Config::foundry_dir_toml().filter(|p| p.exists()) { + figment = Config::merge_toml_provider( + figment, + TomlFileProvider::new(None, global_toml).cached(), + profile.clone(), + ); + } + // merge local foundry.toml file + figment = Config::merge_toml_provider( + figment, + TomlFileProvider::new(Some("FOUNDRY_CONFIG"), c.__root.0.join(Config::FILE_NAME)) + .cached(), + profile.clone(), + ); + + // merge environment variables + figment = figment + .merge( + Env::prefixed("DAPP_") + .ignore(&["REMAPPINGS", "LIBRARIES", "FFI", "FS_PERMISSIONS"]) + .global(), + ) + .merge( + Env::prefixed("DAPP_TEST_") + .ignore(&["CACHE", "FUZZ_RUNS", "DEPTH", "FFI", "FS_PERMISSIONS"]) + .global(), + ) + .merge(DappEnvCompatProvider) + .merge(Env::raw().only(&["ETHERSCAN_API_KEY"])) + .merge( + Env::prefixed("FOUNDRY_") + .ignore(&[ + "PROFILE", + "REMAPPINGS", + "LIBRARIES", + "FFI", + "FS_PERMISSIONS", + ]) + .map(|key| { + let key = key.as_str(); + if Config::STANDALONE_SECTIONS.iter().any(|section| { + key.starts_with(&format!("{}_", section.to_ascii_uppercase())) + }) { + key.replacen('_', ".", 1).into() + } else { + key.into() + } + }) + .global(), + ) + .select(profile.clone()); + + // we try to merge remappings after we've merged all other providers, this prevents + // redundant fs lookups to determine the default remappings that are eventually updated by + // other providers, like the toml file + let remappings = RemappingsProvider { + auto_detect_remappings: figment + .extract_inner::("auto_detect_remappings") + .unwrap_or(true), + lib_paths: figment + .extract_inner::>("libs") + .map(Cow::Owned) + .unwrap_or_else(|_| Cow::Borrowed(&c.libs)), + root: &c.__root.0, + remappings: figment.extract_inner::>("remappings"), + }; + let merge = figment.merge(remappings); + + Figment::from(c).merge(merge).select(profile) + } +} + +/// Wrapper type for `regex::Regex` that implements `PartialEq` +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(transparent)] +pub struct RegexWrapper { + #[serde(with = "serde_regex")] + inner: regex::Regex, +} + +impl std::ops::Deref for RegexWrapper { + type Target = regex::Regex; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl std::cmp::PartialEq for RegexWrapper { + fn eq(&self, other: &Self) -> bool { + self.as_str() == other.as_str() + } +} + +impl From for regex::Regex { + fn from(wrapper: RegexWrapper) -> Self { + wrapper.inner + } +} + +impl From for RegexWrapper { + fn from(re: Regex) -> Self { + RegexWrapper { inner: re } + } +} + +/// Ser/de `globset::Glob` explicitly to handle `Option` properly +pub(crate) mod from_opt_glob { + use serde::{Deserialize, Deserializer, Serializer}; + + pub fn serialize(value: &Option, serializer: S) -> Result + where + S: Serializer, + { + match value { + Some(glob) => serializer.serialize_str(glob.glob()), + None => serializer.serialize_none(), + } + } + + pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + { + let s: Option = Option::deserialize(deserializer)?; + if let Some(s) = s { + return Ok(Some( + globset::Glob::new(&s).map_err(serde::de::Error::custom)?, + )); + } + Ok(None) + } +} + +/// A helper wrapper around the root path used during Config detection +#[derive(Debug, PartialEq, Eq, Hash, Clone, PartialOrd, Ord, Deserialize, Serialize)] +#[serde(transparent)] +pub struct RootPath(pub PathBuf); + +impl Default for RootPath { + fn default() -> Self { + ".".into() + } +} + +impl> From

for RootPath { + fn from(p: P) -> Self { + RootPath(p.into()) + } +} + +impl AsRef for RootPath { + fn as_ref(&self) -> &Path { + &self.0 + } +} + +/// Parses a config profile +/// +/// All `Profile` date is ignored by serde, however the `Config::to_string_pretty` includes it and +/// returns a toml table like +/// +/// ```toml +/// #[profile.default] +/// src = "..." +/// ``` +/// This ignores the `#[profile.default]` part in the toml +pub fn parse_with_profile( + s: &str, +) -> Result, Error> { + let figment = Config::merge_toml_provider( + Figment::new(), + Toml::string(s).nested(), + Config::DEFAULT_PROFILE, + ); + if figment.profiles().any(|p| p == Config::DEFAULT_PROFILE) { + Ok(Some(( + Config::DEFAULT_PROFILE, + figment.select(Config::DEFAULT_PROFILE).extract()?, + ))) + } else { + Ok(None) + } +} + +impl Provider for Config { + fn metadata(&self) -> Metadata { + Metadata::named("Foundry Config") + } + + #[track_caller] + fn data(&self) -> Result, figment::Error> { + let mut data = Serialized::defaults(self).data()?; + if let Some(entry) = data.get_mut(&self.profile) { + entry.insert("root".to_string(), Value::serialize(self.__root.clone())?); + } + Ok(data) + } + + fn profile(&self) -> Option { + Some(self.profile.clone()) + } +} + +impl Default for Config { + fn default() -> Self { + Self { + profile: Self::DEFAULT_PROFILE, + fs_permissions: FsPermissions::new([PathPermission::read("out")]), + cancun: false, + __root: Default::default(), + src: "src".into(), + test: "test".into(), + script: "script".into(), + out: "out".into(), + libs: vec!["lib".into()], + cache: true, + cache_path: "cache".into(), + broadcast: "broadcast".into(), + allow_paths: vec![], + include_paths: vec![], + force: false, + evm_version: EvmVersion::Paris, + gas_reports: vec!["*".to_string()], + gas_reports_ignore: vec![], + solc: None, + auto_detect_solc: true, + offline: false, + optimizer: true, + optimizer_runs: 200, + optimizer_details: None, + model_checker: None, + extra_output: Default::default(), + extra_output_files: Default::default(), + names: false, + sizes: false, + test_pattern: None, + test_pattern_inverse: None, + contract_pattern: None, + contract_pattern_inverse: None, + path_pattern: None, + path_pattern_inverse: None, + fuzz: Default::default(), + invariant: Default::default(), + ffi: false, + sender: Config::DEFAULT_SENDER, + tx_origin: Config::DEFAULT_SENDER, + initial_balance: U256::from(0xffffffffffffffffffffffffu128), + block_number: 1, + fork_block_number: None, + chain_id: None, + gas_limit: i64::MAX.into(), + code_size_limit: None, + gas_price: None, + block_base_fee_per_gas: 0, + block_coinbase: Address::zero(), + block_timestamp: 1, + block_difficulty: 0, + block_prevrandao: Default::default(), + block_gas_limit: None, + memory_limit: 2u64.pow(25), + eth_rpc_url: None, + eth_rpc_jwt: None, + etherscan_api_key: None, + verbosity: 0, + remappings: vec![], + auto_detect_remappings: true, + libraries: vec![], + ignored_error_codes: vec![ + SolidityErrorCode::SpdxLicenseNotProvided, + SolidityErrorCode::ContractExceeds24576Bytes, + SolidityErrorCode::ContractInitCodeSizeExceeds49152Bytes, + ], + deny_warnings: false, + via_ir: false, + rpc_storage_caching: Default::default(), + rpc_endpoints: Default::default(), + etherscan: Default::default(), + no_storage_caching: false, + no_rpc_rate_limit: false, + use_literal_content: false, + bytecode_hash: BytecodeHash::Ipfs, + cbor_metadata: true, + revert_strings: None, + sparse_mode: false, + build_info: false, + build_info_path: None, + fmt: Default::default(), + doc: Default::default(), + __non_exhaustive: (), + __warnings: vec![], + } + } +} + +/// Wrapper for the config's `gas_limit` value necessary because toml-rs can't handle larger number because integers are stored signed: +/// +/// Due to this limitation this type will be serialized/deserialized as String if it's larger than +/// `i64` +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct GasLimit(pub u64); + +impl From for GasLimit { + fn from(gas: u64) -> Self { + Self(gas) + } +} +impl From for GasLimit { + fn from(gas: i64) -> Self { + Self(gas as u64) + } +} +impl From for GasLimit { + fn from(gas: i32) -> Self { + Self(gas as u64) + } +} +impl From for GasLimit { + fn from(gas: u32) -> Self { + Self(gas as u64) + } +} + +impl From for u64 { + fn from(gas: GasLimit) -> Self { + gas.0 + } +} + +impl Serialize for GasLimit { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + if self.0 > i64::MAX as u64 { + serializer.serialize_str(&self.0.to_string()) + } else { + serializer.serialize_u64(self.0) + } + } +} + +impl<'de> Deserialize<'de> for GasLimit { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + use serde::de::Error; + + #[derive(Deserialize)] + #[serde(untagged)] + enum Gas { + Number(u64), + Text(String), + } + + let gas = match Gas::deserialize(deserializer)? { + Gas::Number(num) => GasLimit(num), + Gas::Text(s) => match s.as_str() { + "max" | "MAX" | "Max" | "u64::MAX" | "u64::Max" => GasLimit(u64::MAX), + s => GasLimit(s.parse().map_err(D::Error::custom)?), + }, + }; + + Ok(gas) + } +} + +/// Variants for selecting the [`Solc`] instance +#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] +#[serde(untagged)] +pub enum SolcReq { + /// Requires a specific solc version, that's either already installed (via `svm`) or will be + /// auto installed (via `svm`) + Version(Version), + /// Path to an existing local solc installation + Local(PathBuf), +} + +impl> From for SolcReq { + fn from(s: T) -> Self { + let s = s.as_ref(); + if let Ok(v) = Version::from_str(s) { + SolcReq::Version(v) + } else { + SolcReq::Local(s.into()) + } + } +} + +/// A convenience provider to retrieve a toml file. +/// This will return an error if the env var is set but the file does not exist +struct TomlFileProvider { + pub env_var: Option<&'static str>, + pub default: PathBuf, + pub cache: Option, Error>>, +} + +impl TomlFileProvider { + fn new(env_var: Option<&'static str>, default: impl Into) -> Self { + Self { + env_var, + default: default.into(), + cache: None, + } + } + + fn env_val(&self) -> Option { + self.env_var.and_then(Env::var) + } + + fn file(&self) -> PathBuf { + self.env_val() + .map(PathBuf::from) + .unwrap_or_else(|| self.default.clone()) + } + + fn is_missing(&self) -> bool { + if let Some(file) = self.env_val() { + let path = Path::new(&file); + if !path.exists() { + return true; + } + } + false + } + + pub fn cached(mut self) -> Self { + self.cache = Some(self.read()); + self + } + + fn read(&self) -> Result, Error> { + use serde::de::Error as _; + if let Some(file) = self.env_val() { + let path = Path::new(&file); + if !path.exists() { + return Err(Error::custom(format!( + "Config file `{}` set in env var `{}` does not exist", + file, + self.env_var.unwrap() + ))); + } + Toml::file(file) + } else { + Toml::file(&self.default) + } + .nested() + .data() + } +} + +impl Provider for TomlFileProvider { + fn metadata(&self) -> Metadata { + if self.is_missing() { + Metadata::named("TOML file provider") + } else { + Toml::file(self.file()).nested().metadata() + } + } + + fn data(&self) -> Result, Error> { + if let Some(cache) = self.cache.as_ref() { + cache.clone() + } else { + self.read() + } + } +} + +/// A Provider that ensures all keys are snake case if they're not standalone sections, See +/// `Config::STANDALONE_SECTIONS` +struct ForcedSnakeCaseData

(P); + +impl Provider for ForcedSnakeCaseData

{ + fn metadata(&self) -> Metadata { + self.0.metadata() + } + + fn data(&self) -> Result, Error> { + let mut map = Map::new(); + for (profile, dict) in self.0.data()? { + if Config::STANDALONE_SECTIONS.contains(&profile.as_ref()) { + // don't force snake case for keys in standalone sections + map.insert(profile, dict); + continue; + } + map.insert( + profile, + dict.into_iter() + .map(|(k, v)| (k.to_snake_case(), v)) + .collect(), + ); + } + Ok(map) + } +} + +/// A Provider that handles breaking changes in toml files +struct BackwardsCompatTomlProvider

(P); + +impl Provider for BackwardsCompatTomlProvider

{ + fn metadata(&self) -> Metadata { + self.0.metadata() + } + + fn data(&self) -> Result, Error> { + let mut map = Map::new(); + let solc_env = std::env::var("FOUNDRY_SOLC_VERSION") + .or_else(|_| std::env::var("DAPP_SOLC_VERSION")) + .map(Value::from) + .ok(); + for (profile, mut dict) in self.0.data()? { + if let Some(v) = solc_env.clone().or_else(|| dict.remove("solc_version")) { + dict.insert("solc".to_string(), v); + } + map.insert(profile, dict); + } + Ok(map) + } +} + +/// A provider that sets the `src` and `output` path depending on their existence. +struct DappHardhatDirProvider<'a>(&'a Path); + +impl<'a> Provider for DappHardhatDirProvider<'a> { + fn metadata(&self) -> Metadata { + Metadata::named("Dapp Hardhat dir compat") + } + + fn data(&self) -> Result, Error> { + let mut dict = Dict::new(); + dict.insert( + "src".to_string(), + ProjectPathsConfig::find_source_dir(self.0) + .file_name() + .unwrap() + .to_string_lossy() + .to_string() + .into(), + ); + dict.insert( + "out".to_string(), + ProjectPathsConfig::find_artifacts_dir(self.0) + .file_name() + .unwrap() + .to_string_lossy() + .to_string() + .into(), + ); + + // detect libs folders: + // if `lib` _and_ `node_modules` exists: include both + // if only `node_modules` exists: include `node_modules` + // include `lib` otherwise + let mut libs = vec![]; + let node_modules = self.0.join("node_modules"); + let lib = self.0.join("lib"); + if node_modules.exists() { + if lib.exists() { + libs.push(lib.file_name().unwrap().to_string_lossy().to_string()); + } + libs.push( + node_modules + .file_name() + .unwrap() + .to_string_lossy() + .to_string(), + ); + } else { + libs.push(lib.file_name().unwrap().to_string_lossy().to_string()); + } + + dict.insert("libs".to_string(), libs.into()); + + Ok(Map::from([(Config::selected_profile(), dict)])) + } +} + +/// A provider that checks for DAPP_ env vars that are named differently than FOUNDRY_ +struct DappEnvCompatProvider; + +impl Provider for DappEnvCompatProvider { + fn metadata(&self) -> Metadata { + Metadata::named("Dapp env compat") + } + + fn data(&self) -> Result, Error> { + use serde::de::Error as _; + use std::env; + + let mut dict = Dict::new(); + if let Ok(val) = env::var("DAPP_TEST_NUMBER") { + dict.insert( + "block_number".to_string(), + val.parse::().map_err(figment::Error::custom)?.into(), + ); + } + if let Ok(val) = env::var("DAPP_TEST_ADDRESS") { + dict.insert("sender".to_string(), val.into()); + } + if let Ok(val) = env::var("DAPP_FORK_BLOCK") { + dict.insert( + "fork_block_number".to_string(), + val.parse::().map_err(figment::Error::custom)?.into(), + ); + } else if let Ok(val) = env::var("DAPP_TEST_NUMBER") { + dict.insert( + "fork_block_number".to_string(), + val.parse::().map_err(figment::Error::custom)?.into(), + ); + } + if let Ok(val) = env::var("DAPP_TEST_TIMESTAMP") { + dict.insert( + "block_timestamp".to_string(), + val.parse::().map_err(figment::Error::custom)?.into(), + ); + } + if let Ok(val) = env::var("DAPP_BUILD_OPTIMIZE_RUNS") { + dict.insert( + "optimizer_runs".to_string(), + val.parse::().map_err(figment::Error::custom)?.into(), + ); + } + if let Ok(val) = env::var("DAPP_BUILD_OPTIMIZE") { + // Activate Solidity optimizer (0 or 1) + let val = val.parse::().map_err(figment::Error::custom)?; + if val > 1 { + return Err(format!( + "Invalid $DAPP_BUILD_OPTIMIZE value `{val}`, expected 0 or 1" + ) + .into()); + } + dict.insert("optimizer".to_string(), (val == 1).into()); + } + + // libraries in env vars either as `[..]` or single string separated by comma + if let Ok(val) = env::var("DAPP_LIBRARIES").or_else(|_| env::var("FOUNDRY_LIBRARIES")) { + dict.insert("libraries".to_string(), utils::to_array_value(&val)?); + } + + let mut fuzz_dict = Dict::new(); + if let Ok(val) = env::var("DAPP_TEST_FUZZ_RUNS") { + fuzz_dict.insert( + "runs".to_string(), + val.parse::().map_err(figment::Error::custom)?.into(), + ); + } + dict.insert("fuzz".to_string(), fuzz_dict.into()); + + let mut invariant_dict = Dict::new(); + if let Ok(val) = env::var("DAPP_TEST_DEPTH") { + invariant_dict.insert( + "depth".to_string(), + val.parse::().map_err(figment::Error::custom)?.into(), + ); + } + dict.insert("invariant".to_string(), invariant_dict.into()); + + Ok(Map::from([(Config::selected_profile(), dict)])) + } +} + +/// Renames a profile from `from` to `to +/// +/// For example given: +/// +/// ```toml +/// [from] +/// key = "value" +/// ``` +/// +/// RenameProfileProvider will output +/// +/// ```toml +/// [to] +/// key = "value" +/// ``` +struct RenameProfileProvider

{ + provider: P, + from: Profile, + to: Profile, +} + +impl

RenameProfileProvider

{ + pub fn new(provider: P, from: impl Into, to: impl Into) -> Self { + Self { + provider, + from: from.into(), + to: to.into(), + } + } +} + +impl Provider for RenameProfileProvider

{ + fn metadata(&self) -> Metadata { + self.provider.metadata() + } + fn data(&self) -> Result, Error> { + let mut data = self.provider.data()?; + if let Some(data) = data.remove(&self.from) { + return Ok(Map::from([(self.to.clone(), data)])); + } + Ok(Default::default()) + } + fn profile(&self) -> Option { + Some(self.to.clone()) + } +} + +/// Unwraps a profile reducing the key depth +/// +/// For example given: +/// +/// ```toml +/// [wrapping_key.profile] +/// key = "value" +/// ``` +/// +/// UnwrapProfileProvider will output: +/// +/// ```toml +/// [profile] +/// key = "value" +/// ``` +struct UnwrapProfileProvider

{ + provider: P, + wrapping_key: Profile, + profile: Profile, +} + +impl

UnwrapProfileProvider

{ + pub fn new(provider: P, wrapping_key: impl Into, profile: impl Into) -> Self { + Self { + provider, + wrapping_key: wrapping_key.into(), + profile: profile.into(), + } + } +} + +impl Provider for UnwrapProfileProvider

{ + fn metadata(&self) -> Metadata { + self.provider.metadata() + } + fn data(&self) -> Result, Error> { + self.provider.data().and_then(|mut data| { + if let Some(profiles) = data.remove(&self.wrapping_key) { + for (profile_str, profile_val) in profiles { + let profile = Profile::new(&profile_str); + if profile != self.profile { + continue; + } + match profile_val { + Value::Dict(_, dict) => return Ok(profile.collect(dict)), + bad_val => { + let mut err = Error::from(figment::error::Kind::InvalidType( + bad_val.to_actual(), + "dict".into(), + )); + err.metadata = Some(self.provider.metadata()); + err.profile = Some(self.profile.clone()); + return Err(err); + } + } + } + } + Ok(Default::default()) + }) + } + fn profile(&self) -> Option { + Some(self.profile.clone()) + } +} + +/// Wraps a profile in another profile +/// +/// For example given: +/// +/// ```toml +/// [profile] +/// key = "value" +/// ``` +/// +/// WrapProfileProvider will output: +/// +/// ```toml +/// [wrapping_key.profile] +/// key = "value" +/// ``` +struct WrapProfileProvider

{ + provider: P, + wrapping_key: Profile, + profile: Profile, +} + +impl

WrapProfileProvider

{ + pub fn new(provider: P, wrapping_key: impl Into, profile: impl Into) -> Self { + Self { + provider, + wrapping_key: wrapping_key.into(), + profile: profile.into(), + } + } +} + +impl Provider for WrapProfileProvider

{ + fn metadata(&self) -> Metadata { + self.provider.metadata() + } + fn data(&self) -> Result, Error> { + if let Some(inner) = self.provider.data()?.remove(&self.profile) { + let value = Value::from(inner); + let dict = [(self.profile.to_string().to_snake_case(), value)] + .into_iter() + .collect(); + Ok(self.wrapping_key.collect(dict)) + } else { + Ok(Default::default()) + } + } + fn profile(&self) -> Option { + Some(self.profile.clone()) + } +} + +/// Extracts the profile from the `profile` key and using the original key as backup, merging +/// values where necessary +/// +/// For example given: +/// +/// ```toml +/// [profile.cool] +/// key = "value" +/// +/// [cool] +/// key2 = "value2" +/// ``` +/// +/// OptionalStrictProfileProvider will output: +/// +/// ```toml +/// [cool] +/// key = "value" +/// key2 = "value2" +/// ``` +/// +/// And emit a deprecation warning +struct OptionalStrictProfileProvider

{ + provider: P, + profiles: Vec, +} + +impl

OptionalStrictProfileProvider

{ + pub const PROFILE_PROFILE: Profile = Profile::const_new("profile"); + + pub fn new(provider: P, profiles: impl IntoIterator>) -> Self { + Self { + provider, + profiles: profiles.into_iter().map(|profile| profile.into()).collect(), + } + } +} + +impl Provider for OptionalStrictProfileProvider

{ + fn metadata(&self) -> Metadata { + self.provider.metadata() + } + fn data(&self) -> Result, Error> { + let mut figment = Figment::from(&self.provider); + for profile in &self.profiles { + figment = figment.merge(UnwrapProfileProvider::new( + &self.provider, + Self::PROFILE_PROFILE, + profile.clone(), + )); + } + figment.data().map_err(|err| { + // figment does tag metadata and tries to map metadata to an error, since we use a new + // figment in this provider this new figment does not know about the metadata of the + // provider and can't map the metadata to the error. Therefor we return the root error + // if this error originated in the provider's data. + if let Err(root_err) = self.provider.data() { + return root_err; + } + err + }) + } + fn profile(&self) -> Option { + self.profiles.last().cloned() + } +} + +trait ProviderExt: Provider { + fn rename( + &self, + from: impl Into, + to: impl Into, + ) -> RenameProfileProvider<&Self> { + RenameProfileProvider::new(self, from, to) + } + + fn wrap( + &self, + wrapping_key: impl Into, + profile: impl Into, + ) -> WrapProfileProvider<&Self> { + WrapProfileProvider::new(self, wrapping_key, profile) + } + + fn strict_select( + &self, + profiles: impl IntoIterator>, + ) -> OptionalStrictProfileProvider<&Self> { + OptionalStrictProfileProvider::new(self, profiles) + } + + fn fallback( + &self, + profile: impl Into, + fallback: impl Into, + ) -> FallbackProfileProvider<&Self> { + FallbackProfileProvider::new(self, profile, fallback) + } +} +impl ProviderExt for P {} + +/// A subset of the foundry `Config` +/// used to initialize a `foundry.toml` file +/// +/// # Example +/// +/// ```rust +/// use foundry_config::{Config, BasicConfig}; +/// use serde::Deserialize; +/// +/// let my_config = Config::figment().extract::(); +/// ``` +#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)] +pub struct BasicConfig { + /// the profile tag: `[profile.default]` + #[serde(skip)] + pub profile: Profile, + /// path of the source contracts dir, like `src` or `contracts` + pub src: PathBuf, + /// path to where artifacts shut be written to + pub out: PathBuf, + /// all library folders to include, `lib`, `node_modules` + pub libs: Vec, + /// `Remappings` to use for this repo + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub remappings: Vec, +} + +impl BasicConfig { + /// Serialize the config as a String of TOML. + /// + /// This serializes to a table with the name of the profile + pub fn to_string_pretty(&self) -> Result { + let s = toml::to_string_pretty(self)?; + Ok(format!( + "\ +[profile.{}] +{s} +# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options\n", + self.profile + )) + } +} + +pub(crate) mod from_str_lowercase { + use std::str::FromStr; + + use serde::{Deserialize, Deserializer, Serializer}; + + pub fn serialize(value: &T, serializer: S) -> Result + where + T: std::fmt::Display, + S: Serializer, + { + serializer.collect_str(&value.to_string().to_lowercase()) + } + + pub fn deserialize<'de, T, D>(deserializer: D) -> Result + where + D: Deserializer<'de>, + T: FromStr, + T::Err: std::fmt::Display, + { + String::deserialize(deserializer)? + .to_lowercase() + .parse() + .map_err(serde::de::Error::custom) + } +} + +fn canonic(path: impl Into) -> PathBuf { + let path = path.into(); + ethers_solc::utils::canonicalize(&path).unwrap_or(path) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + cache::{CachedChains, CachedEndpoints}, + endpoints::RpcEndpoint, + etherscan::ResolvedEtherscanConfigs, + fs_permissions::PathPermission, + }; + use ethers_core::types::Chain::Moonbeam; + use ethers_solc::artifacts::{ModelCheckerEngine, YulDetails}; + use figment::{error::Kind::InvalidType, value::Value, Figment}; + use pretty_assertions::assert_eq; + use std::{collections::BTreeMap, fs::File, io::Write, str::FromStr}; + use tempfile::tempdir; + + // Helper function to clear `__warnings` in config, since it will be populated during loading + // from file, causing testing problem when comparing to those created from `default()`, etc. + fn clear_warning(config: &mut Config) { + config.__warnings = vec![]; + } + + #[test] + fn default_sender() { + assert_eq!( + Config::DEFAULT_SENDER, + "0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38" + .parse() + .unwrap() + ); + } + + #[test] + fn test_caching() { + let mut config = Config::default(); + let chain_id = ethers_core::types::Chain::Mainnet; + let url = "https://eth-mainnet.alchemyapi"; + assert!(config.enable_caching(url, chain_id)); + + config.no_storage_caching = true; + assert!(!config.enable_caching(url, chain_id)); + + config.no_storage_caching = false; + assert!(!config.enable_caching(url, ethers_core::types::Chain::Dev)); + } + + #[test] + fn test_install_dir() { + figment::Jail::expect_with(|jail| { + let config = Config::load(); + assert_eq!(config.install_lib_dir(), PathBuf::from("lib")); + jail.create_file( + "foundry.toml", + r#" + [profile.default] + libs = ['node_modules', 'lib'] + "#, + )?; + let config = Config::load(); + assert_eq!(config.install_lib_dir(), PathBuf::from("lib")); + + jail.create_file( + "foundry.toml", + r#" + [profile.default] + libs = ['custom', 'node_modules', 'lib'] + "#, + )?; + let config = Config::load(); + assert_eq!(config.install_lib_dir(), PathBuf::from("custom")); + + Ok(()) + }); + } + + #[test] + fn test_figment_is_default() { + figment::Jail::expect_with(|_| { + let mut default: Config = Config::figment().extract().unwrap(); + default.profile = Config::default().profile; + assert_eq!(default, Config::default()); + Ok(()) + }); + } + + #[test] + fn test_default_round_trip() { + figment::Jail::expect_with(|_| { + let original = Config::figment(); + let roundtrip = Figment::from(Config::from_provider(&original)); + for figment in &[original, roundtrip] { + let config = Config::from_provider(figment); + assert_eq!(config, Config::default()); + } + Ok(()) + }); + } + + #[test] + fn ffi_env_disallowed() { + figment::Jail::expect_with(|jail| { + jail.set_env("FOUNDRY_FFI", "true"); + jail.set_env("FFI", "true"); + jail.set_env("DAPP_FFI", "true"); + let config = Config::load(); + assert!(!config.ffi); + + Ok(()) + }); + } + + #[test] + fn test_profile_env() { + figment::Jail::expect_with(|jail| { + jail.set_env("FOUNDRY_PROFILE", "default"); + let figment = Config::figment(); + assert_eq!(figment.profile(), "default"); + + jail.set_env("FOUNDRY_PROFILE", "hardhat"); + let figment: Figment = Config::hardhat().into(); + assert_eq!(figment.profile(), "hardhat"); + + jail.create_file( + "foundry.toml", + r#" + [profile.default] + libs = ['lib'] + [profile.local] + libs = ['modules'] + "#, + )?; + jail.set_env("FOUNDRY_PROFILE", "local"); + let config = Config::load(); + assert_eq!(config.libs, vec![PathBuf::from("modules")]); + + Ok(()) + }); + } + + #[test] + fn test_default_test_path() { + figment::Jail::expect_with(|_| { + let config = Config::default(); + let paths_config = config.project_paths(); + assert_eq!(paths_config.tests, PathBuf::from(r"test")); + Ok(()) + }); + } + + #[test] + fn test_default_libs() { + figment::Jail::expect_with(|jail| { + let config = Config::load(); + assert_eq!(config.libs, vec![PathBuf::from("lib")]); + + fs::create_dir_all(jail.directory().join("node_modules")).unwrap(); + let config = Config::load(); + assert_eq!(config.libs, vec![PathBuf::from("node_modules")]); + + fs::create_dir_all(jail.directory().join("lib")).unwrap(); + let config = Config::load(); + assert_eq!( + config.libs, + vec![PathBuf::from("lib"), PathBuf::from("node_modules")] + ); + + Ok(()) + }); + } + + #[test] + fn test_inheritance_from_default_test_path() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "foundry.toml", + r#" + [profile.default] + test = "defaulttest" + src = "defaultsrc" + libs = ['lib', 'node_modules'] + + [profile.custom] + src = "customsrc" + "#, + )?; + + let config = Config::load(); + assert_eq!(config.src, PathBuf::from("defaultsrc")); + assert_eq!( + config.libs, + vec![PathBuf::from("lib"), PathBuf::from("node_modules")] + ); + + jail.set_env("FOUNDRY_PROFILE", "custom"); + let config = Config::load(); + + assert_eq!(config.src, PathBuf::from("customsrc")); + assert_eq!(config.test, PathBuf::from("defaulttest")); + assert_eq!( + config.libs, + vec![PathBuf::from("lib"), PathBuf::from("node_modules")] + ); + + Ok(()) + }); + } + + #[test] + fn test_custom_test_path() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "foundry.toml", + r#" + [profile.default] + test = "mytest" + "#, + )?; + + let config = Config::load(); + let paths_config = config.project_paths(); + assert_eq!(paths_config.tests, PathBuf::from(r"mytest")); + Ok(()) + }); + } + + #[test] + fn test_remappings() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "foundry.toml", + r#" + [profile.default] + src = "some-source" + out = "some-out" + cache = true + "#, + )?; + let config = Config::load(); + assert!(config.remappings.is_empty()); + + jail.create_file( + "remappings.txt", + r#" + file-ds-test/=lib/ds-test/ + file-other/=lib/other/ + "#, + )?; + + let config = Config::load(); + assert_eq!( + config.remappings, + vec![ + Remapping::from_str("file-ds-test/=lib/ds-test/") + .unwrap() + .into(), + Remapping::from_str("file-other/=lib/other/") + .unwrap() + .into(), + ], + ); + + jail.set_env("DAPP_REMAPPINGS", "ds-test=lib/ds-test/\nother/=lib/other/"); + let config = Config::load(); + + assert_eq!( + config.remappings, + vec![ + // From environment (should have precedence over remapping.txt) + Remapping::from_str("ds-test=lib/ds-test/").unwrap().into(), + Remapping::from_str("other/=lib/other/").unwrap().into(), + // From remapping.txt (should have less precedence than remapping.txt) + Remapping::from_str("file-ds-test/=lib/ds-test/") + .unwrap() + .into(), + Remapping::from_str("file-other/=lib/other/") + .unwrap() + .into(), + ], + ); + + Ok(()) + }); + } + + #[test] + fn test_remappings_override() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "foundry.toml", + r#" + [profile.default] + src = "some-source" + out = "some-out" + cache = true + "#, + )?; + let config = Config::load(); + assert!(config.remappings.is_empty()); + + jail.create_file( + "remappings.txt", + r#" + ds-test/=lib/ds-test/ + other/=lib/other/ + "#, + )?; + + let config = Config::load(); + assert_eq!( + config.remappings, + vec![ + Remapping::from_str("ds-test/=lib/ds-test/").unwrap().into(), + Remapping::from_str("other/=lib/other/").unwrap().into(), + ], + ); + + jail.set_env( + "DAPP_REMAPPINGS", + "ds-test/=lib/ds-test/src/\nenv-lib/=lib/env-lib/", + ); + let config = Config::load(); + + // Remappings should now be: + // - ds-test from environment (lib/ds-test/src/) + // - other from remappings.txt (lib/other/) + // - env-lib from environment (lib/env-lib/) + assert_eq!( + config.remappings, + vec![ + Remapping::from_str("ds-test/=lib/ds-test/src/") + .unwrap() + .into(), + Remapping::from_str("env-lib/=lib/env-lib/").unwrap().into(), + Remapping::from_str("other/=lib/other/").unwrap().into(), + ], + ); + + // contains additional remapping to the source dir + assert_eq!( + config.get_all_remappings(), + vec![ + Remapping::from_str("ds-test/=lib/ds-test/src/").unwrap(), + Remapping::from_str("env-lib/=lib/env-lib/").unwrap(), + Remapping::from_str("other/=lib/other/").unwrap(), + ], + ); + + Ok(()) + }); + } + + #[test] + fn test_can_update_libs() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "foundry.toml", + r#" + [profile.default] + libs = ["node_modules"] + "#, + )?; + + let mut config = Config::load(); + config.libs.push("libs".into()); + config.update_libs().unwrap(); + + let config = Config::load(); + assert_eq!( + config.libs, + vec![PathBuf::from("node_modules"), PathBuf::from("libs"),] + ); + Ok(()) + }); + } + + #[test] + fn test_large_gas_limit() { + figment::Jail::expect_with(|jail| { + let gas = u64::MAX; + jail.create_file( + "foundry.toml", + &format!( + r#" + [profile.default] + gas_limit = "{gas}" + "# + ), + )?; + + let config = Config::load(); + assert_eq!( + config, + Config { + gas_limit: gas.into(), + ..Config::default() + } + ); + + Ok(()) + }); + } + + #[test] + #[should_panic] + fn test_toml_file_parse_failure() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "foundry.toml", + r#" + [profile.default] + eth_rpc_url = "https://example.com/ + "#, + )?; + + let _config = Config::load(); + + Ok(()) + }); + } + + #[test] + #[should_panic] + fn test_toml_file_non_existing_config_var_failure() { + figment::Jail::expect_with(|jail| { + jail.set_env("FOUNDRY_CONFIG", "this config does not exist"); + + let _config = Config::load(); + + Ok(()) + }); + } + + #[test] + fn test_resolve_etherscan_with_chain() { + figment::Jail::expect_with(|jail| { + let env_key = "__BSC_ETHERSCAN_API_KEY"; + let env_value = "env value"; + jail.create_file( + "foundry.toml", + r#" + [profile.default] + + [etherscan] + bsc = { key = "${__BSC_ETHERSCAN_API_KEY}", url = "https://api.bscscan.com/api" } + "#, + )?; + + let config = Config::load(); + assert!(config + .get_etherscan_config_with_chain(None::) + .unwrap() + .is_none()); + assert!(config + .get_etherscan_config_with_chain(Some(ethers_core::types::Chain::BinanceSmartChain)) + .is_err()); + + std::env::set_var(env_key, env_value); + + assert_eq!( + config + .get_etherscan_config_with_chain(Some( + ethers_core::types::Chain::BinanceSmartChain + )) + .unwrap() + .unwrap() + .key, + env_value + ); + + let mut with_key = config; + with_key.etherscan_api_key = Some("via etherscan_api_key".to_string()); + + assert_eq!( + with_key + .get_etherscan_config_with_chain(Some( + ethers_core::types::Chain::BinanceSmartChain + )) + .unwrap() + .unwrap() + .key, + "via etherscan_api_key" + ); + + std::env::remove_var(env_key); + Ok(()) + }); + } + + #[test] + fn test_resolve_etherscan() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "foundry.toml", + r#" + [profile.default] + + [etherscan] + mainnet = { key = "FX42Z3BBJJEWXWGYV2X1CIPRSCN" } + moonbeam = { key = "${_CONFIG_ETHERSCAN_MOONBEAM}" } + "#, + )?; + + let config = Config::load(); + + assert!(config.etherscan.clone().resolved().has_unresolved()); + + jail.set_env("_CONFIG_ETHERSCAN_MOONBEAM", "123456789"); + + let configs = config.etherscan.resolved(); + assert!(!configs.has_unresolved()); + + let mb_urls = Moonbeam.etherscan_urls().unwrap(); + let mainnet_urls = Mainnet.etherscan_urls().unwrap(); + assert_eq!( + configs, + ResolvedEtherscanConfigs::new([ + ( + "mainnet", + ResolvedEtherscanConfig { + api_url: mainnet_urls.0.to_string(), + chain: Some(Mainnet.into()), + browser_url: Some(mainnet_urls.1.to_string()), + key: "FX42Z3BBJJEWXWGYV2X1CIPRSCN".to_string(), + } + ), + ( + "moonbeam", + ResolvedEtherscanConfig { + api_url: mb_urls.0.to_string(), + chain: Some(Moonbeam.into()), + browser_url: Some(mb_urls.1.to_string()), + key: "123456789".to_string(), + } + ), + ]) + ); + + Ok(()) + }); + } + + #[test] + fn test_resolve_rpc_url() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "foundry.toml", + r#" + [profile.default] + [rpc_endpoints] + optimism = "https://example.com/" + mainnet = "${_CONFIG_MAINNET}" + "#, + )?; + jail.set_env( + "_CONFIG_MAINNET", + "https://eth-mainnet.alchemyapi.io/v2/123455", + ); + + let mut config = Config::load(); + assert_eq!( + "http://localhost:8545", + config.get_rpc_url_or_localhost_http().unwrap() + ); + + config.eth_rpc_url = Some("mainnet".to_string()); + assert_eq!( + "https://eth-mainnet.alchemyapi.io/v2/123455", + config.get_rpc_url_or_localhost_http().unwrap() + ); + + config.eth_rpc_url = Some("optimism".to_string()); + assert_eq!( + "https://example.com/", + config.get_rpc_url_or_localhost_http().unwrap() + ); + + Ok(()) + }) + } + + #[test] + fn test_resolve_rpc_url_if_etherscan_set() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "foundry.toml", + r#" + [profile.default] + etherscan_api_key = "dummy" + [rpc_endpoints] + optimism = "https://example.com/" + "#, + )?; + + let config = Config::load(); + assert_eq!( + "http://localhost:8545", + config.get_rpc_url_or_localhost_http().unwrap() + ); + + Ok(()) + }) + } + + #[test] + fn test_resolve_rpc_url_alias() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "foundry.toml", + r#" + [profile.default] + [rpc_endpoints] + polygonMumbai = "https://polygon-mumbai.g.alchemy.com/v2/${_RESOLVE_RPC_ALIAS}" + "#, + )?; + let mut config = Config::load(); + config.eth_rpc_url = Some("polygonMumbai".to_string()); + assert!(config.get_rpc_url().unwrap().is_err()); + + jail.set_env("_RESOLVE_RPC_ALIAS", "123455"); + + let mut config = Config::load(); + config.eth_rpc_url = Some("polygonMumbai".to_string()); + assert_eq!( + "https://polygon-mumbai.g.alchemy.com/v2/123455", + config.get_rpc_url().unwrap().unwrap() + ); + + Ok(()) + }) + } + + #[test] + fn test_resolve_endpoints() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "foundry.toml", + r#" + [profile.default] + eth_rpc_url = "optimism" + [rpc_endpoints] + optimism = "https://example.com/" + mainnet = "${_CONFIG_MAINNET}" + mainnet_2 = "https://eth-mainnet.alchemyapi.io/v2/${_CONFIG_API_KEY1}" + mainnet_3 = "https://eth-mainnet.alchemyapi.io/v2/${_CONFIG_API_KEY1}/${_CONFIG_API_KEY2}" + "#, + )?; + + let config = Config::load(); + + assert_eq!( + config.get_rpc_url().unwrap().unwrap(), + "https://example.com/" + ); + + assert!(config.rpc_endpoints.clone().resolved().has_unresolved()); + + jail.set_env( + "_CONFIG_MAINNET", + "https://eth-mainnet.alchemyapi.io/v2/123455", + ); + jail.set_env("_CONFIG_API_KEY1", "123456"); + jail.set_env("_CONFIG_API_KEY2", "98765"); + + let endpoints = config.rpc_endpoints.resolved(); + + assert!(!endpoints.has_unresolved()); + + assert_eq!( + endpoints, + RpcEndpoints::new([ + ( + "optimism", + RpcEndpoint::Url("https://example.com/".to_string()) + ), + ( + "mainnet", + RpcEndpoint::Url("https://eth-mainnet.alchemyapi.io/v2/123455".to_string()) + ), + ( + "mainnet_2", + RpcEndpoint::Url("https://eth-mainnet.alchemyapi.io/v2/123456".to_string()) + ), + ( + "mainnet_3", + RpcEndpoint::Url( + "https://eth-mainnet.alchemyapi.io/v2/123456/98765".to_string() + ) + ), + ]) + .resolved() + ); + + Ok(()) + }); + } + + #[test] + fn test_extract_etherscan_config() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "foundry.toml", + r#" + [profile.default] + etherscan_api_key = "optimism" + + [etherscan] + optimism = { key = "https://etherscan-optimism.com/" } + mumbai = { key = "https://etherscan-mumbai.com/" } + "#, + )?; + + let mut config = Config::load(); + + let optimism = config.get_etherscan_api_key(Some(ethers_core::types::Chain::Optimism)); + assert_eq!( + optimism, + Some("https://etherscan-optimism.com/".to_string()) + ); + + config.etherscan_api_key = Some("mumbai".to_string()); + + let mumbai = + config.get_etherscan_api_key(Some(ethers_core::types::Chain::PolygonMumbai)); + assert_eq!(mumbai, Some("https://etherscan-mumbai.com/".to_string())); + + Ok(()) + }); + } + + #[test] + fn test_extract_etherscan_config_by_chain() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "foundry.toml", + r#" + [profile.default] + + [etherscan] + mumbai = { key = "https://etherscan-mumbai.com/", chain = 80001 } + "#, + )?; + + let config = Config::load(); + + let mumbai = config + .get_etherscan_config_with_chain(Some(ethers_core::types::Chain::PolygonMumbai)) + .unwrap() + .unwrap(); + assert_eq!(mumbai.key, "https://etherscan-mumbai.com/".to_string()); + + Ok(()) + }); + } + + #[test] + fn test_extract_etherscan_config_by_chain_with_url() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "foundry.toml", + r#" + [profile.default] + + [etherscan] + mumbai = { key = "https://etherscan-mumbai.com/", chain = 80001 , url = "https://verifier-url.com/"} + "#, + )?; + + let config = Config::load(); + + let mumbai = config + .get_etherscan_config_with_chain(Some(ethers_core::types::Chain::PolygonMumbai)) + .unwrap() + .unwrap(); + assert_eq!(mumbai.key, "https://etherscan-mumbai.com/".to_string()); + assert_eq!(mumbai.api_url, "https://verifier-url.com/".to_string()); + + Ok(()) + }); + } + + #[test] + fn test_extract_etherscan_config_by_chain_and_alias() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "foundry.toml", + r#" + [profile.default] + eth_rpc_url = "mumbai" + + [etherscan] + mumbai = { key = "https://etherscan-mumbai.com/" } + + [rpc_endpoints] + mumbai = "https://polygon-mumbai.g.alchemy.com/v2/mumbai" + "#, + )?; + + let config = Config::load(); + + let mumbai = config + .get_etherscan_config_with_chain(Option::::None) + .unwrap() + .unwrap(); + assert_eq!(mumbai.key, "https://etherscan-mumbai.com/".to_string()); + + let mumbai_rpc = config.get_rpc_url().unwrap().unwrap(); + assert_eq!(mumbai_rpc, "https://polygon-mumbai.g.alchemy.com/v2/mumbai"); + Ok(()) + }); + } + + #[test] + fn test_toml_file() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "foundry.toml", + r#" + [profile.default] + src = "some-source" + out = "some-out" + cache = true + eth_rpc_url = "https://example.com/" + verbosity = 3 + remappings = ["ds-test=lib/ds-test/"] + via_ir = true + rpc_storage_caching = { chains = [1, "optimism", 999999], endpoints = "all"} + use_literal_content = false + bytecode_hash = "ipfs" + cbor_metadata = true + revert_strings = "strip" + allow_paths = ["allow", "paths"] + build_info_path = "build-info" + + [rpc_endpoints] + optimism = "https://example.com/" + mainnet = "${RPC_MAINNET}" + mainnet_2 = "https://eth-mainnet.alchemyapi.io/v2/${API_KEY}" + mainnet_3 = "https://eth-mainnet.alchemyapi.io/v2/${API_KEY}/${ANOTHER_KEY}" + "#, + )?; + + let config = Config::load(); + assert_eq!( + config, + Config { + src: "some-source".into(), + out: "some-out".into(), + cache: true, + eth_rpc_url: Some("https://example.com/".to_string()), + remappings: vec![Remapping::from_str("ds-test=lib/ds-test/").unwrap().into()], + verbosity: 3, + via_ir: true, + rpc_storage_caching: StorageCachingConfig { + chains: CachedChains::Chains(vec![ + Chain::Named(ethers_core::types::Chain::Mainnet), + Chain::Named(ethers_core::types::Chain::Optimism), + Chain::Id(999999) + ]), + endpoints: CachedEndpoints::All + }, + use_literal_content: false, + bytecode_hash: BytecodeHash::Ipfs, + cbor_metadata: true, + revert_strings: Some(RevertStrings::Strip), + allow_paths: vec![PathBuf::from("allow"), PathBuf::from("paths")], + rpc_endpoints: RpcEndpoints::new([ + ( + "optimism", + RpcEndpoint::Url("https://example.com/".to_string()) + ), + ("mainnet", RpcEndpoint::Env("${RPC_MAINNET}".to_string())), + ( + "mainnet_2", + RpcEndpoint::Env( + "https://eth-mainnet.alchemyapi.io/v2/${API_KEY}".to_string() + ) + ), + ( + "mainnet_3", + RpcEndpoint::Env( + "https://eth-mainnet.alchemyapi.io/v2/${API_KEY}/${ANOTHER_KEY}" + .to_string() + ) + ), + ]), + build_info_path: Some("build-info".into()), + ..Config::default() + } + ); + + Ok(()) + }); + } + + #[test] + fn test_load_remappings() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "foundry.toml", + r#" + [profile.default] + remappings = ['nested/=lib/nested/'] + "#, + )?; + + let config = Config::load_with_root(jail.directory()); + assert_eq!( + config.remappings, + vec![Remapping::from_str("nested/=lib/nested/").unwrap().into()] + ); + + Ok(()) + }); + } + + #[test] + fn test_load_full_toml() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "foundry.toml", + r#" + [profile.default] + auto_detect_solc = true + block_base_fee_per_gas = 0 + block_coinbase = '0x0000000000000000000000000000000000000000' + block_difficulty = 0 + block_prevrandao = '0x0000000000000000000000000000000000000000000000000000000000000000' + block_number = 1 + block_timestamp = 1 + use_literal_content = false + bytecode_hash = 'ipfs' + cbor_metadata = true + cache = true + cache_path = 'cache' + evm_version = 'london' + extra_output = [] + extra_output_files = [] + ffi = false + force = false + gas_limit = 9223372036854775807 + gas_price = 0 + gas_reports = ['*'] + ignored_error_codes = [1878] + deny_warnings = false + initial_balance = '0xffffffffffffffffffffffff' + libraries = [] + libs = ['lib'] + memory_limit = 33554432 + names = false + no_storage_caching = false + no_rpc_rate_limit = false + offline = false + optimizer = true + optimizer_runs = 200 + out = 'out' + remappings = ['nested/=lib/nested/'] + sender = '0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38' + sizes = false + sparse_mode = false + src = 'src' + test = 'test' + tx_origin = '0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38' + verbosity = 0 + via_ir = false + + [profile.default.rpc_storage_caching] + chains = 'all' + endpoints = 'all' + + [rpc_endpoints] + optimism = "https://example.com/" + mainnet = "${RPC_MAINNET}" + mainnet_2 = "https://eth-mainnet.alchemyapi.io/v2/${API_KEY}" + + [fuzz] + runs = 256 + seed = '0x3e8' + max_test_rejects = 65536 + + [invariant] + runs = 256 + depth = 15 + fail_on_revert = false + call_override = false + shrink_sequence = true + "#, + )?; + + let config = Config::load_with_root(jail.directory()); + + assert_eq!(config.fuzz.seed, Some(1000.into())); + assert_eq!( + config.remappings, + vec![Remapping::from_str("nested/=lib/nested/").unwrap().into()] + ); + + assert_eq!( + config.rpc_endpoints, + RpcEndpoints::new([ + ( + "optimism", + RpcEndpoint::Url("https://example.com/".to_string()) + ), + ("mainnet", RpcEndpoint::Env("${RPC_MAINNET}".to_string())), + ( + "mainnet_2", + RpcEndpoint::Env( + "https://eth-mainnet.alchemyapi.io/v2/${API_KEY}".to_string() + ) + ), + ]), + ); + + Ok(()) + }); + } + + #[test] + fn test_solc_req() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "foundry.toml", + r#" + [profile.default] + solc_version = "0.8.12" + "#, + )?; + + let config = Config::load(); + assert_eq!( + config.solc, + Some(SolcReq::Version("0.8.12".parse().unwrap())) + ); + + jail.create_file( + "foundry.toml", + r#" + [profile.default] + solc = "0.8.12" + "#, + )?; + + let config = Config::load(); + assert_eq!( + config.solc, + Some(SolcReq::Version("0.8.12".parse().unwrap())) + ); + + jail.create_file( + "foundry.toml", + r#" + [profile.default] + solc = "path/to/local/solc" + "#, + )?; + + let config = Config::load(); + assert_eq!( + config.solc, + Some(SolcReq::Local("path/to/local/solc".into())) + ); + + jail.set_env("FOUNDRY_SOLC_VERSION", "0.6.6"); + let config = Config::load(); + assert_eq!( + config.solc, + Some(SolcReq::Version("0.6.6".parse().unwrap())) + ); + Ok(()) + }); + } + + #[test] + fn test_toml_casing_file() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "foundry.toml", + r#" + [profile.default] + src = "some-source" + out = "some-out" + cache = true + eth-rpc-url = "https://example.com/" + evm-version = "berlin" + auto-detect-solc = false + "#, + )?; + + let config = Config::load(); + assert_eq!( + config, + Config { + src: "some-source".into(), + out: "some-out".into(), + cache: true, + eth_rpc_url: Some("https://example.com/".to_string()), + auto_detect_solc: false, + evm_version: EvmVersion::Berlin, + ..Config::default() + } + ); + + Ok(()) + }); + } + + #[test] + fn test_output_selection() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "foundry.toml", + r#" + [profile.default] + extra_output = ["metadata", "ir-optimized"] + extra_output_files = ["metadata"] + "#, + )?; + + let config = Config::load(); + + assert_eq!( + config.extra_output, + vec![ + ContractOutputSelection::Metadata, + ContractOutputSelection::IrOptimized + ] + ); + assert_eq!( + config.extra_output_files, + vec![ContractOutputSelection::Metadata] + ); + + Ok(()) + }); + } + + #[test] + fn test_precedence() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "foundry.toml", + r#" + [profile.default] + src = "mysrc" + out = "myout" + verbosity = 3 + "#, + )?; + + let config = Config::load(); + assert_eq!( + config, + Config { + src: "mysrc".into(), + out: "myout".into(), + verbosity: 3, + ..Config::default() + } + ); + + jail.set_env("FOUNDRY_SRC", r#"other-src"#); + let config = Config::load(); + assert_eq!( + config, + Config { + src: "other-src".into(), + out: "myout".into(), + verbosity: 3, + ..Config::default() + } + ); + + jail.set_env("FOUNDRY_PROFILE", "foo"); + let val: Result = Config::figment().extract_inner("profile"); + assert!(val.is_err()); + + Ok(()) + }); + } + + #[test] + fn test_extract_basic() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "foundry.toml", + r#" + [profile.default] + src = "mysrc" + out = "myout" + verbosity = 3 + evm_version = 'berlin' + + [profile.other] + src = "other-src" + "#, + )?; + let loaded = Config::load(); + assert_eq!(loaded.evm_version, EvmVersion::Berlin); + let base = loaded.into_basic(); + let default = Config::default(); + assert_eq!( + base, + BasicConfig { + profile: Config::DEFAULT_PROFILE, + src: "mysrc".into(), + out: "myout".into(), + libs: default.libs.clone(), + remappings: default.remappings.clone(), + } + ); + jail.set_env("FOUNDRY_PROFILE", r#"other"#); + let base = Config::figment().extract::().unwrap(); + assert_eq!( + base, + BasicConfig { + profile: Config::DEFAULT_PROFILE, + src: "other-src".into(), + out: "myout".into(), + libs: default.libs.clone(), + remappings: default.remappings, + } + ); + Ok(()) + }); + } + + #[test] + #[should_panic] + fn test_parse_invalid_fuzz_weight() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "foundry.toml", + r#" + [fuzz] + dictionary_weight = 101 + "#, + )?; + let _config = Config::load(); + Ok(()) + }); + } + + #[test] + fn test_fallback_provider() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "foundry.toml", + r#" + [fuzz] + runs = 1 + include_storage = false + dictionary_weight = 99 + + [invariant] + runs = 420 + + [profile.ci.fuzz] + dictionary_weight = 5 + + [profile.ci.invariant] + runs = 400 + "#, + )?; + + let invariant_default = InvariantConfig::default(); + let config = Config::load(); + + assert_ne!(config.invariant.runs, config.fuzz.runs); + assert_eq!(config.invariant.runs, 420); + + assert_ne!( + config.fuzz.dictionary.include_storage, + invariant_default.dictionary.include_storage + ); + assert_eq!( + config.invariant.dictionary.include_storage, + config.fuzz.dictionary.include_storage + ); + + assert_ne!( + config.fuzz.dictionary.dictionary_weight, + invariant_default.dictionary.dictionary_weight + ); + assert_eq!( + config.invariant.dictionary.dictionary_weight, + config.fuzz.dictionary.dictionary_weight + ); + + jail.set_env("FOUNDRY_PROFILE", "ci"); + let ci_config = Config::load(); + assert_eq!(ci_config.fuzz.runs, 1); + assert_eq!(ci_config.invariant.runs, 400); + assert_eq!(ci_config.fuzz.dictionary.dictionary_weight, 5); + assert_eq!( + ci_config.invariant.dictionary.dictionary_weight, + config.fuzz.dictionary.dictionary_weight + ); + + Ok(()) + }) + } + + #[test] + fn test_standalone_profile_sections() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "foundry.toml", + r#" + [fuzz] + runs = 100 + + [invariant] + runs = 120 + + [profile.ci.fuzz] + runs = 420 + + [profile.ci.invariant] + runs = 500 + "#, + )?; + + let config = Config::load(); + assert_eq!(config.fuzz.runs, 100); + assert_eq!(config.invariant.runs, 120); + + jail.set_env("FOUNDRY_PROFILE", "ci"); + let config = Config::load(); + assert_eq!(config.fuzz.runs, 420); + assert_eq!(config.invariant.runs, 500); + + Ok(()) + }); + } + + #[test] + fn can_handle_deviating_dapp_aliases() { + figment::Jail::expect_with(|jail| { + let addr = Address::random(); + jail.set_env("DAPP_TEST_NUMBER", 1337); + jail.set_env("DAPP_TEST_ADDRESS", format!("{addr:?}")); + jail.set_env("DAPP_TEST_FUZZ_RUNS", 420); + jail.set_env("DAPP_TEST_DEPTH", 20); + jail.set_env("DAPP_FORK_BLOCK", 100); + jail.set_env("DAPP_BUILD_OPTIMIZE_RUNS", 999); + jail.set_env("DAPP_BUILD_OPTIMIZE", 0); + + let config = Config::load(); + + assert_eq!(config.block_number, 1337); + assert_eq!(config.sender, addr); + assert_eq!(config.fuzz.runs, 420); + assert_eq!(config.invariant.depth, 20); + assert_eq!(config.fork_block_number, Some(100)); + assert_eq!(config.optimizer_runs, 999); + assert!(!config.optimizer); + + Ok(()) + }); + } + + #[test] + fn can_parse_libraries() { + figment::Jail::expect_with(|jail| { + jail.set_env( + "DAPP_LIBRARIES", + "[src/DssSpell.sol:DssExecLib:0x8De6DDbCd5053d32292AAA0D2105A32d108484a6]", + ); + let config = Config::load(); + assert_eq!( + config.libraries, + vec![ + "src/DssSpell.sol:DssExecLib:0x8De6DDbCd5053d32292AAA0D2105A32d108484a6" + .to_string() + ] + ); + + jail.set_env( + "DAPP_LIBRARIES", + "src/DssSpell.sol:DssExecLib:0x8De6DDbCd5053d32292AAA0D2105A32d108484a6", + ); + let config = Config::load(); + assert_eq!( + config.libraries, + vec![ + "src/DssSpell.sol:DssExecLib:0x8De6DDbCd5053d32292AAA0D2105A32d108484a6" + .to_string(), + ] + ); + + jail.set_env( + "DAPP_LIBRARIES", + "src/DssSpell.sol:DssExecLib:0x8De6DDbCd5053d32292AAA0D2105A32d108484a6,src/DssSpell.sol:DssExecLib:0x8De6DDbCd5053d32292AAA0D2105A32d108484a6", + ); + let config = Config::load(); + assert_eq!( + config.libraries, + vec![ + "src/DssSpell.sol:DssExecLib:0x8De6DDbCd5053d32292AAA0D2105A32d108484a6" + .to_string(), + "src/DssSpell.sol:DssExecLib:0x8De6DDbCd5053d32292AAA0D2105A32d108484a6" + .to_string() + ] + ); + + Ok(()) + }); + } + + #[test] + fn test_parse_many_libraries() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "foundry.toml", + r#" + [profile.default] + libraries= [ + './src/SizeAuctionDiscount.sol:Chainlink:0xffedba5e171c4f15abaaabc86e8bd01f9b54dae5', + './src/SizeAuction.sol:ChainlinkTWAP:0xffedba5e171c4f15abaaabc86e8bd01f9b54dae5', + './src/SizeAuction.sol:Math:0x902f6cf364b8d9470d5793a9b2b2e86bddd21e0c', + './src/test/ChainlinkTWAP.t.sol:ChainlinkTWAP:0xffedba5e171c4f15abaaabc86e8bd01f9b54dae5', + './src/SizeAuctionDiscount.sol:Math:0x902f6cf364b8d9470d5793a9b2b2e86bddd21e0c', + ] + "#, + )?; + let config = Config::load(); + + let libs = config.parsed_libraries().unwrap().libs; + + pretty_assertions::assert_eq!( + libs, + BTreeMap::from([ + ( + PathBuf::from("./src/SizeAuctionDiscount.sol"), + BTreeMap::from([ + ( + "Chainlink".to_string(), + "0xffedba5e171c4f15abaaabc86e8bd01f9b54dae5".to_string() + ), + ( + "Math".to_string(), + "0x902f6cf364b8d9470d5793a9b2b2e86bddd21e0c".to_string() + ) + ]) + ), + ( + PathBuf::from("./src/SizeAuction.sol"), + BTreeMap::from([ + ( + "ChainlinkTWAP".to_string(), + "0xffedba5e171c4f15abaaabc86e8bd01f9b54dae5".to_string() + ), + ( + "Math".to_string(), + "0x902f6cf364b8d9470d5793a9b2b2e86bddd21e0c".to_string() + ) + ]) + ), + ( + PathBuf::from("./src/test/ChainlinkTWAP.t.sol"), + BTreeMap::from([( + "ChainlinkTWAP".to_string(), + "0xffedba5e171c4f15abaaabc86e8bd01f9b54dae5".to_string() + )]) + ), + ]) + ); + + Ok(()) + }); + } + + #[test] + fn config_roundtrip() { + figment::Jail::expect_with(|jail| { + let default = Config::default(); + let basic = default.clone().into_basic(); + jail.create_file("foundry.toml", &basic.to_string_pretty().unwrap())?; + + let mut other = Config::load(); + clear_warning(&mut other); + assert_eq!(default, other); + + let other = other.into_basic(); + assert_eq!(basic, other); + + jail.create_file("foundry.toml", &default.to_string_pretty().unwrap())?; + let mut other = Config::load(); + clear_warning(&mut other); + assert_eq!(default, other); + + Ok(()) + }); + } + + #[test] + fn test_fs_permissions() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "foundry.toml", + r#" + [profile.default] + fs_permissions = [{ access = "read-write", path = "./"}] + "#, + )?; + let loaded = Config::load(); + + assert_eq!( + loaded.fs_permissions, + FsPermissions::new(vec![PathPermission::read_write("./")]) + ); + + jail.create_file( + "foundry.toml", + r#" + [profile.default] + fs_permissions = [{ access = "none", path = "./"}] + "#, + )?; + let loaded = Config::load(); + assert_eq!( + loaded.fs_permissions, + FsPermissions::new(vec![PathPermission::none("./")]) + ); + + Ok(()) + }); + } + + #[test] + fn test_optimizer_settings_basic() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "foundry.toml", + r#" + [profile.default] + optimizer = true + + [profile.default.optimizer_details] + yul = false + + [profile.default.optimizer_details.yulDetails] + stackAllocation = true + "#, + )?; + let mut loaded = Config::load(); + clear_warning(&mut loaded); + assert_eq!( + loaded.optimizer_details, + Some(OptimizerDetails { + yul: Some(false), + yul_details: Some(YulDetails { + stack_allocation: Some(true), + ..Default::default() + }), + ..Default::default() + }) + ); + + let s = loaded.to_string_pretty().unwrap(); + jail.create_file("foundry.toml", &s)?; + + let mut reloaded = Config::load(); + clear_warning(&mut reloaded); + assert_eq!(loaded, reloaded); + + Ok(()) + }); + } + + #[test] + fn test_model_checker_settings_basic() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "foundry.toml", + r#" + [profile.default] + + [profile.default.model_checker] + contracts = { 'a.sol' = [ 'A1', 'A2' ], 'b.sol' = [ 'B1', 'B2' ] } + engine = 'chc' + targets = [ 'assert', 'outOfBounds' ] + timeout = 10000 + "#, + )?; + let mut loaded = Config::load(); + clear_warning(&mut loaded); + assert_eq!( + loaded.model_checker, + Some(ModelCheckerSettings { + contracts: BTreeMap::from([ + ( + "a.sol".to_string(), + vec!["A1".to_string(), "A2".to_string()] + ), + ( + "b.sol".to_string(), + vec!["B1".to_string(), "B2".to_string()] + ), + ]), + engine: Some(ModelCheckerEngine::CHC), + targets: Some(vec![ + ModelCheckerTarget::Assert, + ModelCheckerTarget::OutOfBounds + ]), + timeout: Some(10000), + invariants: None, + show_unproved: None, + div_mod_with_slacks: None, + solvers: None, + show_unsupported: None, + show_proved_safe: None, + }) + ); + + let s = loaded.to_string_pretty().unwrap(); + jail.create_file("foundry.toml", &s)?; + + let mut reloaded = Config::load(); + clear_warning(&mut reloaded); + assert_eq!(loaded, reloaded); + + Ok(()) + }); + } + + #[test] + fn test_model_checker_settings_relative_paths() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "foundry.toml", + r#" + [profile.default] + + [profile.default.model_checker] + contracts = { 'a.sol' = [ 'A1', 'A2' ], 'b.sol' = [ 'B1', 'B2' ] } + engine = 'chc' + targets = [ 'assert', 'outOfBounds' ] + timeout = 10000 + "#, + )?; + let loaded = Config::load().sanitized(); + + // NOTE(onbjerg): We have to canonicalize the path here using dunce because figment will + // canonicalize the jail path using the standard library. The standard library *always* + // transforms Windows paths to some weird extended format, which none of our code base + // does. + let dir = ethers_solc::utils::canonicalize(jail.directory()) + .expect("Could not canonicalize jail path"); + assert_eq!( + loaded.model_checker, + Some(ModelCheckerSettings { + contracts: BTreeMap::from([ + ( + format!("{}", dir.join("a.sol").display()), + vec!["A1".to_string(), "A2".to_string()] + ), + ( + format!("{}", dir.join("b.sol").display()), + vec!["B1".to_string(), "B2".to_string()] + ), + ]), + engine: Some(ModelCheckerEngine::CHC), + targets: Some(vec![ + ModelCheckerTarget::Assert, + ModelCheckerTarget::OutOfBounds + ]), + timeout: Some(10000), + invariants: None, + show_unproved: None, + div_mod_with_slacks: None, + solvers: None, + show_unsupported: None, + show_proved_safe: None, + }) + ); + + Ok(()) + }); + } + + #[test] + fn test_fmt_config() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "foundry.toml", + r#" + [fmt] + line_length = 100 + tab_width = 2 + bracket_spacing = true + "#, + )?; + let loaded = Config::load().sanitized(); + assert_eq!( + loaded.fmt, + FormatterConfig { + line_length: 100, + tab_width: 2, + bracket_spacing: true, + ..Default::default() + } + ); + + Ok(()) + }); + } + + #[test] + fn test_invariant_config() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "foundry.toml", + r#" + [invariant] + runs = 512 + depth = 10 + "#, + )?; + + let loaded = Config::load().sanitized(); + assert_eq!( + loaded.invariant, + InvariantConfig { + runs: 512, + depth: 10, + ..Default::default() + } + ); + + Ok(()) + }); + } + + #[test] + fn test_standalone_sections_env() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "foundry.toml", + r#" + [fuzz] + runs = 100 + + [invariant] + depth = 1 + "#, + )?; + + jail.set_env("FOUNDRY_FMT_LINE_LENGTH", "95"); + jail.set_env("FOUNDRY_FUZZ_DICTIONARY_WEIGHT", "99"); + jail.set_env("FOUNDRY_INVARIANT_DEPTH", "5"); + + let config = Config::load(); + assert_eq!(config.fmt.line_length, 95); + assert_eq!(config.fuzz.dictionary.dictionary_weight, 99); + assert_eq!(config.invariant.depth, 5); + + Ok(()) + }); + } + + #[test] + fn test_parse_with_profile() { + let foundry_str = r#" + [profile.default] + src = 'src' + out = 'out' + libs = ['lib'] + + # See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options + "#; + assert_eq!( + parse_with_profile::(foundry_str) + .unwrap() + .unwrap(), + ( + Config::DEFAULT_PROFILE, + BasicConfig { + profile: Config::DEFAULT_PROFILE, + src: "src".into(), + out: "out".into(), + libs: vec!["lib".into()], + remappings: vec![] + } + ) + ); + } + + #[test] + fn test_implicit_profile_loads() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "foundry.toml", + r#" + [default] + src = 'my-src' + out = 'my-out' + "#, + )?; + let loaded = Config::load().sanitized(); + assert_eq!(loaded.src.file_name().unwrap(), "my-src"); + assert_eq!(loaded.out.file_name().unwrap(), "my-out"); + assert_eq!( + loaded.__warnings, + vec![Warning::UnknownSection { + unknown_section: Profile::new("default"), + source: Some("foundry.toml".into()) + }] + ); + + Ok(()) + }); + } + + // a test to print the config, mainly used to update the example config in the README + #[test] + #[ignore] + fn print_config() { + let config = Config { + optimizer_details: Some(OptimizerDetails { + peephole: None, + inliner: None, + jumpdest_remover: None, + order_literals: None, + deduplicate: None, + cse: None, + constant_optimizer: Some(true), + yul: Some(true), + yul_details: Some(YulDetails { + stack_allocation: None, + optimizer_steps: Some("dhfoDgvulfnTUtnIf".to_string()), + }), + }), + ..Default::default() + }; + println!("{}", config.to_string_pretty().unwrap()); + } + + #[test] + fn can_use_impl_figment_macro() { + #[derive(Default, Serialize)] + struct MyArgs { + #[serde(skip_serializing_if = "Option::is_none")] + root: Option, + } + impl_figment_convert!(MyArgs); + + impl Provider for MyArgs { + fn metadata(&self) -> Metadata { + Metadata::default() + } + + fn data(&self) -> Result, Error> { + let value = Value::serialize(self)?; + let error = InvalidType(value.to_actual(), "map".into()); + let dict = value.into_dict().ok_or(error)?; + Ok(Map::from([(Config::selected_profile(), dict)])) + } + } + + let _figment: Figment = From::from(&MyArgs::default()); + let _config: Config = From::from(&MyArgs::default()); + + #[derive(Default)] + struct Outer { + start: MyArgs, + other: MyArgs, + another: MyArgs, + } + impl_figment_convert!(Outer, start, other, another); + + let _figment: Figment = From::from(&Outer::default()); + let _config: Config = From::from(&Outer::default()); + } + + #[test] + fn list_cached_blocks() -> eyre::Result<()> { + fn fake_block_cache(chain_path: &Path, block_number: &str, size_bytes: usize) { + let block_path = chain_path.join(block_number); + fs::create_dir(block_path.as_path()).unwrap(); + let file_path = block_path.join("storage.json"); + let mut file = File::create(file_path).unwrap(); + writeln!( + file, + "{}", + vec![' '; size_bytes - 1].iter().collect::() + ) + .unwrap(); + } + + let chain_dir = tempdir()?; + + fake_block_cache(chain_dir.path(), "1", 100); + fake_block_cache(chain_dir.path(), "2", 500); + // Pollution file that should not show up in the cached block + let mut pol_file = File::create(chain_dir.path().join("pol.txt")).unwrap(); + writeln!(pol_file, "{}", [' '; 10].iter().collect::()).unwrap(); + + let result = Config::get_cached_blocks(chain_dir.path())?; + + assert_eq!(result.len(), 2); + let block1 = &result.iter().find(|x| x.0 == "1").unwrap(); + let block2 = &result.iter().find(|x| x.0 == "2").unwrap(); + assert_eq!(block1.0, "1"); + assert_eq!(block1.1, 100); + assert_eq!(block2.0, "2"); + assert_eq!(block2.1, 500); + + chain_dir.close()?; + Ok(()) + } + + #[test] + fn list_etherscan_cache() -> eyre::Result<()> { + fn fake_etherscan_cache(chain_path: &Path, address: &str, size_bytes: usize) { + let metadata_path = chain_path.join("sources"); + let abi_path = chain_path.join("abi"); + let _ = fs::create_dir(metadata_path.as_path()); + let _ = fs::create_dir(abi_path.as_path()); + + let metadata_file_path = metadata_path.join(address); + let mut metadata_file = File::create(metadata_file_path).unwrap(); + writeln!( + metadata_file, + "{}", + vec![' '; size_bytes / 2 - 1].iter().collect::() + ) + .unwrap(); + + let abi_file_path = abi_path.join(address); + let mut abi_file = File::create(abi_file_path).unwrap(); + writeln!( + abi_file, + "{}", + vec![' '; size_bytes / 2 - 1].iter().collect::() + ) + .unwrap(); + } + + let chain_dir = tempdir()?; + + fake_etherscan_cache(chain_dir.path(), "1", 100); + fake_etherscan_cache(chain_dir.path(), "2", 500); + + let result = Config::get_cached_block_explorer_data(chain_dir.path())?; + + assert_eq!(result, 600); + + chain_dir.close()?; + Ok(()) + } + + #[test] + fn test_parse_error_codes() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "foundry.toml", + r#" + [default] + ignored_error_codes = ["license", "unreachable", 1337] + "#, + )?; + + let config = Config::load(); + assert_eq!( + config.ignored_error_codes, + vec![ + SolidityErrorCode::SpdxLicenseNotProvided, + SolidityErrorCode::Unreachable, + SolidityErrorCode::Other(1337) + ] + ); + + Ok(()) + }); + } + + #[test] + fn test_parse_optimizer_settings() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "foundry.toml", + r#" + [default] + [profile.default.optimizer_details] + "#, + )?; + + let config = Config::load(); + assert_eq!(config.optimizer_details, Some(OptimizerDetails::default())); + + Ok(()) + }); + } +} diff --git a/foundry-config/src/macros.rs b/foundry-config/src/macros.rs new file mode 100644 index 000000000..21291a8fb --- /dev/null +++ b/foundry-config/src/macros.rs @@ -0,0 +1,239 @@ +/// A macro to implement converters from a type to [`Config`] and [`figment::Figment`] +/// +/// This can be used to remove some boilerplate code that's necessary to add additional layer(s) to +/// the [`Config`]'s default `Figment`. +/// +/// `impl_figment` takes the default `Config` and merges additional `Provider`, therefore the +/// targeted type, requires an implementation of `figment::Profile`. +/// +/// # Example +/// +/// Use `impl_figment` on a type with a `root: Option` field, which will be used for +/// [`Config::figment_with_root()`] +/// +/// ```rust +/// use std::path::PathBuf; +/// use serde::Serialize; +/// use foundry_config::{Config, impl_figment_convert}; +/// use foundry_config::figment::*; +/// use foundry_config::figment::error::Kind::InvalidType; +/// use foundry_config::figment::value::*; +/// #[derive(Default, Serialize)] +/// struct MyArgs { +/// #[serde(skip_serializing_if = "Option::is_none")] +/// root: Option, +/// } +/// impl_figment_convert!(MyArgs); +/// +/// impl Provider for MyArgs { +/// fn metadata(&self) -> Metadata { +/// Metadata::default() +/// } +/// +/// fn data(&self) -> Result, Error> { +/// let value = Value::serialize(self)?; +/// let error = InvalidType(value.to_actual(), "map".into()); +/// let mut dict = value.into_dict().ok_or(error)?; +/// Ok(Map::from([(Config::selected_profile(), dict)])) +/// } +/// } +/// +/// let figment: Figment = From::from(&MyArgs::default()); +/// let config: Config = From::from(&MyArgs::default()); +/// +/// // Use `impl_figment` on a type that has several nested `Provider` as fields but is _not_ a `Provider` itself +/// +/// #[derive(Default)] +/// struct Outer { +/// start: MyArgs, +/// second: MyArgs, +/// third: MyArgs, +/// } +/// impl_figment_convert!(Outer, start, second, third); +/// +/// let figment: Figment = From::from(&Outer::default()); +/// let config: Config = From::from(&Outer::default()); +/// ``` +#[macro_export] +macro_rules! impl_figment_convert { + ($name:ty) => { + impl<'a> From<&'a $name> for $crate::figment::Figment { + fn from(args: &'a $name) -> Self { + if let Some(root) = args.root.clone() { + $crate::Config::figment_with_root(root) + } else { + $crate::Config::figment_with_root($crate::find_project_root_path(None).unwrap()) + } + .merge(args) + } + } + + impl<'a> From<&'a $name> for $crate::Config { + fn from(args: &'a $name) -> Self { + let figment: $crate::figment::Figment = args.into(); + $crate::Config::from_provider(figment).sanitized() + } + } + }; + ($name:ty, $start:ident $(, $more:ident)*) => { + impl<'a> From<&'a $name> for $crate::figment::Figment { + fn from(args: &'a $name) -> Self { + let mut figment: $crate::figment::Figment = From::from(&args.$start); + $ ( + figment = figment.merge(&args.$more); + )* + figment + } + } + + impl<'a> From<&'a $name> for $crate::Config { + fn from(args: &'a $name) -> Self { + let figment: $crate::figment::Figment = args.into(); + $crate::Config::from_provider(figment).sanitized() + } + } + }; + ($name:ty, self, $start:ident $(, $more:ident)*) => { + impl<'a> From<&'a $name> for $crate::figment::Figment { + fn from(args: &'a $name) -> Self { + let mut figment: $crate::figment::Figment = From::from(&args.$start); + $ ( + figment = figment.merge(&args.$more); + )* + figment = figment.merge(args); + figment + } + } + + impl<'a> From<&'a $name> for $crate::Config { + fn from(args: &'a $name) -> Self { + let figment: $crate::figment::Figment = args.into(); + $crate::Config::from_provider(figment).sanitized() + } + } + }; +} + +/// Same as `impl_figment_convert` but also merges the type itself into the figment +/// +/// # Example +/// +/// Merge several nested `Provider` together with the type itself +/// +/// ```rust +/// use std::path::PathBuf; +/// use foundry_config::{Config, merge_impl_figment_convert, impl_figment_convert}; +/// use foundry_config::figment::*; +/// use foundry_config::figment::value::*; +/// +/// #[derive(Default)] +/// struct MyArgs { +/// root: Option, +/// } +/// +/// impl Provider for MyArgs { +/// fn metadata(&self) -> Metadata { +/// Metadata::default() +/// } +/// +/// fn data(&self) -> Result, Error> { +/// todo!() +/// } +/// } +/// +/// impl_figment_convert!(MyArgs); +/// +/// #[derive(Default)] +/// struct OuterArgs { +/// value: u64, +/// inner: MyArgs +/// } +/// +/// impl Provider for OuterArgs { +/// fn metadata(&self) -> Metadata { +/// Metadata::default() +/// } +/// +/// fn data(&self) -> Result, Error> { +/// todo!() +/// } +/// } +/// +/// merge_impl_figment_convert!(OuterArgs, inner); +/// ``` +#[macro_export] +macro_rules! merge_impl_figment_convert { + ($name:ty, $start:ident $(, $more:ident)*) => { + impl<'a> From<&'a $name> for $crate::figment::Figment { + fn from(args: &'a $name) -> Self { + let mut figment: $crate::figment::Figment = From::from(&args.$start); + $ ( + figment = figment.merge(&args.$more); + )* + figment = figment.merge(args); + figment + } + } + + impl<'a> From<&'a $name> for $crate::Config { + fn from(args: &'a $name) -> Self { + let figment: $crate::figment::Figment = args.into(); + $crate::Config::from_provider(figment).sanitized() + } + } + }; +} + +/// A macro to implement converters from a type to [`Config`] and [`figment::Figment`] +#[macro_export] +macro_rules! impl_figment_convert_cast { + ($name:ty) => { + impl<'a> From<&'a $name> for $crate::figment::Figment { + fn from(args: &'a $name) -> Self { + $crate::Config::figment_with_root($crate::find_project_root_path(None).unwrap()) + .merge(args) + } + } + + impl<'a> From<&'a $name> for $crate::Config { + fn from(args: &'a $name) -> Self { + let figment: $crate::figment::Figment = args.into(); + $crate::Config::from_provider(figment).sanitized() + } + } + }; +} + +/// Same as `impl_figment_convert` but also implies `Provider` for the given `Serialize` type for +/// convenience. The `Provider` only provides the "root" value for the current profile +#[macro_export] +macro_rules! impl_figment_convert_basic { + ($name:ty) => { + $crate::impl_figment_convert!($name); + + impl $crate::figment::Provider for $name { + fn metadata(&self) -> $crate::figment::Metadata { + $crate::figment::Metadata::named(stringify!($name)) + } + + fn data( + &self, + ) -> Result< + $crate::figment::value::Map<$crate::figment::Profile, $crate::figment::value::Dict>, + $crate::figment::Error, + > { + let mut dict = $crate::figment::value::Dict::new(); + if let Some(root) = self.root.as_ref() { + dict.insert( + "root".to_string(), + $crate::figment::value::Value::serialize(root)?, + ); + } + Ok($crate::figment::value::Map::from([( + $crate::Config::selected_profile(), + dict, + )])) + } + } + }; +} diff --git a/foundry-config/src/providers/mod.rs b/foundry-config/src/providers/mod.rs new file mode 100644 index 000000000..b18927512 --- /dev/null +++ b/foundry-config/src/providers/mod.rs @@ -0,0 +1,158 @@ +use crate::{Config, Warning, DEPRECATIONS}; +use figment::{ + value::{Dict, Map, Value}, + Error, Figment, Metadata, Profile, Provider, +}; + +/// Remappings provider +pub mod remappings; + +/// Generate warnings for unknown sections and deprecated keys +pub struct WarningsProvider

{ + provider: P, + profile: Profile, + old_warnings: Result, Error>, +} + +impl

WarningsProvider

{ + const WARNINGS_KEY: &'static str = "__warnings"; + + /// Creates a new warnings provider. + pub fn new( + provider: P, + profile: impl Into, + old_warnings: Result, Error>, + ) -> Self { + Self { + provider, + profile: profile.into(), + old_warnings, + } + } + + /// Creates a new figment warnings provider. + pub fn for_figment(provider: P, figment: &Figment) -> Self { + let old_warnings = { + let warnings_res = figment.extract_inner(Self::WARNINGS_KEY); + if warnings_res + .as_ref() + .err() + .map(|err| err.missing()) + .unwrap_or(false) + { + Ok(vec![]) + } else { + warnings_res + } + }; + Self::new(provider, figment.profile().clone(), old_warnings) + } +} + +impl WarningsProvider

{ + /// Collects all warnings. + pub fn collect_warnings(&self) -> Result, Error> { + let mut out = self.old_warnings.clone()?; + // add warning for unknown sections + out.extend( + self.provider + .data() + .unwrap_or_default() + .keys() + .filter(|k| { + k != &Config::PROFILE_SECTION + && !Config::STANDALONE_SECTIONS.iter().any(|s| s == k) + }) + .map(|unknown_section| { + let source = self.provider.metadata().source.map(|s| s.to_string()); + Warning::UnknownSection { + unknown_section: unknown_section.clone(), + source, + } + }), + ); + // add warning for deprecated keys + out.extend( + self.provider + .data() + .unwrap_or_default() + .iter() + .flat_map(|(profile, dict)| dict.keys().map(move |key| format!("{profile}.{key}"))) + .filter(|k| DEPRECATIONS.contains_key(k)) + .map(|deprecated_key| Warning::DeprecatedKey { + old: deprecated_key.clone(), + new: DEPRECATIONS.get(&deprecated_key).unwrap().to_string(), + }), + ); + Ok(out) + } +} + +impl Provider for WarningsProvider

{ + fn metadata(&self) -> Metadata { + if let Some(source) = self.provider.metadata().source { + Metadata::from("Warnings", source) + } else { + Metadata::named("Warnings") + } + } + fn data(&self) -> Result, Error> { + Ok(Map::from([( + self.profile.clone(), + Dict::from([( + Self::WARNINGS_KEY.to_string(), + Value::serialize(self.collect_warnings()?)?, + )]), + )])) + } + fn profile(&self) -> Option { + Some(self.profile.clone()) + } +} + +/// Extracts the profile from the `profile` key and sets unset values according to the fallback +/// provider +pub struct FallbackProfileProvider

{ + provider: P, + profile: Profile, + fallback: Profile, +} + +impl

FallbackProfileProvider

{ + /// Creates a new fallback profile provider. + pub fn new(provider: P, profile: impl Into, fallback: impl Into) -> Self { + FallbackProfileProvider { + provider, + profile: profile.into(), + fallback: fallback.into(), + } + } +} + +impl Provider for FallbackProfileProvider

{ + fn metadata(&self) -> Metadata { + self.provider.metadata() + } + + fn data(&self) -> Result, Error> { + if let Some(fallback) = self.provider.data()?.get(&self.fallback) { + let mut inner = self + .provider + .data()? + .remove(&self.profile) + .unwrap_or_default(); + for (k, v) in fallback.iter() { + if !inner.contains_key(k) { + inner.insert(k.to_owned(), v.clone()); + } + } + Ok(self.profile.collect(inner)) + } else { + self.provider.data() + } + } + + fn profile(&self) -> Option { + Some(self.profile.clone()) + } +} diff --git a/foundry-config/src/providers/remappings.rs b/foundry-config/src/providers/remappings.rs new file mode 100644 index 000000000..f5e0715c4 --- /dev/null +++ b/foundry-config/src/providers/remappings.rs @@ -0,0 +1,281 @@ +use crate::{foundry_toml_dirs, remappings_from_env_var, remappings_from_newline, Config}; +use ethers_solc::remappings::{RelativeRemapping, Remapping}; +use figment::{ + value::{Dict, Map}, + Error, Metadata, Profile, Provider, +}; +use std::{ + borrow::Cow, + collections::{btree_map::Entry, BTreeMap, HashSet}, + fs, + path::{Path, PathBuf}, +}; +use tracing::trace; + +/// Wrapper types over a `Vec` that only appends unique remappings. +#[derive(Debug, Clone, Default)] +pub struct Remappings { + /// Remappings. + remappings: Vec, +} + +impl Remappings { + /// Create a new `Remappings` wrapper with an empty vector. + pub fn new() -> Self { + Self { + remappings: Vec::new(), + } + } + + /// Create a new `Remappings` wrapper with a vector of remappings. + pub fn new_with_remappings(remappings: Vec) -> Self { + Self { remappings } + } + + /// Consumes the wrapper and returns the inner remappings vector. + pub fn into_inner(self) -> Vec { + let mut tmp = HashSet::new(); + let remappings = self + .remappings + .iter() + .filter(|r| tmp.insert(r.name.clone())) + .cloned() + .collect(); + remappings + } + + /// Push an element ot the remappings vector, but only if it's not already present. + pub fn push(&mut self, remapping: Remapping) { + if !self.remappings.iter().any(|existing| { + // What we're doing here is filtering for ambiguous paths. For example, if we have + // @prb/math/=node_modules/@prb/math/src/ as existing, and + // @prb/=node_modules/@prb/ as the one being checked, + // we want to keep the already existing one, which is the first one. This way we avoid + // having to deal with ambiguous paths which is unwanted when autodetecting remappings. + existing.name.starts_with(&remapping.name) && existing.context == remapping.context + }) { + self.remappings.push(remapping) + } + } + + /// Extend the remappings vector, leaving out the remappings that are already present. + pub fn extend(&mut self, remappings: Vec) { + for remapping in remappings { + self.push(remapping); + } + } +} + +/// A figment provider that checks if the remappings were previously set and if they're unset looks +/// up the fs via +/// - `DAPP_REMAPPINGS` || `FOUNDRY_REMAPPINGS` env var +/// - `/remappings.txt` file +/// - `Remapping::find_many`. +pub struct RemappingsProvider<'a> { + /// Whether to auto detect remappings from the `lib_paths` + pub auto_detect_remappings: bool, + /// The lib/dependency directories to scan for remappings + pub lib_paths: Cow<'a, Vec>, + /// the root path used to turn an absolute `Remapping`, as we're getting it from + /// `Remapping::find_many` into a relative one. + pub root: &'a PathBuf, + /// This contains either: + /// - previously set remappings + /// - a `MissingField` error, which means previous provider didn't set the "remappings" field + /// - other error, like formatting + pub remappings: Result, Error>, +} + +impl<'a> RemappingsProvider<'a> { + /// Find and parse remappings for the projects + /// + /// **Order** + /// + /// Remappings are built in this order (last item takes precedence) + /// - Autogenerated remappings + /// - toml remappings + /// - `remappings.txt` + /// - Environment variables + /// - CLI parameters + fn get_remappings(&self, remappings: Vec) -> Result, Error> { + trace!("get all remappings from {:?}", self.root); + /// prioritizes remappings that are closer: shorter `path` + /// - ("a", "1/2") over ("a", "1/2/3") + /// grouped by remapping context + fn insert_closest( + mappings: &mut BTreeMap, BTreeMap>, + context: Option, + key: String, + path: PathBuf, + ) { + let context_mappings = mappings.entry(context).or_default(); + match context_mappings.entry(key) { + Entry::Occupied(mut e) => { + if e.get().components().count() > path.components().count() { + e.insert(path); + } + } + Entry::Vacant(e) => { + e.insert(path); + } + } + } + + // Let's first just extend the remappings with the ones that were passed in, + // without any filtering. + let mut user_remappings = Vec::new(); + + // check env vars + if let Some(env_remappings) = remappings_from_env_var("DAPP_REMAPPINGS") + .or_else(|| remappings_from_env_var("FOUNDRY_REMAPPINGS")) + { + user_remappings + .extend(env_remappings.map_err::(|err| err.to_string().into())?); + } + + // check remappings.txt file + let remappings_file = self.root.join("remappings.txt"); + if remappings_file.is_file() { + let content = fs::read_to_string(remappings_file).map_err(|err| err.to_string())?; + let remappings_from_file: Result, _> = + remappings_from_newline(&content).collect(); + user_remappings + .extend(remappings_from_file.map_err::(|err| err.to_string().into())?); + } + + user_remappings.extend(remappings); + // Let's now use the wrapper to conditionally extend the remappings with the autodetected + // ones. We want to avoid duplicates, and the wrapper will handle this for us. + let mut all_remappings = Remappings::new_with_remappings(user_remappings); + + // scan all library dirs and autodetect remappings + // todo: if a lib specifies contexts for remappings manually, we need to figure out how to + // resolve that + if self.auto_detect_remappings { + let mut lib_remappings = BTreeMap::new(); + // find all remappings of from libs that use a foundry.toml + for r in self.lib_foundry_toml_remappings() { + insert_closest(&mut lib_remappings, r.context, r.name, r.path.into()); + } + // use auto detection for all libs + for r in self + .lib_paths + .iter() + .map(|lib| self.root.join(lib)) + .inspect(|lib| { + trace!("find all remappings in lib path: {:?}", lib); + }) + .flat_map(Remapping::find_many) + { + // this is an additional safety check for weird auto-detected remappings + if ["lib/", "src/", "contracts/"].contains(&r.name.as_str()) { + trace!(target: "forge", "- skipping the remapping"); + continue; + } + insert_closest(&mut lib_remappings, r.context, r.name, r.path.into()); + } + + all_remappings.extend( + lib_remappings + .into_iter() + .flat_map(|(context, remappings)| { + remappings.into_iter().map(move |(name, path)| Remapping { + context: context.clone(), + name, + path: path.to_string_lossy().into(), + }) + }) + .collect(), + ); + } + + Ok(all_remappings.into_inner()) + } + + /// Returns all remappings declared in foundry.toml files of libraries + fn lib_foundry_toml_remappings(&self) -> impl Iterator + '_ { + self.lib_paths + .iter() + .map(|p| self.root.join(p)) + .flat_map(foundry_toml_dirs) + .inspect(|lib| { + trace!("find all remappings of nested foundry.toml lib: {:?}", lib); + }) + .flat_map(|lib: PathBuf| { + // load config, of the nested lib if it exists + let config = Config::load_with_root(&lib).sanitized(); + + // if the configured _src_ directory is set to something that + // [Remapping::find_many()] doesn't classify as a src directory (src, contracts, + // lib), then we need to manually add a remapping here + let mut src_remapping = None; + if ![Path::new("src"), Path::new("contracts"), Path::new("lib")] + .contains(&config.src.as_path()) + { + if let Some(name) = lib.file_name().and_then(|s| s.to_str()) { + let mut r = Remapping { + context: None, + name: format!("{name}/"), + path: format!("{}", lib.join(&config.src).display()), + }; + if !r.path.ends_with('/') { + r.path.push('/') + } + src_remapping = Some(r); + } + } + + // Eventually, we could set context for remappings at this location, + // taking into account the OS platform. We'll need to be able to handle nested + // contexts depending on dependencies for this to work. + // For now, we just leave the default context (none). + let mut remappings = config + .remappings + .into_iter() + .map(Remapping::from) + .collect::>(); + + if let Some(r) = src_remapping { + remappings.push(r); + } + remappings + }) + } +} + +impl<'a> Provider for RemappingsProvider<'a> { + fn metadata(&self) -> Metadata { + Metadata::named("Remapping Provider") + } + + fn data(&self) -> Result, Error> { + let remappings = match &self.remappings { + Ok(remappings) => self.get_remappings(remappings.clone()), + Err(err) => { + if let figment::error::Kind::MissingField(_) = err.kind { + self.get_remappings(vec![]) + } else { + return Err(err.clone()); + } + } + }?; + + // turn the absolute remapping into a relative one by stripping the `root` + let remappings = remappings + .into_iter() + .map(|r| RelativeRemapping::new(r, self.root).to_string()) + .collect::>(); + + Ok(Map::from([( + Config::selected_profile(), + Dict::from([( + "remappings".to_string(), + figment::value::Value::from(remappings), + )]), + )])) + } + + fn profile(&self) -> Option { + Some(Config::selected_profile()) + } +} diff --git a/foundry-config/src/resolve.rs b/foundry-config/src/resolve.rs new file mode 100644 index 000000000..b062558b3 --- /dev/null +++ b/foundry-config/src/resolve.rs @@ -0,0 +1,84 @@ +//! Helper for resolving env vars + +use once_cell::sync::Lazy; +use regex::Regex; +use std::{env, env::VarError, fmt}; + +/// A regex that matches `${val}` placeholders +pub static RE_PLACEHOLDER: Lazy = + Lazy::new(|| Regex::new(r"(?m)(?P\$\{\s*(?P.*?)\s*})").unwrap()); + +/// Error when we failed to resolve an env var +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct UnresolvedEnvVarError { + /// The unresolved input string + pub unresolved: String, + /// Var that couldn't be resolved + pub var: String, + /// the `env::var` error + pub source: VarError, +} + +// === impl UnresolvedEnvVarError === + +impl UnresolvedEnvVarError { + /// Tries to resolve a value + pub fn try_resolve(&self) -> Result { + interpolate(&self.unresolved) + } +} + +impl fmt::Display for UnresolvedEnvVarError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "Failed to resolve env var `{}` in `{}`: {}", + self.var, self.unresolved, self.source + ) + } +} + +impl std::error::Error for UnresolvedEnvVarError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + Some(&self.source) + } +} + +/// Replaces all Env var placeholders in the input string with the values they hold +pub fn interpolate(input: &str) -> Result { + let mut res = input.to_string(); + + // loop over all placeholders in the input and replace them one by one + for caps in RE_PLACEHOLDER.captures_iter(input) { + let var = &caps["inner"]; + let value = env::var(var).map_err(|source| UnresolvedEnvVarError { + unresolved: input.to_string(), + var: var.to_string(), + source, + })?; + + res = res.replacen(&caps["outer"], &value, 1); + } + Ok(res) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn can_find_placeholder() { + let val = "https://eth-mainnet.alchemyapi.io/v2/346273846238426342"; + assert!(!RE_PLACEHOLDER.is_match(val)); + + let val = "${RPC_ENV}"; + assert!(RE_PLACEHOLDER.is_match(val)); + + let val = "https://eth-mainnet.alchemyapi.io/v2/${API_KEY}"; + assert!(RE_PLACEHOLDER.is_match(val)); + + let cap = RE_PLACEHOLDER.captures(val).unwrap(); + assert_eq!(cap.name("outer").unwrap().as_str(), "${API_KEY}"); + assert_eq!(cap.name("inner").unwrap().as_str(), "API_KEY"); + } +} diff --git a/foundry-config/src/utils.rs b/foundry-config/src/utils.rs new file mode 100644 index 000000000..b27aa40c0 --- /dev/null +++ b/foundry-config/src/utils.rs @@ -0,0 +1,325 @@ +//! Utility functions + +use crate::Config; +use ethers_core::types::{serde_helpers::Numeric, U256}; +use ethers_solc::{ + remappings::{Remapping, RemappingError}, + EvmVersion, +}; +use figment::value::Value; +use revm_primitives::SpecId; +use serde::{de::Error, Deserialize, Deserializer}; +use std::{ + path::{Path, PathBuf}, + str::FromStr, +}; +use toml_edit::{Document, Item}; + +/// Loads the config for the current project workspace +pub fn load_config() -> Config { + load_config_with_root(None) +} + +/// Loads the config for the current project workspace or the provided root path +pub fn load_config_with_root(root: Option) -> Config { + if let Some(root) = root { + Config::load_with_root(root) + } else { + Config::load_with_root(find_project_root_path(None).unwrap()) + } + .sanitized() +} + +/// Returns the path of the top-level directory of the working git tree. If there is no working +/// tree, an error is returned. +pub fn find_git_root_path(relative_to: impl AsRef) -> eyre::Result { + let path = std::process::Command::new("git") + .args(["rev-parse", "--show-toplevel"]) + .current_dir(relative_to.as_ref()) + .output()? + .stdout; + let path = std::str::from_utf8(&path)?.trim_end_matches('\n'); + Ok(PathBuf::from(path)) +} + +/// Returns the root path to set for the project root +/// +/// traverse the dir tree up and look for a `foundry.toml` file starting at the given path or cwd, +/// but only until the root dir of the current repo so that +/// +/// ```text +/// -- foundry.toml +/// +/// -- repo +/// |__ .git +/// |__sub +/// |__ [given_path | cwd] +/// ``` +/// will still detect `repo` as root +pub fn find_project_root_path(path: Option<&PathBuf>) -> std::io::Result { + let cwd = &std::env::current_dir()?; + let cwd = path.unwrap_or(cwd); + let boundary = find_git_root_path(cwd) + .ok() + .filter(|p| !p.as_os_str().is_empty()) + .unwrap_or_else(|| cwd.clone()); + let mut cwd = cwd.as_path(); + // traverse as long as we're in the current git repo cwd + while cwd.starts_with(&boundary) { + let file_path = cwd.join(Config::FILE_NAME); + if file_path.is_file() { + return Ok(cwd.to_path_buf()); + } + if let Some(parent) = cwd.parent() { + cwd = parent; + } else { + break; + } + } + // no foundry.toml found + Ok(boundary) +} + +/// Returns all [`Remapping`]s contained in the `remappings` str separated by newlines +/// +/// # Example +/// +/// ``` +/// use foundry_config::remappings_from_newline; +/// let remappings: Result, _> = remappings_from_newline( +/// r#" +/// file-ds-test/=lib/ds-test/ +/// file-other/=lib/other/ +/// "#, +/// ) +/// .collect(); +/// ``` +pub fn remappings_from_newline( + remappings: &str, +) -> impl Iterator> + '_ { + remappings + .lines() + .map(|x| x.trim()) + .filter(|x| !x.is_empty()) + .map(Remapping::from_str) +} + +/// Returns the remappings from the given var +/// +/// Returns `None` if the env var is not set, otherwise all Remappings, See +/// `remappings_from_newline` +pub fn remappings_from_env_var(env_var: &str) -> Option, RemappingError>> { + let val = std::env::var(env_var).ok()?; + Some(remappings_from_newline(&val).collect()) +} + +/// Converts the `val` into a `figment::Value::Array` +/// +/// The values should be separated by commas, surrounding brackets are also supported `[a,b,c]` +pub fn to_array_value(val: &str) -> Result { + let value: Value = match Value::from(val) { + Value::String(_, val) => val + .trim_start_matches('[') + .trim_end_matches(']') + .split(',') + .map(|s| s.to_string()) + .collect::>() + .into(), + Value::Empty(_, _) => Vec::::new().into(), + val @ Value::Array(_, _) => val, + _ => return Err(format!("Invalid value `{val}`, expected an array").into()), + }; + Ok(value) +} + +/// Returns a list of _unique_ paths to all folders under `root` that contain a `foundry.toml` file +/// +/// This will also resolve symlinks +/// +/// # Example +/// +/// ```no_run +/// use foundry_config::utils; +/// let dirs = utils::foundry_toml_dirs("./lib"); +/// ``` +/// +/// for following layout this will return +/// `["lib/dep1", "lib/dep2"]` +/// +/// ```text +/// lib +/// └── dep1 +/// │ ├── foundry.toml +/// └── dep2 +/// ├── foundry.toml +/// ``` +pub fn foundry_toml_dirs(root: impl AsRef) -> Vec { + walkdir::WalkDir::new(root) + .max_depth(1) + .into_iter() + .filter_map(Result::ok) + .filter(|e| e.file_type().is_dir()) + .filter_map(|e| ethers_solc::utils::canonicalize(e.path()).ok()) + .filter(|p| p.join(Config::FILE_NAME).exists()) + .collect() +} + +/// Returns a remapping for the given dir +pub(crate) fn get_dir_remapping(dir: impl AsRef) -> Option { + let dir = dir.as_ref(); + if let Some(dir_name) = dir + .file_name() + .and_then(|s| s.to_str()) + .filter(|s| !s.is_empty()) + { + let mut r = Remapping { + context: None, + name: format!("{dir_name}/"), + path: format!("{}", dir.display()), + }; + if !r.path.ends_with('/') { + r.path.push('/') + } + Some(r) + } else { + None + } +} + +/// Returns all available `profile` keys in a given `.toml` file +/// +/// i.e. The toml below would return would return `["default", "ci", "local"]` +/// ```toml +/// [profile.default] +/// ... +/// [profile.ci] +/// ... +/// [profile.local] +/// ``` +pub fn get_available_profiles(toml_path: impl AsRef) -> eyre::Result> { + let mut result = vec![Config::DEFAULT_PROFILE.to_string()]; + + if !toml_path.as_ref().exists() { + return Ok(result); + } + + let doc = read_toml(toml_path)?; + + if let Some(Item::Table(profiles)) = doc.as_table().get(Config::PROFILE_SECTION) { + for (_, (profile, _)) in profiles.iter().enumerate() { + let p = profile.to_string(); + if !result.contains(&p) { + result.push(p); + } + } + } + + Ok(result) +} + +/// Returns a [`toml_edit::Document`] loaded from the provided `path`. +/// Can raise an error in case of I/O or parsing errors. +fn read_toml(path: impl AsRef) -> eyre::Result { + let path = path.as_ref().to_owned(); + let doc: Document = std::fs::read_to_string(path)?.parse()?; + Ok(doc) +} + +/// Deserialize stringified percent. The value must be between 0 and 100 inclusive. +pub(crate) fn deserialize_stringified_percent<'de, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, +{ + let num: U256 = Numeric::deserialize(deserializer)? + .try_into() + .map_err(serde::de::Error::custom)?; + let num: u64 = num.try_into().map_err(serde::de::Error::custom)?; + if num <= 100 { + num.try_into().map_err(serde::de::Error::custom) + } else { + Err(serde::de::Error::custom("percent must be lte 100")) + } +} + +/// Deserialize an usize or +pub(crate) fn deserialize_usize_or_max<'de, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, +{ + #[derive(Deserialize)] + #[serde(untagged)] + enum Val { + Number(usize), + Text(String), + } + + let num = match Val::deserialize(deserializer)? { + Val::Number(num) => num, + Val::Text(s) => { + match s.as_str() { + "max" | "MAX" | "Max" => { + // toml limitation + i64::MAX as usize + } + s => s.parse::().map_err(D::Error::custom).unwrap(), + } + } + }; + Ok(num) +} + +/// Returns the [SpecId] derived from [EvmVersion] +#[inline] +pub fn evm_spec_id(evm_version: &EvmVersion) -> SpecId { + match evm_version { + EvmVersion::Homestead => SpecId::HOMESTEAD, + EvmVersion::TangerineWhistle => SpecId::TANGERINE, + EvmVersion::SpuriousDragon => SpecId::SPURIOUS_DRAGON, + EvmVersion::Byzantium => SpecId::BYZANTIUM, + EvmVersion::Constantinople => SpecId::CONSTANTINOPLE, + EvmVersion::Petersburg => SpecId::PETERSBURG, + EvmVersion::Istanbul => SpecId::ISTANBUL, + EvmVersion::Berlin => SpecId::BERLIN, + EvmVersion::London => SpecId::LONDON, + EvmVersion::Paris => SpecId::MERGE, + EvmVersion::Shanghai => SpecId::SHANGHAI, + } +} + +#[cfg(test)] +mod tests { + use crate::get_available_profiles; + use std::path::Path; + + #[test] + fn get_profiles_from_toml() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "foundry.toml", + r#" + [foo.baz] + libs = ['node_modules', 'lib'] + + [profile.default] + libs = ['node_modules', 'lib'] + + [profile.ci] + libs = ['node_modules', 'lib'] + + [profile.local] + libs = ['node_modules', 'lib'] + "#, + )?; + + let path = Path::new("./foundry.toml"); + let profiles = get_available_profiles(path).unwrap(); + + assert_eq!( + profiles, + vec!["default".to_string(), "ci".to_string(), "local".to_string()] + ); + + Ok(()) + }); + } +} diff --git a/foundry-config/src/warning.rs b/foundry-config/src/warning.rs new file mode 100644 index 000000000..fc98be3d4 --- /dev/null +++ b/foundry-config/src/warning.rs @@ -0,0 +1,84 @@ +use figment::Profile; +use serde::{Deserialize, Serialize}; +use std::{fmt, path::PathBuf}; + +/// Warnings emitted during loading or managing Configuration +#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq)] +#[serde(tag = "type")] +pub enum Warning { + /// An unknown section was encountered in a TOML file + UnknownSection { + /// The unknown key + unknown_section: Profile, + /// The source where the key was found + source: Option, + }, + /// No local TOML file found, with location tried + NoLocalToml(PathBuf), + /// Could not read TOML + CouldNotReadToml { + /// The path of the TOML file + path: PathBuf, + /// The error message that occurred + err: String, + }, + /// Could not write TOML + CouldNotWriteToml { + /// The path of the TOML file + path: PathBuf, + /// The error message that occurred + err: String, + }, + /// Invalid profile. Profile should be a table + CouldNotFixProfile { + /// The path of the TOML file + path: PathBuf, + /// The profile to be fixed + profile: String, + /// The error message that occurred + err: String, + }, + /// Deprecated key. + DeprecatedKey { + /// The key being deprecated + old: String, + /// The new key replacing the deprecated one if not empty, otherwise, meaning the old one + /// is being removed completely without replacement + new: String, + }, +} + +impl fmt::Display for Warning { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::UnknownSection { unknown_section, source } => { + let source = source.as_ref().map(|src| format!(" in {src}")).unwrap_or_default(); + f.write_fmt(format_args!("Unknown section [{unknown_section}] found{source}. This notation for profiles has been deprecated and may result in the profile not being registered in future versions. Please use [profile.{unknown_section}] instead or run `forge config --fix`.")) + } + Self::NoLocalToml(tried) => { + let path = tried.display(); + f.write_fmt(format_args!("No local TOML found to fix at {path}. Change the current directory to a project path or set the foundry.toml path with the FOUNDRY_CONFIG environment variable")) + } + Self::CouldNotReadToml { path, err } => { + f.write_fmt(format_args!("Could not read TOML at {}: {err}", path.display())) + } + Self::CouldNotWriteToml { path, err } => { + f.write_fmt(format_args!("Could not write TOML to {}: {err}", path.display())) + } + Self::CouldNotFixProfile { path, profile, err } => f.write_fmt(format_args!( + "Could not fix [{}] in TOML at {}: {}", + profile, + path.display(), + err + )), + Self::DeprecatedKey { old, new } if new.is_empty() => f.write_fmt(format_args!( + "Key `{old}` is being deprecated and will be removed in future versions.", + )), + Self::DeprecatedKey { old, new } => f.write_fmt(format_args!( + "Key `{old}` is being deprecated in favor of `{new}`. It will be removed in future versions.", + )), + } + } +} + +impl std::error::Error for Warning {} diff --git a/src/bin/languageserver/mod.rs b/src/bin/languageserver/mod.rs index 34de2e3e8..83e4d702e 100644 --- a/src/bin/languageserver/mod.rs +++ b/src/bin/languageserver/mod.rs @@ -16,6 +16,7 @@ use solang::{ use solang_parser::pt; use std::{collections::HashMap, ffi::OsString, path::PathBuf}; use tokio::sync::Mutex; +use tower_lsp::lsp_types::{DocumentFormattingParams, TextEdit}; use tower_lsp::{ jsonrpc::{Error, ErrorCode, Result}, lsp_types::{ @@ -38,6 +39,8 @@ use tower_lsp::{ Client, LanguageServer, LspService, Server, }; +use forge_fmt::{format, parse, FormatterConfig}; + use crate::cli::{target_arg, LanguageServerCommand}; /// Represents the type of the code object that a reference points to @@ -1776,6 +1779,7 @@ impl LanguageServer for SolangServer { type_definition_provider: Some(TypeDefinitionProviderCapability::Simple(true)), implementation_provider: Some(ImplementationProviderCapability::Simple(true)), references_provider: Some(OneOf::Left(true)), + document_formatting_provider: Some(OneOf::Left(true)), ..ServerCapabilities::default() }, }) @@ -2133,6 +2137,36 @@ impl LanguageServer for SolangServer { Ok(locations) } + + async fn formatting(&self, params: DocumentFormattingParams) -> Result>> { + let source_path = params.text_document.uri.to_file_path().unwrap(); + let source = std::fs::read_to_string(source_path).unwrap(); + let source_parsed = parse(&source).unwrap(); + + let config = FormatterConfig { + line_length: 80, + ..Default::default() + }; + + let mut source_formatted = String::new(); + format(&mut source_formatted, source_parsed, config).unwrap(); + + let text_edit = TextEdit { + range: Range { + start: Position { + line: 0, + character: 0, + }, + end: Position { + line: u32::max_value(), + character: u32::max_value(), + }, + }, + new_text: source_formatted, + }; + + Ok(Some(vec![text_edit])) + } } /// Calculate the line and column from the Loc offset received from the parser diff --git a/vscode/package.json b/vscode/package.json index 28963d248..26651eb37 100644 --- a/vscode/package.json +++ b/vscode/package.json @@ -56,7 +56,10 @@ } }, "capabilities": { - "hoverProvider": "true" + "hoverProvider": "true", + "formatting": { + "dynamicRegistration": true + } }, "languages": [ { From b40e300366d369634450986b96abc8a122d878eb Mon Sep 17 00:00:00 2001 From: Govardhan G D Date: Sun, 17 Sep 2023 06:40:42 +0530 Subject: [PATCH 13/25] refactoring + comments Signed-off-by: Govardhan G D --- src/bin/languageserver/mod.rs | 73 +++++++++++++++++++++++++++-------- src/sema/ast.rs | 7 ---- src/sema/contracts.rs | 3 +- 3 files changed, 58 insertions(+), 25 deletions(-) diff --git a/src/bin/languageserver/mod.rs b/src/bin/languageserver/mod.rs index 6aee7d988..cdd93de14 100644 --- a/src/bin/languageserver/mod.rs +++ b/src/bin/languageserver/mod.rs @@ -124,7 +124,6 @@ struct FileCache { file: ast::File, hovers: Lapper, references: Lapper, - declarations: Declarations, } /// Stores information used by the language server to service requests (eg: `Go to Definitions`) received from the client. @@ -137,10 +136,12 @@ struct FileCache { /// Each field stores *some information* about a code object. The code object is uniquely identified by its `DefinitionIndex`. /// * `definitions` maps `DefinitionIndex` of a code object to its source code location where it is defined. /// * `types` maps the `DefinitionIndex` of a code object to that of its type. +/// * `declarations` maps the `DefinitionIndex` of a `Contract` method to a list of methods that it overrides. The overridden methods belong to the parent `Contract`s /// * `implementations` maps the `DefinitionIndex` of a `Contract` to the `DefinitionIndex`s of methods defined as part of the `Contract`. struct GlobalCache { definitions: Definitions, types: Types, + declarations: Declarations, implementations: Implementations, } @@ -207,6 +208,7 @@ pub async fn start_server(language_args: &LanguageServerCommand) -> ! { global_cache: Mutex::new(GlobalCache { definitions: HashMap::new(), types: HashMap::new(), + declarations: HashMap::new(), implementations: HashMap::new(), }), }); @@ -291,7 +293,7 @@ impl SolangServer { let res = self.client.publish_diagnostics(uri, diags, None); - let (caches, definitions, types, implementations) = Builder::build(&ns); + let (caches, definitions, types, declarations, implementations) = Builder::build(&ns); let mut files = self.files.lock().await; for (f, c) in ns.files.iter().zip(caches.into_iter()) { @@ -303,6 +305,7 @@ impl SolangServer { let mut gc = self.global_cache.lock().await; gc.definitions.extend(definitions); gc.types.extend(types); + gc.declarations.extend(declarations); gc.implementations.extend(implementations); res.await; @@ -345,11 +348,11 @@ struct Builder<'a> { hovers: Vec<(usize, HoverEntry)>, // `usize` is the file number the reference belongs to references: Vec<(usize, ReferenceEntry)>, - declarations: Vec, definitions: Definitions, - implementations: Implementations, types: Types, + declarations: Declarations, + implementations: Implementations, ns: &'a ast::Namespace, } @@ -1329,15 +1332,23 @@ impl<'a> Builder<'a> { /// Traverses namespace to extract information used later by the language server /// This includes hover messages, locations where code objects are declared and used - fn build(ns: &ast::Namespace) -> (Vec, Definitions, Types, Implementations) { + fn build( + ns: &ast::Namespace, + ) -> ( + Vec, + Definitions, + Types, + Declarations, + Implementations, + ) { let mut builder = Builder { hovers: Vec::new(), references: Vec::new(), - declarations: vec![HashMap::new(); ns.files.len()], definitions: HashMap::new(), - implementations: HashMap::new(), types: HashMap::new(), + declarations: HashMap::new(), + implementations: HashMap::new(), ns, }; @@ -1589,6 +1600,7 @@ impl<'a> Builder<'a> { def_path: file.path.clone(), def_type: DefinitionType::Contract(ci), }; + builder .definitions .insert(cdi.clone(), loc_to_range(&contract.loc, file)); @@ -1608,12 +1620,21 @@ impl<'a> Builder<'a> { .virtual_functions .iter() .filter_map(|(_, indices)| { + // due to the way the `indices` vector is populated during namespace creation, + // the last element in the vector contains the overriding function that belongs to the current contract. let func = DefinitionIndex { def_path: file.path.clone(), + // `unwrap` is alright here as the `indices` vector is guaranteed to have at least 1 element + // the vector is always initialised with one initial element + // and the elements in the vector are never removed during namespace construction def_type: DefinitionType::Function(indices.last().copied().unwrap()), }; + // get all the functions overridden by the current function let all_decls: HashSet = HashSet::from_iter(indices.iter().copied()); + + // choose the overridden functions that belong to the parent contracts + // due to multiple inheritance, a contract can have multiple parents let parent_decls = contract .bases .iter() @@ -1626,6 +1647,7 @@ impl<'a> Builder<'a> { }) .reduce(|acc, e| acc.union(&e).copied().collect()); + // get the `DefinitionIndex`s of the overridden funcions parent_decls.map(|parent_decls| { let decls = parent_decls .iter() @@ -1641,7 +1663,8 @@ impl<'a> Builder<'a> { (func, decls) }) }); - builder.declarations[file_no].extend(decls); + + builder.declarations.extend(decls); } for (ei, event) in builder.ns.events.iter().enumerate() { @@ -1679,6 +1702,10 @@ impl<'a> Builder<'a> { } } + // `defs_to_files` and `defs_to_file_nos` are used to insert the correct filepath where a code object is defined. + // previously, a dummy path was filled. + // In a single namespace, there can't be two (or more) code objects with a given `DefinitionType`. + // So, there exists a one-to-one mapping between `DefinitionIndex` and `DefinitionType` when we are dealing with just one namespace. let defs_to_files = builder .definitions .keys() @@ -1716,6 +1743,7 @@ impl<'a> Builder<'a> { .enumerate() .map(|(i, f)| FileCache { file: f.clone(), + // get `hovers` that belong to the current file hovers: Lapper::new( builder .hovers @@ -1724,6 +1752,7 @@ impl<'a> Builder<'a> { .map(|(_, i)| i.clone()) .collect(), ), + // get `references` that belong to the current file references: Lapper::new( builder .references @@ -1736,7 +1765,6 @@ impl<'a> Builder<'a> { }) .collect(), ), - declarations: builder.declarations[i].clone(), }) .collect(); @@ -1744,6 +1772,7 @@ impl<'a> Builder<'a> { caches, builder.definitions, builder.types, + builder.declarations, builder.implementations, ) } @@ -2129,21 +2158,33 @@ impl LanguageServer for SolangServer { Ok(impls) } + /// Called when "Go to Declaration" is called by the user on the client side. + /// + /// Expected to return a list (possibly empty) of methods that the given method overrides. + /// Only the methods belonging to the immediate parent contracts (due to multiple inheritance, there can be more than one parent) are to be returned. + /// + /// ### Arguments + /// * `GotoDeclarationParams` provides the source code location (filename, line number, column number) of the code object for which the request was made. + /// + /// ### Edge cases + /// * Returns `Err` when an invalid file path is received. + /// * Returns `Ok(None)` when the location passed in the arguments doesn't belong to a contract method defined in user code. async fn goto_declaration( &self, params: GotoDeclarationParams, ) -> Result> { + // fetch the `DefinitionIndex` of the code object in question let Some(reference) = self.get_reference_from_params(params).await? else { return Ok(None); }; - let caches = &self.files.lock().await.caches; - let decls = caches - .get(&reference.def_path) - .and_then(|cache| cache.declarations.get(&reference)); - let gc = self.global_cache.lock().await; - let decls = decls + + // get a list of `DefinitionIndex`s of overridden functions from parent contracts + let decls = gc.declarations.get(&reference); + + // get a list of locations in source code where the overridden functions are present + let locations = decls .map(|decls| { decls .iter() @@ -2158,7 +2199,7 @@ impl LanguageServer for SolangServer { }) .map(GotoImplementationResponse::Array); - Ok(decls) + Ok(locations) } /// Called when "Go to References" is called by the user on the client side. diff --git a/src/sema/ast.rs b/src/sema/ast.rs index 8a35e727d..546efe86f 100644 --- a/src/sema/ast.rs +++ b/src/sema/ast.rs @@ -786,13 +786,6 @@ pub struct Contract { pub program_id: Option>, } -impl Contract { - pub fn _virtual_functions(&self, key: &String) -> usize { - let a = &self.virtual_functions[key]; - a[a.len() - 1] - } -} - impl Contract { // Is this a concrete contract, which can be instantiated pub fn is_concrete(&self) -> bool { diff --git a/src/sema/contracts.rs b/src/sema/contracts.rs index ce6fe6554..871e54be4 100644 --- a/src/sema/contracts.rs +++ b/src/sema/contracts.rs @@ -599,8 +599,7 @@ fn check_inheritance(contract_no: usize, ns: &mut ast::Namespace) { .virtual_functions .entry(signature) .or_insert_with(Vec::new) - .push(function_no); - // .insert(signature, function_no); + .push(function_no); // there is always at least 1 element in the vector } ns.contracts[contract_no] From dd29b5869c9d4a1ab585e7feabf4fac75399b09d Mon Sep 17 00:00:00 2001 From: Govardhan G D Date: Sun, 17 Sep 2023 07:46:12 +0530 Subject: [PATCH 14/25] add tests Signed-off-by: Govardhan G D --- src/bin/languageserver/mod.rs | 2 +- vscode/src/test/suite/extension.test.ts | 148 +++++++++++++++++------- vscode/src/testFixture/impls.sol | 16 ++- 3 files changed, 116 insertions(+), 50 deletions(-) diff --git a/src/bin/languageserver/mod.rs b/src/bin/languageserver/mod.rs index cdd93de14..cd6b4c686 100644 --- a/src/bin/languageserver/mod.rs +++ b/src/bin/languageserver/mod.rs @@ -2290,7 +2290,7 @@ impl LanguageServer for SolangServer { let new_text = params.new_name; // create `TextEdit` instances that represent the changes to be made for every occurrence of the old symbol - // these `TextEdit` objects are then grouped into separate list per source file to which they bolong + // these `TextEdit` objects are then grouped into separate list per source file to which they belong let caches = &self.files.lock().await.caches; let ws = caches .iter() diff --git a/vscode/src/test/suite/extension.test.ts b/vscode/src/test/suite/extension.test.ts index 8a430b198..408837cdf 100644 --- a/vscode/src/test/suite/extension.test.ts +++ b/vscode/src/test/suite/extension.test.ts @@ -83,6 +83,13 @@ suite('Extension Test Suite', function () { await testtypedefs(typedefdoc1); }); + // Tests for goto-declaration + this.timeout(20000); + const declsdoc1 = getDocUri('impls.sol'); + test('Testing for GoToDeclaration', async () => { + await testdecls(declsdoc1); + }); + // Tests for goto-impls this.timeout(20000); const implsdoc1 = getDocUri('impls.sol'); @@ -119,8 +126,8 @@ async function testdefs(docUri: vscode.Uri) { 'vscode.executeDefinitionProvider', docUri, pos1 - )) as vscode.Definition[]; - const loc1 = actualdef1[0] as vscode.Location; + )) as vscode.Location[]; + const loc1 = actualdef1[0]; assert.strictEqual(loc1.range.start.line, 27); assert.strictEqual(loc1.range.start.character, 24); assert.strictEqual(loc1.range.end.line, 27); @@ -132,8 +139,8 @@ async function testdefs(docUri: vscode.Uri) { 'vscode.executeDefinitionProvider', docUri, pos2 - )) as vscode.Definition[]; - const loc2 = actualdef2[0] as vscode.Location; + )) as vscode.Location[]; + const loc2 = actualdef2[0]; assert.strictEqual(loc2.range.start.line, 27); assert.strictEqual(loc2.range.start.character, 50); assert.strictEqual(loc2.range.end.line, 27); @@ -145,8 +152,8 @@ async function testdefs(docUri: vscode.Uri) { 'vscode.executeDefinitionProvider', docUri, pos3 - )) as vscode.Definition[]; - const loc3 = actualdef3[0] as vscode.Location; + )) as vscode.Location[]; + const loc3 = actualdef3[0]; assert.strictEqual(loc3.range.start.line, 19); assert.strictEqual(loc3.range.start.character, 8); assert.strictEqual(loc3.range.end.line, 19); @@ -158,8 +165,8 @@ async function testdefs(docUri: vscode.Uri) { 'vscode.executeDefinitionProvider', docUri, pos4 - )) as vscode.Definition[]; - const loc4 = actualdef4[0] as vscode.Location; + )) as vscode.Location[]; + const loc4 = actualdef4[0]; assert.strictEqual(loc4.range.start.line, 23); assert.strictEqual(loc4.range.start.character, 8); assert.strictEqual(loc4.range.end.line, 23); @@ -171,8 +178,8 @@ async function testdefs(docUri: vscode.Uri) { 'vscode.executeDefinitionProvider', docUri, pos5 - )) as vscode.Definition[]; - const loc5 = actualdef5[0] as vscode.Location; + )) as vscode.Location[]; + const loc5 = actualdef5[0]; assert.strictEqual(loc5.range.start.line, 24); assert.strictEqual(loc5.range.start.character, 8); assert.strictEqual(loc5.range.end.line, 24); @@ -188,8 +195,8 @@ async function testtypedefs(docUri: vscode.Uri) { 'vscode.executeTypeDefinitionProvider', docUri, pos0, - )) as vscode.Definition[]; - const loc0 = actualtypedef0[0] as vscode.Location; + )) as vscode.Location[]; + const loc0 = actualtypedef0[0]; assert.strictEqual(loc0.range.start.line, 22); assert.strictEqual(loc0.range.start.character, 11); assert.strictEqual(loc0.range.end.line, 22); @@ -201,8 +208,8 @@ async function testtypedefs(docUri: vscode.Uri) { 'vscode.executeTypeDefinitionProvider', docUri, pos1, - )) as vscode.Definition[]; - const loc1 = actualtypedef1[0] as vscode.Location; + )) as vscode.Location[]; + const loc1 = actualtypedef1[0]; assert.strictEqual(loc1.range.start.line, 7); assert.strictEqual(loc1.range.start.character, 4); assert.strictEqual(loc1.range.end.line, 21); @@ -210,6 +217,59 @@ async function testtypedefs(docUri: vscode.Uri) { assert.strictEqual(loc1.uri.path, docUri.path); } +async function testdecls(docUri: vscode.Uri) { + await activate(docUri); + + const pos0 = new vscode.Position(6, 14); + const actualdecl0 = (await vscode.commands.executeCommand( + 'vscode.executeDeclarationProvider', + docUri, + pos0, + )) as vscode.Location[]; + assert.strictEqual(actualdecl0.length, 2); + const loc00 = actualdecl0[0]; + assert.strictEqual(loc00.range.start.line, 12); + assert.strictEqual(loc00.range.start.character, 4); + assert.strictEqual(loc00.range.end.line, 12); + assert.strictEqual(loc00.range.end.character, 61); + assert.strictEqual(loc00.uri.path, docUri.path); + const loc01 = actualdecl0[1]; + assert.strictEqual(loc01.range.start.line, 22); + assert.strictEqual(loc01.range.start.character, 4); + assert.strictEqual(loc01.range.end.line, 22); + assert.strictEqual(loc01.range.end.character, 61); + assert.strictEqual(loc01.uri.path, docUri.path); + + const pos1 = new vscode.Position(12, 14); + const actualdecl1 = (await vscode.commands.executeCommand( + 'vscode.executeDeclarationProvider', + docUri, + pos1, + )) as vscode.Location[]; + assert.strictEqual(actualdecl1.length, 1); + const loc10 = actualdecl1[0]; + assert.strictEqual(loc10.range.start.line, 32); + assert.strictEqual(loc10.range.start.character, 4); + assert.strictEqual(loc10.range.end.line, 32); + assert.strictEqual(loc10.range.end.character, 52); + assert.strictEqual(loc10.uri.path, docUri.path); + + const pos2 = new vscode.Position(22, 14); + const actualdecl2 = (await vscode.commands.executeCommand( + 'vscode.executeDeclarationProvider', + docUri, + pos2, + )) as vscode.Location[]; + assert.strictEqual(actualdecl2.length, 1); + const loc20 = actualdecl2[0]; + assert.strictEqual(loc20.range.start.line, 32); + assert.strictEqual(loc20.range.start.character, 4); + assert.strictEqual(loc20.range.end.line, 32); + assert.strictEqual(loc20.range.end.character, 52); + assert.strictEqual(loc20.uri.path, docUri.path); +} + + async function testimpls(docUri: vscode.Uri) { await activate(docUri); @@ -218,19 +278,19 @@ async function testimpls(docUri: vscode.Uri) { 'vscode.executeImplementationProvider', docUri, pos0, - )) as vscode.Definition[]; + )) as vscode.Location[]; assert.strictEqual(actualimpl0.length, 2); - const loc00 = actualimpl0[0] as vscode.Location; + const loc00 = actualimpl0[0]; assert.strictEqual(loc00.range.start.line, 1); assert.strictEqual(loc00.range.start.character, 4); assert.strictEqual(loc00.range.end.line, 1); assert.strictEqual(loc00.range.end.character, 42); assert.strictEqual(loc00.uri.path, docUri.path); - const loc01 = actualimpl0[1] as vscode.Location; + const loc01 = actualimpl0[1]; assert.strictEqual(loc01.range.start.line, 6); assert.strictEqual(loc01.range.start.character, 4); assert.strictEqual(loc01.range.end.line, 6); - assert.strictEqual(loc01.range.end.character, 61); + assert.strictEqual(loc01.range.end.character, 65); assert.strictEqual(loc01.uri.path, docUri.path); @@ -239,15 +299,15 @@ async function testimpls(docUri: vscode.Uri) { 'vscode.executeImplementationProvider', docUri, pos1, - )) as vscode.Definition[]; + )) as vscode.Location[]; assert.strictEqual(actualimpl1.length, 2); - const loc10 = actualimpl1[0] as vscode.Location; + const loc10 = actualimpl1[0]; assert.strictEqual(loc10.range.start.line, 12); assert.strictEqual(loc10.range.start.character, 4); assert.strictEqual(loc10.range.end.line, 12); - assert.strictEqual(loc10.range.end.character, 52); + assert.strictEqual(loc10.range.end.character, 61); assert.strictEqual(loc10.uri.path, docUri.path); - const loc11 = actualimpl1[1] as vscode.Location; + const loc11 = actualimpl1[1]; assert.strictEqual(loc11.range.start.line, 16); assert.strictEqual(loc11.range.start.character, 4); assert.strictEqual(loc11.range.end.line, 16); @@ -260,15 +320,15 @@ async function testimpls(docUri: vscode.Uri) { 'vscode.executeImplementationProvider', docUri, pos2, - )) as vscode.Definition[]; + )) as vscode.Location[]; assert.strictEqual(actualimpl2.length, 2); - const loc20 = actualimpl2[0] as vscode.Location; + const loc20 = actualimpl2[0]; assert.strictEqual(loc20.range.start.line, 22); assert.strictEqual(loc20.range.start.character, 4); assert.strictEqual(loc20.range.end.line, 22); - assert.strictEqual(loc20.range.end.character, 52); + assert.strictEqual(loc20.range.end.character, 61); assert.strictEqual(loc20.uri.path, docUri.path); - const loc21 = actualimpl2[1] as vscode.Location; + const loc21 = actualimpl2[1]; assert.strictEqual(loc21.range.start.line, 26); assert.strictEqual(loc21.range.start.character, 4); assert.strictEqual(loc21.range.end.line, 26); @@ -284,33 +344,33 @@ async function testrefs(docUri: vscode.Uri) { 'vscode.executeReferenceProvider', docUri, pos0, - )) as vscode.Definition[]; + )) as vscode.Location[]; assert.strictEqual(actualref0.length, 5); - const loc00 = actualref0[0] as vscode.Location; + const loc00 = actualref0[0]; assert.strictEqual(loc00.range.start.line, 27); assert.strictEqual(loc00.range.start.character, 50); assert.strictEqual(loc00.range.end.line, 27); assert.strictEqual(loc00.range.end.character, 55); assert.strictEqual(loc00.uri.path, docUri.path); - const loc01 = actualref0[1] as vscode.Location; + const loc01 = actualref0[1]; assert.strictEqual(loc01.range.start.line, 30); assert.strictEqual(loc01.range.start.character, 16); assert.strictEqual(loc01.range.end.line, 30); assert.strictEqual(loc01.range.end.character, 22); assert.strictEqual(loc01.uri.path, docUri.path); - const loc02 = actualref0[2] as vscode.Location; + const loc02 = actualref0[2]; assert.strictEqual(loc02.range.start.line, 33); assert.strictEqual(loc02.range.start.character, 16); assert.strictEqual(loc02.range.end.line, 33); assert.strictEqual(loc02.range.end.character, 22); assert.strictEqual(loc02.uri.path, docUri.path); - const loc03 = actualref0[3] as vscode.Location; + const loc03 = actualref0[3]; assert.strictEqual(loc03.range.start.line, 36); assert.strictEqual(loc03.range.start.character, 16); assert.strictEqual(loc03.range.end.line, 36); assert.strictEqual(loc03.range.end.character, 22); assert.strictEqual(loc03.uri.path, docUri.path); - const loc04 = actualref0[4] as vscode.Location; + const loc04 = actualref0[4]; assert.strictEqual(loc04.range.start.line, 39); assert.strictEqual(loc04.range.start.character, 16); assert.strictEqual(loc04.range.end.line, 39); @@ -322,39 +382,39 @@ async function testrefs(docUri: vscode.Uri) { 'vscode.executeReferenceProvider', docUri, pos1, - )) as vscode.Definition[]; + )) as vscode.Location[]; assert.strictEqual(actualref1.length, 6); - const loc10 = actualref1[0] as vscode.Location; + const loc10 = actualref1[0]; assert.strictEqual(loc10.range.start.line, 27); assert.strictEqual(loc10.range.start.character, 24); assert.strictEqual(loc10.range.end.line, 27); assert.strictEqual(loc10.range.end.character, 25); assert.strictEqual(loc10.uri.path, docUri.path); - const loc11 = actualref1[1] as vscode.Location; + const loc11 = actualref1[1]; assert.strictEqual(loc11.range.start.line, 28); assert.strictEqual(loc11.range.start.character, 12); assert.strictEqual(loc11.range.end.line, 28); assert.strictEqual(loc11.range.end.character, 14); assert.strictEqual(loc11.uri.path, docUri.path); - const loc12 = actualref1[2] as vscode.Location; + const loc12 = actualref1[2]; assert.strictEqual(loc12.range.start.line, 29); assert.strictEqual(loc12.range.start.character, 16); assert.strictEqual(loc12.range.end.line, 29); assert.strictEqual(loc12.range.end.character, 18); assert.strictEqual(loc12.uri.path, docUri.path); - const loc13 = actualref1[3] as vscode.Location; + const loc13 = actualref1[3]; assert.strictEqual(loc13.range.start.line, 32); assert.strictEqual(loc13.range.start.character, 16); assert.strictEqual(loc13.range.end.line, 32); assert.strictEqual(loc13.range.end.character, 18); assert.strictEqual(loc13.uri.path, docUri.path); - const loc14 = actualref1[4] as vscode.Location; + const loc14 = actualref1[4]; assert.strictEqual(loc14.range.start.line, 35); assert.strictEqual(loc14.range.start.character, 16); assert.strictEqual(loc14.range.end.line, 35); assert.strictEqual(loc14.range.end.character, 18); assert.strictEqual(loc14.uri.path, docUri.path); - const loc15 = actualref1[5] as vscode.Location; + const loc15 = actualref1[5]; assert.strictEqual(loc15.range.start.line, 38); assert.strictEqual(loc15.range.start.character, 16); assert.strictEqual(loc15.range.end.line, 38); @@ -376,27 +436,27 @@ async function testrename(docUri: vscode.Uri) { assert(rename0.has(docUri)); - const loc0 = rename0.get(docUri); + const loc0 = rename0.get(docUri) as vscode.TextEdit[]; - const loc00 = loc0[0] as vscode.TextEdit; + const loc00 = loc0[0]; assert.strictEqual(loc00.range.start.line, 0); assert.strictEqual(loc00.range.start.character, 41); assert.strictEqual(loc00.range.end.line, 0); assert.strictEqual(loc00.range.end.character, 42); assert.strictEqual(loc00.newText, newname0); - const loc01 = loc0[1] as vscode.TextEdit; + const loc01 = loc0[1]; assert.strictEqual(loc01.range.start.line, 1); assert.strictEqual(loc01.range.start.character, 4); assert.strictEqual(loc01.range.end.line, 1); assert.strictEqual(loc01.range.end.character, 6); assert.strictEqual(loc01.newText, newname0); - const loc02 = loc0[2] as vscode.TextEdit; + const loc02 = loc0[2]; assert.strictEqual(loc02.range.start.line, 9); assert.strictEqual(loc02.range.start.character, 8); assert.strictEqual(loc02.range.end.line, 9); assert.strictEqual(loc02.range.end.character, 10); assert.strictEqual(loc02.newText, newname0); - const loc03 = loc0[3] as vscode.TextEdit; + const loc03 = loc0[3]; assert.strictEqual(loc03.range.start.line, 9); assert.strictEqual(loc03.range.start.character, 12); assert.strictEqual(loc03.range.end.line, 9); diff --git a/vscode/src/testFixture/impls.sol b/vscode/src/testFixture/impls.sol index 8ceb85b0f..9cdf8257f 100644 --- a/vscode/src/testFixture/impls.sol +++ b/vscode/src/testFixture/impls.sol @@ -4,13 +4,13 @@ contract a is b1, b2 { return super.foo(); } - function foo() internal override(b1, b2) returns (uint64) { + function foo() internal override(b1, b2, b3) returns (uint64) { return 2; } } -abstract contract b1 { - function foo() internal virtual returns (uint64) { +abstract contract b1 is b3 { + function foo() internal virtual override returns (uint64) { return 100; } @@ -19,8 +19,8 @@ abstract contract b1 { } } -abstract contract b2 { - function foo() internal virtual returns (uint64) { +abstract contract b2 is b3 { + function foo() internal virtual override returns (uint64) { return 200; } @@ -28,3 +28,9 @@ abstract contract b2 { return 25; } } + +abstract contract b3 { + function foo() internal virtual returns (uint64) { + return 400; + } +} From 8ffad9340c37fcc7512f92f6279bea50e5f4031d Mon Sep 17 00:00:00 2001 From: Govardhan G D Date: Sun, 17 Sep 2023 09:13:21 +0530 Subject: [PATCH 15/25] separate config file for forge-fmt from foundry-config Signed-off-by: Govardhan G D --- Cargo.toml | 4 +- forge-fmt/Cargo.toml | 22 +- .../src/fmt.rs => forge-fmt/src/config.rs | 0 forge-fmt/src/formatter.rs | 2 +- forge-fmt/src/lib.rs | 4 +- foundry-config/Cargo.toml | 58 - foundry-config/README.md | 302 -- foundry-config/src/cache.rs | 318 -- foundry-config/src/chain.rs | 181 - foundry-config/src/doc.rs | 38 - foundry-config/src/endpoints.rs | 175 - foundry-config/src/error.rs | 274 - foundry-config/src/etherscan.rs | 471 -- foundry-config/src/fix.rs | 344 -- foundry-config/src/fs_permissions.rs | 255 - foundry-config/src/fuzz.rs | 170 - foundry-config/src/inline/conf_parser.rs | 212 - foundry-config/src/inline/mod.rs | 85 - foundry-config/src/inline/natspec.rs | 241 - foundry-config/src/invariant.rs | 129 - foundry-config/src/lib.rs | 4630 ----------------- foundry-config/src/macros.rs | 239 - foundry-config/src/providers/mod.rs | 158 - foundry-config/src/providers/remappings.rs | 281 - foundry-config/src/resolve.rs | 84 - foundry-config/src/utils.rs | 325 -- foundry-config/src/warning.rs | 84 - 27 files changed, 12 insertions(+), 9074 deletions(-) rename foundry-config/src/fmt.rs => forge-fmt/src/config.rs (100%) delete mode 100644 foundry-config/Cargo.toml delete mode 100644 foundry-config/README.md delete mode 100644 foundry-config/src/cache.rs delete mode 100644 foundry-config/src/chain.rs delete mode 100644 foundry-config/src/doc.rs delete mode 100644 foundry-config/src/endpoints.rs delete mode 100644 foundry-config/src/error.rs delete mode 100644 foundry-config/src/etherscan.rs delete mode 100644 foundry-config/src/fix.rs delete mode 100644 foundry-config/src/fs_permissions.rs delete mode 100644 foundry-config/src/fuzz.rs delete mode 100644 foundry-config/src/inline/conf_parser.rs delete mode 100644 foundry-config/src/inline/mod.rs delete mode 100644 foundry-config/src/inline/natspec.rs delete mode 100644 foundry-config/src/invariant.rs delete mode 100644 foundry-config/src/lib.rs delete mode 100644 foundry-config/src/macros.rs delete mode 100644 foundry-config/src/providers/mod.rs delete mode 100644 foundry-config/src/providers/remappings.rs delete mode 100644 foundry-config/src/resolve.rs delete mode 100644 foundry-config/src/utils.rs delete mode 100644 foundry-config/src/warning.rs diff --git a/Cargo.toml b/Cargo.toml index 56a01791d..f8ed67362 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -66,9 +66,7 @@ contract-build = { version = "3.0.1", optional = true } primitive-types = { version = "0.12", features = ["codec"] } normalize-path = "0.2.1" bitflags = "2.3.3" - forge-fmt = {path = "forge-fmt"} -foundry-config = {path = "foundry-config"} [dev-dependencies] num-derive = "0.4" @@ -101,4 +99,4 @@ llvm = ["inkwell", "libc"] wasm_opt = ["llvm", "wasm-opt", "contract-build"] [workspace] -members = ["solang-parser", "tests/wasm_host_attr", "forge-fmt", "foundry-config"] +members = ["solang-parser", "tests/wasm_host_attr", "forge-fmt"] diff --git a/forge-fmt/Cargo.toml b/forge-fmt/Cargo.toml index fbb6a6340..377163122 100644 --- a/forge-fmt/Cargo.toml +++ b/forge-fmt/Cargo.toml @@ -1,27 +1,21 @@ [package] name = "forge-fmt" -version = "0.3.2" -authors = ["Sean Young ", "Lucas Steuernagel ", "Cyrill Leutwiler "] -homepage = "https://github.com/hyperledger/solang" -documentation = "https://solang.readthedocs.io/" -license = "Apache-2.0" +version = "0.2.0" edition = "2021" -# version.workspace = true -# edition.workspace = true -# rust-version.workspace = true -# authors.workspace = true -# license.workspace = true -# homepage.workspace = true -# repository.workspace = true +rust-version = "1.72" +authors = ["Foundry Contributors"] +license = "MIT OR Apache-2.0" +homepage = "https://github.com/foundry-rs/foundry" +repository = "https://github.com/foundry-rs/foundry" [dependencies] -foundry-config ={path = "../foundry-config"} ethers-core = { git = "https://github.com/gakonst/ethers-rs", default-features = false } -solang-parser = {path = "../solang-parser"} +solang-parser = { path = "../solang-parser" } itertools = "0.11" thiserror = "1" ariadne = "0.2" tracing = "0.1" +serde = { version = "1", features = ["derive"] } [dev-dependencies] pretty_assertions = "1" diff --git a/foundry-config/src/fmt.rs b/forge-fmt/src/config.rs similarity index 100% rename from foundry-config/src/fmt.rs rename to forge-fmt/src/config.rs diff --git a/forge-fmt/src/formatter.rs b/forge-fmt/src/formatter.rs index eaaf69c04..429902200 100644 --- a/forge-fmt/src/formatter.rs +++ b/forge-fmt/src/formatter.rs @@ -6,6 +6,7 @@ use crate::{ comments::{ CommentPosition, CommentState, CommentStringExt, CommentType, CommentWithMetadata, Comments, }, + config::{MultilineFuncHeaderStyle, SingleLineBlockStyle}, helpers::import_path_string, macros::*, solang_ext::{pt::*, *}, @@ -14,7 +15,6 @@ use crate::{ FormatterConfig, InlineConfig, IntTypes, NumberUnderscore, }; use ethers_core::{types::H160, utils::to_checksum}; -use foundry_config::fmt::{MultilineFuncHeaderStyle, SingleLineBlockStyle}; use itertools::{Either, Itertools}; use solang_parser::pt::ImportPath; use std::{fmt::Write, str::FromStr}; diff --git a/forge-fmt/src/lib.rs b/forge-fmt/src/lib.rs index 66f8bc4a5..a79a269d9 100644 --- a/forge-fmt/src/lib.rs +++ b/forge-fmt/src/lib.rs @@ -7,6 +7,7 @@ extern crate tracing; mod buffer; pub mod chunk; mod comments; +pub mod config; mod formatter; mod helpers; pub mod inline_config; @@ -15,9 +16,8 @@ pub mod solang_ext; mod string; pub mod visit; -pub use foundry_config::fmt::*; - pub use comments::Comments; +pub use config::*; pub use formatter::{Formatter, FormatterError}; pub use helpers::{fmt, format, offset_to_line_column, parse, print_diagnostics_report, Parsed}; pub use inline_config::InlineConfig; diff --git a/foundry-config/Cargo.toml b/foundry-config/Cargo.toml deleted file mode 100644 index 25e465a3c..000000000 --- a/foundry-config/Cargo.toml +++ /dev/null @@ -1,58 +0,0 @@ -[package] -name = "foundry-config" -version = "0.3.2" -authors = ["Sean Young ", "Lucas Steuernagel ", "Cyrill Leutwiler "] -homepage = "https://github.com/hyperledger/solang" -documentation = "https://solang.readthedocs.io/" -license = "Apache-2.0" -edition = "2021" - -# version.workspace = true -# edition.workspace = true -# rust-version.workspace = true -# authors.workspace = true -# license.workspace = true -# homepage.workspace = true -# repository.workspace = true - -[dependencies] -# eth -ethers-core = { git = "https://github.com/gakonst/ethers-rs", default-features = false } -ethers-solc = { git = "https://github.com/gakonst/ethers-rs", features = ["async", "svm-solc"]} -ethers-etherscan = { git = "https://github.com/gakonst/ethers-rs", default-features = false } -revm-primitives = { git = "https://github.com/bluealloy/revm/", rev = "429da731cd8efdc0939ed912240b2667b9155834" } - -# formats -Inflector = "0.11" -figment = { version = "0.10", features = ["toml", "env"] } -number_prefix = "0.4" -serde = { version = "1", features = ["derive"] } -serde_regex = "1" -serde_json = "1" -toml = { version = "0.7", features = ["preserve_order"] } -toml_edit = "0.19" - -# dirs -dirs-next = "2" -globset = "0.4" -walkdir = "2" - -# encoding -open-fastrlp = "0.1" - -# misc -eyre = "0.6" -regex = "1" -semver = { version = "1", features = ["serde"] } -tracing = "0.1" -once_cell = "1" -thiserror = "1" -reqwest = { version = "0.11", default-features = false } - -[target.'cfg(target_os = "windows")'.dependencies] -path-slash = "0.2.1" - -[dev-dependencies] -pretty_assertions = "1" -figment = { version = "0.10", features = ["test"] } -tempfile = "3" diff --git a/foundry-config/README.md b/foundry-config/README.md deleted file mode 100644 index 1913cadee..000000000 --- a/foundry-config/README.md +++ /dev/null @@ -1,302 +0,0 @@ -# Configuration - -Foundry's configuration system allows you to configure it's tools the way _you_ want while also providing with a -sensible set of defaults. - -## Profiles - -Configurations can be arbitrarily namespaced by profiles. Foundry's default config is also named `default`, but can -arbitrarily name and configure profiles as you like and set the `FOUNDRY_PROFILE` environment variable to the selected -profile's name. This results in foundry's tools (forge) preferring the values in the profile with the named that's set -in `FOUNDRY_PROFILE`. But all custom profiles inherit from the `default` profile. - -## foundry.toml - -Foundry's tools search for a `foundry.toml` or the filename in a `FOUNDRY_CONFIG` environment variable starting at the -current working directory. If it is not found, the parent directory, its parent directory, and so on are searched until -the file is found or the root is reached. But the typical location for the global `foundry.toml` would -be `~/.foundry/foundry.toml`, which is also checked. If the path set in `FOUNDRY_CONFIG` is absolute, no such search -takes place and the absolute path is used directly. - -In `foundry.toml` you can define multiple profiles, therefore the file is assumed to be _nested_, so each top-level key -declares a profile and its values configure the profile. - -The following is an example of what such a file might look like. This can also be obtained with `forge config` - -```toml -## defaults for _all_ profiles -[profile.default] -src = "src" -out = "out" -libs = ["lib"] -solc = "0.8.10" # to use a specific local solc install set the path as `solc = "/solc"` -eth-rpc-url = "https://mainnet.infura.io" - -## set only when the `hardhat` profile is selected -[profile.hardhat] -src = "contracts" -out = "artifacts" -libs = ["node_modules"] - -## set only when the `spells` profile is selected -[profile.spells] -## --snip-- more settings -``` - -## Default profile - -When determining the profile to use, `Config` considers the following sources in ascending priority order to read from -and merge, at the per-key level: - -1. [`Config::default()`], which provides default values for all parameters. -2. `foundry.toml` _or_ TOML file path in `FOUNDRY_CONFIG` environment variable. -3. `FOUNDRY_` or `DAPP_` prefixed environment variables. - -The selected profile is the value of the `FOUNDRY_PROFILE` environment variable, or if it is not set, "default". - -### All Options - -The following is a foundry.toml file with all configuration options set. See also [/config/src/lib.rs](/config/src/lib.rs) and [/cli/tests/it/config.rs](/cli/tests/it/config.rs). - -```toml -## defaults for _all_ profiles -[profile.default] -src = 'src' -test = 'test' -script = 'script' -out = 'out' -libs = ['lib'] -auto_detect_remappings = true # recursive auto-detection of remappings -remappings = [] -# list of libraries to link in the form of `::

`: `"src/MyLib.sol:MyLib:0x8De6DDbCd5053d32292AAA0D2105A32d108484a6"` -# the supports remappings -libraries = [] -cache = true -cache_path = 'cache' -broadcast = 'broadcast' -# additional solc allow paths -allow_paths = [] -# additional solc include paths -include_paths = [] -force = false -evm_version = 'shanghai' -gas_reports = ['*'] -gas_reports_ignore = [] -## Sets the concrete solc version to use, this overrides the `auto_detect_solc` value -# solc = '0.8.10' -auto_detect_solc = true -offline = false -optimizer = true -optimizer_runs = 200 -model_checker = { contracts = { 'a.sol' = [ - 'A1', - 'A2', -], 'b.sol' = [ - 'B1', - 'B2', -] }, engine = 'chc', targets = [ - 'assert', - 'outOfBounds', -], timeout = 10000 } -verbosity = 0 -eth_rpc_url = "https://example.com/" -# Setting this option enables decoding of error traces from mainnet deployed / verfied contracts via etherscan -etherscan_api_key = "YOURETHERSCANAPIKEY" -# ignore solc warnings for missing license and exceeded contract size -# known error codes are: ["unreachable", "unused-return", "unused-param", "unused-var", "code-size", "shadowing", "func-mutability", "license", "pragma-solidity", "virtual-interfaces", "same-varname"] -# additional warnings can be added using their numeric error code: ["license", 1337] -ignored_error_codes = ["license", "code-size"] -deny_warnings = false -match_test = "Foo" -no_match_test = "Bar" -match_contract = "Foo" -no_match_contract = "Bar" -match_path = "*/Foo*" -no_match_path = "*/Bar*" -ffi = false -# These are the default callers, generated using `address(uint160(uint256(keccak256("foundry default caller"))))` -sender = '0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38' -tx_origin = '0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38' -initial_balance = '0xffffffffffffffffffffffff' -block_number = 0 -fork_block_number = 0 -chain_id = 1 -# NOTE due to a toml-rs limitation, this value needs to be a string if the desired gas limit exceeds `i64::MAX` (9223372036854775807) -# `gas_limit = "Max"` is equivalent to `gas_limit = "18446744073709551615"` -gas_limit = 9223372036854775807 -gas_price = 0 -block_base_fee_per_gas = 0 -block_coinbase = '0x0000000000000000000000000000000000000000' -block_timestamp = 0 -block_difficulty = 0 -block_prevrandao = '0x0000000000000000000000000000000000000000' -block_gas_limit = 30000000 -memory_limit = 33554432 -extra_output = ["metadata"] -extra_output_files = [] -names = false -sizes = false -via_ir = false -# caches storage retrieved locally for certain chains and endpoints -# can also be restricted to `chains = ["optimism", "mainnet"]` -# by default all endpoints will be cached, alternative options are "remote" for only caching non localhost endpoints and "" -# to disable storage caching entirely set `no_storage_caching = true` -rpc_storage_caching = { chains = "all", endpoints = "all" } -# this overrides `rpc_storage_caching` entirely -no_storage_caching = false -# Whether to store the referenced sources in the metadata as literal data. -use_literal_content = false -# use ipfs method to generate the metadata hash, solc's default. -# To not include the metadata hash, to allow for deterministic code: https://docs.soliditylang.org/en/latest/metadata.html, use "none" -bytecode_hash = "ipfs" -# Whether to append the metadata hash to the bytecode -cbor_metadata = true -# How to treat revert (and require) reason strings. -# Possible values are: "default", "strip", "debug" and "verboseDebug". -# "default" does not inject compiler-generated revert strings and keeps user-supplied ones. -# "strip" removes all revert strings (if possible, i.e. if literals are used) keeping side-effects -# "debug" injects strings for compiler-generated internal reverts, implemented for ABI encoders V1 and V2 for now. -# "verboseDebug" even appends further information to user-supplied revert strings (not yet implemented) -revert_strings = "default" -# If this option is enabled, Solc is instructed to generate output (bytecode) only for the required contracts -# this can reduce compile time for `forge test` a bit but is considered experimental at this point. -sparse_mode = false -build_info = true -build_info_path = "build-info" -root = "root" -# Configures permissions for cheatcodes that touch the filesystem like `vm.writeFile` -# `access` restricts how the `path` can be accessed via cheatcodes -# `read-write` | `true` => `read` + `write` access allowed (`vm.readFile` + `vm.writeFile`) -# `none`| `false` => no access -# `read` => only read access (`vm.readFile`) -# `write` => only write access (`vm.writeFile`) -# The `allowed_paths` further lists the paths that are considered, e.g. `./` represents the project root directory -# By default, only read access is granted to the project's out dir, so generated artifacts can be read by default -# following example enables read-write access for the project dir : -# `fs_permissions = [{ access = "read-write", path = "./"}]` -fs_permissions = [{ access = "read", path = "./out"}] -[fuzz] -runs = 256 -max_test_rejects = 65536 -seed = '0x3e8' -dictionary_weight = 40 -include_storage = true -include_push_bytes = true - -[invariant] -runs = 256 -depth = 15 -fail_on_revert = false -call_override = false -dictionary_weight = 80 -include_storage = true -include_push_bytes = true -shrink_sequence = true - -[fmt] -line_length = 100 -tab_width = 2 -bracket_spacing = true -``` - -#### Additional Optimizer settings - -Optimizer components can be tweaked with the `OptimizerDetails` object: - -See [Compiler Input Description `settings.optimizer.details`](https://docs.soliditylang.org/en/latest/using-the-compiler.html#compiler-input-and-output-json-description) - -The `optimizer_details` (`optimizerDetails` also works) settings must be prefixed with the profile they correspond -to: `[profile.default.optimizer_details]` -belongs to the `[profile.default]` profile - -```toml -[profile.default.optimizer_details] -constantOptimizer = true -yul = true -# this sets the `yulDetails` of the `optimizer_details` for the `default` profile -[profile.default.optimizer_details.yulDetails] -stackAllocation = true -optimizerSteps = 'dhfoDgvulfnTUtnIf' -``` - -#### RPC-Endpoints settings - -The `rpc_endpoints` value accepts a list of `alias = ""` pairs. - -The following example declares two pairs: -The alias `optimism` references the endpoint URL directly. -The alias `mainnet` references the environment variable `RPC_MAINNET` which holds the entire URL. -The alias `goerli` references an endpoint that will be interpolated with the value the `GOERLI_API_KEY` holds. - -Environment variables need to be wrapped in `${}` - -```toml -[rpc_endpoints] -optimism = "https://optimism.alchemyapi.io/v2/1234567" -mainnet = "${RPC_MAINNET}" -goerli = "https://eth-goerli.alchemyapi.io/v2/${GOERLI_API_KEY}" -``` - -#### Etherscan API Key settings - -The `etherscan` value accepts a list of `alias = "{key = "", url? ="", chain?= """""}"` items. - -the `key` attribute is always required and should contain the actual API key for that chain or an env var that holds the key in the form `${ENV_VAR}` -The `chain` attribute is optional if the `alias` is the already the `chain` name, such as in `mainnet = { key = "${ETHERSCAN_MAINNET_KEY}"}` -The optional `url` attribute can be used to explicitly set the Etherscan API url, this is the recommended setting for chains not natively supported by name. - -```toml -[etherscan] -mainnet = { key = "${ETHERSCAN_MAINNET_KEY}" } -mainnet2 = { key = "ABCDEFG", chain = "mainnet" } -optimism = { key = "1234576" } -unknownchain = { key = "ABCDEFG", url = "https://" } -``` - -##### Additional Model Checker settings - -[Solidity's built-in model checker](https://docs.soliditylang.org/en/latest/smtchecker.html#tutorial) -is an opt-in module that can be enabled via the `ModelChecker` object. - -See [Compiler Input Description `settings.modelChecker`](https://docs.soliditylang.org/en/latest/using-the-compiler.html#compiler-input-and-output-json-description) -and [the model checker's options](https://docs.soliditylang.org/en/latest/smtchecker.html#smtchecker-options-and-tuning). - -The module is available in `solc` release binaries for OSX and Linux. -The latter requires the z3 library version [4.8.8, 4.8.14] to be installed -in the system (SO version 4.8). - -Similarly to the optimizer settings above, the `model_checker` settings must be -prefixed with the profile they correspond to: `[profile.default.model_checker]` belongs -to the `[profile.default]` profile. - -```toml -[profile.default.model_checker] -contracts = { 'src/Contract.sol' = [ 'Contract' ] } -engine = 'chc' -timeout = 10000 -targets = [ 'assert' ] -``` - -The fields above are recommended when using the model checker. -Setting which contract should be verified is extremely important, otherwise all -available contracts will be verified which can consume a lot of time. -The recommended engine is `chc`, but `bmc` and `all` (runs both) are also -accepted. -It is also important to set a proper timeout (given in milliseconds), since the -default time given to the underlying solvers may not be enough. -If no verification targets are given, only assertions will be checked. - -The model checker will run when `forge build` is invoked, and will show -findings as warnings if any. - -## Environment Variables - -Foundry's tools read all environment variable names prefixed with `FOUNDRY_` using the string after the `_` as the name -of a configuration value as the value of the parameter as the value itself. But the -corresponding [dapptools](https://github.com/dapphub/dapptools/tree/master/src/dapp#configuration) config vars are also -supported, this means that `FOUNDRY_SRC` and `DAPP_SRC` are equivalent. - -Some exceptions to the above are [explicitly ignored](https://github.com/foundry-rs/foundry/blob/10440422e63aae660104e079dfccd5b0ae5fd720/config/src/lib.rs#L1539-L15522) due to security concerns. - -Environment variables take precedence over values in `foundry.toml`. Values are parsed as loose form of TOML syntax. -Consider the following examples: diff --git a/foundry-config/src/cache.rs b/foundry-config/src/cache.rs deleted file mode 100644 index ecad2a814..000000000 --- a/foundry-config/src/cache.rs +++ /dev/null @@ -1,318 +0,0 @@ -//! Support types for configuring storage caching - -use crate::chain::Chain; -use number_prefix::NumberPrefix; -use serde::{Deserialize, Deserializer, Serialize, Serializer}; -use std::{fmt, fmt::Formatter, str::FromStr}; - -/// Settings to configure caching of remote -#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)] -pub struct StorageCachingConfig { - /// chains to cache - pub chains: CachedChains, - /// endpoints to cache - pub endpoints: CachedEndpoints, -} - -impl StorageCachingConfig { - /// Whether caching should be enabled for the endpoint - pub fn enable_for_endpoint(&self, endpoint: impl AsRef) -> bool { - self.endpoints.is_match(endpoint) - } - - /// Whether caching should be enabled for the chain id - pub fn enable_for_chain_id(&self, chain_id: u64) -> bool { - // ignore dev chains - if [99, 1337, 31337].contains(&chain_id) { - return false; - } - self.chains.is_match(chain_id) - } -} - -/// What chains to cache -#[derive(Debug, Clone, PartialEq, Eq, Default)] -pub enum CachedChains { - /// Cache all chains - #[default] - All, - /// Don't cache anything - None, - /// Only cache these chains - Chains(Vec), -} -impl CachedChains { - /// Whether the `endpoint` matches - pub fn is_match(&self, chain: u64) -> bool { - match self { - CachedChains::All => true, - CachedChains::None => false, - CachedChains::Chains(chains) => chains.iter().any(|c| c.id() == chain), - } - } -} - -impl Serialize for CachedChains { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - match self { - CachedChains::All => serializer.serialize_str("all"), - CachedChains::None => serializer.serialize_str("none"), - CachedChains::Chains(chains) => chains.serialize(serializer), - } - } -} - -impl<'de> Deserialize<'de> for CachedChains { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - #[derive(Deserialize)] - #[serde(untagged)] - enum Chains { - All(String), - Chains(Vec), - } - - match Chains::deserialize(deserializer)? { - Chains::All(s) => match s.as_str() { - "all" => Ok(CachedChains::All), - "none" => Ok(CachedChains::None), - s => Err(serde::de::Error::unknown_variant(s, &["all", "none"])), - }, - Chains::Chains(chains) => Ok(CachedChains::Chains(chains)), - } - } -} - -/// What endpoints to enable caching for -#[derive(Debug, Clone, Default)] -pub enum CachedEndpoints { - /// Cache all endpoints - #[default] - All, - /// Only cache non-local host endpoints - Remote, - /// Only cache these chains - Pattern(regex::Regex), -} - -impl CachedEndpoints { - /// Whether the `endpoint` matches - pub fn is_match(&self, endpoint: impl AsRef) -> bool { - let endpoint = endpoint.as_ref(); - match self { - CachedEndpoints::All => true, - CachedEndpoints::Remote => { - !endpoint.contains("localhost:") && !endpoint.contains("127.0.0.1:") - } - CachedEndpoints::Pattern(re) => re.is_match(endpoint), - } - } -} - -impl PartialEq for CachedEndpoints { - fn eq(&self, other: &Self) -> bool { - match (self, other) { - (CachedEndpoints::Pattern(a), CachedEndpoints::Pattern(b)) => a.as_str() == b.as_str(), - (&CachedEndpoints::All, &CachedEndpoints::All) => true, - (&CachedEndpoints::Remote, &CachedEndpoints::Remote) => true, - _ => false, - } - } -} - -impl Eq for CachedEndpoints {} - -impl fmt::Display for CachedEndpoints { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - CachedEndpoints::All => f.write_str("all"), - CachedEndpoints::Remote => f.write_str("remote"), - CachedEndpoints::Pattern(s) => s.fmt(f), - } - } -} - -impl FromStr for CachedEndpoints { - type Err = regex::Error; - - fn from_str(s: &str) -> Result { - match s { - "all" => Ok(CachedEndpoints::All), - "remote" => Ok(CachedEndpoints::Remote), - _ => Ok(CachedEndpoints::Pattern(s.parse()?)), - } - } -} - -impl<'de> Deserialize<'de> for CachedEndpoints { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - String::deserialize(deserializer)? - .parse() - .map_err(serde::de::Error::custom) - } -} - -impl Serialize for CachedEndpoints { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - match self { - CachedEndpoints::All => serializer.serialize_str("all"), - CachedEndpoints::Remote => serializer.serialize_str("remote"), - CachedEndpoints::Pattern(pattern) => serializer.serialize_str(pattern.as_str()), - } - } -} - -/// Content of the foundry cache folder -#[derive(Debug, Default)] -pub struct Cache { - /// The list of chains in the cache - pub chains: Vec, -} - -impl fmt::Display for Cache { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - for chain in &self.chains { - match NumberPrefix::decimal( - chain.block_explorer as f32 + chain.blocks.iter().map(|x| x.1).sum::() as f32, - ) { - NumberPrefix::Standalone(size) => { - writeln!(f, "-️ {} ({size:.1} B)", chain.name)?; - } - NumberPrefix::Prefixed(prefix, size) => { - writeln!(f, "-️ {} ({size:.1} {prefix}B)", chain.name)?; - } - } - match NumberPrefix::decimal(chain.block_explorer as f32) { - NumberPrefix::Standalone(size) => { - writeln!(f, "\t-️ Block Explorer ({size:.1} B)\n")?; - } - NumberPrefix::Prefixed(prefix, size) => { - writeln!(f, "\t-️ Block Explorer ({size:.1} {prefix}B)\n")?; - } - } - for block in &chain.blocks { - match NumberPrefix::decimal(block.1 as f32) { - NumberPrefix::Standalone(size) => { - writeln!(f, "\t-️ Block {} ({size:.1} B)", block.0)?; - } - NumberPrefix::Prefixed(prefix, size) => { - writeln!(f, "\t-️ Block {} ({size:.1} {prefix}B)", block.0)?; - } - } - } - } - Ok(()) - } -} - -/// A representation of data for a given chain in the foundry cache -#[derive(Debug)] -pub struct ChainCache { - /// The name of the chain - pub name: String, - - /// A tuple containing block number and the block directory size in bytes - pub blocks: Vec<(String, u64)>, - - /// The size of the block explorer directory in bytes - pub block_explorer: u64, -} - -#[cfg(test)] -mod tests { - use pretty_assertions::assert_str_eq; - - use super::*; - - #[test] - fn can_parse_storage_config() { - #[derive(Serialize, Deserialize)] - pub struct Wrapper { - pub rpc_storage_caching: StorageCachingConfig, - } - - let s = r#"rpc_storage_caching = { chains = "all", endpoints = "remote"}"#; - let w: Wrapper = toml::from_str(s).unwrap(); - - assert_eq!( - w.rpc_storage_caching, - StorageCachingConfig { - chains: CachedChains::All, - endpoints: CachedEndpoints::Remote - } - ); - - let s = r#"rpc_storage_caching = { chains = [1, "optimism", 999999], endpoints = "all"}"#; - let w: Wrapper = toml::from_str(s).unwrap(); - - assert_eq!( - w.rpc_storage_caching, - StorageCachingConfig { - chains: CachedChains::Chains(vec![ - Chain::Named(ethers_core::types::Chain::Mainnet), - Chain::Named(ethers_core::types::Chain::Optimism), - Chain::Id(999999) - ]), - endpoints: CachedEndpoints::All - } - ) - } - - #[test] - fn cache_to_string() { - let cache = Cache { - chains: vec![ - ChainCache { - name: "mainnet".to_string(), - blocks: vec![("1".to_string(), 1), ("2".to_string(), 2)], - block_explorer: 500, - }, - ChainCache { - name: "ropsten".to_string(), - blocks: vec![("1".to_string(), 1), ("2".to_string(), 2)], - block_explorer: 4567, - }, - ChainCache { - name: "rinkeby".to_string(), - blocks: vec![("1".to_string(), 1032), ("2".to_string(), 2000000)], - block_explorer: 4230000, - }, - ChainCache { - name: "mumbai".to_string(), - blocks: vec![("1".to_string(), 1), ("2".to_string(), 2)], - block_explorer: 0, - }, - ], - }; - - let expected = "\ - -️ mainnet (503.0 B)\n\t\ - -️ Block Explorer (500.0 B)\n\n\t\ - -️ Block 1 (1.0 B)\n\t\ - -️ Block 2 (2.0 B)\n\ - -️ ropsten (4.6 kB)\n\t\ - -️ Block Explorer (4.6 kB)\n\n\t\ - -️ Block 1 (1.0 B)\n\t\ - -️ Block 2 (2.0 B)\n\ - -️ rinkeby (6.2 MB)\n\t\ - -️ Block Explorer (4.2 MB)\n\n\t\ - -️ Block 1 (1.0 kB)\n\t\ - -️ Block 2 (2.0 MB)\n\ - -️ mumbai (3.0 B)\n\t\ - -️ Block Explorer (0.0 B)\n\n\t\ - -️ Block 1 (1.0 B)\n\t\ - -️ Block 2 (2.0 B)\n"; - assert_str_eq!(format!("{cache}"), expected); - } -} diff --git a/foundry-config/src/chain.rs b/foundry-config/src/chain.rs deleted file mode 100644 index 38eacecc3..000000000 --- a/foundry-config/src/chain.rs +++ /dev/null @@ -1,181 +0,0 @@ -use crate::U256; -use ethers_core::types::{Chain as NamedChain, U64}; -use eyre::Result; -use open_fastrlp::{Decodable, Encodable}; -use serde::{Deserialize, Deserializer, Serialize}; -use std::{fmt, str::FromStr}; - -/// Either a named or chain id or the actual id value -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize)] -#[serde(untagged)] -pub enum Chain { - /// Contains a known chain - #[serde(serialize_with = "super::from_str_lowercase::serialize")] - Named(NamedChain), - /// Contains the id of a chain - Id(u64), -} - -impl Chain { - /// The id of the chain. - pub const fn id(&self) -> u64 { - match self { - Chain::Named(chain) => *chain as u64, - Chain::Id(id) => *id, - } - } - - /// Returns the wrapped named chain or tries converting the ID into one. - pub fn named(&self) -> Result { - match self { - Self::Named(chain) => Ok(*chain), - Self::Id(id) => { - NamedChain::try_from(*id).map_err(|_| eyre::eyre!("Unsupported chain: {id}")) - } - } - } - - /// Helper function for checking if a chainid corresponds to a legacy chainid - /// without eip1559 - pub fn is_legacy(&self) -> bool { - self.named().map_or(false, |c| c.is_legacy()) - } - - /// Returns the corresponding etherscan URLs - pub fn etherscan_urls(&self) -> Option<(&'static str, &'static str)> { - self.named().ok().and_then(|c| c.etherscan_urls()) - } -} - -impl fmt::Display for Chain { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Chain::Named(chain) => chain.fmt(f), - Chain::Id(id) => { - if let Ok(chain) = NamedChain::try_from(*id) { - chain.fmt(f) - } else { - id.fmt(f) - } - } - } - } -} - -impl From for Chain { - fn from(id: NamedChain) -> Self { - Chain::Named(id) - } -} - -impl From for Chain { - fn from(id: u64) -> Self { - NamedChain::try_from(id) - .map(Chain::Named) - .unwrap_or_else(|_| Chain::Id(id)) - } -} - -impl From for Chain { - fn from(id: U256) -> Self { - id.as_u64().into() - } -} - -impl From for u64 { - fn from(c: Chain) -> Self { - match c { - Chain::Named(c) => c as u64, - Chain::Id(id) => id, - } - } -} - -impl From for U64 { - fn from(c: Chain) -> Self { - u64::from(c).into() - } -} - -impl From for U256 { - fn from(c: Chain) -> Self { - u64::from(c).into() - } -} - -impl TryFrom for NamedChain { - type Error = >::Error; - - fn try_from(chain: Chain) -> Result { - match chain { - Chain::Named(chain) => Ok(chain), - Chain::Id(id) => id.try_into(), - } - } -} - -impl FromStr for Chain { - type Err = String; - - fn from_str(s: &str) -> Result { - if let Ok(chain) = NamedChain::from_str(s) { - Ok(Chain::Named(chain)) - } else { - s.parse::() - .map(Chain::Id) - .map_err(|_| format!("Expected known chain or integer, found: {s}")) - } - } -} - -impl<'de> Deserialize<'de> for Chain { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - #[derive(Deserialize)] - #[serde(untagged)] - enum ChainId { - Named(String), - Id(u64), - } - - match ChainId::deserialize(deserializer)? { - ChainId::Named(s) => s - .to_lowercase() - .parse() - .map(Chain::Named) - .map_err(serde::de::Error::custom), - ChainId::Id(id) => Ok(NamedChain::try_from(id) - .map(Chain::Named) - .unwrap_or_else(|_| Chain::Id(id))), - } - } -} - -impl Encodable for Chain { - fn length(&self) -> usize { - match self { - Self::Named(chain) => u64::from(*chain).length(), - Self::Id(id) => id.length(), - } - } - fn encode(&self, out: &mut dyn open_fastrlp::BufMut) { - match self { - Self::Named(chain) => u64::from(*chain).encode(out), - Self::Id(id) => id.encode(out), - } - } -} - -impl Decodable for Chain { - fn decode(buf: &mut &[u8]) -> Result { - Ok(u64::decode(buf)?.into()) - } -} - -impl Default for Chain { - fn default() -> Self { - NamedChain::Mainnet.into() - } -} diff --git a/foundry-config/src/doc.rs b/foundry-config/src/doc.rs deleted file mode 100644 index 2dfac01b4..000000000 --- a/foundry-config/src/doc.rs +++ /dev/null @@ -1,38 +0,0 @@ -//! Configuration specific to the `forge doc` command and the `forge_doc` package - -use serde::{Deserialize, Serialize}; -use std::path::PathBuf; - -/// Contains the config for parsing and rendering docs -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct DocConfig { - /// Doc output path. - pub out: PathBuf, - /// The documentation title. - pub title: String, - /// Path to user provided `book.toml`. - pub book: PathBuf, - /// Path to user provided welcome markdown. - /// - /// If none is provided, it defaults to `README.md`. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub homepage: Option, - /// The repository url. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub repository: Option, - /// Globs to ignore - pub ignore: Vec, -} - -impl Default for DocConfig { - fn default() -> Self { - Self { - out: PathBuf::from("docs"), - book: PathBuf::from("book.toml"), - homepage: Some(PathBuf::from("README.md")), - title: String::default(), - repository: None, - ignore: Vec::default(), - } - } -} diff --git a/foundry-config/src/endpoints.rs b/foundry-config/src/endpoints.rs deleted file mode 100644 index 8fb346e56..000000000 --- a/foundry-config/src/endpoints.rs +++ /dev/null @@ -1,175 +0,0 @@ -//! Support for multiple RPC-endpoints - -use crate::resolve::{interpolate, UnresolvedEnvVarError, RE_PLACEHOLDER}; -use serde::{Deserialize, Deserializer, Serialize, Serializer}; -use std::{ - collections::BTreeMap, - fmt, - ops::{Deref, DerefMut}, -}; - -/// Container type for API endpoints, like various RPC endpoints -#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)] -#[serde(transparent)] -pub struct RpcEndpoints { - endpoints: BTreeMap, -} - -// === impl RpcEndpoints === - -impl RpcEndpoints { - /// Creates a new list of endpoints - pub fn new(endpoints: impl IntoIterator, RpcEndpoint)>) -> Self { - Self { - endpoints: endpoints - .into_iter() - .map(|(name, url)| (name.into(), url)) - .collect(), - } - } - - /// Returns `true` if this type doesn't contain any endpoints - pub fn is_empty(&self) -> bool { - self.endpoints.is_empty() - } - - /// Returns all (alias -> url) pairs - pub fn resolved(self) -> ResolvedRpcEndpoints { - ResolvedRpcEndpoints { - endpoints: self - .endpoints - .into_iter() - .map(|(name, e)| (name, e.resolve())) - .collect(), - } - } -} - -impl Deref for RpcEndpoints { - type Target = BTreeMap; - - fn deref(&self) -> &Self::Target { - &self.endpoints - } -} - -/// Represents a single endpoint -/// -/// This type preserves the value as it's stored in the config. If the value is a reference to an -/// env var, then the `Endpoint::Env` var will hold the reference (`${MAIN_NET}`) and _not_ the -/// value of the env var itself. -/// In other words, this type does not resolve env vars when it's being deserialized -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum RpcEndpoint { - /// A raw Url (ws, http) - Url(String), - /// An endpoint that contains at least one `${ENV_VAR}` placeholder - /// - /// **Note:** this contains the endpoint as is, like `https://eth-mainnet.alchemyapi.io/v2/${API_KEY}` or `${EPC_ENV_VAR}` - Env(String), -} - -// === impl RpcEndpoint === - -impl RpcEndpoint { - /// Returns the url variant - pub fn as_url(&self) -> Option<&str> { - match self { - RpcEndpoint::Url(url) => Some(url), - RpcEndpoint::Env(_) => None, - } - } - - /// Returns the env variant - pub fn as_env(&self) -> Option<&str> { - match self { - RpcEndpoint::Env(val) => Some(val), - RpcEndpoint::Url(_) => None, - } - } - - /// Returns the url this type holds - /// - /// # Error - /// - /// Returns an error if the type holds a reference to an env var and the env var is not set - pub fn resolve(self) -> Result { - match self { - RpcEndpoint::Url(url) => Ok(url), - RpcEndpoint::Env(val) => interpolate(&val), - } - } -} - -impl fmt::Display for RpcEndpoint { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - RpcEndpoint::Url(url) => url.fmt(f), - RpcEndpoint::Env(var) => var.fmt(f), - } - } -} - -impl TryFrom for String { - type Error = UnresolvedEnvVarError; - - fn try_from(value: RpcEndpoint) -> Result { - value.resolve() - } -} - -impl Serialize for RpcEndpoint { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - serializer.serialize_str(&self.to_string()) - } -} - -impl<'de> Deserialize<'de> for RpcEndpoint { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let val = String::deserialize(deserializer)?; - let endpoint = if RE_PLACEHOLDER.is_match(&val) { - RpcEndpoint::Env(val) - } else { - RpcEndpoint::Url(val) - }; - - Ok(endpoint) - } -} - -/// Container type for _resolved_ endpoints, see [RpcEndpoints::resolve_all()] -#[derive(Debug, Clone, PartialEq, Eq, Default)] -pub struct ResolvedRpcEndpoints { - /// contains all named endpoints and their URL or an error if we failed to resolve the env var - /// alias - endpoints: BTreeMap>, -} - -// === impl ResolvedEndpoints === - -impl ResolvedRpcEndpoints { - /// Returns true if there's an endpoint that couldn't be resolved - pub fn has_unresolved(&self) -> bool { - self.endpoints.values().any(|val| val.is_err()) - } -} - -impl Deref for ResolvedRpcEndpoints { - type Target = BTreeMap>; - - fn deref(&self) -> &Self::Target { - &self.endpoints - } -} - -impl DerefMut for ResolvedRpcEndpoints { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.endpoints - } -} diff --git a/foundry-config/src/error.rs b/foundry-config/src/error.rs deleted file mode 100644 index 207315153..000000000 --- a/foundry-config/src/error.rs +++ /dev/null @@ -1,274 +0,0 @@ -//! error handling and solc error codes -use figment::providers::{Format, Toml}; -use serde::{Deserialize, Deserializer, Serialize, Serializer}; -use std::{collections::HashSet, error::Error, fmt, str::FromStr}; - -/// The message shown upon panic if the config could not be extracted from the figment -pub const FAILED_TO_EXTRACT_CONFIG_PANIC_MSG: &str = "failed to extract foundry config:"; - -/// Represents a failed attempt to extract `Config` from a `Figment` -#[derive(Clone, Debug, PartialEq)] -pub struct ExtractConfigError { - /// error thrown when extracting the `Config` - pub(crate) error: figment::Error, -} - -impl ExtractConfigError { - /// Wraps the figment error - pub fn new(error: figment::Error) -> Self { - Self { error } - } -} - -impl fmt::Display for ExtractConfigError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let mut unique_errors = Vec::with_capacity(self.error.count()); - let mut unique = HashSet::with_capacity(self.error.count()); - for err in self.error.clone().into_iter() { - let err = if err - .metadata - .as_ref() - .map(|meta| meta.name.contains(Toml::NAME)) - .unwrap_or_default() - { - FoundryConfigError::Toml(err) - } else { - FoundryConfigError::Other(err) - }; - - if unique.insert(err.to_string()) { - unique_errors.push(err); - } - } - writeln!(f, "{FAILED_TO_EXTRACT_CONFIG_PANIC_MSG}")?; - for err in unique_errors { - writeln!(f, "{err}")?; - } - Ok(()) - } -} - -impl Error for ExtractConfigError { - fn source(&self) -> Option<&(dyn Error + 'static)> { - Error::source(&self.error) - } -} - -/// Represents an error that can occur when constructing the `Config` -#[derive(Debug, Clone, PartialEq)] -pub enum FoundryConfigError { - /// An error thrown during toml parsing - Toml(figment::Error), - /// Any other error thrown when constructing the config's figment - Other(figment::Error), -} - -impl fmt::Display for FoundryConfigError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let fmt_err = |err: &figment::Error, f: &mut fmt::Formatter<'_>| { - write!(f, "{err}")?; - if !err.path.is_empty() { - // the path will contain the setting value like `["etherscan_api_key"]` - write!(f, " for setting `{}`", err.path.join("."))?; - } - Ok(()) - }; - - match self { - FoundryConfigError::Toml(err) => { - f.write_str("foundry.toml error: ")?; - fmt_err(err, f) - } - FoundryConfigError::Other(err) => { - f.write_str("foundry config error: ")?; - fmt_err(err, f) - } - } - } -} - -impl Error for FoundryConfigError { - fn source(&self) -> Option<&(dyn Error + 'static)> { - match self { - FoundryConfigError::Other(error) | FoundryConfigError::Toml(error) => { - Error::source(error) - } - } - } -} - -/// A non-exhaustive list of solidity error codes -#[derive(Debug, Clone, Copy, Eq, PartialEq)] -pub enum SolidityErrorCode { - /// Warning that SPDX license identifier not provided in source file - SpdxLicenseNotProvided, - /// Warning that contract code size exceeds 24576 bytes (a limit introduced in Spurious - /// Dragon). - ContractExceeds24576Bytes, - /// Warning after shanghai if init code size exceeds 49152 bytes - ContractInitCodeSizeExceeds49152Bytes, - /// Warning that Function state mutability can be restricted to [view,pure] - FunctionStateMutabilityCanBeRestricted, - /// Warning: Unused local variable - UnusedLocalVariable, - /// Warning: Unused function parameter. Remove or comment out the variable name to silence this - /// warning. - UnusedFunctionParameter, - /// Warning: Return value of low-level calls not used. - ReturnValueOfCallsNotUsed, - /// Warning: Interface functions are implicitly "virtual" - InterfacesExplicitlyVirtual, - /// Warning: This contract has a payable fallback function, but no receive ether function. - /// Consider adding a receive ether function. - PayableNoReceiveEther, - /// Warning: This declaration shadows an existing declaration. - ShadowsExistingDeclaration, - /// This declaration has the same name as another declaration. - DeclarationSameNameAsAnother, - /// Unnamed return variable can remain unassigned - UnnamedReturnVariable, - /// Unreachable code - Unreachable, - /// Missing pragma solidity - PragmaSolidity, - /// All other error codes - Other(u64), -} - -// === impl SolidityErrorCode === - -impl SolidityErrorCode { - /// The textual identifier for this error - /// - /// Returns `Err(code)` if unknown error - pub fn as_str(&self) -> Result<&'static str, u64> { - let s = match self { - SolidityErrorCode::SpdxLicenseNotProvided => "license", - SolidityErrorCode::ContractExceeds24576Bytes => "code-size", - SolidityErrorCode::ContractInitCodeSizeExceeds49152Bytes => "init-code-size", - SolidityErrorCode::FunctionStateMutabilityCanBeRestricted => "func-mutability", - SolidityErrorCode::UnusedLocalVariable => "unused-var", - SolidityErrorCode::UnusedFunctionParameter => "unused-param", - SolidityErrorCode::ReturnValueOfCallsNotUsed => "unused-return", - SolidityErrorCode::InterfacesExplicitlyVirtual => "virtual-interfaces", - SolidityErrorCode::PayableNoReceiveEther => "missing-receive-ether", - SolidityErrorCode::ShadowsExistingDeclaration => "shadowing", - SolidityErrorCode::DeclarationSameNameAsAnother => "same-varname", - SolidityErrorCode::UnnamedReturnVariable => "unnamed-return", - SolidityErrorCode::Unreachable => "unreachable", - SolidityErrorCode::PragmaSolidity => "pragma-solidity", - SolidityErrorCode::Other(code) => return Err(*code), - }; - Ok(s) - } -} - -impl From for u64 { - fn from(code: SolidityErrorCode) -> u64 { - match code { - SolidityErrorCode::SpdxLicenseNotProvided => 1878, - SolidityErrorCode::ContractExceeds24576Bytes => 5574, - SolidityErrorCode::FunctionStateMutabilityCanBeRestricted => 2018, - SolidityErrorCode::UnusedLocalVariable => 2072, - SolidityErrorCode::UnusedFunctionParameter => 5667, - SolidityErrorCode::ReturnValueOfCallsNotUsed => 9302, - SolidityErrorCode::InterfacesExplicitlyVirtual => 5815, - SolidityErrorCode::PayableNoReceiveEther => 3628, - SolidityErrorCode::ShadowsExistingDeclaration => 2519, - SolidityErrorCode::DeclarationSameNameAsAnother => 8760, - SolidityErrorCode::UnnamedReturnVariable => 6321, - SolidityErrorCode::Unreachable => 5740, - SolidityErrorCode::PragmaSolidity => 3420, - SolidityErrorCode::ContractInitCodeSizeExceeds49152Bytes => 3860, - SolidityErrorCode::Other(code) => code, - } - } -} - -impl fmt::Display for SolidityErrorCode { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self.as_str() { - Ok(name) => name.fmt(f), - Err(code) => code.fmt(f), - } - } -} - -impl FromStr for SolidityErrorCode { - type Err = String; - - fn from_str(s: &str) -> Result { - let code = match s { - "unreachable" => SolidityErrorCode::Unreachable, - "unused-return" => SolidityErrorCode::UnnamedReturnVariable, - "unused-param" => SolidityErrorCode::UnusedFunctionParameter, - "unused-var" => SolidityErrorCode::UnusedLocalVariable, - "code-size" => SolidityErrorCode::ContractExceeds24576Bytes, - "init-code-size" => SolidityErrorCode::ContractInitCodeSizeExceeds49152Bytes, - "shadowing" => SolidityErrorCode::ShadowsExistingDeclaration, - "func-mutability" => SolidityErrorCode::FunctionStateMutabilityCanBeRestricted, - "license" => SolidityErrorCode::SpdxLicenseNotProvided, - "pragma-solidity" => SolidityErrorCode::PragmaSolidity, - "virtual-interfaces" => SolidityErrorCode::InterfacesExplicitlyVirtual, - "missing-receive-ether" => SolidityErrorCode::PayableNoReceiveEther, - "same-varname" => SolidityErrorCode::DeclarationSameNameAsAnother, - _ => return Err(format!("Unknown variant {s}")), - }; - - Ok(code) - } -} - -impl From for SolidityErrorCode { - fn from(code: u64) -> Self { - match code { - 1878 => SolidityErrorCode::SpdxLicenseNotProvided, - 5574 => SolidityErrorCode::ContractExceeds24576Bytes, - 3860 => SolidityErrorCode::ContractInitCodeSizeExceeds49152Bytes, - 2018 => SolidityErrorCode::FunctionStateMutabilityCanBeRestricted, - 2072 => SolidityErrorCode::UnusedLocalVariable, - 5667 => SolidityErrorCode::UnusedFunctionParameter, - 9302 => SolidityErrorCode::ReturnValueOfCallsNotUsed, - 5815 => SolidityErrorCode::InterfacesExplicitlyVirtual, - 3628 => SolidityErrorCode::PayableNoReceiveEther, - 2519 => SolidityErrorCode::ShadowsExistingDeclaration, - 8760 => SolidityErrorCode::DeclarationSameNameAsAnother, - 6321 => SolidityErrorCode::UnnamedReturnVariable, - 3420 => SolidityErrorCode::PragmaSolidity, - 5740 => SolidityErrorCode::Unreachable, - other => SolidityErrorCode::Other(other), - } - } -} - -impl Serialize for SolidityErrorCode { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - match self.as_str() { - Ok(alias) => serializer.serialize_str(alias), - Err(code) => serializer.serialize_u64(code), - } - } -} - -impl<'de> Deserialize<'de> for SolidityErrorCode { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - /// Helper deserializer for error codes as names and codes - #[derive(Deserialize)] - #[serde(untagged)] - enum SolCode { - Name(String), - Code(u64), - } - - match SolCode::deserialize(deserializer)? { - SolCode::Code(code) => Ok(code.into()), - SolCode::Name(name) => name.parse().map_err(serde::de::Error::custom), - } - } -} diff --git a/foundry-config/src/etherscan.rs b/foundry-config/src/etherscan.rs deleted file mode 100644 index 3b9d2b5f3..000000000 --- a/foundry-config/src/etherscan.rs +++ /dev/null @@ -1,471 +0,0 @@ -//! Support for multiple etherscan keys -use crate::{ - resolve::{interpolate, UnresolvedEnvVarError, RE_PLACEHOLDER}, - Chain, Config, -}; -use serde::{Deserialize, Deserializer, Serialize, Serializer}; -use std::{ - collections::BTreeMap, - fmt, - ops::{Deref, DerefMut}, - time::Duration, -}; -use tracing::warn; - -/// The user agent to use when querying the etherscan API. -pub const ETHERSCAN_USER_AGENT: &str = concat!("foundry/", env!("CARGO_PKG_VERSION")); - -/// Errors that can occur when creating an `EtherscanConfig` -#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)] -pub enum EtherscanConfigError { - #[error(transparent)] - Unresolved(#[from] UnresolvedEnvVarError), - - #[error("No known Etherscan API URL for config{0} with chain `{1}`. Please specify a `url`")] - UnknownChain(String, Chain), - - #[error("At least one of `url` or `chain` must be present{0}")] - MissingUrlOrChain(String), -} - -/// Container type for Etherscan API keys and URLs. -#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -#[serde(transparent)] -pub struct EtherscanConfigs { - configs: BTreeMap, -} - -// === impl Endpoints === - -impl EtherscanConfigs { - /// Creates a new list of etherscan configs - pub fn new(configs: impl IntoIterator, EtherscanConfig)>) -> Self { - Self { - configs: configs - .into_iter() - .map(|(name, config)| (name.into(), config)) - .collect(), - } - } - - /// Returns `true` if this type doesn't contain any configs - pub fn is_empty(&self) -> bool { - self.configs.is_empty() - } - - /// Returns the first config that matches the chain - pub fn find_chain(&self, chain: Chain) -> Option<&EtherscanConfig> { - self.configs - .values() - .find(|config| config.chain == Some(chain)) - } - - /// Returns all (alias -> url) pairs - pub fn resolved(self) -> ResolvedEtherscanConfigs { - ResolvedEtherscanConfigs { - configs: self - .configs - .into_iter() - .map(|(name, e)| { - let resolved = e.resolve(Some(&name)); - (name, resolved) - }) - .collect(), - } - } -} - -impl Deref for EtherscanConfigs { - type Target = BTreeMap; - - fn deref(&self) -> &Self::Target { - &self.configs - } -} - -impl DerefMut for EtherscanConfigs { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.configs - } -} - -/// Container type for _resolved_ etherscan keys, see [EtherscanConfigs::resolve_all()] -#[derive(Debug, Clone, PartialEq, Eq, Default)] -pub struct ResolvedEtherscanConfigs { - /// contains all named `ResolvedEtherscanConfig` or an error if we failed to resolve the env - /// var alias - configs: BTreeMap>, -} - -// === impl ResolvedEtherscanConfigs === - -impl ResolvedEtherscanConfigs { - /// Creates a new list of resolved etherscan configs - pub fn new( - configs: impl IntoIterator, ResolvedEtherscanConfig)>, - ) -> Self { - Self { - configs: configs - .into_iter() - .map(|(name, config)| (name.into(), Ok(config))) - .collect(), - } - } - - /// Returns the first config that matches the chain - pub fn find_chain( - self, - chain: Chain, - ) -> Option> { - for (_, config) in self.configs.into_iter() { - match config { - Ok(c) if c.chain == Some(chain) => return Some(Ok(c)), - Err(e) => return Some(Err(e)), - _ => continue, - } - } - None - } - - /// Returns true if there's a config that couldn't be resolved - pub fn has_unresolved(&self) -> bool { - self.configs.values().any(|val| val.is_err()) - } -} - -impl Deref for ResolvedEtherscanConfigs { - type Target = BTreeMap>; - - fn deref(&self) -> &Self::Target { - &self.configs - } -} - -impl DerefMut for ResolvedEtherscanConfigs { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.configs - } -} - -/// Represents all info required to create an etherscan client -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct EtherscanConfig { - /// Chain name/id that can be used to derive the api url - #[serde(default, skip_serializing_if = "Option::is_none")] - pub chain: Option, - /// Etherscan API URL - #[serde(default, skip_serializing_if = "Option::is_none")] - pub url: Option, - /// The etherscan API KEY that's required to make requests - pub key: EtherscanApiKey, -} - -// === impl EtherscanConfig === - -impl EtherscanConfig { - /// Returns the etherscan config required to create a client. - /// - /// # Errors - /// - /// Returns an error if the type holds a reference to an env var and the env var is not set or - /// no chain or url is configured - pub fn resolve( - self, - alias: Option<&str>, - ) -> Result { - let EtherscanConfig { - chain, - mut url, - key, - } = self; - - if let Some(url) = &mut url { - *url = interpolate(url)?; - } - - let (chain, alias) = match (chain, alias) { - // fill one with the other - (Some(chain), None) => (Some(chain), Some(chain.to_string())), - (None, Some(alias)) => (alias.parse().ok(), Some(alias.into())), - // leave as is - (Some(chain), Some(alias)) => (Some(chain), Some(alias.into())), - (None, None) => (None, None), - }; - let key = key.resolve()?; - - match (chain, url) { - (Some(chain), Some(api_url)) => Ok(ResolvedEtherscanConfig { - api_url, - browser_url: chain.etherscan_urls().map(|(_, url)| url.to_string()), - key, - chain: Some(chain), - }), - (Some(chain), None) => ResolvedEtherscanConfig::create(key, chain).ok_or_else(|| { - let msg = alias.map(|a| format!(" `{a}`")).unwrap_or_default(); - EtherscanConfigError::UnknownChain(msg, chain) - }), - (None, Some(api_url)) => Ok(ResolvedEtherscanConfig { - api_url, - browser_url: None, - key, - chain: None, - }), - (None, None) => { - let msg = alias - .map(|a| format!(" for Etherscan config `{a}`")) - .unwrap_or_default(); - Err(EtherscanConfigError::MissingUrlOrChain(msg)) - } - } - } -} - -/// Contains required url + api key to set up an etherscan client -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct ResolvedEtherscanConfig { - /// Etherscan API URL - #[serde(rename = "url")] - pub api_url: String, - /// Optional browser url - #[serde(default, skip_serializing_if = "Option::is_none")] - pub browser_url: Option, - /// Resolved api key - pub key: String, - /// The chain if set - #[serde(default, skip_serializing_if = "Option::is_none")] - pub chain: Option, -} - -// === impl ResolvedEtherscanConfig === - -impl ResolvedEtherscanConfig { - /// Creates a new instance using the api key and chain - pub fn create(api_key: impl Into, chain: impl Into) -> Option { - let chain = chain.into(); - let (api_url, browser_url) = chain.etherscan_urls()?; - Some(Self { - api_url: api_url.to_string(), - browser_url: Some(browser_url.to_string()), - key: api_key.into(), - chain: Some(chain), - }) - } - - /// Sets the chain value and consumes the type - /// - /// This is only used to set derive the appropriate Cache path for the etherscan client - pub fn with_chain(mut self, chain: impl Into) -> Self { - self.set_chain(chain); - self - } - - /// Sets the chain value - pub fn set_chain(&mut self, chain: impl Into) -> &mut Self { - let chain = chain.into(); - if let Some((api, browser)) = chain.etherscan_urls() { - self.api_url = api.to_string(); - self.browser_url = Some(browser.to_string()); - } - self.chain = Some(chain); - self - } - - /// Returns the corresponding `ethers_etherscan::Client`, configured with the `api_url`, - /// `api_key` and cache - pub fn into_client( - self, - ) -> Result { - let ResolvedEtherscanConfig { - api_url, - browser_url, - key: api_key, - chain, - } = self; - let (mainnet_api, mainnet_url) = ethers_core::types::Chain::Mainnet - .etherscan_urls() - .expect("exist; qed"); - - let cache = chain - .or_else(|| { - if api_url == mainnet_api { - // try to match against mainnet, which is usually the most common target - Some(ethers_core::types::Chain::Mainnet.into()) - } else { - None - } - }) - .and_then(Config::foundry_etherscan_chain_cache_dir); - - if let Some(ref cache_path) = cache { - // we also create the `sources` sub dir here - if let Err(err) = std::fs::create_dir_all(cache_path.join("sources")) { - warn!("could not create etherscan cache dir: {:?}", err); - } - } - - ethers_etherscan::Client::builder() - .with_client( - reqwest::Client::builder() - .user_agent(ETHERSCAN_USER_AGENT) - .build()?, - ) - .with_api_key(api_key) - .with_api_url(api_url.as_str())? - .with_url( - // the browser url is not used/required by the client so we can simply set the - // mainnet browser url here - browser_url.as_deref().unwrap_or(mainnet_url), - )? - .with_cache(cache, Duration::from_secs(24 * 60 * 60)) - .build() - } -} - -/// Represents a single etherscan API key -/// -/// This type preserves the value as it's stored in the config. If the value is a reference to an -/// env var, then the `EtherscanKey::Key` var will hold the reference (`${MAIN_NET}`) and _not_ the -/// value of the env var itself. -/// In other words, this type does not resolve env vars when it's being deserialized -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum EtherscanApiKey { - /// A raw key - Key(String), - /// An endpoint that contains at least one `${ENV_VAR}` placeholder - /// - /// **Note:** this contains the key or `${ETHERSCAN_KEY}` - Env(String), -} - -// === impl EtherscanApiKey === - -impl EtherscanApiKey { - /// Returns the key variant - pub fn as_key(&self) -> Option<&str> { - match self { - EtherscanApiKey::Key(url) => Some(url), - EtherscanApiKey::Env(_) => None, - } - } - - /// Returns the env variant - pub fn as_env(&self) -> Option<&str> { - match self { - EtherscanApiKey::Env(val) => Some(val), - EtherscanApiKey::Key(_) => None, - } - } - - /// Returns the key this type holds - /// - /// # Error - /// - /// Returns an error if the type holds a reference to an env var and the env var is not set - pub fn resolve(self) -> Result { - match self { - EtherscanApiKey::Key(key) => Ok(key), - EtherscanApiKey::Env(val) => interpolate(&val), - } - } -} - -impl Serialize for EtherscanApiKey { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - serializer.serialize_str(&self.to_string()) - } -} - -impl<'de> Deserialize<'de> for EtherscanApiKey { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let val = String::deserialize(deserializer)?; - let endpoint = if RE_PLACEHOLDER.is_match(&val) { - EtherscanApiKey::Env(val) - } else { - EtherscanApiKey::Key(val) - }; - - Ok(endpoint) - } -} - -impl fmt::Display for EtherscanApiKey { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - EtherscanApiKey::Key(key) => key.fmt(f), - EtherscanApiKey::Env(var) => var.fmt(f), - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use ethers_core::types::Chain::Mainnet; - - #[test] - fn can_create_client_via_chain() { - let mut configs = EtherscanConfigs::default(); - configs.insert( - "mainnet".to_string(), - EtherscanConfig { - chain: Some(Mainnet.into()), - url: None, - key: EtherscanApiKey::Key("ABCDEFG".to_string()), - }, - ); - - let mut resolved = configs.resolved(); - let config = resolved.remove("mainnet").unwrap().unwrap(); - let _ = config.into_client().unwrap(); - } - - #[test] - fn can_create_client_via_url_and_chain() { - let mut configs = EtherscanConfigs::default(); - configs.insert( - "mainnet".to_string(), - EtherscanConfig { - chain: Some(Mainnet.into()), - url: Some("https://api.etherscan.io/api".to_string()), - key: EtherscanApiKey::Key("ABCDEFG".to_string()), - }, - ); - - let mut resolved = configs.resolved(); - let config = resolved.remove("mainnet").unwrap().unwrap(); - let _ = config.into_client().unwrap(); - } - - #[test] - fn can_create_client_via_url_and_chain_env_var() { - let mut configs = EtherscanConfigs::default(); - let env = "_CONFIG_ETHERSCAN_API_KEY"; - configs.insert( - "mainnet".to_string(), - EtherscanConfig { - chain: Some(Mainnet.into()), - url: Some("https://api.etherscan.io/api".to_string()), - key: EtherscanApiKey::Env(format!("${{{env}}}")), - }, - ); - - let mut resolved = configs.clone().resolved(); - let config = resolved.remove("mainnet").unwrap(); - assert!(config.is_err()); - - std::env::set_var(env, "ABCDEFG"); - - let mut resolved = configs.resolved(); - let config = resolved.remove("mainnet").unwrap().unwrap(); - assert_eq!(config.key, "ABCDEFG"); - let _ = config.into_client().unwrap(); - - std::env::remove_var(env); - } -} diff --git a/foundry-config/src/fix.rs b/foundry-config/src/fix.rs deleted file mode 100644 index ead6d521b..000000000 --- a/foundry-config/src/fix.rs +++ /dev/null @@ -1,344 +0,0 @@ -//! Helpers to automatically fix configuration warnings - -use crate::{Config, Warning}; -use figment::providers::Env; -use std::{ - fs, io, - ops::{Deref, DerefMut}, - path::{Path, PathBuf}, -}; - -/// A convenience wrapper around a TOML document and the path it was read from -struct TomlFile { - doc: toml_edit::Document, - path: PathBuf, -} - -impl TomlFile { - fn open(path: impl AsRef) -> Result> { - let path = path.as_ref().to_owned(); - let doc = fs::read_to_string(&path)?.parse()?; - Ok(Self { doc, path }) - } - fn doc(&self) -> &toml_edit::Document { - &self.doc - } - fn doc_mut(&mut self) -> &mut toml_edit::Document { - &mut self.doc - } - fn path(&self) -> &Path { - self.path.as_ref() - } - fn save(&self) -> io::Result<()> { - fs::write(self.path(), self.doc().to_string()) - } -} - -impl Deref for TomlFile { - type Target = toml_edit::Document; - fn deref(&self) -> &Self::Target { - self.doc() - } -} - -impl DerefMut for TomlFile { - fn deref_mut(&mut self) -> &mut Self::Target { - self.doc_mut() - } -} - -/// The error emitted when failing to insert a profile into [profile] -#[derive(Debug)] -struct InsertProfileError { - pub message: String, - pub value: toml_edit::Item, -} - -impl std::fmt::Display for InsertProfileError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(&self.message) - } -} - -impl std::error::Error for InsertProfileError {} - -impl TomlFile { - /// Insert a name as `[profile.name]`. Creating the `[profile]` table where necessary and - /// throwing an error if there exists a conflict - fn insert_profile( - &mut self, - profile_str: &str, - value: toml_edit::Item, - ) -> Result<(), InsertProfileError> { - if !value.is_table_like() { - return Err(InsertProfileError { - message: format!("Expected [{profile_str}] to be a Table"), - value, - }); - } - // get or create the profile section - let profile_map = if let Some(map) = self.get_mut(Config::PROFILE_SECTION) { - map - } else { - // insert profile section at the beginning of the map - let mut profile_section = toml_edit::Table::new(); - profile_section.set_position(0); - profile_section.set_implicit(true); - self.insert( - Config::PROFILE_SECTION, - toml_edit::Item::Table(profile_section), - ); - self.get_mut(Config::PROFILE_SECTION) - .expect("exists per above") - }; - // ensure the profile section is a table - let profile_map = if let Some(table) = profile_map.as_table_like_mut() { - table - } else { - return Err(InsertProfileError { - message: format!("Expected [{}] to be a Table", Config::PROFILE_SECTION), - value, - }); - }; - // check the profile map for structure and existing keys - if let Some(profile) = profile_map.get(profile_str) { - if let Some(profile_table) = profile.as_table_like() { - if !profile_table.is_empty() { - return Err(InsertProfileError { - message: format!( - "[{}.{}] already exists", - Config::PROFILE_SECTION, - profile_str - ), - value, - }); - } - } else { - return Err(InsertProfileError { - message: format!( - "Expected [{}.{}] to be a Table", - Config::PROFILE_SECTION, - profile_str - ), - value, - }); - } - } - // insert the profile - profile_map.insert(profile_str, value); - Ok(()) - } -} - -/// Making sure any implicit profile `[name]` becomes `[profile.name]` for the given file and -/// returns the implicit profiles and the result of editing them -fn fix_toml_non_strict_profiles( - toml_file: &mut TomlFile, -) -> Vec<(String, Result<(), InsertProfileError>)> { - let mut results = vec![]; - - // get any non root level keys that need to be inserted into [profile] - let profiles = toml_file - .as_table() - .iter() - .map(|(k, _)| k.to_string()) - .filter(|k| { - !(k == Config::PROFILE_SECTION || Config::STANDALONE_SECTIONS.contains(&k.as_str())) - }) - .collect::>(); - - // remove each profile and insert into [profile] section - for profile in profiles { - if let Some(value) = toml_file.remove(&profile) { - let res = toml_file.insert_profile(&profile, value); - if let Err(err) = res.as_ref() { - toml_file.insert(&profile, err.value.clone()); - } - results.push((profile, res)) - } - } - results -} - -/// Fix foundry.toml files. Making sure any implicit profile `[name]` becomes -/// `[profile.name]`. Return any warnings -pub fn fix_tomls() -> Vec { - let mut warnings = vec![]; - - let tomls = { - let mut tomls = vec![]; - if let Some(global_toml) = Config::foundry_dir_toml().filter(|p| p.exists()) { - tomls.push(global_toml); - } - let local_toml = PathBuf::from( - Env::var("FOUNDRY_CONFIG").unwrap_or_else(|| Config::FILE_NAME.to_string()), - ); - if local_toml.exists() { - tomls.push(local_toml); - } else { - warnings.push(Warning::NoLocalToml(local_toml)); - } - tomls - }; - - for toml in tomls { - let mut toml_file = match TomlFile::open(&toml) { - Ok(toml_file) => toml_file, - Err(err) => { - warnings.push(Warning::CouldNotReadToml { - path: toml, - err: err.to_string(), - }); - continue; - } - }; - - let results = fix_toml_non_strict_profiles(&mut toml_file); - let was_edited = results.iter().any(|(_, res)| res.is_ok()); - for (profile, err) in results - .into_iter() - .filter_map(|(profile, res)| res.err().map(|err| (profile, err.message))) - { - warnings.push(Warning::CouldNotFixProfile { - path: toml_file.path().into(), - profile, - err, - }) - } - - if was_edited { - if let Err(err) = toml_file.save() { - warnings.push(Warning::CouldNotWriteToml { - path: toml_file.path().into(), - err: err.to_string(), - }); - } - } - } - - warnings -} - -#[cfg(test)] -mod tests { - use super::*; - use figment::Jail; - use pretty_assertions::assert_eq; - - macro_rules! fix_test { - ($(#[$meta:meta])* $name:ident, $fun:expr) => { - #[test] - $(#[$meta])* - fn $name() { - Jail::expect_with(|jail| { - // setup home directory, - // **Note** this only has an effect on unix, as [`dirs_next::home_dir()`] on windows uses `FOLDERID_Profile` - jail.set_env("HOME", jail.directory().display().to_string()); - std::fs::create_dir(jail.directory().join(".foundry")).unwrap(); - - // define function type to allow implicit params / return - let f: Box Result<(), figment::Error>> = Box::new($fun); - f(jail)?; - - Ok(()) - }); - } - }; - } - - fix_test!(test_implicit_profile_name_changed, |jail| { - jail.create_file( - "foundry.toml", - r#" - [default] - src = "src" - # comment - - [other] - src = "other-src" - "#, - )?; - fix_tomls(); - assert_eq!( - fs::read_to_string("foundry.toml").unwrap(), - r#" - [profile.default] - src = "src" - # comment - - [profile.other] - src = "other-src" - "# - ); - Ok(()) - }); - - fix_test!(test_leave_standalone_sections_alone, |jail| { - jail.create_file( - "foundry.toml", - r#" - [default] - src = "src" - - [fmt] - line_length = 100 - - [rpc_endpoints] - optimism = "https://example.com/" - "#, - )?; - fix_tomls(); - assert_eq!( - fs::read_to_string("foundry.toml").unwrap(), - r#" - [profile.default] - src = "src" - - [fmt] - line_length = 100 - - [rpc_endpoints] - optimism = "https://example.com/" - "# - ); - Ok(()) - }); - - // mocking the `$HOME` has no effect on windows, see [`dirs_next::home_dir()`] - fix_test!( - #[cfg(not(windows))] - test_global_toml_is_edited, - |jail| { - jail.create_file( - "foundry.toml", - r#" - [other] - src = "other-src" - "#, - )?; - jail.create_file( - ".foundry/foundry.toml", - r#" - [default] - src = "src" - "#, - )?; - fix_tomls(); - assert_eq!( - fs::read_to_string("foundry.toml").unwrap(), - r#" - [profile.other] - src = "other-src" - "# - ); - assert_eq!( - fs::read_to_string(".foundry/foundry.toml").unwrap(), - r#" - [profile.default] - src = "src" - "# - ); - Ok(()) - } - ); -} diff --git a/foundry-config/src/fs_permissions.rs b/foundry-config/src/fs_permissions.rs deleted file mode 100644 index fa6d431db..000000000 --- a/foundry-config/src/fs_permissions.rs +++ /dev/null @@ -1,255 +0,0 @@ -//! Support for controlling fs access - -use serde::{Deserialize, Deserializer, Serialize, Serializer}; -use std::{ - fmt, - path::{Path, PathBuf}, - str::FromStr, -}; - -/// Configures file system access -/// -/// E.g. for cheat codes (`vm.writeFile`) -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)] -#[serde(transparent)] -pub struct FsPermissions { - /// what kind of access is allowed - pub permissions: Vec, -} - -// === impl FsPermissions === - -impl FsPermissions { - /// Creates anew instance with the given `permissions` - pub fn new(permissions: impl IntoIterator) -> Self { - Self { - permissions: permissions.into_iter().collect(), - } - } - - /// Returns true if access to the specified path is allowed with the specified. - /// - /// This first checks permission, and only if it is granted, whether the path is allowed. - /// - /// We only allow paths that are inside allowed paths. - /// - /// Caution: This should be called with normalized paths if the `allowed_paths` are also - /// normalized. - pub fn is_path_allowed(&self, path: &Path, kind: FsAccessKind) -> bool { - self.find_permission(path) - .map(|perm| perm.is_granted(kind)) - .unwrap_or_default() - } - - /// Returns the permission for the matching path - pub fn find_permission(&self, path: &Path) -> Option { - self.permissions - .iter() - .find(|perm| path.starts_with(&perm.path)) - .map(|perm| perm.access) - } - - /// Updates all `allowed_paths` and joins ([`Path::join`]) the `root` with all entries - pub fn join_all(&mut self, root: impl AsRef) { - let root = root.as_ref(); - self.permissions.iter_mut().for_each(|perm| { - perm.path = root.join(&perm.path); - }) - } - - /// Same as [`Self::join_all`] but consumes the type - pub fn joined(mut self, root: impl AsRef) -> Self { - self.join_all(root); - self - } - - /// Removes all existing permissions for the given path - pub fn remove(&mut self, path: impl AsRef) { - let path = path.as_ref(); - self.permissions - .retain(|permission| permission.path != path) - } - - /// Returns true if no permissions are configured - pub fn is_empty(&self) -> bool { - self.permissions.is_empty() - } - - /// Returns the number of configured permissions - pub fn len(&self) -> usize { - self.permissions.len() - } -} - -/// Represents an access permission to a single path -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)] -pub struct PathPermission { - /// Permission level to access the `path` - pub access: FsAccessPermission, - /// The targeted path guarded by the permission - pub path: PathBuf, -} - -// === impl PathPermission === - -impl PathPermission { - /// Returns a new permission for the path and the given access - pub fn new(path: impl Into, access: FsAccessPermission) -> Self { - Self { - path: path.into(), - access, - } - } - - /// Returns a new read-only permission for the path - pub fn read(path: impl Into) -> Self { - Self::new(path, FsAccessPermission::Read) - } - - /// Returns a new read-write permission for the path - pub fn read_write(path: impl Into) -> Self { - Self::new(path, FsAccessPermission::ReadWrite) - } - - /// Returns a new write-only permission for the path - pub fn write(path: impl Into) -> Self { - Self::new(path, FsAccessPermission::Write) - } - - /// Returns a non permission for the path - pub fn none(path: impl Into) -> Self { - Self::new(path, FsAccessPermission::None) - } - - /// Returns true if the access is allowed - pub fn is_granted(&self, kind: FsAccessKind) -> bool { - self.access.is_granted(kind) - } -} - -/// Represents the operation on the fs -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub enum FsAccessKind { - /// read from fs (`vm.readFile`) - Read, - /// write to fs (`vm.writeFile`) - Write, -} - -impl fmt::Display for FsAccessKind { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - FsAccessKind::Read => f.write_str("read"), - FsAccessKind::Write => f.write_str("write"), - } - } -} - -/// Determines the status of file system access -#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)] -pub enum FsAccessPermission { - /// FS access is _not_ allowed - #[default] - None, - /// FS access is allowed, this includes `read` + `write` - ReadWrite, - /// Only reading is allowed - Read, - /// Only writing is allowed - Write, -} - -// === impl FsAccessPermission === - -impl FsAccessPermission { - /// Returns true if the access is allowed - pub fn is_granted(&self, kind: FsAccessKind) -> bool { - match (self, kind) { - (FsAccessPermission::ReadWrite, _) => true, - (FsAccessPermission::None, _) => false, - (FsAccessPermission::Read, FsAccessKind::Read) => true, - (FsAccessPermission::Write, FsAccessKind::Write) => true, - _ => false, - } - } -} - -impl FromStr for FsAccessPermission { - type Err = String; - - fn from_str(s: &str) -> Result { - match s { - "true" | "read-write" | "readwrite" => Ok(FsAccessPermission::ReadWrite), - "false" | "none" => Ok(FsAccessPermission::None), - "read" => Ok(FsAccessPermission::Read), - "write" => Ok(FsAccessPermission::Write), - _ => Err(format!("Unknown variant {s}")), - } - } -} - -impl fmt::Display for FsAccessPermission { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - FsAccessPermission::ReadWrite => f.write_str("read-write"), - FsAccessPermission::None => f.write_str("none"), - FsAccessPermission::Read => f.write_str("read"), - FsAccessPermission::Write => f.write_str("write"), - } - } -} - -impl Serialize for FsAccessPermission { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - match self { - FsAccessPermission::ReadWrite => serializer.serialize_bool(true), - FsAccessPermission::None => serializer.serialize_bool(false), - FsAccessPermission::Read => serializer.serialize_str("read"), - FsAccessPermission::Write => serializer.serialize_str("write"), - } - } -} - -impl<'de> Deserialize<'de> for FsAccessPermission { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - #[derive(Deserialize)] - #[serde(untagged)] - enum Status { - Bool(bool), - String(String), - } - match Status::deserialize(deserializer)? { - Status::Bool(enabled) => { - let status = if enabled { - FsAccessPermission::ReadWrite - } else { - FsAccessPermission::None - }; - Ok(status) - } - Status::String(val) => val.parse().map_err(serde::de::Error::custom), - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn can_parse_permission() { - assert_eq!(FsAccessPermission::ReadWrite, "true".parse().unwrap()); - assert_eq!(FsAccessPermission::ReadWrite, "readwrite".parse().unwrap()); - assert_eq!(FsAccessPermission::ReadWrite, "read-write".parse().unwrap()); - assert_eq!(FsAccessPermission::None, "false".parse().unwrap()); - assert_eq!(FsAccessPermission::None, "none".parse().unwrap()); - assert_eq!(FsAccessPermission::Read, "read".parse().unwrap()); - assert_eq!(FsAccessPermission::Write, "write".parse().unwrap()); - } -} diff --git a/foundry-config/src/fuzz.rs b/foundry-config/src/fuzz.rs deleted file mode 100644 index 7bbeb9398..000000000 --- a/foundry-config/src/fuzz.rs +++ /dev/null @@ -1,170 +0,0 @@ -//! Configuration for fuzz testing - -use ethers_core::types::U256; -use serde::{Deserialize, Serialize}; - -use crate::inline::{ - parse_config_u32, InlineConfigParser, InlineConfigParserError, INLINE_CONFIG_FUZZ_KEY, -}; - -/// Contains for fuzz testing -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] -pub struct FuzzConfig { - /// The number of test cases that must execute for each property test - pub runs: u32, - /// The maximum number of test case rejections allowed by proptest, to be - /// encountered during usage of `vm.assume` cheatcode. This will be used - /// to set the `max_global_rejects` value in proptest test runner config. - /// `max_local_rejects` option isn't exposed here since we're not using - /// `prop_filter`. - pub max_test_rejects: u32, - /// Optional seed for the fuzzing RNG algorithm - #[serde( - deserialize_with = "ethers_core::types::serde_helpers::deserialize_stringified_numeric_opt" - )] - pub seed: Option, - /// The fuzz dictionary configuration - #[serde(flatten)] - pub dictionary: FuzzDictionaryConfig, -} - -impl Default for FuzzConfig { - fn default() -> Self { - FuzzConfig { - runs: 256, - max_test_rejects: 65536, - seed: None, - dictionary: FuzzDictionaryConfig::default(), - } - } -} - -impl InlineConfigParser for FuzzConfig { - fn config_key() -> String { - INLINE_CONFIG_FUZZ_KEY.into() - } - - fn try_merge(&self, configs: &[String]) -> Result, InlineConfigParserError> { - let overrides: Vec<(String, String)> = Self::get_config_overrides(configs); - - if overrides.is_empty() { - return Ok(None); - } - - // self is Copy. We clone it with dereference. - let mut conf_clone = *self; - - for pair in overrides { - let key = pair.0; - let value = pair.1; - match key.as_str() { - "runs" => conf_clone.runs = parse_config_u32(key, value)?, - "max-test-rejects" => conf_clone.max_test_rejects = parse_config_u32(key, value)?, - "dictionary-weight" => { - conf_clone.dictionary.dictionary_weight = parse_config_u32(key, value)? - } - _ => Err(InlineConfigParserError::InvalidConfigProperty(key))?, - } - } - Ok(Some(conf_clone)) - } -} - -/// Contains for fuzz testing -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] -pub struct FuzzDictionaryConfig { - /// The weight of the dictionary - #[serde(deserialize_with = "crate::deserialize_stringified_percent")] - pub dictionary_weight: u32, - /// The flag indicating whether to include values from storage - pub include_storage: bool, - /// The flag indicating whether to include push bytes values - pub include_push_bytes: bool, - /// How many addresses to record at most. - /// Once the fuzzer exceeds this limit, it will start evicting random entries - /// - /// This limit is put in place to prevent memory blowup. - #[serde(deserialize_with = "crate::deserialize_usize_or_max")] - pub max_fuzz_dictionary_addresses: usize, - /// How many values to record at most. - /// Once the fuzzer exceeds this limit, it will start evicting random entries - #[serde(deserialize_with = "crate::deserialize_usize_or_max")] - pub max_fuzz_dictionary_values: usize, -} - -impl Default for FuzzDictionaryConfig { - fn default() -> Self { - FuzzDictionaryConfig { - dictionary_weight: 40, - include_storage: true, - include_push_bytes: true, - // limit this to 300MB - max_fuzz_dictionary_addresses: (300 * 1024 * 1024) / 20, - // limit this to 200MB - max_fuzz_dictionary_values: (200 * 1024 * 1024) / 32, - } - } -} - -#[cfg(test)] -mod tests { - use crate::{inline::InlineConfigParser, FuzzConfig}; - - #[test] - fn unrecognized_property() { - let configs = &["forge-config: default.fuzz.unknownprop = 200".to_string()]; - let base_config = FuzzConfig::default(); - if let Err(e) = base_config.try_merge(configs) { - assert_eq!(e.to_string(), "'unknownprop' is an invalid config property"); - } else { - unreachable!() - } - } - - #[test] - fn successful_merge() { - let configs = &[ - "forge-config: default.fuzz.runs = 42424242".to_string(), - "forge-config: default.fuzz.dictionary-weight = 42".to_string(), - ]; - let base_config = FuzzConfig::default(); - let merged: FuzzConfig = base_config.try_merge(configs).expect("No errors").unwrap(); - assert_eq!(merged.runs, 42424242); - assert_eq!(merged.dictionary.dictionary_weight, 42); - } - - #[test] - fn merge_is_none() { - let empty_config = &[]; - let base_config = FuzzConfig::default(); - let merged = base_config.try_merge(empty_config).expect("No errors"); - assert!(merged.is_none()); - } - - #[test] - fn merge_is_none_unrelated_property() { - let unrelated_configs = &["forge-config: default.invariant.runs = 2".to_string()]; - let base_config = FuzzConfig::default(); - let merged = base_config.try_merge(unrelated_configs).expect("No errors"); - assert!(merged.is_none()); - } - - #[test] - fn override_detection() { - let configs = &[ - "forge-config: default.fuzz.runs = 42424242".to_string(), - "forge-config: ci.fuzz.runs = 666666".to_string(), - "forge-config: default.invariant.runs = 2".to_string(), - "forge-config: default.fuzz.dictionary-weight = 42".to_string(), - ]; - let variables = FuzzConfig::get_config_overrides(configs); - assert_eq!( - variables, - vec![ - ("runs".into(), "42424242".into()), - ("runs".into(), "666666".into()), - ("dictionary-weight".into(), "42".into()) - ] - ); - } -} diff --git a/foundry-config/src/inline/conf_parser.rs b/foundry-config/src/inline/conf_parser.rs deleted file mode 100644 index ef1263745..000000000 --- a/foundry-config/src/inline/conf_parser.rs +++ /dev/null @@ -1,212 +0,0 @@ -use regex::Regex; - -use crate::{InlineConfigError, NatSpec}; - -use super::{remove_whitespaces, INLINE_CONFIG_PREFIX}; - -/// Errors returned by the [`InlineConfigParser`] trait. -#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)] -pub enum InlineConfigParserError { - /// An invalid configuration property has been provided. - /// The property cannot be mapped to the configuration object - #[error("'{0}' is an invalid config property")] - InvalidConfigProperty(String), - /// An invalid profile has been provided - #[error("'{0}' specifies an invalid profile. Available profiles are: {1}")] - InvalidProfile(String, String), - /// An error occurred while trying to parse an integer configuration value - #[error("Invalid config value for key '{0}'. Unable to parse '{1}' into an integer value")] - ParseInt(String, String), - /// An error occurred while trying to parse a boolean configuration value - #[error("Invalid config value for key '{0}'. Unable to parse '{1}' into a boolean value")] - ParseBool(String, String), -} - -/// This trait is intended to parse configurations from -/// structured text. Foundry users can annotate Solidity test functions, -/// providing special configs just for the execution of a specific test. -/// -/// An example: -/// -/// ```solidity -/// contract MyTest is Test { -/// /// forge-config: default.fuzz.runs = 100 -/// /// forge-config: ci.fuzz.runs = 500 -/// function test_SimpleFuzzTest(uint256 x) public {...} -/// -/// /// forge-config: default.fuzz.runs = 500 -/// /// forge-config: ci.fuzz.runs = 10000 -/// function test_ImportantFuzzTest(uint256 x) public {...} -/// } -/// ``` -pub trait InlineConfigParser -where - Self: Clone + Default + Sized + 'static, -{ - /// Returns a config key that is common to all valid configuration lines - /// for the current impl. This helps to extract correct values out of a text. - /// - /// An example key would be `fuzz` of `invariant`. - fn config_key() -> String; - - /// Tries to override `self` properties with values specified in the `configs` parameter. - /// - /// Returns - /// - `Some(Self)` in case some configurations are merged into self. - /// - `None` in case there are no configurations that can be applied to self. - /// - `Err(InlineConfigParserError)` in case of wrong configuration. - fn try_merge(&self, configs: &[String]) -> Result, InlineConfigParserError>; - - /// Validates all configurations contained in a natspec that apply - /// to the current configuration key. - /// - /// i.e. Given the `invariant` config key and a natspec comment of the form, - /// ```solidity - /// /// forge-config: default.invariant.runs = 500 - /// /// forge-config: default.invariant.depth = 500 - /// /// forge-config: ci.invariant.depth = 500 - /// /// forge-config: ci.fuzz.runs = 10 - /// ``` - /// would validate the whole `invariant` configuration. - fn validate_configs(natspec: &NatSpec) -> Result<(), InlineConfigError> { - let config_key = Self::config_key(); - - let configs = natspec - .config_lines() - .filter(|l| l.contains(&config_key)) - .collect::>(); - - Self::default().try_merge(&configs).map_err(|e| { - let line = natspec.debug_context(); - InlineConfigError { line, source: e } - })?; - - Ok(()) - } - - /// Given a list of `config_lines, returns all available pairs (key, value) - /// matching the current config key - /// - /// i.e. Given the `invariant` config key and a vector of config lines - /// ```rust - /// let _config_lines = vec![ - /// "forge-config: default.invariant.runs = 500", - /// "forge-config: default.invariant.depth = 500", - /// "forge-config: ci.invariant.depth = 500", - /// "forge-config: ci.fuzz.runs = 10" - /// ]; - /// ``` - /// would return the whole set of `invariant` configs. - /// ```rust - /// let _result = vec![ - /// ("runs", "500"), - /// ("depth", "500"), - /// ("depth", "500"), - /// ]; - /// ``` - fn get_config_overrides(config_lines: &[String]) -> Vec<(String, String)> { - let mut result: Vec<(String, String)> = vec![]; - let config_key = Self::config_key(); - let profile = ".*"; - let prefix = format!("^{INLINE_CONFIG_PREFIX}:{profile}{config_key}\\."); - let re = Regex::new(&prefix).unwrap(); - - config_lines - .iter() - .map(|l| remove_whitespaces(l)) - .filter(|l| re.is_match(l)) - .map(|l| re.replace(&l, "").to_string()) - .for_each(|line| { - let key_value = line.split('=').collect::>(); // i.e. "['runs', '500']" - if let Some(key) = key_value.first() { - if let Some(value) = key_value.last() { - result.push((key.to_string(), value.to_string())); - } - } - }); - - result - } -} - -/// Checks if all configuration lines specified in `natspec` use a valid profile. -/// -/// i.e. Given available profiles -/// ```rust -/// let _profiles = vec!["ci", "default"]; -/// ``` -/// A configuration like `forge-config: ciii.invariant.depth = 1` would result -/// in an error. -pub fn validate_profiles(natspec: &NatSpec, profiles: &[String]) -> Result<(), InlineConfigError> { - for config in natspec.config_lines() { - if !profiles - .iter() - .any(|p| config.starts_with(&format!("{INLINE_CONFIG_PREFIX}:{p}."))) - { - let err_line: String = natspec.debug_context(); - let profiles = format!("{profiles:?}"); - Err(InlineConfigError { - source: InlineConfigParserError::InvalidProfile(config, profiles), - line: err_line, - })? - } - } - Ok(()) -} - -/// Tries to parse a `u32` from `value`. The `key` argument is used to give details -/// in the case of an error. -pub fn parse_config_u32(key: String, value: String) -> Result { - value - .parse() - .map_err(|_| InlineConfigParserError::ParseInt(key, value)) -} - -/// Tries to parse a `bool` from `value`. The `key` argument is used to give details -/// in the case of an error. -pub fn parse_config_bool(key: String, value: String) -> Result { - value - .parse() - .map_err(|_| InlineConfigParserError::ParseBool(key, value)) -} - -#[cfg(test)] -mod tests { - use crate::{inline::conf_parser::validate_profiles, NatSpec}; - - #[test] - fn can_reject_invalid_profiles() { - let profiles = ["ci".to_string(), "default".to_string()]; - let natspec = NatSpec { - contract: Default::default(), - function: Default::default(), - line: Default::default(), - docs: r#" - forge-config: ciii.invariant.depth = 1 - forge-config: default.invariant.depth = 1 - "# - .into(), - }; - - let result = validate_profiles(&natspec, &profiles); - assert!(result.is_err()); - } - - #[test] - fn can_accept_valid_profiles() { - let profiles = ["ci".to_string(), "default".to_string()]; - let natspec = NatSpec { - contract: Default::default(), - function: Default::default(), - line: Default::default(), - docs: r#" - forge-config: ci.invariant.depth = 1 - forge-config: default.invariant.depth = 1 - "# - .into(), - }; - - let result = validate_profiles(&natspec, &profiles); - assert!(result.is_ok()); - } -} diff --git a/foundry-config/src/inline/mod.rs b/foundry-config/src/inline/mod.rs deleted file mode 100644 index 3d34d1f36..000000000 --- a/foundry-config/src/inline/mod.rs +++ /dev/null @@ -1,85 +0,0 @@ -mod conf_parser; -pub use conf_parser::{ - parse_config_bool, parse_config_u32, validate_profiles, InlineConfigParser, - InlineConfigParserError, -}; -use once_cell::sync::Lazy; -use std::collections::HashMap; - -mod natspec; -pub use natspec::NatSpec; - -use crate::Config; - -pub const INLINE_CONFIG_FUZZ_KEY: &str = "fuzz"; -pub const INLINE_CONFIG_INVARIANT_KEY: &str = "invariant"; -const INLINE_CONFIG_PREFIX: &str = "forge-config"; - -static INLINE_CONFIG_PREFIX_SELECTED_PROFILE: Lazy = Lazy::new(|| { - let selected_profile = Config::selected_profile().to_string(); - format!("{INLINE_CONFIG_PREFIX}:{selected_profile}.") -}); - -/// Wrapper error struct that catches config parsing -/// errors [`InlineConfigParserError`], enriching them with context information -/// reporting the misconfigured line. -#[derive(thiserror::Error, Debug)] -#[error("Inline config error detected at {line}")] -pub struct InlineConfigError { - /// Specifies the misconfigured line. This is something of the form - /// `dir/TestContract.t.sol:FuzzContract:10:12:111` - pub line: String, - /// The inner error - pub source: InlineConfigParserError, -} - -/// Represents a (test-contract, test-function) pair -type InlineConfigKey = (String, String); - -/// Represents per-test configurations, declared inline -/// as structured comments in Solidity test files. This allows -/// to create configs directly bound to a solidity test. -#[derive(Default, Debug, Clone)] -pub struct InlineConfig { - /// Maps a (test-contract, test-function) pair - /// to a specific configuration provided by the user. - configs: HashMap, -} - -impl InlineConfig { - /// Returns an inline configuration, if any, for a test function. - /// Configuration is identified by the pair "contract", "function". - pub fn get>(&self, contract_id: S, fn_name: S) -> Option<&T> { - self.configs.get(&(contract_id.into(), fn_name.into())) - } - - /// Inserts an inline configuration, for a test function. - /// Configuration is identified by the pair "contract", "function". - pub fn insert>(&mut self, contract_id: S, fn_name: S, config: T) { - self.configs - .insert((contract_id.into(), fn_name.into()), config); - } -} - -fn remove_whitespaces(s: &str) -> String { - s.chars().filter(|c| !c.is_whitespace()).collect() -} - -#[cfg(test)] -mod tests { - use super::InlineConfigParserError; - use crate::InlineConfigError; - - #[test] - fn can_format_inline_config_errors() { - let source = InlineConfigParserError::ParseBool("key".into(), "invalid-bool-value".into()); - let line = "dir/TestContract.t.sol:FuzzContract".to_string(); - let error = InlineConfigError { - line: line.clone(), - source, - }; - - let expected = format!("Inline config error detected at {line}"); - assert_eq!(error.to_string(), expected); - } -} diff --git a/foundry-config/src/inline/natspec.rs b/foundry-config/src/inline/natspec.rs deleted file mode 100644 index 44efe4fc8..000000000 --- a/foundry-config/src/inline/natspec.rs +++ /dev/null @@ -1,241 +0,0 @@ -use super::{remove_whitespaces, INLINE_CONFIG_PREFIX, INLINE_CONFIG_PREFIX_SELECTED_PROFILE}; -use ethers_solc::{ - artifacts::{ast::NodeType, Node}, - ProjectCompileOutput, -}; -use serde_json::Value; -use std::{collections::BTreeMap, path::Path}; - -/// Convenient struct to hold in-line per-test configurations -pub struct NatSpec { - /// The parent contract of the natspec - pub contract: String, - /// The function annotated with the natspec - pub function: String, - /// The line the natspec appears, in the form - /// `row:col:length` i.e. `10:21:122` - pub line: String, - /// The actual natspec comment, without slashes or block - /// punctuation - pub docs: String, -} - -impl NatSpec { - /// Factory function that extracts a vector of [`NatSpec`] instances from - /// a solc compiler output. The root path is to express contract base dirs. - /// That is essential to match per-test configs at runtime. - pub fn parse(output: &ProjectCompileOutput, root: &Path) -> Vec { - let mut natspecs: Vec = vec![]; - - for (id, artifact) in output.artifact_ids() { - let Some(ast) = &artifact.ast else { continue }; - let path = id.source.as_path(); - let path = path.strip_prefix(root).unwrap_or(path); - // id.identifier - let contract = format!("{}:{}", path.display(), id.name); - let Some(node) = contract_root_node(&ast.nodes, &contract) else { - continue; - }; - apply(&mut natspecs, &contract, node) - } - - natspecs - } - - /// Returns a string describing the natspec - /// context, for debugging purposes 🐞 - /// i.e. `test/Counter.t.sol:CounterTest:testFuzz_SetNumber` - pub fn debug_context(&self) -> String { - format!("{}:{}", self.contract, self.function) - } - - /// Returns a list of configuration lines that match the current profile - pub fn current_profile_configs(&self) -> impl Iterator + '_ { - self.config_lines_with_prefix(INLINE_CONFIG_PREFIX_SELECTED_PROFILE.as_str()) - } - - /// Returns a list of configuration lines that match a specific string prefix - pub fn config_lines_with_prefix<'a>( - &'a self, - prefix: &'a str, - ) -> impl Iterator + 'a { - self.config_lines().filter(move |l| l.starts_with(prefix)) - } - - /// Returns a list of all the configuration lines available in the natspec - pub fn config_lines(&self) -> impl Iterator + '_ { - self.docs - .lines() - .map(remove_whitespaces) - .filter(|line| line.contains(INLINE_CONFIG_PREFIX)) - } -} - -/// Given a list of nodes, find a "ContractDefinition" node that matches -/// the provided contract_id. -fn contract_root_node<'a>(nodes: &'a [Node], contract_id: &'a str) -> Option<&'a Node> { - for n in nodes.iter() { - if let NodeType::ContractDefinition = n.node_type { - let contract_data = &n.other; - if let Value::String(contract_name) = contract_data.get("name")? { - if contract_id.ends_with(contract_name) { - return Some(n); - } - } - } - } - None -} - -/// Implements a DFS over a compiler output node and its children. -/// If a natspec is found it is added to `natspecs` -fn apply(natspecs: &mut Vec, contract: &str, node: &Node) { - for n in node.nodes.iter() { - if let Some((function, docs, line)) = get_fn_data(n) { - natspecs.push(NatSpec { - contract: contract.into(), - function, - line, - docs, - }) - } - apply(natspecs, contract, n); - } -} - -/// Given a compilation output node, if it is a function definition -/// that also contains a natspec then return a tuple of: -/// - Function name -/// - Natspec text -/// - Natspec position with format "row:col:length" -/// -/// Return None otherwise. -fn get_fn_data(node: &Node) -> Option<(String, String, String)> { - if let NodeType::FunctionDefinition = node.node_type { - let fn_data = &node.other; - let fn_name: String = get_fn_name(fn_data)?; - let (fn_docs, docs_src_line): (String, String) = get_fn_docs(fn_data)?; - return Some((fn_name, fn_docs, docs_src_line)); - } - - None -} - -/// Given a dictionary of function data returns the name of the function. -fn get_fn_name(fn_data: &BTreeMap) -> Option { - match fn_data.get("name")? { - Value::String(fn_name) => Some(fn_name.into()), - _ => None, - } -} - -/// Inspects Solc compiler output for documentation comments. Returns: -/// - `Some((String, String))` in case the function has natspec comments. First item is a textual -/// natspec representation, the second item is the natspec src line, in the form "raw:col:length". -/// - `None` in case the function has not natspec comments. -fn get_fn_docs(fn_data: &BTreeMap) -> Option<(String, String)> { - if let Value::Object(fn_docs) = fn_data.get("documentation")? { - if let Value::String(comment) = fn_docs.get("text")? { - if comment.contains(INLINE_CONFIG_PREFIX) { - let mut src_line = fn_docs - .get("src") - .map(|src| src.to_string()) - .unwrap_or_else(|| String::from("")); - - src_line.retain(|c| c != '"'); - return Some((comment.into(), src_line)); - } - } - } - None -} - -#[cfg(test)] -mod tests { - use crate::{inline::natspec::get_fn_docs, NatSpec}; - use serde_json::{json, Value}; - use std::collections::BTreeMap; - - #[test] - fn config_lines() { - let natspec = natspec(); - let config_lines = natspec.config_lines(); - assert_eq!( - config_lines.collect::>(), - vec![ - "forge-config:default.fuzz.runs=600".to_string(), - "forge-config:ci.fuzz.runs=500".to_string(), - "forge-config:default.invariant.runs=1".to_string() - ] - ) - } - - #[test] - fn current_profile_configs() { - let natspec = natspec(); - let config_lines = natspec.current_profile_configs(); - - assert_eq!( - config_lines.collect::>(), - vec![ - "forge-config:default.fuzz.runs=600".to_string(), - "forge-config:default.invariant.runs=1".to_string() - ] - ); - } - - #[test] - fn config_lines_with_prefix() { - use super::INLINE_CONFIG_PREFIX; - let natspec = natspec(); - let prefix = format!("{INLINE_CONFIG_PREFIX}:default"); - let config_lines = natspec.config_lines_with_prefix(&prefix); - assert_eq!( - config_lines.collect::>(), - vec![ - "forge-config:default.fuzz.runs=600".to_string(), - "forge-config:default.invariant.runs=1".to_string() - ] - ) - } - - #[test] - fn can_handle_unavailable_src_line_with_fallback() { - let mut fn_data: BTreeMap = BTreeMap::new(); - let doc_withouth_src_field = json!({ "text": "forge-config:default.fuzz.runs=600" }); - fn_data.insert("documentation".into(), doc_withouth_src_field); - let (_, src_line) = get_fn_docs(&fn_data).expect("Some docs"); - assert_eq!(src_line, "".to_string()); - } - - #[test] - fn can_handle_available_src_line() { - let mut fn_data: BTreeMap = BTreeMap::new(); - let doc_withouth_src_field = - json!({ "text": "forge-config:default.fuzz.runs=600", "src": "73:21:12" }); - fn_data.insert("documentation".into(), doc_withouth_src_field); - let (_, src_line) = get_fn_docs(&fn_data).expect("Some docs"); - assert_eq!(src_line, "73:21:12".to_string()); - } - - fn natspec() -> NatSpec { - let conf = r#" - forge-config: default.fuzz.runs = 600 - forge-config: ci.fuzz.runs = 500 - ========= SOME NOISY TEXT ============= - 䩹𧀫Jx닧Ʀ̳盅K擷􅟽Ɂw첊}ꏻk86ᖪk-檻ܴ렝[Dz𐤬oᘓƤ - ꣖ۻ%Ƅ㪕ς:(饁΍av/烲ڻ̛߉橞㗡𥺃̹M봓䀖ؿ̄󵼁)𯖛d􂽰񮍃 - ϊ&»ϿЏ񊈞2򕄬񠪁鞷砕eߥH󶑶J粊񁼯머?槿ᴴጅ𙏑ϖ뀓򨙺򷃅Ӽ츙4󍔹 - 醤㭊r􎜕󷾸𶚏 ܖ̹灱녗V*竅􋹲⒪苏贗񾦼=숽ؓ򗋲бݧ󫥛𛲍ʹ園Ьi - ======================================= - forge-config: default.invariant.runs = 1 - "#; - - NatSpec { - contract: "dir/TestContract.t.sol:FuzzContract".to_string(), - function: "test_myFunction".to_string(), - line: "10:12:111".to_string(), - docs: conf.to_string(), - } - } -} diff --git a/foundry-config/src/invariant.rs b/foundry-config/src/invariant.rs deleted file mode 100644 index ce11e19d7..000000000 --- a/foundry-config/src/invariant.rs +++ /dev/null @@ -1,129 +0,0 @@ -//! Configuration for invariant testing - -use crate::{ - fuzz::FuzzDictionaryConfig, - inline::{ - parse_config_bool, parse_config_u32, InlineConfigParser, InlineConfigParserError, - INLINE_CONFIG_INVARIANT_KEY, - }, -}; -use serde::{Deserialize, Serialize}; - -/// Contains for invariant testing -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] -pub struct InvariantConfig { - /// The number of runs that must execute for each invariant test group. - pub runs: u32, - /// The number of calls executed to attempt to break invariants in one run. - pub depth: u32, - /// Fails the invariant fuzzing if a revert occurs - pub fail_on_revert: bool, - /// Allows overriding an unsafe external call when running invariant tests. eg. reentrancy - /// checks - pub call_override: bool, - /// The fuzz dictionary configuration - #[serde(flatten)] - pub dictionary: FuzzDictionaryConfig, - /// Attempt to shrink the failure case to its smallest sequence of calls - pub shrink_sequence: bool, -} - -impl Default for InvariantConfig { - fn default() -> Self { - InvariantConfig { - runs: 256, - depth: 15, - fail_on_revert: false, - call_override: false, - dictionary: FuzzDictionaryConfig { - dictionary_weight: 80, - ..Default::default() - }, - shrink_sequence: true, - } - } -} - -impl InlineConfigParser for InvariantConfig { - fn config_key() -> String { - INLINE_CONFIG_INVARIANT_KEY.into() - } - - fn try_merge(&self, configs: &[String]) -> Result, InlineConfigParserError> { - let overrides: Vec<(String, String)> = Self::get_config_overrides(configs); - - if overrides.is_empty() { - return Ok(None); - } - - // self is Copy. We clone it with dereference. - let mut conf_clone = *self; - - for pair in overrides { - let key = pair.0; - let value = pair.1; - match key.as_str() { - "runs" => conf_clone.runs = parse_config_u32(key, value)?, - "depth" => conf_clone.depth = parse_config_u32(key, value)?, - "fail-on-revert" => conf_clone.fail_on_revert = parse_config_bool(key, value)?, - "call-override" => conf_clone.call_override = parse_config_bool(key, value)?, - "shrink-sequence" => conf_clone.shrink_sequence = parse_config_bool(key, value)?, - _ => Err(InlineConfigParserError::InvalidConfigProperty( - key.to_string(), - ))?, - } - } - Ok(Some(conf_clone)) - } -} - -#[cfg(test)] -mod tests { - use crate::{inline::InlineConfigParser, InvariantConfig}; - - #[test] - fn unrecognized_property() { - let configs = &["forge-config: default.invariant.unknownprop = 200".to_string()]; - let base_config = InvariantConfig::default(); - if let Err(e) = base_config.try_merge(configs) { - assert_eq!(e.to_string(), "'unknownprop' is an invalid config property"); - } else { - unreachable!() - } - } - - #[test] - fn successful_merge() { - let configs = &["forge-config: default.invariant.runs = 42424242".to_string()]; - let base_config = InvariantConfig::default(); - let merged: InvariantConfig = base_config.try_merge(configs).expect("No errors").unwrap(); - assert_eq!(merged.runs, 42424242); - } - - #[test] - fn merge_is_none() { - let empty_config = &[]; - let base_config = InvariantConfig::default(); - let merged = base_config.try_merge(empty_config).expect("No errors"); - assert!(merged.is_none()); - } - - #[test] - fn can_merge_unrelated_properties_into_config() { - let unrelated_configs = &["forge-config: default.fuzz.runs = 2".to_string()]; - let base_config = InvariantConfig::default(); - let merged = base_config.try_merge(unrelated_configs).expect("No errors"); - assert!(merged.is_none()); - } - - #[test] - fn override_detection() { - let configs = &[ - "forge-config: default.fuzz.runs = 42424242".to_string(), - "forge-config: ci.fuzz.runs = 666666".to_string(), - "forge-config: default.invariant.runs = 2".to_string(), - ]; - let variables = InvariantConfig::get_config_overrides(configs); - assert_eq!(variables, vec![("runs".into(), "2".into())]); - } -} diff --git a/foundry-config/src/lib.rs b/foundry-config/src/lib.rs deleted file mode 100644 index 0d17ba3a7..000000000 --- a/foundry-config/src/lib.rs +++ /dev/null @@ -1,4630 +0,0 @@ -//! Foundry configuration. - -#![warn(missing_docs, unused_crate_dependencies)] - -use crate::cache::StorageCachingConfig; -use ethers_core::types::{Address, Chain::Mainnet, H160, H256, U256}; -pub use ethers_solc::{self, artifacts::OptimizerDetails}; -use ethers_solc::{ - artifacts::{ - output_selection::ContractOutputSelection, serde_helpers, BytecodeHash, DebuggingSettings, - Libraries, ModelCheckerSettings, ModelCheckerTarget, Optimizer, RevertStrings, Settings, - SettingsMetadata, Severity, - }, - cache::SOLIDITY_FILES_CACHE_FILENAME, - error::SolcError, - remappings::{RelativeRemapping, Remapping}, - ConfigurableArtifacts, EvmVersion, Project, ProjectPathsConfig, Solc, SolcConfig, -}; -use eyre::{ContextCompat, WrapErr}; -use figment::{ - providers::{Env, Format, Serialized, Toml}, - value::{Dict, Map, Value}, - Error, Figment, Metadata, Profile, Provider, -}; -use inflector::Inflector; -use once_cell::sync::Lazy; -use regex::Regex; -use semver::Version; -use serde::{Deserialize, Deserializer, Serialize, Serializer}; -use std::{ - borrow::Cow, - collections::HashMap, - fs, - path::{Path, PathBuf}, - str::FromStr, -}; -pub(crate) use tracing::trace; - -// Macros useful for creating a figment. -mod macros; - -// Utilities for making it easier to handle tests. -pub mod utils; -pub use crate::utils::*; - -mod endpoints; -pub use endpoints::{ResolvedRpcEndpoints, RpcEndpoint, RpcEndpoints}; - -mod etherscan; -mod resolve; -pub use resolve::UnresolvedEnvVarError; - -pub mod cache; -use cache::{Cache, ChainCache}; - -mod chain; -pub use chain::Chain; - -pub mod fmt; -pub use fmt::FormatterConfig; - -pub mod fs_permissions; -pub use crate::fs_permissions::FsPermissions; - -pub mod error; -pub use error::SolidityErrorCode; - -pub mod doc; -pub use doc::DocConfig; - -mod warning; -pub use warning::*; - -// helpers for fixing configuration warnings -pub mod fix; - -// reexport so cli types can implement `figment::Provider` to easily merge compiler arguments -pub use figment; -use revm_primitives::SpecId; -use tracing::warn; - -/// config providers -pub mod providers; - -use crate::{ - error::ExtractConfigError, - etherscan::{EtherscanConfigError, EtherscanConfigs, ResolvedEtherscanConfig}, -}; -use providers::*; - -mod fuzz; -pub use fuzz::{FuzzConfig, FuzzDictionaryConfig}; - -mod invariant; -use crate::fs_permissions::PathPermission; -pub use invariant::InvariantConfig; -use providers::remappings::RemappingsProvider; - -mod inline; -pub use inline::{validate_profiles, InlineConfig, InlineConfigError, InlineConfigParser, NatSpec}; - -/// Foundry configuration -/// -/// # Defaults -/// -/// All configuration values have a default, documented in the [fields](#fields) -/// section below. [`Config::default()`] returns the default values for -/// the default profile while [`Config::with_root()`] returns the values based on the given -/// directory. [`Config::load()`] starts with the default profile and merges various providers into -/// the config, same for [`Config::load_with_root()`], but there the default values are determined -/// by [`Config::with_root()`] -/// -/// # Provider Details -/// -/// `Config` is a Figment [`Provider`] with the following characteristics: -/// -/// * **Profile** -/// -/// The profile is set to the value of the `profile` field. -/// -/// * **Metadata** -/// -/// This provider is named `Foundry Config`. It does not specify a -/// [`Source`](figment::Source) and uses default interpolation. -/// -/// * **Data** -/// -/// The data emitted by this provider are the keys and values corresponding -/// to the fields and values of the structure. The dictionary is emitted to -/// the "default" meta-profile. -/// -/// Note that these behaviors differ from those of [`Config::figment()`]. -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] -pub struct Config { - /// The selected profile. **(default: _default_ `default`)** - /// - /// **Note:** This field is never serialized nor deserialized. When a - /// `Config` is merged into a `Figment` as a `Provider`, this profile is - /// selected on the `Figment`. When a `Config` is extracted, this field is - /// set to the extracting Figment's selected `Profile`. - #[serde(skip)] - pub profile: Profile, - /// path of the source contracts dir, like `src` or `contracts` - pub src: PathBuf, - /// path of the test dir - pub test: PathBuf, - /// path of the script dir - pub script: PathBuf, - /// path to where artifacts shut be written to - pub out: PathBuf, - /// all library folders to include, `lib`, `node_modules` - pub libs: Vec, - /// `Remappings` to use for this repo - pub remappings: Vec, - /// Whether to autodetect remappings by scanning the `libs` folders recursively - pub auto_detect_remappings: bool, - /// library addresses to link - pub libraries: Vec, - /// whether to enable cache - pub cache: bool, - /// where the cache is stored if enabled - pub cache_path: PathBuf, - /// where the broadcast logs are stored - pub broadcast: PathBuf, - /// additional solc allow paths for `--allow-paths` - pub allow_paths: Vec, - /// additional solc include paths for `--include-path` - pub include_paths: Vec, - /// whether to force a `project.clean()` - pub force: bool, - /// evm version to use - #[serde(with = "from_str_lowercase")] - pub evm_version: EvmVersion, - /// list of contracts to report gas of - pub gas_reports: Vec, - /// list of contracts to ignore for gas reports - pub gas_reports_ignore: Vec, - /// The Solc instance to use if any. - /// - /// This takes precedence over `auto_detect_solc`, if a version is set then this overrides - /// auto-detection. - /// - /// **Note** for backwards compatibility reasons this also accepts solc_version from the toml - /// file, see [`BackwardsCompatProvider`] - pub solc: Option, - /// whether to autodetect the solc compiler version to use - pub auto_detect_solc: bool, - /// Offline mode, if set, network access (downloading solc) is disallowed. - /// - /// Relationship with `auto_detect_solc`: - /// - if `auto_detect_solc = true` and `offline = true`, the required solc version(s) will - /// be auto detected but if the solc version is not installed, it will _not_ try to - /// install it - pub offline: bool, - /// Whether to activate optimizer - pub optimizer: bool, - /// Sets the optimizer runs - pub optimizer_runs: usize, - /// Switch optimizer components on or off in detail. - /// The "enabled" switch above provides two defaults which can be - /// tweaked here. If "details" is given, "enabled" can be omitted. - pub optimizer_details: Option, - /// Model checker settings. - pub model_checker: Option, - /// verbosity to use - pub verbosity: u8, - /// url of the rpc server that should be used for any rpc calls - pub eth_rpc_url: Option, - /// JWT secret that should be used for any rpc calls - pub eth_rpc_jwt: Option, - /// etherscan API key, or alias for an `EtherscanConfig` in `etherscan` table - pub etherscan_api_key: Option, - /// Multiple etherscan api configs and their aliases - #[serde(default, skip_serializing_if = "EtherscanConfigs::is_empty")] - pub etherscan: EtherscanConfigs, - /// list of solidity error codes to always silence in the compiler output - pub ignored_error_codes: Vec, - /// When true, compiler warnings are treated as errors - pub deny_warnings: bool, - /// Only run test functions matching the specified regex pattern. - #[serde(rename = "match_test")] - pub test_pattern: Option, - /// Only run test functions that do not match the specified regex pattern. - #[serde(rename = "no_match_test")] - pub test_pattern_inverse: Option, - /// Only run tests in contracts matching the specified regex pattern. - #[serde(rename = "match_contract")] - pub contract_pattern: Option, - /// Only run tests in contracts that do not match the specified regex pattern. - #[serde(rename = "no_match_contract")] - pub contract_pattern_inverse: Option, - /// Only run tests in source files matching the specified glob pattern. - #[serde(rename = "match_path", with = "from_opt_glob")] - pub path_pattern: Option, - /// Only run tests in source files that do not match the specified glob pattern. - #[serde(rename = "no_match_path", with = "from_opt_glob")] - pub path_pattern_inverse: Option, - /// Configuration for fuzz testing - pub fuzz: FuzzConfig, - /// Configuration for invariant testing - pub invariant: InvariantConfig, - /// Whether to allow ffi cheatcodes in test - pub ffi: bool, - /// The address which will be executing all tests - pub sender: Address, - /// The tx.origin value during EVM execution - pub tx_origin: Address, - /// the initial balance of each deployed test contract - pub initial_balance: U256, - /// the block.number value during EVM execution - pub block_number: u64, - /// pins the block number for the state fork - pub fork_block_number: Option, - /// The chain id to use - pub chain_id: Option, - /// Block gas limit - pub gas_limit: GasLimit, - /// EIP-170: Contract code size limit in bytes. Useful to increase this because of tests. - pub code_size_limit: Option, - /// `tx.gasprice` value during EVM execution" - /// - /// This is an Option, so we can determine in fork mode whether to use the config's gas price - /// (if set by user) or the remote client's gas price - pub gas_price: Option, - /// the base fee in a block - pub block_base_fee_per_gas: u64, - /// the `block.coinbase` value during EVM execution - pub block_coinbase: Address, - /// the `block.timestamp` value during EVM execution - pub block_timestamp: u64, - /// the `block.difficulty` value during EVM execution - pub block_difficulty: u64, - /// Before merge the `block.max_hash` after merge it is `block.prevrandao` - pub block_prevrandao: H256, - /// the `block.gaslimit` value during EVM execution - pub block_gas_limit: Option, - /// The memory limit of the EVM (32 MB by default) - pub memory_limit: u64, - /// Additional output selection for all contracts - /// such as "ir", "devdoc", "storageLayout", etc. - /// See [Solc Compiler Api](https://docs.soliditylang.org/en/latest/using-the-compiler.html#compiler-api) - /// - /// The following values are always set because they're required by `forge` - //{ - // "*": [ - // "abi", - // "evm.bytecode", - // "evm.deployedBytecode", - // "evm.methodIdentifiers" - // ] - // } - // "# - #[serde(default)] - pub extra_output: Vec, - /// If set , a separate `json` file will be emitted for every contract depending on the - /// selection, eg. `extra_output_files = ["metadata"]` will create a `metadata.json` for - /// each contract in the project. See [Contract Metadata](https://docs.soliditylang.org/en/latest/metadata.html) - /// - /// The difference between `extra_output = ["metadata"]` and - /// `extra_output_files = ["metadata"]` is that the former will include the - /// contract's metadata in the contract's json artifact, whereas the latter will emit the - /// output selection as separate files. - #[serde(default)] - pub extra_output_files: Vec, - /// Print the names of the compiled contracts - pub names: bool, - /// Print the sizes of the compiled contracts - pub sizes: bool, - /// If set to true, changes compilation pipeline to go through the Yul intermediate - /// representation. - pub via_ir: bool, - /// RPC storage caching settings determines what chains and endpoints to cache - pub rpc_storage_caching: StorageCachingConfig, - /// Disables storage caching entirely. This overrides any settings made in - /// `rpc_storage_caching` - pub no_storage_caching: bool, - /// Disables rate limiting entirely. This overrides any settings made in - /// `compute_units_per_second` - pub no_rpc_rate_limit: bool, - /// Multiple rpc endpoints and their aliases - #[serde(default, skip_serializing_if = "RpcEndpoints::is_empty")] - pub rpc_endpoints: RpcEndpoints, - /// Whether to store the referenced sources in the metadata as literal data. - pub use_literal_content: bool, - /// Whether to include the metadata hash. - /// - /// The metadata hash is machine dependent. By default, this is set to [BytecodeHash::None] to allow for deterministic code, See: - #[serde(with = "from_str_lowercase")] - pub bytecode_hash: BytecodeHash, - /// Whether to append the metadata hash to the bytecode. - /// - /// If this is `false` and the `bytecode_hash` option above is not `None` solc will issue a - /// warning. - pub cbor_metadata: bool, - /// How to treat revert (and require) reason strings. - #[serde(with = "serde_helpers::display_from_str_opt")] - pub revert_strings: Option, - /// Whether to compile in sparse mode - /// - /// If this option is enabled, only the required contracts/files will be selected to be - /// included in solc's output selection, see also - /// [OutputSelection](ethers_solc::artifacts::output_selection::OutputSelection) - pub sparse_mode: bool, - /// Whether to emit additional build info files - /// - /// If set to `true`, `ethers-solc` will generate additional build info json files for every - /// new build, containing the `CompilerInput` and `CompilerOutput` - pub build_info: bool, - /// The path to the `build-info` directory that contains the build info json files. - pub build_info_path: Option, - /// Configuration for `forge fmt` - pub fmt: FormatterConfig, - /// Configuration for `forge doc` - pub doc: DocConfig, - /// Configures the permissions of cheat codes that touch the file system. - /// - /// This includes what operations can be executed (read, write) - pub fs_permissions: FsPermissions, - - /// Temporary config to enable [SpecId::CANCUN] - /// - /// - /// Should be removed once EvmVersion Cancun is supported by solc - pub cancun: bool, - - /// The root path where the config detection started from, `Config::with_root` - #[doc(hidden)] - // We're skipping serialization here, so it won't be included in the [`Config::to_string()`] - // representation, but will be deserialized from the `Figment` so that forge commands can - // override it. - #[serde(rename = "root", default, skip_serializing)] - pub __root: RootPath, - /// PRIVATE: This structure may grow, As such, constructing this structure should - /// _always_ be done using a public constructor or update syntax: - /// - /// ```rust - /// use foundry_config::Config; - /// - /// let config = Config { - /// src: "other".into(), - /// ..Default::default() - /// }; - /// ``` - #[doc(hidden)] - #[serde(skip)] - pub __non_exhaustive: (), - /// Warnings gathered when loading the Config. See [`WarningsProvider`] for more information - #[serde(default, skip_serializing)] - pub __warnings: Vec, -} - -/// Mapping of fallback standalone sections. See [`FallbackProfileProvider`] -pub static STANDALONE_FALLBACK_SECTIONS: Lazy> = - Lazy::new(|| HashMap::from([("invariant", "fuzz")])); - -/// Deprecated keys. -pub static DEPRECATIONS: Lazy> = Lazy::new(|| HashMap::from([])); - -impl Config { - /// The default profile: "default" - pub const DEFAULT_PROFILE: Profile = Profile::const_new("default"); - - /// The hardhat profile: "hardhat" - pub const HARDHAT_PROFILE: Profile = Profile::const_new("hardhat"); - - /// TOML section for profiles - pub const PROFILE_SECTION: &'static str = "profile"; - - /// Standalone sections in the config which get integrated into the selected profile - pub const STANDALONE_SECTIONS: &'static [&'static str] = &[ - "rpc_endpoints", - "etherscan", - "fmt", - "doc", - "fuzz", - "invariant", - ]; - - /// File name of config toml file - pub const FILE_NAME: &'static str = "foundry.toml"; - - /// The name of the directory foundry reserves for itself under the user's home directory: `~` - pub const FOUNDRY_DIR_NAME: &'static str = ".foundry"; - - /// Default address for tx.origin - /// - /// `0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38` - pub const DEFAULT_SENDER: H160 = H160([ - 0x18, 0x04, 0xc8, 0xAB, 0x1F, 0x12, 0xE6, 0xbb, 0xF3, 0x89, 0x4D, 0x40, 0x83, 0xF3, 0x3E, - 0x07, 0x30, 0x9D, 0x1F, 0x38, - ]); - - /// Returns the current `Config` - /// - /// See `Config::figment` - #[track_caller] - pub fn load() -> Self { - Config::from_provider(Config::figment()) - } - - /// Returns the current `Config` - /// - /// See `Config::figment_with_root` - #[track_caller] - pub fn load_with_root(root: impl Into) -> Self { - Config::from_provider(Config::figment_with_root(root)) - } - - /// Extract a `Config` from `provider`, panicking if extraction fails. - /// - /// # Panics - /// - /// If extraction fails, prints an error message indicating the failure and - /// panics. For a version that doesn't panic, use [`Config::try_from()`]. - /// - /// # Example - /// - /// ```no_run - /// use foundry_config::Config; - /// use figment::providers::{Toml, Format, Env}; - /// - /// // Use foundry's default `Figment`, but allow values from `other.toml` - /// // to supersede its values. - /// let figment = Config::figment() - /// .merge(Toml::file("other.toml").nested()); - /// - /// let config = Config::from_provider(figment); - /// ``` - #[track_caller] - pub fn from_provider(provider: T) -> Self { - trace!("load config with provider: {:?}", provider.metadata()); - Self::try_from(provider).unwrap_or_else(|err| panic!("{}", err)) - } - - /// Attempts to extract a `Config` from `provider`, returning the result. - /// - /// # Example - /// - /// ```rust - /// use foundry_config::Config; - /// use figment::providers::{Toml, Format, Env}; - /// - /// // Use foundry's default `Figment`, but allow values from `other.toml` - /// // to supersede its values. - /// let figment = Config::figment() - /// .merge(Toml::file("other.toml").nested()); - /// - /// let config = Config::try_from(figment); - /// ``` - pub fn try_from(provider: T) -> Result { - let figment = Figment::from(provider); - let mut config = figment.extract::().map_err(ExtractConfigError::new)?; - config.profile = figment.profile().clone(); - Ok(config) - } - - /// The config supports relative paths and tracks the root path separately see - /// `Config::with_root` - /// - /// This joins all relative paths with the current root and attempts to make them canonic - #[must_use] - pub fn canonic(self) -> Self { - let root = self.__root.0.clone(); - self.canonic_at(root) - } - - /// Joins all relative paths with the given root so that paths that are defined as: - /// - /// ```toml - /// [profile.default] - /// src = "src" - /// out = "./out" - /// libs = ["lib", "/var/lib"] - /// ``` - /// - /// Will be made canonic with the given root: - /// - /// ```toml - /// [profile.default] - /// src = "/src" - /// out = "/out" - /// libs = ["/lib", "/var/lib"] - /// ``` - #[must_use] - pub fn canonic_at(mut self, root: impl Into) -> Self { - let root = canonic(root); - - fn p(root: &Path, rem: &Path) -> PathBuf { - canonic(root.join(rem)) - } - - self.src = p(&root, &self.src); - self.test = p(&root, &self.test); - self.script = p(&root, &self.script); - self.out = p(&root, &self.out); - self.broadcast = p(&root, &self.broadcast); - self.cache_path = p(&root, &self.cache_path); - - if let Some(build_info_path) = self.build_info_path { - self.build_info_path = Some(p(&root, &build_info_path)); - } - - self.libs = self.libs.into_iter().map(|lib| p(&root, &lib)).collect(); - - self.remappings = self - .remappings - .into_iter() - .map(|r| RelativeRemapping::new(r.into(), &root)) - .collect(); - - self.allow_paths = self - .allow_paths - .into_iter() - .map(|allow| p(&root, &allow)) - .collect(); - - self.include_paths = self - .include_paths - .into_iter() - .map(|allow| p(&root, &allow)) - .collect(); - - self.fs_permissions.join_all(&root); - - if let Some(ref mut model_checker) = self.model_checker { - model_checker.contracts = std::mem::take(&mut model_checker.contracts) - .into_iter() - .map(|(path, contracts)| { - (format!("{}", p(&root, path.as_ref()).display()), contracts) - }) - .collect(); - } - - self - } - - /// Returns a sanitized version of the Config where are paths are set correctly and potential - /// duplicates are resolved - /// - /// See [`Self::canonic`] - #[must_use] - pub fn sanitized(self) -> Self { - let mut config = self.canonic(); - - config.sanitize_remappings(); - - config.libs.sort_unstable(); - config.libs.dedup(); - - config - } - - /// Cleans up any duplicate `Remapping` and sorts them - /// - /// On windows this will convert any `\` in the remapping path into a `/` - pub fn sanitize_remappings(&mut self) { - #[cfg(target_os = "windows")] - { - // force `/` in remappings on windows - use path_slash::PathBufExt; - self.remappings.iter_mut().for_each(|r| { - r.path.path = r.path.path.to_slash_lossy().into_owned().into(); - }); - } - } - - /// Returns the directory in which dependencies should be installed - /// - /// Returns the first dir from `libs` that is not `node_modules` or `lib` if `libs` is empty - pub fn install_lib_dir(&self) -> &Path { - self.libs - .iter() - .find(|p| !p.ends_with("node_modules")) - .map(|p| p.as_path()) - .unwrap_or_else(|| Path::new("lib")) - } - - /// Serves as the entrypoint for obtaining the project. - /// - /// Returns the `Project` configured with all `solc` and path related values. - /// - /// *Note*: this also _cleans_ [`Project::cleanup`] the workspace if `force` is set to true. - /// - /// # Example - /// - /// ``` - /// use foundry_config::Config; - /// let config = Config::load_with_root(".").sanitized(); - /// let project = config.project(); - /// ``` - pub fn project(&self) -> Result { - self.create_project(true, false) - } - - /// Same as [`Self::project()`] but sets configures the project to not emit artifacts and ignore - /// cache, caching causes no output until https://github.com/gakonst/ethers-rs/issues/727 - pub fn ephemeral_no_artifacts_project(&self) -> Result { - self.create_project(false, true) - } - - fn create_project(&self, cached: bool, no_artifacts: bool) -> Result { - let mut project = Project::builder() - .artifacts(self.configured_artifacts_handler()) - .paths(self.project_paths()) - .allowed_path(&self.__root.0) - .allowed_paths(&self.libs) - .allowed_paths(&self.allow_paths) - .include_paths(&self.include_paths) - .solc_config( - SolcConfig::builder() - .settings(self.solc_settings()?) - .build(), - ) - .ignore_error_codes(self.ignored_error_codes.iter().copied().map(Into::into)) - .set_compiler_severity_filter(if self.deny_warnings { - Severity::Warning - } else { - Severity::Error - }) - .set_auto_detect(self.is_auto_detect()) - .set_offline(self.offline) - .set_cached(cached) - .set_build_info(cached & self.build_info) - .set_no_artifacts(no_artifacts) - .build()?; - - if self.force { - project.cleanup()?; - } - - if let Some(solc) = self.ensure_solc()? { - project.solc = solc; - } - - Ok(project) - } - - /// Ensures that the configured version is installed if explicitly set - /// - /// If `solc` is [`SolcReq::Version`] then this will download and install the solc version if - /// it's missing, unless the `offline` flag is enabled, in which case an error is thrown. - /// - /// If `solc` is [`SolcReq::Local`] then this will ensure that the path exists. - fn ensure_solc(&self) -> Result, SolcError> { - if let Some(ref solc) = self.solc { - let solc = match solc { - SolcReq::Version(version) => { - let v = version.to_string(); - let mut solc = Solc::find_svm_installed_version(&v)?; - if solc.is_none() { - if self.offline { - return Err(SolcError::msg(format!( - "can't install missing solc {version} in offline mode" - ))); - } - Solc::blocking_install(version)?; - solc = Solc::find_svm_installed_version(&v)?; - } - solc - } - SolcReq::Local(solc) => { - if !solc.is_file() { - return Err(SolcError::msg(format!( - "`solc` {} does not exist", - solc.display() - ))); - } - Some(Solc::new(solc)) - } - }; - return Ok(solc); - } - - Ok(None) - } - - /// Returns the [SpecId] derived from the configured [EvmVersion] - #[inline] - pub fn evm_spec_id(&self) -> SpecId { - if self.cancun { - return SpecId::CANCUN; - } - evm_spec_id(&self.evm_version) - } - - /// Returns whether the compiler version should be auto-detected - /// - /// Returns `false` if `solc_version` is explicitly set, otherwise returns the value of - /// `auto_detect_solc` - pub fn is_auto_detect(&self) -> bool { - if self.solc.is_some() { - return false; - } - self.auto_detect_solc - } - - /// Whether caching should be enabled for the given chain id - pub fn enable_caching(&self, endpoint: &str, chain_id: impl Into) -> bool { - !self.no_storage_caching - && self - .rpc_storage_caching - .enable_for_chain_id(chain_id.into()) - && self.rpc_storage_caching.enable_for_endpoint(endpoint) - } - - /// Returns the `ProjectPathsConfig` sub set of the config. - /// - /// **NOTE**: this uses the paths as they are and does __not__ modify them, see - /// `[Self::sanitized]` - /// - /// # Example - /// - /// ``` - /// use foundry_config::Config; - /// let config = Config::load_with_root(".").sanitized(); - /// let paths = config.project_paths(); - /// ``` - pub fn project_paths(&self) -> ProjectPathsConfig { - let mut builder = ProjectPathsConfig::builder() - .cache(self.cache_path.join(SOLIDITY_FILES_CACHE_FILENAME)) - .sources(&self.src) - .tests(&self.test) - .scripts(&self.script) - .artifacts(&self.out) - .libs(self.libs.clone()) - .remappings(self.get_all_remappings()); - - if let Some(build_info_path) = &self.build_info_path { - builder = builder.build_infos(build_info_path); - } - - builder.build_with_root(&self.__root.0) - } - - /// Returns all configured [`Remappings`] - /// - /// **Note:** this will add an additional `/=` remapping here, see - /// [Self::get_source_dir_remapping()] - /// - /// So that - /// - /// ```solidity - /// import "./math/math.sol"; - /// import "contracts/tokens/token.sol"; - /// ``` - /// - /// in `contracts/contract.sol` are resolved to - /// - /// ```text - /// contracts/tokens/token.sol - /// contracts/math/math.sol - /// ``` - pub fn get_all_remappings(&self) -> Vec { - self.remappings.iter().map(|m| m.clone().into()).collect() - } - - /// Returns the configured rpc jwt secret - /// - /// Returns: - /// - The jwt secret, if configured - /// - /// # Example - /// - /// ``` - /// - /// use foundry_config::Config; - /// # fn t() { - /// let config = Config::with_root("./"); - /// let rpc_jwt = config.get_rpc_jwt_secret().unwrap().unwrap(); - /// # } - /// ``` - pub fn get_rpc_jwt_secret(&self) -> Result>, UnresolvedEnvVarError> { - Ok(self - .eth_rpc_jwt - .as_ref() - .map(|jwt| Cow::Borrowed(jwt.as_str()))) - } - - /// Returns the configured rpc url - /// - /// Returns: - /// - the matching, resolved url of `rpc_endpoints` if `eth_rpc_url` is an alias - /// - the `eth_rpc_url` as-is if it isn't an alias - /// - /// # Example - /// - /// ``` - /// - /// use foundry_config::Config; - /// # fn t() { - /// let config = Config::with_root("./"); - /// let rpc_url = config.get_rpc_url().unwrap().unwrap(); - /// # } - /// ``` - pub fn get_rpc_url(&self) -> Option, UnresolvedEnvVarError>> { - let maybe_alias = self - .eth_rpc_url - .as_ref() - .or(self.etherscan_api_key.as_ref())?; - if let Some(alias) = self.get_rpc_url_with_alias(maybe_alias) { - Some(alias) - } else { - Some(Ok(Cow::Borrowed(self.eth_rpc_url.as_deref()?))) - } - } - - /// Resolves the given alias to a matching rpc url - /// - /// Returns: - /// - the matching, resolved url of `rpc_endpoints` if `maybe_alias` is an alias - /// - None otherwise - /// - /// # Example - /// - /// ``` - /// - /// use foundry_config::Config; - /// # fn t() { - /// let config = Config::with_root("./"); - /// let rpc_url = config.get_rpc_url_with_alias("mainnet").unwrap().unwrap(); - /// # } - /// ``` - pub fn get_rpc_url_with_alias( - &self, - maybe_alias: &str, - ) -> Option, UnresolvedEnvVarError>> { - let mut endpoints = self.rpc_endpoints.clone().resolved(); - Some(endpoints.remove(maybe_alias)?.map(Cow::Owned)) - } - - /// Returns the configured rpc, or the fallback url - /// - /// # Example - /// - /// ``` - /// - /// use foundry_config::Config; - /// # fn t() { - /// let config = Config::with_root("./"); - /// let rpc_url = config.get_rpc_url_or("http://localhost:8545").unwrap(); - /// # } - /// ``` - pub fn get_rpc_url_or<'a>( - &'a self, - fallback: impl Into>, - ) -> Result, UnresolvedEnvVarError> { - if let Some(url) = self.get_rpc_url() { - url - } else { - Ok(fallback.into()) - } - } - - /// Returns the configured rpc or `"http://localhost:8545"` if no `eth_rpc_url` is set - /// - /// # Example - /// - /// ``` - /// - /// use foundry_config::Config; - /// # fn t() { - /// let config = Config::with_root("./"); - /// let rpc_url = config.get_rpc_url_or_localhost_http().unwrap(); - /// # } - /// ``` - pub fn get_rpc_url_or_localhost_http(&self) -> Result, UnresolvedEnvVarError> { - self.get_rpc_url_or("http://localhost:8545") - } - - /// Returns the `EtherscanConfig` to use, if any - /// - /// Returns - /// - the matching `ResolvedEtherscanConfig` of the `etherscan` table if `etherscan_api_key` is - /// an alias - /// - the Mainnet `ResolvedEtherscanConfig` if `etherscan_api_key` is set, `None` otherwise - /// - /// # Example - /// - /// ``` - /// - /// use foundry_config::Config; - /// # fn t() { - /// let config = Config::with_root("./"); - /// let etherscan_config = config.get_etherscan_config().unwrap().unwrap(); - /// let client = etherscan_config.into_client().unwrap(); - /// # } - /// ``` - pub fn get_etherscan_config( - &self, - ) -> Option> { - let maybe_alias = self - .etherscan_api_key - .as_ref() - .or(self.eth_rpc_url.as_ref())?; - if self.etherscan.contains_key(maybe_alias) { - // etherscan points to an alias in the `etherscan` table, so we try to resolve that - let mut resolved = self.etherscan.clone().resolved(); - return resolved.remove(maybe_alias); - } - - // we treat the `etherscan_api_key` as actual API key - // if no chain provided, we assume mainnet - let chain = self.chain_id.unwrap_or(Chain::Named(Mainnet)); - let api_key = self.etherscan_api_key.as_ref()?; - ResolvedEtherscanConfig::create(api_key, chain).map(Ok) - } - - /// Same as [`Self::get_etherscan_config()`] but optionally updates the config with the given - /// `chain`, and `etherscan_api_key` - /// - /// If not matching alias was found, then this will try to find the first entry in the table - /// with a matching chain id. If an etherscan_api_key is already set it will take precedence - /// over the chain's entry in the table. - pub fn get_etherscan_config_with_chain( - &self, - chain: Option>, - ) -> Result, EtherscanConfigError> { - let chain = chain.map(Into::into); - if let Some(maybe_alias) = self - .etherscan_api_key - .as_ref() - .or(self.eth_rpc_url.as_ref()) - { - if self.etherscan.contains_key(maybe_alias) { - return self - .etherscan - .clone() - .resolved() - .remove(maybe_alias) - .transpose(); - } - } - - // try to find by comparing chain IDs after resolving - if let Some(res) = - chain.and_then(|chain| self.etherscan.clone().resolved().find_chain(chain)) - { - match (res, self.etherscan_api_key.as_ref()) { - (Ok(mut config), Some(key)) => { - // we update the key, because if an etherscan_api_key is set, it should take - // precedence over the entry, since this is usually set via env var or CLI args. - config.key = key.clone(); - return Ok(Some(config)); - } - (Ok(config), None) => return Ok(Some(config)), - (Err(err), None) => return Err(err), - (Err(_), Some(_)) => { - // use the etherscan key as fallback - } - } - } - - // etherscan fallback via API key - if let Some(key) = self.etherscan_api_key.as_ref() { - let chain = chain.or(self.chain_id).unwrap_or_default(); - return Ok(ResolvedEtherscanConfig::create(key, chain)); - } - - Ok(None) - } - - /// Helper function to just get the API key - pub fn get_etherscan_api_key(&self, chain: Option>) -> Option { - self.get_etherscan_config_with_chain(chain) - .ok() - .flatten() - .map(|c| c.key) - } - - /// Returns the remapping for the project's _src_ directory - /// - /// **Note:** this will add an additional `/=` remapping here so imports that - /// look like `import {Foo} from "src/Foo.sol";` are properly resolved. - /// - /// This is due the fact that `solc`'s VFS resolves [direct imports](https://docs.soliditylang.org/en/develop/path-resolution.html#direct-imports) that start with the source directory's name. - pub fn get_source_dir_remapping(&self) -> Option { - get_dir_remapping(&self.src) - } - - /// Returns the remapping for the project's _test_ directory, but only if it exists - pub fn get_test_dir_remapping(&self) -> Option { - if self.__root.0.join(&self.test).exists() { - get_dir_remapping(&self.test) - } else { - None - } - } - - /// Returns the remapping for the project's _script_ directory, but only if it exists - pub fn get_script_dir_remapping(&self) -> Option { - if self.__root.0.join(&self.script).exists() { - get_dir_remapping(&self.script) - } else { - None - } - } - - /// Returns the `Optimizer` based on the configured settings - pub fn optimizer(&self) -> Optimizer { - // only configure optimizer settings if optimizer is enabled - let details = if self.optimizer { - self.optimizer_details.clone() - } else { - None - }; - - Optimizer { - enabled: Some(self.optimizer), - runs: Some(self.optimizer_runs), - details, - } - } - - /// returns the [`ethers_solc::ConfigurableArtifacts`] for this config, that includes the - /// `extra_output` fields - pub fn configured_artifacts_handler(&self) -> ConfigurableArtifacts { - let mut extra_output = self.extra_output.clone(); - // Sourcify verification requires solc metadata output. Since, it doesn't - // affect the UX & performance of the compiler, output the metadata files - // by default. - // For more info see: - // Metadata is not emitted as separate file because this breaks typechain support: - if !extra_output.contains(&ContractOutputSelection::Metadata) { - extra_output.push(ContractOutputSelection::Metadata); - } - - ConfigurableArtifacts::new(extra_output, self.extra_output_files.clone()) - } - - /// Parses all libraries in the form of - /// `::` - pub fn parsed_libraries(&self) -> Result { - Libraries::parse(&self.libraries) - } - - /// Returns the configured `solc` `Settings` that includes: - /// - all libraries - /// - the optimizer (including details, if configured) - /// - evm version - pub fn solc_settings(&self) -> Result { - let libraries = self - .parsed_libraries()? - .with_applied_remappings(&self.project_paths()); - let optimizer = self.optimizer(); - - // By default if no targets are specifically selected the model checker uses all targets. - // This might be too much here, so only enable assertion checks. - // If users wish to enable all options they need to do so explicitly. - let mut model_checker = self.model_checker.clone(); - if let Some(ref mut model_checker_settings) = model_checker { - if model_checker_settings.targets.is_none() { - model_checker_settings.targets = Some(vec![ModelCheckerTarget::Assert]); - } - } - - let mut settings = Settings { - optimizer, - evm_version: Some(self.evm_version), - libraries, - metadata: Some(SettingsMetadata { - use_literal_content: Some(self.use_literal_content), - bytecode_hash: Some(self.bytecode_hash), - cbor_metadata: Some(self.cbor_metadata), - }), - debug: self.revert_strings.map(|revert_strings| DebuggingSettings { - revert_strings: Some(revert_strings), - debug_info: Vec::new(), - }), - model_checker, - ..Default::default() - } - .with_extra_output(self.configured_artifacts_handler().output_selection()) - .with_ast(); - - if self.via_ir { - settings = settings.with_via_ir(); - } - - Ok(settings) - } - - /// Returns the default figment - /// - /// The default figment reads from the following sources, in ascending - /// priority order: - /// - /// 1. [`Config::default()`] (see [defaults](#defaults)) - /// 2. `foundry.toml` _or_ filename in `FOUNDRY_CONFIG` environment variable - /// 3. `FOUNDRY_` prefixed environment variables - /// - /// The profile selected is the value set in the `FOUNDRY_PROFILE` - /// environment variable. If it is not set, it defaults to `default`. - /// - /// # Example - /// - /// ```rust - /// use foundry_config::Config; - /// use serde::Deserialize; - /// - /// let my_config = Config::figment().extract::(); - /// ``` - pub fn figment() -> Figment { - Config::default().into() - } - - /// Returns the default figment enhanced with additional context extracted from the provided - /// root, like remappings and directories. - /// - /// # Example - /// - /// ```rust - /// use foundry_config::Config; - /// use serde::Deserialize; - /// - /// let my_config = Config::figment_with_root(".").extract::(); - /// ``` - pub fn figment_with_root(root: impl Into) -> Figment { - Self::with_root(root).into() - } - - /// Creates a new Config that adds additional context extracted from the provided root. - /// - /// # Example - /// - /// ```rust - /// use foundry_config::Config; - /// let my_config = Config::with_root("."); - /// ``` - pub fn with_root(root: impl Into) -> Self { - // autodetect paths - let root = root.into(); - let paths = ProjectPathsConfig::builder().build_with_root(&root); - let artifacts: PathBuf = paths.artifacts.file_name().unwrap().into(); - Config { - __root: paths.root.into(), - src: paths.sources.file_name().unwrap().into(), - out: artifacts.clone(), - libs: paths - .libraries - .into_iter() - .map(|lib| lib.file_name().unwrap().into()) - .collect(), - remappings: paths - .remappings - .into_iter() - .map(|r| RelativeRemapping::new(r, &root)) - .collect(), - fs_permissions: FsPermissions::new([PathPermission::read(artifacts)]), - ..Config::default() - } - } - - /// Returns the default config but with hardhat paths - pub fn hardhat() -> Self { - Config { - src: "contracts".into(), - out: "artifacts".into(), - libs: vec!["node_modules".into()], - ..Config::default() - } - } - - /// Returns the default config that uses dapptools style paths - pub fn dapptools() -> Self { - Config { - chain_id: Some(Chain::Id(99)), - block_timestamp: 0, - block_number: 0, - ..Config::default() - } - } - - /// Extracts a basic subset of the config, used for initialisations. - /// - /// # Example - /// - /// ```rust - /// use foundry_config::Config; - /// let my_config = Config::with_root(".").into_basic(); - /// ``` - pub fn into_basic(self) -> BasicConfig { - BasicConfig { - profile: self.profile, - src: self.src, - out: self.out, - libs: self.libs, - remappings: self.remappings, - } - } - - /// Updates the `foundry.toml` file for the given `root` based on the provided closure. - /// - /// **Note:** the closure will only be invoked if the `foundry.toml` file exists, See - /// [Self::get_config_path()] and if the closure returns `true`. - pub fn update_at(root: impl Into, f: F) -> eyre::Result<()> - where - F: FnOnce(&Config, &mut toml_edit::Document) -> bool, - { - let config = Self::load_with_root(root).sanitized(); - config.update(|doc| f(&config, doc)) - } - - /// Updates the `foundry.toml` file this `Config` ias based on with the provided closure. - /// - /// **Note:** the closure will only be invoked if the `foundry.toml` file exists, See - /// [Self::get_config_path()] and if the closure returns `true` - pub fn update(&self, f: F) -> eyre::Result<()> - where - F: FnOnce(&mut toml_edit::Document) -> bool, - { - let file_path = self.get_config_path(); - if !file_path.exists() { - return Ok(()); - } - let contents = fs::read_to_string(&file_path)?; - let mut doc = contents.parse::()?; - if f(&mut doc) { - fs::write(file_path, doc.to_string())?; - } - Ok(()) - } - - /// Sets the `libs` entry inside a `foundry.toml` file but only if it exists - /// - /// # Errors - /// - /// An error if the `foundry.toml` could not be parsed. - pub fn update_libs(&self) -> eyre::Result<()> { - self.update(|doc| { - let profile = self.profile.as_str().as_str(); - let root = &self.__root.0; - let libs: toml_edit::Value = self - .libs - .iter() - .map(|path| { - let path = if let Ok(relative) = path.strip_prefix(root) { - relative - } else { - path - }; - toml_edit::Value::from(&*path.to_string_lossy()) - }) - .collect(); - let libs = toml_edit::value(libs); - doc[Config::PROFILE_SECTION][profile]["libs"] = libs; - true - }) - } - - /// Serialize the config type as a String of TOML. - /// - /// This serializes to a table with the name of the profile - /// - /// ```toml - /// [profile.default] - /// src = "src" - /// out = "out" - /// libs = ["lib"] - /// # ... - /// ``` - pub fn to_string_pretty(&self) -> Result { - // serializing to value first to prevent `ValueAfterTable` errors - let mut value = toml::Value::try_from(self)?; - // Config map always gets serialized as a table - let value_table = value.as_table_mut().unwrap(); - // remove standalone sections from inner table - let standalone_sections = Config::STANDALONE_SECTIONS - .iter() - .filter_map(|section| { - let section = section.to_string(); - value_table.remove(§ion).map(|value| (section, value)) - }) - .collect::>(); - // wrap inner table in [profile.] - let mut wrapping_table = [( - Config::PROFILE_SECTION.into(), - toml::Value::Table([(self.profile.to_string(), value)].into_iter().collect()), - )] - .into_iter() - .collect::>(); - // insert standalone sections - for (section, value) in standalone_sections { - wrapping_table.insert(section, value); - } - // stringify - toml::to_string_pretty(&toml::Value::Table(wrapping_table)) - } - - /// Returns the path to the `foundry.toml` of this `Config` - pub fn get_config_path(&self) -> PathBuf { - self.__root.0.join(Config::FILE_NAME) - } - - /// Returns the selected profile - /// - /// If the `FOUNDRY_PROFILE` env variable is not set, this returns the `DEFAULT_PROFILE` - pub fn selected_profile() -> Profile { - Profile::from_env_or("FOUNDRY_PROFILE", Config::DEFAULT_PROFILE) - } - - /// Returns the path to foundry's global toml file that's stored at `~/.foundry/foundry.toml` - pub fn foundry_dir_toml() -> Option { - Self::foundry_dir().map(|p| p.join(Config::FILE_NAME)) - } - - /// Returns the path to foundry's config dir `~/.foundry/` - pub fn foundry_dir() -> Option { - dirs_next::home_dir().map(|p| p.join(Config::FOUNDRY_DIR_NAME)) - } - - /// Returns the path to foundry's cache dir `~/.foundry/cache` - pub fn foundry_cache_dir() -> Option { - Self::foundry_dir().map(|p| p.join("cache")) - } - - /// Returns the path to foundry rpc cache dir `~/.foundry/cache/rpc` - pub fn foundry_rpc_cache_dir() -> Option { - Some(Self::foundry_cache_dir()?.join("rpc")) - } - /// Returns the path to foundry chain's cache dir `~/.foundry/cache/rpc/` - pub fn foundry_chain_cache_dir(chain_id: impl Into) -> Option { - Some(Self::foundry_rpc_cache_dir()?.join(chain_id.into().to_string())) - } - - /// Returns the path to foundry's etherscan cache dir `~/.foundry/cache/etherscan` - pub fn foundry_etherscan_cache_dir() -> Option { - Some(Self::foundry_cache_dir()?.join("etherscan")) - } - - /// Returns the path to foundry's keystores dir `~/.foundry/keystores` - pub fn foundry_keystores_dir() -> Option { - Some(Self::foundry_dir()?.join("keystores")) - } - - /// Returns the path to foundry's etherscan cache dir for `chain_id` - /// `~/.foundry/cache/etherscan/` - pub fn foundry_etherscan_chain_cache_dir(chain_id: impl Into) -> Option { - Some(Self::foundry_etherscan_cache_dir()?.join(chain_id.into().to_string())) - } - - /// Returns the path to the cache dir of the `block` on the `chain` - /// `~/.foundry/cache/rpc// - pub fn foundry_block_cache_dir(chain_id: impl Into, block: u64) -> Option { - Some(Self::foundry_chain_cache_dir(chain_id)?.join(format!("{block}"))) - } - - /// Returns the path to the cache file of the `block` on the `chain` - /// `~/.foundry/cache/rpc///storage.json` - pub fn foundry_block_cache_file(chain_id: impl Into, block: u64) -> Option { - Some(Self::foundry_block_cache_dir(chain_id, block)?.join("storage.json")) - } - - #[doc = r#"Returns the path to `foundry`'s data directory inside the user's data directory - |Platform | Value | Example | - | ------- | ------------------------------------- | -------------------------------- | - | Linux | `$XDG_CONFIG_HOME` or `$HOME`/.config/foundry | /home/alice/.config/foundry| - | macOS | `$HOME`/Library/Application Support/foundry | /Users/Alice/Library/Application Support/foundry | - | Windows | `{FOLDERID_RoamingAppData}/foundry` | C:\Users\Alice\AppData\Roaming/foundry | - "#] - pub fn data_dir() -> eyre::Result { - let path = dirs_next::data_dir() - .wrap_err("Failed to find data directory")? - .join("foundry"); - std::fs::create_dir_all(&path).wrap_err("Failed to create module directory")?; - Ok(path) - } - - /// Returns the path to the `foundry.toml` file, the file is searched for in - /// the current working directory and all parent directories until the root, - /// and the first hit is used. - /// - /// If this search comes up empty, then it checks if a global `foundry.toml` exists at - /// `~/.foundry/foundry.tol`, see [`Self::foundry_dir_toml()`] - pub fn find_config_file() -> Option { - fn find(path: &Path) -> Option { - if path.is_absolute() { - return match path.is_file() { - true => Some(path.to_path_buf()), - false => None, - }; - } - let cwd = std::env::current_dir().ok()?; - let mut cwd = cwd.as_path(); - loop { - let file_path = cwd.join(path); - if file_path.is_file() { - return Some(file_path); - } - cwd = cwd.parent()?; - } - } - find(Env::var_or("FOUNDRY_CONFIG", Config::FILE_NAME).as_ref()) - .or_else(|| Self::foundry_dir_toml().filter(|p| p.exists())) - } - - /// Clears the foundry cache - pub fn clean_foundry_cache() -> eyre::Result<()> { - if let Some(cache_dir) = Config::foundry_cache_dir() { - let path = cache_dir.as_path(); - let _ = fs::remove_dir_all(path); - } else { - eyre::bail!("failed to get foundry_cache_dir"); - } - - Ok(()) - } - - /// Clears the foundry cache for `chain` - pub fn clean_foundry_chain_cache(chain: Chain) -> eyre::Result<()> { - if let Some(cache_dir) = Config::foundry_chain_cache_dir(chain) { - let path = cache_dir.as_path(); - let _ = fs::remove_dir_all(path); - } else { - eyre::bail!("failed to get foundry_chain_cache_dir"); - } - - Ok(()) - } - - /// Clears the foundry cache for `chain` and `block` - pub fn clean_foundry_block_cache(chain: Chain, block: u64) -> eyre::Result<()> { - if let Some(cache_dir) = Config::foundry_block_cache_dir(chain, block) { - let path = cache_dir.as_path(); - let _ = fs::remove_dir_all(path); - } else { - eyre::bail!("failed to get foundry_block_cache_dir"); - } - - Ok(()) - } - - /// Clears the foundry etherscan cache - pub fn clean_foundry_etherscan_cache() -> eyre::Result<()> { - if let Some(cache_dir) = Config::foundry_etherscan_cache_dir() { - let path = cache_dir.as_path(); - let _ = fs::remove_dir_all(path); - } else { - eyre::bail!("failed to get foundry_etherscan_cache_dir"); - } - - Ok(()) - } - - /// Clears the foundry etherscan cache for `chain` - pub fn clean_foundry_etherscan_chain_cache(chain: Chain) -> eyre::Result<()> { - if let Some(cache_dir) = Config::foundry_etherscan_chain_cache_dir(chain) { - let path = cache_dir.as_path(); - let _ = fs::remove_dir_all(path); - } else { - eyre::bail!( - "failed to get foundry_etherscan_cache_dir for chain: {}", - chain - ); - } - - Ok(()) - } - - /// List the data in the foundry cache - pub fn list_foundry_cache() -> eyre::Result { - if let Some(cache_dir) = Config::foundry_rpc_cache_dir() { - let mut cache = Cache { chains: vec![] }; - if !cache_dir.exists() { - return Ok(cache); - } - if let Ok(entries) = cache_dir.as_path().read_dir() { - for entry in entries.flatten().filter(|x| x.path().is_dir()) { - match Chain::from_str(&entry.file_name().to_string_lossy()) { - Ok(chain) => cache.chains.push(Self::list_foundry_chain_cache(chain)?), - Err(_) => continue, - } - } - Ok(cache) - } else { - eyre::bail!("failed to access foundry_cache_dir"); - } - } else { - eyre::bail!("failed to get foundry_cache_dir"); - } - } - - /// List the cached data for `chain` - pub fn list_foundry_chain_cache(chain: Chain) -> eyre::Result { - let block_explorer_data_size = match Config::foundry_etherscan_chain_cache_dir(chain) { - Some(cache_dir) => Self::get_cached_block_explorer_data(&cache_dir)?, - None => { - warn!("failed to access foundry_etherscan_chain_cache_dir"); - 0 - } - }; - - if let Some(cache_dir) = Config::foundry_chain_cache_dir(chain) { - let blocks = Self::get_cached_blocks(&cache_dir)?; - Ok(ChainCache { - name: chain.to_string(), - blocks, - block_explorer: block_explorer_data_size, - }) - } else { - eyre::bail!("failed to get foundry_chain_cache_dir"); - } - } - - //The path provided to this function should point to a cached chain folder - fn get_cached_blocks(chain_path: &Path) -> eyre::Result> { - let mut blocks = vec![]; - if !chain_path.exists() { - return Ok(blocks); - } - for block in chain_path - .read_dir()? - .flatten() - .filter(|x| x.file_type().unwrap().is_dir()) - { - let filepath = block.path().join("storage.json"); - blocks.push(( - block.file_name().to_string_lossy().into_owned(), - fs::metadata(filepath)?.len(), - )); - } - Ok(blocks) - } - - //The path provided to this function should point to the etherscan cache for a chain - fn get_cached_block_explorer_data(chain_path: &Path) -> eyre::Result { - if !chain_path.exists() { - return Ok(0); - } - - fn dir_size_recursive(mut dir: fs::ReadDir) -> eyre::Result { - dir.try_fold(0, |acc, file| { - let file = file?; - let size = match file.metadata()? { - data if data.is_dir() => dir_size_recursive(fs::read_dir(file.path())?)?, - data => data.len(), - }; - Ok(acc + size) - }) - } - - dir_size_recursive(fs::read_dir(chain_path)?) - } - - fn merge_toml_provider( - mut figment: Figment, - toml_provider: impl Provider, - profile: Profile, - ) -> Figment { - figment = figment.select(profile.clone()); - - // add warnings - figment = { - let warnings = WarningsProvider::for_figment(&toml_provider, &figment); - figment.merge(warnings) - }; - - // use [profile.] as [] - let mut profiles = vec![Config::DEFAULT_PROFILE]; - if profile != Config::DEFAULT_PROFILE { - profiles.push(profile.clone()); - } - let provider = toml_provider.strict_select(profiles); - - // apply any key fixes - let provider = BackwardsCompatTomlProvider(ForcedSnakeCaseData(provider)); - - // merge the default profile as a base - if profile != Config::DEFAULT_PROFILE { - figment = figment.merge(provider.rename(Config::DEFAULT_PROFILE, profile.clone())); - } - // merge special keys into config - for standalone_key in Config::STANDALONE_SECTIONS { - if let Some(fallback) = STANDALONE_FALLBACK_SECTIONS.get(standalone_key) { - figment = figment.merge( - provider - .fallback(standalone_key, fallback) - .wrap(profile.clone(), standalone_key), - ); - } else { - figment = figment.merge(provider.wrap(profile.clone(), standalone_key)); - } - } - // merge the profile - figment = figment.merge(provider); - figment - } -} - -impl From for Figment { - fn from(c: Config) -> Figment { - let profile = Config::selected_profile(); - let mut figment = Figment::default().merge(DappHardhatDirProvider(&c.__root.0)); - - // merge global foundry.toml file - if let Some(global_toml) = Config::foundry_dir_toml().filter(|p| p.exists()) { - figment = Config::merge_toml_provider( - figment, - TomlFileProvider::new(None, global_toml).cached(), - profile.clone(), - ); - } - // merge local foundry.toml file - figment = Config::merge_toml_provider( - figment, - TomlFileProvider::new(Some("FOUNDRY_CONFIG"), c.__root.0.join(Config::FILE_NAME)) - .cached(), - profile.clone(), - ); - - // merge environment variables - figment = figment - .merge( - Env::prefixed("DAPP_") - .ignore(&["REMAPPINGS", "LIBRARIES", "FFI", "FS_PERMISSIONS"]) - .global(), - ) - .merge( - Env::prefixed("DAPP_TEST_") - .ignore(&["CACHE", "FUZZ_RUNS", "DEPTH", "FFI", "FS_PERMISSIONS"]) - .global(), - ) - .merge(DappEnvCompatProvider) - .merge(Env::raw().only(&["ETHERSCAN_API_KEY"])) - .merge( - Env::prefixed("FOUNDRY_") - .ignore(&[ - "PROFILE", - "REMAPPINGS", - "LIBRARIES", - "FFI", - "FS_PERMISSIONS", - ]) - .map(|key| { - let key = key.as_str(); - if Config::STANDALONE_SECTIONS.iter().any(|section| { - key.starts_with(&format!("{}_", section.to_ascii_uppercase())) - }) { - key.replacen('_', ".", 1).into() - } else { - key.into() - } - }) - .global(), - ) - .select(profile.clone()); - - // we try to merge remappings after we've merged all other providers, this prevents - // redundant fs lookups to determine the default remappings that are eventually updated by - // other providers, like the toml file - let remappings = RemappingsProvider { - auto_detect_remappings: figment - .extract_inner::("auto_detect_remappings") - .unwrap_or(true), - lib_paths: figment - .extract_inner::>("libs") - .map(Cow::Owned) - .unwrap_or_else(|_| Cow::Borrowed(&c.libs)), - root: &c.__root.0, - remappings: figment.extract_inner::>("remappings"), - }; - let merge = figment.merge(remappings); - - Figment::from(c).merge(merge).select(profile) - } -} - -/// Wrapper type for `regex::Regex` that implements `PartialEq` -#[derive(Debug, Clone, Deserialize, Serialize)] -#[serde(transparent)] -pub struct RegexWrapper { - #[serde(with = "serde_regex")] - inner: regex::Regex, -} - -impl std::ops::Deref for RegexWrapper { - type Target = regex::Regex; - - fn deref(&self) -> &Self::Target { - &self.inner - } -} - -impl std::cmp::PartialEq for RegexWrapper { - fn eq(&self, other: &Self) -> bool { - self.as_str() == other.as_str() - } -} - -impl From for regex::Regex { - fn from(wrapper: RegexWrapper) -> Self { - wrapper.inner - } -} - -impl From for RegexWrapper { - fn from(re: Regex) -> Self { - RegexWrapper { inner: re } - } -} - -/// Ser/de `globset::Glob` explicitly to handle `Option` properly -pub(crate) mod from_opt_glob { - use serde::{Deserialize, Deserializer, Serializer}; - - pub fn serialize(value: &Option, serializer: S) -> Result - where - S: Serializer, - { - match value { - Some(glob) => serializer.serialize_str(glob.glob()), - None => serializer.serialize_none(), - } - } - - pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> - where - D: Deserializer<'de>, - { - let s: Option = Option::deserialize(deserializer)?; - if let Some(s) = s { - return Ok(Some( - globset::Glob::new(&s).map_err(serde::de::Error::custom)?, - )); - } - Ok(None) - } -} - -/// A helper wrapper around the root path used during Config detection -#[derive(Debug, PartialEq, Eq, Hash, Clone, PartialOrd, Ord, Deserialize, Serialize)] -#[serde(transparent)] -pub struct RootPath(pub PathBuf); - -impl Default for RootPath { - fn default() -> Self { - ".".into() - } -} - -impl> From

for RootPath { - fn from(p: P) -> Self { - RootPath(p.into()) - } -} - -impl AsRef for RootPath { - fn as_ref(&self) -> &Path { - &self.0 - } -} - -/// Parses a config profile -/// -/// All `Profile` date is ignored by serde, however the `Config::to_string_pretty` includes it and -/// returns a toml table like -/// -/// ```toml -/// #[profile.default] -/// src = "..." -/// ``` -/// This ignores the `#[profile.default]` part in the toml -pub fn parse_with_profile( - s: &str, -) -> Result, Error> { - let figment = Config::merge_toml_provider( - Figment::new(), - Toml::string(s).nested(), - Config::DEFAULT_PROFILE, - ); - if figment.profiles().any(|p| p == Config::DEFAULT_PROFILE) { - Ok(Some(( - Config::DEFAULT_PROFILE, - figment.select(Config::DEFAULT_PROFILE).extract()?, - ))) - } else { - Ok(None) - } -} - -impl Provider for Config { - fn metadata(&self) -> Metadata { - Metadata::named("Foundry Config") - } - - #[track_caller] - fn data(&self) -> Result, figment::Error> { - let mut data = Serialized::defaults(self).data()?; - if let Some(entry) = data.get_mut(&self.profile) { - entry.insert("root".to_string(), Value::serialize(self.__root.clone())?); - } - Ok(data) - } - - fn profile(&self) -> Option { - Some(self.profile.clone()) - } -} - -impl Default for Config { - fn default() -> Self { - Self { - profile: Self::DEFAULT_PROFILE, - fs_permissions: FsPermissions::new([PathPermission::read("out")]), - cancun: false, - __root: Default::default(), - src: "src".into(), - test: "test".into(), - script: "script".into(), - out: "out".into(), - libs: vec!["lib".into()], - cache: true, - cache_path: "cache".into(), - broadcast: "broadcast".into(), - allow_paths: vec![], - include_paths: vec![], - force: false, - evm_version: EvmVersion::Paris, - gas_reports: vec!["*".to_string()], - gas_reports_ignore: vec![], - solc: None, - auto_detect_solc: true, - offline: false, - optimizer: true, - optimizer_runs: 200, - optimizer_details: None, - model_checker: None, - extra_output: Default::default(), - extra_output_files: Default::default(), - names: false, - sizes: false, - test_pattern: None, - test_pattern_inverse: None, - contract_pattern: None, - contract_pattern_inverse: None, - path_pattern: None, - path_pattern_inverse: None, - fuzz: Default::default(), - invariant: Default::default(), - ffi: false, - sender: Config::DEFAULT_SENDER, - tx_origin: Config::DEFAULT_SENDER, - initial_balance: U256::from(0xffffffffffffffffffffffffu128), - block_number: 1, - fork_block_number: None, - chain_id: None, - gas_limit: i64::MAX.into(), - code_size_limit: None, - gas_price: None, - block_base_fee_per_gas: 0, - block_coinbase: Address::zero(), - block_timestamp: 1, - block_difficulty: 0, - block_prevrandao: Default::default(), - block_gas_limit: None, - memory_limit: 2u64.pow(25), - eth_rpc_url: None, - eth_rpc_jwt: None, - etherscan_api_key: None, - verbosity: 0, - remappings: vec![], - auto_detect_remappings: true, - libraries: vec![], - ignored_error_codes: vec![ - SolidityErrorCode::SpdxLicenseNotProvided, - SolidityErrorCode::ContractExceeds24576Bytes, - SolidityErrorCode::ContractInitCodeSizeExceeds49152Bytes, - ], - deny_warnings: false, - via_ir: false, - rpc_storage_caching: Default::default(), - rpc_endpoints: Default::default(), - etherscan: Default::default(), - no_storage_caching: false, - no_rpc_rate_limit: false, - use_literal_content: false, - bytecode_hash: BytecodeHash::Ipfs, - cbor_metadata: true, - revert_strings: None, - sparse_mode: false, - build_info: false, - build_info_path: None, - fmt: Default::default(), - doc: Default::default(), - __non_exhaustive: (), - __warnings: vec![], - } - } -} - -/// Wrapper for the config's `gas_limit` value necessary because toml-rs can't handle larger number because integers are stored signed: -/// -/// Due to this limitation this type will be serialized/deserialized as String if it's larger than -/// `i64` -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct GasLimit(pub u64); - -impl From for GasLimit { - fn from(gas: u64) -> Self { - Self(gas) - } -} -impl From for GasLimit { - fn from(gas: i64) -> Self { - Self(gas as u64) - } -} -impl From for GasLimit { - fn from(gas: i32) -> Self { - Self(gas as u64) - } -} -impl From for GasLimit { - fn from(gas: u32) -> Self { - Self(gas as u64) - } -} - -impl From for u64 { - fn from(gas: GasLimit) -> Self { - gas.0 - } -} - -impl Serialize for GasLimit { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - if self.0 > i64::MAX as u64 { - serializer.serialize_str(&self.0.to_string()) - } else { - serializer.serialize_u64(self.0) - } - } -} - -impl<'de> Deserialize<'de> for GasLimit { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - use serde::de::Error; - - #[derive(Deserialize)] - #[serde(untagged)] - enum Gas { - Number(u64), - Text(String), - } - - let gas = match Gas::deserialize(deserializer)? { - Gas::Number(num) => GasLimit(num), - Gas::Text(s) => match s.as_str() { - "max" | "MAX" | "Max" | "u64::MAX" | "u64::Max" => GasLimit(u64::MAX), - s => GasLimit(s.parse().map_err(D::Error::custom)?), - }, - }; - - Ok(gas) - } -} - -/// Variants for selecting the [`Solc`] instance -#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] -#[serde(untagged)] -pub enum SolcReq { - /// Requires a specific solc version, that's either already installed (via `svm`) or will be - /// auto installed (via `svm`) - Version(Version), - /// Path to an existing local solc installation - Local(PathBuf), -} - -impl> From for SolcReq { - fn from(s: T) -> Self { - let s = s.as_ref(); - if let Ok(v) = Version::from_str(s) { - SolcReq::Version(v) - } else { - SolcReq::Local(s.into()) - } - } -} - -/// A convenience provider to retrieve a toml file. -/// This will return an error if the env var is set but the file does not exist -struct TomlFileProvider { - pub env_var: Option<&'static str>, - pub default: PathBuf, - pub cache: Option, Error>>, -} - -impl TomlFileProvider { - fn new(env_var: Option<&'static str>, default: impl Into) -> Self { - Self { - env_var, - default: default.into(), - cache: None, - } - } - - fn env_val(&self) -> Option { - self.env_var.and_then(Env::var) - } - - fn file(&self) -> PathBuf { - self.env_val() - .map(PathBuf::from) - .unwrap_or_else(|| self.default.clone()) - } - - fn is_missing(&self) -> bool { - if let Some(file) = self.env_val() { - let path = Path::new(&file); - if !path.exists() { - return true; - } - } - false - } - - pub fn cached(mut self) -> Self { - self.cache = Some(self.read()); - self - } - - fn read(&self) -> Result, Error> { - use serde::de::Error as _; - if let Some(file) = self.env_val() { - let path = Path::new(&file); - if !path.exists() { - return Err(Error::custom(format!( - "Config file `{}` set in env var `{}` does not exist", - file, - self.env_var.unwrap() - ))); - } - Toml::file(file) - } else { - Toml::file(&self.default) - } - .nested() - .data() - } -} - -impl Provider for TomlFileProvider { - fn metadata(&self) -> Metadata { - if self.is_missing() { - Metadata::named("TOML file provider") - } else { - Toml::file(self.file()).nested().metadata() - } - } - - fn data(&self) -> Result, Error> { - if let Some(cache) = self.cache.as_ref() { - cache.clone() - } else { - self.read() - } - } -} - -/// A Provider that ensures all keys are snake case if they're not standalone sections, See -/// `Config::STANDALONE_SECTIONS` -struct ForcedSnakeCaseData

(P); - -impl Provider for ForcedSnakeCaseData

{ - fn metadata(&self) -> Metadata { - self.0.metadata() - } - - fn data(&self) -> Result, Error> { - let mut map = Map::new(); - for (profile, dict) in self.0.data()? { - if Config::STANDALONE_SECTIONS.contains(&profile.as_ref()) { - // don't force snake case for keys in standalone sections - map.insert(profile, dict); - continue; - } - map.insert( - profile, - dict.into_iter() - .map(|(k, v)| (k.to_snake_case(), v)) - .collect(), - ); - } - Ok(map) - } -} - -/// A Provider that handles breaking changes in toml files -struct BackwardsCompatTomlProvider

(P); - -impl Provider for BackwardsCompatTomlProvider

{ - fn metadata(&self) -> Metadata { - self.0.metadata() - } - - fn data(&self) -> Result, Error> { - let mut map = Map::new(); - let solc_env = std::env::var("FOUNDRY_SOLC_VERSION") - .or_else(|_| std::env::var("DAPP_SOLC_VERSION")) - .map(Value::from) - .ok(); - for (profile, mut dict) in self.0.data()? { - if let Some(v) = solc_env.clone().or_else(|| dict.remove("solc_version")) { - dict.insert("solc".to_string(), v); - } - map.insert(profile, dict); - } - Ok(map) - } -} - -/// A provider that sets the `src` and `output` path depending on their existence. -struct DappHardhatDirProvider<'a>(&'a Path); - -impl<'a> Provider for DappHardhatDirProvider<'a> { - fn metadata(&self) -> Metadata { - Metadata::named("Dapp Hardhat dir compat") - } - - fn data(&self) -> Result, Error> { - let mut dict = Dict::new(); - dict.insert( - "src".to_string(), - ProjectPathsConfig::find_source_dir(self.0) - .file_name() - .unwrap() - .to_string_lossy() - .to_string() - .into(), - ); - dict.insert( - "out".to_string(), - ProjectPathsConfig::find_artifacts_dir(self.0) - .file_name() - .unwrap() - .to_string_lossy() - .to_string() - .into(), - ); - - // detect libs folders: - // if `lib` _and_ `node_modules` exists: include both - // if only `node_modules` exists: include `node_modules` - // include `lib` otherwise - let mut libs = vec![]; - let node_modules = self.0.join("node_modules"); - let lib = self.0.join("lib"); - if node_modules.exists() { - if lib.exists() { - libs.push(lib.file_name().unwrap().to_string_lossy().to_string()); - } - libs.push( - node_modules - .file_name() - .unwrap() - .to_string_lossy() - .to_string(), - ); - } else { - libs.push(lib.file_name().unwrap().to_string_lossy().to_string()); - } - - dict.insert("libs".to_string(), libs.into()); - - Ok(Map::from([(Config::selected_profile(), dict)])) - } -} - -/// A provider that checks for DAPP_ env vars that are named differently than FOUNDRY_ -struct DappEnvCompatProvider; - -impl Provider for DappEnvCompatProvider { - fn metadata(&self) -> Metadata { - Metadata::named("Dapp env compat") - } - - fn data(&self) -> Result, Error> { - use serde::de::Error as _; - use std::env; - - let mut dict = Dict::new(); - if let Ok(val) = env::var("DAPP_TEST_NUMBER") { - dict.insert( - "block_number".to_string(), - val.parse::().map_err(figment::Error::custom)?.into(), - ); - } - if let Ok(val) = env::var("DAPP_TEST_ADDRESS") { - dict.insert("sender".to_string(), val.into()); - } - if let Ok(val) = env::var("DAPP_FORK_BLOCK") { - dict.insert( - "fork_block_number".to_string(), - val.parse::().map_err(figment::Error::custom)?.into(), - ); - } else if let Ok(val) = env::var("DAPP_TEST_NUMBER") { - dict.insert( - "fork_block_number".to_string(), - val.parse::().map_err(figment::Error::custom)?.into(), - ); - } - if let Ok(val) = env::var("DAPP_TEST_TIMESTAMP") { - dict.insert( - "block_timestamp".to_string(), - val.parse::().map_err(figment::Error::custom)?.into(), - ); - } - if let Ok(val) = env::var("DAPP_BUILD_OPTIMIZE_RUNS") { - dict.insert( - "optimizer_runs".to_string(), - val.parse::().map_err(figment::Error::custom)?.into(), - ); - } - if let Ok(val) = env::var("DAPP_BUILD_OPTIMIZE") { - // Activate Solidity optimizer (0 or 1) - let val = val.parse::().map_err(figment::Error::custom)?; - if val > 1 { - return Err(format!( - "Invalid $DAPP_BUILD_OPTIMIZE value `{val}`, expected 0 or 1" - ) - .into()); - } - dict.insert("optimizer".to_string(), (val == 1).into()); - } - - // libraries in env vars either as `[..]` or single string separated by comma - if let Ok(val) = env::var("DAPP_LIBRARIES").or_else(|_| env::var("FOUNDRY_LIBRARIES")) { - dict.insert("libraries".to_string(), utils::to_array_value(&val)?); - } - - let mut fuzz_dict = Dict::new(); - if let Ok(val) = env::var("DAPP_TEST_FUZZ_RUNS") { - fuzz_dict.insert( - "runs".to_string(), - val.parse::().map_err(figment::Error::custom)?.into(), - ); - } - dict.insert("fuzz".to_string(), fuzz_dict.into()); - - let mut invariant_dict = Dict::new(); - if let Ok(val) = env::var("DAPP_TEST_DEPTH") { - invariant_dict.insert( - "depth".to_string(), - val.parse::().map_err(figment::Error::custom)?.into(), - ); - } - dict.insert("invariant".to_string(), invariant_dict.into()); - - Ok(Map::from([(Config::selected_profile(), dict)])) - } -} - -/// Renames a profile from `from` to `to -/// -/// For example given: -/// -/// ```toml -/// [from] -/// key = "value" -/// ``` -/// -/// RenameProfileProvider will output -/// -/// ```toml -/// [to] -/// key = "value" -/// ``` -struct RenameProfileProvider

{ - provider: P, - from: Profile, - to: Profile, -} - -impl

RenameProfileProvider

{ - pub fn new(provider: P, from: impl Into, to: impl Into) -> Self { - Self { - provider, - from: from.into(), - to: to.into(), - } - } -} - -impl Provider for RenameProfileProvider

{ - fn metadata(&self) -> Metadata { - self.provider.metadata() - } - fn data(&self) -> Result, Error> { - let mut data = self.provider.data()?; - if let Some(data) = data.remove(&self.from) { - return Ok(Map::from([(self.to.clone(), data)])); - } - Ok(Default::default()) - } - fn profile(&self) -> Option { - Some(self.to.clone()) - } -} - -/// Unwraps a profile reducing the key depth -/// -/// For example given: -/// -/// ```toml -/// [wrapping_key.profile] -/// key = "value" -/// ``` -/// -/// UnwrapProfileProvider will output: -/// -/// ```toml -/// [profile] -/// key = "value" -/// ``` -struct UnwrapProfileProvider

{ - provider: P, - wrapping_key: Profile, - profile: Profile, -} - -impl

UnwrapProfileProvider

{ - pub fn new(provider: P, wrapping_key: impl Into, profile: impl Into) -> Self { - Self { - provider, - wrapping_key: wrapping_key.into(), - profile: profile.into(), - } - } -} - -impl Provider for UnwrapProfileProvider

{ - fn metadata(&self) -> Metadata { - self.provider.metadata() - } - fn data(&self) -> Result, Error> { - self.provider.data().and_then(|mut data| { - if let Some(profiles) = data.remove(&self.wrapping_key) { - for (profile_str, profile_val) in profiles { - let profile = Profile::new(&profile_str); - if profile != self.profile { - continue; - } - match profile_val { - Value::Dict(_, dict) => return Ok(profile.collect(dict)), - bad_val => { - let mut err = Error::from(figment::error::Kind::InvalidType( - bad_val.to_actual(), - "dict".into(), - )); - err.metadata = Some(self.provider.metadata()); - err.profile = Some(self.profile.clone()); - return Err(err); - } - } - } - } - Ok(Default::default()) - }) - } - fn profile(&self) -> Option { - Some(self.profile.clone()) - } -} - -/// Wraps a profile in another profile -/// -/// For example given: -/// -/// ```toml -/// [profile] -/// key = "value" -/// ``` -/// -/// WrapProfileProvider will output: -/// -/// ```toml -/// [wrapping_key.profile] -/// key = "value" -/// ``` -struct WrapProfileProvider

{ - provider: P, - wrapping_key: Profile, - profile: Profile, -} - -impl

WrapProfileProvider

{ - pub fn new(provider: P, wrapping_key: impl Into, profile: impl Into) -> Self { - Self { - provider, - wrapping_key: wrapping_key.into(), - profile: profile.into(), - } - } -} - -impl Provider for WrapProfileProvider

{ - fn metadata(&self) -> Metadata { - self.provider.metadata() - } - fn data(&self) -> Result, Error> { - if let Some(inner) = self.provider.data()?.remove(&self.profile) { - let value = Value::from(inner); - let dict = [(self.profile.to_string().to_snake_case(), value)] - .into_iter() - .collect(); - Ok(self.wrapping_key.collect(dict)) - } else { - Ok(Default::default()) - } - } - fn profile(&self) -> Option { - Some(self.profile.clone()) - } -} - -/// Extracts the profile from the `profile` key and using the original key as backup, merging -/// values where necessary -/// -/// For example given: -/// -/// ```toml -/// [profile.cool] -/// key = "value" -/// -/// [cool] -/// key2 = "value2" -/// ``` -/// -/// OptionalStrictProfileProvider will output: -/// -/// ```toml -/// [cool] -/// key = "value" -/// key2 = "value2" -/// ``` -/// -/// And emit a deprecation warning -struct OptionalStrictProfileProvider

{ - provider: P, - profiles: Vec, -} - -impl

OptionalStrictProfileProvider

{ - pub const PROFILE_PROFILE: Profile = Profile::const_new("profile"); - - pub fn new(provider: P, profiles: impl IntoIterator>) -> Self { - Self { - provider, - profiles: profiles.into_iter().map(|profile| profile.into()).collect(), - } - } -} - -impl Provider for OptionalStrictProfileProvider

{ - fn metadata(&self) -> Metadata { - self.provider.metadata() - } - fn data(&self) -> Result, Error> { - let mut figment = Figment::from(&self.provider); - for profile in &self.profiles { - figment = figment.merge(UnwrapProfileProvider::new( - &self.provider, - Self::PROFILE_PROFILE, - profile.clone(), - )); - } - figment.data().map_err(|err| { - // figment does tag metadata and tries to map metadata to an error, since we use a new - // figment in this provider this new figment does not know about the metadata of the - // provider and can't map the metadata to the error. Therefor we return the root error - // if this error originated in the provider's data. - if let Err(root_err) = self.provider.data() { - return root_err; - } - err - }) - } - fn profile(&self) -> Option { - self.profiles.last().cloned() - } -} - -trait ProviderExt: Provider { - fn rename( - &self, - from: impl Into, - to: impl Into, - ) -> RenameProfileProvider<&Self> { - RenameProfileProvider::new(self, from, to) - } - - fn wrap( - &self, - wrapping_key: impl Into, - profile: impl Into, - ) -> WrapProfileProvider<&Self> { - WrapProfileProvider::new(self, wrapping_key, profile) - } - - fn strict_select( - &self, - profiles: impl IntoIterator>, - ) -> OptionalStrictProfileProvider<&Self> { - OptionalStrictProfileProvider::new(self, profiles) - } - - fn fallback( - &self, - profile: impl Into, - fallback: impl Into, - ) -> FallbackProfileProvider<&Self> { - FallbackProfileProvider::new(self, profile, fallback) - } -} -impl ProviderExt for P {} - -/// A subset of the foundry `Config` -/// used to initialize a `foundry.toml` file -/// -/// # Example -/// -/// ```rust -/// use foundry_config::{Config, BasicConfig}; -/// use serde::Deserialize; -/// -/// let my_config = Config::figment().extract::(); -/// ``` -#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)] -pub struct BasicConfig { - /// the profile tag: `[profile.default]` - #[serde(skip)] - pub profile: Profile, - /// path of the source contracts dir, like `src` or `contracts` - pub src: PathBuf, - /// path to where artifacts shut be written to - pub out: PathBuf, - /// all library folders to include, `lib`, `node_modules` - pub libs: Vec, - /// `Remappings` to use for this repo - #[serde(default, skip_serializing_if = "Vec::is_empty")] - pub remappings: Vec, -} - -impl BasicConfig { - /// Serialize the config as a String of TOML. - /// - /// This serializes to a table with the name of the profile - pub fn to_string_pretty(&self) -> Result { - let s = toml::to_string_pretty(self)?; - Ok(format!( - "\ -[profile.{}] -{s} -# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options\n", - self.profile - )) - } -} - -pub(crate) mod from_str_lowercase { - use std::str::FromStr; - - use serde::{Deserialize, Deserializer, Serializer}; - - pub fn serialize(value: &T, serializer: S) -> Result - where - T: std::fmt::Display, - S: Serializer, - { - serializer.collect_str(&value.to_string().to_lowercase()) - } - - pub fn deserialize<'de, T, D>(deserializer: D) -> Result - where - D: Deserializer<'de>, - T: FromStr, - T::Err: std::fmt::Display, - { - String::deserialize(deserializer)? - .to_lowercase() - .parse() - .map_err(serde::de::Error::custom) - } -} - -fn canonic(path: impl Into) -> PathBuf { - let path = path.into(); - ethers_solc::utils::canonicalize(&path).unwrap_or(path) -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::{ - cache::{CachedChains, CachedEndpoints}, - endpoints::RpcEndpoint, - etherscan::ResolvedEtherscanConfigs, - fs_permissions::PathPermission, - }; - use ethers_core::types::Chain::Moonbeam; - use ethers_solc::artifacts::{ModelCheckerEngine, YulDetails}; - use figment::{error::Kind::InvalidType, value::Value, Figment}; - use pretty_assertions::assert_eq; - use std::{collections::BTreeMap, fs::File, io::Write, str::FromStr}; - use tempfile::tempdir; - - // Helper function to clear `__warnings` in config, since it will be populated during loading - // from file, causing testing problem when comparing to those created from `default()`, etc. - fn clear_warning(config: &mut Config) { - config.__warnings = vec![]; - } - - #[test] - fn default_sender() { - assert_eq!( - Config::DEFAULT_SENDER, - "0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38" - .parse() - .unwrap() - ); - } - - #[test] - fn test_caching() { - let mut config = Config::default(); - let chain_id = ethers_core::types::Chain::Mainnet; - let url = "https://eth-mainnet.alchemyapi"; - assert!(config.enable_caching(url, chain_id)); - - config.no_storage_caching = true; - assert!(!config.enable_caching(url, chain_id)); - - config.no_storage_caching = false; - assert!(!config.enable_caching(url, ethers_core::types::Chain::Dev)); - } - - #[test] - fn test_install_dir() { - figment::Jail::expect_with(|jail| { - let config = Config::load(); - assert_eq!(config.install_lib_dir(), PathBuf::from("lib")); - jail.create_file( - "foundry.toml", - r#" - [profile.default] - libs = ['node_modules', 'lib'] - "#, - )?; - let config = Config::load(); - assert_eq!(config.install_lib_dir(), PathBuf::from("lib")); - - jail.create_file( - "foundry.toml", - r#" - [profile.default] - libs = ['custom', 'node_modules', 'lib'] - "#, - )?; - let config = Config::load(); - assert_eq!(config.install_lib_dir(), PathBuf::from("custom")); - - Ok(()) - }); - } - - #[test] - fn test_figment_is_default() { - figment::Jail::expect_with(|_| { - let mut default: Config = Config::figment().extract().unwrap(); - default.profile = Config::default().profile; - assert_eq!(default, Config::default()); - Ok(()) - }); - } - - #[test] - fn test_default_round_trip() { - figment::Jail::expect_with(|_| { - let original = Config::figment(); - let roundtrip = Figment::from(Config::from_provider(&original)); - for figment in &[original, roundtrip] { - let config = Config::from_provider(figment); - assert_eq!(config, Config::default()); - } - Ok(()) - }); - } - - #[test] - fn ffi_env_disallowed() { - figment::Jail::expect_with(|jail| { - jail.set_env("FOUNDRY_FFI", "true"); - jail.set_env("FFI", "true"); - jail.set_env("DAPP_FFI", "true"); - let config = Config::load(); - assert!(!config.ffi); - - Ok(()) - }); - } - - #[test] - fn test_profile_env() { - figment::Jail::expect_with(|jail| { - jail.set_env("FOUNDRY_PROFILE", "default"); - let figment = Config::figment(); - assert_eq!(figment.profile(), "default"); - - jail.set_env("FOUNDRY_PROFILE", "hardhat"); - let figment: Figment = Config::hardhat().into(); - assert_eq!(figment.profile(), "hardhat"); - - jail.create_file( - "foundry.toml", - r#" - [profile.default] - libs = ['lib'] - [profile.local] - libs = ['modules'] - "#, - )?; - jail.set_env("FOUNDRY_PROFILE", "local"); - let config = Config::load(); - assert_eq!(config.libs, vec![PathBuf::from("modules")]); - - Ok(()) - }); - } - - #[test] - fn test_default_test_path() { - figment::Jail::expect_with(|_| { - let config = Config::default(); - let paths_config = config.project_paths(); - assert_eq!(paths_config.tests, PathBuf::from(r"test")); - Ok(()) - }); - } - - #[test] - fn test_default_libs() { - figment::Jail::expect_with(|jail| { - let config = Config::load(); - assert_eq!(config.libs, vec![PathBuf::from("lib")]); - - fs::create_dir_all(jail.directory().join("node_modules")).unwrap(); - let config = Config::load(); - assert_eq!(config.libs, vec![PathBuf::from("node_modules")]); - - fs::create_dir_all(jail.directory().join("lib")).unwrap(); - let config = Config::load(); - assert_eq!( - config.libs, - vec![PathBuf::from("lib"), PathBuf::from("node_modules")] - ); - - Ok(()) - }); - } - - #[test] - fn test_inheritance_from_default_test_path() { - figment::Jail::expect_with(|jail| { - jail.create_file( - "foundry.toml", - r#" - [profile.default] - test = "defaulttest" - src = "defaultsrc" - libs = ['lib', 'node_modules'] - - [profile.custom] - src = "customsrc" - "#, - )?; - - let config = Config::load(); - assert_eq!(config.src, PathBuf::from("defaultsrc")); - assert_eq!( - config.libs, - vec![PathBuf::from("lib"), PathBuf::from("node_modules")] - ); - - jail.set_env("FOUNDRY_PROFILE", "custom"); - let config = Config::load(); - - assert_eq!(config.src, PathBuf::from("customsrc")); - assert_eq!(config.test, PathBuf::from("defaulttest")); - assert_eq!( - config.libs, - vec![PathBuf::from("lib"), PathBuf::from("node_modules")] - ); - - Ok(()) - }); - } - - #[test] - fn test_custom_test_path() { - figment::Jail::expect_with(|jail| { - jail.create_file( - "foundry.toml", - r#" - [profile.default] - test = "mytest" - "#, - )?; - - let config = Config::load(); - let paths_config = config.project_paths(); - assert_eq!(paths_config.tests, PathBuf::from(r"mytest")); - Ok(()) - }); - } - - #[test] - fn test_remappings() { - figment::Jail::expect_with(|jail| { - jail.create_file( - "foundry.toml", - r#" - [profile.default] - src = "some-source" - out = "some-out" - cache = true - "#, - )?; - let config = Config::load(); - assert!(config.remappings.is_empty()); - - jail.create_file( - "remappings.txt", - r#" - file-ds-test/=lib/ds-test/ - file-other/=lib/other/ - "#, - )?; - - let config = Config::load(); - assert_eq!( - config.remappings, - vec![ - Remapping::from_str("file-ds-test/=lib/ds-test/") - .unwrap() - .into(), - Remapping::from_str("file-other/=lib/other/") - .unwrap() - .into(), - ], - ); - - jail.set_env("DAPP_REMAPPINGS", "ds-test=lib/ds-test/\nother/=lib/other/"); - let config = Config::load(); - - assert_eq!( - config.remappings, - vec![ - // From environment (should have precedence over remapping.txt) - Remapping::from_str("ds-test=lib/ds-test/").unwrap().into(), - Remapping::from_str("other/=lib/other/").unwrap().into(), - // From remapping.txt (should have less precedence than remapping.txt) - Remapping::from_str("file-ds-test/=lib/ds-test/") - .unwrap() - .into(), - Remapping::from_str("file-other/=lib/other/") - .unwrap() - .into(), - ], - ); - - Ok(()) - }); - } - - #[test] - fn test_remappings_override() { - figment::Jail::expect_with(|jail| { - jail.create_file( - "foundry.toml", - r#" - [profile.default] - src = "some-source" - out = "some-out" - cache = true - "#, - )?; - let config = Config::load(); - assert!(config.remappings.is_empty()); - - jail.create_file( - "remappings.txt", - r#" - ds-test/=lib/ds-test/ - other/=lib/other/ - "#, - )?; - - let config = Config::load(); - assert_eq!( - config.remappings, - vec![ - Remapping::from_str("ds-test/=lib/ds-test/").unwrap().into(), - Remapping::from_str("other/=lib/other/").unwrap().into(), - ], - ); - - jail.set_env( - "DAPP_REMAPPINGS", - "ds-test/=lib/ds-test/src/\nenv-lib/=lib/env-lib/", - ); - let config = Config::load(); - - // Remappings should now be: - // - ds-test from environment (lib/ds-test/src/) - // - other from remappings.txt (lib/other/) - // - env-lib from environment (lib/env-lib/) - assert_eq!( - config.remappings, - vec![ - Remapping::from_str("ds-test/=lib/ds-test/src/") - .unwrap() - .into(), - Remapping::from_str("env-lib/=lib/env-lib/").unwrap().into(), - Remapping::from_str("other/=lib/other/").unwrap().into(), - ], - ); - - // contains additional remapping to the source dir - assert_eq!( - config.get_all_remappings(), - vec![ - Remapping::from_str("ds-test/=lib/ds-test/src/").unwrap(), - Remapping::from_str("env-lib/=lib/env-lib/").unwrap(), - Remapping::from_str("other/=lib/other/").unwrap(), - ], - ); - - Ok(()) - }); - } - - #[test] - fn test_can_update_libs() { - figment::Jail::expect_with(|jail| { - jail.create_file( - "foundry.toml", - r#" - [profile.default] - libs = ["node_modules"] - "#, - )?; - - let mut config = Config::load(); - config.libs.push("libs".into()); - config.update_libs().unwrap(); - - let config = Config::load(); - assert_eq!( - config.libs, - vec![PathBuf::from("node_modules"), PathBuf::from("libs"),] - ); - Ok(()) - }); - } - - #[test] - fn test_large_gas_limit() { - figment::Jail::expect_with(|jail| { - let gas = u64::MAX; - jail.create_file( - "foundry.toml", - &format!( - r#" - [profile.default] - gas_limit = "{gas}" - "# - ), - )?; - - let config = Config::load(); - assert_eq!( - config, - Config { - gas_limit: gas.into(), - ..Config::default() - } - ); - - Ok(()) - }); - } - - #[test] - #[should_panic] - fn test_toml_file_parse_failure() { - figment::Jail::expect_with(|jail| { - jail.create_file( - "foundry.toml", - r#" - [profile.default] - eth_rpc_url = "https://example.com/ - "#, - )?; - - let _config = Config::load(); - - Ok(()) - }); - } - - #[test] - #[should_panic] - fn test_toml_file_non_existing_config_var_failure() { - figment::Jail::expect_with(|jail| { - jail.set_env("FOUNDRY_CONFIG", "this config does not exist"); - - let _config = Config::load(); - - Ok(()) - }); - } - - #[test] - fn test_resolve_etherscan_with_chain() { - figment::Jail::expect_with(|jail| { - let env_key = "__BSC_ETHERSCAN_API_KEY"; - let env_value = "env value"; - jail.create_file( - "foundry.toml", - r#" - [profile.default] - - [etherscan] - bsc = { key = "${__BSC_ETHERSCAN_API_KEY}", url = "https://api.bscscan.com/api" } - "#, - )?; - - let config = Config::load(); - assert!(config - .get_etherscan_config_with_chain(None::) - .unwrap() - .is_none()); - assert!(config - .get_etherscan_config_with_chain(Some(ethers_core::types::Chain::BinanceSmartChain)) - .is_err()); - - std::env::set_var(env_key, env_value); - - assert_eq!( - config - .get_etherscan_config_with_chain(Some( - ethers_core::types::Chain::BinanceSmartChain - )) - .unwrap() - .unwrap() - .key, - env_value - ); - - let mut with_key = config; - with_key.etherscan_api_key = Some("via etherscan_api_key".to_string()); - - assert_eq!( - with_key - .get_etherscan_config_with_chain(Some( - ethers_core::types::Chain::BinanceSmartChain - )) - .unwrap() - .unwrap() - .key, - "via etherscan_api_key" - ); - - std::env::remove_var(env_key); - Ok(()) - }); - } - - #[test] - fn test_resolve_etherscan() { - figment::Jail::expect_with(|jail| { - jail.create_file( - "foundry.toml", - r#" - [profile.default] - - [etherscan] - mainnet = { key = "FX42Z3BBJJEWXWGYV2X1CIPRSCN" } - moonbeam = { key = "${_CONFIG_ETHERSCAN_MOONBEAM}" } - "#, - )?; - - let config = Config::load(); - - assert!(config.etherscan.clone().resolved().has_unresolved()); - - jail.set_env("_CONFIG_ETHERSCAN_MOONBEAM", "123456789"); - - let configs = config.etherscan.resolved(); - assert!(!configs.has_unresolved()); - - let mb_urls = Moonbeam.etherscan_urls().unwrap(); - let mainnet_urls = Mainnet.etherscan_urls().unwrap(); - assert_eq!( - configs, - ResolvedEtherscanConfigs::new([ - ( - "mainnet", - ResolvedEtherscanConfig { - api_url: mainnet_urls.0.to_string(), - chain: Some(Mainnet.into()), - browser_url: Some(mainnet_urls.1.to_string()), - key: "FX42Z3BBJJEWXWGYV2X1CIPRSCN".to_string(), - } - ), - ( - "moonbeam", - ResolvedEtherscanConfig { - api_url: mb_urls.0.to_string(), - chain: Some(Moonbeam.into()), - browser_url: Some(mb_urls.1.to_string()), - key: "123456789".to_string(), - } - ), - ]) - ); - - Ok(()) - }); - } - - #[test] - fn test_resolve_rpc_url() { - figment::Jail::expect_with(|jail| { - jail.create_file( - "foundry.toml", - r#" - [profile.default] - [rpc_endpoints] - optimism = "https://example.com/" - mainnet = "${_CONFIG_MAINNET}" - "#, - )?; - jail.set_env( - "_CONFIG_MAINNET", - "https://eth-mainnet.alchemyapi.io/v2/123455", - ); - - let mut config = Config::load(); - assert_eq!( - "http://localhost:8545", - config.get_rpc_url_or_localhost_http().unwrap() - ); - - config.eth_rpc_url = Some("mainnet".to_string()); - assert_eq!( - "https://eth-mainnet.alchemyapi.io/v2/123455", - config.get_rpc_url_or_localhost_http().unwrap() - ); - - config.eth_rpc_url = Some("optimism".to_string()); - assert_eq!( - "https://example.com/", - config.get_rpc_url_or_localhost_http().unwrap() - ); - - Ok(()) - }) - } - - #[test] - fn test_resolve_rpc_url_if_etherscan_set() { - figment::Jail::expect_with(|jail| { - jail.create_file( - "foundry.toml", - r#" - [profile.default] - etherscan_api_key = "dummy" - [rpc_endpoints] - optimism = "https://example.com/" - "#, - )?; - - let config = Config::load(); - assert_eq!( - "http://localhost:8545", - config.get_rpc_url_or_localhost_http().unwrap() - ); - - Ok(()) - }) - } - - #[test] - fn test_resolve_rpc_url_alias() { - figment::Jail::expect_with(|jail| { - jail.create_file( - "foundry.toml", - r#" - [profile.default] - [rpc_endpoints] - polygonMumbai = "https://polygon-mumbai.g.alchemy.com/v2/${_RESOLVE_RPC_ALIAS}" - "#, - )?; - let mut config = Config::load(); - config.eth_rpc_url = Some("polygonMumbai".to_string()); - assert!(config.get_rpc_url().unwrap().is_err()); - - jail.set_env("_RESOLVE_RPC_ALIAS", "123455"); - - let mut config = Config::load(); - config.eth_rpc_url = Some("polygonMumbai".to_string()); - assert_eq!( - "https://polygon-mumbai.g.alchemy.com/v2/123455", - config.get_rpc_url().unwrap().unwrap() - ); - - Ok(()) - }) - } - - #[test] - fn test_resolve_endpoints() { - figment::Jail::expect_with(|jail| { - jail.create_file( - "foundry.toml", - r#" - [profile.default] - eth_rpc_url = "optimism" - [rpc_endpoints] - optimism = "https://example.com/" - mainnet = "${_CONFIG_MAINNET}" - mainnet_2 = "https://eth-mainnet.alchemyapi.io/v2/${_CONFIG_API_KEY1}" - mainnet_3 = "https://eth-mainnet.alchemyapi.io/v2/${_CONFIG_API_KEY1}/${_CONFIG_API_KEY2}" - "#, - )?; - - let config = Config::load(); - - assert_eq!( - config.get_rpc_url().unwrap().unwrap(), - "https://example.com/" - ); - - assert!(config.rpc_endpoints.clone().resolved().has_unresolved()); - - jail.set_env( - "_CONFIG_MAINNET", - "https://eth-mainnet.alchemyapi.io/v2/123455", - ); - jail.set_env("_CONFIG_API_KEY1", "123456"); - jail.set_env("_CONFIG_API_KEY2", "98765"); - - let endpoints = config.rpc_endpoints.resolved(); - - assert!(!endpoints.has_unresolved()); - - assert_eq!( - endpoints, - RpcEndpoints::new([ - ( - "optimism", - RpcEndpoint::Url("https://example.com/".to_string()) - ), - ( - "mainnet", - RpcEndpoint::Url("https://eth-mainnet.alchemyapi.io/v2/123455".to_string()) - ), - ( - "mainnet_2", - RpcEndpoint::Url("https://eth-mainnet.alchemyapi.io/v2/123456".to_string()) - ), - ( - "mainnet_3", - RpcEndpoint::Url( - "https://eth-mainnet.alchemyapi.io/v2/123456/98765".to_string() - ) - ), - ]) - .resolved() - ); - - Ok(()) - }); - } - - #[test] - fn test_extract_etherscan_config() { - figment::Jail::expect_with(|jail| { - jail.create_file( - "foundry.toml", - r#" - [profile.default] - etherscan_api_key = "optimism" - - [etherscan] - optimism = { key = "https://etherscan-optimism.com/" } - mumbai = { key = "https://etherscan-mumbai.com/" } - "#, - )?; - - let mut config = Config::load(); - - let optimism = config.get_etherscan_api_key(Some(ethers_core::types::Chain::Optimism)); - assert_eq!( - optimism, - Some("https://etherscan-optimism.com/".to_string()) - ); - - config.etherscan_api_key = Some("mumbai".to_string()); - - let mumbai = - config.get_etherscan_api_key(Some(ethers_core::types::Chain::PolygonMumbai)); - assert_eq!(mumbai, Some("https://etherscan-mumbai.com/".to_string())); - - Ok(()) - }); - } - - #[test] - fn test_extract_etherscan_config_by_chain() { - figment::Jail::expect_with(|jail| { - jail.create_file( - "foundry.toml", - r#" - [profile.default] - - [etherscan] - mumbai = { key = "https://etherscan-mumbai.com/", chain = 80001 } - "#, - )?; - - let config = Config::load(); - - let mumbai = config - .get_etherscan_config_with_chain(Some(ethers_core::types::Chain::PolygonMumbai)) - .unwrap() - .unwrap(); - assert_eq!(mumbai.key, "https://etherscan-mumbai.com/".to_string()); - - Ok(()) - }); - } - - #[test] - fn test_extract_etherscan_config_by_chain_with_url() { - figment::Jail::expect_with(|jail| { - jail.create_file( - "foundry.toml", - r#" - [profile.default] - - [etherscan] - mumbai = { key = "https://etherscan-mumbai.com/", chain = 80001 , url = "https://verifier-url.com/"} - "#, - )?; - - let config = Config::load(); - - let mumbai = config - .get_etherscan_config_with_chain(Some(ethers_core::types::Chain::PolygonMumbai)) - .unwrap() - .unwrap(); - assert_eq!(mumbai.key, "https://etherscan-mumbai.com/".to_string()); - assert_eq!(mumbai.api_url, "https://verifier-url.com/".to_string()); - - Ok(()) - }); - } - - #[test] - fn test_extract_etherscan_config_by_chain_and_alias() { - figment::Jail::expect_with(|jail| { - jail.create_file( - "foundry.toml", - r#" - [profile.default] - eth_rpc_url = "mumbai" - - [etherscan] - mumbai = { key = "https://etherscan-mumbai.com/" } - - [rpc_endpoints] - mumbai = "https://polygon-mumbai.g.alchemy.com/v2/mumbai" - "#, - )?; - - let config = Config::load(); - - let mumbai = config - .get_etherscan_config_with_chain(Option::::None) - .unwrap() - .unwrap(); - assert_eq!(mumbai.key, "https://etherscan-mumbai.com/".to_string()); - - let mumbai_rpc = config.get_rpc_url().unwrap().unwrap(); - assert_eq!(mumbai_rpc, "https://polygon-mumbai.g.alchemy.com/v2/mumbai"); - Ok(()) - }); - } - - #[test] - fn test_toml_file() { - figment::Jail::expect_with(|jail| { - jail.create_file( - "foundry.toml", - r#" - [profile.default] - src = "some-source" - out = "some-out" - cache = true - eth_rpc_url = "https://example.com/" - verbosity = 3 - remappings = ["ds-test=lib/ds-test/"] - via_ir = true - rpc_storage_caching = { chains = [1, "optimism", 999999], endpoints = "all"} - use_literal_content = false - bytecode_hash = "ipfs" - cbor_metadata = true - revert_strings = "strip" - allow_paths = ["allow", "paths"] - build_info_path = "build-info" - - [rpc_endpoints] - optimism = "https://example.com/" - mainnet = "${RPC_MAINNET}" - mainnet_2 = "https://eth-mainnet.alchemyapi.io/v2/${API_KEY}" - mainnet_3 = "https://eth-mainnet.alchemyapi.io/v2/${API_KEY}/${ANOTHER_KEY}" - "#, - )?; - - let config = Config::load(); - assert_eq!( - config, - Config { - src: "some-source".into(), - out: "some-out".into(), - cache: true, - eth_rpc_url: Some("https://example.com/".to_string()), - remappings: vec![Remapping::from_str("ds-test=lib/ds-test/").unwrap().into()], - verbosity: 3, - via_ir: true, - rpc_storage_caching: StorageCachingConfig { - chains: CachedChains::Chains(vec![ - Chain::Named(ethers_core::types::Chain::Mainnet), - Chain::Named(ethers_core::types::Chain::Optimism), - Chain::Id(999999) - ]), - endpoints: CachedEndpoints::All - }, - use_literal_content: false, - bytecode_hash: BytecodeHash::Ipfs, - cbor_metadata: true, - revert_strings: Some(RevertStrings::Strip), - allow_paths: vec![PathBuf::from("allow"), PathBuf::from("paths")], - rpc_endpoints: RpcEndpoints::new([ - ( - "optimism", - RpcEndpoint::Url("https://example.com/".to_string()) - ), - ("mainnet", RpcEndpoint::Env("${RPC_MAINNET}".to_string())), - ( - "mainnet_2", - RpcEndpoint::Env( - "https://eth-mainnet.alchemyapi.io/v2/${API_KEY}".to_string() - ) - ), - ( - "mainnet_3", - RpcEndpoint::Env( - "https://eth-mainnet.alchemyapi.io/v2/${API_KEY}/${ANOTHER_KEY}" - .to_string() - ) - ), - ]), - build_info_path: Some("build-info".into()), - ..Config::default() - } - ); - - Ok(()) - }); - } - - #[test] - fn test_load_remappings() { - figment::Jail::expect_with(|jail| { - jail.create_file( - "foundry.toml", - r#" - [profile.default] - remappings = ['nested/=lib/nested/'] - "#, - )?; - - let config = Config::load_with_root(jail.directory()); - assert_eq!( - config.remappings, - vec![Remapping::from_str("nested/=lib/nested/").unwrap().into()] - ); - - Ok(()) - }); - } - - #[test] - fn test_load_full_toml() { - figment::Jail::expect_with(|jail| { - jail.create_file( - "foundry.toml", - r#" - [profile.default] - auto_detect_solc = true - block_base_fee_per_gas = 0 - block_coinbase = '0x0000000000000000000000000000000000000000' - block_difficulty = 0 - block_prevrandao = '0x0000000000000000000000000000000000000000000000000000000000000000' - block_number = 1 - block_timestamp = 1 - use_literal_content = false - bytecode_hash = 'ipfs' - cbor_metadata = true - cache = true - cache_path = 'cache' - evm_version = 'london' - extra_output = [] - extra_output_files = [] - ffi = false - force = false - gas_limit = 9223372036854775807 - gas_price = 0 - gas_reports = ['*'] - ignored_error_codes = [1878] - deny_warnings = false - initial_balance = '0xffffffffffffffffffffffff' - libraries = [] - libs = ['lib'] - memory_limit = 33554432 - names = false - no_storage_caching = false - no_rpc_rate_limit = false - offline = false - optimizer = true - optimizer_runs = 200 - out = 'out' - remappings = ['nested/=lib/nested/'] - sender = '0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38' - sizes = false - sparse_mode = false - src = 'src' - test = 'test' - tx_origin = '0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38' - verbosity = 0 - via_ir = false - - [profile.default.rpc_storage_caching] - chains = 'all' - endpoints = 'all' - - [rpc_endpoints] - optimism = "https://example.com/" - mainnet = "${RPC_MAINNET}" - mainnet_2 = "https://eth-mainnet.alchemyapi.io/v2/${API_KEY}" - - [fuzz] - runs = 256 - seed = '0x3e8' - max_test_rejects = 65536 - - [invariant] - runs = 256 - depth = 15 - fail_on_revert = false - call_override = false - shrink_sequence = true - "#, - )?; - - let config = Config::load_with_root(jail.directory()); - - assert_eq!(config.fuzz.seed, Some(1000.into())); - assert_eq!( - config.remappings, - vec![Remapping::from_str("nested/=lib/nested/").unwrap().into()] - ); - - assert_eq!( - config.rpc_endpoints, - RpcEndpoints::new([ - ( - "optimism", - RpcEndpoint::Url("https://example.com/".to_string()) - ), - ("mainnet", RpcEndpoint::Env("${RPC_MAINNET}".to_string())), - ( - "mainnet_2", - RpcEndpoint::Env( - "https://eth-mainnet.alchemyapi.io/v2/${API_KEY}".to_string() - ) - ), - ]), - ); - - Ok(()) - }); - } - - #[test] - fn test_solc_req() { - figment::Jail::expect_with(|jail| { - jail.create_file( - "foundry.toml", - r#" - [profile.default] - solc_version = "0.8.12" - "#, - )?; - - let config = Config::load(); - assert_eq!( - config.solc, - Some(SolcReq::Version("0.8.12".parse().unwrap())) - ); - - jail.create_file( - "foundry.toml", - r#" - [profile.default] - solc = "0.8.12" - "#, - )?; - - let config = Config::load(); - assert_eq!( - config.solc, - Some(SolcReq::Version("0.8.12".parse().unwrap())) - ); - - jail.create_file( - "foundry.toml", - r#" - [profile.default] - solc = "path/to/local/solc" - "#, - )?; - - let config = Config::load(); - assert_eq!( - config.solc, - Some(SolcReq::Local("path/to/local/solc".into())) - ); - - jail.set_env("FOUNDRY_SOLC_VERSION", "0.6.6"); - let config = Config::load(); - assert_eq!( - config.solc, - Some(SolcReq::Version("0.6.6".parse().unwrap())) - ); - Ok(()) - }); - } - - #[test] - fn test_toml_casing_file() { - figment::Jail::expect_with(|jail| { - jail.create_file( - "foundry.toml", - r#" - [profile.default] - src = "some-source" - out = "some-out" - cache = true - eth-rpc-url = "https://example.com/" - evm-version = "berlin" - auto-detect-solc = false - "#, - )?; - - let config = Config::load(); - assert_eq!( - config, - Config { - src: "some-source".into(), - out: "some-out".into(), - cache: true, - eth_rpc_url: Some("https://example.com/".to_string()), - auto_detect_solc: false, - evm_version: EvmVersion::Berlin, - ..Config::default() - } - ); - - Ok(()) - }); - } - - #[test] - fn test_output_selection() { - figment::Jail::expect_with(|jail| { - jail.create_file( - "foundry.toml", - r#" - [profile.default] - extra_output = ["metadata", "ir-optimized"] - extra_output_files = ["metadata"] - "#, - )?; - - let config = Config::load(); - - assert_eq!( - config.extra_output, - vec![ - ContractOutputSelection::Metadata, - ContractOutputSelection::IrOptimized - ] - ); - assert_eq!( - config.extra_output_files, - vec![ContractOutputSelection::Metadata] - ); - - Ok(()) - }); - } - - #[test] - fn test_precedence() { - figment::Jail::expect_with(|jail| { - jail.create_file( - "foundry.toml", - r#" - [profile.default] - src = "mysrc" - out = "myout" - verbosity = 3 - "#, - )?; - - let config = Config::load(); - assert_eq!( - config, - Config { - src: "mysrc".into(), - out: "myout".into(), - verbosity: 3, - ..Config::default() - } - ); - - jail.set_env("FOUNDRY_SRC", r#"other-src"#); - let config = Config::load(); - assert_eq!( - config, - Config { - src: "other-src".into(), - out: "myout".into(), - verbosity: 3, - ..Config::default() - } - ); - - jail.set_env("FOUNDRY_PROFILE", "foo"); - let val: Result = Config::figment().extract_inner("profile"); - assert!(val.is_err()); - - Ok(()) - }); - } - - #[test] - fn test_extract_basic() { - figment::Jail::expect_with(|jail| { - jail.create_file( - "foundry.toml", - r#" - [profile.default] - src = "mysrc" - out = "myout" - verbosity = 3 - evm_version = 'berlin' - - [profile.other] - src = "other-src" - "#, - )?; - let loaded = Config::load(); - assert_eq!(loaded.evm_version, EvmVersion::Berlin); - let base = loaded.into_basic(); - let default = Config::default(); - assert_eq!( - base, - BasicConfig { - profile: Config::DEFAULT_PROFILE, - src: "mysrc".into(), - out: "myout".into(), - libs: default.libs.clone(), - remappings: default.remappings.clone(), - } - ); - jail.set_env("FOUNDRY_PROFILE", r#"other"#); - let base = Config::figment().extract::().unwrap(); - assert_eq!( - base, - BasicConfig { - profile: Config::DEFAULT_PROFILE, - src: "other-src".into(), - out: "myout".into(), - libs: default.libs.clone(), - remappings: default.remappings, - } - ); - Ok(()) - }); - } - - #[test] - #[should_panic] - fn test_parse_invalid_fuzz_weight() { - figment::Jail::expect_with(|jail| { - jail.create_file( - "foundry.toml", - r#" - [fuzz] - dictionary_weight = 101 - "#, - )?; - let _config = Config::load(); - Ok(()) - }); - } - - #[test] - fn test_fallback_provider() { - figment::Jail::expect_with(|jail| { - jail.create_file( - "foundry.toml", - r#" - [fuzz] - runs = 1 - include_storage = false - dictionary_weight = 99 - - [invariant] - runs = 420 - - [profile.ci.fuzz] - dictionary_weight = 5 - - [profile.ci.invariant] - runs = 400 - "#, - )?; - - let invariant_default = InvariantConfig::default(); - let config = Config::load(); - - assert_ne!(config.invariant.runs, config.fuzz.runs); - assert_eq!(config.invariant.runs, 420); - - assert_ne!( - config.fuzz.dictionary.include_storage, - invariant_default.dictionary.include_storage - ); - assert_eq!( - config.invariant.dictionary.include_storage, - config.fuzz.dictionary.include_storage - ); - - assert_ne!( - config.fuzz.dictionary.dictionary_weight, - invariant_default.dictionary.dictionary_weight - ); - assert_eq!( - config.invariant.dictionary.dictionary_weight, - config.fuzz.dictionary.dictionary_weight - ); - - jail.set_env("FOUNDRY_PROFILE", "ci"); - let ci_config = Config::load(); - assert_eq!(ci_config.fuzz.runs, 1); - assert_eq!(ci_config.invariant.runs, 400); - assert_eq!(ci_config.fuzz.dictionary.dictionary_weight, 5); - assert_eq!( - ci_config.invariant.dictionary.dictionary_weight, - config.fuzz.dictionary.dictionary_weight - ); - - Ok(()) - }) - } - - #[test] - fn test_standalone_profile_sections() { - figment::Jail::expect_with(|jail| { - jail.create_file( - "foundry.toml", - r#" - [fuzz] - runs = 100 - - [invariant] - runs = 120 - - [profile.ci.fuzz] - runs = 420 - - [profile.ci.invariant] - runs = 500 - "#, - )?; - - let config = Config::load(); - assert_eq!(config.fuzz.runs, 100); - assert_eq!(config.invariant.runs, 120); - - jail.set_env("FOUNDRY_PROFILE", "ci"); - let config = Config::load(); - assert_eq!(config.fuzz.runs, 420); - assert_eq!(config.invariant.runs, 500); - - Ok(()) - }); - } - - #[test] - fn can_handle_deviating_dapp_aliases() { - figment::Jail::expect_with(|jail| { - let addr = Address::random(); - jail.set_env("DAPP_TEST_NUMBER", 1337); - jail.set_env("DAPP_TEST_ADDRESS", format!("{addr:?}")); - jail.set_env("DAPP_TEST_FUZZ_RUNS", 420); - jail.set_env("DAPP_TEST_DEPTH", 20); - jail.set_env("DAPP_FORK_BLOCK", 100); - jail.set_env("DAPP_BUILD_OPTIMIZE_RUNS", 999); - jail.set_env("DAPP_BUILD_OPTIMIZE", 0); - - let config = Config::load(); - - assert_eq!(config.block_number, 1337); - assert_eq!(config.sender, addr); - assert_eq!(config.fuzz.runs, 420); - assert_eq!(config.invariant.depth, 20); - assert_eq!(config.fork_block_number, Some(100)); - assert_eq!(config.optimizer_runs, 999); - assert!(!config.optimizer); - - Ok(()) - }); - } - - #[test] - fn can_parse_libraries() { - figment::Jail::expect_with(|jail| { - jail.set_env( - "DAPP_LIBRARIES", - "[src/DssSpell.sol:DssExecLib:0x8De6DDbCd5053d32292AAA0D2105A32d108484a6]", - ); - let config = Config::load(); - assert_eq!( - config.libraries, - vec![ - "src/DssSpell.sol:DssExecLib:0x8De6DDbCd5053d32292AAA0D2105A32d108484a6" - .to_string() - ] - ); - - jail.set_env( - "DAPP_LIBRARIES", - "src/DssSpell.sol:DssExecLib:0x8De6DDbCd5053d32292AAA0D2105A32d108484a6", - ); - let config = Config::load(); - assert_eq!( - config.libraries, - vec![ - "src/DssSpell.sol:DssExecLib:0x8De6DDbCd5053d32292AAA0D2105A32d108484a6" - .to_string(), - ] - ); - - jail.set_env( - "DAPP_LIBRARIES", - "src/DssSpell.sol:DssExecLib:0x8De6DDbCd5053d32292AAA0D2105A32d108484a6,src/DssSpell.sol:DssExecLib:0x8De6DDbCd5053d32292AAA0D2105A32d108484a6", - ); - let config = Config::load(); - assert_eq!( - config.libraries, - vec![ - "src/DssSpell.sol:DssExecLib:0x8De6DDbCd5053d32292AAA0D2105A32d108484a6" - .to_string(), - "src/DssSpell.sol:DssExecLib:0x8De6DDbCd5053d32292AAA0D2105A32d108484a6" - .to_string() - ] - ); - - Ok(()) - }); - } - - #[test] - fn test_parse_many_libraries() { - figment::Jail::expect_with(|jail| { - jail.create_file( - "foundry.toml", - r#" - [profile.default] - libraries= [ - './src/SizeAuctionDiscount.sol:Chainlink:0xffedba5e171c4f15abaaabc86e8bd01f9b54dae5', - './src/SizeAuction.sol:ChainlinkTWAP:0xffedba5e171c4f15abaaabc86e8bd01f9b54dae5', - './src/SizeAuction.sol:Math:0x902f6cf364b8d9470d5793a9b2b2e86bddd21e0c', - './src/test/ChainlinkTWAP.t.sol:ChainlinkTWAP:0xffedba5e171c4f15abaaabc86e8bd01f9b54dae5', - './src/SizeAuctionDiscount.sol:Math:0x902f6cf364b8d9470d5793a9b2b2e86bddd21e0c', - ] - "#, - )?; - let config = Config::load(); - - let libs = config.parsed_libraries().unwrap().libs; - - pretty_assertions::assert_eq!( - libs, - BTreeMap::from([ - ( - PathBuf::from("./src/SizeAuctionDiscount.sol"), - BTreeMap::from([ - ( - "Chainlink".to_string(), - "0xffedba5e171c4f15abaaabc86e8bd01f9b54dae5".to_string() - ), - ( - "Math".to_string(), - "0x902f6cf364b8d9470d5793a9b2b2e86bddd21e0c".to_string() - ) - ]) - ), - ( - PathBuf::from("./src/SizeAuction.sol"), - BTreeMap::from([ - ( - "ChainlinkTWAP".to_string(), - "0xffedba5e171c4f15abaaabc86e8bd01f9b54dae5".to_string() - ), - ( - "Math".to_string(), - "0x902f6cf364b8d9470d5793a9b2b2e86bddd21e0c".to_string() - ) - ]) - ), - ( - PathBuf::from("./src/test/ChainlinkTWAP.t.sol"), - BTreeMap::from([( - "ChainlinkTWAP".to_string(), - "0xffedba5e171c4f15abaaabc86e8bd01f9b54dae5".to_string() - )]) - ), - ]) - ); - - Ok(()) - }); - } - - #[test] - fn config_roundtrip() { - figment::Jail::expect_with(|jail| { - let default = Config::default(); - let basic = default.clone().into_basic(); - jail.create_file("foundry.toml", &basic.to_string_pretty().unwrap())?; - - let mut other = Config::load(); - clear_warning(&mut other); - assert_eq!(default, other); - - let other = other.into_basic(); - assert_eq!(basic, other); - - jail.create_file("foundry.toml", &default.to_string_pretty().unwrap())?; - let mut other = Config::load(); - clear_warning(&mut other); - assert_eq!(default, other); - - Ok(()) - }); - } - - #[test] - fn test_fs_permissions() { - figment::Jail::expect_with(|jail| { - jail.create_file( - "foundry.toml", - r#" - [profile.default] - fs_permissions = [{ access = "read-write", path = "./"}] - "#, - )?; - let loaded = Config::load(); - - assert_eq!( - loaded.fs_permissions, - FsPermissions::new(vec![PathPermission::read_write("./")]) - ); - - jail.create_file( - "foundry.toml", - r#" - [profile.default] - fs_permissions = [{ access = "none", path = "./"}] - "#, - )?; - let loaded = Config::load(); - assert_eq!( - loaded.fs_permissions, - FsPermissions::new(vec![PathPermission::none("./")]) - ); - - Ok(()) - }); - } - - #[test] - fn test_optimizer_settings_basic() { - figment::Jail::expect_with(|jail| { - jail.create_file( - "foundry.toml", - r#" - [profile.default] - optimizer = true - - [profile.default.optimizer_details] - yul = false - - [profile.default.optimizer_details.yulDetails] - stackAllocation = true - "#, - )?; - let mut loaded = Config::load(); - clear_warning(&mut loaded); - assert_eq!( - loaded.optimizer_details, - Some(OptimizerDetails { - yul: Some(false), - yul_details: Some(YulDetails { - stack_allocation: Some(true), - ..Default::default() - }), - ..Default::default() - }) - ); - - let s = loaded.to_string_pretty().unwrap(); - jail.create_file("foundry.toml", &s)?; - - let mut reloaded = Config::load(); - clear_warning(&mut reloaded); - assert_eq!(loaded, reloaded); - - Ok(()) - }); - } - - #[test] - fn test_model_checker_settings_basic() { - figment::Jail::expect_with(|jail| { - jail.create_file( - "foundry.toml", - r#" - [profile.default] - - [profile.default.model_checker] - contracts = { 'a.sol' = [ 'A1', 'A2' ], 'b.sol' = [ 'B1', 'B2' ] } - engine = 'chc' - targets = [ 'assert', 'outOfBounds' ] - timeout = 10000 - "#, - )?; - let mut loaded = Config::load(); - clear_warning(&mut loaded); - assert_eq!( - loaded.model_checker, - Some(ModelCheckerSettings { - contracts: BTreeMap::from([ - ( - "a.sol".to_string(), - vec!["A1".to_string(), "A2".to_string()] - ), - ( - "b.sol".to_string(), - vec!["B1".to_string(), "B2".to_string()] - ), - ]), - engine: Some(ModelCheckerEngine::CHC), - targets: Some(vec![ - ModelCheckerTarget::Assert, - ModelCheckerTarget::OutOfBounds - ]), - timeout: Some(10000), - invariants: None, - show_unproved: None, - div_mod_with_slacks: None, - solvers: None, - show_unsupported: None, - show_proved_safe: None, - }) - ); - - let s = loaded.to_string_pretty().unwrap(); - jail.create_file("foundry.toml", &s)?; - - let mut reloaded = Config::load(); - clear_warning(&mut reloaded); - assert_eq!(loaded, reloaded); - - Ok(()) - }); - } - - #[test] - fn test_model_checker_settings_relative_paths() { - figment::Jail::expect_with(|jail| { - jail.create_file( - "foundry.toml", - r#" - [profile.default] - - [profile.default.model_checker] - contracts = { 'a.sol' = [ 'A1', 'A2' ], 'b.sol' = [ 'B1', 'B2' ] } - engine = 'chc' - targets = [ 'assert', 'outOfBounds' ] - timeout = 10000 - "#, - )?; - let loaded = Config::load().sanitized(); - - // NOTE(onbjerg): We have to canonicalize the path here using dunce because figment will - // canonicalize the jail path using the standard library. The standard library *always* - // transforms Windows paths to some weird extended format, which none of our code base - // does. - let dir = ethers_solc::utils::canonicalize(jail.directory()) - .expect("Could not canonicalize jail path"); - assert_eq!( - loaded.model_checker, - Some(ModelCheckerSettings { - contracts: BTreeMap::from([ - ( - format!("{}", dir.join("a.sol").display()), - vec!["A1".to_string(), "A2".to_string()] - ), - ( - format!("{}", dir.join("b.sol").display()), - vec!["B1".to_string(), "B2".to_string()] - ), - ]), - engine: Some(ModelCheckerEngine::CHC), - targets: Some(vec![ - ModelCheckerTarget::Assert, - ModelCheckerTarget::OutOfBounds - ]), - timeout: Some(10000), - invariants: None, - show_unproved: None, - div_mod_with_slacks: None, - solvers: None, - show_unsupported: None, - show_proved_safe: None, - }) - ); - - Ok(()) - }); - } - - #[test] - fn test_fmt_config() { - figment::Jail::expect_with(|jail| { - jail.create_file( - "foundry.toml", - r#" - [fmt] - line_length = 100 - tab_width = 2 - bracket_spacing = true - "#, - )?; - let loaded = Config::load().sanitized(); - assert_eq!( - loaded.fmt, - FormatterConfig { - line_length: 100, - tab_width: 2, - bracket_spacing: true, - ..Default::default() - } - ); - - Ok(()) - }); - } - - #[test] - fn test_invariant_config() { - figment::Jail::expect_with(|jail| { - jail.create_file( - "foundry.toml", - r#" - [invariant] - runs = 512 - depth = 10 - "#, - )?; - - let loaded = Config::load().sanitized(); - assert_eq!( - loaded.invariant, - InvariantConfig { - runs: 512, - depth: 10, - ..Default::default() - } - ); - - Ok(()) - }); - } - - #[test] - fn test_standalone_sections_env() { - figment::Jail::expect_with(|jail| { - jail.create_file( - "foundry.toml", - r#" - [fuzz] - runs = 100 - - [invariant] - depth = 1 - "#, - )?; - - jail.set_env("FOUNDRY_FMT_LINE_LENGTH", "95"); - jail.set_env("FOUNDRY_FUZZ_DICTIONARY_WEIGHT", "99"); - jail.set_env("FOUNDRY_INVARIANT_DEPTH", "5"); - - let config = Config::load(); - assert_eq!(config.fmt.line_length, 95); - assert_eq!(config.fuzz.dictionary.dictionary_weight, 99); - assert_eq!(config.invariant.depth, 5); - - Ok(()) - }); - } - - #[test] - fn test_parse_with_profile() { - let foundry_str = r#" - [profile.default] - src = 'src' - out = 'out' - libs = ['lib'] - - # See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options - "#; - assert_eq!( - parse_with_profile::(foundry_str) - .unwrap() - .unwrap(), - ( - Config::DEFAULT_PROFILE, - BasicConfig { - profile: Config::DEFAULT_PROFILE, - src: "src".into(), - out: "out".into(), - libs: vec!["lib".into()], - remappings: vec![] - } - ) - ); - } - - #[test] - fn test_implicit_profile_loads() { - figment::Jail::expect_with(|jail| { - jail.create_file( - "foundry.toml", - r#" - [default] - src = 'my-src' - out = 'my-out' - "#, - )?; - let loaded = Config::load().sanitized(); - assert_eq!(loaded.src.file_name().unwrap(), "my-src"); - assert_eq!(loaded.out.file_name().unwrap(), "my-out"); - assert_eq!( - loaded.__warnings, - vec![Warning::UnknownSection { - unknown_section: Profile::new("default"), - source: Some("foundry.toml".into()) - }] - ); - - Ok(()) - }); - } - - // a test to print the config, mainly used to update the example config in the README - #[test] - #[ignore] - fn print_config() { - let config = Config { - optimizer_details: Some(OptimizerDetails { - peephole: None, - inliner: None, - jumpdest_remover: None, - order_literals: None, - deduplicate: None, - cse: None, - constant_optimizer: Some(true), - yul: Some(true), - yul_details: Some(YulDetails { - stack_allocation: None, - optimizer_steps: Some("dhfoDgvulfnTUtnIf".to_string()), - }), - }), - ..Default::default() - }; - println!("{}", config.to_string_pretty().unwrap()); - } - - #[test] - fn can_use_impl_figment_macro() { - #[derive(Default, Serialize)] - struct MyArgs { - #[serde(skip_serializing_if = "Option::is_none")] - root: Option, - } - impl_figment_convert!(MyArgs); - - impl Provider for MyArgs { - fn metadata(&self) -> Metadata { - Metadata::default() - } - - fn data(&self) -> Result, Error> { - let value = Value::serialize(self)?; - let error = InvalidType(value.to_actual(), "map".into()); - let dict = value.into_dict().ok_or(error)?; - Ok(Map::from([(Config::selected_profile(), dict)])) - } - } - - let _figment: Figment = From::from(&MyArgs::default()); - let _config: Config = From::from(&MyArgs::default()); - - #[derive(Default)] - struct Outer { - start: MyArgs, - other: MyArgs, - another: MyArgs, - } - impl_figment_convert!(Outer, start, other, another); - - let _figment: Figment = From::from(&Outer::default()); - let _config: Config = From::from(&Outer::default()); - } - - #[test] - fn list_cached_blocks() -> eyre::Result<()> { - fn fake_block_cache(chain_path: &Path, block_number: &str, size_bytes: usize) { - let block_path = chain_path.join(block_number); - fs::create_dir(block_path.as_path()).unwrap(); - let file_path = block_path.join("storage.json"); - let mut file = File::create(file_path).unwrap(); - writeln!( - file, - "{}", - vec![' '; size_bytes - 1].iter().collect::() - ) - .unwrap(); - } - - let chain_dir = tempdir()?; - - fake_block_cache(chain_dir.path(), "1", 100); - fake_block_cache(chain_dir.path(), "2", 500); - // Pollution file that should not show up in the cached block - let mut pol_file = File::create(chain_dir.path().join("pol.txt")).unwrap(); - writeln!(pol_file, "{}", [' '; 10].iter().collect::()).unwrap(); - - let result = Config::get_cached_blocks(chain_dir.path())?; - - assert_eq!(result.len(), 2); - let block1 = &result.iter().find(|x| x.0 == "1").unwrap(); - let block2 = &result.iter().find(|x| x.0 == "2").unwrap(); - assert_eq!(block1.0, "1"); - assert_eq!(block1.1, 100); - assert_eq!(block2.0, "2"); - assert_eq!(block2.1, 500); - - chain_dir.close()?; - Ok(()) - } - - #[test] - fn list_etherscan_cache() -> eyre::Result<()> { - fn fake_etherscan_cache(chain_path: &Path, address: &str, size_bytes: usize) { - let metadata_path = chain_path.join("sources"); - let abi_path = chain_path.join("abi"); - let _ = fs::create_dir(metadata_path.as_path()); - let _ = fs::create_dir(abi_path.as_path()); - - let metadata_file_path = metadata_path.join(address); - let mut metadata_file = File::create(metadata_file_path).unwrap(); - writeln!( - metadata_file, - "{}", - vec![' '; size_bytes / 2 - 1].iter().collect::() - ) - .unwrap(); - - let abi_file_path = abi_path.join(address); - let mut abi_file = File::create(abi_file_path).unwrap(); - writeln!( - abi_file, - "{}", - vec![' '; size_bytes / 2 - 1].iter().collect::() - ) - .unwrap(); - } - - let chain_dir = tempdir()?; - - fake_etherscan_cache(chain_dir.path(), "1", 100); - fake_etherscan_cache(chain_dir.path(), "2", 500); - - let result = Config::get_cached_block_explorer_data(chain_dir.path())?; - - assert_eq!(result, 600); - - chain_dir.close()?; - Ok(()) - } - - #[test] - fn test_parse_error_codes() { - figment::Jail::expect_with(|jail| { - jail.create_file( - "foundry.toml", - r#" - [default] - ignored_error_codes = ["license", "unreachable", 1337] - "#, - )?; - - let config = Config::load(); - assert_eq!( - config.ignored_error_codes, - vec![ - SolidityErrorCode::SpdxLicenseNotProvided, - SolidityErrorCode::Unreachable, - SolidityErrorCode::Other(1337) - ] - ); - - Ok(()) - }); - } - - #[test] - fn test_parse_optimizer_settings() { - figment::Jail::expect_with(|jail| { - jail.create_file( - "foundry.toml", - r#" - [default] - [profile.default.optimizer_details] - "#, - )?; - - let config = Config::load(); - assert_eq!(config.optimizer_details, Some(OptimizerDetails::default())); - - Ok(()) - }); - } -} diff --git a/foundry-config/src/macros.rs b/foundry-config/src/macros.rs deleted file mode 100644 index 21291a8fb..000000000 --- a/foundry-config/src/macros.rs +++ /dev/null @@ -1,239 +0,0 @@ -/// A macro to implement converters from a type to [`Config`] and [`figment::Figment`] -/// -/// This can be used to remove some boilerplate code that's necessary to add additional layer(s) to -/// the [`Config`]'s default `Figment`. -/// -/// `impl_figment` takes the default `Config` and merges additional `Provider`, therefore the -/// targeted type, requires an implementation of `figment::Profile`. -/// -/// # Example -/// -/// Use `impl_figment` on a type with a `root: Option` field, which will be used for -/// [`Config::figment_with_root()`] -/// -/// ```rust -/// use std::path::PathBuf; -/// use serde::Serialize; -/// use foundry_config::{Config, impl_figment_convert}; -/// use foundry_config::figment::*; -/// use foundry_config::figment::error::Kind::InvalidType; -/// use foundry_config::figment::value::*; -/// #[derive(Default, Serialize)] -/// struct MyArgs { -/// #[serde(skip_serializing_if = "Option::is_none")] -/// root: Option, -/// } -/// impl_figment_convert!(MyArgs); -/// -/// impl Provider for MyArgs { -/// fn metadata(&self) -> Metadata { -/// Metadata::default() -/// } -/// -/// fn data(&self) -> Result, Error> { -/// let value = Value::serialize(self)?; -/// let error = InvalidType(value.to_actual(), "map".into()); -/// let mut dict = value.into_dict().ok_or(error)?; -/// Ok(Map::from([(Config::selected_profile(), dict)])) -/// } -/// } -/// -/// let figment: Figment = From::from(&MyArgs::default()); -/// let config: Config = From::from(&MyArgs::default()); -/// -/// // Use `impl_figment` on a type that has several nested `Provider` as fields but is _not_ a `Provider` itself -/// -/// #[derive(Default)] -/// struct Outer { -/// start: MyArgs, -/// second: MyArgs, -/// third: MyArgs, -/// } -/// impl_figment_convert!(Outer, start, second, third); -/// -/// let figment: Figment = From::from(&Outer::default()); -/// let config: Config = From::from(&Outer::default()); -/// ``` -#[macro_export] -macro_rules! impl_figment_convert { - ($name:ty) => { - impl<'a> From<&'a $name> for $crate::figment::Figment { - fn from(args: &'a $name) -> Self { - if let Some(root) = args.root.clone() { - $crate::Config::figment_with_root(root) - } else { - $crate::Config::figment_with_root($crate::find_project_root_path(None).unwrap()) - } - .merge(args) - } - } - - impl<'a> From<&'a $name> for $crate::Config { - fn from(args: &'a $name) -> Self { - let figment: $crate::figment::Figment = args.into(); - $crate::Config::from_provider(figment).sanitized() - } - } - }; - ($name:ty, $start:ident $(, $more:ident)*) => { - impl<'a> From<&'a $name> for $crate::figment::Figment { - fn from(args: &'a $name) -> Self { - let mut figment: $crate::figment::Figment = From::from(&args.$start); - $ ( - figment = figment.merge(&args.$more); - )* - figment - } - } - - impl<'a> From<&'a $name> for $crate::Config { - fn from(args: &'a $name) -> Self { - let figment: $crate::figment::Figment = args.into(); - $crate::Config::from_provider(figment).sanitized() - } - } - }; - ($name:ty, self, $start:ident $(, $more:ident)*) => { - impl<'a> From<&'a $name> for $crate::figment::Figment { - fn from(args: &'a $name) -> Self { - let mut figment: $crate::figment::Figment = From::from(&args.$start); - $ ( - figment = figment.merge(&args.$more); - )* - figment = figment.merge(args); - figment - } - } - - impl<'a> From<&'a $name> for $crate::Config { - fn from(args: &'a $name) -> Self { - let figment: $crate::figment::Figment = args.into(); - $crate::Config::from_provider(figment).sanitized() - } - } - }; -} - -/// Same as `impl_figment_convert` but also merges the type itself into the figment -/// -/// # Example -/// -/// Merge several nested `Provider` together with the type itself -/// -/// ```rust -/// use std::path::PathBuf; -/// use foundry_config::{Config, merge_impl_figment_convert, impl_figment_convert}; -/// use foundry_config::figment::*; -/// use foundry_config::figment::value::*; -/// -/// #[derive(Default)] -/// struct MyArgs { -/// root: Option, -/// } -/// -/// impl Provider for MyArgs { -/// fn metadata(&self) -> Metadata { -/// Metadata::default() -/// } -/// -/// fn data(&self) -> Result, Error> { -/// todo!() -/// } -/// } -/// -/// impl_figment_convert!(MyArgs); -/// -/// #[derive(Default)] -/// struct OuterArgs { -/// value: u64, -/// inner: MyArgs -/// } -/// -/// impl Provider for OuterArgs { -/// fn metadata(&self) -> Metadata { -/// Metadata::default() -/// } -/// -/// fn data(&self) -> Result, Error> { -/// todo!() -/// } -/// } -/// -/// merge_impl_figment_convert!(OuterArgs, inner); -/// ``` -#[macro_export] -macro_rules! merge_impl_figment_convert { - ($name:ty, $start:ident $(, $more:ident)*) => { - impl<'a> From<&'a $name> for $crate::figment::Figment { - fn from(args: &'a $name) -> Self { - let mut figment: $crate::figment::Figment = From::from(&args.$start); - $ ( - figment = figment.merge(&args.$more); - )* - figment = figment.merge(args); - figment - } - } - - impl<'a> From<&'a $name> for $crate::Config { - fn from(args: &'a $name) -> Self { - let figment: $crate::figment::Figment = args.into(); - $crate::Config::from_provider(figment).sanitized() - } - } - }; -} - -/// A macro to implement converters from a type to [`Config`] and [`figment::Figment`] -#[macro_export] -macro_rules! impl_figment_convert_cast { - ($name:ty) => { - impl<'a> From<&'a $name> for $crate::figment::Figment { - fn from(args: &'a $name) -> Self { - $crate::Config::figment_with_root($crate::find_project_root_path(None).unwrap()) - .merge(args) - } - } - - impl<'a> From<&'a $name> for $crate::Config { - fn from(args: &'a $name) -> Self { - let figment: $crate::figment::Figment = args.into(); - $crate::Config::from_provider(figment).sanitized() - } - } - }; -} - -/// Same as `impl_figment_convert` but also implies `Provider` for the given `Serialize` type for -/// convenience. The `Provider` only provides the "root" value for the current profile -#[macro_export] -macro_rules! impl_figment_convert_basic { - ($name:ty) => { - $crate::impl_figment_convert!($name); - - impl $crate::figment::Provider for $name { - fn metadata(&self) -> $crate::figment::Metadata { - $crate::figment::Metadata::named(stringify!($name)) - } - - fn data( - &self, - ) -> Result< - $crate::figment::value::Map<$crate::figment::Profile, $crate::figment::value::Dict>, - $crate::figment::Error, - > { - let mut dict = $crate::figment::value::Dict::new(); - if let Some(root) = self.root.as_ref() { - dict.insert( - "root".to_string(), - $crate::figment::value::Value::serialize(root)?, - ); - } - Ok($crate::figment::value::Map::from([( - $crate::Config::selected_profile(), - dict, - )])) - } - } - }; -} diff --git a/foundry-config/src/providers/mod.rs b/foundry-config/src/providers/mod.rs deleted file mode 100644 index b18927512..000000000 --- a/foundry-config/src/providers/mod.rs +++ /dev/null @@ -1,158 +0,0 @@ -use crate::{Config, Warning, DEPRECATIONS}; -use figment::{ - value::{Dict, Map, Value}, - Error, Figment, Metadata, Profile, Provider, -}; - -/// Remappings provider -pub mod remappings; - -/// Generate warnings for unknown sections and deprecated keys -pub struct WarningsProvider

{ - provider: P, - profile: Profile, - old_warnings: Result, Error>, -} - -impl

WarningsProvider

{ - const WARNINGS_KEY: &'static str = "__warnings"; - - /// Creates a new warnings provider. - pub fn new( - provider: P, - profile: impl Into, - old_warnings: Result, Error>, - ) -> Self { - Self { - provider, - profile: profile.into(), - old_warnings, - } - } - - /// Creates a new figment warnings provider. - pub fn for_figment(provider: P, figment: &Figment) -> Self { - let old_warnings = { - let warnings_res = figment.extract_inner(Self::WARNINGS_KEY); - if warnings_res - .as_ref() - .err() - .map(|err| err.missing()) - .unwrap_or(false) - { - Ok(vec![]) - } else { - warnings_res - } - }; - Self::new(provider, figment.profile().clone(), old_warnings) - } -} - -impl WarningsProvider

{ - /// Collects all warnings. - pub fn collect_warnings(&self) -> Result, Error> { - let mut out = self.old_warnings.clone()?; - // add warning for unknown sections - out.extend( - self.provider - .data() - .unwrap_or_default() - .keys() - .filter(|k| { - k != &Config::PROFILE_SECTION - && !Config::STANDALONE_SECTIONS.iter().any(|s| s == k) - }) - .map(|unknown_section| { - let source = self.provider.metadata().source.map(|s| s.to_string()); - Warning::UnknownSection { - unknown_section: unknown_section.clone(), - source, - } - }), - ); - // add warning for deprecated keys - out.extend( - self.provider - .data() - .unwrap_or_default() - .iter() - .flat_map(|(profile, dict)| dict.keys().map(move |key| format!("{profile}.{key}"))) - .filter(|k| DEPRECATIONS.contains_key(k)) - .map(|deprecated_key| Warning::DeprecatedKey { - old: deprecated_key.clone(), - new: DEPRECATIONS.get(&deprecated_key).unwrap().to_string(), - }), - ); - Ok(out) - } -} - -impl Provider for WarningsProvider

{ - fn metadata(&self) -> Metadata { - if let Some(source) = self.provider.metadata().source { - Metadata::from("Warnings", source) - } else { - Metadata::named("Warnings") - } - } - fn data(&self) -> Result, Error> { - Ok(Map::from([( - self.profile.clone(), - Dict::from([( - Self::WARNINGS_KEY.to_string(), - Value::serialize(self.collect_warnings()?)?, - )]), - )])) - } - fn profile(&self) -> Option { - Some(self.profile.clone()) - } -} - -/// Extracts the profile from the `profile` key and sets unset values according to the fallback -/// provider -pub struct FallbackProfileProvider

{ - provider: P, - profile: Profile, - fallback: Profile, -} - -impl

FallbackProfileProvider

{ - /// Creates a new fallback profile provider. - pub fn new(provider: P, profile: impl Into, fallback: impl Into) -> Self { - FallbackProfileProvider { - provider, - profile: profile.into(), - fallback: fallback.into(), - } - } -} - -impl Provider for FallbackProfileProvider

{ - fn metadata(&self) -> Metadata { - self.provider.metadata() - } - - fn data(&self) -> Result, Error> { - if let Some(fallback) = self.provider.data()?.get(&self.fallback) { - let mut inner = self - .provider - .data()? - .remove(&self.profile) - .unwrap_or_default(); - for (k, v) in fallback.iter() { - if !inner.contains_key(k) { - inner.insert(k.to_owned(), v.clone()); - } - } - Ok(self.profile.collect(inner)) - } else { - self.provider.data() - } - } - - fn profile(&self) -> Option { - Some(self.profile.clone()) - } -} diff --git a/foundry-config/src/providers/remappings.rs b/foundry-config/src/providers/remappings.rs deleted file mode 100644 index f5e0715c4..000000000 --- a/foundry-config/src/providers/remappings.rs +++ /dev/null @@ -1,281 +0,0 @@ -use crate::{foundry_toml_dirs, remappings_from_env_var, remappings_from_newline, Config}; -use ethers_solc::remappings::{RelativeRemapping, Remapping}; -use figment::{ - value::{Dict, Map}, - Error, Metadata, Profile, Provider, -}; -use std::{ - borrow::Cow, - collections::{btree_map::Entry, BTreeMap, HashSet}, - fs, - path::{Path, PathBuf}, -}; -use tracing::trace; - -/// Wrapper types over a `Vec` that only appends unique remappings. -#[derive(Debug, Clone, Default)] -pub struct Remappings { - /// Remappings. - remappings: Vec, -} - -impl Remappings { - /// Create a new `Remappings` wrapper with an empty vector. - pub fn new() -> Self { - Self { - remappings: Vec::new(), - } - } - - /// Create a new `Remappings` wrapper with a vector of remappings. - pub fn new_with_remappings(remappings: Vec) -> Self { - Self { remappings } - } - - /// Consumes the wrapper and returns the inner remappings vector. - pub fn into_inner(self) -> Vec { - let mut tmp = HashSet::new(); - let remappings = self - .remappings - .iter() - .filter(|r| tmp.insert(r.name.clone())) - .cloned() - .collect(); - remappings - } - - /// Push an element ot the remappings vector, but only if it's not already present. - pub fn push(&mut self, remapping: Remapping) { - if !self.remappings.iter().any(|existing| { - // What we're doing here is filtering for ambiguous paths. For example, if we have - // @prb/math/=node_modules/@prb/math/src/ as existing, and - // @prb/=node_modules/@prb/ as the one being checked, - // we want to keep the already existing one, which is the first one. This way we avoid - // having to deal with ambiguous paths which is unwanted when autodetecting remappings. - existing.name.starts_with(&remapping.name) && existing.context == remapping.context - }) { - self.remappings.push(remapping) - } - } - - /// Extend the remappings vector, leaving out the remappings that are already present. - pub fn extend(&mut self, remappings: Vec) { - for remapping in remappings { - self.push(remapping); - } - } -} - -/// A figment provider that checks if the remappings were previously set and if they're unset looks -/// up the fs via -/// - `DAPP_REMAPPINGS` || `FOUNDRY_REMAPPINGS` env var -/// - `/remappings.txt` file -/// - `Remapping::find_many`. -pub struct RemappingsProvider<'a> { - /// Whether to auto detect remappings from the `lib_paths` - pub auto_detect_remappings: bool, - /// The lib/dependency directories to scan for remappings - pub lib_paths: Cow<'a, Vec>, - /// the root path used to turn an absolute `Remapping`, as we're getting it from - /// `Remapping::find_many` into a relative one. - pub root: &'a PathBuf, - /// This contains either: - /// - previously set remappings - /// - a `MissingField` error, which means previous provider didn't set the "remappings" field - /// - other error, like formatting - pub remappings: Result, Error>, -} - -impl<'a> RemappingsProvider<'a> { - /// Find and parse remappings for the projects - /// - /// **Order** - /// - /// Remappings are built in this order (last item takes precedence) - /// - Autogenerated remappings - /// - toml remappings - /// - `remappings.txt` - /// - Environment variables - /// - CLI parameters - fn get_remappings(&self, remappings: Vec) -> Result, Error> { - trace!("get all remappings from {:?}", self.root); - /// prioritizes remappings that are closer: shorter `path` - /// - ("a", "1/2") over ("a", "1/2/3") - /// grouped by remapping context - fn insert_closest( - mappings: &mut BTreeMap, BTreeMap>, - context: Option, - key: String, - path: PathBuf, - ) { - let context_mappings = mappings.entry(context).or_default(); - match context_mappings.entry(key) { - Entry::Occupied(mut e) => { - if e.get().components().count() > path.components().count() { - e.insert(path); - } - } - Entry::Vacant(e) => { - e.insert(path); - } - } - } - - // Let's first just extend the remappings with the ones that were passed in, - // without any filtering. - let mut user_remappings = Vec::new(); - - // check env vars - if let Some(env_remappings) = remappings_from_env_var("DAPP_REMAPPINGS") - .or_else(|| remappings_from_env_var("FOUNDRY_REMAPPINGS")) - { - user_remappings - .extend(env_remappings.map_err::(|err| err.to_string().into())?); - } - - // check remappings.txt file - let remappings_file = self.root.join("remappings.txt"); - if remappings_file.is_file() { - let content = fs::read_to_string(remappings_file).map_err(|err| err.to_string())?; - let remappings_from_file: Result, _> = - remappings_from_newline(&content).collect(); - user_remappings - .extend(remappings_from_file.map_err::(|err| err.to_string().into())?); - } - - user_remappings.extend(remappings); - // Let's now use the wrapper to conditionally extend the remappings with the autodetected - // ones. We want to avoid duplicates, and the wrapper will handle this for us. - let mut all_remappings = Remappings::new_with_remappings(user_remappings); - - // scan all library dirs and autodetect remappings - // todo: if a lib specifies contexts for remappings manually, we need to figure out how to - // resolve that - if self.auto_detect_remappings { - let mut lib_remappings = BTreeMap::new(); - // find all remappings of from libs that use a foundry.toml - for r in self.lib_foundry_toml_remappings() { - insert_closest(&mut lib_remappings, r.context, r.name, r.path.into()); - } - // use auto detection for all libs - for r in self - .lib_paths - .iter() - .map(|lib| self.root.join(lib)) - .inspect(|lib| { - trace!("find all remappings in lib path: {:?}", lib); - }) - .flat_map(Remapping::find_many) - { - // this is an additional safety check for weird auto-detected remappings - if ["lib/", "src/", "contracts/"].contains(&r.name.as_str()) { - trace!(target: "forge", "- skipping the remapping"); - continue; - } - insert_closest(&mut lib_remappings, r.context, r.name, r.path.into()); - } - - all_remappings.extend( - lib_remappings - .into_iter() - .flat_map(|(context, remappings)| { - remappings.into_iter().map(move |(name, path)| Remapping { - context: context.clone(), - name, - path: path.to_string_lossy().into(), - }) - }) - .collect(), - ); - } - - Ok(all_remappings.into_inner()) - } - - /// Returns all remappings declared in foundry.toml files of libraries - fn lib_foundry_toml_remappings(&self) -> impl Iterator + '_ { - self.lib_paths - .iter() - .map(|p| self.root.join(p)) - .flat_map(foundry_toml_dirs) - .inspect(|lib| { - trace!("find all remappings of nested foundry.toml lib: {:?}", lib); - }) - .flat_map(|lib: PathBuf| { - // load config, of the nested lib if it exists - let config = Config::load_with_root(&lib).sanitized(); - - // if the configured _src_ directory is set to something that - // [Remapping::find_many()] doesn't classify as a src directory (src, contracts, - // lib), then we need to manually add a remapping here - let mut src_remapping = None; - if ![Path::new("src"), Path::new("contracts"), Path::new("lib")] - .contains(&config.src.as_path()) - { - if let Some(name) = lib.file_name().and_then(|s| s.to_str()) { - let mut r = Remapping { - context: None, - name: format!("{name}/"), - path: format!("{}", lib.join(&config.src).display()), - }; - if !r.path.ends_with('/') { - r.path.push('/') - } - src_remapping = Some(r); - } - } - - // Eventually, we could set context for remappings at this location, - // taking into account the OS platform. We'll need to be able to handle nested - // contexts depending on dependencies for this to work. - // For now, we just leave the default context (none). - let mut remappings = config - .remappings - .into_iter() - .map(Remapping::from) - .collect::>(); - - if let Some(r) = src_remapping { - remappings.push(r); - } - remappings - }) - } -} - -impl<'a> Provider for RemappingsProvider<'a> { - fn metadata(&self) -> Metadata { - Metadata::named("Remapping Provider") - } - - fn data(&self) -> Result, Error> { - let remappings = match &self.remappings { - Ok(remappings) => self.get_remappings(remappings.clone()), - Err(err) => { - if let figment::error::Kind::MissingField(_) = err.kind { - self.get_remappings(vec![]) - } else { - return Err(err.clone()); - } - } - }?; - - // turn the absolute remapping into a relative one by stripping the `root` - let remappings = remappings - .into_iter() - .map(|r| RelativeRemapping::new(r, self.root).to_string()) - .collect::>(); - - Ok(Map::from([( - Config::selected_profile(), - Dict::from([( - "remappings".to_string(), - figment::value::Value::from(remappings), - )]), - )])) - } - - fn profile(&self) -> Option { - Some(Config::selected_profile()) - } -} diff --git a/foundry-config/src/resolve.rs b/foundry-config/src/resolve.rs deleted file mode 100644 index b062558b3..000000000 --- a/foundry-config/src/resolve.rs +++ /dev/null @@ -1,84 +0,0 @@ -//! Helper for resolving env vars - -use once_cell::sync::Lazy; -use regex::Regex; -use std::{env, env::VarError, fmt}; - -/// A regex that matches `${val}` placeholders -pub static RE_PLACEHOLDER: Lazy = - Lazy::new(|| Regex::new(r"(?m)(?P\$\{\s*(?P.*?)\s*})").unwrap()); - -/// Error when we failed to resolve an env var -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct UnresolvedEnvVarError { - /// The unresolved input string - pub unresolved: String, - /// Var that couldn't be resolved - pub var: String, - /// the `env::var` error - pub source: VarError, -} - -// === impl UnresolvedEnvVarError === - -impl UnresolvedEnvVarError { - /// Tries to resolve a value - pub fn try_resolve(&self) -> Result { - interpolate(&self.unresolved) - } -} - -impl fmt::Display for UnresolvedEnvVarError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "Failed to resolve env var `{}` in `{}`: {}", - self.var, self.unresolved, self.source - ) - } -} - -impl std::error::Error for UnresolvedEnvVarError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - Some(&self.source) - } -} - -/// Replaces all Env var placeholders in the input string with the values they hold -pub fn interpolate(input: &str) -> Result { - let mut res = input.to_string(); - - // loop over all placeholders in the input and replace them one by one - for caps in RE_PLACEHOLDER.captures_iter(input) { - let var = &caps["inner"]; - let value = env::var(var).map_err(|source| UnresolvedEnvVarError { - unresolved: input.to_string(), - var: var.to_string(), - source, - })?; - - res = res.replacen(&caps["outer"], &value, 1); - } - Ok(res) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn can_find_placeholder() { - let val = "https://eth-mainnet.alchemyapi.io/v2/346273846238426342"; - assert!(!RE_PLACEHOLDER.is_match(val)); - - let val = "${RPC_ENV}"; - assert!(RE_PLACEHOLDER.is_match(val)); - - let val = "https://eth-mainnet.alchemyapi.io/v2/${API_KEY}"; - assert!(RE_PLACEHOLDER.is_match(val)); - - let cap = RE_PLACEHOLDER.captures(val).unwrap(); - assert_eq!(cap.name("outer").unwrap().as_str(), "${API_KEY}"); - assert_eq!(cap.name("inner").unwrap().as_str(), "API_KEY"); - } -} diff --git a/foundry-config/src/utils.rs b/foundry-config/src/utils.rs deleted file mode 100644 index b27aa40c0..000000000 --- a/foundry-config/src/utils.rs +++ /dev/null @@ -1,325 +0,0 @@ -//! Utility functions - -use crate::Config; -use ethers_core::types::{serde_helpers::Numeric, U256}; -use ethers_solc::{ - remappings::{Remapping, RemappingError}, - EvmVersion, -}; -use figment::value::Value; -use revm_primitives::SpecId; -use serde::{de::Error, Deserialize, Deserializer}; -use std::{ - path::{Path, PathBuf}, - str::FromStr, -}; -use toml_edit::{Document, Item}; - -/// Loads the config for the current project workspace -pub fn load_config() -> Config { - load_config_with_root(None) -} - -/// Loads the config for the current project workspace or the provided root path -pub fn load_config_with_root(root: Option) -> Config { - if let Some(root) = root { - Config::load_with_root(root) - } else { - Config::load_with_root(find_project_root_path(None).unwrap()) - } - .sanitized() -} - -/// Returns the path of the top-level directory of the working git tree. If there is no working -/// tree, an error is returned. -pub fn find_git_root_path(relative_to: impl AsRef) -> eyre::Result { - let path = std::process::Command::new("git") - .args(["rev-parse", "--show-toplevel"]) - .current_dir(relative_to.as_ref()) - .output()? - .stdout; - let path = std::str::from_utf8(&path)?.trim_end_matches('\n'); - Ok(PathBuf::from(path)) -} - -/// Returns the root path to set for the project root -/// -/// traverse the dir tree up and look for a `foundry.toml` file starting at the given path or cwd, -/// but only until the root dir of the current repo so that -/// -/// ```text -/// -- foundry.toml -/// -/// -- repo -/// |__ .git -/// |__sub -/// |__ [given_path | cwd] -/// ``` -/// will still detect `repo` as root -pub fn find_project_root_path(path: Option<&PathBuf>) -> std::io::Result { - let cwd = &std::env::current_dir()?; - let cwd = path.unwrap_or(cwd); - let boundary = find_git_root_path(cwd) - .ok() - .filter(|p| !p.as_os_str().is_empty()) - .unwrap_or_else(|| cwd.clone()); - let mut cwd = cwd.as_path(); - // traverse as long as we're in the current git repo cwd - while cwd.starts_with(&boundary) { - let file_path = cwd.join(Config::FILE_NAME); - if file_path.is_file() { - return Ok(cwd.to_path_buf()); - } - if let Some(parent) = cwd.parent() { - cwd = parent; - } else { - break; - } - } - // no foundry.toml found - Ok(boundary) -} - -/// Returns all [`Remapping`]s contained in the `remappings` str separated by newlines -/// -/// # Example -/// -/// ``` -/// use foundry_config::remappings_from_newline; -/// let remappings: Result, _> = remappings_from_newline( -/// r#" -/// file-ds-test/=lib/ds-test/ -/// file-other/=lib/other/ -/// "#, -/// ) -/// .collect(); -/// ``` -pub fn remappings_from_newline( - remappings: &str, -) -> impl Iterator> + '_ { - remappings - .lines() - .map(|x| x.trim()) - .filter(|x| !x.is_empty()) - .map(Remapping::from_str) -} - -/// Returns the remappings from the given var -/// -/// Returns `None` if the env var is not set, otherwise all Remappings, See -/// `remappings_from_newline` -pub fn remappings_from_env_var(env_var: &str) -> Option, RemappingError>> { - let val = std::env::var(env_var).ok()?; - Some(remappings_from_newline(&val).collect()) -} - -/// Converts the `val` into a `figment::Value::Array` -/// -/// The values should be separated by commas, surrounding brackets are also supported `[a,b,c]` -pub fn to_array_value(val: &str) -> Result { - let value: Value = match Value::from(val) { - Value::String(_, val) => val - .trim_start_matches('[') - .trim_end_matches(']') - .split(',') - .map(|s| s.to_string()) - .collect::>() - .into(), - Value::Empty(_, _) => Vec::::new().into(), - val @ Value::Array(_, _) => val, - _ => return Err(format!("Invalid value `{val}`, expected an array").into()), - }; - Ok(value) -} - -/// Returns a list of _unique_ paths to all folders under `root` that contain a `foundry.toml` file -/// -/// This will also resolve symlinks -/// -/// # Example -/// -/// ```no_run -/// use foundry_config::utils; -/// let dirs = utils::foundry_toml_dirs("./lib"); -/// ``` -/// -/// for following layout this will return -/// `["lib/dep1", "lib/dep2"]` -/// -/// ```text -/// lib -/// └── dep1 -/// │ ├── foundry.toml -/// └── dep2 -/// ├── foundry.toml -/// ``` -pub fn foundry_toml_dirs(root: impl AsRef) -> Vec { - walkdir::WalkDir::new(root) - .max_depth(1) - .into_iter() - .filter_map(Result::ok) - .filter(|e| e.file_type().is_dir()) - .filter_map(|e| ethers_solc::utils::canonicalize(e.path()).ok()) - .filter(|p| p.join(Config::FILE_NAME).exists()) - .collect() -} - -/// Returns a remapping for the given dir -pub(crate) fn get_dir_remapping(dir: impl AsRef) -> Option { - let dir = dir.as_ref(); - if let Some(dir_name) = dir - .file_name() - .and_then(|s| s.to_str()) - .filter(|s| !s.is_empty()) - { - let mut r = Remapping { - context: None, - name: format!("{dir_name}/"), - path: format!("{}", dir.display()), - }; - if !r.path.ends_with('/') { - r.path.push('/') - } - Some(r) - } else { - None - } -} - -/// Returns all available `profile` keys in a given `.toml` file -/// -/// i.e. The toml below would return would return `["default", "ci", "local"]` -/// ```toml -/// [profile.default] -/// ... -/// [profile.ci] -/// ... -/// [profile.local] -/// ``` -pub fn get_available_profiles(toml_path: impl AsRef) -> eyre::Result> { - let mut result = vec![Config::DEFAULT_PROFILE.to_string()]; - - if !toml_path.as_ref().exists() { - return Ok(result); - } - - let doc = read_toml(toml_path)?; - - if let Some(Item::Table(profiles)) = doc.as_table().get(Config::PROFILE_SECTION) { - for (_, (profile, _)) in profiles.iter().enumerate() { - let p = profile.to_string(); - if !result.contains(&p) { - result.push(p); - } - } - } - - Ok(result) -} - -/// Returns a [`toml_edit::Document`] loaded from the provided `path`. -/// Can raise an error in case of I/O or parsing errors. -fn read_toml(path: impl AsRef) -> eyre::Result { - let path = path.as_ref().to_owned(); - let doc: Document = std::fs::read_to_string(path)?.parse()?; - Ok(doc) -} - -/// Deserialize stringified percent. The value must be between 0 and 100 inclusive. -pub(crate) fn deserialize_stringified_percent<'de, D>(deserializer: D) -> Result -where - D: Deserializer<'de>, -{ - let num: U256 = Numeric::deserialize(deserializer)? - .try_into() - .map_err(serde::de::Error::custom)?; - let num: u64 = num.try_into().map_err(serde::de::Error::custom)?; - if num <= 100 { - num.try_into().map_err(serde::de::Error::custom) - } else { - Err(serde::de::Error::custom("percent must be lte 100")) - } -} - -/// Deserialize an usize or -pub(crate) fn deserialize_usize_or_max<'de, D>(deserializer: D) -> Result -where - D: Deserializer<'de>, -{ - #[derive(Deserialize)] - #[serde(untagged)] - enum Val { - Number(usize), - Text(String), - } - - let num = match Val::deserialize(deserializer)? { - Val::Number(num) => num, - Val::Text(s) => { - match s.as_str() { - "max" | "MAX" | "Max" => { - // toml limitation - i64::MAX as usize - } - s => s.parse::().map_err(D::Error::custom).unwrap(), - } - } - }; - Ok(num) -} - -/// Returns the [SpecId] derived from [EvmVersion] -#[inline] -pub fn evm_spec_id(evm_version: &EvmVersion) -> SpecId { - match evm_version { - EvmVersion::Homestead => SpecId::HOMESTEAD, - EvmVersion::TangerineWhistle => SpecId::TANGERINE, - EvmVersion::SpuriousDragon => SpecId::SPURIOUS_DRAGON, - EvmVersion::Byzantium => SpecId::BYZANTIUM, - EvmVersion::Constantinople => SpecId::CONSTANTINOPLE, - EvmVersion::Petersburg => SpecId::PETERSBURG, - EvmVersion::Istanbul => SpecId::ISTANBUL, - EvmVersion::Berlin => SpecId::BERLIN, - EvmVersion::London => SpecId::LONDON, - EvmVersion::Paris => SpecId::MERGE, - EvmVersion::Shanghai => SpecId::SHANGHAI, - } -} - -#[cfg(test)] -mod tests { - use crate::get_available_profiles; - use std::path::Path; - - #[test] - fn get_profiles_from_toml() { - figment::Jail::expect_with(|jail| { - jail.create_file( - "foundry.toml", - r#" - [foo.baz] - libs = ['node_modules', 'lib'] - - [profile.default] - libs = ['node_modules', 'lib'] - - [profile.ci] - libs = ['node_modules', 'lib'] - - [profile.local] - libs = ['node_modules', 'lib'] - "#, - )?; - - let path = Path::new("./foundry.toml"); - let profiles = get_available_profiles(path).unwrap(); - - assert_eq!( - profiles, - vec!["default".to_string(), "ci".to_string(), "local".to_string()] - ); - - Ok(()) - }); - } -} diff --git a/foundry-config/src/warning.rs b/foundry-config/src/warning.rs deleted file mode 100644 index fc98be3d4..000000000 --- a/foundry-config/src/warning.rs +++ /dev/null @@ -1,84 +0,0 @@ -use figment::Profile; -use serde::{Deserialize, Serialize}; -use std::{fmt, path::PathBuf}; - -/// Warnings emitted during loading or managing Configuration -#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq)] -#[serde(tag = "type")] -pub enum Warning { - /// An unknown section was encountered in a TOML file - UnknownSection { - /// The unknown key - unknown_section: Profile, - /// The source where the key was found - source: Option, - }, - /// No local TOML file found, with location tried - NoLocalToml(PathBuf), - /// Could not read TOML - CouldNotReadToml { - /// The path of the TOML file - path: PathBuf, - /// The error message that occurred - err: String, - }, - /// Could not write TOML - CouldNotWriteToml { - /// The path of the TOML file - path: PathBuf, - /// The error message that occurred - err: String, - }, - /// Invalid profile. Profile should be a table - CouldNotFixProfile { - /// The path of the TOML file - path: PathBuf, - /// The profile to be fixed - profile: String, - /// The error message that occurred - err: String, - }, - /// Deprecated key. - DeprecatedKey { - /// The key being deprecated - old: String, - /// The new key replacing the deprecated one if not empty, otherwise, meaning the old one - /// is being removed completely without replacement - new: String, - }, -} - -impl fmt::Display for Warning { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::UnknownSection { unknown_section, source } => { - let source = source.as_ref().map(|src| format!(" in {src}")).unwrap_or_default(); - f.write_fmt(format_args!("Unknown section [{unknown_section}] found{source}. This notation for profiles has been deprecated and may result in the profile not being registered in future versions. Please use [profile.{unknown_section}] instead or run `forge config --fix`.")) - } - Self::NoLocalToml(tried) => { - let path = tried.display(); - f.write_fmt(format_args!("No local TOML found to fix at {path}. Change the current directory to a project path or set the foundry.toml path with the FOUNDRY_CONFIG environment variable")) - } - Self::CouldNotReadToml { path, err } => { - f.write_fmt(format_args!("Could not read TOML at {}: {err}", path.display())) - } - Self::CouldNotWriteToml { path, err } => { - f.write_fmt(format_args!("Could not write TOML to {}: {err}", path.display())) - } - Self::CouldNotFixProfile { path, profile, err } => f.write_fmt(format_args!( - "Could not fix [{}] in TOML at {}: {}", - profile, - path.display(), - err - )), - Self::DeprecatedKey { old, new } if new.is_empty() => f.write_fmt(format_args!( - "Key `{old}` is being deprecated and will be removed in future versions.", - )), - Self::DeprecatedKey { old, new } => f.write_fmt(format_args!( - "Key `{old}` is being deprecated in favor of `{new}`. It will be removed in future versions.", - )), - } - } -} - -impl std::error::Error for Warning {} From 760d70d504601dcba85a7b95eb74af0b8d8a732f Mon Sep 17 00:00:00 2001 From: Govardhan G D Date: Sun, 17 Sep 2023 10:12:47 +0530 Subject: [PATCH 16/25] error handling + comments Signed-off-by: Govardhan G D --- src/bin/languageserver/mod.rs | 54 ++++++++++++++++++++++++++++++----- 1 file changed, 47 insertions(+), 7 deletions(-) diff --git a/src/bin/languageserver/mod.rs b/src/bin/languageserver/mod.rs index 83e4d702e..bbf845e15 100644 --- a/src/bin/languageserver/mod.rs +++ b/src/bin/languageserver/mod.rs @@ -1,5 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 +use forge_fmt::{format, parse, FormatterConfig}; use itertools::Itertools; use num_traits::ToPrimitive; use rust_lapper::{Interval, Lapper}; @@ -39,8 +40,6 @@ use tower_lsp::{ Client, LanguageServer, LspService, Server, }; -use forge_fmt::{format, parse, FormatterConfig}; - use crate::cli::{target_arg, LanguageServerCommand}; /// Represents the type of the code object that a reference points to @@ -2138,19 +2137,60 @@ impl LanguageServer for SolangServer { Ok(locations) } + /// Called when "Format Document" is called by the user on the client side. + /// + /// Expected to return the formatted version of source code present in the file on which this method was triggered. + /// + /// ### Arguments + /// * `DocumentFormattingParams` + /// * provides the name of the file whose code is to be formatted. + /// * provides options that help configure how the file is formatted. + /// + /// ### Edge cases + /// * Returns `Err` when + /// * an invalid file path is received. + /// * reading the file fails. + /// * parsing the file fails. + /// * formatting the file fails. async fn formatting(&self, params: DocumentFormattingParams) -> Result>> { - let source_path = params.text_document.uri.to_file_path().unwrap(); - let source = std::fs::read_to_string(source_path).unwrap(); - let source_parsed = parse(&source).unwrap(); + // get parse tree for the input file + let uri = params.text_document.uri; + let source_path = uri.to_file_path().map_err(|_| Error { + code: ErrorCode::InvalidRequest, + message: format!("Received invalid URI: {uri}").into(), + data: None, + })?; + let source = std::fs::read_to_string(source_path).map_err(|err| Error { + code: ErrorCode::InternalError, + message: format!("Failed to read file: {uri}").into(), + data: Some(Value::String(format!("{:?}", err))), + })?; + let source_parsed = parse(&source).map_err(|err| { + let err = err + .into_iter() + .map(|e| Value::String(e.message)) + .collect::>(); + Error { + code: ErrorCode::InternalError, + message: format!("Failed to parse file: {uri}").into(), + data: Some(Value::Array(err)), + } + })?; + // get the formatted text let config = FormatterConfig { line_length: 80, + tab_width: params.options.tab_size as _, ..Default::default() }; - let mut source_formatted = String::new(); - format(&mut source_formatted, source_parsed, config).unwrap(); + format(&mut source_formatted, source_parsed, config).map_err(|err| Error { + code: ErrorCode::InternalError, + message: format!("Failed to format file: {uri}").into(), + data: Some(Value::String(format!("{:?}", err))), + })?; + // create a `TextEdit` instance that replaces the contents of the file with the formatted text let text_edit = TextEdit { range: Range { start: Position { From 82308731b41aad31b83871ac148c2f632614b291 Mon Sep 17 00:00:00 2001 From: Govardhan G D Date: Tue, 19 Sep 2023 14:01:58 +0530 Subject: [PATCH 17/25] set MSRV to 1.70 Signed-off-by: Govardhan G D --- forge-fmt/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/forge-fmt/Cargo.toml b/forge-fmt/Cargo.toml index 377163122..6579f9c1d 100644 --- a/forge-fmt/Cargo.toml +++ b/forge-fmt/Cargo.toml @@ -2,14 +2,14 @@ name = "forge-fmt" version = "0.2.0" edition = "2021" -rust-version = "1.72" +rust-version = "1.70" authors = ["Foundry Contributors"] license = "MIT OR Apache-2.0" homepage = "https://github.com/foundry-rs/foundry" repository = "https://github.com/foundry-rs/foundry" [dependencies] -ethers-core = { git = "https://github.com/gakonst/ethers-rs", default-features = false } +ethers-core = "2.0.10" solang-parser = { path = "../solang-parser" } itertools = "0.11" thiserror = "1" From 53301acc02f000d90e75d3c5e7ab92f5899ef3cb Mon Sep 17 00:00:00 2001 From: Govardhan G D Date: Tue, 19 Sep 2023 17:43:30 +0530 Subject: [PATCH 18/25] add SPDX licence to the newly added files Signed-off-by: Govardhan G D --- forge-fmt/src/buffer.rs | 2 ++ forge-fmt/src/chunk.rs | 2 ++ forge-fmt/src/comments.rs | 2 ++ forge-fmt/src/config.rs | 2 ++ forge-fmt/src/formatter.rs | 2 ++ forge-fmt/src/helpers.rs | 2 ++ forge-fmt/src/inline_config.rs | 2 ++ forge-fmt/src/lib.rs | 2 ++ forge-fmt/src/macros.rs | 2 ++ forge-fmt/src/solang_ext/ast_eq.rs | 2 ++ forge-fmt/src/solang_ext/loc.rs | 2 ++ forge-fmt/src/solang_ext/mod.rs | 2 ++ forge-fmt/src/solang_ext/safe_unwrap.rs | 2 ++ forge-fmt/src/string.rs | 2 ++ forge-fmt/src/visit.rs | 2 ++ forge-fmt/tests/formatter.rs | 2 ++ 16 files changed, 32 insertions(+) diff --git a/forge-fmt/src/buffer.rs b/forge-fmt/src/buffer.rs index ca946dae4..60aac6c30 100644 --- a/forge-fmt/src/buffer.rs +++ b/forge-fmt/src/buffer.rs @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: Apache-2.0 + //! Format buffer use std::fmt::Write; diff --git a/forge-fmt/src/chunk.rs b/forge-fmt/src/chunk.rs index 9d069ffdf..9568e31f1 100644 --- a/forge-fmt/src/chunk.rs +++ b/forge-fmt/src/chunk.rs @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: Apache-2.0 + use crate::comments::CommentWithMetadata; /// Holds information about a non-whitespace-splittable string, and the surrounding comments diff --git a/forge-fmt/src/comments.rs b/forge-fmt/src/comments.rs index a51952627..61ccbde71 100644 --- a/forge-fmt/src/comments.rs +++ b/forge-fmt/src/comments.rs @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: Apache-2.0 + use crate::inline_config::{InlineConfigItem, InvalidInlineConfigItem}; use itertools::Itertools; use solang_parser::pt::*; diff --git a/forge-fmt/src/config.rs b/forge-fmt/src/config.rs index 54fa65ed5..a13d54147 100644 --- a/forge-fmt/src/config.rs +++ b/forge-fmt/src/config.rs @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: Apache-2.0 + //! Configuration specific to the `forge fmt` command and the `forge_fmt` package use serde::{Deserialize, Serialize}; diff --git a/forge-fmt/src/formatter.rs b/forge-fmt/src/formatter.rs index 429902200..b738475cb 100644 --- a/forge-fmt/src/formatter.rs +++ b/forge-fmt/src/formatter.rs @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: Apache-2.0 + //! A Solidity formatter use crate::{ diff --git a/forge-fmt/src/helpers.rs b/forge-fmt/src/helpers.rs index 98e8fa7f7..ac021d033 100644 --- a/forge-fmt/src/helpers.rs +++ b/forge-fmt/src/helpers.rs @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: Apache-2.0 + use crate::{ inline_config::{InlineConfig, InvalidInlineConfigItem}, Comments, Formatter, FormatterConfig, FormatterError, Visitable, diff --git a/forge-fmt/src/inline_config.rs b/forge-fmt/src/inline_config.rs index ae6a6adf2..e88d873d3 100644 --- a/forge-fmt/src/inline_config.rs +++ b/forge-fmt/src/inline_config.rs @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: Apache-2.0 + use crate::comments::{CommentState, CommentStringExt}; use itertools::Itertools; use solang_parser::pt::Loc; diff --git a/forge-fmt/src/lib.rs b/forge-fmt/src/lib.rs index a79a269d9..729ca9796 100644 --- a/forge-fmt/src/lib.rs +++ b/forge-fmt/src/lib.rs @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: Apache-2.0 + #![doc = include_str!("../README.md")] #![cfg_attr(not(test), warn(unused_crate_dependencies))] diff --git a/forge-fmt/src/macros.rs b/forge-fmt/src/macros.rs index 68305d9f2..c7ca59efd 100644 --- a/forge-fmt/src/macros.rs +++ b/forge-fmt/src/macros.rs @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: Apache-2.0 + macro_rules! write_chunk { ($self:expr, $format_str:literal) => {{ write_chunk!($self, $format_str,) diff --git a/forge-fmt/src/solang_ext/ast_eq.rs b/forge-fmt/src/solang_ext/ast_eq.rs index 862bdb9d4..cd5b20253 100644 --- a/forge-fmt/src/solang_ext/ast_eq.rs +++ b/forge-fmt/src/solang_ext/ast_eq.rs @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: Apache-2.0 + use ethers_core::types::{H160, I256, U256}; use solang_parser::pt::*; use std::str::FromStr; diff --git a/forge-fmt/src/solang_ext/loc.rs b/forge-fmt/src/solang_ext/loc.rs index 2fcbaf995..e59b419f4 100644 --- a/forge-fmt/src/solang_ext/loc.rs +++ b/forge-fmt/src/solang_ext/loc.rs @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: Apache-2.0 + use solang_parser::pt; use std::{borrow::Cow, rc::Rc, sync::Arc}; diff --git a/forge-fmt/src/solang_ext/mod.rs b/forge-fmt/src/solang_ext/mod.rs index aa4fe734e..5ca238473 100644 --- a/forge-fmt/src/solang_ext/mod.rs +++ b/forge-fmt/src/solang_ext/mod.rs @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: Apache-2.0 + //! Extension traits and modules to the [`solang_parser`] crate. /// Same as [`solang_parser::pt`], but with the patched `CodeLocation`. diff --git a/forge-fmt/src/solang_ext/safe_unwrap.rs b/forge-fmt/src/solang_ext/safe_unwrap.rs index fe2810ad9..f3ec77f8c 100644 --- a/forge-fmt/src/solang_ext/safe_unwrap.rs +++ b/forge-fmt/src/solang_ext/safe_unwrap.rs @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: Apache-2.0 + use solang_parser::pt; /// Trait implemented to unwrap optional parse tree items initially introduced in diff --git a/forge-fmt/src/string.rs b/forge-fmt/src/string.rs index c387d2389..34e7a996a 100644 --- a/forge-fmt/src/string.rs +++ b/forge-fmt/src/string.rs @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: Apache-2.0 + //! Helpfers for dealing with quoted strings /// The state of a character in a string with quotable components diff --git a/forge-fmt/src/visit.rs b/forge-fmt/src/visit.rs index d4ed491cc..edbbc3827 100644 --- a/forge-fmt/src/visit.rs +++ b/forge-fmt/src/visit.rs @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: Apache-2.0 + //! Visitor helpers to traverse the [solang Solidity Parse Tree](solang_parser::pt). use crate::solang_ext::pt::*; diff --git a/forge-fmt/tests/formatter.rs b/forge-fmt/tests/formatter.rs index 363b5dac9..d125d5c18 100644 --- a/forge-fmt/tests/formatter.rs +++ b/forge-fmt/tests/formatter.rs @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: Apache-2.0 + use forge_fmt::{format, parse, solang_ext::AstEq, FormatterConfig}; use itertools::Itertools; use std::{fs, path::PathBuf}; From 47bab7d3a1d6fd90a0cf9817e5a65249b1ce07ba Mon Sep 17 00:00:00 2001 From: Govardhan G D Date: Tue, 19 Sep 2023 17:57:00 +0530 Subject: [PATCH 19/25] add comments for the virtual_functions field in Contract struct Signed-off-by: Govardhan G D --- src/sema/ast.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/sema/ast.rs b/src/sema/ast.rs index 546efe86f..2118b39ad 100644 --- a/src/sema/ast.rs +++ b/src/sema/ast.rs @@ -768,6 +768,9 @@ pub struct Contract { pub fixed_layout_size: BigInt, pub functions: Vec, pub all_functions: BTreeMap, + // maps the name of virtual functions to a vector of overriden functions. + // Each time a virtual function is overriden, there will be an entry pushed to the vector. The last + // element represents the current overriding function - there will be at least one entry in this vector. pub virtual_functions: HashMap>, pub yul_functions: Vec, pub variables: Vec, From 6561dd743311ae587503c07b3b02b779161117a1 Mon Sep 17 00:00:00 2001 From: Govardhan G D Date: Fri, 22 Sep 2023 11:21:55 +0530 Subject: [PATCH 20/25] add forge-fmt dependency Signed-off-by: Govardhan G D --- Cargo.toml | 4 +- forge-fmt/Cargo.toml | 24 - forge-fmt/README.md | 193 - forge-fmt/src/buffer.rs | 449 -- forge-fmt/src/chunk.rs | 73 - forge-fmt/src/comments.rs | 481 --- forge-fmt/src/config.rs | 126 - forge-fmt/src/formatter.rs | 3696 ----------------- forge-fmt/src/helpers.rs | 121 - forge-fmt/src/inline_config.rs | 177 - forge-fmt/src/lib.rs | 26 - forge-fmt/src/macros.rs | 127 - forge-fmt/src/solang_ext/ast_eq.rs | 703 ---- forge-fmt/src/solang_ext/loc.rs | 158 - forge-fmt/src/solang_ext/mod.rs | 30 - forge-fmt/src/solang_ext/safe_unwrap.rs | 54 - forge-fmt/src/string.rs | 184 - forge-fmt/src/visit.rs | 657 --- forge-fmt/testdata/Annotation/fmt.sol | 15 - forge-fmt/testdata/Annotation/original.sol | 15 - forge-fmt/testdata/ArrayExpressions/fmt.sol | 69 - .../testdata/ArrayExpressions/original.sol | 46 - .../ConditionalOperatorExpression/fmt.sol | 37 - .../original.sol | 33 - .../testdata/ConstructorDefinition/fmt.sol | 42 - .../ConstructorDefinition/original.sol | 17 - .../bracket-spacing.fmt.sol | 37 - .../contract-new-lines.fmt.sol | 52 - forge-fmt/testdata/ContractDefinition/fmt.sol | 47 - .../testdata/ContractDefinition/original.sol | 40 - forge-fmt/testdata/DoWhileStatement/fmt.sol | 30 - .../testdata/DoWhileStatement/original.sol | 24 - forge-fmt/testdata/DocComments/fmt.sol | 100 - forge-fmt/testdata/DocComments/original.sol | 95 - .../DocComments/wrap-comments.fmt.sol | 128 - forge-fmt/testdata/EmitStatement/fmt.sol | 31 - forge-fmt/testdata/EmitStatement/original.sol | 24 - .../EnumDefinition/bracket-spacing.fmt.sol | 21 - forge-fmt/testdata/EnumDefinition/fmt.sol | 20 - .../testdata/EnumDefinition/original.sol | 7 - forge-fmt/testdata/ErrorDefinition/fmt.sol | 14 - .../testdata/ErrorDefinition/original.sol | 14 - forge-fmt/testdata/EventDefinition/fmt.sol | 144 - .../testdata/EventDefinition/original.sol | 36 - forge-fmt/testdata/ForStatement/fmt.sol | 37 - forge-fmt/testdata/ForStatement/original.sol | 33 - .../FunctionCall/bracket-spacing.fmt.sol | 37 - forge-fmt/testdata/FunctionCall/fmt.sol | 36 - forge-fmt/testdata/FunctionCall/original.sol | 29 - .../bracket-spacing.fmt.sol | 55 - .../FunctionCallArgsStatement/fmt.sol | 54 - .../FunctionCallArgsStatement/original.sol | 50 - .../testdata/FunctionDefinition/all.fmt.sol | 730 ---- forge-fmt/testdata/FunctionDefinition/fmt.sol | 709 ---- .../testdata/FunctionDefinition/original.sol | 218 - .../override-spacing.fmt.sol | 710 ---- .../FunctionDefinition/params-first.fmt.sol | 710 ---- forge-fmt/testdata/FunctionType/fmt.sol | 31 - forge-fmt/testdata/FunctionType/original.sol | 31 - .../testdata/IfStatement/block-multi.fmt.sol | 171 - .../testdata/IfStatement/block-single.fmt.sol | 123 - forge-fmt/testdata/IfStatement/fmt.sol | 145 - forge-fmt/testdata/IfStatement/original.sol | 119 - forge-fmt/testdata/IfStatement2/fmt.sol | 7 - forge-fmt/testdata/IfStatement2/original.sol | 10 - .../ImportDirective/bracket-spacing.fmt.sol | 21 - forge-fmt/testdata/ImportDirective/fmt.sol | 20 - .../testdata/ImportDirective/original.sol | 10 - .../ImportDirective/preserve-quote.fmt.sol | 21 - .../ImportDirective/single-quote.fmt.sol | 21 - forge-fmt/testdata/InlineDisable/fmt.sol | 491 --- forge-fmt/testdata/InlineDisable/original.sol | 469 --- forge-fmt/testdata/IntTypes/fmt.sol | 24 - forge-fmt/testdata/IntTypes/original.sol | 24 - forge-fmt/testdata/IntTypes/preserve.fmt.sol | 25 - forge-fmt/testdata/IntTypes/short.fmt.sol | 25 - forge-fmt/testdata/LiteralExpression/fmt.sol | 59 - .../testdata/LiteralExpression/original.sol | 58 - .../LiteralExpression/preserve-quote.fmt.sol | 60 - .../LiteralExpression/single-quote.fmt.sol | 60 - forge-fmt/testdata/MappingType/fmt.sol | 35 - forge-fmt/testdata/MappingType/original.sol | 23 - forge-fmt/testdata/ModifierDefinition/fmt.sol | 14 - .../testdata/ModifierDefinition/original.sol | 9 - .../override-spacing.fmt.sol | 15 - .../NamedFunctionCallExpression/fmt.sol | 47 - .../NamedFunctionCallExpression/original.sol | 28 - .../testdata/NumberLiteralUnderscore/fmt.sol | 25 - .../NumberLiteralUnderscore/original.sol | 25 - .../NumberLiteralUnderscore/remove.fmt.sol | 26 - .../NumberLiteralUnderscore/thousands.fmt.sol | 26 - .../testdata/OperatorExpressions/fmt.sol | 43 - .../testdata/OperatorExpressions/original.sol | 30 - forge-fmt/testdata/PragmaDirective/fmt.sol | 9 - .../testdata/PragmaDirective/original.sol | 9 - forge-fmt/testdata/Repros/fmt.sol | 7 - forge-fmt/testdata/Repros/original.sol | 7 - forge-fmt/testdata/ReturnStatement/fmt.sol | 66 - .../testdata/ReturnStatement/original.sol | 60 - .../testdata/RevertNamedArgsStatement/fmt.sol | 35 - .../RevertNamedArgsStatement/original.sol | 32 - forge-fmt/testdata/RevertStatement/fmt.sol | 56 - .../testdata/RevertStatement/original.sol | 44 - forge-fmt/testdata/SimpleComments/fmt.sol | 80 - .../testdata/SimpleComments/original.sol | 83 - .../SimpleComments/wrap-comments.fmt.sol | 92 - .../StatementBlock/bracket-spacing.fmt.sol | 20 - forge-fmt/testdata/StatementBlock/fmt.sol | 19 - .../testdata/StatementBlock/original.sol | 17 - .../StructDefinition/bracket-spacing.fmt.sol | 15 - forge-fmt/testdata/StructDefinition/fmt.sol | 14 - .../testdata/StructDefinition/original.sol | 10 - forge-fmt/testdata/ThisExpression/fmt.sol | 20 - .../testdata/ThisExpression/original.sol | 17 - forge-fmt/testdata/TrailingComma/fmt.sol | 12 - forge-fmt/testdata/TrailingComma/original.sol | 12 - forge-fmt/testdata/TryStatement/fmt.sol | 74 - forge-fmt/testdata/TryStatement/original.sol | 66 - forge-fmt/testdata/TypeDefinition/fmt.sol | 12 - .../testdata/TypeDefinition/original.sol | 12 - forge-fmt/testdata/UnitExpression/fmt.sol | 24 - .../testdata/UnitExpression/original.sol | 23 - forge-fmt/testdata/UsingDirective/fmt.sol | 36 - .../testdata/UsingDirective/original.sol | 11 - .../bracket-spacing.fmt.sol | 27 - forge-fmt/testdata/VariableAssignment/fmt.sol | 26 - .../testdata/VariableAssignment/original.sol | 26 - forge-fmt/testdata/VariableDefinition/fmt.sol | 65 - .../testdata/VariableDefinition/original.sol | 27 - .../override-spacing.fmt.sol | 66 - .../WhileStatement/block-multi.fmt.sol | 80 - .../WhileStatement/block-single.fmt.sol | 52 - forge-fmt/testdata/WhileStatement/fmt.sol | 59 - .../testdata/WhileStatement/original.sol | 51 - forge-fmt/testdata/Yul/fmt.sol | 188 - forge-fmt/testdata/Yul/original.sol | 141 - forge-fmt/testdata/YulStrings/fmt.sol | 16 - forge-fmt/testdata/YulStrings/original.sol | 16 - .../YulStrings/preserve-quote.fmt.sol | 17 - .../testdata/YulStrings/single-quote.fmt.sol | 17 - forge-fmt/tests/formatter.rs | 213 - 141 files changed, 2 insertions(+), 16279 deletions(-) delete mode 100644 forge-fmt/Cargo.toml delete mode 100644 forge-fmt/README.md delete mode 100644 forge-fmt/src/buffer.rs delete mode 100644 forge-fmt/src/chunk.rs delete mode 100644 forge-fmt/src/comments.rs delete mode 100644 forge-fmt/src/config.rs delete mode 100644 forge-fmt/src/formatter.rs delete mode 100644 forge-fmt/src/helpers.rs delete mode 100644 forge-fmt/src/inline_config.rs delete mode 100644 forge-fmt/src/lib.rs delete mode 100644 forge-fmt/src/macros.rs delete mode 100644 forge-fmt/src/solang_ext/ast_eq.rs delete mode 100644 forge-fmt/src/solang_ext/loc.rs delete mode 100644 forge-fmt/src/solang_ext/mod.rs delete mode 100644 forge-fmt/src/solang_ext/safe_unwrap.rs delete mode 100644 forge-fmt/src/string.rs delete mode 100644 forge-fmt/src/visit.rs delete mode 100644 forge-fmt/testdata/Annotation/fmt.sol delete mode 100644 forge-fmt/testdata/Annotation/original.sol delete mode 100644 forge-fmt/testdata/ArrayExpressions/fmt.sol delete mode 100644 forge-fmt/testdata/ArrayExpressions/original.sol delete mode 100644 forge-fmt/testdata/ConditionalOperatorExpression/fmt.sol delete mode 100644 forge-fmt/testdata/ConditionalOperatorExpression/original.sol delete mode 100644 forge-fmt/testdata/ConstructorDefinition/fmt.sol delete mode 100644 forge-fmt/testdata/ConstructorDefinition/original.sol delete mode 100644 forge-fmt/testdata/ContractDefinition/bracket-spacing.fmt.sol delete mode 100644 forge-fmt/testdata/ContractDefinition/contract-new-lines.fmt.sol delete mode 100644 forge-fmt/testdata/ContractDefinition/fmt.sol delete mode 100644 forge-fmt/testdata/ContractDefinition/original.sol delete mode 100644 forge-fmt/testdata/DoWhileStatement/fmt.sol delete mode 100644 forge-fmt/testdata/DoWhileStatement/original.sol delete mode 100644 forge-fmt/testdata/DocComments/fmt.sol delete mode 100644 forge-fmt/testdata/DocComments/original.sol delete mode 100644 forge-fmt/testdata/DocComments/wrap-comments.fmt.sol delete mode 100644 forge-fmt/testdata/EmitStatement/fmt.sol delete mode 100644 forge-fmt/testdata/EmitStatement/original.sol delete mode 100644 forge-fmt/testdata/EnumDefinition/bracket-spacing.fmt.sol delete mode 100644 forge-fmt/testdata/EnumDefinition/fmt.sol delete mode 100644 forge-fmt/testdata/EnumDefinition/original.sol delete mode 100644 forge-fmt/testdata/ErrorDefinition/fmt.sol delete mode 100644 forge-fmt/testdata/ErrorDefinition/original.sol delete mode 100644 forge-fmt/testdata/EventDefinition/fmt.sol delete mode 100644 forge-fmt/testdata/EventDefinition/original.sol delete mode 100644 forge-fmt/testdata/ForStatement/fmt.sol delete mode 100644 forge-fmt/testdata/ForStatement/original.sol delete mode 100644 forge-fmt/testdata/FunctionCall/bracket-spacing.fmt.sol delete mode 100644 forge-fmt/testdata/FunctionCall/fmt.sol delete mode 100644 forge-fmt/testdata/FunctionCall/original.sol delete mode 100644 forge-fmt/testdata/FunctionCallArgsStatement/bracket-spacing.fmt.sol delete mode 100644 forge-fmt/testdata/FunctionCallArgsStatement/fmt.sol delete mode 100644 forge-fmt/testdata/FunctionCallArgsStatement/original.sol delete mode 100644 forge-fmt/testdata/FunctionDefinition/all.fmt.sol delete mode 100644 forge-fmt/testdata/FunctionDefinition/fmt.sol delete mode 100644 forge-fmt/testdata/FunctionDefinition/original.sol delete mode 100644 forge-fmt/testdata/FunctionDefinition/override-spacing.fmt.sol delete mode 100644 forge-fmt/testdata/FunctionDefinition/params-first.fmt.sol delete mode 100644 forge-fmt/testdata/FunctionType/fmt.sol delete mode 100644 forge-fmt/testdata/FunctionType/original.sol delete mode 100644 forge-fmt/testdata/IfStatement/block-multi.fmt.sol delete mode 100644 forge-fmt/testdata/IfStatement/block-single.fmt.sol delete mode 100644 forge-fmt/testdata/IfStatement/fmt.sol delete mode 100644 forge-fmt/testdata/IfStatement/original.sol delete mode 100644 forge-fmt/testdata/IfStatement2/fmt.sol delete mode 100644 forge-fmt/testdata/IfStatement2/original.sol delete mode 100644 forge-fmt/testdata/ImportDirective/bracket-spacing.fmt.sol delete mode 100644 forge-fmt/testdata/ImportDirective/fmt.sol delete mode 100644 forge-fmt/testdata/ImportDirective/original.sol delete mode 100644 forge-fmt/testdata/ImportDirective/preserve-quote.fmt.sol delete mode 100644 forge-fmt/testdata/ImportDirective/single-quote.fmt.sol delete mode 100644 forge-fmt/testdata/InlineDisable/fmt.sol delete mode 100644 forge-fmt/testdata/InlineDisable/original.sol delete mode 100644 forge-fmt/testdata/IntTypes/fmt.sol delete mode 100644 forge-fmt/testdata/IntTypes/original.sol delete mode 100644 forge-fmt/testdata/IntTypes/preserve.fmt.sol delete mode 100644 forge-fmt/testdata/IntTypes/short.fmt.sol delete mode 100644 forge-fmt/testdata/LiteralExpression/fmt.sol delete mode 100644 forge-fmt/testdata/LiteralExpression/original.sol delete mode 100644 forge-fmt/testdata/LiteralExpression/preserve-quote.fmt.sol delete mode 100644 forge-fmt/testdata/LiteralExpression/single-quote.fmt.sol delete mode 100644 forge-fmt/testdata/MappingType/fmt.sol delete mode 100644 forge-fmt/testdata/MappingType/original.sol delete mode 100644 forge-fmt/testdata/ModifierDefinition/fmt.sol delete mode 100644 forge-fmt/testdata/ModifierDefinition/original.sol delete mode 100644 forge-fmt/testdata/ModifierDefinition/override-spacing.fmt.sol delete mode 100644 forge-fmt/testdata/NamedFunctionCallExpression/fmt.sol delete mode 100644 forge-fmt/testdata/NamedFunctionCallExpression/original.sol delete mode 100644 forge-fmt/testdata/NumberLiteralUnderscore/fmt.sol delete mode 100644 forge-fmt/testdata/NumberLiteralUnderscore/original.sol delete mode 100644 forge-fmt/testdata/NumberLiteralUnderscore/remove.fmt.sol delete mode 100644 forge-fmt/testdata/NumberLiteralUnderscore/thousands.fmt.sol delete mode 100644 forge-fmt/testdata/OperatorExpressions/fmt.sol delete mode 100644 forge-fmt/testdata/OperatorExpressions/original.sol delete mode 100644 forge-fmt/testdata/PragmaDirective/fmt.sol delete mode 100644 forge-fmt/testdata/PragmaDirective/original.sol delete mode 100644 forge-fmt/testdata/Repros/fmt.sol delete mode 100644 forge-fmt/testdata/Repros/original.sol delete mode 100644 forge-fmt/testdata/ReturnStatement/fmt.sol delete mode 100644 forge-fmt/testdata/ReturnStatement/original.sol delete mode 100644 forge-fmt/testdata/RevertNamedArgsStatement/fmt.sol delete mode 100644 forge-fmt/testdata/RevertNamedArgsStatement/original.sol delete mode 100644 forge-fmt/testdata/RevertStatement/fmt.sol delete mode 100644 forge-fmt/testdata/RevertStatement/original.sol delete mode 100644 forge-fmt/testdata/SimpleComments/fmt.sol delete mode 100644 forge-fmt/testdata/SimpleComments/original.sol delete mode 100644 forge-fmt/testdata/SimpleComments/wrap-comments.fmt.sol delete mode 100644 forge-fmt/testdata/StatementBlock/bracket-spacing.fmt.sol delete mode 100644 forge-fmt/testdata/StatementBlock/fmt.sol delete mode 100644 forge-fmt/testdata/StatementBlock/original.sol delete mode 100644 forge-fmt/testdata/StructDefinition/bracket-spacing.fmt.sol delete mode 100644 forge-fmt/testdata/StructDefinition/fmt.sol delete mode 100644 forge-fmt/testdata/StructDefinition/original.sol delete mode 100644 forge-fmt/testdata/ThisExpression/fmt.sol delete mode 100644 forge-fmt/testdata/ThisExpression/original.sol delete mode 100644 forge-fmt/testdata/TrailingComma/fmt.sol delete mode 100644 forge-fmt/testdata/TrailingComma/original.sol delete mode 100644 forge-fmt/testdata/TryStatement/fmt.sol delete mode 100644 forge-fmt/testdata/TryStatement/original.sol delete mode 100644 forge-fmt/testdata/TypeDefinition/fmt.sol delete mode 100644 forge-fmt/testdata/TypeDefinition/original.sol delete mode 100644 forge-fmt/testdata/UnitExpression/fmt.sol delete mode 100644 forge-fmt/testdata/UnitExpression/original.sol delete mode 100644 forge-fmt/testdata/UsingDirective/fmt.sol delete mode 100644 forge-fmt/testdata/UsingDirective/original.sol delete mode 100644 forge-fmt/testdata/VariableAssignment/bracket-spacing.fmt.sol delete mode 100644 forge-fmt/testdata/VariableAssignment/fmt.sol delete mode 100644 forge-fmt/testdata/VariableAssignment/original.sol delete mode 100644 forge-fmt/testdata/VariableDefinition/fmt.sol delete mode 100644 forge-fmt/testdata/VariableDefinition/original.sol delete mode 100644 forge-fmt/testdata/VariableDefinition/override-spacing.fmt.sol delete mode 100644 forge-fmt/testdata/WhileStatement/block-multi.fmt.sol delete mode 100644 forge-fmt/testdata/WhileStatement/block-single.fmt.sol delete mode 100644 forge-fmt/testdata/WhileStatement/fmt.sol delete mode 100644 forge-fmt/testdata/WhileStatement/original.sol delete mode 100644 forge-fmt/testdata/Yul/fmt.sol delete mode 100644 forge-fmt/testdata/Yul/original.sol delete mode 100644 forge-fmt/testdata/YulStrings/fmt.sol delete mode 100644 forge-fmt/testdata/YulStrings/original.sol delete mode 100644 forge-fmt/testdata/YulStrings/preserve-quote.fmt.sol delete mode 100644 forge-fmt/testdata/YulStrings/single-quote.fmt.sol delete mode 100644 forge-fmt/tests/formatter.rs diff --git a/Cargo.toml b/Cargo.toml index f8ed67362..2ef9cbf9f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -66,7 +66,7 @@ contract-build = { version = "3.0.1", optional = true } primitive-types = { version = "0.12", features = ["codec"] } normalize-path = "0.2.1" bitflags = "2.3.3" -forge-fmt = {path = "forge-fmt"} +forge-fmt = "0.2.0" [dev-dependencies] num-derive = "0.4" @@ -99,4 +99,4 @@ llvm = ["inkwell", "libc"] wasm_opt = ["llvm", "wasm-opt", "contract-build"] [workspace] -members = ["solang-parser", "tests/wasm_host_attr", "forge-fmt"] +members = ["solang-parser", "tests/wasm_host_attr"] diff --git a/forge-fmt/Cargo.toml b/forge-fmt/Cargo.toml deleted file mode 100644 index 6579f9c1d..000000000 --- a/forge-fmt/Cargo.toml +++ /dev/null @@ -1,24 +0,0 @@ -[package] -name = "forge-fmt" -version = "0.2.0" -edition = "2021" -rust-version = "1.70" -authors = ["Foundry Contributors"] -license = "MIT OR Apache-2.0" -homepage = "https://github.com/foundry-rs/foundry" -repository = "https://github.com/foundry-rs/foundry" - -[dependencies] -ethers-core = "2.0.10" -solang-parser = { path = "../solang-parser" } -itertools = "0.11" -thiserror = "1" -ariadne = "0.2" -tracing = "0.1" -serde = { version = "1", features = ["derive"] } - -[dev-dependencies] -pretty_assertions = "1" -itertools = "0.11" -toml = "0.7" -tracing-subscriber = { version = "0.3", features = ["env-filter"] } diff --git a/forge-fmt/README.md b/forge-fmt/README.md deleted file mode 100644 index 7e51bf7f8..000000000 --- a/forge-fmt/README.md +++ /dev/null @@ -1,193 +0,0 @@ -# Formatter (`fmt`) - -Solidity formatter that respects (some parts of) the [Style Guide](https://docs.soliditylang.org/en/latest/style-guide.html) and -is tested on the [Prettier Solidity Plugin](https://github.com/prettier-solidity/prettier-plugin-solidity) cases. - -## Architecture - -The formatter works in two steps: - -1. Parse Solidity source code with [solang](https://github.com/hyperledger-labs/solang) into the PT (Parse Tree) - (not the same as Abstract Syntax Tree, [see difference](https://stackoverflow.com/a/9864571)). -2. Walk the PT and output new source code that's compliant with provided config and rule set. - -The technique for walking the tree is based on [Visitor Pattern](https://en.wikipedia.org/wiki/Visitor_pattern) -and works as following: - -1. Implement `Formatter` callback functions for each PT node type. - Every callback function should write formatted output for the current node - and call `Visitable::visit` function for child nodes delegating the output writing. -1. Implement `Visitable` trait and its `visit` function for each PT node type. Every `visit` function should call corresponding `Formatter`'s callback function. - -### Output - -The formatted output is written into the output buffer in _chunks_. The `Chunk` struct holds the content to be written & metadata for it. This includes the comments surrounding the content as well as the `needs_space` flag specifying whether this _chunk_ needs a space. The flag overrides the default behavior of `Formatter::next_char_needs_space` method. - -The content gets written into the `FormatBuffer` which contains the information about the current indentation level, indentation length, current state as well as the other data determining the rules for writing the content. `FormatBuffer` implements the `std::fmt::Write` trait where it evaluates the current information and decides how the content should be written to the destination. - -### Comments - -The solang parser does not output comments as a type of parse tree node, but rather -in a list alongside the parse tree with location information. It is therefore necessary -to infer where to insert the comments and how to format them while traversing the parse tree. - -To handle this, the formatter pre-parses the comments and puts them into two categories: -Prefix and Postfix comments. Prefix comments refer to the node directly after them, and -postfix comments refer to the node before them. As an illustration: - -```solidity -// This is a prefix comment -/* This is also a prefix comment */ -uint variable = 1 + 2; /* this is postfix */ // this is postfix too - // and this is a postfix comment on the next line -``` - -To insert the comments into the appropriate areas, strings get converted to chunks -before being written to the buffer. A chunk is any string that cannot be split by -whitespace. A chunk also carries with it the surrounding comment information. Thereby -when writing the chunk the comments can be added before and after the chunk as well -as any any whitespace surrounding. - -To construct a chunk, the string and the location of the string is given to the -Formatter and the pre-parsed comments before the start and end of the string are -associated with that string. The source code can then further be chunked before the -chunks are written to the buffer. - -To write the chunk, first the comments associated with the start of the chunk get -written to the buffer. Then the Formatter checks if any whitespace is needed between -what's been written to the buffer and what's in the chunk and inserts it where appropriate. -If the chunk content fits on the same line, it will be written directly to the buffer, -otherwise it will be written on the next line. Finally, any associated postfix -comments also get written. - -### Example - -Source code - -```solidity -pragma solidity ^0.8.10 ; -contract HelloWorld { - string public message; - constructor( string memory initMessage) { message = initMessage;} -} - - -event Greet( string indexed name) ; -``` - -Parse Tree (simplified) - -```text -SourceUnit - | PragmaDirective("solidity", "^0.8.10") - | ContractDefinition("HelloWorld") - | VariableDefinition("string", "message", null, ["public"]) - | FunctionDefinition("constructor") - | Parameter("string", "initMessage", ["memory"]) - | EventDefinition("string", "Greet", ["indexed"], ["name"]) -``` - -Formatted source code that was reconstructed from the Parse Tree - -```solidity -pragma solidity ^0.8.10; - -contract HelloWorld { - string public message; - - constructor(string memory initMessage) { - message = initMessage; - } -} - -event Greet(string indexed name); -``` - -### Configuration - -The formatter supports multiple configuration options defined in `FormatterConfig`. - -| Option | Default | Description | -| -------------------------------- | -------- | ---------------------------------------------------------------------------------------------- | -| line_length | 120 | Maximum line length where formatter will try to wrap the line | -| tab_width | 4 | Number of spaces per indentation level | -| bracket_spacing | false | Print spaces between brackets | -| int_types | long | Style of uint/int256 types. Available options: `long`, `short`, `preserve` | -| func_attrs_with_params_multiline | true | If function parameters are multiline then always put the function attributes on separate lines | -| quote_style | double | Style of quotation marks. Available options: `double`, `single`, `preserve` | -| number_underscore | preserve | Style of underscores in number literals. Available options: `remove`, `thousands`, `preserve` | - -TODO: update ^ - -### Disable Line - -The formatter can be disabled on specific lines by adding a comment `// forgefmt: disable-next-line`, like this: - -```solidity -// forgefmt: disable-next-line -uint x = 100; -``` - -Alternatively, the comment can also be placed at the end of the line. In this case, you'd have to use `disable-line` instead: - -```solidity -uint x = 100; // forgefmt: disable-line -``` - -### Testing - -Tests reside under the `fmt/testdata` folder and specify the malformatted & expected Solidity code. The source code file is named `original.sol` and expected file(s) are named in a format `({prefix}.)?fmt.sol`. Multiple expected files are needed for tests covering available configuration options. - -The default configuration values can be overridden from within the expected file by adding a comment in the format `// config: {config_entry} = {config_value}`. For example: - -```solidity -// config: line_length = 160 -``` - -The `test_directory` macro is used to specify a new folder with source files for the test suite. Each test suite has the following process: - -1. Preparse comments with config values -2. Parse and compare the AST for source & expected files. - - The `AstEq` trait defines the comparison rules for the AST nodes -3. Format the source file and assert the equality of the output with the expected file. -4. Format the expected files and assert the idempotency of the formatting operation. - -## Contributing - -Check out the [foundry contribution guide](https://github.com/foundry-rs/foundry/blob/master/CONTRIBUTING.md). - -Guidelines for contributing to `forge fmt`: - -### Opening an issue - -1. Create a short concise title describing an issue. - - Bad Title Examples - ```text - Forge fmt does not work - Forge fmt breaks - Forge fmt unexpected behavior - ``` - - Good Title Examples - ```text - Forge fmt postfix comment misplaced - Forge fmt does not inline short yul blocks - ``` -2. Fill in the issue template fields that include foundry version, platform & component info. -3. Provide the code snippets showing the current & expected behaviors. -4. If it's a feature request, specify why this feature is needed. -5. Besides the default label (`T-Bug` for bugs or `T-feature` for features), add `C-forge` and `Cmd-forge-fmt` labels. - -### Fixing A Bug - -1. Specify an issue that is being addressed in the PR description. -2. Add a note on the solution in the PR description. -3. Make sure the PR includes the acceptance test(s). - -### Developing A Feature - -1. Specify an issue that is being addressed in the PR description. -2. Add a note on the solution in the PR description. -3. Provide the test coverage for the new feature. These should include: - - Adding malformatted & expected solidity code under `fmt/testdata/$dir/` - - Testing the behavior of pre and postfix comments - - If it's a new config value, tests covering **all** available options diff --git a/forge-fmt/src/buffer.rs b/forge-fmt/src/buffer.rs deleted file mode 100644 index 60aac6c30..000000000 --- a/forge-fmt/src/buffer.rs +++ /dev/null @@ -1,449 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -//! Format buffer - -use std::fmt::Write; - -use crate::{ - comments::{CommentState, CommentStringExt}, - string::{QuoteState, QuotedStringExt}, -}; - -/// An indent group. The group may optionally skip the first line -#[derive(Default, Clone, Debug)] -struct IndentGroup { - skip_line: bool, -} - -#[derive(Clone, Copy, Debug)] -enum WriteState { - LineStart(CommentState), - WriteTokens(CommentState), - WriteString(char), -} - -impl WriteState { - fn comment_state(&self) -> CommentState { - match self { - WriteState::LineStart(state) => *state, - WriteState::WriteTokens(state) => *state, - WriteState::WriteString(_) => CommentState::None, - } - } -} - -impl Default for WriteState { - fn default() -> Self { - WriteState::LineStart(CommentState::default()) - } -} - -/// A wrapper around a `std::fmt::Write` interface. The wrapper keeps track of indentation as well -/// as information about the last `write_str` command if available. The formatter may also be -/// restricted to a single line, in which case it will throw an error on a newline -#[derive(Clone, Debug)] -pub struct FormatBuffer { - pub w: W, - indents: Vec, - base_indent_len: usize, - tab_width: usize, - last_char: Option, - current_line_len: usize, - restrict_to_single_line: bool, - state: WriteState, -} - -impl FormatBuffer { - pub fn new(w: W, tab_width: usize) -> Self { - Self { - w, - tab_width, - base_indent_len: 0, - indents: vec![], - current_line_len: 0, - last_char: None, - restrict_to_single_line: false, - state: WriteState::default(), - } - } - - /// Create a new temporary buffer based on an existing buffer which retains information about - /// the buffer state, but has a blank String as its underlying `Write` interface - pub fn create_temp_buf(&self) -> FormatBuffer { - let mut new = FormatBuffer::new(String::new(), self.tab_width); - new.base_indent_len = self.total_indent_len(); - new.current_line_len = self.current_line_len(); - new.last_char = self.last_char; - new.restrict_to_single_line = self.restrict_to_single_line; - new.state = match self.state { - WriteState::WriteTokens(state) | WriteState::LineStart(state) => { - WriteState::LineStart(state) - } - WriteState::WriteString(ch) => WriteState::WriteString(ch), - }; - new - } - - /// Restrict the buffer to a single line - pub fn restrict_to_single_line(&mut self, restricted: bool) { - self.restrict_to_single_line = restricted; - } - - /// Indent the buffer by delta - pub fn indent(&mut self, delta: usize) { - self.indents - .extend(std::iter::repeat(IndentGroup::default()).take(delta)); - } - - /// Dedent the buffer by delta - pub fn dedent(&mut self, delta: usize) { - self.indents.truncate(self.indents.len() - delta); - } - - /// Get the current level of the indent. This is multiplied by the tab width to get the - /// resulting indent - fn level(&self) -> usize { - self.indents.iter().filter(|i| !i.skip_line).count() - } - - /// Check if the last indent group is being skipped - pub fn last_indent_group_skipped(&self) -> bool { - self.indents.last().map(|i| i.skip_line).unwrap_or(false) - } - - /// Set whether the last indent group should be skipped - pub fn set_last_indent_group_skipped(&mut self, skip_line: bool) { - if let Some(i) = self.indents.last_mut() { - i.skip_line = skip_line - } - } - - /// Get the current indent size (level * tab_width) - pub fn current_indent_len(&self) -> usize { - self.level() * self.tab_width - } - - /// Get the total indent size - pub fn total_indent_len(&self) -> usize { - self.current_indent_len() + self.base_indent_len - } - - /// Get the current written position (this does not include the indent size) - pub fn current_line_len(&self) -> usize { - self.current_line_len - } - - /// Check if the buffer is at the beginning of a new line - pub fn is_beginning_of_line(&self) -> bool { - matches!(self.state, WriteState::LineStart(_)) - } - - /// Start a new indent group (skips first indent) - pub fn start_group(&mut self) { - self.indents.push(IndentGroup { skip_line: true }); - } - - /// End the last indent group - pub fn end_group(&mut self) { - self.indents.pop(); - } - - /// Get the last char written to the buffer - pub fn last_char(&self) -> Option { - self.last_char - } - - /// When writing a newline apply state changes - fn handle_newline(&mut self, mut comment_state: CommentState) { - if comment_state == CommentState::Line { - comment_state = CommentState::None; - } - self.current_line_len = 0; - self.set_last_indent_group_skipped(false); - self.last_char = Some('\n'); - self.state = WriteState::LineStart(comment_state); - } -} - -impl FormatBuffer { - /// Write a raw string to the buffer. This will ignore indents and remove the indents of the - /// written string to match the current base indent of this buffer if it is a temp buffer - pub fn write_raw(&mut self, s: impl AsRef) -> std::fmt::Result { - let mut lines = s.as_ref().lines().peekable(); - let mut comment_state = self.state.comment_state(); - while let Some(line) = lines.next() { - // remove the whitespace that covered by the base indent length (this is normally the - // case with temporary buffers as this will be readded by the underlying IndentWriter - // later on - let (new_comment_state, line_start) = line - .comment_state_char_indices() - .with_state(comment_state) - .take(self.base_indent_len) - .take_while(|(_, _, ch)| ch.is_whitespace()) - .last() - .map(|(state, idx, _)| (state, idx + 1)) - .unwrap_or((comment_state, 0)); - comment_state = new_comment_state; - let trimmed_line = &line[line_start..]; - if !trimmed_line.is_empty() { - self.w.write_str(trimmed_line)?; - self.current_line_len += trimmed_line.len(); - self.last_char = trimmed_line.chars().next_back(); - self.state = WriteState::WriteTokens(comment_state); - } - if lines.peek().is_some() || s.as_ref().ends_with('\n') { - if self.restrict_to_single_line { - return Err(std::fmt::Error); - } - self.w.write_char('\n')?; - self.handle_newline(comment_state); - } - } - Ok(()) - } -} - -impl Write for FormatBuffer { - fn write_str(&mut self, mut s: &str) -> std::fmt::Result { - if s.is_empty() { - return Ok(()); - } - - let mut indent = " ".repeat(self.current_indent_len()); - - loop { - match self.state { - WriteState::LineStart(mut comment_state) => { - match s.find(|b| b != '\n') { - // No non-empty lines in input, write the entire string (only newlines) - None => { - if !s.is_empty() { - self.w.write_str(s)?; - self.handle_newline(comment_state); - } - break; - } - - // We can see the next non-empty line. Write up to the - // beginning of that line, then insert an indent, then - // continue. - Some(len) => { - let (head, tail) = s.split_at(len); - self.w.write_str(head)?; - self.w.write_str(&indent)?; - self.current_line_len = 0; - self.last_char = Some(' '); - // a newline has been inserted - if len > 0 { - if self.last_indent_group_skipped() { - indent = " ".repeat(self.current_indent_len() + self.tab_width); - self.set_last_indent_group_skipped(false); - } - if comment_state == CommentState::Line { - comment_state = CommentState::None; - } - } - s = tail; - self.state = WriteState::WriteTokens(comment_state); - } - } - } - WriteState::WriteTokens(comment_state) => { - if s.is_empty() { - break; - } - - // find the next newline or non-comment string separator (e.g. ' or ") - let mut len = 0; - let mut new_state = WriteState::WriteTokens(comment_state); - for (state, idx, ch) in s.comment_state_char_indices().with_state(comment_state) - { - len = idx; - if ch == '\n' { - if self.restrict_to_single_line { - return Err(std::fmt::Error); - } - new_state = WriteState::LineStart(state); - break; - } else if state == CommentState::None && (ch == '\'' || ch == '"') { - new_state = WriteState::WriteString(ch); - break; - } else { - new_state = WriteState::WriteTokens(state); - } - } - - if matches!(new_state, WriteState::WriteTokens(_)) { - // No newlines or strings found, write the entire string - self.w.write_str(s)?; - self.current_line_len += s.len(); - self.last_char = s.chars().next_back(); - self.state = new_state; - break; - } else { - // A newline or string has been found. Write up to that character and - // continue on the tail - let (head, tail) = s.split_at(len + 1); - self.w.write_str(head)?; - s = tail; - match new_state { - WriteState::LineStart(comment_state) => { - self.handle_newline(comment_state) - } - new_state => { - self.current_line_len += head.len(); - self.last_char = head.chars().next_back(); - self.state = new_state; - } - } - } - } - WriteState::WriteString(quote) => { - match s - .quoted_ranges() - .with_state(QuoteState::String(quote)) - .next() - { - // No end found, write the rest of the string - None => { - self.w.write_str(s)?; - self.current_line_len += s.len(); - self.last_char = s.chars().next_back(); - break; - } - // String end found, write the string and continue to add tokens after - Some((_, _, len)) => { - let (head, tail) = s.split_at(len + 1); - self.w.write_str(head)?; - if let Some((_, last)) = head.rsplit_once('\n') { - self.set_last_indent_group_skipped(false); - self.current_line_len = last.len(); - } else { - self.current_line_len += head.len(); - } - self.last_char = Some(quote); - s = tail; - self.state = WriteState::WriteTokens(CommentState::None); - } - } - } - } - } - - Ok(()) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - const TAB_WIDTH: usize = 4; - - #[test] - fn test_buffer_indents() -> std::fmt::Result { - let delta = 1; - - let mut buf = FormatBuffer::new(String::new(), TAB_WIDTH); - assert_eq!(buf.indents.len(), 0); - assert_eq!(buf.level(), 0); - assert_eq!(buf.current_indent_len(), 0); - - buf.indent(delta); - assert_eq!(buf.indents.len(), delta); - assert_eq!(buf.level(), delta); - assert_eq!(buf.current_indent_len(), delta * TAB_WIDTH); - - buf.indent(delta); - buf.set_last_indent_group_skipped(true); - assert!(buf.last_indent_group_skipped()); - assert_eq!(buf.indents.len(), delta * 2); - assert_eq!(buf.level(), delta); - assert_eq!(buf.current_indent_len(), delta * TAB_WIDTH); - buf.dedent(delta); - - buf.dedent(delta); - assert_eq!(buf.indents.len(), 0); - assert_eq!(buf.level(), 0); - assert_eq!(buf.current_indent_len(), 0); - - // panics on extra dedent - let res = std::panic::catch_unwind(|| buf.clone().dedent(delta)); - assert!(res.is_err()); - - Ok(()) - } - - #[test] - fn test_identical_temp_buf() -> std::fmt::Result { - let content = "test string"; - let multiline_content = "test\nmultiline\nmultiple"; - let mut buf = FormatBuffer::new(String::new(), TAB_WIDTH); - - // create identical temp buf - let mut temp = buf.create_temp_buf(); - writeln!(buf, "{content}")?; - writeln!(temp, "{content}")?; - assert_eq!(buf.w, format!("{content}\n")); - assert_eq!(temp.w, buf.w); - assert_eq!(temp.current_line_len, buf.current_line_len); - assert_eq!(temp.base_indent_len, buf.total_indent_len()); - - let delta = 1; - buf.indent(delta); - - let mut temp_indented = buf.create_temp_buf(); - assert!(temp_indented.w.is_empty()); - assert_eq!(temp_indented.base_indent_len, buf.total_indent_len()); - assert_eq!(temp_indented.level() + delta, buf.level()); - - let indent = " ".repeat(delta * TAB_WIDTH); - - let mut original_buf = buf.clone(); - write!(buf, "{multiline_content}")?; - let expected_content = format!( - "{}\n{}{}", - content, - indent, - multiline_content - .lines() - .collect::>() - .join(&format!("\n{indent}")) - ); - assert_eq!(buf.w, expected_content); - - write!(temp_indented, "{multiline_content}")?; - - // write temp buf to original and assert the result - write!(original_buf, "{}", temp_indented.w)?; - assert_eq!(buf.w, original_buf.w); - - Ok(()) - } - - #[test] - fn test_preserves_original_content_with_default_settings() -> std::fmt::Result { - let contents = [ - "simple line", - r#" - some - multiline - content"#, - "// comment", - "/* comment */", - r#"mutliline - content - // comment1 - with comments - /* comment2 */ "#, - ]; - - for content in contents.iter() { - let mut buf = FormatBuffer::new(String::new(), TAB_WIDTH); - write!(buf, "{content}")?; - assert_eq!(&buf.w, content); - } - - Ok(()) - } -} diff --git a/forge-fmt/src/chunk.rs b/forge-fmt/src/chunk.rs deleted file mode 100644 index 9568e31f1..000000000 --- a/forge-fmt/src/chunk.rs +++ /dev/null @@ -1,73 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -use crate::comments::CommentWithMetadata; - -/// Holds information about a non-whitespace-splittable string, and the surrounding comments -#[derive(Clone, Debug, Default)] -pub struct Chunk { - pub postfixes_before: Vec, - pub prefixes: Vec, - pub content: String, - pub postfixes: Vec, - pub needs_space: Option, -} - -impl From for Chunk { - fn from(string: String) -> Self { - Chunk { - content: string, - ..Default::default() - } - } -} - -impl From<&str> for Chunk { - fn from(string: &str) -> Self { - Chunk { - content: string.to_owned(), - ..Default::default() - } - } -} - -// The struct with information about chunks used in the [Formatter::surrounded] method -#[derive(Debug)] -pub struct SurroundingChunk { - pub before: Option, - pub next: Option, - pub spaced: Option, - pub content: String, -} - -impl SurroundingChunk { - pub fn new( - content: impl std::fmt::Display, - before: Option, - next: Option, - ) -> Self { - SurroundingChunk { - before, - next, - content: format!("{content}"), - spaced: None, - } - } - - pub fn spaced(mut self) -> Self { - self.spaced = Some(true); - self - } - - pub fn non_spaced(mut self) -> Self { - self.spaced = Some(false); - self - } - - pub fn loc_before(&self) -> usize { - self.before.unwrap_or_default() - } - - pub fn loc_next(&self) -> Option { - self.next - } -} diff --git a/forge-fmt/src/comments.rs b/forge-fmt/src/comments.rs deleted file mode 100644 index 61ccbde71..000000000 --- a/forge-fmt/src/comments.rs +++ /dev/null @@ -1,481 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -use crate::inline_config::{InlineConfigItem, InvalidInlineConfigItem}; -use itertools::Itertools; -use solang_parser::pt::*; -use std::collections::VecDeque; - -/// The type of a Comment -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum CommentType { - /// A Line comment (e.g. `// ...`) - Line, - /// A Block comment (e.g. `/* ... */`) - Block, - /// A Doc Line comment (e.g. `/// ...`) - DocLine, - /// A Doc Block comment (e.g. `/** ... */`) - DocBlock, -} - -/// The comment position -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum CommentPosition { - /// Comes before the code it describes - Prefix, - /// Comes after the code it describes - Postfix, -} - -/// Comment with additional metadata -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct CommentWithMetadata { - pub ty: CommentType, - pub loc: Loc, - pub has_newline_before: bool, - pub indent_len: usize, - pub comment: String, - pub position: CommentPosition, -} - -impl PartialOrd for CommentWithMetadata { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl Ord for CommentWithMetadata { - fn cmp(&self, other: &Self) -> std::cmp::Ordering { - self.loc.cmp(&other.loc) - } -} - -impl CommentWithMetadata { - fn new( - comment: Comment, - position: CommentPosition, - has_newline_before: bool, - indent_len: usize, - ) -> Self { - let (ty, loc, comment) = match comment { - Comment::Line(loc, comment) => (CommentType::Line, loc, comment), - Comment::Block(loc, comment) => (CommentType::Block, loc, comment), - Comment::DocLine(loc, comment) => (CommentType::DocLine, loc, comment), - Comment::DocBlock(loc, comment) => (CommentType::DocBlock, loc, comment), - }; - Self { - comment: comment.trim_end().to_string(), - ty, - loc, - position, - has_newline_before, - indent_len, - } - } - - /// Construct a comment with metadata by analyzing its surrounding source code - fn from_comment_and_src( - comment: Comment, - src: &str, - last_comment: Option<&CommentWithMetadata>, - ) -> Self { - let src_before = &src[..comment.loc().start()]; - if src_before.is_empty() { - return Self::new(comment, CommentPosition::Prefix, false, 0); - } - - let mut lines_before = src_before.lines().rev(); - let this_line = if src_before.ends_with('\n') { - "" - } else { - lines_before.next().unwrap_or_default() - }; - let indent_len = this_line.chars().take_while(|c| c.is_whitespace()).count(); - let last_line = lines_before.next().map(str::trim_start); - - if matches!(comment, Comment::DocLine(..) | Comment::DocBlock(..)) { - return Self::new( - comment, - CommentPosition::Prefix, - last_line.map_or(true, str::is_empty), - indent_len, - ); - } - - // TODO: this loop takes almost the entirety of the time spent in parsing, which is up to - // 80% of `crate::fmt` - let mut code_end = 0; - for (state, idx, ch) in src_before.comment_state_char_indices() { - if matches!(state, CommentState::None) && !ch.is_whitespace() { - code_end = idx; - } - } - - let (position, has_newline_before) = if src_before[code_end..].contains('\n') { - // comment sits on a line without code - if let Some(last_line) = last_line { - if last_line.is_empty() { - // line before is empty - (CommentPosition::Prefix, true) - } else { - // line has something - // check if the last comment after code was a postfix comment - if last_comment - .map_or(false, |last| last.loc.end() > code_end && !last.is_prefix()) - { - // get the indent size of the next item of code - let next_indent_len = src[comment.loc().end()..] - .non_comment_chars() - .take_while(|ch| ch.is_whitespace()) - .fold( - indent_len, - |indent, ch| if ch == '\n' { 0 } else { indent + 1 }, - ); - if indent_len > next_indent_len { - // the comment indent is bigger than the next code indent - (CommentPosition::Postfix, false) - } else { - // the comment indent is equal to or less than the next code - // indent - (CommentPosition::Prefix, false) - } - } else { - // if there is no postfix comment after the piece of code - (CommentPosition::Prefix, false) - } - } - } else { - // beginning of file - (CommentPosition::Prefix, false) - } - } else { - // comment is after some code - (CommentPosition::Postfix, false) - }; - - Self::new(comment, position, has_newline_before, indent_len) - } - - pub fn is_line(&self) -> bool { - matches!(self.ty, CommentType::Line | CommentType::DocLine) - } - - pub fn is_prefix(&self) -> bool { - matches!(self.position, CommentPosition::Prefix) - } - - pub fn is_before(&self, byte: usize) -> bool { - self.loc.start() < byte - } - - /// Returns the contents of the comment without the start and end tokens - pub fn contents(&self) -> &str { - let mut s = self.comment.as_str(); - if let Some(stripped) = s.strip_prefix(self.start_token()) { - s = stripped; - } - if let Some(end_token) = self.end_token() { - if let Some(stripped) = s.strip_suffix(end_token) { - s = stripped; - } - } - s - } - - /// The start token of the comment - #[inline] - pub const fn start_token(&self) -> &'static str { - match self.ty { - CommentType::Line => "//", - CommentType::Block => "/*", - CommentType::DocLine => "///", - CommentType::DocBlock => "/**", - } - } - - /// The token that gets written on the newline when the - /// comment is wrapped - #[inline] - pub const fn wrap_token(&self) -> &'static str { - match self.ty { - CommentType::Line => "// ", - CommentType::DocLine => "/// ", - CommentType::Block => "", - CommentType::DocBlock => " * ", - } - } - - /// The end token of the comment - #[inline] - pub const fn end_token(&self) -> Option<&'static str> { - match self.ty { - CommentType::Line | CommentType::DocLine => None, - CommentType::Block | CommentType::DocBlock => Some("*/"), - } - } -} - -/// A list of comments -#[derive(Default, Debug, Clone)] -pub struct Comments { - prefixes: VecDeque, - postfixes: VecDeque, -} - -impl Comments { - pub fn new(mut comments: Vec, src: &str) -> Self { - let mut prefixes = VecDeque::with_capacity(comments.len()); - let mut postfixes = VecDeque::with_capacity(comments.len()); - let mut last_comment = None; - - comments.sort_by_key(|comment| comment.loc()); - for comment in comments { - let comment = CommentWithMetadata::from_comment_and_src(comment, src, last_comment); - let vec = if comment.is_prefix() { - &mut prefixes - } else { - &mut postfixes - }; - vec.push_back(comment); - last_comment = Some(vec.back().unwrap()); - } - Self { - prefixes, - postfixes, - } - } - - /// Heloer for removing comments before a byte offset - fn remove_comments_before( - comments: &mut VecDeque, - byte: usize, - ) -> Vec { - let pos = comments - .iter() - .find_position(|comment| !comment.is_before(byte)) - .map(|(idx, _)| idx) - .unwrap_or_else(|| comments.len()); - if pos == 0 { - return Vec::new(); - } - comments.rotate_left(pos); - comments.split_off(comments.len() - pos).into() - } - - /// Remove any prefix comments that occur before the byte offset in the src - pub(crate) fn remove_prefixes_before(&mut self, byte: usize) -> Vec { - Self::remove_comments_before(&mut self.prefixes, byte) - } - - /// Remove any postfix comments that occur before the byte offset in the src - pub(crate) fn remove_postfixes_before(&mut self, byte: usize) -> Vec { - Self::remove_comments_before(&mut self.postfixes, byte) - } - - /// Remove any comments that occur before the byte offset in the src - pub(crate) fn remove_all_comments_before(&mut self, byte: usize) -> Vec { - self.remove_prefixes_before(byte) - .into_iter() - .merge(self.remove_postfixes_before(byte)) - .collect() - } - - pub(crate) fn pop(&mut self) -> Option { - if self.iter().next()?.is_prefix() { - self.prefixes.pop_front() - } else { - self.postfixes.pop_front() - } - } - - pub(crate) fn iter(&self) -> impl Iterator { - self.prefixes.iter().merge(self.postfixes.iter()) - } - - /// Parse all comments to return a list of inline config items. This will return an iterator of - /// results of parsing comments which start with `forgefmt:` - pub fn parse_inline_config_items( - &self, - ) -> impl Iterator> + '_ - { - self.iter() - .filter_map(|comment| { - Some(( - comment, - comment - .contents() - .trim_start() - .strip_prefix("forgefmt:")? - .trim(), - )) - }) - .map(|(comment, item)| { - let loc = comment.loc; - item.parse().map(|out| (loc, out)).map_err(|out| (loc, out)) - }) - } -} - -/// The state of a character in a string with possible comments -#[derive(Copy, Clone, Debug, Eq, PartialEq, Default)] -pub enum CommentState { - /// character not in a comment - #[default] - None, - /// First `/` in line comment start `"//"` - LineStart1, - /// Second `/` in line comment start `"//"` - LineStart2, - /// Character in a line comment - Line, - /// `/` in block comment start `"/*"` - BlockStart1, - /// `*` in block comment start `"/*"` - BlockStart2, - /// Character in a block comment - Block, - /// `*` in block comment end `"*/"` - BlockEnd1, - /// `/` in block comment end `"*/"` - BlockEnd2, -} - -/// An Iterator over characters and indices in a string slice with information about the state of -/// comments -pub struct CommentStateCharIndices<'a> { - iter: std::str::CharIndices<'a>, - state: CommentState, -} - -impl<'a> CommentStateCharIndices<'a> { - #[inline] - fn new(string: &'a str) -> Self { - Self { - iter: string.char_indices(), - state: CommentState::None, - } - } - - #[inline] - pub fn with_state(mut self, state: CommentState) -> Self { - self.state = state; - self - } - - #[inline] - pub fn peek(&mut self) -> Option<(usize, char)> { - self.iter.clone().next() - } -} - -impl Iterator for CommentStateCharIndices<'_> { - type Item = (CommentState, usize, char); - - #[inline] - fn next(&mut self) -> Option { - let (idx, ch) = self.iter.next()?; - match self.state { - CommentState::None => { - if ch == '/' { - self.state = match self.peek() { - Some((_, '/')) => CommentState::LineStart1, - Some((_, '*')) => CommentState::BlockStart1, - _ => CommentState::None, - }; - } - } - CommentState::LineStart1 => { - self.state = CommentState::LineStart2; - } - CommentState::LineStart2 => { - self.state = CommentState::Line; - } - CommentState::Line => { - if ch == '\n' { - self.state = CommentState::None; - } - } - CommentState::BlockStart1 => { - self.state = CommentState::BlockStart2; - } - CommentState::BlockStart2 => { - self.state = CommentState::Block; - } - CommentState::Block => { - if ch == '*' { - if let Some((_, '/')) = self.peek() { - self.state = CommentState::BlockEnd1; - } - } - } - CommentState::BlockEnd1 => { - self.state = CommentState::BlockEnd2; - } - CommentState::BlockEnd2 => { - self.state = CommentState::None; - } - } - Some((self.state, idx, ch)) - } - - #[inline] - fn count(self) -> usize { - self.iter.count() - } - - #[inline] - fn size_hint(&self) -> (usize, Option) { - self.iter.size_hint() - } -} - -impl std::iter::FusedIterator for CommentStateCharIndices<'_> {} - -/// An Iterator over characters in a string slice which are not a apart of comments -pub struct NonCommentChars<'a>(CommentStateCharIndices<'a>); - -impl<'a> Iterator for NonCommentChars<'a> { - type Item = char; - - #[inline] - fn next(&mut self) -> Option { - for (state, _, ch) in self.0.by_ref() { - if state == CommentState::None { - return Some(ch); - } - } - None - } -} - -/// Helpers for iterating over comment containing strings -pub trait CommentStringExt { - fn comment_state_char_indices(&self) -> CommentStateCharIndices; - - #[inline] - fn non_comment_chars(&self) -> NonCommentChars { - NonCommentChars(self.comment_state_char_indices()) - } - - #[inline] - fn trim_comments(&self) -> String { - self.non_comment_chars().collect() - } -} - -impl CommentStringExt for T -where - T: AsRef, -{ - #[inline] - fn comment_state_char_indices(&self) -> CommentStateCharIndices { - CommentStateCharIndices::new(self.as_ref()) - } -} - -impl CommentStringExt for str { - #[inline] - fn comment_state_char_indices(&self) -> CommentStateCharIndices { - CommentStateCharIndices::new(self) - } -} diff --git a/forge-fmt/src/config.rs b/forge-fmt/src/config.rs deleted file mode 100644 index a13d54147..000000000 --- a/forge-fmt/src/config.rs +++ /dev/null @@ -1,126 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -//! Configuration specific to the `forge fmt` command and the `forge_fmt` package - -use serde::{Deserialize, Serialize}; - -/// Contains the config and rule set -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct FormatterConfig { - /// Maximum line length where formatter will try to wrap the line - pub line_length: usize, - /// Number of spaces per indentation level - pub tab_width: usize, - /// Print spaces between brackets - pub bracket_spacing: bool, - /// Style of uint/int256 types - pub int_types: IntTypes, - /// Style of multiline function header in case it doesn't fit - pub multiline_func_header: MultilineFuncHeaderStyle, - /// Style of quotation marks - pub quote_style: QuoteStyle, - /// Style of underscores in number literals - pub number_underscore: NumberUnderscore, - /// Style of single line blocks in statements - pub single_line_statement_blocks: SingleLineBlockStyle, - /// Print space in state variable, function and modifier `override` attribute - pub override_spacing: bool, - /// Wrap comments on `line_length` reached - pub wrap_comments: bool, - /// Globs to ignore - pub ignore: Vec, - /// Add new line at start and end of contract declarations - pub contract_new_lines: bool, -} - -/// Style of uint/int256 types -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] -pub enum IntTypes { - /// Print the explicit uint256 or int256 - Long, - /// Print the implicit uint or int - Short, - /// Use the type defined in the source code - Preserve, -} - -/// Style of underscores in number literals -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] -pub enum NumberUnderscore { - /// Remove all underscores - Remove, - /// Add an underscore every thousand, if greater than 9999 - /// e.g. 1000 -> 1000 and 10000 -> 10_000 - Thousands, - /// Use the underscores defined in the source code - Preserve, -} - -/// Style of string quotes -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] -pub enum QuoteStyle { - /// Use double quotes where possible - Double, - /// Use single quotes where possible - Single, - /// Use quotation mark defined in the source code - Preserve, -} - -impl QuoteStyle { - /// Get associated quotation mark with option - pub fn quote(self) -> Option { - match self { - QuoteStyle::Double => Some('"'), - QuoteStyle::Single => Some('\''), - QuoteStyle::Preserve => None, - } - } -} - -/// Style of single line blocks in statements -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] -pub enum SingleLineBlockStyle { - /// Prefer single line block when possible - Single, - /// Always use multiline block - Multi, - /// Preserve the original style - Preserve, -} - -/// Style of function header in case it doesn't fit -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] -pub enum MultilineFuncHeaderStyle { - /// Write function parameters multiline first - ParamsFirst, - /// Write function attributes multiline first - AttributesFirst, - /// If function params or attrs are multiline - /// split the rest - All, -} - -impl Default for FormatterConfig { - fn default() -> Self { - FormatterConfig { - line_length: 120, - tab_width: 4, - bracket_spacing: false, - int_types: IntTypes::Long, - multiline_func_header: MultilineFuncHeaderStyle::AttributesFirst, - quote_style: QuoteStyle::Double, - number_underscore: NumberUnderscore::Preserve, - single_line_statement_blocks: SingleLineBlockStyle::Preserve, - override_spacing: false, - wrap_comments: false, - ignore: vec![], - contract_new_lines: false, - } - } -} diff --git a/forge-fmt/src/formatter.rs b/forge-fmt/src/formatter.rs deleted file mode 100644 index b738475cb..000000000 --- a/forge-fmt/src/formatter.rs +++ /dev/null @@ -1,3696 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -//! A Solidity formatter - -use crate::{ - buffer::*, - chunk::*, - comments::{ - CommentPosition, CommentState, CommentStringExt, CommentType, CommentWithMetadata, Comments, - }, - config::{MultilineFuncHeaderStyle, SingleLineBlockStyle}, - helpers::import_path_string, - macros::*, - solang_ext::{pt::*, *}, - string::{QuoteState, QuotedStringExt}, - visit::{Visitable, Visitor}, - FormatterConfig, InlineConfig, IntTypes, NumberUnderscore, -}; -use ethers_core::{types::H160, utils::to_checksum}; -use itertools::{Either, Itertools}; -use solang_parser::pt::ImportPath; -use std::{fmt::Write, str::FromStr}; -use thiserror::Error; - -type Result = std::result::Result; - -/// A custom Error thrown by the Formatter -#[derive(Error, Debug)] -pub enum FormatterError { - /// Error thrown by `std::fmt::Write` interfaces - #[error(transparent)] - Fmt(#[from] std::fmt::Error), - /// Encountered invalid parse tree item. - #[error("Encountered invalid parse tree item at {0:?}")] - InvalidParsedItem(Loc), - /// All other errors - #[error(transparent)] - Custom(Box), -} - -impl FormatterError { - fn fmt() -> Self { - Self::Fmt(std::fmt::Error) - } - fn custom(err: impl std::error::Error + 'static) -> Self { - Self::Custom(Box::new(err)) - } -} - -#[allow(unused_macros)] -macro_rules! format_err { - ($msg:literal $(,)?) => { - $crate::formatter::FormatterError::custom($msg.to_string()) - }; - ($err:expr $(,)?) => { - $crate::formatter::FormatterError::custom($err) - }; - ($fmt:expr, $($arg:tt)*) => { - $crate::formatter::FormatterError::custom(format!($fmt, $($arg)*)) - }; -} - -#[allow(unused_macros)] -macro_rules! bail { - ($msg:literal $(,)?) => { - return Err($crate::formatter::format_err!($msg)) - }; - ($err:expr $(,)?) => { - return Err($err) - }; - ($fmt:expr, $($arg:tt)*) => { - return Err($crate::formatter::format_err!($fmt, $(arg)*)) - }; -} - -// TODO: store context entities as references without copying -/// Current context of the Formatter (e.g. inside Contract or Function definition) -#[derive(Default, Debug)] -struct Context { - contract: Option, - function: Option, - if_stmt_single_line: Option, -} - -/// A Solidity formatter -#[derive(Debug)] -pub struct Formatter<'a, W> { - buf: FormatBuffer, - source: &'a str, - config: FormatterConfig, - temp_bufs: Vec>, - context: Context, - comments: Comments, - inline_config: InlineConfig, -} - -/// An action which may be committed to a Formatter -struct Transaction<'f, 'a, W> { - fmt: &'f mut Formatter<'a, W>, - buffer: String, - comments: Comments, -} - -impl<'f, 'a, W> std::ops::Deref for Transaction<'f, 'a, W> { - type Target = Formatter<'a, W>; - fn deref(&self) -> &Self::Target { - self.fmt - } -} - -impl<'f, 'a, W> std::ops::DerefMut for Transaction<'f, 'a, W> { - fn deref_mut(&mut self) -> &mut Self::Target { - self.fmt - } -} - -impl<'f, 'a, W: Write> Transaction<'f, 'a, W> { - /// Create a new transaction from a callback - fn new( - fmt: &'f mut Formatter<'a, W>, - fun: impl FnMut(&mut Formatter<'a, W>) -> Result<()>, - ) -> Result { - let mut comments = fmt.comments.clone(); - let buffer = fmt.with_temp_buf(fun)?.w; - comments = std::mem::replace(&mut fmt.comments, comments); - Ok(Self { - fmt, - buffer, - comments, - }) - } - - /// Commit the transaction to the Formatter - fn commit(self) -> Result { - self.fmt.comments = self.comments; - write_chunk!(self.fmt, "{}", self.buffer)?; - Ok(self.buffer) - } -} - -impl<'a, W: Write> Formatter<'a, W> { - pub fn new( - w: W, - source: &'a str, - comments: Comments, - inline_config: InlineConfig, - config: FormatterConfig, - ) -> Self { - Self { - buf: FormatBuffer::new(w, config.tab_width), - source, - config, - temp_bufs: Vec::new(), - context: Context::default(), - comments, - inline_config, - } - } - - /// Get the Write interface of the current temp buffer or the underlying Write - fn buf(&mut self) -> &mut dyn Write { - match &mut self.temp_bufs[..] { - [] => &mut self.buf as &mut dyn Write, - [.., buf] => buf as &mut dyn Write, - } - } - - /// Casts the current writer `w` as a `String` reference. Should only be used for debugging. - #[allow(dead_code)] - unsafe fn buf_contents(&self) -> &String { - *(&self.buf.w as *const W as *const &mut String) - } - - /// Casts the current `W` writer or the current temp buffer as a `String` reference. - /// Should only be used for debugging. - #[allow(dead_code)] - unsafe fn temp_buf_contents(&self) -> &String { - match &self.temp_bufs[..] { - [] => self.buf_contents(), - [.., buf] => &buf.w, - } - } - - buf_fn! { fn indent(&mut self, delta: usize) } - buf_fn! { fn dedent(&mut self, delta: usize) } - buf_fn! { fn start_group(&mut self) } - buf_fn! { fn end_group(&mut self) } - buf_fn! { fn create_temp_buf(&self) -> FormatBuffer } - buf_fn! { fn restrict_to_single_line(&mut self, restricted: bool) } - buf_fn! { fn current_line_len(&self) -> usize } - buf_fn! { fn total_indent_len(&self) -> usize } - buf_fn! { fn is_beginning_of_line(&self) -> bool } - buf_fn! { fn last_char(&self) -> Option } - buf_fn! { fn last_indent_group_skipped(&self) -> bool } - buf_fn! { fn set_last_indent_group_skipped(&mut self, skip: bool) } - buf_fn! { fn write_raw(&mut self, s: impl AsRef) -> std::fmt::Result } - - /// Do the callback within the context of a temp buffer - fn with_temp_buf( - &mut self, - mut fun: impl FnMut(&mut Self) -> Result<()>, - ) -> Result> { - self.temp_bufs.push(self.create_temp_buf()); - let res = fun(self); - let out = self.temp_bufs.pop().unwrap(); - res?; - Ok(out) - } - - /// Does the next written character require whitespace before - fn next_char_needs_space(&self, next_char: char) -> bool { - if self.is_beginning_of_line() { - return false; - } - let last_char = if let Some(last_char) = self.last_char() { - last_char - } else { - return false; - }; - if last_char.is_whitespace() || next_char.is_whitespace() { - return false; - } - match last_char { - '{' => match next_char { - '{' | '[' | '(' => false, - '/' => true, - _ => self.config.bracket_spacing, - }, - '(' | '.' | '[' => matches!(next_char, '/'), - '/' => true, - _ => match next_char { - '}' => self.config.bracket_spacing, - ')' | ',' | '.' | ';' | ']' => false, - _ => true, - }, - } - } - - /// Is length of the `text` with respect to already written line <= `config.line_length` - fn will_it_fit(&self, text: impl AsRef) -> bool { - let text = text.as_ref(); - if text.is_empty() { - return true; - } - if text.contains('\n') { - return false; - } - let space: usize = self - .next_char_needs_space(text.chars().next().unwrap()) - .into(); - self.config.line_length - >= self - .total_indent_len() - .saturating_add(self.current_line_len()) - .saturating_add(text.chars().count() + space) - } - - /// Write empty brackets with respect to `config.bracket_spacing` setting: - /// `"{ }"` if `true`, `"{}"` if `false` - fn write_empty_brackets(&mut self) -> Result<()> { - let brackets = if self.config.bracket_spacing { - "{ }" - } else { - "{}" - }; - write_chunk!(self, "{brackets}")?; - Ok(()) - } - - /// Write semicolon to the buffer - fn write_semicolon(&mut self) -> Result<()> { - write!(self.buf(), ";")?; - Ok(()) - } - - /// Write whitespace separator to the buffer - /// `"\n"` if `multiline` is `true`, `" "` if `false` - fn write_whitespace_separator(&mut self, multiline: bool) -> Result<()> { - if !self.is_beginning_of_line() { - write!(self.buf(), "{}", if multiline { "\n" } else { " " })?; - } - Ok(()) - } - - /// Write new line with preserved `last_indent_group_skipped` flag - fn write_preserved_line(&mut self) -> Result<()> { - let last_indent_group_skipped = self.last_indent_group_skipped(); - writeln!(self.buf())?; - self.set_last_indent_group_skipped(last_indent_group_skipped); - Ok(()) - } - - /// Returns number of blank lines in source between two byte indexes - fn blank_lines(&self, start: usize, end: usize) -> usize { - self.source[start..end] - .trim_comments() - .matches('\n') - .count() - } - - /// Get the byte offset of the next line - fn find_next_line(&self, byte_offset: usize) -> Option { - let mut iter = self.source[byte_offset..].char_indices(); - while let Some((_, ch)) = iter.next() { - match ch { - '\n' => return iter.next().map(|(idx, _)| byte_offset + idx), - '\r' => { - return iter.next().and_then(|(idx, ch)| match ch { - '\n' => iter.next().map(|(idx, _)| byte_offset + idx), - _ => Some(byte_offset + idx), - }) - } - _ => {} - } - } - None - } - - /// Find the next instance of the character in source excluding comments - fn find_next_in_src(&self, byte_offset: usize, needle: char) -> Option { - self.source[byte_offset..] - .comment_state_char_indices() - .position(|(state, _, ch)| needle == ch && state == CommentState::None) - .map(|p| byte_offset + p) - } - - /// Find the start of the next instance of a slice in source - fn find_next_str_in_src(&self, byte_offset: usize, needle: &str) -> Option { - let subset = &self.source[byte_offset..]; - needle.chars().next().and_then(|first_char| { - subset - .comment_state_char_indices() - .position(|(state, idx, ch)| { - first_char == ch - && state == CommentState::None - && idx + needle.len() <= subset.len() - && subset[idx..idx + needle.len()].eq(needle) - }) - .map(|p| byte_offset + p) - }) - } - - /// Extends the location to the next instance of a character. Returns true if the loc was - /// extended - fn extend_loc_until(&self, loc: &mut Loc, needle: char) -> bool { - if let Some(end) = self - .find_next_in_src(loc.end(), needle) - .map(|offset| offset + 1) - { - *loc = loc.with_end(end); - true - } else { - false - } - } - - /// Return the flag whether the attempt should be made - /// to write the block on a single line. - /// If the block style is configured to [SingleLineBlockStyle::Preserve], - /// lookup whether there was a newline introduced in `[start_from, end_at]` range - /// where `end_at` is the start of the block. - fn should_attempt_block_single_line( - &mut self, - stmt: &mut Statement, - start_from: usize, - ) -> bool { - match self.config.single_line_statement_blocks { - SingleLineBlockStyle::Single => true, - SingleLineBlockStyle::Multi => false, - SingleLineBlockStyle::Preserve => { - let end_at = match stmt { - Statement::Block { statements, .. } if !statements.is_empty() => { - statements.first().as_ref().unwrap().loc().start() - } - Statement::Expression(loc, _) => loc.start(), - _ => stmt.loc().start(), - }; - - self.find_next_line(start_from) - .map_or(false, |loc| loc >= end_at) - } - } - } - - /// Create a chunk given a string and the location information - fn chunk_at( - &mut self, - byte_offset: usize, - next_byte_offset: Option, - needs_space: Option, - content: impl std::fmt::Display, - ) -> Chunk { - Chunk { - postfixes_before: self.comments.remove_postfixes_before(byte_offset), - prefixes: self.comments.remove_prefixes_before(byte_offset), - content: content.to_string(), - postfixes: next_byte_offset - .map(|byte_offset| self.comments.remove_postfixes_before(byte_offset)) - .unwrap_or_default(), - needs_space, - } - } - - /// Create a chunk given a callback - fn chunked( - &mut self, - byte_offset: usize, - next_byte_offset: Option, - fun: impl FnMut(&mut Self) -> Result<()>, - ) -> Result { - let postfixes_before = self.comments.remove_postfixes_before(byte_offset); - let prefixes = self.comments.remove_prefixes_before(byte_offset); - let content = self.with_temp_buf(fun)?.w; - let postfixes = next_byte_offset - .map(|byte_offset| self.comments.remove_postfixes_before(byte_offset)) - .unwrap_or_default(); - Ok(Chunk { - postfixes_before, - prefixes, - content, - postfixes, - needs_space: None, - }) - } - - /// Create a chunk given a [Visitable] item - fn visit_to_chunk( - &mut self, - byte_offset: usize, - next_byte_offset: Option, - visitable: &mut impl Visitable, - ) -> Result { - self.chunked(byte_offset, next_byte_offset, |fmt| { - visitable.visit(fmt)?; - Ok(()) - }) - } - - /// Transform [Visitable] items to the list of chunks - fn items_to_chunks<'b>( - &mut self, - next_byte_offset: Option, - items: impl Iterator + 'b, - ) -> Result> { - let mut items = items.peekable(); - let mut out = Vec::with_capacity(items.size_hint().1.unwrap_or(0)); - while let Some((loc, item)) = items.next() { - let chunk_next_byte_offset = items - .peek() - .map(|(loc, _)| loc.start()) - .or(next_byte_offset); - out.push(self.visit_to_chunk(loc.start(), chunk_next_byte_offset, item)?); - } - Ok(out) - } - - /// Transform [Visitable] items to a list of chunks and then sort those chunks by [AttrSortKey] - fn items_to_chunks_sorted<'b>( - &mut self, - next_byte_offset: Option, - items: impl Iterator + 'b, - ) -> Result> { - let mut items = items.peekable(); - let mut out = Vec::with_capacity(items.size_hint().1.unwrap_or(0)); - while let Some(item) = items.next() { - let chunk_next_byte_offset = items - .peek() - .map(|next| next.loc().start()) - .or(next_byte_offset); - let chunk = self.visit_to_chunk(item.loc().start(), chunk_next_byte_offset, item)?; - out.push((item, chunk)); - } - out.sort_by(|(a, _), (b, _)| a.cmp(b)); - Ok(out.into_iter().map(|(_, c)| c).collect()) - } - - /// Write a comment to the buffer formatted. - /// WARNING: This may introduce a newline if the comment is a Line comment - /// or if the comment are wrapped - fn write_comment(&mut self, comment: &CommentWithMetadata, is_first: bool) -> Result<()> { - if self.inline_config.is_disabled(comment.loc) { - return self.write_raw_comment(comment); - } - - match comment.position { - CommentPosition::Prefix => self.write_prefix_comment(comment, is_first), - CommentPosition::Postfix => self.write_postfix_comment(comment), - } - } - - /// Write a comment with position [CommentPosition::Prefix] - fn write_prefix_comment( - &mut self, - comment: &CommentWithMetadata, - is_first: bool, - ) -> Result<()> { - if !self.is_beginning_of_line() { - self.write_preserved_line()?; - } - if !is_first && comment.has_newline_before { - self.write_preserved_line()?; - } - - if matches!(comment.ty, CommentType::DocBlock) { - let mut lines = comment.contents().trim().lines(); - writeln!(self.buf(), "{}", comment.start_token())?; - lines.try_for_each(|l| self.write_doc_block_line(comment, l))?; - write!(self.buf(), " {}", comment.end_token().unwrap())?; - self.write_preserved_line()?; - return Ok(()); - } - - write!(self.buf(), "{}", comment.start_token())?; - - let mut wrapped = false; - let contents = comment.contents(); - let mut lines = contents.lines().peekable(); - while let Some(line) = lines.next() { - wrapped |= self.write_comment_line(comment, line)?; - if lines.peek().is_some() { - self.write_preserved_line()?; - } - } - - if let Some(end) = comment.end_token() { - // Check if the end token in the original comment was on the separate line - if !wrapped && comment.comment.lines().count() > contents.lines().count() { - self.write_preserved_line()?; - } - write!(self.buf(), "{end}")?; - } - if self.find_next_line(comment.loc.end()).is_some() { - self.write_preserved_line()?; - } - - Ok(()) - } - - /// Write a comment with position [CommentPosition::Postfix] - fn write_postfix_comment(&mut self, comment: &CommentWithMetadata) -> Result<()> { - let indented = self.is_beginning_of_line(); - self.indented_if(indented, 1, |fmt| { - if !indented && fmt.next_char_needs_space('/') { - fmt.write_whitespace_separator(false)?; - } - - write!(fmt.buf(), "{}", comment.start_token())?; - let start_token_pos = fmt.current_line_len(); - - let mut lines = comment.contents().lines().peekable(); - fmt.grouped(|fmt| { - while let Some(line) = lines.next() { - fmt.write_comment_line(comment, line)?; - if lines.peek().is_some() { - fmt.write_whitespace_separator(true)?; - } - } - Ok(()) - })?; - - if let Some(end) = comment.end_token() { - // If comment is not multiline, end token has to be aligned with the start - if fmt.is_beginning_of_line() { - write!(fmt.buf(), "{}{end}", " ".repeat(start_token_pos))?; - } else { - write!(fmt.buf(), "{end}")?; - } - } - - if comment.is_line() { - fmt.write_whitespace_separator(true)?; - } - Ok(()) - }) - } - - /// Write the line of a doc block comment line - fn write_doc_block_line(&mut self, comment: &CommentWithMetadata, line: &str) -> Result<()> { - if line.trim().starts_with('*') { - let line = line.trim().trim_start_matches('*'); - let needs_space = line.chars().next().map_or(false, |ch| !ch.is_whitespace()); - write!(self.buf(), " *{}", if needs_space { " " } else { "" })?; - self.write_comment_line(comment, line)?; - self.write_whitespace_separator(true)?; - return Ok(()); - } - - let indent_whitespace_count = line - .char_indices() - .take_while(|(idx, ch)| ch.is_whitespace() && *idx <= self.buf.current_indent_len()) - .count(); - let to_skip = indent_whitespace_count - indent_whitespace_count % self.config.tab_width; - write!(self.buf(), " * ")?; - self.write_comment_line(comment, &line[to_skip..])?; - self.write_whitespace_separator(true)?; - Ok(()) - } - - /// Write a comment line that might potentially overflow the maximum line length - /// and, if configured, will be wrapped to the next line. - fn write_comment_line(&mut self, comment: &CommentWithMetadata, line: &str) -> Result { - if self.will_it_fit(line) || !self.config.wrap_comments { - let start_with_ws = line - .chars() - .next() - .map(|ch| ch.is_whitespace()) - .unwrap_or_default(); - if !self.is_beginning_of_line() || !start_with_ws { - write!(self.buf(), "{line}")?; - return Ok(false); - } - - // if this is the beginning of the line, - // the comment should start with at least an indent - let indent = self.buf.current_indent_len(); - let mut chars = line - .char_indices() - .skip_while(|(idx, ch)| ch.is_whitespace() && *idx < indent) - .map(|(_, ch)| ch); - let padded = format!("{}{}", " ".repeat(indent), chars.join("")); - self.write_raw(padded)?; - return Ok(false); - } - - let mut words = line.split(' ').peekable(); - while let Some(word) = words.next() { - if self.is_beginning_of_line() { - write!(self.buf(), "{}", word.trim_start())?; - } else { - self.write_raw(word)?; - } - - if let Some(next) = words.peek() { - if !word.is_empty() && !self.will_it_fit(next) { - // the next word doesn't fit on this line, - // write remaining words on the next - self.write_whitespace_separator(true)?; - // write newline wrap token - write!(self.buf(), "{}", comment.wrap_token())?; - self.write_comment_line(comment, &words.join(" "))?; - return Ok(true); - } - - self.write_whitespace_separator(false)?; - } - } - Ok(false) - } - - /// Write a raw comment. This is like [`write_comment`] but won't do any formatting or worry - /// about whitespace behind the comment - fn write_raw_comment(&mut self, comment: &CommentWithMetadata) -> Result<()> { - self.write_raw(&comment.comment)?; - if comment.is_line() { - self.write_preserved_line()?; - } - Ok(()) - } - - // TODO handle whitespace between comments for disabled sections - /// Write multiple comments - fn write_comments<'b>( - &mut self, - comments: impl IntoIterator, - ) -> Result<()> { - let mut comments = comments.into_iter().peekable(); - let mut last_byte_written = match comments.peek() { - Some(comment) => comment.loc.start(), - None => return Ok(()), - }; - let mut is_first = true; - for comment in comments { - let unwritten_whitespace_loc = Loc::File( - comment.loc.file_no(), - last_byte_written, - comment.loc.start(), - ); - if self.inline_config.is_disabled(unwritten_whitespace_loc) { - self.write_raw(&self.source[unwritten_whitespace_loc.range()])?; - self.write_raw_comment(comment)?; - last_byte_written = if comment.is_line() { - self.find_next_line(comment.loc.end()) - .unwrap_or_else(|| comment.loc.end()) - } else { - comment.loc.end() - }; - } else { - self.write_comment(comment, is_first)?; - } - is_first = false; - } - Ok(()) - } - - /// Write a postfix comments before a given location - fn write_postfix_comments_before(&mut self, byte_end: usize) -> Result<()> { - let comments = self.comments.remove_postfixes_before(byte_end); - self.write_comments(&comments) - } - - /// Write all prefix comments before a given location - fn write_prefix_comments_before(&mut self, byte_end: usize) -> Result<()> { - let comments = self.comments.remove_prefixes_before(byte_end); - self.write_comments(&comments) - } - - /// Check if a chunk will fit on the current line - fn will_chunk_fit(&mut self, format_string: &str, chunk: &Chunk) -> Result { - if let Some(chunk_str) = self.simulate_to_single_line(|fmt| fmt.write_chunk(chunk))? { - Ok(self.will_it_fit(format_string.replacen("{}", &chunk_str, 1))) - } else { - Ok(false) - } - } - - /// Check if a separated list of chunks will fit on the current line - fn are_chunks_separated_multiline<'b>( - &mut self, - format_string: &str, - items: impl IntoIterator, - separator: &str, - ) -> Result { - let items = items.into_iter().collect_vec(); - if let Some(chunks) = self.simulate_to_single_line(|fmt| { - fmt.write_chunks_separated(items.iter().copied(), separator, false) - })? { - Ok(!self.will_it_fit(format_string.replacen("{}", &chunks, 1))) - } else { - Ok(true) - } - } - - /// Write the chunk and any surrounding comments into the buffer - /// This will automatically add whitespace before the chunk given the rule set in - /// `next_char_needs_space`. If the chunk does not fit on the current line it will be put on - /// to the next line - fn write_chunk(&mut self, chunk: &Chunk) -> Result<()> { - // handle comments before chunk - self.write_comments(&chunk.postfixes_before)?; - self.write_comments(&chunk.prefixes)?; - - // trim chunk start - let content = if chunk.content.starts_with('\n') { - let mut chunk = chunk.content.trim_start().to_string(); - chunk.insert(0, '\n'); - chunk - } else if chunk.content.starts_with(' ') { - let mut chunk = chunk.content.trim_start().to_string(); - chunk.insert(0, ' '); - chunk - } else { - chunk.content.clone() - }; - - if !content.is_empty() { - // add whitespace if necessary - let needs_space = chunk - .needs_space - .unwrap_or_else(|| self.next_char_needs_space(content.chars().next().unwrap())); - if needs_space { - if self.will_it_fit(&content) { - write!(self.buf(), " ")?; - } else { - writeln!(self.buf())?; - } - } - - // write chunk - write!(self.buf(), "{content}")?; - } - - // write any postfix comments - self.write_comments(&chunk.postfixes)?; - - Ok(()) - } - - /// Write chunks separated by a separator. If `multiline`, each chunk will be written to a - /// separate line - fn write_chunks_separated<'b>( - &mut self, - chunks: impl IntoIterator, - separator: &str, - multiline: bool, - ) -> Result<()> { - let mut chunks = chunks.into_iter().peekable(); - while let Some(chunk) = chunks.next() { - let mut chunk = chunk.clone(); - - // handle postfixes before and add newline if necessary - self.write_comments(&std::mem::take(&mut chunk.postfixes_before))?; - if multiline && !self.is_beginning_of_line() { - writeln!(self.buf())?; - } - - // remove postfixes so we can add separator between - let postfixes = std::mem::take(&mut chunk.postfixes); - - self.write_chunk(&chunk)?; - - // add separator - if chunks.peek().is_some() { - write!(self.buf(), "{separator}")?; - self.write_comments(&postfixes)?; - if multiline && !self.is_beginning_of_line() { - writeln!(self.buf())?; - } - } else { - self.write_comments(&postfixes)?; - } - } - Ok(()) - } - - /// Apply the callback indented by the indent size - fn indented(&mut self, delta: usize, fun: impl FnMut(&mut Self) -> Result<()>) -> Result<()> { - self.indented_if(true, delta, fun) - } - - /// Apply the callback indented by the indent size if the condition is true - fn indented_if( - &mut self, - condition: bool, - delta: usize, - mut fun: impl FnMut(&mut Self) -> Result<()>, - ) -> Result<()> { - if condition { - self.indent(delta); - } - let res = fun(self); - if condition { - self.dedent(delta); - } - res?; - Ok(()) - } - - /// Apply the callback into an indent group. The first line of the indent group is not - /// indented but lines thereafter are - fn grouped(&mut self, mut fun: impl FnMut(&mut Self) -> Result<()>) -> Result { - self.start_group(); - let res = fun(self); - let indented = !self.last_indent_group_skipped(); - self.end_group(); - res?; - Ok(indented) - } - - /// Add a function context around a procedure and revert the context at the end of the procedure - /// regardless of the response - fn with_function_context( - &mut self, - context: FunctionDefinition, - mut fun: impl FnMut(&mut Self) -> Result<()>, - ) -> Result<()> { - self.context.function = Some(context); - let res = fun(self); - self.context.function = None; - res - } - - /// Add a contract context around a procedure and revert the context at the end of the procedure - /// regardless of the response - fn with_contract_context( - &mut self, - context: ContractDefinition, - mut fun: impl FnMut(&mut Self) -> Result<()>, - ) -> Result<()> { - self.context.contract = Some(context); - let res = fun(self); - self.context.contract = None; - res - } - - /// Create a transaction. The result of the transaction is not applied to the buffer unless - /// `Transacton::commit` is called - fn transact<'b>( - &'b mut self, - fun: impl FnMut(&mut Self) -> Result<()>, - ) -> Result> { - Transaction::new(self, fun) - } - - /// Do the callback and return the result on the buffer as a string - fn simulate_to_string(&mut self, fun: impl FnMut(&mut Self) -> Result<()>) -> Result { - Ok(self.transact(fun)?.buffer) - } - - /// Turn a chunk and its surrounding comments into a a string - fn chunk_to_string(&mut self, chunk: &Chunk) -> Result { - self.simulate_to_string(|fmt| fmt.write_chunk(chunk)) - } - - /// Try to create a string based on a callback. If the string does not fit on a single line - /// this will return `None` - fn simulate_to_single_line( - &mut self, - mut fun: impl FnMut(&mut Self) -> Result<()>, - ) -> Result> { - let mut single_line = false; - let tx = self.transact(|fmt| { - fmt.restrict_to_single_line(true); - single_line = match fun(fmt) { - Ok(()) => true, - Err(FormatterError::Fmt(_)) => false, - Err(err) => bail!(err), - }; - Ok(()) - })?; - Ok(if single_line && tx.will_it_fit(&tx.buffer) { - Some(tx.buffer) - } else { - None - }) - } - - /// Try to apply a callback to a single line. If the callback cannot be applied to a single - /// line the callback will not be applied to the buffer and `false` will be returned. Otherwise - /// `true` will be returned - fn try_on_single_line(&mut self, mut fun: impl FnMut(&mut Self) -> Result<()>) -> Result { - let mut single_line = false; - let tx = self.transact(|fmt| { - fmt.restrict_to_single_line(true); - single_line = match fun(fmt) { - Ok(()) => true, - Err(FormatterError::Fmt(_)) => false, - Err(err) => bail!(err), - }; - Ok(()) - })?; - Ok(if single_line && tx.will_it_fit(&tx.buffer) { - tx.commit()?; - true - } else { - false - }) - } - - /// Surrounds a callback with parentheses. The callback will try to be applied to a single - /// line. If the callback cannot be applied to a single line the callback will applied to the - /// nextline indented. The callback receives a `multiline` hint as the second argument which - /// receives `true` in the latter case - fn surrounded( - &mut self, - first: SurroundingChunk, - last: SurroundingChunk, - mut fun: impl FnMut(&mut Self, bool) -> Result<()>, - ) -> Result<()> { - let first_chunk = self.chunk_at( - first.loc_before(), - first.loc_next(), - first.spaced, - first.content, - ); - self.write_chunk(&first_chunk)?; - - let multiline = !self.try_on_single_line(|fmt| { - fun(fmt, false)?; - let last_chunk = fmt.chunk_at( - last.loc_before(), - last.loc_next(), - last.spaced, - &last.content, - ); - fmt.write_chunk(&last_chunk)?; - Ok(()) - })?; - - if multiline { - self.indented(1, |fmt| { - fmt.write_whitespace_separator(true)?; - let stringified = fmt.with_temp_buf(|fmt| fun(fmt, true))?.w; - write_chunk!(fmt, "{}", stringified.trim_start()) - })?; - if !last.content.trim_start().is_empty() { - self.write_whitespace_separator(true)?; - } - let last_chunk = self.chunk_at( - last.loc_before(), - last.loc_next(), - last.spaced, - &last.content, - ); - self.write_chunk(&last_chunk)?; - } - - Ok(()) - } - - /// Write each [Visitable] item on a separate line. The function will check if there are any - /// blank lines between each visitable statement and will apply a single blank line if there - /// exists any. The `needs_space` callback can force a newline and is given the last_item if - /// any and the next item as arguments - fn write_lined_visitable<'b, I, V, F>( - &mut self, - loc: Loc, - items: I, - needs_space_fn: F, - ) -> Result<()> - where - I: Iterator + 'b, - V: Visitable + CodeLocation + 'b, - F: Fn(&V, &V) -> bool, - { - let mut items = items.collect::>(); - items.reverse(); - // get next item - let pop_next = |fmt: &mut Self, items: &mut Vec<&'b mut V>| { - let comment = fmt - .comments - .iter() - .next() - .filter(|comment| comment.loc.end() < loc.end()); - let item = items.last(); - if let (Some(comment), Some(item)) = (comment, item) { - if comment.loc < item.loc() { - Some(Either::Left(fmt.comments.pop().unwrap())) - } else { - Some(Either::Right(items.pop().unwrap())) - } - } else if comment.is_some() { - Some(Either::Left(fmt.comments.pop().unwrap())) - } else if item.is_some() { - Some(Either::Right(items.pop().unwrap())) - } else { - None - } - }; - // get whitespace between to offsets. this needs to account for possible left over - // semicolons which are not included in the `Loc` - let unwritten_whitespace = |from: usize, to: usize| { - let to = to.max(from); - let mut loc = Loc::File(loc.file_no(), from, to); - let src = &self.source[from..to]; - if let Some(semi) = src.find(';') { - loc = loc.with_start(from + semi + 1); - } - (loc, &self.source[loc.range()]) - }; - - let mut last_byte_written = match ( - self.comments - .iter() - .next() - .filter(|comment| comment.loc.end() < loc.end()), - items.last(), - ) { - (Some(comment), Some(item)) => comment.loc.min(item.loc()), - (None, Some(item)) => item.loc(), - (Some(comment), None) => comment.loc, - (None, None) => return Ok(()), - } - .start(); - let mut last_loc: Option = None; - let mut needs_space = false; - while let Some(mut line_item) = pop_next(self, &mut items) { - let loc = line_item.as_ref().either(|c| c.loc, |i| i.loc()); - let (unwritten_whitespace_loc, unwritten_whitespace) = - unwritten_whitespace(last_byte_written, loc.start()); - let ignore_whitespace = if self.inline_config.is_disabled(unwritten_whitespace_loc) { - trace!("Unwritten whitespace: {unwritten_whitespace:?}"); - self.write_raw(unwritten_whitespace)?; - true - } else { - false - }; - match line_item.as_mut() { - Either::Left(comment) => { - if ignore_whitespace { - self.write_raw_comment(comment)?; - if unwritten_whitespace.contains('\n') { - needs_space = false; - } - } else { - self.write_comment(comment, last_loc.is_none())?; - if last_loc.is_some() && comment.has_newline_before { - needs_space = false; - } - } - } - Either::Right(item) => { - if !ignore_whitespace { - self.write_whitespace_separator(true)?; - if let Some(last_loc) = last_loc { - if needs_space || self.blank_lines(last_loc.end(), loc.start()) > 1 { - writeln!(self.buf())?; - } - } - } - if let Some(next_item) = items.last() { - needs_space = needs_space_fn(item, next_item); - } - trace!("Visiting {}", { - let n = std::any::type_name::(); - n.strip_prefix("solang_parser::pt::").unwrap_or(n) - }); - item.visit(self)?; - } - } - - last_loc = Some(loc); - last_byte_written = loc.end(); - if let Some(comment) = line_item.as_ref().left() { - if comment.is_line() { - last_byte_written = self - .find_next_line(last_byte_written) - .unwrap_or(last_byte_written); - } - } - } - - // write manually to avoid eof comment being detected as first - let comments = self.comments.remove_prefixes_before(loc.end()); - for comment in comments { - self.write_comment(&comment, false)?; - } - - let (unwritten_src_loc, mut unwritten_whitespace) = - unwritten_whitespace(last_byte_written, loc.end()); - if self.inline_config.is_disabled(unwritten_src_loc) { - if unwritten_src_loc.end() == self.source.len() { - // remove EOF line ending - unwritten_whitespace = unwritten_whitespace - .strip_suffix('\n') - .map(|w| w.strip_suffix('\r').unwrap_or(w)) - .unwrap_or(unwritten_whitespace); - } - trace!("Unwritten whitespace: {unwritten_whitespace:?}"); - self.write_raw(unwritten_whitespace)?; - } - - Ok(()) - } - - /// Visit the right side of an assignment. The function will try to write the assignment on a - /// single line or indented on the next line. If it can't do this it resorts to letting the - /// expression decide how to split iself on multiple lines - fn visit_assignment(&mut self, expr: &mut Expression) -> Result<()> { - if self.try_on_single_line(|fmt| expr.visit(fmt))? { - return Ok(()); - } - - self.write_postfix_comments_before(expr.loc().start())?; - self.write_prefix_comments_before(expr.loc().start())?; - - if self.try_on_single_line(|fmt| fmt.indented(1, |fmt| expr.visit(fmt)))? { - return Ok(()); - } - - let mut fit_on_next_line = false; - self.indented(1, |fmt| { - let tx = fmt.transact(|fmt| { - writeln!(fmt.buf())?; - fit_on_next_line = fmt.try_on_single_line(|fmt| expr.visit(fmt))?; - Ok(()) - })?; - if fit_on_next_line { - tx.commit()?; - } - Ok(()) - })?; - - if !fit_on_next_line { - self.indented_if(expr.is_unsplittable(), 1, |fmt| expr.visit(fmt))?; - } - - Ok(()) - } - - /// Visit the list of comma separated items. - /// If the prefix is not empty, then the function will write - /// the whitespace before the parentheses (if they are required). - fn visit_list( - &mut self, - prefix: &str, - items: &mut Vec, - start_offset: Option, - end_offset: Option, - paren_required: bool, - ) -> Result<()> - where - T: Visitable + CodeLocation, - { - write_chunk!(self, "{}", prefix)?; - let whitespace = if !prefix.is_empty() { " " } else { "" }; - let next_after_start_offset = items.first().map(|item| item.loc().start()); - let first_surrounding = SurroundingChunk::new("", start_offset, next_after_start_offset); - let last_surronding = SurroundingChunk::new(")", None, end_offset); - if items.is_empty() { - if paren_required { - write!(self.buf(), "{whitespace}(")?; - self.surrounded(first_surrounding, last_surronding, |fmt, _| { - // write comments before the list end - write_chunk!(fmt, end_offset.unwrap_or_default(), "")?; - Ok(()) - })?; - } - } else { - write!(self.buf(), "{whitespace}(")?; - self.surrounded(first_surrounding, last_surronding, |fmt, multiline| { - let args = - fmt.items_to_chunks(end_offset, items.iter_mut().map(|arg| (arg.loc(), arg)))?; - let multiline = - multiline && fmt.are_chunks_separated_multiline("{}", &args, ",")?; - fmt.write_chunks_separated(&args, ",", multiline)?; - Ok(()) - })?; - } - Ok(()) - } - - /// Visit the block item. Attempt to write it on the single - /// line if requested. Surround by curly braces and indent - /// each line otherwise. Returns `true` if the block fit - /// on a single line - fn visit_block( - &mut self, - loc: Loc, - statements: &mut Vec, - attempt_single_line: bool, - attempt_omit_braces: bool, - ) -> Result - where - T: Visitable + CodeLocation, - { - if attempt_single_line && statements.len() == 1 { - let fits_on_single = self.try_on_single_line(|fmt| { - if !attempt_omit_braces { - write!(fmt.buf(), "{{ ")?; - } - statements.first_mut().unwrap().visit(fmt)?; - if !attempt_omit_braces { - write!(fmt.buf(), " }}")?; - } - Ok(()) - })?; - - if fits_on_single { - return Ok(true); - } - } - - write_chunk!(self, "{{")?; - - if let Some(statement) = statements.first() { - self.write_whitespace_separator(true)?; - self.write_postfix_comments_before(CodeLocation::loc(statement).start())?; - } - - self.indented(1, |fmt| { - fmt.write_lined_visitable(loc, statements.iter_mut(), |_, _| false)?; - Ok(()) - })?; - - if !statements.is_empty() { - self.write_whitespace_separator(true)?; - } - write_chunk!(self, loc.end(), "}}")?; - - Ok(false) - } - - /// Visit statement as `Statement::Block`. - fn visit_stmt_as_block( - &mut self, - stmt: &mut Statement, - attempt_single_line: bool, - ) -> Result { - match stmt { - Statement::Block { - loc, statements, .. - } => self.visit_block(*loc, statements, attempt_single_line, true), - _ => self.visit_block(stmt.loc(), &mut vec![stmt], attempt_single_line, true), - } - } - - /// Visit the generic member access expression and - /// attempt flatten it by checking if the inner expression - /// matches a given member access variant. - fn visit_member_access<'b, T, M>( - &mut self, - expr: &'b mut Box, - ident: &mut Identifier, - mut matcher: M, - ) -> Result<()> - where - T: CodeLocation + Visitable, - M: FnMut(&mut Self, &'b mut Box) -> Result, &'b mut Identifier)>>, - { - let chunk_member_access = |fmt: &mut Self, ident: &mut Identifier, expr: &mut Box| { - fmt.chunked(ident.loc.start(), Some(expr.loc().start()), |fmt| { - ident.visit(fmt) - }) - }; - - let mut chunks: Vec = vec![chunk_member_access(self, ident, expr)?]; - let mut remaining = expr; - while let Some((inner_expr, inner_ident)) = matcher(self, remaining)? { - chunks.push(chunk_member_access(self, inner_ident, inner_expr)?); - remaining = inner_expr; - } - - chunks.reverse(); - chunks - .iter_mut() - .for_each(|chunk| chunk.content.insert(0, '.')); - - if !self.try_on_single_line(|fmt| fmt.write_chunks_separated(&chunks, "", false))? { - self.grouped(|fmt| fmt.write_chunks_separated(&chunks, "", true))?; - } - Ok(()) - } - - /// Visit the yul string with an optional identifier. - /// If the identifier is present, write the value in the format `:`. - /// Ref: https://docs.soliditylang.org/en/v0.8.15/yul.html#variable-declarations - fn visit_yul_string_with_ident( - &mut self, - loc: Loc, - val: &str, - ident: &mut Option, - ) -> Result<()> { - let ident = if let Some(ident) = ident { - format!(":{}", ident.name) - } else { - "".to_owned() - }; - write_chunk!(self, loc.start(), loc.end(), "{val}{ident}")?; - Ok(()) - } - - /// Format a quoted string as `prefix"string"` where the quote character is handled - /// by the configuration `quote_style` - fn quote_str(&self, loc: Loc, prefix: Option<&str>, string: &str) -> String { - let get_og_quote = || { - self.source[loc.range()] - .quote_state_char_indices() - .find_map(|(state, _, ch)| { - if matches!(state, QuoteState::Opening(_)) { - Some(ch) - } else { - None - } - }) - .expect("Could not find quote character for quoted string") - }; - let mut quote = self.config.quote_style.quote().unwrap_or_else(get_og_quote); - let mut quoted = format!("{quote}{string}{quote}"); - if !quoted.is_quoted() { - quote = get_og_quote(); - quoted = format!("{quote}{string}{quote}"); - } - let prefix = prefix.unwrap_or(""); - format!("{prefix}{quoted}") - } - - /// Write a quoted string. See `Formatter::quote_str` for more information - fn write_quoted_str(&mut self, loc: Loc, prefix: Option<&str>, string: &str) -> Result<()> { - write_chunk!( - self, - loc.start(), - loc.end(), - "{}", - self.quote_str(loc, prefix, string) - ) - } - - /// Write and format numbers. This will fix underscores as well as remove unnecessary 0's and - /// exponents - fn write_num_literal( - &mut self, - loc: Loc, - value: &str, - fractional: Option<&str>, - exponent: &str, - unit: &mut Option, - ) -> Result<()> { - let config = self.config.number_underscore; - - // get source if we preserve underscores - let (value, fractional, exponent) = if matches!(config, NumberUnderscore::Preserve) { - let source = &self.source[loc.start()..loc.end()]; - // Strip unit - let (source, _) = source.split_once(' ').unwrap_or((source, "")); - let (val, exp) = source.split_once(['e', 'E']).unwrap_or((source, "")); - let (val, fract) = val - .split_once('.') - .map(|(val, fract)| (val, Some(fract))) - .unwrap_or((val, None)); - ( - val.trim().to_string(), - fract.map(|fract| fract.trim().to_string()), - exp.trim().to_string(), - ) - } else { - // otherwise strip underscores - ( - value.trim().replace('_', ""), - fractional.map(|fract| fract.trim().replace('_', "")), - exponent.trim().replace('_', ""), - ) - }; - - // strip any padded 0's - let val = value.trim_start_matches('0'); - let fract = fractional.as_ref().map(|fract| fract.trim_end_matches('0')); - let (exp_sign, mut exp) = if let Some(exp) = exponent.strip_prefix('-') { - ("-", exp) - } else { - ("", exponent.as_str()) - }; - exp = exp.trim().trim_start_matches('0'); - - let add_underscores = |string: &str, reversed: bool| -> String { - if !matches!(config, NumberUnderscore::Thousands) || string.len() < 5 { - return string.to_string(); - } - if reversed { - Box::new(string.as_bytes().chunks(3)) as Box> - } else { - Box::new(string.as_bytes().rchunks(3).rev()) as Box> - } - .map(|chunk| std::str::from_utf8(chunk).expect("valid utf8 content.")) - .collect::>() - .join("_") - }; - - let mut out = String::new(); - if val.is_empty() { - out.push('0'); - } else { - out.push_str(&add_underscores(val, false)); - } - if let Some(fract) = fract { - out.push('.'); - if fract.is_empty() { - out.push('0'); - } else { - // TODO re-enable me on the next solang-parser v0.1.18 - // currently disabled because of the following bug - // https://github.com/hyperledger-labs/solang/pull/954 - // out.push_str(&add_underscores(fract, true)); - out.push_str(fract) - } - } - if !exp.is_empty() { - out.push('e'); - out.push_str(exp_sign); - out.push_str(&add_underscores(exp, false)); - } - - write_chunk!(self, loc.start(), loc.end(), "{out}")?; - self.write_unit(unit) - } - - /// Write built-in unit. - fn write_unit(&mut self, unit: &mut Option) -> Result<()> { - if let Some(unit) = unit { - write_chunk!(self, unit.loc.start(), unit.loc.end(), "{}", unit.name)?; - } - Ok(()) - } - - /// Write the function header - fn write_function_header( - &mut self, - func: &mut FunctionDefinition, - body_loc: Option, - header_multiline: bool, - ) -> Result { - let func_name = if let Some(ident) = &func.name { - format!("{} {}", func.ty, ident.name) - } else { - func.ty.to_string() - }; - - // calculate locations of chunk groups - let attrs_loc = func.attributes.first().map(|attr| attr.loc()); - let returns_loc = func.returns.first().map(|param| param.0); - - let params_next_offset = attrs_loc - .as_ref() - .or(returns_loc.as_ref()) - .or(body_loc.as_ref()) - .map(|loc| loc.start()); - let attrs_end = returns_loc - .as_ref() - .or(body_loc.as_ref()) - .map(|loc| loc.start()); - let returns_end = body_loc.as_ref().map(|loc| loc.start()); - - let mut params_multiline = false; - - let params_loc = { - let mut loc = func.loc.with_end(func.loc.start()); - self.extend_loc_until(&mut loc, ')'); - loc - }; - if self.inline_config.is_disabled(params_loc) { - let chunk = self.chunked(func.loc.start(), None, |fmt| fmt.visit_source(params_loc))?; - params_multiline = chunk.content.contains('\n'); - self.write_chunk(&chunk)?; - } else { - let first_surrounding = SurroundingChunk::new( - format!("{func_name}("), - Some(func.loc.start()), - Some( - func.params - .first() - .map(|param| param.0.start()) - .unwrap_or_else(|| params_loc.end()), - ), - ); - self.surrounded( - first_surrounding, - SurroundingChunk::new(")", None, params_next_offset), - |fmt, multiline| { - let params = fmt.items_to_chunks( - params_next_offset, - func.params - .iter_mut() - .filter_map(|(loc, param)| param.as_mut().map(|param| (*loc, param))), - )?; - let after_params = if !func.attributes.is_empty() || !func.returns.is_empty() { - "" - } else if func.body.is_some() { - " {" - } else { - ";" - }; - let should_multiline = header_multiline - && matches!( - fmt.config.multiline_func_header, - MultilineFuncHeaderStyle::ParamsFirst | MultilineFuncHeaderStyle::All - ); - params_multiline = should_multiline - || multiline - || fmt.are_chunks_separated_multiline( - &format!("{{}}){after_params}"), - ¶ms, - ",", - )?; - fmt.write_chunks_separated(¶ms, ",", params_multiline)?; - Ok(()) - }, - )?; - } - - let mut write_attributes = |fmt: &mut Self, multiline: bool| -> Result<()> { - // write attributes - if !func.attributes.is_empty() { - let attrs_loc = func - .attributes - .first() - .unwrap() - .loc() - .with_end_from(&func.attributes.last().unwrap().loc()); - if fmt.inline_config.is_disabled(attrs_loc) { - fmt.indented(1, |fmt| fmt.visit_source(attrs_loc))?; - } else { - fmt.write_postfix_comments_before(attrs_loc.start())?; - fmt.write_whitespace_separator(multiline)?; - let attributes = - fmt.items_to_chunks_sorted(attrs_end, func.attributes.iter_mut())?; - fmt.indented(1, |fmt| { - fmt.write_chunks_separated(&attributes, "", multiline)?; - Ok(()) - })?; - } - } - - // write returns - if !func.returns.is_empty() { - let returns_start_loc = func.returns.first().unwrap().0; - let returns_loc = returns_start_loc.with_end_from(&func.returns.last().unwrap().0); - if fmt.inline_config.is_disabled(returns_loc) { - fmt.indented(1, |fmt| fmt.visit_source(returns_loc))?; - } else { - let returns = fmt.items_to_chunks( - returns_end, - func.returns - .iter_mut() - .filter_map(|(loc, param)| param.as_mut().map(|param| (*loc, param))), - )?; - fmt.write_postfix_comments_before(returns_loc.start())?; - fmt.write_whitespace_separator(multiline)?; - fmt.indented(1, |fmt| { - fmt.surrounded( - SurroundingChunk::new("returns (", Some(returns_loc.start()), None), - SurroundingChunk::new(")", None, returns_end), - |fmt, multiline_hint| { - fmt.write_chunks_separated(&returns, ",", multiline_hint)?; - Ok(()) - }, - )?; - Ok(()) - })?; - } - } - Ok(()) - }; - - let should_multiline = header_multiline - && if params_multiline { - matches!( - self.config.multiline_func_header, - MultilineFuncHeaderStyle::All - ) - } else { - matches!( - self.config.multiline_func_header, - MultilineFuncHeaderStyle::AttributesFirst - ) - }; - let attrs_multiline = should_multiline - || !self.try_on_single_line(|fmt| { - write_attributes(fmt, false)?; - if !fmt.will_it_fit(if func.body.is_some() { " {" } else { ";" }) { - bail!(FormatterError::fmt()) - } - Ok(()) - })?; - if attrs_multiline { - write_attributes(self, true)?; - } - Ok(attrs_multiline) - } - - /// Write potentially nested `if statements` - fn write_if_stmt( - &mut self, - loc: Loc, - cond: &mut Expression, - if_branch: &mut Box, - else_branch: &mut Option>, - ) -> Result<(), FormatterError> { - let single_line_stmt_wide = self.context.if_stmt_single_line.unwrap_or_default(); - - visit_source_if_disabled_else!(self, loc.with_end(if_branch.loc().start()), { - self.surrounded( - SurroundingChunk::new("if (", Some(loc.start()), Some(cond.loc().start())), - SurroundingChunk::new(")", None, Some(if_branch.loc().start())), - |fmt, _| { - cond.visit(fmt)?; - fmt.write_postfix_comments_before(if_branch.loc().start()) - }, - )?; - }); - - let cond_close_paren_loc = self - .find_next_in_src(cond.loc().end(), ')') - .unwrap_or_else(|| cond.loc().end()); - let attempt_single_line = single_line_stmt_wide - && self.should_attempt_block_single_line(if_branch.as_mut(), cond_close_paren_loc); - let if_branch_is_single_line = self.visit_stmt_as_block(if_branch, attempt_single_line)?; - if single_line_stmt_wide && !if_branch_is_single_line { - bail!(FormatterError::fmt()) - } - - if let Some(else_branch) = else_branch { - self.write_postfix_comments_before(else_branch.loc().start())?; - if if_branch_is_single_line { - writeln!(self.buf())?; - } - write_chunk!(self, else_branch.loc().start(), "else")?; - if let Statement::If(loc, cond, if_branch, else_branch) = else_branch.as_mut() { - self.visit_if(*loc, cond, if_branch, else_branch, false)?; - } else { - let else_branch_is_single_line = - self.visit_stmt_as_block(else_branch, if_branch_is_single_line)?; - if single_line_stmt_wide && !else_branch_is_single_line { - bail!(FormatterError::fmt()) - } - } - } - Ok(()) - } -} - -// Traverse the Solidity Parse Tree and write to the code formatter -impl<'a, W: Write> Visitor for Formatter<'a, W> { - type Error = FormatterError; - - #[instrument(name = "source", skip(self))] - fn visit_source(&mut self, loc: Loc) -> Result<()> { - let source = String::from_utf8(self.source.as_bytes()[loc.range()].to_vec()) - .map_err(FormatterError::custom)?; - let mut lines = source.splitn(2, '\n'); - - write_chunk!(self, loc.start(), "{}", lines.next().unwrap())?; - if let Some(remainder) = lines.next() { - // Call with `self.write_str` and not `write!`, so we can have `\n` at the beginning - // without triggering an indentation - self.write_raw(format!("\n{remainder}"))?; - } - - let _ = self.comments.remove_all_comments_before(loc.end()); - - Ok(()) - } - - #[instrument(name = "SU", skip_all)] - fn visit_source_unit(&mut self, source_unit: &mut SourceUnit) -> Result<()> { - // TODO: do we need to put pragma and import directives at the top of the file? - // source_unit.0.sort_by_key(|item| match item { - // SourceUnitPart::PragmaDirective(_, _, _) => 0, - // SourceUnitPart::ImportDirective(_, _) => 1, - // _ => usize::MAX, - // }); - let loc = Loc::File( - source_unit - .loc_opt() - .or_else(|| self.comments.iter().next().map(|comment| comment.loc)) - .map(|loc| loc.file_no()) - .unwrap_or_default(), - 0, - self.source.len(), - ); - - self.write_lined_visitable( - loc, - source_unit.0.iter_mut(), - |last_unit, unit| match last_unit { - SourceUnitPart::PragmaDirective(..) => { - !matches!(unit, SourceUnitPart::PragmaDirective(..)) - } - SourceUnitPart::ImportDirective(_) => { - !matches!(unit, SourceUnitPart::ImportDirective(_)) - } - SourceUnitPart::ErrorDefinition(_) => { - !matches!(unit, SourceUnitPart::ErrorDefinition(_)) - } - SourceUnitPart::Using(_) => !matches!(unit, SourceUnitPart::Using(_)), - SourceUnitPart::VariableDefinition(_) => { - !matches!(unit, SourceUnitPart::VariableDefinition(_)) - } - SourceUnitPart::Annotation(_) => false, - _ => true, - }, - )?; - - // EOF newline - if self.last_char().map_or(true, |char| char != '\n') { - writeln!(self.buf())?; - } - - Ok(()) - } - - #[instrument(name = "contract", skip_all)] - fn visit_contract(&mut self, contract: &mut ContractDefinition) -> Result<()> { - return_source_if_disabled!(self, contract.loc); - - self.with_contract_context(contract.clone(), |fmt| { - let contract_name = contract.name.safe_unwrap(); - - visit_source_if_disabled_else!( - fmt, - contract.loc.with_end_from( - &contract - .base - .first() - .map(|b| b.loc) - .unwrap_or(contract_name.loc) - ), - { - fmt.grouped(|fmt| { - write_chunk!(fmt, contract.loc.start(), "{}", contract.ty)?; - write_chunk!(fmt, contract_name.loc.end(), "{}", contract_name.name)?; - if !contract.base.is_empty() { - write_chunk!( - fmt, - contract_name.loc.end(), - contract.base.first().unwrap().loc.start(), - "is" - )?; - } - Ok(()) - })?; - } - ); - - if !contract.base.is_empty() { - visit_source_if_disabled_else!( - fmt, - contract - .base - .first() - .unwrap() - .loc - .with_end_from(&contract.base.last().unwrap().loc), - { - fmt.indented(1, |fmt| { - let base_end = contract.parts.first().map(|part| part.loc().start()); - let bases = fmt.items_to_chunks( - base_end, - contract.base.iter_mut().map(|base| (base.loc, base)), - )?; - let multiline = - fmt.are_chunks_separated_multiline("{}", &bases, ",")?; - fmt.write_chunks_separated(&bases, ",", multiline)?; - fmt.write_whitespace_separator(multiline)?; - Ok(()) - })?; - } - ); - } - - write_chunk!(fmt, "{{")?; - - fmt.indented(1, |fmt| { - if let Some(first) = contract.parts.first() { - fmt.write_postfix_comments_before(first.loc().start())?; - fmt.write_whitespace_separator(true)?; - } else { - return Ok(()); - } - - if fmt.config.contract_new_lines { - write_chunk!(fmt, "\n")?; - } - - fmt.write_lined_visitable( - contract.loc, - contract.parts.iter_mut(), - |last_part, part| match last_part { - ContractPart::ErrorDefinition(_) => { - !matches!(part, ContractPart::ErrorDefinition(_)) - } - ContractPart::EventDefinition(_) => { - !matches!(part, ContractPart::EventDefinition(_)) - } - ContractPart::VariableDefinition(_) => { - !matches!(part, ContractPart::VariableDefinition(_)) - } - ContractPart::TypeDefinition(_) => { - !matches!(part, ContractPart::TypeDefinition(_)) - } - ContractPart::EnumDefinition(_) => { - !matches!(part, ContractPart::EnumDefinition(_)) - } - ContractPart::Using(_) => !matches!(part, ContractPart::Using(_)), - ContractPart::FunctionDefinition(last_def) => { - if last_def.is_empty() { - match part { - ContractPart::FunctionDefinition(def) => !def.is_empty(), - _ => true, - } - } else { - true - } - } - ContractPart::Annotation(_) => false, - _ => true, - }, - ) - })?; - - if !contract.parts.is_empty() { - fmt.write_whitespace_separator(true)?; - - if fmt.config.contract_new_lines { - write_chunk!(fmt, "\n")?; - } - } - - write_chunk!(fmt, contract.loc.end(), "}}")?; - - Ok(()) - })?; - - Ok(()) - } - - #[instrument(name = "pragma", skip_all)] - fn visit_pragma( - &mut self, - loc: Loc, - ident: &mut Option, - string: &mut Option, - ) -> Result<()> { - let (ident, string) = (ident.safe_unwrap(), string.safe_unwrap()); - return_source_if_disabled!(self, loc, ';'); - - #[allow(clippy::if_same_then_else)] - let pragma_descriptor = if ident.name == "solidity" { - // There are some issues with parsing Solidity's versions with crates like `semver`: - // 1. Ranges like `>=0.4.21<0.6.0` or `>=0.4.21 <0.6.0` are not parseable at all. - // 2. Versions like `0.8.10` got transformed into `^0.8.10` which is not the same. - // TODO: semver-solidity crate :D - &string.string - } else { - &string.string - }; - - write_chunk!( - self, - string.loc.end(), - "pragma {} {};", - &ident.name, - pragma_descriptor - )?; - - Ok(()) - } - - #[instrument(name = "import_plain", skip_all)] - fn visit_import_plain(&mut self, loc: Loc, import: &mut ImportPath) -> Result<()> { - return_source_if_disabled!(self, loc, ';'); - - self.grouped(|fmt| { - write_chunk!(fmt, loc.start(), import.loc().start(), "import")?; - fmt.write_quoted_str(import.loc(), None, &import_path_string(import))?; - fmt.write_semicolon()?; - Ok(()) - })?; - Ok(()) - } - - #[instrument(name = "import_global", skip_all)] - fn visit_import_global( - &mut self, - loc: Loc, - global: &mut ImportPath, - alias: &mut Identifier, - ) -> Result<()> { - return_source_if_disabled!(self, loc, ';'); - - self.grouped(|fmt| { - write_chunk!(fmt, loc.start(), global.loc().start(), "import")?; - fmt.write_quoted_str(global.loc(), None, &import_path_string(global))?; - write_chunk!(fmt, loc.start(), alias.loc.start(), "as")?; - alias.visit(fmt)?; - fmt.write_semicolon()?; - Ok(()) - })?; - Ok(()) - } - - #[instrument(name = "import_renames", skip_all)] - fn visit_import_renames( - &mut self, - loc: Loc, - imports: &mut [(Identifier, Option)], - from: &mut ImportPath, - ) -> Result<()> { - return_source_if_disabled!(self, loc, ';'); - - if imports.is_empty() { - self.grouped(|fmt| { - write_chunk!(fmt, loc.start(), "import")?; - fmt.write_empty_brackets()?; - write_chunk!(fmt, loc.start(), from.loc().start(), "from")?; - fmt.write_quoted_str(from.loc(), None, &import_path_string(from))?; - fmt.write_semicolon()?; - Ok(()) - })?; - return Ok(()); - } - - let imports_start = imports.first().unwrap().0.loc.start(); - - write_chunk!(self, loc.start(), imports_start, "import")?; - - self.surrounded( - SurroundingChunk::new("{", Some(imports_start), None), - SurroundingChunk::new("}", None, Some(from.loc().start())), - |fmt, _multiline| { - let mut imports = imports.iter_mut().peekable(); - let mut import_chunks = Vec::new(); - while let Some((ident, alias)) = imports.next() { - import_chunks.push(fmt.chunked( - ident.loc.start(), - imports.peek().map(|(ident, _)| ident.loc.start()), - |fmt| { - fmt.grouped(|fmt| { - ident.visit(fmt)?; - if let Some(alias) = alias { - write_chunk!(fmt, ident.loc.end(), alias.loc.start(), "as")?; - alias.visit(fmt)?; - } - Ok(()) - })?; - Ok(()) - }, - )?); - } - - let multiline = fmt.are_chunks_separated_multiline( - &format!("{{}} }} from \"{}\";", import_path_string(from)), - &import_chunks, - ",", - )?; - fmt.write_chunks_separated(&import_chunks, ",", multiline)?; - Ok(()) - }, - )?; - - self.grouped(|fmt| { - write_chunk!(fmt, imports_start, from.loc().start(), "from")?; - fmt.write_quoted_str(from.loc(), None, &import_path_string(from))?; - fmt.write_semicolon()?; - Ok(()) - })?; - - Ok(()) - } - - #[instrument(name = "enum", skip_all)] - fn visit_enum(&mut self, enumeration: &mut EnumDefinition) -> Result<()> { - return_source_if_disabled!(self, enumeration.loc); - - let enum_name = enumeration.name.safe_unwrap_mut(); - let mut name = - self.visit_to_chunk(enum_name.loc.start(), Some(enum_name.loc.end()), enum_name)?; - name.content = format!("enum {}", name.content); - self.write_chunk(&name)?; - - if enumeration.values.is_empty() { - self.write_empty_brackets()?; - } else { - self.surrounded( - SurroundingChunk::new( - "{", - Some( - enumeration - .values - .first_mut() - .unwrap() - .safe_unwrap() - .loc - .start(), - ), - None, - ), - SurroundingChunk::new("}", None, Some(enumeration.loc.end())), - |fmt, _multiline| { - let values = fmt.items_to_chunks( - Some(enumeration.loc.end()), - enumeration.values.iter_mut().map(|ident| { - let ident = ident.safe_unwrap_mut(); - (ident.loc, ident) - }), - )?; - fmt.write_chunks_separated(&values, ",", true)?; - Ok(()) - }, - )?; - } - - Ok(()) - } - - #[instrument(name = "expr", skip_all)] - fn visit_expr(&mut self, loc: Loc, expr: &mut Expression) -> Result<()> { - return_source_if_disabled!(self, loc); - - match expr { - Expression::Type(loc, typ) => match typ { - Type::Address => write_chunk!(self, loc.start(), "address")?, - Type::AddressPayable => write_chunk!(self, loc.start(), "address payable")?, - Type::Payable => write_chunk!(self, loc.start(), "payable")?, - Type::Bool => write_chunk!(self, loc.start(), "bool")?, - Type::String => write_chunk!(self, loc.start(), "string")?, - Type::Bytes(n) => write_chunk!(self, loc.start(), "bytes{}", n)?, - Type::Rational => write_chunk!(self, loc.start(), "rational")?, - Type::DynamicBytes => write_chunk!(self, loc.start(), "bytes")?, - Type::Int(ref n) | Type::Uint(ref n) => { - let int = if matches!(typ, Type::Int(_)) { - "int" - } else { - "uint" - }; - match n { - 256 => match self.config.int_types { - IntTypes::Long => write_chunk!(self, loc.start(), "{int}{n}")?, - IntTypes::Short => write_chunk!(self, loc.start(), "{int}")?, - IntTypes::Preserve => self.visit_source(*loc)?, - }, - _ => write_chunk!(self, loc.start(), "{int}{n}")?, - } - } - Type::Mapping { - loc, - key, - key_name, - value, - value_name, - } => { - let arrow_loc = self.find_next_str_in_src(loc.start(), "=>"); - let close_paren_loc = self - .find_next_in_src(value.loc().end(), ')') - .unwrap_or(loc.end()); - let first = SurroundingChunk::new( - "mapping(", - Some(loc.start()), - Some(key.loc().start()), - ); - let last = SurroundingChunk::new(")", Some(close_paren_loc), Some(loc.end())) - .non_spaced(); - self.surrounded(first, last, |fmt, multiline| { - fmt.grouped(|fmt| { - key.visit(fmt)?; - - if let Some(name) = key_name { - let end_loc = arrow_loc.unwrap_or(value.loc().start()); - write_chunk!(fmt, name.loc.start(), end_loc, " {}", name)?; - } else if let Some(arrow_loc) = arrow_loc { - fmt.write_postfix_comments_before(arrow_loc)?; - } - - let mut write_arrow_and_value = |fmt: &mut Self| { - write!(fmt.buf(), "=> ")?; - value.visit(fmt)?; - if let Some(name) = value_name { - write_chunk!(fmt, name.loc.start(), " {}", name)?; - } - Ok(()) - }; - - let rest_str = fmt.simulate_to_string(&mut write_arrow_and_value)?; - let multiline = multiline && !fmt.will_it_fit(rest_str); - fmt.write_whitespace_separator(multiline)?; - - write_arrow_and_value(fmt)?; - - fmt.write_postfix_comments_before(close_paren_loc)?; - fmt.write_prefix_comments_before(close_paren_loc) - })?; - Ok(()) - })?; - } - Type::Function { .. } => self.visit_source(*loc)?, - }, - Expression::BoolLiteral(loc, val) => { - write_chunk!(self, loc.start(), loc.end(), "{val}")?; - } - Expression::NumberLiteral(loc, val, exp, unit) => { - self.write_num_literal(*loc, val, None, exp, unit)?; - } - Expression::HexNumberLiteral(loc, val, unit) => { - // ref: https://docs.soliditylang.org/en/latest/types.html?highlight=address%20literal#address-literals - let val = if val.len() == 42 { - to_checksum(&H160::from_str(val).expect(""), None) - } else { - val.to_owned() - }; - write_chunk!(self, loc.start(), loc.end(), "{val}")?; - self.write_unit(unit)?; - } - Expression::RationalNumberLiteral(loc, val, fraction, exp, unit) => { - self.write_num_literal(*loc, val, Some(fraction), exp, unit)?; - } - Expression::StringLiteral(vals) => { - for StringLiteral { - loc, - string, - unicode, - } in vals - { - let prefix = if *unicode { Some("unicode") } else { None }; - self.write_quoted_str(*loc, prefix, string)?; - } - } - Expression::HexLiteral(vals) => { - for HexLiteral { loc, hex } in vals { - self.write_quoted_str(*loc, Some("hex"), hex)?; - } - } - Expression::AddressLiteral(loc, val) => { - // support of solana/substrate address literals - self.write_quoted_str(*loc, Some("address"), val)?; - } - Expression::Parenthesis(loc, expr) => { - self.surrounded( - SurroundingChunk::new("(", Some(loc.start()), None), - SurroundingChunk::new(")", None, Some(loc.end())), - |fmt, _| expr.visit(fmt), - )?; - } - Expression::ArraySubscript(_, ty_exp, index_expr) => { - ty_exp.visit(self)?; - write!(self.buf(), "[")?; - index_expr - .as_mut() - .map(|index| index.visit(self)) - .transpose()?; - write!(self.buf(), "]")?; - } - Expression::ArraySlice(loc, expr, start, end) => { - expr.visit(self)?; - write!(self.buf(), "[")?; - let mut write_slice = |fmt: &mut Self, multiline| -> Result<()> { - if multiline { - fmt.write_whitespace_separator(true)?; - } - fmt.grouped(|fmt| { - start.as_mut().map(|start| start.visit(fmt)).transpose()?; - write!(fmt.buf(), ":")?; - if let Some(end) = end { - let mut chunk = - fmt.chunked(end.loc().start(), Some(loc.end()), |fmt| { - end.visit(fmt) - })?; - if chunk.prefixes.is_empty() - && chunk.postfixes_before.is_empty() - && (start.is_none() || fmt.will_it_fit(&chunk.content)) - { - chunk.needs_space = Some(false); - } - fmt.write_chunk(&chunk)?; - } - Ok(()) - })?; - if multiline { - fmt.write_whitespace_separator(true)?; - } - Ok(()) - }; - - if !self.try_on_single_line(|fmt| write_slice(fmt, false))? { - self.indented(1, |fmt| write_slice(fmt, true))?; - } - - write!(self.buf(), "]")?; - } - Expression::ArrayLiteral(loc, exprs) => { - write_chunk!(self, loc.start(), "[")?; - let chunks = self.items_to_chunks( - Some(loc.end()), - exprs.iter_mut().map(|expr| (expr.loc(), expr)), - )?; - let multiline = self.are_chunks_separated_multiline("{}]", &chunks, ",")?; - self.indented_if(multiline, 1, |fmt| { - fmt.write_chunks_separated(&chunks, ",", multiline)?; - if multiline { - fmt.write_postfix_comments_before(loc.end())?; - fmt.write_prefix_comments_before(loc.end())?; - fmt.write_whitespace_separator(true)?; - } - Ok(()) - })?; - write_chunk!(self, loc.end(), "]")?; - } - Expression::PreIncrement(..) - | Expression::PostIncrement(..) - | Expression::PreDecrement(..) - | Expression::PostDecrement(..) - | Expression::Not(..) - | Expression::UnaryPlus(..) - | Expression::Add(..) - | Expression::Negate(..) - | Expression::Subtract(..) - | Expression::Power(..) - | Expression::Multiply(..) - | Expression::Divide(..) - | Expression::Modulo(..) - | Expression::ShiftLeft(..) - | Expression::ShiftRight(..) - | Expression::BitwiseNot(..) - | Expression::BitwiseAnd(..) - | Expression::BitwiseXor(..) - | Expression::BitwiseOr(..) - | Expression::Less(..) - | Expression::More(..) - | Expression::LessEqual(..) - | Expression::MoreEqual(..) - | Expression::And(..) - | Expression::Or(..) - | Expression::Equal(..) - | Expression::NotEqual(..) => { - let spaced = expr.has_space_around(); - let op = expr.operator().unwrap(); - - match expr.components_mut() { - (Some(left), Some(right)) => { - left.visit(self)?; - - let right_chunk = - self.chunked(right.loc().start(), Some(loc.end()), |fmt| { - write_chunk!(fmt, right.loc().start(), "{op}")?; - right.visit(fmt)?; - Ok(()) - })?; - - self.grouped(|fmt| fmt.write_chunk(&right_chunk))?; - } - (Some(left), None) => { - left.visit(self)?; - write_chunk_spaced!(self, loc.end(), Some(spaced), "{op}")?; - } - (None, Some(right)) => { - write_chunk!(self, right.loc().start(), "{op}")?; - let mut right_chunk = - self.visit_to_chunk(right.loc().end(), Some(loc.end()), right)?; - right_chunk.needs_space = Some(spaced); - self.write_chunk(&right_chunk)?; - } - (None, None) => {} - } - } - Expression::Assign(..) - | Expression::AssignOr(..) - | Expression::AssignAnd(..) - | Expression::AssignXor(..) - | Expression::AssignShiftLeft(..) - | Expression::AssignShiftRight(..) - | Expression::AssignAdd(..) - | Expression::AssignSubtract(..) - | Expression::AssignMultiply(..) - | Expression::AssignDivide(..) - | Expression::AssignModulo(..) => { - let op = expr.operator().unwrap(); - let (left, right) = expr.components_mut(); - let (left, right) = (left.unwrap(), right.unwrap()); - - left.visit(self)?; - write_chunk!(self, "{op}")?; - self.visit_assignment(right)?; - } - Expression::ConditionalOperator(loc, cond, first_expr, second_expr) => { - cond.visit(self)?; - - let first_expr = self.chunked( - first_expr.loc().start(), - Some(second_expr.loc().start()), - |fmt| { - write_chunk!(fmt, "?")?; - first_expr.visit(fmt) - }, - )?; - let second_expr = - self.chunked(second_expr.loc().start(), Some(loc.end()), |fmt| { - write_chunk!(fmt, ":")?; - second_expr.visit(fmt) - })?; - - let chunks = vec![first_expr, second_expr]; - if !self.try_on_single_line(|fmt| fmt.write_chunks_separated(&chunks, "", false))? { - self.grouped(|fmt| fmt.write_chunks_separated(&chunks, "", true))?; - } - } - Expression::Variable(ident) => { - write_chunk!(self, loc.end(), "{}", ident.name)?; - } - Expression::MemberAccess(_, expr, ident) => { - self.visit_member_access(expr, ident, |fmt, expr| match expr.as_mut() { - Expression::MemberAccess(_, inner_expr, inner_ident) => { - Ok(Some((inner_expr, inner_ident))) - } - expr => { - expr.visit(fmt)?; - Ok(None) - } - })?; - } - Expression::List(loc, items) => { - self.surrounded( - SurroundingChunk::new( - "(", - Some(loc.start()), - items.first().map(|item| item.0.start()), - ), - SurroundingChunk::new(")", None, Some(loc.end())), - |fmt, _| { - let items = fmt.items_to_chunks( - Some(loc.end()), - items.iter_mut().map(|(loc, item)| (*loc, item)), - )?; - let write_items = |fmt: &mut Self, multiline| { - fmt.write_chunks_separated(&items, ",", multiline) - }; - if !fmt.try_on_single_line(|fmt| write_items(fmt, false))? { - write_items(fmt, true)?; - } - Ok(()) - }, - )?; - } - Expression::FunctionCall(loc, expr, exprs) => { - self.visit_expr(expr.loc(), expr)?; - self.visit_list("", exprs, Some(expr.loc().end()), Some(loc.end()), true)?; - } - Expression::NamedFunctionCall(loc, expr, args) => { - self.visit_expr(expr.loc(), expr)?; - write!(self.buf(), "(")?; - self.visit_args(*loc, args)?; - write!(self.buf(), ")")?; - } - Expression::FunctionCallBlock(_, expr, stmt) => { - expr.visit(self)?; - stmt.visit(self)?; - } - _ => self.visit_source(loc)?, - }; - - Ok(()) - } - - #[instrument(name = "ident", skip_all)] - fn visit_ident(&mut self, loc: Loc, ident: &mut Identifier) -> Result<()> { - return_source_if_disabled!(self, loc); - write_chunk!(self, loc.end(), "{}", ident.name)?; - Ok(()) - } - - #[instrument(name = "ident_path", skip_all)] - fn visit_ident_path(&mut self, idents: &mut IdentifierPath) -> Result<(), Self::Error> { - if idents.identifiers.is_empty() { - return Ok(()); - } - return_source_if_disabled!(self, idents.loc); - - idents.identifiers.iter_mut().skip(1).for_each(|chunk| { - if !chunk.name.starts_with('.') { - chunk.name.insert(0, '.') - } - }); - let chunks = self.items_to_chunks( - Some(idents.loc.end()), - idents - .identifiers - .iter_mut() - .map(|ident| (ident.loc, ident)), - )?; - self.grouped(|fmt| { - let multiline = fmt.are_chunks_separated_multiline("{}", &chunks, "")?; - fmt.write_chunks_separated(&chunks, "", multiline) - })?; - Ok(()) - } - - #[instrument(name = "emit", skip_all)] - fn visit_emit(&mut self, loc: Loc, event: &mut Expression) -> Result<()> { - return_source_if_disabled!(self, loc); - write_chunk!(self, loc.start(), "emit")?; - event.visit(self)?; - self.write_semicolon()?; - Ok(()) - } - - #[instrument(name = "var_declaration", skip_all)] - fn visit_var_declaration(&mut self, var: &mut VariableDeclaration) -> Result<()> { - return_source_if_disabled!(self, var.loc); - self.grouped(|fmt| { - var.ty.visit(fmt)?; - if let Some(storage) = &var.storage { - write_chunk!(fmt, storage.loc().end(), "{storage}")?; - } - let var_name = var.name.safe_unwrap(); - write_chunk!(fmt, var_name.loc.end(), "{var_name}") - })?; - Ok(()) - } - - #[instrument(name = "break", skip_all)] - fn visit_break(&mut self, loc: Loc, semicolon: bool) -> Result<()> { - if semicolon { - return_source_if_disabled!(self, loc, ';'); - } else { - return_source_if_disabled!(self, loc); - } - write_chunk!( - self, - loc.start(), - loc.end(), - "break{}", - if semicolon { ";" } else { "" } - ) - } - - #[instrument(name = "continue", skip_all)] - fn visit_continue(&mut self, loc: Loc, semicolon: bool) -> Result<()> { - if semicolon { - return_source_if_disabled!(self, loc, ';'); - } else { - return_source_if_disabled!(self, loc); - } - write_chunk!( - self, - loc.start(), - loc.end(), - "continue{}", - if semicolon { ";" } else { "" } - ) - } - - #[instrument(name = "function", skip_all)] - fn visit_function(&mut self, func: &mut FunctionDefinition) -> Result<()> { - if func.body.is_some() { - return_source_if_disabled!(self, func.loc()); - } else { - return_source_if_disabled!(self, func.loc(), ';'); - } - - self.with_function_context(func.clone(), |fmt| { - fmt.write_postfix_comments_before(func.loc.start())?; - fmt.write_prefix_comments_before(func.loc.start())?; - - let body_loc = func.body.as_ref().map(CodeLocation::loc); - let mut attrs_multiline = false; - let fits_on_single = fmt.try_on_single_line(|fmt| { - fmt.write_function_header(func, body_loc, false)?; - Ok(()) - })?; - if !fits_on_single { - attrs_multiline = fmt.write_function_header(func, body_loc, true)?; - } - - // write function body - match &mut func.body { - Some(body) => { - let body_loc = body.loc(); - let byte_offset = body_loc.start(); - let body = fmt.visit_to_chunk(byte_offset, Some(body_loc.end()), body)?; - fmt.write_whitespace_separator( - attrs_multiline && !(func.attributes.is_empty() && func.returns.is_empty()), - )?; - fmt.write_chunk(&body)?; - } - None => fmt.write_semicolon()?, - } - - Ok(()) - })?; - - Ok(()) - } - - #[instrument(name = "function_attribute", skip_all)] - fn visit_function_attribute(&mut self, attribute: &mut FunctionAttribute) -> Result<()> { - return_source_if_disabled!(self, attribute.loc()); - - match attribute { - FunctionAttribute::Mutability(mutability) => { - write_chunk!(self, mutability.loc().end(), "{mutability}")? - } - FunctionAttribute::Visibility(visibility) => { - // Visibility will always have a location in a Function attribute - write_chunk!(self, visibility.loc_opt().unwrap().end(), "{visibility}")? - } - FunctionAttribute::Virtual(loc) => write_chunk!(self, loc.end(), "virtual")?, - FunctionAttribute::Immutable(loc) => write_chunk!(self, loc.end(), "immutable")?, - FunctionAttribute::Override(loc, args) => { - write_chunk!(self, loc.start(), "override")?; - if !args.is_empty() && self.config.override_spacing { - self.write_whitespace_separator(false)?; - } - self.visit_list("", args, None, Some(loc.end()), false)? - } - FunctionAttribute::BaseOrModifier(loc, base) => { - let is_contract_base = self.context.contract.as_ref().map_or(false, |contract| { - contract.base.iter().any(|contract_base| { - contract_base - .name - .identifiers - .iter() - .zip(&base.name.identifiers) - .all(|(l, r)| l.name == r.name) - }) - }); - - if is_contract_base { - base.visit(self)?; - } else { - let mut base_or_modifier = - self.visit_to_chunk(loc.start(), Some(loc.end()), base)?; - if base_or_modifier.content.ends_with("()") { - base_or_modifier - .content - .truncate(base_or_modifier.content.len() - 2); - } - self.write_chunk(&base_or_modifier)?; - } - } - FunctionAttribute::Error(loc) => self.visit_parser_error(*loc)?, - }; - - Ok(()) - } - - #[instrument(name = "base", skip_all)] - fn visit_base(&mut self, base: &mut Base) -> Result<()> { - return_source_if_disabled!(self, base.loc); - - let name_loc = &base.name.loc; - let mut name = self.chunked(name_loc.start(), Some(name_loc.end()), |fmt| { - fmt.visit_ident_path(&mut base.name)?; - Ok(()) - })?; - - if base.args.is_none() || base.args.as_ref().unwrap().is_empty() { - if self.context.function.is_some() { - name.content.push_str("()"); - } - self.write_chunk(&name)?; - return Ok(()); - } - - let args = base.args.as_mut().unwrap(); - let args_start = CodeLocation::loc(args.first().unwrap()).start(); - - name.content.push('('); - let formatted_name = self.chunk_to_string(&name)?; - - let multiline = !self.will_it_fit(&formatted_name); - - self.surrounded( - SurroundingChunk::new(&formatted_name, Some(args_start), None), - SurroundingChunk::new(")", None, Some(base.loc.end())), - |fmt, multiline_hint| { - let args = fmt.items_to_chunks( - Some(base.loc.end()), - args.iter_mut().map(|arg| (arg.loc(), arg)), - )?; - let multiline = multiline - || multiline_hint - || fmt.are_chunks_separated_multiline("{}", &args, ",")?; - fmt.write_chunks_separated(&args, ",", multiline)?; - Ok(()) - }, - )?; - - Ok(()) - } - - #[instrument(name = "parameter", skip_all)] - fn visit_parameter(&mut self, parameter: &mut Parameter) -> Result<()> { - return_source_if_disabled!(self, parameter.loc); - self.grouped(|fmt| { - parameter.ty.visit(fmt)?; - if let Some(storage) = ¶meter.storage { - write_chunk!(fmt, storage.loc().end(), "{storage}")?; - } - if let Some(name) = ¶meter.name { - write_chunk!(fmt, parameter.loc.end(), "{}", name.name)?; - } - Ok(()) - })?; - Ok(()) - } - - #[instrument(name = "struct", skip_all)] - fn visit_struct(&mut self, structure: &mut StructDefinition) -> Result<()> { - return_source_if_disabled!(self, structure.loc); - self.grouped(|fmt| { - let struct_name = structure.name.safe_unwrap_mut(); - write_chunk!(fmt, struct_name.loc.start(), "struct")?; - struct_name.visit(fmt)?; - if structure.fields.is_empty() { - return fmt.write_empty_brackets(); - } - - write!(fmt.buf(), " {{")?; - fmt.surrounded( - SurroundingChunk::new("", Some(struct_name.loc.end()), None), - SurroundingChunk::new("}", None, Some(structure.loc.end())), - |fmt, _multiline| { - let chunks = fmt.items_to_chunks( - Some(structure.loc.end()), - structure.fields.iter_mut().map(|ident| (ident.loc, ident)), - )?; - for mut chunk in chunks { - chunk.content.push(';'); - fmt.write_chunk(&chunk)?; - fmt.write_whitespace_separator(true)?; - } - Ok(()) - }, - ) - })?; - - Ok(()) - } - - #[instrument(name = "type_definition", skip_all)] - fn visit_type_definition(&mut self, def: &mut TypeDefinition) -> Result<()> { - return_source_if_disabled!(self, def.loc, ';'); - self.grouped(|fmt| { - write_chunk!(fmt, def.loc.start(), def.name.loc.start(), "type")?; - def.name.visit(fmt)?; - write_chunk!( - fmt, - def.name.loc.end(), - CodeLocation::loc(&def.ty).start(), - "is" - )?; - def.ty.visit(fmt)?; - fmt.write_semicolon()?; - Ok(()) - })?; - Ok(()) - } - - #[instrument(name = "stray_semicolon", skip_all)] - fn visit_stray_semicolon(&mut self) -> Result<()> { - self.write_semicolon() - } - - #[instrument(name = "block", skip_all)] - fn visit_block( - &mut self, - loc: Loc, - unchecked: bool, - statements: &mut Vec, - ) -> Result<()> { - return_source_if_disabled!(self, loc); - if unchecked { - write_chunk!(self, loc.start(), "unchecked ")?; - } - - self.visit_block(loc, statements, false, false)?; - Ok(()) - } - - #[instrument(name = "opening_paren", skip_all)] - fn visit_opening_paren(&mut self) -> Result<()> { - write_chunk!(self, "(")?; - Ok(()) - } - - #[instrument(name = "closing_paren", skip_all)] - fn visit_closing_paren(&mut self) -> Result<()> { - write_chunk!(self, ")")?; - Ok(()) - } - - #[instrument(name = "newline", skip_all)] - fn visit_newline(&mut self) -> Result<()> { - writeln_chunk!(self)?; - Ok(()) - } - - #[instrument(name = "event", skip_all)] - fn visit_event(&mut self, event: &mut EventDefinition) -> Result<()> { - return_source_if_disabled!(self, event.loc, ';'); - - let event_name = event.name.safe_unwrap_mut(); - let mut name = - self.visit_to_chunk(event_name.loc.start(), Some(event.loc.end()), event_name)?; - name.content = format!("event {}(", name.content); - - let last_chunk = if event.anonymous { - ") anonymous;" - } else { - ");" - }; - if event.fields.is_empty() { - name.content.push_str(last_chunk); - self.write_chunk(&name)?; - } else { - let byte_offset = event.fields.first().unwrap().loc.start(); - let first_chunk = self.chunk_to_string(&name)?; - self.surrounded( - SurroundingChunk::new(first_chunk, Some(byte_offset), None), - SurroundingChunk::new(last_chunk, None, Some(event.loc.end())), - |fmt, multiline| { - let params = fmt - .items_to_chunks(None, event.fields.iter_mut().map(|arg| (arg.loc, arg)))?; - - let multiline = - multiline && fmt.are_chunks_separated_multiline("{}", ¶ms, ",")?; - fmt.write_chunks_separated(¶ms, ",", multiline) - }, - )?; - } - - Ok(()) - } - - #[instrument(name = "event_parameter", skip_all)] - fn visit_event_parameter(&mut self, param: &mut EventParameter) -> Result<()> { - return_source_if_disabled!(self, param.loc); - - self.grouped(|fmt| { - param.ty.visit(fmt)?; - if param.indexed { - write_chunk!(fmt, param.loc.start(), "indexed")?; - } - if let Some(name) = ¶m.name { - write_chunk!(fmt, name.loc.end(), "{}", name.name)?; - } - Ok(()) - })?; - Ok(()) - } - - #[instrument(name = "error", skip_all)] - fn visit_error(&mut self, error: &mut ErrorDefinition) -> Result<()> { - return_source_if_disabled!(self, error.loc, ';'); - - let error_name = error.name.safe_unwrap_mut(); - let mut name = self.visit_to_chunk(error_name.loc.start(), None, error_name)?; - name.content = format!("error {}", name.content); - - let formatted_name = self.chunk_to_string(&name)?; - write!(self.buf(), "{formatted_name}")?; - let start_offset = error.fields.first().map(|f| f.loc.start()); - self.visit_list( - "", - &mut error.fields, - start_offset, - Some(error.loc.end()), - true, - )?; - self.write_semicolon()?; - - Ok(()) - } - - #[instrument(name = "error_parameter", skip_all)] - fn visit_error_parameter(&mut self, param: &mut ErrorParameter) -> Result<()> { - return_source_if_disabled!(self, param.loc); - self.grouped(|fmt| { - param.ty.visit(fmt)?; - if let Some(name) = ¶m.name { - write_chunk!(fmt, name.loc.end(), "{}", name.name)?; - } - Ok(()) - })?; - Ok(()) - } - - #[instrument(name = "using", skip_all)] - fn visit_using(&mut self, using: &mut Using) -> Result<()> { - return_source_if_disabled!(self, using.loc, ';'); - - write_chunk!(self, using.loc.start(), "using")?; - - let ty_start = using.ty.as_mut().map(|ty| CodeLocation::loc(&ty).start()); - let global_start = using.global.as_mut().map(|global| global.loc.start()); - let loc_end = using.loc.end(); - - let (is_library, mut list_chunks) = match &mut using.list { - UsingList::Library(library) => ( - true, - vec![self.visit_to_chunk(library.loc.start(), None, library)?], - ), - UsingList::Functions(funcs) => { - let mut funcs = funcs.iter_mut().peekable(); - let mut chunks = Vec::new(); - while let Some(func) = funcs.next() { - let next_byte_end = funcs.peek().map(|func| func.loc.start()); - chunks.push(self.chunked(func.loc.start(), next_byte_end, |fmt| { - fmt.visit_ident_path(&mut func.path)?; - if let Some(op) = func.oper { - write!(fmt.buf(), " as {op}")?; - } - Ok(()) - })?); - } - (false, chunks) - } - UsingList::Error => return self.visit_parser_error(using.loc), - }; - - let for_chunk = self.chunk_at( - using.loc.start(), - Some(ty_start.or(global_start).unwrap_or(loc_end)), - None, - "for", - ); - let ty_chunk = if let Some(ty) = &mut using.ty { - self.visit_to_chunk(ty.loc().start(), Some(global_start.unwrap_or(loc_end)), ty)? - } else { - self.chunk_at( - using.loc.start(), - Some(global_start.unwrap_or(loc_end)), - None, - "*", - ) - }; - let global_chunk = using - .global - .as_mut() - .map(|global| self.visit_to_chunk(global.loc.start(), Some(using.loc.end()), global)) - .transpose()?; - - let write_for_def = |fmt: &mut Self| { - fmt.grouped(|fmt| { - fmt.write_chunk(&for_chunk)?; - fmt.write_chunk(&ty_chunk)?; - if let Some(global_chunk) = global_chunk.as_ref() { - fmt.write_chunk(global_chunk)?; - } - Ok(()) - })?; - Ok(()) - }; - - let simulated_for_def = self.simulate_to_string(write_for_def)?; - - if is_library { - let chunk = list_chunks.pop().unwrap(); - if self.will_chunk_fit(&format!("{{}} {simulated_for_def};"), &chunk)? { - self.write_chunk(&chunk)?; - write_for_def(self)?; - } else { - self.write_whitespace_separator(true)?; - self.grouped(|fmt| { - fmt.write_chunk(&chunk)?; - Ok(()) - })?; - self.write_whitespace_separator(true)?; - write_for_def(self)?; - } - } else { - self.surrounded( - SurroundingChunk::new("{", Some(using.loc.start()), None), - SurroundingChunk::new( - "}", - None, - Some(ty_start.or(global_start).unwrap_or(loc_end)), - ), - |fmt, _multiline| { - let multiline = fmt.are_chunks_separated_multiline( - &format!("{{ {{}} }} {simulated_for_def};"), - &list_chunks, - ",", - )?; - fmt.write_chunks_separated(&list_chunks, ",", multiline)?; - Ok(()) - }, - )?; - write_for_def(self)?; - } - - self.write_semicolon()?; - - Ok(()) - } - - #[instrument(name = "var_attribute", skip_all)] - fn visit_var_attribute(&mut self, attribute: &mut VariableAttribute) -> Result<()> { - return_source_if_disabled!(self, attribute.loc()); - - let token = match attribute { - VariableAttribute::Visibility(visibility) => Some(visibility.to_string()), - VariableAttribute::Constant(_) => Some("constant".to_string()), - VariableAttribute::Immutable(_) => Some("immutable".to_string()), - VariableAttribute::Override(loc, idents) => { - write_chunk!(self, loc.start(), "override")?; - if !idents.is_empty() && self.config.override_spacing { - self.write_whitespace_separator(false)?; - } - self.visit_list("", idents, Some(loc.start()), Some(loc.end()), false)?; - None - } - }; - if let Some(token) = token { - let loc = attribute.loc(); - write_chunk!(self, loc.start(), loc.end(), "{}", token)?; - } - Ok(()) - } - - #[instrument(name = "var_definition", skip_all)] - fn visit_var_definition(&mut self, var: &mut VariableDefinition) -> Result<()> { - return_source_if_disabled!(self, var.loc, ';'); - - var.ty.visit(self)?; - - let multiline = self.grouped(|fmt| { - let var_name = var.name.safe_unwrap_mut(); - let name_start = var_name.loc.start(); - - let attrs = fmt.items_to_chunks_sorted(Some(name_start), var.attrs.iter_mut())?; - if !fmt.try_on_single_line(|fmt| fmt.write_chunks_separated(&attrs, "", false))? { - fmt.write_chunks_separated(&attrs, "", true)?; - } - - let mut name = fmt.visit_to_chunk(name_start, Some(var_name.loc.end()), var_name)?; - if var.initializer.is_some() { - name.content.push_str(" ="); - } - fmt.write_chunk(&name)?; - - Ok(()) - })?; - - var.initializer - .as_mut() - .map(|init| self.indented_if(multiline, 1, |fmt| fmt.visit_assignment(init))) - .transpose()?; - - self.write_semicolon()?; - - Ok(()) - } - - #[instrument(name = "var_definition_stmt", skip_all)] - fn visit_var_definition_stmt( - &mut self, - loc: Loc, - declaration: &mut VariableDeclaration, - expr: &mut Option, - ) -> Result<()> { - return_source_if_disabled!(self, loc, ';'); - - let declaration = self.chunked(declaration.loc.start(), None, |fmt| { - fmt.visit_var_declaration(declaration) - })?; - let multiline = declaration.content.contains('\n'); - self.write_chunk(&declaration)?; - - if let Some(expr) = expr { - write!(self.buf(), " =")?; - self.indented_if(multiline, 1, |fmt| fmt.visit_assignment(expr))?; - } - - self.write_semicolon() - } - - #[instrument(name = "for", skip_all)] - fn visit_for( - &mut self, - loc: Loc, - init: &mut Option>, - cond: &mut Option>, - update: &mut Option>, - body: &mut Option>, - ) -> Result<(), Self::Error> { - return_source_if_disabled!(self, loc); - - let next_byte_end = update.as_ref().map(|u| u.loc().end()); - self.surrounded( - SurroundingChunk::new("for (", Some(loc.start()), None), - SurroundingChunk::new(")", None, next_byte_end), - |fmt, _| { - let mut write_for_loop_header = |fmt: &mut Self, multiline: bool| -> Result<()> { - match init { - Some(stmt) => stmt.visit(fmt), - None => fmt.write_semicolon(), - }?; - if multiline { - fmt.write_whitespace_separator(true)?; - } - - cond.visit(fmt)?; - fmt.write_semicolon()?; - if multiline { - fmt.write_whitespace_separator(true)?; - } - - match update { - Some(expr) => expr.visit(fmt), - None => Ok(()), - } - }; - let multiline = !fmt.try_on_single_line(|fmt| write_for_loop_header(fmt, false))?; - if multiline { - write_for_loop_header(fmt, true)?; - } - Ok(()) - }, - )?; - match body { - Some(body) => { - self.visit_stmt_as_block(body, false)?; - } - None => { - self.write_empty_brackets()?; - } - }; - Ok(()) - } - - #[instrument(name = "while", skip_all)] - fn visit_while( - &mut self, - loc: Loc, - cond: &mut Expression, - body: &mut Statement, - ) -> Result<(), Self::Error> { - return_source_if_disabled!(self, loc); - self.surrounded( - SurroundingChunk::new("while (", Some(loc.start()), None), - SurroundingChunk::new(")", None, Some(cond.loc().end())), - |fmt, _| { - cond.visit(fmt)?; - fmt.write_postfix_comments_before(body.loc().start()) - }, - )?; - - let cond_close_paren_loc = self - .find_next_in_src(cond.loc().end(), ')') - .unwrap_or_else(|| cond.loc().end()); - let attempt_single_line = self.should_attempt_block_single_line(body, cond_close_paren_loc); - self.visit_stmt_as_block(body, attempt_single_line)?; - Ok(()) - } - - #[instrument(name = "do_while", skip_all)] - fn visit_do_while( - &mut self, - loc: Loc, - body: &mut Statement, - cond: &mut Expression, - ) -> Result<(), Self::Error> { - return_source_if_disabled!(self, loc, ';'); - write_chunk!(self, loc.start(), "do ")?; - self.visit_stmt_as_block(body, false)?; - visit_source_if_disabled_else!(self, loc.with_start(body.loc().end()), { - self.surrounded( - SurroundingChunk::new("while (", Some(cond.loc().start()), None), - SurroundingChunk::new(");", None, Some(loc.end())), - |fmt, _| cond.visit(fmt), - )?; - }); - Ok(()) - } - - #[instrument(name = "if", skip_all)] - fn visit_if( - &mut self, - loc: Loc, - cond: &mut Expression, - if_branch: &mut Box, - else_branch: &mut Option>, - is_first_stmt: bool, - ) -> Result<(), Self::Error> { - return_source_if_disabled!(self, loc); - - if !is_first_stmt { - self.write_if_stmt(loc, cond, if_branch, else_branch)?; - return Ok(()); - } - - self.context.if_stmt_single_line = Some(true); - let mut stmt_fits_on_single = false; - let tx = self.transact(|fmt| { - stmt_fits_on_single = match fmt.write_if_stmt(loc, cond, if_branch, else_branch) { - Ok(()) => true, - Err(FormatterError::Fmt(_)) => false, - Err(err) => bail!(err), - }; - Ok(()) - })?; - - if stmt_fits_on_single { - tx.commit()?; - } else { - self.context.if_stmt_single_line = Some(false); - self.write_if_stmt(loc, cond, if_branch, else_branch)?; - } - self.context.if_stmt_single_line = None; - - Ok(()) - } - - #[instrument(name = "args", skip_all)] - fn visit_args(&mut self, loc: Loc, args: &mut Vec) -> Result<(), Self::Error> { - return_source_if_disabled!(self, loc); - - write!(self.buf(), "{{")?; - - let mut args_iter = args.iter_mut().peekable(); - let mut chunks = Vec::new(); - while let Some(NamedArgument { - loc: arg_loc, - name, - expr, - }) = args_iter.next() - { - let next_byte_offset = args_iter - .peek() - .map(|NamedArgument { loc: arg_loc, .. }| arg_loc.start()) - .unwrap_or_else(|| loc.end()); - chunks.push( - self.chunked(arg_loc.start(), Some(next_byte_offset), |fmt| { - fmt.grouped(|fmt| { - write_chunk!(fmt, name.loc.start(), "{}: ", name.name)?; - expr.visit(fmt) - })?; - Ok(()) - })?, - ); - } - - if let Some(first) = chunks.first_mut() { - if first.prefixes.is_empty() - && first.postfixes_before.is_empty() - && !self.config.bracket_spacing - { - first.needs_space = Some(false); - } - } - let multiline = self.are_chunks_separated_multiline("{}}", &chunks, ",")?; - self.indented_if(multiline, 1, |fmt| { - fmt.write_chunks_separated(&chunks, ",", multiline) - })?; - - let prefix = if multiline && !self.is_beginning_of_line() { - "\n" - } else if self.config.bracket_spacing { - " " - } else { - "" - }; - let closing_bracket = format!("{prefix}{}", "}"); - let closing_bracket_loc = args.last().unwrap().loc.end(); - write_chunk!(self, closing_bracket_loc, "{closing_bracket}")?; - - Ok(()) - } - - #[instrument(name = "revert", skip_all)] - fn visit_revert( - &mut self, - loc: Loc, - error: &mut Option, - args: &mut Vec, - ) -> Result<(), Self::Error> { - return_source_if_disabled!(self, loc, ';'); - write_chunk!(self, loc.start(), "revert")?; - if let Some(error) = error { - error.visit(self)?; - } - self.visit_list("", args, None, Some(loc.end()), true)?; - self.write_semicolon()?; - - Ok(()) - } - - #[instrument(name = "revert_named_args", skip_all)] - fn visit_revert_named_args( - &mut self, - loc: Loc, - error: &mut Option, - args: &mut Vec, - ) -> Result<(), Self::Error> { - return_source_if_disabled!(self, loc, ';'); - - write_chunk!(self, loc.start(), "revert")?; - let mut error_indented = false; - if let Some(error) = error { - if !self.try_on_single_line(|fmt| error.visit(fmt))? { - error.visit(self)?; - error_indented = true; - } - } - - if args.is_empty() { - write!(self.buf(), "({{}});")?; - return Ok(()); - } - - write!(self.buf(), "(")?; - self.indented_if(error_indented, 1, |fmt| fmt.visit_args(loc, args))?; - write!(self.buf(), ")")?; - self.write_semicolon()?; - - Ok(()) - } - - #[instrument(name = "return", skip_all)] - fn visit_return(&mut self, loc: Loc, expr: &mut Option) -> Result<(), Self::Error> { - return_source_if_disabled!(self, loc, ';'); - - self.write_postfix_comments_before(loc.start())?; - self.write_prefix_comments_before(loc.start())?; - - if expr.is_none() { - write_chunk!(self, loc.end(), "return;")?; - return Ok(()); - } - - let expr = expr.as_mut().unwrap(); - let expr_loc_start = expr.loc().start(); - let write_return = |fmt: &mut Self| -> Result<()> { - write_chunk!(fmt, loc.start(), "return")?; - fmt.write_postfix_comments_before(expr_loc_start)?; - Ok(()) - }; - - let mut write_return_with_expr = |fmt: &mut Self| -> Result<()> { - let fits_on_single = fmt.try_on_single_line(|fmt| { - write_return(fmt)?; - expr.visit(fmt) - })?; - if fits_on_single { - return Ok(()); - } - - let mut fit_on_next_line = false; - let tx = fmt.transact(|fmt| { - fmt.grouped(|fmt| { - write_return(fmt)?; - if !fmt.is_beginning_of_line() { - fmt.write_whitespace_separator(true)?; - } - fit_on_next_line = fmt.try_on_single_line(|fmt| expr.visit(fmt))?; - Ok(()) - })?; - Ok(()) - })?; - if fit_on_next_line { - tx.commit()?; - return Ok(()); - } - - write_return(fmt)?; - expr.visit(fmt)?; - Ok(()) - }; - - write_return_with_expr(self)?; - write_chunk!(self, loc.end(), ";")?; - Ok(()) - } - - #[instrument(name = "try", skip_all)] - fn visit_try( - &mut self, - loc: Loc, - expr: &mut Expression, - returns: &mut Option<(Vec<(Loc, Option)>, Box)>, - clauses: &mut Vec, - ) -> Result<(), Self::Error> { - return_source_if_disabled!(self, loc); - - let try_next_byte = clauses.first().map(|c| match c { - CatchClause::Simple(loc, ..) => loc.start(), - CatchClause::Named(loc, ..) => loc.start(), - }); - let try_chunk = self.chunked(loc.start(), try_next_byte, |fmt| { - write_chunk!(fmt, loc.start(), expr.loc().start(), "try")?; - expr.visit(fmt)?; - if let Some((params, stmt)) = returns { - let mut params = params - .iter_mut() - .filter(|(_, param)| param.is_some()) - .collect::>(); - let byte_offset = params.first().map_or(stmt.loc().start(), |p| p.0.start()); - fmt.surrounded( - SurroundingChunk::new("returns (", Some(byte_offset), None), - SurroundingChunk::new(")", None, params.last().map(|p| p.0.end())), - |fmt, _| { - let chunks = fmt.items_to_chunks( - Some(stmt.loc().start()), - params.iter_mut().map(|(loc, ref mut ident)| (*loc, ident)), - )?; - let multiline = fmt.are_chunks_separated_multiline("{})", &chunks, ",")?; - fmt.write_chunks_separated(&chunks, ",", multiline)?; - Ok(()) - }, - )?; - stmt.visit(fmt)?; - } - Ok(()) - })?; - - let mut chunks = vec![try_chunk]; - for clause in clauses { - let (loc, ident, mut param, stmt) = match clause { - CatchClause::Simple(loc, param, stmt) => (loc, None, param.as_mut(), stmt), - CatchClause::Named(loc, ident, param, stmt) => { - (loc, Some(ident), Some(param), stmt) - } - }; - - let chunk = self.chunked(loc.start(), Some(stmt.loc().start()), |fmt| { - write_chunk!(fmt, "catch")?; - if let Some(ident) = ident.as_ref() { - fmt.write_postfix_comments_before( - param - .as_ref() - .map(|p| p.loc.start()) - .unwrap_or_else(|| ident.loc.end()), - )?; - write_chunk!(fmt, ident.loc.start(), "{}", ident.name)?; - } - if let Some(param) = param.as_mut() { - write_chunk_spaced!(fmt, param.loc.start(), Some(ident.is_none()), "(")?; - fmt.surrounded( - SurroundingChunk::new("", Some(param.loc.start()), None), - SurroundingChunk::new(")", None, Some(stmt.loc().start())), - |fmt, _| param.visit(fmt), - )?; - } - - stmt.visit(fmt)?; - Ok(()) - })?; - - chunks.push(chunk); - } - - let multiline = self.are_chunks_separated_multiline("{}", &chunks, "")?; - if !multiline { - self.write_chunks_separated(&chunks, "", false)?; - return Ok(()); - } - - let mut chunks = chunks.iter_mut().peekable(); - let mut prev_multiline = false; - - // write try chunk first - if let Some(chunk) = chunks.next() { - let chunk_str = self.simulate_to_string(|fmt| fmt.write_chunk(chunk))?; - write!(self.buf(), "{chunk_str}")?; - prev_multiline = chunk_str.contains('\n'); - } - - while let Some(chunk) = chunks.next() { - let chunk_str = self.simulate_to_string(|fmt| fmt.write_chunk(chunk))?; - let multiline = chunk_str.contains('\n'); - self.indented_if(!multiline, 1, |fmt| { - chunk.needs_space = Some(false); - let on_same_line = prev_multiline && (multiline || chunks.peek().is_none()); - let prefix = if fmt.is_beginning_of_line() { - "" - } else if on_same_line { - " " - } else { - "\n" - }; - let chunk_str = format!("{prefix}{chunk_str}"); - write!(fmt.buf(), "{chunk_str}")?; - Ok(()) - })?; - prev_multiline = multiline; - } - Ok(()) - } - - #[instrument(name = "assembly", skip_all)] - fn visit_assembly( - &mut self, - loc: Loc, - dialect: &mut Option, - block: &mut YulBlock, - flags: &mut Option>, - ) -> Result<(), Self::Error> { - return_source_if_disabled!(self, loc); - - write_chunk!(self, loc.start(), "assembly")?; - if let Some(StringLiteral { loc, string, .. }) = dialect { - write_chunk!(self, loc.start(), loc.end(), "\"{string}\"")?; - } - if let Some(flags) = flags { - if !flags.is_empty() { - let loc_start = flags.first().unwrap().loc.start(); - self.surrounded( - SurroundingChunk::new("(", Some(loc_start), None), - SurroundingChunk::new(")", None, Some(block.loc.start())), - |fmt, _| { - let mut flags = flags.iter_mut().peekable(); - let mut chunks = vec![]; - while let Some(flag) = flags.next() { - let next_byte_offset = - flags.peek().map(|next_flag| next_flag.loc.start()); - chunks.push(fmt.chunked( - flag.loc.start(), - next_byte_offset, - |fmt| { - write!(fmt.buf(), "\"{}\"", flag.string)?; - Ok(()) - }, - )?); - } - fmt.write_chunks_separated(&chunks, ",", false)?; - Ok(()) - }, - )?; - } - } - - block.visit(self) - } - - #[instrument(name = "yul_block", skip_all)] - fn visit_yul_block( - &mut self, - loc: Loc, - statements: &mut Vec, - attempt_single_line: bool, - ) -> Result<(), Self::Error> { - return_source_if_disabled!(self, loc); - self.visit_block(loc, statements, attempt_single_line, false)?; - Ok(()) - } - - #[instrument(name = "yul_assignment", skip_all)] - fn visit_yul_assignment( - &mut self, - loc: Loc, - exprs: &mut Vec, - expr: &mut Option<&mut YulExpression>, - ) -> Result<(), Self::Error> - where - T: Visitable + CodeLocation, - { - return_source_if_disabled!(self, loc); - - self.grouped(|fmt| { - let chunks = - fmt.items_to_chunks(None, exprs.iter_mut().map(|expr| (expr.loc(), expr)))?; - - let multiline = fmt.are_chunks_separated_multiline("{} := ", &chunks, ",")?; - fmt.write_chunks_separated(&chunks, ",", multiline)?; - - if let Some(expr) = expr { - write_chunk!(fmt, expr.loc().start(), ":=")?; - let chunk = fmt.visit_to_chunk(expr.loc().start(), Some(loc.end()), expr)?; - if !fmt.will_chunk_fit("{}", &chunk)? { - fmt.write_whitespace_separator(true)?; - } - fmt.write_chunk(&chunk)?; - } - Ok(()) - })?; - Ok(()) - } - - #[instrument(name = "yul_expr", skip_all)] - fn visit_yul_expr(&mut self, expr: &mut YulExpression) -> Result<(), Self::Error> { - return_source_if_disabled!(self, expr.loc()); - - match expr { - YulExpression::BoolLiteral(loc, val, ident) => { - let val = if *val { "true" } else { "false" }; - self.visit_yul_string_with_ident(*loc, val, ident) - } - YulExpression::FunctionCall(expr) => self.visit_yul_function_call(expr), - YulExpression::HexNumberLiteral(loc, val, ident) => { - self.visit_yul_string_with_ident(*loc, val, ident) - } - YulExpression::HexStringLiteral(val, ident) => self.visit_yul_string_with_ident( - val.loc, - &self.quote_str(val.loc, Some("hex"), &val.hex), - ident, - ), - YulExpression::NumberLiteral(loc, val, expr, ident) => { - let val = if expr.is_empty() { - val.to_owned() - } else { - format!("{val}e{expr}") - }; - self.visit_yul_string_with_ident(*loc, &val, ident) - } - YulExpression::StringLiteral(val, ident) => self.visit_yul_string_with_ident( - val.loc, - &self.quote_str(val.loc, None, &val.string), - ident, - ), - YulExpression::SuffixAccess(_, expr, ident) => { - self.visit_member_access(expr, ident, |fmt, expr| match expr.as_mut() { - YulExpression::SuffixAccess(_, inner_expr, inner_ident) => { - Ok(Some((inner_expr, inner_ident))) - } - expr => { - expr.visit(fmt)?; - Ok(None) - } - }) - } - YulExpression::Variable(ident) => { - write_chunk!(self, ident.loc.start(), ident.loc.end(), "{}", ident.name) - } - } - } - - #[instrument(name = "yul_for", skip_all)] - fn visit_yul_for(&mut self, stmt: &mut YulFor) -> Result<(), Self::Error> { - return_source_if_disabled!(self, stmt.loc); - write_chunk!(self, stmt.loc.start(), "for")?; - self.visit_yul_block(stmt.init_block.loc, &mut stmt.init_block.statements, true)?; - stmt.condition.visit(self)?; - self.visit_yul_block(stmt.post_block.loc, &mut stmt.post_block.statements, true)?; - self.visit_yul_block( - stmt.execution_block.loc, - &mut stmt.execution_block.statements, - true, - )?; - Ok(()) - } - - #[instrument(name = "yul_function_call", skip_all)] - fn visit_yul_function_call(&mut self, stmt: &mut YulFunctionCall) -> Result<(), Self::Error> { - return_source_if_disabled!(self, stmt.loc); - write_chunk!(self, stmt.loc.start(), "{}", stmt.id.name)?; - self.visit_list("", &mut stmt.arguments, None, Some(stmt.loc.end()), true) - } - - #[instrument(name = "yul_typed_ident", skip_all)] - fn visit_yul_typed_ident(&mut self, ident: &mut YulTypedIdentifier) -> Result<(), Self::Error> { - return_source_if_disabled!(self, ident.loc); - self.visit_yul_string_with_ident(ident.loc, &ident.id.name, &mut ident.ty) - } - - #[instrument(name = "yul_fun_def", skip_all)] - fn visit_yul_fun_def(&mut self, stmt: &mut YulFunctionDefinition) -> Result<(), Self::Error> { - return_source_if_disabled!(self, stmt.loc); - - write_chunk!(self, stmt.loc.start(), "function {}", stmt.id.name)?; - - self.visit_list("", &mut stmt.params, None, None, true)?; - - if !stmt.returns.is_empty() { - self.grouped(|fmt| { - write_chunk!(fmt, "->")?; - - let chunks = fmt.items_to_chunks( - Some(stmt.body.loc.start()), - stmt.returns.iter_mut().map(|param| (param.loc, param)), - )?; - let multiline = fmt.are_chunks_separated_multiline("{}", &chunks, ",")?; - fmt.write_chunks_separated(&chunks, ",", multiline)?; - if multiline { - fmt.write_whitespace_separator(true)?; - } - Ok(()) - })?; - } - - stmt.body.visit(self)?; - - Ok(()) - } - - #[instrument(name = "yul_if", skip_all)] - fn visit_yul_if( - &mut self, - loc: Loc, - expr: &mut YulExpression, - block: &mut YulBlock, - ) -> Result<(), Self::Error> { - return_source_if_disabled!(self, loc); - write_chunk!(self, loc.start(), "if")?; - expr.visit(self)?; - self.visit_yul_block(block.loc, &mut block.statements, true) - } - - #[instrument(name = "yul_leave", skip_all)] - fn visit_yul_leave(&mut self, loc: Loc) -> Result<(), Self::Error> { - return_source_if_disabled!(self, loc); - write_chunk!(self, loc.start(), loc.end(), "leave") - } - - #[instrument(name = "yul_switch", skip_all)] - fn visit_yul_switch(&mut self, stmt: &mut YulSwitch) -> Result<(), Self::Error> { - return_source_if_disabled!(self, stmt.loc); - - write_chunk!(self, stmt.loc.start(), "switch")?; - stmt.condition.visit(self)?; - writeln_chunk!(self)?; - let mut cases = stmt.cases.iter_mut().peekable(); - while let Some(YulSwitchOptions::Case(loc, expr, block)) = cases.next() { - write_chunk!(self, loc.start(), "case")?; - expr.visit(self)?; - self.visit_yul_block(block.loc, &mut block.statements, true)?; - let is_last = cases.peek().is_none(); - if !is_last || stmt.default.is_some() { - writeln_chunk!(self)?; - } - } - if let Some(YulSwitchOptions::Default(loc, ref mut block)) = stmt.default { - write_chunk!(self, loc.start(), "default")?; - self.visit_yul_block(block.loc, &mut block.statements, true)?; - } - Ok(()) - } - - #[instrument(name = "yul_var_declaration", skip_all)] - fn visit_yul_var_declaration( - &mut self, - loc: Loc, - idents: &mut Vec, - expr: &mut Option, - ) -> Result<(), Self::Error> { - return_source_if_disabled!(self, loc); - self.grouped(|fmt| { - write_chunk!(fmt, loc.start(), "let")?; - fmt.visit_yul_assignment(loc, idents, &mut expr.as_mut()) - })?; - Ok(()) - } - - // Support extension for Solana/Substrate - #[instrument(name = "annotation", skip_all)] - fn visit_annotation(&mut self, annotation: &mut Annotation) -> Result<()> { - return_source_if_disabled!(self, annotation.loc); - let id = self.simulate_to_string(|fmt| annotation.id.visit(fmt))?; - write!(self.buf(), "@{id}")?; - write!(self.buf(), "(")?; - annotation.value.visit(self)?; - write!(self.buf(), ")")?; - Ok(()) - } - - #[instrument(name = "parser_error", skip_all)] - fn visit_parser_error(&mut self, loc: Loc) -> Result<()> { - Err(FormatterError::InvalidParsedItem(loc)) - } -} diff --git a/forge-fmt/src/helpers.rs b/forge-fmt/src/helpers.rs deleted file mode 100644 index ac021d033..000000000 --- a/forge-fmt/src/helpers.rs +++ /dev/null @@ -1,121 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -use crate::{ - inline_config::{InlineConfig, InvalidInlineConfigItem}, - Comments, Formatter, FormatterConfig, FormatterError, Visitable, -}; -use ariadne::{Color, Fmt, Label, Report, ReportKind, Source}; -use itertools::Itertools; -use solang_parser::{diagnostics::Diagnostic, pt::*}; -use std::{fmt::Write, path::Path}; - -/// Result of parsing the source code -#[derive(Debug)] -pub struct Parsed<'a> { - /// The original source code - pub src: &'a str, - /// The Parse Tree via [`solang`] - pub pt: SourceUnit, - /// Parsed comments - pub comments: Comments, - /// Parsed inline config - pub inline_config: InlineConfig, - /// Invalid inline config items parsed - pub invalid_inline_config_items: Vec<(Loc, InvalidInlineConfigItem)>, -} - -/// Parse source code -pub fn parse(src: &str) -> Result> { - let (pt, comments) = solang_parser::parse(src, 0)?; - let comments = Comments::new(comments, src); - let (inline_config_items, invalid_inline_config_items): (Vec<_>, Vec<_>) = - comments.parse_inline_config_items().partition_result(); - let inline_config = InlineConfig::new(inline_config_items, src); - Ok(Parsed { - src, - pt, - comments, - inline_config, - invalid_inline_config_items, - }) -} - -/// Format parsed code -pub fn format( - writer: W, - mut parsed: Parsed, - config: FormatterConfig, -) -> Result<(), FormatterError> { - trace!(?parsed, ?config, "Formatting"); - let mut formatter = Formatter::new( - writer, - parsed.src, - parsed.comments, - parsed.inline_config, - config, - ); - parsed.pt.visit(&mut formatter) -} - -/// Parse and format a string with default settings -pub fn fmt(src: &str) -> Result { - let parsed = parse(src).map_err(|_| FormatterError::Fmt(std::fmt::Error))?; - - let mut output = String::new(); - format(&mut output, parsed, FormatterConfig::default())?; - - Ok(output) -} - -/// Converts the start offset of a `Loc` to `(line, col)` -pub fn offset_to_line_column(content: &str, start: usize) -> (usize, usize) { - debug_assert!(content.len() > start); - - // first line is `1` - let mut line_counter = 1; - for (offset, c) in content.chars().enumerate() { - if c == '\n' { - line_counter += 1; - } - if offset > start { - return (line_counter, offset - start); - } - } - - unreachable!("content.len() > start") -} - -/// Print the report of parser's diagnostics -pub fn print_diagnostics_report( - content: &str, - path: Option<&Path>, - diagnostics: Vec, -) -> std::io::Result<()> { - let filename = path - .map(|p| p.file_name().unwrap().to_string_lossy().to_string()) - .unwrap_or_default(); - for diag in diagnostics { - let (start, end) = (diag.loc.start(), diag.loc.end()); - let mut report = Report::build(ReportKind::Error, &filename, start) - .with_message(format!("{:?}", diag.ty)) - .with_label( - Label::new((&filename, start..end)) - .with_color(Color::Red) - .with_message(format!("{}", diag.message.fg(Color::Red))), - ); - - for note in diag.notes { - report = report.with_note(note.message); - } - - report.finish().print((&filename, Source::from(content)))?; - } - Ok(()) -} - -pub fn import_path_string(path: &ImportPath) -> String { - match path { - ImportPath::Filename(s) => s.string.clone(), - ImportPath::Path(p) => p.to_string(), - } -} diff --git a/forge-fmt/src/inline_config.rs b/forge-fmt/src/inline_config.rs deleted file mode 100644 index e88d873d3..000000000 --- a/forge-fmt/src/inline_config.rs +++ /dev/null @@ -1,177 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -use crate::comments::{CommentState, CommentStringExt}; -use itertools::Itertools; -use solang_parser::pt::Loc; -use std::{fmt, str::FromStr}; - -/// An inline config item -#[allow(clippy::enum_variant_names)] -#[derive(Debug, Clone, Copy)] -pub enum InlineConfigItem { - /// Disables the next code item regardless of newlines - DisableNextItem, - /// Disables formatting on the current line - DisableLine, - /// Disables formatting between the next newline and the newline after - DisableNextLine, - /// Disables formatting for any code that follows this and before the next "disable-end" - DisableStart, - /// Disables formatting for any code that precedes this and after the previous "disable-start" - DisableEnd, -} - -impl FromStr for InlineConfigItem { - type Err = InvalidInlineConfigItem; - fn from_str(s: &str) -> Result { - Ok(match s { - "disable-next-item" => InlineConfigItem::DisableNextItem, - "disable-line" => InlineConfigItem::DisableLine, - "disable-next-line" => InlineConfigItem::DisableNextLine, - "disable-start" => InlineConfigItem::DisableStart, - "disable-end" => InlineConfigItem::DisableEnd, - s => return Err(InvalidInlineConfigItem(s.into())), - }) - } -} - -#[derive(Debug)] -pub struct InvalidInlineConfigItem(String); - -impl fmt::Display for InvalidInlineConfigItem { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_fmt(format_args!("Invalid inline config item: {}", self.0)) - } -} - -/// A disabled formatting range. `loose` designates that the range includes any loc which -/// may start in between start and end, whereas the strict version requires that -/// `range.start >= loc.start <=> loc.end <= range.end` -#[derive(Debug)] -struct DisabledRange { - start: usize, - end: usize, - loose: bool, -} - -impl DisabledRange { - fn includes(&self, loc: Loc) -> bool { - loc.start() >= self.start && (if self.loose { loc.start() } else { loc.end() } <= self.end) - } -} - -/// An inline config. Keeps track of disabled ranges. -/// -/// This is a list of Inline Config items for locations in a source file. This is -/// usually acquired by parsing the comments for an `forgefmt:` items. See -/// [`Comments::parse_inline_config_items`] for details. -#[derive(Default, Debug)] -pub struct InlineConfig { - disabled_ranges: Vec, -} - -impl InlineConfig { - /// Build a new inline config with an iterator of inline config items and their locations in a - /// source file - pub fn new(items: impl IntoIterator, src: &str) -> Self { - let mut disabled_ranges = vec![]; - let mut disabled_range_start = None; - let mut disabled_depth = 0usize; - for (loc, item) in items.into_iter().sorted_by_key(|(loc, _)| loc.start()) { - match item { - InlineConfigItem::DisableNextItem => { - let offset = loc.end(); - let mut char_indices = src[offset..] - .comment_state_char_indices() - .filter_map(|(state, idx, ch)| match state { - CommentState::None => Some((idx, ch)), - _ => None, - }) - .skip_while(|(_, ch)| ch.is_whitespace()); - if let Some((mut start, _)) = char_indices.next() { - start += offset; - let end = char_indices - .find(|(_, ch)| !ch.is_whitespace()) - .map(|(idx, _)| offset + idx) - .unwrap_or(src.len()); - disabled_ranges.push(DisabledRange { - start, - end, - loose: true, - }); - } - } - InlineConfigItem::DisableLine => { - let mut prev_newline = src[..loc.start()] - .char_indices() - .rev() - .skip_while(|(_, ch)| *ch != '\n'); - let start = prev_newline.next().map(|(idx, _)| idx).unwrap_or_default(); - - let end_offset = loc.end(); - let mut next_newline = src[end_offset..] - .char_indices() - .skip_while(|(_, ch)| *ch != '\n'); - let end = - end_offset + next_newline.next().map(|(idx, _)| idx).unwrap_or_default(); - - disabled_ranges.push(DisabledRange { - start, - end, - loose: false, - }); - } - InlineConfigItem::DisableNextLine => { - let offset = loc.end(); - let mut char_indices = src[offset..] - .char_indices() - .skip_while(|(_, ch)| *ch != '\n') - .skip(1); - if let Some((mut start, _)) = char_indices.next() { - start += offset; - let end = char_indices - .find(|(_, ch)| *ch == '\n') - .map(|(idx, _)| offset + idx + 1) - .unwrap_or(src.len()); - disabled_ranges.push(DisabledRange { - start, - end, - loose: false, - }); - } - } - InlineConfigItem::DisableStart => { - if disabled_depth == 0 { - disabled_range_start = Some(loc.end()); - } - disabled_depth += 1; - } - InlineConfigItem::DisableEnd => { - disabled_depth = disabled_depth.saturating_sub(1); - if disabled_depth == 0 { - if let Some(start) = disabled_range_start.take() { - disabled_ranges.push(DisabledRange { - start, - end: loc.start(), - loose: false, - }) - } - } - } - } - } - if let Some(start) = disabled_range_start.take() { - disabled_ranges.push(DisabledRange { - start, - end: src.len(), - loose: false, - }) - } - Self { disabled_ranges } - } - - /// Check if the location is in a disabled range - pub fn is_disabled(&self, loc: Loc) -> bool { - self.disabled_ranges.iter().any(|range| range.includes(loc)) - } -} diff --git a/forge-fmt/src/lib.rs b/forge-fmt/src/lib.rs deleted file mode 100644 index 729ca9796..000000000 --- a/forge-fmt/src/lib.rs +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -#![doc = include_str!("../README.md")] -#![cfg_attr(not(test), warn(unused_crate_dependencies))] - -#[macro_use] -extern crate tracing; - -mod buffer; -pub mod chunk; -mod comments; -pub mod config; -mod formatter; -mod helpers; -pub mod inline_config; -mod macros; -pub mod solang_ext; -mod string; -pub mod visit; - -pub use comments::Comments; -pub use config::*; -pub use formatter::{Formatter, FormatterError}; -pub use helpers::{fmt, format, offset_to_line_column, parse, print_diagnostics_report, Parsed}; -pub use inline_config::InlineConfig; -pub use visit::{Visitable, Visitor}; diff --git a/forge-fmt/src/macros.rs b/forge-fmt/src/macros.rs deleted file mode 100644 index c7ca59efd..000000000 --- a/forge-fmt/src/macros.rs +++ /dev/null @@ -1,127 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -macro_rules! write_chunk { - ($self:expr, $format_str:literal) => {{ - write_chunk!($self, $format_str,) - }}; - ($self:expr, $format_str:literal, $($arg:tt)*) => {{ - $self.write_chunk(&format!($format_str, $($arg)*).into()) - }}; - ($self:expr, $loc:expr) => {{ - write_chunk!($self, $loc, "") - }}; - ($self:expr, $loc:expr, $format_str:literal) => {{ - write_chunk!($self, $loc, $format_str,) - }}; - ($self:expr, $loc:expr, $format_str:literal, $($arg:tt)*) => {{ - let chunk = $self.chunk_at($loc, None, None, format_args!($format_str, $($arg)*),); - $self.write_chunk(&chunk) - }}; - ($self:expr, $loc:expr, $end_loc:expr, $format_str:literal) => {{ - write_chunk!($self, $loc, $end_loc, $format_str,) - }}; - ($self:expr, $loc:expr, $end_loc:expr, $format_str:literal, $($arg:tt)*) => {{ - let chunk = $self.chunk_at($loc, Some($end_loc), None, format_args!($format_str, $($arg)*),); - $self.write_chunk(&chunk) - }}; - ($self:expr, $loc:expr, $end_loc:expr, $needs_space:expr, $format_str:literal, $($arg:tt)*) => {{ - let chunk = $self.chunk_at($loc, Some($end_loc), Some($needs_space), format_args!($format_str, $($arg)*),); - $self.write_chunk(&chunk) - }}; -} - -macro_rules! writeln_chunk { - ($self:expr) => {{ - writeln_chunk!($self, "") - }}; - ($self:expr, $format_str:literal) => {{ - writeln_chunk!($self, $format_str,) - }}; - ($self:expr, $format_str:literal, $($arg:tt)*) => {{ - write_chunk!($self, "{}\n", format_args!($format_str, $($arg)*)) - }}; - ($self:expr, $loc:expr) => {{ - writeln_chunk!($self, $loc, "") - }}; - ($self:expr, $loc:expr, $format_str:literal) => {{ - writeln_chunk!($self, $loc, $format_str,) - }}; - ($self:expr, $loc:expr, $format_str:literal, $($arg:tt)*) => {{ - write_chunk!($self, $loc, "{}\n", format_args!($format_str, $($arg)*)) - }}; - ($self:expr, $loc:expr, $end_loc:expr, $format_str:literal) => {{ - writeln_chunk!($self, $loc, $end_loc, $format_str,) - }}; - ($self:expr, $loc:expr, $end_loc:expr, $format_str:literal, $($arg:tt)*) => {{ - write_chunk!($self, $loc, $end_loc, "{}\n", format_args!($format_str, $($arg)*)) - }}; -} - -macro_rules! write_chunk_spaced { - ($self:expr, $loc:expr, $needs_space:expr, $format_str:literal) => {{ - write_chunk_spaced!($self, $loc, $needs_space, $format_str,) - }}; - ($self:expr, $loc:expr, $needs_space:expr, $format_str:literal, $($arg:tt)*) => {{ - let chunk = $self.chunk_at($loc, None, $needs_space, format_args!($format_str, $($arg)*),); - $self.write_chunk(&chunk) - }}; -} - -macro_rules! buf_fn { - ($vis:vis fn $name:ident(&self $(,)? $($arg_name:ident : $arg_ty:ty),*) $(-> $ret:ty)?) => { - $vis fn $name(&self, $($arg_name : $arg_ty),*) $(-> $ret)? { - if self.temp_bufs.is_empty() { - self.buf.$name($($arg_name),*) - } else { - self.temp_bufs.last().unwrap().$name($($arg_name),*) - } - } - }; - ($vis:vis fn $name:ident(&mut self $(,)? $($arg_name:ident : $arg_ty:ty),*) $(-> $ret:ty)?) => { - $vis fn $name(&mut self, $($arg_name : $arg_ty),*) $(-> $ret)? { - if self.temp_bufs.is_empty() { - self.buf.$name($($arg_name),*) - } else { - self.temp_bufs.last_mut().unwrap().$name($($arg_name),*) - } - } - }; -} - -macro_rules! return_source_if_disabled { - ($self:expr, $loc:expr) => {{ - let loc = $loc; - if $self.inline_config.is_disabled(loc) { - trace!("Returning because disabled: {loc:?}"); - return $self.visit_source(loc); - } - }}; - ($self:expr, $loc:expr, $suffix:literal) => {{ - let mut loc = $loc; - let has_suffix = $self.extend_loc_until(&mut loc, $suffix); - if $self.inline_config.is_disabled(loc) { - $self.visit_source(loc)?; - trace!("Returning because disabled: {loc:?}"); - if !has_suffix { - write!($self.buf(), "{}", $suffix)?; - } - return Ok(()); - } - }}; -} - -macro_rules! visit_source_if_disabled_else { - ($self:expr, $loc:expr, $block:block) => {{ - let loc = $loc; - if $self.inline_config.is_disabled(loc) { - $self.visit_source(loc)?; - } else $block - }}; -} - -pub(crate) use buf_fn; -pub(crate) use return_source_if_disabled; -pub(crate) use visit_source_if_disabled_else; -pub(crate) use write_chunk; -pub(crate) use write_chunk_spaced; -pub(crate) use writeln_chunk; diff --git a/forge-fmt/src/solang_ext/ast_eq.rs b/forge-fmt/src/solang_ext/ast_eq.rs deleted file mode 100644 index cd5b20253..000000000 --- a/forge-fmt/src/solang_ext/ast_eq.rs +++ /dev/null @@ -1,703 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -use ethers_core::types::{H160, I256, U256}; -use solang_parser::pt::*; -use std::str::FromStr; - -/// Helper to convert a string number into a comparable one -fn to_num(string: &str) -> I256 { - if string.is_empty() { - return I256::from(0); - } - string.replace('_', "").trim().parse().unwrap() -} - -/// Helper to convert the fractional part of a number into a comparable one. -/// This will reverse the number so that 0's can be ignored -fn to_num_reversed(string: &str) -> U256 { - if string.is_empty() { - return U256::from(0); - } - string - .replace('_', "") - .trim() - .chars() - .rev() - .collect::() - .parse() - .unwrap() -} - -/// Helper to filter [ParameterList] to omit empty -/// parameters -fn filter_params(list: &ParameterList) -> ParameterList { - list.iter() - .filter(|(_, param)| param.is_some()) - .cloned() - .collect::>() -} - -/// Check if two ParseTrees are equal ignoring location information or ordering if ordering does -/// not matter -pub trait AstEq { - fn ast_eq(&self, other: &Self) -> bool; -} - -impl AstEq for Loc { - fn ast_eq(&self, _other: &Self) -> bool { - true - } -} - -impl AstEq for IdentifierPath { - fn ast_eq(&self, other: &Self) -> bool { - self.identifiers.ast_eq(&other.identifiers) - } -} - -impl AstEq for SourceUnit { - fn ast_eq(&self, other: &Self) -> bool { - self.0.ast_eq(&other.0) - } -} - -impl AstEq for VariableDefinition { - fn ast_eq(&self, other: &Self) -> bool { - let sorted_attrs = |def: &Self| { - let mut attrs = def.attrs.clone(); - attrs.sort(); - attrs - }; - self.ty.ast_eq(&other.ty) - && self.name.ast_eq(&other.name) - && self.initializer.ast_eq(&other.initializer) - && sorted_attrs(self).ast_eq(&sorted_attrs(other)) - } -} - -impl AstEq for FunctionDefinition { - fn ast_eq(&self, other: &Self) -> bool { - // attributes - let sorted_attrs = |def: &Self| { - let mut attrs = def.attributes.clone(); - attrs.sort(); - attrs - }; - - // params - let left_params = filter_params(&self.params); - let right_params = filter_params(&other.params); - let left_returns = filter_params(&self.returns); - let right_returns = filter_params(&other.returns); - - self.ty.ast_eq(&other.ty) - && self.name.ast_eq(&other.name) - && left_params.ast_eq(&right_params) - && self.return_not_returns.ast_eq(&other.return_not_returns) - && left_returns.ast_eq(&right_returns) - && self.body.ast_eq(&other.body) - && sorted_attrs(self).ast_eq(&sorted_attrs(other)) - } -} - -impl AstEq for Base { - fn ast_eq(&self, other: &Self) -> bool { - self.name.ast_eq(&other.name) - && self - .args - .clone() - .unwrap_or_default() - .ast_eq(&other.args.clone().unwrap_or_default()) - } -} - -impl AstEq for Vec -where - T: AstEq, -{ - fn ast_eq(&self, other: &Self) -> bool { - if self.len() != other.len() { - false - } else { - self.iter() - .zip(other.iter()) - .all(|(left, right)| left.ast_eq(right)) - } - } -} - -impl AstEq for Option -where - T: AstEq, -{ - fn ast_eq(&self, other: &Self) -> bool { - match (self, other) { - (Some(left), Some(right)) => left.ast_eq(right), - (None, None) => true, - _ => false, - } - } -} - -impl AstEq for Box -where - T: AstEq, -{ - fn ast_eq(&self, other: &Self) -> bool { - T::ast_eq(self, other) - } -} - -impl AstEq for () { - fn ast_eq(&self, _other: &Self) -> bool { - true - } -} - -impl AstEq for &T -where - T: AstEq, -{ - fn ast_eq(&self, other: &Self) -> bool { - T::ast_eq(self, other) - } -} - -impl AstEq for String { - fn ast_eq(&self, other: &Self) -> bool { - match (H160::from_str(self), H160::from_str(other)) { - (Ok(left), Ok(right)) => left.eq(&right), - _ => self == other, - } - } -} - -macro_rules! ast_eq_field { - (#[ast_eq_use($convert_func:ident)] $field:ident) => { - $convert_func($field) - }; - ($field:ident) => { - $field - }; -} - -macro_rules! gen_ast_eq_enum { - ($self:expr, $other:expr, $name:ident { - $($unit_variant:ident),* $(,)? - _ - $($tuple_variant:ident ( $($(#[ast_eq_use($tuple_convert_func:ident)])? $tuple_field:ident),* $(,)? )),* $(,)? - _ - $($struct_variant:ident { $($(#[ast_eq_use($struct_convert_func:ident)])? $struct_field:ident),* $(,)? }),* $(,)? - }) => { - match $self { - $($name::$unit_variant => gen_ast_eq_enum!($other, $name, $unit_variant),)* - $($name::$tuple_variant($($tuple_field),*) => - gen_ast_eq_enum!($other, $name, $tuple_variant ($($(#[ast_eq_use($tuple_convert_func)])? $tuple_field),*)),)* - $($name::$struct_variant { $($struct_field),* } => - gen_ast_eq_enum!($other, $name, $struct_variant {$($(#[ast_eq_use($struct_convert_func)])? $struct_field),*}),)* - } - }; - ($other:expr, $name:ident, $unit_variant:ident) => { - { - matches!($other, $name::$unit_variant) - } - }; - ($other:expr, $name:ident, $tuple_variant:ident ( $($(#[ast_eq_use($tuple_convert_func:ident)])? $tuple_field:ident),* $(,)? ) ) => { - { - let left = ($(ast_eq_field!($(#[ast_eq_use($tuple_convert_func)])? $tuple_field)),*); - if let $name::$tuple_variant($($tuple_field),*) = $other { - let right = ($(ast_eq_field!($(#[ast_eq_use($tuple_convert_func)])? $tuple_field)),*); - left.ast_eq(&right) - } else { - false - } - } - }; - ($other:expr, $name:ident, $struct_variant:ident { $($(#[ast_eq_use($struct_convert_func:ident)])? $struct_field:ident),* $(,)? } ) => { - { - let left = ($(ast_eq_field!($(#[ast_eq_use($struct_convert_func)])? $struct_field)),*); - if let $name::$struct_variant { $($struct_field),* } = $other { - let right = ($(ast_eq_field!($(#[ast_eq_use($struct_convert_func)])? $struct_field)),*); - left.ast_eq(&right) - } else { - false - } - } - }; -} - -macro_rules! wrap_in_box { - ($stmt:expr, $loc:expr) => { - if !matches!(**$stmt, Statement::Block { .. }) { - Box::new(Statement::Block { - loc: $loc, - unchecked: false, - statements: vec![*$stmt.clone()], - }) - } else { - $stmt.clone() - } - }; -} - -impl AstEq for Statement { - fn ast_eq(&self, other: &Self) -> bool { - match self { - Statement::If(loc, expr, stmt1, stmt2) => { - #[allow(clippy::borrowed_box)] - let wrap_if = |stmt1: &Box, stmt2: &Option>| { - ( - wrap_in_box!(stmt1, *loc), - stmt2.as_ref().map(|stmt2| { - if matches!(**stmt2, Statement::If(..)) { - stmt2.clone() - } else { - wrap_in_box!(stmt2, *loc) - } - }), - ) - }; - let (stmt1, stmt2) = wrap_if(stmt1, stmt2); - let left = (loc, expr, &stmt1, &stmt2); - if let Statement::If(loc, expr, stmt1, stmt2) = other { - let (stmt1, stmt2) = wrap_if(stmt1, stmt2); - let right = (loc, expr, &stmt1, &stmt2); - left.ast_eq(&right) - } else { - false - } - } - Statement::While(loc, expr, stmt1) => { - let stmt1 = wrap_in_box!(stmt1, *loc); - let left = (loc, expr, &stmt1); - if let Statement::While(loc, expr, stmt1) = other { - let stmt1 = wrap_in_box!(stmt1, *loc); - let right = (loc, expr, &stmt1); - left.ast_eq(&right) - } else { - false - } - } - Statement::DoWhile(loc, stmt1, expr) => { - let stmt1 = wrap_in_box!(stmt1, *loc); - let left = (loc, &stmt1, expr); - if let Statement::DoWhile(loc, stmt1, expr) = other { - let stmt1 = wrap_in_box!(stmt1, *loc); - let right = (loc, &stmt1, expr); - left.ast_eq(&right) - } else { - false - } - } - Statement::For(loc, stmt1, expr, stmt2, stmt3) => { - let stmt3 = stmt3.as_ref().map(|stmt3| wrap_in_box!(stmt3, *loc)); - let left = (loc, stmt1, expr, stmt2, &stmt3); - if let Statement::For(loc, stmt1, expr, stmt2, stmt3) = other { - let stmt3 = stmt3.as_ref().map(|stmt3| wrap_in_box!(stmt3, *loc)); - let right = (loc, stmt1, expr, stmt2, &stmt3); - left.ast_eq(&right) - } else { - false - } - } - Statement::Try(loc, expr, returns, catch) => { - let left_returns = returns - .as_ref() - .map(|(params, stmt)| (filter_params(params), stmt)); - let left = (loc, expr, left_returns, catch); - if let Statement::Try(loc, expr, returns, catch) = other { - let right_returns = returns - .as_ref() - .map(|(params, stmt)| (filter_params(params), stmt)); - let right = (loc, expr, right_returns, catch); - left.ast_eq(&right) - } else { - false - } - } - _ => gen_ast_eq_enum!(self, other, Statement { - _ - Args(loc, args), - Expression(loc, expr), - VariableDefinition(loc, decl, expr), - Continue(loc, ), - Break(loc, ), - Return(loc, expr), - Revert(loc, expr, expr2), - RevertNamedArgs(loc, expr, args), - Emit(loc, expr), - // provide overridden variants regardless - If(loc, expr, stmt1, stmt2), - While(loc, expr, stmt1), - DoWhile(loc, stmt1, expr), - For(loc, stmt1, expr, stmt2, stmt3), - Try(loc, expr, params, claus), - Error(loc) - _ - Block { - loc, - unchecked, - statements, - }, - Assembly { - loc, - dialect, - block, - flags, - }, - }), - } - } -} - -macro_rules! derive_ast_eq { - ($name:ident) => { - impl AstEq for $name { - fn ast_eq(&self, other: &Self) -> bool { - self == other - } - } - }; - (($($index:tt $gen:tt),*)) => { - impl < $( $gen ),* > AstEq for ($($gen,)*) where $($gen: AstEq),* { - fn ast_eq(&self, other: &Self) -> bool { - $( - if !self.$index.ast_eq(&other.$index) { - return false - } - )* - true - } - } - }; - (struct $name:ident { $($field:ident),* $(,)? }) => { - impl AstEq for $name { - fn ast_eq(&self, other: &Self) -> bool { - let $name { $($field),* } = self; - let left = ($($field),*); - let $name { $($field),* } = other; - let right = ($($field),*); - left.ast_eq(&right) - } - } - }; - (enum $name:ident { - $($unit_variant:ident),* $(,)? - _ - $($tuple_variant:ident ( $($(#[ast_eq_use($tuple_convert_func:ident)])? $tuple_field:ident),* $(,)? )),* $(,)? - _ - $($struct_variant:ident { $($(#[ast_eq_use($struct_convert_func:ident)])? $struct_field:ident),* $(,)? }),* $(,)? - }) => { - impl AstEq for $name { - fn ast_eq(&self, other: &Self) -> bool { - gen_ast_eq_enum!(self, other, $name { - $($unit_variant),* - _ - $($tuple_variant ( $($(#[ast_eq_use($tuple_convert_func)])? $tuple_field),* )),* - _ - $($struct_variant { $($(#[ast_eq_use($struct_convert_func)])? $struct_field),* }),* - }) - } - } - } -} - -derive_ast_eq! { (0 A) } -derive_ast_eq! { (0 A, 1 B) } -derive_ast_eq! { (0 A, 1 B, 2 C) } -derive_ast_eq! { (0 A, 1 B, 2 C, 3 D) } -derive_ast_eq! { (0 A, 1 B, 2 C, 3 D, 4 E) } -derive_ast_eq! { bool } -derive_ast_eq! { u8 } -derive_ast_eq! { u16 } -derive_ast_eq! { I256 } -derive_ast_eq! { U256 } -derive_ast_eq! { struct Identifier { loc, name } } -derive_ast_eq! { struct HexLiteral { loc, hex } } -derive_ast_eq! { struct StringLiteral { loc, unicode, string } } -derive_ast_eq! { struct Parameter { loc, annotation, ty, storage, name } } -derive_ast_eq! { struct NamedArgument { loc, name, expr } } -derive_ast_eq! { struct YulBlock { loc, statements } } -derive_ast_eq! { struct YulFunctionCall { loc, id, arguments } } -derive_ast_eq! { struct YulFunctionDefinition { loc, id, params, returns, body } } -derive_ast_eq! { struct YulSwitch { loc, condition, cases, default } } -derive_ast_eq! { struct YulFor { - loc, - init_block, - condition, - post_block, - execution_block, -}} -derive_ast_eq! { struct YulTypedIdentifier { loc, id, ty } } -derive_ast_eq! { struct VariableDeclaration { loc, ty, storage, name } } -derive_ast_eq! { struct Using { loc, list, ty, global } } -derive_ast_eq! { struct UsingFunction { loc, path, oper } } -derive_ast_eq! { struct TypeDefinition { loc, name, ty } } -derive_ast_eq! { struct ContractDefinition { loc, ty, name, base, parts } } -derive_ast_eq! { struct EventParameter { loc, ty, indexed, name } } -derive_ast_eq! { struct ErrorParameter { loc, ty, name } } -derive_ast_eq! { struct EventDefinition { loc, name, fields, anonymous } } -derive_ast_eq! { struct ErrorDefinition { loc, keyword, name, fields } } -derive_ast_eq! { struct StructDefinition { loc, name, fields } } -derive_ast_eq! { struct EnumDefinition { loc, name, values } } -derive_ast_eq! { struct Annotation { loc, id, value } } -derive_ast_eq! { enum UsingList { - Error, - _ - Library(expr), - Functions(exprs), - _ -}} -derive_ast_eq! { enum UserDefinedOperator { - BitwiseAnd, - BitwiseNot, - Negate, - BitwiseOr, - BitwiseXor, - Add, - Divide, - Modulo, - Multiply, - Subtract, - Equal, - More, - MoreEqual, - Less, - LessEqual, - NotEqual, - _ - _ -}} -derive_ast_eq! { enum Visibility { - _ - External(loc), - Public(loc), - Internal(loc), - Private(loc), - _ -}} -derive_ast_eq! { enum Mutability { - _ - Pure(loc), - View(loc), - Constant(loc), - Payable(loc), - _ -}} -derive_ast_eq! { enum FunctionAttribute { - _ - Mutability(muta), - Visibility(visi), - Virtual(loc), - Immutable(loc), - Override(loc, idents), - BaseOrModifier(loc, base), - Error(loc), - _ -}} -derive_ast_eq! { enum StorageLocation { - _ - Memory(loc), - Storage(loc), - Calldata(loc), - _ -}} -derive_ast_eq! { enum Type { - Address, - AddressPayable, - Payable, - Bool, - Rational, - DynamicBytes, - String, - _ - Int(int), - Uint(int), - Bytes(int), - _ - Mapping{ loc, key, key_name, value, value_name }, - Function { params, attributes, returns }, -}} -derive_ast_eq! { enum Expression { - _ - PostIncrement(loc, expr1), - PostDecrement(loc, expr1), - New(loc, expr1), - ArraySubscript(loc, expr1, expr2), - ArraySlice( - loc, - expr1, - expr2, - expr3, - ), - MemberAccess(loc, expr1, ident1), - FunctionCall(loc, expr1, exprs1), - FunctionCallBlock(loc, expr1, stmt), - NamedFunctionCall(loc, expr1, args), - Not(loc, expr1), - BitwiseNot(loc, expr1), - Delete(loc, expr1), - PreIncrement(loc, expr1), - PreDecrement(loc, expr1), - UnaryPlus(loc, expr1), - Negate(loc, expr1), - Power(loc, expr1, expr2), - Multiply(loc, expr1, expr2), - Divide(loc, expr1, expr2), - Modulo(loc, expr1, expr2), - Add(loc, expr1, expr2), - Subtract(loc, expr1, expr2), - ShiftLeft(loc, expr1, expr2), - ShiftRight(loc, expr1, expr2), - BitwiseAnd(loc, expr1, expr2), - BitwiseXor(loc, expr1, expr2), - BitwiseOr(loc, expr1, expr2), - Less(loc, expr1, expr2), - More(loc, expr1, expr2), - LessEqual(loc, expr1, expr2), - MoreEqual(loc, expr1, expr2), - Equal(loc, expr1, expr2), - NotEqual(loc, expr1, expr2), - And(loc, expr1, expr2), - Or(loc, expr1, expr2), - ConditionalOperator(loc, expr1, expr2, expr3), - Assign(loc, expr1, expr2), - AssignOr(loc, expr1, expr2), - AssignAnd(loc, expr1, expr2), - AssignXor(loc, expr1, expr2), - AssignShiftLeft(loc, expr1, expr2), - AssignShiftRight(loc, expr1, expr2), - AssignAdd(loc, expr1, expr2), - AssignSubtract(loc, expr1, expr2), - AssignMultiply(loc, expr1, expr2), - AssignDivide(loc, expr1, expr2), - AssignModulo(loc, expr1, expr2), - BoolLiteral(loc, bool1), - NumberLiteral(loc, #[ast_eq_use(to_num)] str1, #[ast_eq_use(to_num)] str2, unit), - RationalNumberLiteral( - loc, - #[ast_eq_use(to_num)] str1, - #[ast_eq_use(to_num_reversed)] str2, - #[ast_eq_use(to_num)] str3, - unit - ), - HexNumberLiteral(loc, str1, unit), - StringLiteral(strs1), - Type(loc, ty1), - HexLiteral(hexs1), - AddressLiteral(loc, str1), - Variable(ident1), - List(loc, params1), - ArrayLiteral(loc, exprs1), - Parenthesis(loc, expr) - _ -}} -derive_ast_eq! { enum CatchClause { - _ - Simple(param, ident, stmt), - Named(loc, ident, param, stmt), - _ -}} -derive_ast_eq! { enum YulStatement { - _ - Assign(loc, exprs, expr), - VariableDeclaration(loc, idents, expr), - If(loc, expr, block), - For(yul_for), - Switch(switch), - Leave(loc), - Break(loc), - Continue(loc), - Block(block), - FunctionDefinition(def), - FunctionCall(func), - Error(loc), - _ -}} -derive_ast_eq! { enum YulExpression { - _ - BoolLiteral(loc, boo, ident), - NumberLiteral(loc, string1, string2, ident), - HexNumberLiteral(loc, string, ident), - HexStringLiteral(hex, ident), - StringLiteral(string, ident), - Variable(ident), - FunctionCall(func), - SuffixAccess(loc, expr, ident), - _ -}} -derive_ast_eq! { enum YulSwitchOptions { - _ - Case(loc, expr, block), - Default(loc, block), - _ -}} -derive_ast_eq! { enum SourceUnitPart { - _ - ContractDefinition(def), - PragmaDirective(loc, ident, string), - ImportDirective(import), - EnumDefinition(def), - StructDefinition(def), - EventDefinition(def), - ErrorDefinition(def), - FunctionDefinition(def), - VariableDefinition(def), - TypeDefinition(def), - Using(using), - StraySemicolon(loc), - Annotation(annotation), - _ -}} -derive_ast_eq! { enum ImportPath { - _ - Filename(lit), - Path(path), - _ -}} -derive_ast_eq! { enum Import { - _ - Plain(string, loc), - GlobalSymbol(string, ident, loc), - Rename(string, idents, loc), - _ -}} -derive_ast_eq! { enum FunctionTy { - Constructor, - Function, - Fallback, - Receive, - Modifier, - _ - _ -}} -derive_ast_eq! { enum ContractPart { - _ - StructDefinition(def), - EventDefinition(def), - EnumDefinition(def), - ErrorDefinition(def), - VariableDefinition(def), - FunctionDefinition(def), - TypeDefinition(def), - StraySemicolon(loc), - Using(using), - Annotation(annotation), - _ -}} -derive_ast_eq! { enum ContractTy { - _ - Abstract(loc), - Contract(loc), - Interface(loc), - Library(loc), - _ -}} -derive_ast_eq! { enum VariableAttribute { - _ - Visibility(visi), - Constant(loc), - Immutable(loc), - Override(loc, idents), - _ -}} diff --git a/forge-fmt/src/solang_ext/loc.rs b/forge-fmt/src/solang_ext/loc.rs deleted file mode 100644 index e59b419f4..000000000 --- a/forge-fmt/src/solang_ext/loc.rs +++ /dev/null @@ -1,158 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -use solang_parser::pt; -use std::{borrow::Cow, rc::Rc, sync::Arc}; - -/// Returns the code location. -/// -/// Patched version of [`pt::CodeLocation`]: includes the block of a [`pt::FunctionDefinition`] in -/// its `loc`. -pub trait CodeLocationExt { - /// Returns the code location of `self`. - fn loc(&self) -> pt::Loc; -} - -impl<'a, T: ?Sized + CodeLocationExt> CodeLocationExt for &'a T { - fn loc(&self) -> pt::Loc { - (**self).loc() - } -} - -impl<'a, T: ?Sized + CodeLocationExt> CodeLocationExt for &'a mut T { - fn loc(&self) -> pt::Loc { - (**self).loc() - } -} - -impl<'a, T: ?Sized + ToOwned + CodeLocationExt> CodeLocationExt for Cow<'a, T> { - fn loc(&self) -> pt::Loc { - (**self).loc() - } -} - -impl CodeLocationExt for Box { - fn loc(&self) -> pt::Loc { - (**self).loc() - } -} - -impl CodeLocationExt for Rc { - fn loc(&self) -> pt::Loc { - (**self).loc() - } -} - -impl CodeLocationExt for Arc { - fn loc(&self) -> pt::Loc { - (**self).loc() - } -} - -// FunctionDefinition patch -impl CodeLocationExt for pt::FunctionDefinition { - #[inline] - #[track_caller] - fn loc(&self) -> pt::Loc { - let mut loc = self.loc; - if let Some(ref body) = self.body { - loc.use_end_from(&pt::CodeLocation::loc(body)); - } - loc - } -} - -impl CodeLocationExt for pt::ContractPart { - #[inline] - #[track_caller] - fn loc(&self) -> pt::Loc { - match self { - Self::FunctionDefinition(f) => f.loc(), - _ => pt::CodeLocation::loc(self), - } - } -} - -impl CodeLocationExt for pt::SourceUnitPart { - #[inline] - #[track_caller] - fn loc(&self) -> pt::Loc { - match self { - Self::FunctionDefinition(f) => f.loc(), - _ => pt::CodeLocation::loc(self), - } - } -} - -impl CodeLocationExt for pt::ImportPath { - fn loc(&self) -> pt::Loc { - match self { - Self::Filename(s) => s.loc(), - Self::Path(i) => i.loc(), - } - } -} - -macro_rules! impl_delegate { - ($($t:ty),+ $(,)?) => {$( - impl CodeLocationExt for $t { - #[inline] - #[track_caller] - fn loc(&self) -> pt::Loc { - pt::CodeLocation::loc(self) - } - } - )+}; -} - -impl_delegate! { - pt::Annotation, - pt::Base, - pt::ContractDefinition, - pt::EnumDefinition, - pt::ErrorDefinition, - pt::ErrorParameter, - pt::EventDefinition, - pt::EventParameter, - // pt::FunctionDefinition, - pt::HexLiteral, - pt::Identifier, - pt::IdentifierPath, - pt::NamedArgument, - pt::Parameter, - // pt::SourceUnit, - pt::StringLiteral, - pt::StructDefinition, - pt::TypeDefinition, - pt::Using, - pt::UsingFunction, - pt::VariableDeclaration, - pt::VariableDefinition, - pt::YulBlock, - pt::YulFor, - pt::YulFunctionCall, - pt::YulFunctionDefinition, - pt::YulSwitch, - pt::YulTypedIdentifier, - - pt::CatchClause, - pt::Comment, - // pt::ContractPart, - pt::ContractTy, - pt::Expression, - pt::FunctionAttribute, - // pt::FunctionTy, - pt::Import, - pt::Loc, - pt::Mutability, - // pt::SourceUnitPart, - pt::Statement, - pt::StorageLocation, - // pt::Type, - // pt::UserDefinedOperator, - pt::UsingList, - pt::VariableAttribute, - // pt::Visibility, - pt::YulExpression, - pt::YulStatement, - pt::YulSwitchOptions, -} diff --git a/forge-fmt/src/solang_ext/mod.rs b/forge-fmt/src/solang_ext/mod.rs deleted file mode 100644 index 5ca238473..000000000 --- a/forge-fmt/src/solang_ext/mod.rs +++ /dev/null @@ -1,30 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -//! Extension traits and modules to the [`solang_parser`] crate. - -/// Same as [`solang_parser::pt`], but with the patched `CodeLocation`. -pub mod pt { - #[doc(no_inline)] - pub use super::loc::CodeLocationExt as CodeLocation; - - #[doc(no_inline)] - pub use solang_parser::pt::{ - Annotation, Base, CatchClause, Comment, ContractDefinition, ContractPart, ContractTy, - EnumDefinition, ErrorDefinition, ErrorParameter, EventDefinition, EventParameter, - Expression, FunctionAttribute, FunctionDefinition, FunctionTy, HexLiteral, Identifier, - IdentifierPath, Import, ImportPath, Loc, Mutability, NamedArgument, OptionalCodeLocation, - Parameter, ParameterList, SourceUnit, SourceUnitPart, Statement, StorageLocation, - StringLiteral, StructDefinition, Type, TypeDefinition, UserDefinedOperator, Using, - UsingFunction, UsingList, VariableAttribute, VariableDeclaration, VariableDefinition, - Visibility, YulBlock, YulExpression, YulFor, YulFunctionCall, YulFunctionDefinition, - YulStatement, YulSwitch, YulSwitchOptions, YulTypedIdentifier, - }; -} - -mod ast_eq; -mod loc; -mod safe_unwrap; - -pub use ast_eq::AstEq; -pub use loc::CodeLocationExt; -pub use safe_unwrap::SafeUnwrap; diff --git a/forge-fmt/src/solang_ext/safe_unwrap.rs b/forge-fmt/src/solang_ext/safe_unwrap.rs deleted file mode 100644 index f3ec77f8c..000000000 --- a/forge-fmt/src/solang_ext/safe_unwrap.rs +++ /dev/null @@ -1,54 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -use solang_parser::pt; - -/// Trait implemented to unwrap optional parse tree items initially introduced in -/// [hyperledger/solang#1068]. -/// -/// Note that the methods of this trait should only be used on parse tree items' fields, like -/// [pt::VariableDefinition] or [pt::EventDefinition], where the `name` field is `None` only when an -/// error occurred during parsing. -/// -/// [hyperledger/solang#1068]: https://github.com/hyperledger/solang/pull/1068 -pub trait SafeUnwrap { - /// See [SafeUnwrap]. - fn safe_unwrap(&self) -> &T; - - /// See [SafeUnwrap]. - fn safe_unwrap_mut(&mut self) -> &mut T; -} - -#[inline(never)] -#[cold] -#[track_caller] -fn invalid() -> ! { - panic!("invalid parse tree") -} - -macro_rules! impl_ { - ($($t:ty),+ $(,)?) => { - $( - impl SafeUnwrap<$t> for Option<$t> { - #[inline] - #[track_caller] - fn safe_unwrap(&self) -> &$t { - match *self { - Some(ref x) => x, - None => invalid(), - } - } - - #[inline] - #[track_caller] - fn safe_unwrap_mut(&mut self) -> &mut $t { - match *self { - Some(ref mut x) => x, - None => invalid(), - } - } - } - )+ - }; -} - -impl_!(pt::Identifier, pt::StringLiteral); diff --git a/forge-fmt/src/string.rs b/forge-fmt/src/string.rs deleted file mode 100644 index 34e7a996a..000000000 --- a/forge-fmt/src/string.rs +++ /dev/null @@ -1,184 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -//! Helpfers for dealing with quoted strings - -/// The state of a character in a string with quotable components -/// This is a simplified version of the -/// [actual parser](https://docs.soliditylang.org/en/v0.8.15/grammar.html#a4.SolidityLexer.EscapeSequence) -/// as we don't care about hex or other character meanings -#[derive(Copy, Clone, Debug, Eq, PartialEq, Default)] -pub enum QuoteState { - /// Not currently in quoted string - #[default] - None, - /// The opening character of a quoted string - Opening(char), - /// A character in a quoted string - String(char), - /// The `\` in an escape sequence `"\n"` - Escaping(char), - /// The escaped character e.g. `n` in `"\n"` - Escaped(char), - /// The closing character - Closing(char), -} - -/// An iterator over characters and indices in a string slice with information about quoted string -/// states -pub struct QuoteStateCharIndices<'a> { - iter: std::str::CharIndices<'a>, - state: QuoteState, -} - -impl<'a> QuoteStateCharIndices<'a> { - fn new(string: &'a str) -> Self { - Self { - iter: string.char_indices(), - state: QuoteState::None, - } - } - pub fn with_state(mut self, state: QuoteState) -> Self { - self.state = state; - self - } -} - -impl<'a> Iterator for QuoteStateCharIndices<'a> { - type Item = (QuoteState, usize, char); - fn next(&mut self) -> Option { - let (idx, ch) = self.iter.next()?; - match self.state { - QuoteState::None | QuoteState::Closing(_) => { - if ch == '\'' || ch == '"' { - self.state = QuoteState::Opening(ch); - } else { - self.state = QuoteState::None - } - } - QuoteState::String(quote) | QuoteState::Opening(quote) | QuoteState::Escaped(quote) => { - if ch == quote { - self.state = QuoteState::Closing(quote) - } else if ch == '\\' { - self.state = QuoteState::Escaping(quote) - } else { - self.state = QuoteState::String(quote) - } - } - QuoteState::Escaping(quote) => self.state = QuoteState::Escaped(quote), - } - Some((self.state, idx, ch)) - } -} - -/// An iterator over the the indices of quoted string locations -pub struct QuotedRanges<'a>(QuoteStateCharIndices<'a>); - -impl<'a> QuotedRanges<'a> { - pub fn with_state(mut self, state: QuoteState) -> Self { - self.0 = self.0.with_state(state); - self - } -} - -impl<'a> Iterator for QuotedRanges<'a> { - type Item = (char, usize, usize); - fn next(&mut self) -> Option { - let (quote, start) = loop { - let (state, idx, _) = self.0.next()?; - match state { - QuoteState::Opening(quote) - | QuoteState::Escaping(quote) - | QuoteState::Escaped(quote) - | QuoteState::String(quote) => break (quote, idx), - QuoteState::Closing(quote) => return Some((quote, idx, idx)), - QuoteState::None => {} - } - }; - for (state, idx, _) in self.0.by_ref() { - if matches!(state, QuoteState::Closing(_)) { - return Some((quote, start, idx)); - } - } - None - } -} - -/// Helpers for iterating over quoted strings -pub trait QuotedStringExt { - /// Get an iterator of characters, indices and their quoted string state - fn quote_state_char_indices(&self) -> QuoteStateCharIndices; - /// Get an iterator of quoted string ranges - fn quoted_ranges(&self) -> QuotedRanges { - QuotedRanges(self.quote_state_char_indices()) - } - /// Check to see if a string is quoted. This will return true if the first character - /// is a quote and the last character is a quote with no non-quoted sections in between. - fn is_quoted(&self) -> bool { - let mut iter = self.quote_state_char_indices(); - if !matches!(iter.next(), Some((QuoteState::Opening(_), _, _))) { - return false; - } - while let Some((state, _, _)) = iter.next() { - if matches!(state, QuoteState::Closing(_)) { - return iter.next().is_none(); - } - } - false - } -} - -impl QuotedStringExt for T -where - T: AsRef, -{ - fn quote_state_char_indices(&self) -> QuoteStateCharIndices { - QuoteStateCharIndices::new(self.as_ref()) - } -} - -impl QuotedStringExt for str { - fn quote_state_char_indices(&self) -> QuoteStateCharIndices { - QuoteStateCharIndices::new(self) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use pretty_assertions::assert_eq; - - #[test] - fn quote_state_char_indices() { - assert_eq!( - r#"a'a"\'\"\n\\'a"#.quote_state_char_indices().collect::>(), - vec![ - (QuoteState::None, 0, 'a'), - (QuoteState::Opening('\''), 1, '\''), - (QuoteState::String('\''), 2, 'a'), - (QuoteState::String('\''), 3, '"'), - (QuoteState::Escaping('\''), 4, '\\'), - (QuoteState::Escaped('\''), 5, '\''), - (QuoteState::Escaping('\''), 6, '\\'), - (QuoteState::Escaped('\''), 7, '"'), - (QuoteState::Escaping('\''), 8, '\\'), - (QuoteState::Escaped('\''), 9, 'n'), - (QuoteState::Escaping('\''), 10, '\\'), - (QuoteState::Escaped('\''), 11, '\\'), - (QuoteState::Closing('\''), 12, '\''), - (QuoteState::None, 13, 'a'), - ] - ); - } - - #[test] - fn quoted_ranges() { - let string = r#"testing "double quoted" and 'single quoted' strings"#; - assert_eq!( - string - .quoted_ranges() - .map(|(quote, start, end)| (quote, &string[start..=end])) - .collect::>(), - vec![('"', r#""double quoted""#), ('\'', "'single quoted'")] - ); - } -} diff --git a/forge-fmt/src/visit.rs b/forge-fmt/src/visit.rs deleted file mode 100644 index edbbc3827..000000000 --- a/forge-fmt/src/visit.rs +++ /dev/null @@ -1,657 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -//! Visitor helpers to traverse the [solang Solidity Parse Tree](solang_parser::pt). - -use crate::solang_ext::pt::*; - -/// A trait that is invoked while traversing the Solidity Parse Tree. -/// Each method of the [Visitor] trait is a hook that can be potentially overridden. -/// -/// Currently the main implementor of this trait is the [`Formatter`](crate::Formatter) struct. -pub trait Visitor { - type Error: std::error::Error; - - fn visit_source(&mut self, _loc: Loc) -> Result<(), Self::Error> { - Ok(()) - } - - fn visit_source_unit(&mut self, _source_unit: &mut SourceUnit) -> Result<(), Self::Error> { - Ok(()) - } - - fn visit_contract(&mut self, _contract: &mut ContractDefinition) -> Result<(), Self::Error> { - Ok(()) - } - - fn visit_annotation(&mut self, annotation: &mut Annotation) -> Result<(), Self::Error> { - self.visit_source(annotation.loc) - } - - fn visit_pragma( - &mut self, - loc: Loc, - _ident: &mut Option, - _str: &mut Option, - ) -> Result<(), Self::Error> { - self.visit_source(loc) - } - - fn visit_import_plain( - &mut self, - _loc: Loc, - _import: &mut ImportPath, - ) -> Result<(), Self::Error> { - Ok(()) - } - - fn visit_import_global( - &mut self, - _loc: Loc, - _global: &mut ImportPath, - _alias: &mut Identifier, - ) -> Result<(), Self::Error> { - Ok(()) - } - - fn visit_import_renames( - &mut self, - _loc: Loc, - _imports: &mut [(Identifier, Option)], - _from: &mut ImportPath, - ) -> Result<(), Self::Error> { - Ok(()) - } - - fn visit_enum(&mut self, _enum: &mut EnumDefinition) -> Result<(), Self::Error> { - Ok(()) - } - - fn visit_assembly( - &mut self, - loc: Loc, - _dialect: &mut Option, - _block: &mut YulBlock, - _flags: &mut Option>, - ) -> Result<(), Self::Error> { - self.visit_source(loc) - } - - fn visit_block( - &mut self, - loc: Loc, - _unchecked: bool, - _statements: &mut Vec, - ) -> Result<(), Self::Error> { - self.visit_source(loc) - } - - fn visit_args(&mut self, loc: Loc, _args: &mut Vec) -> Result<(), Self::Error> { - self.visit_source(loc) - } - - /// Don't write semicolon at the end because expressions can appear as both - /// part of other node and a statement in the function body - fn visit_expr(&mut self, loc: Loc, _expr: &mut Expression) -> Result<(), Self::Error> { - self.visit_source(loc) - } - - fn visit_ident(&mut self, loc: Loc, _ident: &mut Identifier) -> Result<(), Self::Error> { - self.visit_source(loc) - } - - fn visit_ident_path(&mut self, idents: &mut IdentifierPath) -> Result<(), Self::Error> { - self.visit_source(idents.loc) - } - - fn visit_emit(&mut self, loc: Loc, _event: &mut Expression) -> Result<(), Self::Error> { - self.visit_source(loc)?; - self.visit_stray_semicolon()?; - - Ok(()) - } - - fn visit_var_definition(&mut self, var: &mut VariableDefinition) -> Result<(), Self::Error> { - self.visit_source(var.loc)?; - self.visit_stray_semicolon()?; - - Ok(()) - } - - fn visit_var_definition_stmt( - &mut self, - loc: Loc, - _declaration: &mut VariableDeclaration, - _expr: &mut Option, - ) -> Result<(), Self::Error> { - self.visit_source(loc)?; - self.visit_stray_semicolon() - } - - fn visit_var_declaration(&mut self, var: &mut VariableDeclaration) -> Result<(), Self::Error> { - self.visit_source(var.loc) - } - - fn visit_return( - &mut self, - loc: Loc, - _expr: &mut Option, - ) -> Result<(), Self::Error> { - self.visit_source(loc)?; - self.visit_stray_semicolon()?; - - Ok(()) - } - - fn visit_revert( - &mut self, - loc: Loc, - _error: &mut Option, - _args: &mut Vec, - ) -> Result<(), Self::Error> { - self.visit_source(loc)?; - self.visit_stray_semicolon()?; - - Ok(()) - } - - fn visit_revert_named_args( - &mut self, - loc: Loc, - _error: &mut Option, - _args: &mut Vec, - ) -> Result<(), Self::Error> { - self.visit_source(loc)?; - self.visit_stray_semicolon()?; - - Ok(()) - } - - fn visit_break(&mut self, loc: Loc, _semicolon: bool) -> Result<(), Self::Error> { - self.visit_source(loc) - } - - fn visit_continue(&mut self, loc: Loc, _semicolon: bool) -> Result<(), Self::Error> { - self.visit_source(loc) - } - - #[allow(clippy::type_complexity)] - fn visit_try( - &mut self, - loc: Loc, - _expr: &mut Expression, - _returns: &mut Option<(Vec<(Loc, Option)>, Box)>, - _clauses: &mut Vec, - ) -> Result<(), Self::Error> { - self.visit_source(loc) - } - - fn visit_if( - &mut self, - loc: Loc, - _cond: &mut Expression, - _if_branch: &mut Box, - _else_branch: &mut Option>, - _is_first_stmt: bool, - ) -> Result<(), Self::Error> { - self.visit_source(loc) - } - - fn visit_do_while( - &mut self, - loc: Loc, - _body: &mut Statement, - _cond: &mut Expression, - ) -> Result<(), Self::Error> { - self.visit_source(loc) - } - - fn visit_while( - &mut self, - loc: Loc, - _cond: &mut Expression, - _body: &mut Statement, - ) -> Result<(), Self::Error> { - self.visit_source(loc) - } - - fn visit_for( - &mut self, - loc: Loc, - _init: &mut Option>, - _cond: &mut Option>, - _update: &mut Option>, - _body: &mut Option>, - ) -> Result<(), Self::Error> { - self.visit_source(loc) - } - - fn visit_function(&mut self, func: &mut FunctionDefinition) -> Result<(), Self::Error> { - self.visit_source(func.loc())?; - if func.body.is_none() { - self.visit_stray_semicolon()?; - } - - Ok(()) - } - - fn visit_function_attribute( - &mut self, - attribute: &mut FunctionAttribute, - ) -> Result<(), Self::Error> { - self.visit_source(attribute.loc())?; - Ok(()) - } - - fn visit_var_attribute( - &mut self, - attribute: &mut VariableAttribute, - ) -> Result<(), Self::Error> { - self.visit_source(attribute.loc())?; - Ok(()) - } - - fn visit_base(&mut self, base: &mut Base) -> Result<(), Self::Error> { - self.visit_source(base.loc) - } - - fn visit_parameter(&mut self, parameter: &mut Parameter) -> Result<(), Self::Error> { - self.visit_source(parameter.loc) - } - - fn visit_struct(&mut self, structure: &mut StructDefinition) -> Result<(), Self::Error> { - self.visit_source(structure.loc)?; - - Ok(()) - } - - fn visit_event(&mut self, event: &mut EventDefinition) -> Result<(), Self::Error> { - self.visit_source(event.loc)?; - self.visit_stray_semicolon()?; - - Ok(()) - } - - fn visit_event_parameter(&mut self, param: &mut EventParameter) -> Result<(), Self::Error> { - self.visit_source(param.loc) - } - - fn visit_error(&mut self, error: &mut ErrorDefinition) -> Result<(), Self::Error> { - self.visit_source(error.loc)?; - self.visit_stray_semicolon()?; - - Ok(()) - } - - fn visit_error_parameter(&mut self, param: &mut ErrorParameter) -> Result<(), Self::Error> { - self.visit_source(param.loc) - } - - fn visit_type_definition(&mut self, def: &mut TypeDefinition) -> Result<(), Self::Error> { - self.visit_source(def.loc) - } - - fn visit_stray_semicolon(&mut self) -> Result<(), Self::Error> { - Ok(()) - } - - fn visit_opening_paren(&mut self) -> Result<(), Self::Error> { - Ok(()) - } - - fn visit_closing_paren(&mut self) -> Result<(), Self::Error> { - Ok(()) - } - - fn visit_newline(&mut self) -> Result<(), Self::Error> { - Ok(()) - } - - fn visit_using(&mut self, using: &mut Using) -> Result<(), Self::Error> { - self.visit_source(using.loc)?; - self.visit_stray_semicolon()?; - - Ok(()) - } - - fn visit_yul_block( - &mut self, - loc: Loc, - _stmts: &mut Vec, - _attempt_single_line: bool, - ) -> Result<(), Self::Error> { - self.visit_source(loc) - } - - fn visit_yul_expr(&mut self, expr: &mut YulExpression) -> Result<(), Self::Error> { - self.visit_source(expr.loc()) - } - - fn visit_yul_assignment( - &mut self, - loc: Loc, - _exprs: &mut Vec, - _expr: &mut Option<&mut YulExpression>, - ) -> Result<(), Self::Error> - where - T: Visitable + CodeLocation, - { - self.visit_source(loc) - } - - fn visit_yul_for(&mut self, stmt: &mut YulFor) -> Result<(), Self::Error> { - self.visit_source(stmt.loc) - } - - fn visit_yul_function_call(&mut self, stmt: &mut YulFunctionCall) -> Result<(), Self::Error> { - self.visit_source(stmt.loc) - } - - fn visit_yul_fun_def(&mut self, stmt: &mut YulFunctionDefinition) -> Result<(), Self::Error> { - self.visit_source(stmt.loc) - } - - fn visit_yul_if( - &mut self, - loc: Loc, - _expr: &mut YulExpression, - _block: &mut YulBlock, - ) -> Result<(), Self::Error> { - self.visit_source(loc) - } - - fn visit_yul_leave(&mut self, loc: Loc) -> Result<(), Self::Error> { - self.visit_source(loc) - } - - fn visit_yul_switch(&mut self, stmt: &mut YulSwitch) -> Result<(), Self::Error> { - self.visit_source(stmt.loc) - } - - fn visit_yul_var_declaration( - &mut self, - loc: Loc, - _idents: &mut Vec, - _expr: &mut Option, - ) -> Result<(), Self::Error> { - self.visit_source(loc) - } - - fn visit_yul_typed_ident(&mut self, ident: &mut YulTypedIdentifier) -> Result<(), Self::Error> { - self.visit_source(ident.loc) - } - - fn visit_parser_error(&mut self, loc: Loc) -> Result<(), Self::Error> { - self.visit_source(loc) - } -} - -/// All [`solang_parser::pt`] types, such as [Statement], should implement the [Visitable] trait -/// that accepts a trait [Visitor] implementation, which has various callback handles for Solidity -/// Parse Tree nodes. -/// -/// We want to take a `&mut self` to be able to implement some advanced features in the future such -/// as modifying the Parse Tree before formatting it. -pub trait Visitable { - fn visit(&mut self, v: &mut V) -> Result<(), V::Error> - where - V: Visitor; -} - -impl Visitable for &mut T -where - T: Visitable, -{ - fn visit(&mut self, v: &mut V) -> Result<(), V::Error> - where - V: Visitor, - { - T::visit(self, v) - } -} - -impl Visitable for Option -where - T: Visitable, -{ - fn visit(&mut self, v: &mut V) -> Result<(), V::Error> - where - V: Visitor, - { - if let Some(inner) = self.as_mut() { - inner.visit(v) - } else { - Ok(()) - } - } -} - -impl Visitable for Box -where - T: Visitable, -{ - fn visit(&mut self, v: &mut V) -> Result<(), V::Error> - where - V: Visitor, - { - T::visit(self, v) - } -} - -impl Visitable for Vec -where - T: Visitable, -{ - fn visit(&mut self, v: &mut V) -> Result<(), V::Error> - where - V: Visitor, - { - for item in self.iter_mut() { - item.visit(v)?; - } - Ok(()) - } -} - -impl Visitable for SourceUnitPart { - fn visit(&mut self, v: &mut V) -> Result<(), V::Error> - where - V: Visitor, - { - match self { - SourceUnitPart::ContractDefinition(contract) => v.visit_contract(contract), - SourceUnitPart::PragmaDirective(loc, ident, str) => v.visit_pragma(*loc, ident, str), - SourceUnitPart::ImportDirective(import) => import.visit(v), - SourceUnitPart::EnumDefinition(enumeration) => v.visit_enum(enumeration), - SourceUnitPart::StructDefinition(structure) => v.visit_struct(structure), - SourceUnitPart::EventDefinition(event) => v.visit_event(event), - SourceUnitPart::ErrorDefinition(error) => v.visit_error(error), - SourceUnitPart::FunctionDefinition(function) => v.visit_function(function), - SourceUnitPart::VariableDefinition(variable) => v.visit_var_definition(variable), - SourceUnitPart::TypeDefinition(def) => v.visit_type_definition(def), - SourceUnitPart::StraySemicolon(_) => v.visit_stray_semicolon(), - SourceUnitPart::Using(using) => v.visit_using(using), - SourceUnitPart::Annotation(annotation) => v.visit_annotation(annotation), - } - } -} - -impl Visitable for Import { - fn visit(&mut self, v: &mut V) -> Result<(), V::Error> - where - V: Visitor, - { - match self { - Import::Plain(import, loc) => v.visit_import_plain(*loc, import), - Import::GlobalSymbol(global, import_as, loc) => { - v.visit_import_global(*loc, global, import_as) - } - Import::Rename(from, imports, loc) => v.visit_import_renames(*loc, imports, from), - } - } -} - -impl Visitable for ContractPart { - fn visit(&mut self, v: &mut V) -> Result<(), V::Error> - where - V: Visitor, - { - match self { - ContractPart::StructDefinition(structure) => v.visit_struct(structure), - ContractPart::EventDefinition(event) => v.visit_event(event), - ContractPart::ErrorDefinition(error) => v.visit_error(error), - ContractPart::EnumDefinition(enumeration) => v.visit_enum(enumeration), - ContractPart::VariableDefinition(variable) => v.visit_var_definition(variable), - ContractPart::FunctionDefinition(function) => v.visit_function(function), - ContractPart::TypeDefinition(def) => v.visit_type_definition(def), - ContractPart::StraySemicolon(_) => v.visit_stray_semicolon(), - ContractPart::Using(using) => v.visit_using(using), - ContractPart::Annotation(annotation) => v.visit_annotation(annotation), - } - } -} - -impl Visitable for Statement { - fn visit(&mut self, v: &mut V) -> Result<(), V::Error> - where - V: Visitor, - { - match self { - Statement::Block { - loc, - unchecked, - statements, - } => v.visit_block(*loc, *unchecked, statements), - Statement::Assembly { - loc, - dialect, - block, - flags, - } => v.visit_assembly(*loc, dialect, block, flags), - Statement::Args(loc, args) => v.visit_args(*loc, args), - Statement::If(loc, cond, if_branch, else_branch) => { - v.visit_if(*loc, cond, if_branch, else_branch, true) - } - Statement::While(loc, cond, body) => v.visit_while(*loc, cond, body), - Statement::Expression(loc, expr) => { - v.visit_expr(*loc, expr)?; - v.visit_stray_semicolon() - } - Statement::VariableDefinition(loc, declaration, expr) => { - v.visit_var_definition_stmt(*loc, declaration, expr) - } - Statement::For(loc, init, cond, update, body) => { - v.visit_for(*loc, init, cond, update, body) - } - Statement::DoWhile(loc, body, cond) => v.visit_do_while(*loc, body, cond), - Statement::Continue(loc) => v.visit_continue(*loc, true), - Statement::Break(loc) => v.visit_break(*loc, true), - Statement::Return(loc, expr) => v.visit_return(*loc, expr), - Statement::Revert(loc, error, args) => v.visit_revert(*loc, error, args), - Statement::RevertNamedArgs(loc, error, args) => { - v.visit_revert_named_args(*loc, error, args) - } - Statement::Emit(loc, event) => v.visit_emit(*loc, event), - Statement::Try(loc, expr, returns, clauses) => { - v.visit_try(*loc, expr, returns, clauses) - } - Statement::Error(loc) => v.visit_parser_error(*loc), - } - } -} - -impl Visitable for Loc { - fn visit(&mut self, v: &mut V) -> Result<(), V::Error> - where - V: Visitor, - { - v.visit_source(*self) - } -} - -impl Visitable for Expression { - fn visit(&mut self, v: &mut V) -> Result<(), V::Error> - where - V: Visitor, - { - v.visit_expr(self.loc(), self) - } -} - -impl Visitable for Identifier { - fn visit(&mut self, v: &mut V) -> Result<(), V::Error> - where - V: Visitor, - { - v.visit_ident(self.loc, self) - } -} - -impl Visitable for VariableDeclaration { - fn visit(&mut self, v: &mut V) -> Result<(), V::Error> - where - V: Visitor, - { - v.visit_var_declaration(self) - } -} - -impl Visitable for YulBlock { - fn visit(&mut self, v: &mut V) -> Result<(), V::Error> - where - V: Visitor, - { - v.visit_yul_block(self.loc, self.statements.as_mut(), false) - } -} - -impl Visitable for YulStatement { - fn visit(&mut self, v: &mut V) -> Result<(), V::Error> - where - V: Visitor, - { - match self { - YulStatement::Assign(loc, exprs, expr) => { - v.visit_yul_assignment(*loc, exprs, &mut Some(expr)) - } - YulStatement::Block(block) => { - v.visit_yul_block(block.loc, block.statements.as_mut(), false) - } - YulStatement::Break(loc) => v.visit_break(*loc, false), - YulStatement::Continue(loc) => v.visit_continue(*loc, false), - YulStatement::For(stmt) => v.visit_yul_for(stmt), - YulStatement::FunctionCall(stmt) => v.visit_yul_function_call(stmt), - YulStatement::FunctionDefinition(stmt) => v.visit_yul_fun_def(stmt), - YulStatement::If(loc, expr, block) => v.visit_yul_if(*loc, expr, block), - YulStatement::Leave(loc) => v.visit_yul_leave(*loc), - YulStatement::Switch(stmt) => v.visit_yul_switch(stmt), - YulStatement::VariableDeclaration(loc, idents, expr) => { - v.visit_yul_var_declaration(*loc, idents, expr) - } - YulStatement::Error(loc) => v.visit_parser_error(*loc), - } - } -} - -macro_rules! impl_visitable { - ($type:ty, $func:ident) => { - impl Visitable for $type { - fn visit(&mut self, v: &mut V) -> Result<(), V::Error> - where - V: Visitor, - { - v.$func(self) - } - } - }; -} - -impl_visitable!(SourceUnit, visit_source_unit); -impl_visitable!(FunctionAttribute, visit_function_attribute); -impl_visitable!(VariableAttribute, visit_var_attribute); -impl_visitable!(Parameter, visit_parameter); -impl_visitable!(Base, visit_base); -impl_visitable!(EventParameter, visit_event_parameter); -impl_visitable!(ErrorParameter, visit_error_parameter); -impl_visitable!(IdentifierPath, visit_ident_path); -impl_visitable!(YulExpression, visit_yul_expr); -impl_visitable!(YulTypedIdentifier, visit_yul_typed_ident); diff --git a/forge-fmt/testdata/Annotation/fmt.sol b/forge-fmt/testdata/Annotation/fmt.sol deleted file mode 100644 index 75bbcf2dd..000000000 --- a/forge-fmt/testdata/Annotation/fmt.sol +++ /dev/null @@ -1,15 +0,0 @@ -// Support for Solana/Substrate annotations -contract A { - @selector([1, 2, 3, 4]) - function foo() public {} - - @selector("another one") - function bar() public {} - - @first("") - @second("") - function foobar() public {} -} - -@topselector(2) -contract B {} diff --git a/forge-fmt/testdata/Annotation/original.sol b/forge-fmt/testdata/Annotation/original.sol deleted file mode 100644 index 4551f7d1e..000000000 --- a/forge-fmt/testdata/Annotation/original.sol +++ /dev/null @@ -1,15 +0,0 @@ -// Support for Solana/Substrate annotations -contract A { - @selector([1,2,3,4]) - function foo() public {} - - @selector("another one") - function bar() public {} - - @first("") - @second("") - function foobar() public {} -} - -@topselector(2) -contract B {} diff --git a/forge-fmt/testdata/ArrayExpressions/fmt.sol b/forge-fmt/testdata/ArrayExpressions/fmt.sol deleted file mode 100644 index 0476da9e1..000000000 --- a/forge-fmt/testdata/ArrayExpressions/fmt.sol +++ /dev/null @@ -1,69 +0,0 @@ -contract ArrayExpressions { - function test() external { - /* ARRAY SUBSCRIPT */ - uint256[10] memory sample; - - uint256 length = 10; - uint256[] memory sample2 = new uint[](length); - - uint256[] /* comment1 */ memory /* comment2 */ sample3; // comment3 - - /* ARRAY SLICE */ - msg.data[4:]; - msg.data[:msg.data.length]; - msg.data[4:msg.data.length]; - - msg.data[ - // comment1 - 4: - ]; - msg.data[ - : /* comment2 */ msg.data.length // comment3 - ]; - msg.data[ - // comment4 - 4: // comment5 - msg.data.length /* comment6 */ - ]; - - uint256 - someVeryVeryVeryLongVariableNameThatDenotesTheStartOfTheMessageDataSlice = - 4; - uint256 - someVeryVeryVeryLongVariableNameThatDenotesTheEndOfTheMessageDataSlice = - msg.data.length; - msg.data[ - someVeryVeryVeryLongVariableNameThatDenotesTheStartOfTheMessageDataSlice: - ]; - msg.data[ - :someVeryVeryVeryLongVariableNameThatDenotesTheEndOfTheMessageDataSlice - ]; - msg.data[ - someVeryVeryVeryLongVariableNameThatDenotesTheStartOfTheMessageDataSlice: - someVeryVeryVeryLongVariableNameThatDenotesTheEndOfTheMessageDataSlice - ]; - - /* ARRAY LITERAL */ - [1, 2, 3]; - - uint256 someVeryVeryLongVariableName = 0; - [ - someVeryVeryLongVariableName, - someVeryVeryLongVariableName, - someVeryVeryLongVariableName - ]; - uint256[3] memory literal = [ - someVeryVeryLongVariableName, - someVeryVeryLongVariableName, - someVeryVeryLongVariableName - ]; - - uint8[3] memory literal2 = /* comment7 */ [ // comment8 - 1, - 2, /* comment9 */ - 3 // comment10 - ]; - uint256[1] memory literal3 = - [ /* comment11 */ someVeryVeryLongVariableName /* comment13 */ ]; - } -} diff --git a/forge-fmt/testdata/ArrayExpressions/original.sol b/forge-fmt/testdata/ArrayExpressions/original.sol deleted file mode 100644 index 7f26bd5f7..000000000 --- a/forge-fmt/testdata/ArrayExpressions/original.sol +++ /dev/null @@ -1,46 +0,0 @@ -contract ArrayExpressions { - function test() external { - /* ARRAY SUBSCRIPT */ - uint[10] memory sample; - - uint256 length = 10; - uint[] memory sample2 = new uint[](length); - - uint /* comment1 */ [] memory /* comment2 */ sample3 // comment3 - ; - - /* ARRAY SLICE */ - msg.data[4:]; - msg.data[:msg.data.length]; - msg.data[4:msg.data.length]; - - msg.data[ - // comment1 - 4:]; - msg.data[ - : /* comment2 */ msg.data.length // comment3 - ]; - msg.data[ - // comment4 - 4 // comment5 - :msg.data.length /* comment6 */]; - - uint256 someVeryVeryVeryLongVariableNameThatDenotesTheStartOfTheMessageDataSlice = 4; - uint256 someVeryVeryVeryLongVariableNameThatDenotesTheEndOfTheMessageDataSlice = msg.data.length; - msg.data[someVeryVeryVeryLongVariableNameThatDenotesTheStartOfTheMessageDataSlice:]; - msg.data[:someVeryVeryVeryLongVariableNameThatDenotesTheEndOfTheMessageDataSlice]; - msg.data[someVeryVeryVeryLongVariableNameThatDenotesTheStartOfTheMessageDataSlice:someVeryVeryVeryLongVariableNameThatDenotesTheEndOfTheMessageDataSlice]; - - /* ARRAY LITERAL */ - [1, 2, 3]; - - uint256 someVeryVeryLongVariableName = 0; - [someVeryVeryLongVariableName, someVeryVeryLongVariableName, someVeryVeryLongVariableName]; - uint256[3] memory literal = [someVeryVeryLongVariableName,someVeryVeryLongVariableName,someVeryVeryLongVariableName]; - - uint8[3] memory literal2 = /* comment7 */ [ // comment8 - 1, 2, /* comment9 */ 3 // comment10 - ]; - uint256[1] memory literal3 = [ /* comment11 */ someVeryVeryLongVariableName /* comment13 */]; - } -} \ No newline at end of file diff --git a/forge-fmt/testdata/ConditionalOperatorExpression/fmt.sol b/forge-fmt/testdata/ConditionalOperatorExpression/fmt.sol deleted file mode 100644 index 733ae2d8c..000000000 --- a/forge-fmt/testdata/ConditionalOperatorExpression/fmt.sol +++ /dev/null @@ -1,37 +0,0 @@ -contract TernaryExpression { - function test() external { - bool condition; - bool someVeryVeryLongConditionUsedInTheTernaryExpression; - - condition ? 0 : 1; - - someVeryVeryLongConditionUsedInTheTernaryExpression - ? 1234567890 - : 987654321; - - condition /* comment1 */ /* comment2 */ - ? 1001 /* comment3 */ /* comment4 */ - : 2002; - - // comment5 - someVeryVeryLongConditionUsedInTheTernaryExpression - ? 1 - // comment6 - // comment7 - : 0; // comment8 - - uint256 amount = msg.value > 0 - ? msg.value - : parseAmount(IERC20(asset).balanceOf(msg.sender), msg.data); - - uint256 amount = msg.value > 0 - ? msg.value - // comment9 - : parseAmount(IERC20(asset).balanceOf(msg.sender), msg.data); - - uint256 amount = msg.value > 0 - // comment10 - ? msg.value - : parseAmount(IERC20(asset).balanceOf(msg.sender), msg.data); - } -} diff --git a/forge-fmt/testdata/ConditionalOperatorExpression/original.sol b/forge-fmt/testdata/ConditionalOperatorExpression/original.sol deleted file mode 100644 index f03328873..000000000 --- a/forge-fmt/testdata/ConditionalOperatorExpression/original.sol +++ /dev/null @@ -1,33 +0,0 @@ -contract TernaryExpression { - function test() external { - bool condition; - bool someVeryVeryLongConditionUsedInTheTernaryExpression; - - condition ? 0 : 1; - - someVeryVeryLongConditionUsedInTheTernaryExpression ? 1234567890 : 987654321; - - condition /* comment1 */ ? /* comment2 */ 1001 /* comment3 */ : /* comment4 */ 2002; - - // comment5 - someVeryVeryLongConditionUsedInTheTernaryExpression ? 1 - // comment6 - : - // comment7 - 0; // comment8 - - uint256 amount = msg.value > 0 - ? msg.value - : parseAmount(IERC20(asset).balanceOf(msg.sender), msg.data); - - uint256 amount = msg.value > 0 - ? msg.value - // comment9 - : parseAmount(IERC20(asset).balanceOf(msg.sender), msg.data); - - uint amount = msg.value > 0 - // comment10 - ? msg.value - : parseAmount(IERC20(asset).balanceOf(msg.sender), msg.data); - } -} \ No newline at end of file diff --git a/forge-fmt/testdata/ConstructorDefinition/fmt.sol b/forge-fmt/testdata/ConstructorDefinition/fmt.sol deleted file mode 100644 index dd62a0cf4..000000000 --- a/forge-fmt/testdata/ConstructorDefinition/fmt.sol +++ /dev/null @@ -1,42 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.5.2; - -// comment block starts here -// comment block continues -// - -// comment block 2 starts here -// comment block 2 continues - -contract Constructors is Ownable, Changeable { - function Constructors(variable1) - public - Changeable(variable1) - Ownable() - onlyOwner - {} - - constructor( - variable1, - variable2, - variable3, - variable4, - variable5, - variable6, - variable7 - ) - public - Changeable( - variable1, - variable2, - variable3, - variable4, - variable5, - variable6, - variable7 - ) - Ownable() - onlyOwner - {} -} diff --git a/forge-fmt/testdata/ConstructorDefinition/original.sol b/forge-fmt/testdata/ConstructorDefinition/original.sol deleted file mode 100644 index f69205196..000000000 --- a/forge-fmt/testdata/ConstructorDefinition/original.sol +++ /dev/null @@ -1,17 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.5.2; - -// comment block starts here -// comment block continues -// - -// comment block 2 starts here -// comment block 2 continues - -contract Constructors is Ownable, Changeable { - function Constructors(variable1) public Changeable(variable1) Ownable() onlyOwner { - } - - constructor(variable1, variable2, variable3, variable4, variable5, variable6, variable7) public Changeable(variable1, variable2, variable3, variable4, variable5, variable6, variable7) Ownable() onlyOwner {} -} diff --git a/forge-fmt/testdata/ContractDefinition/bracket-spacing.fmt.sol b/forge-fmt/testdata/ContractDefinition/bracket-spacing.fmt.sol deleted file mode 100644 index dca4e325d..000000000 --- a/forge-fmt/testdata/ContractDefinition/bracket-spacing.fmt.sol +++ /dev/null @@ -1,37 +0,0 @@ -// config: line_length = 160 -// config: bracket_spacing = true -contract ContractDefinition is Contract1, Contract2, Contract3, Contract4, Contract5 { } - -// comment 7 -contract SampleContract { - // spaced comment 1 - - // spaced comment 2 - // that spans multiple lines - - // comment 8 - constructor() { /* comment 9 */ } // comment 10 - - // comment 11 - function max( /* comment 13 */ uint256 arg1, uint256 /* comment 14 */ arg2, uint256 /* comment 15 */ ) - // comment 16 - external /* comment 17 */ - pure - returns (uint256) - // comment 18 - { - // comment 19 - return arg1 > arg2 ? arg1 : arg2; - } -} - -// comment 20 -contract /* comment 21 */ ExampleContract is /* comment 22 */ SampleContract { } - -contract ERC20DecimalsMock is ERC20 { - uint8 private immutable _decimals; - - constructor(string memory name_, string memory symbol_, uint8 decimals_) ERC20(name_, symbol_) { - _decimals = decimals_; - } -} diff --git a/forge-fmt/testdata/ContractDefinition/contract-new-lines.fmt.sol b/forge-fmt/testdata/ContractDefinition/contract-new-lines.fmt.sol deleted file mode 100644 index 2e9661f95..000000000 --- a/forge-fmt/testdata/ContractDefinition/contract-new-lines.fmt.sol +++ /dev/null @@ -1,52 +0,0 @@ -// config: contract_new_lines = true -contract ContractDefinition is - Contract1, - Contract2, - Contract3, - Contract4, - Contract5 -{} - -// comment 7 -contract SampleContract { - - // spaced comment 1 - - // spaced comment 2 - // that spans multiple lines - - // comment 8 - constructor() { /* comment 9 */ } // comment 10 - - // comment 11 - function max( /* comment 13 */ - uint256 arg1, - uint256 /* comment 14 */ arg2, - uint256 /* comment 15 */ - ) - // comment 16 - external /* comment 17 */ - pure - returns (uint256) - // comment 18 - { - // comment 19 - return arg1 > arg2 ? arg1 : arg2; - } - -} - -// comment 20 -contract /* comment 21 */ ExampleContract is /* comment 22 */ SampleContract {} - -contract ERC20DecimalsMock is ERC20 { - - uint8 private immutable _decimals; - - constructor(string memory name_, string memory symbol_, uint8 decimals_) - ERC20(name_, symbol_) - { - _decimals = decimals_; - } - -} diff --git a/forge-fmt/testdata/ContractDefinition/fmt.sol b/forge-fmt/testdata/ContractDefinition/fmt.sol deleted file mode 100644 index 551e84dec..000000000 --- a/forge-fmt/testdata/ContractDefinition/fmt.sol +++ /dev/null @@ -1,47 +0,0 @@ -contract ContractDefinition is - Contract1, - Contract2, - Contract3, - Contract4, - Contract5 -{} - -// comment 7 -contract SampleContract { - // spaced comment 1 - - // spaced comment 2 - // that spans multiple lines - - // comment 8 - constructor() { /* comment 9 */ } // comment 10 - - // comment 11 - function max( /* comment 13 */ - uint256 arg1, - uint256 /* comment 14 */ arg2, - uint256 /* comment 15 */ - ) - // comment 16 - external /* comment 17 */ - pure - returns (uint256) - // comment 18 - { - // comment 19 - return arg1 > arg2 ? arg1 : arg2; - } -} - -// comment 20 -contract /* comment 21 */ ExampleContract is /* comment 22 */ SampleContract {} - -contract ERC20DecimalsMock is ERC20 { - uint8 private immutable _decimals; - - constructor(string memory name_, string memory symbol_, uint8 decimals_) - ERC20(name_, symbol_) - { - _decimals = decimals_; - } -} diff --git a/forge-fmt/testdata/ContractDefinition/original.sol b/forge-fmt/testdata/ContractDefinition/original.sol deleted file mode 100644 index 4c671985b..000000000 --- a/forge-fmt/testdata/ContractDefinition/original.sol +++ /dev/null @@ -1,40 +0,0 @@ -contract ContractDefinition is Contract1, Contract2, Contract3, Contract4, Contract5 { -} - -// comment 7 -contract SampleContract { - - // spaced comment 1 - - // spaced comment 2 - // that spans multiple lines - - // comment 8 - constructor() { /* comment 9 */ } // comment 10 - - // comment 11 - function max(/* comment 13 */ uint256 arg1, uint256 /* comment 14 */ arg2, uint256 /* comment 15 */) - // comment 16 - external /* comment 17 */ - pure - returns(uint256) - // comment 18 - { // comment 19 - return arg1 > arg2 ? arg1 : arg2; - } -} - -// comment 20 -contract /* comment 21 */ ExampleContract /* comment 22 */ is SampleContract {} - -contract ERC20DecimalsMock is ERC20 { - uint8 private immutable _decimals; - - constructor( - string memory name_, - string memory symbol_, - uint8 decimals_ - ) ERC20(name_, symbol_) { - _decimals = decimals_; - } -} diff --git a/forge-fmt/testdata/DoWhileStatement/fmt.sol b/forge-fmt/testdata/DoWhileStatement/fmt.sol deleted file mode 100644 index c3c8c71c5..000000000 --- a/forge-fmt/testdata/DoWhileStatement/fmt.sol +++ /dev/null @@ -1,30 +0,0 @@ -pragma solidity ^0.8.8; - -contract DoWhileStatement { - function test() external { - uint256 i; - do { - "test"; - } while (i != 0); - - do {} while (i != 0); - - bool someVeryVeryLongCondition; - do { - "test"; - } while ( - someVeryVeryLongCondition && !someVeryVeryLongCondition - && !someVeryVeryLongCondition && someVeryVeryLongCondition - ); - - do { - i++; - } while (i < 10); - - do { - do { - i++; - } while (i < 30); - } while (i < 20); - } -} diff --git a/forge-fmt/testdata/DoWhileStatement/original.sol b/forge-fmt/testdata/DoWhileStatement/original.sol deleted file mode 100644 index 51063c878..000000000 --- a/forge-fmt/testdata/DoWhileStatement/original.sol +++ /dev/null @@ -1,24 +0,0 @@ -pragma solidity ^0.8.8; - - contract DoWhileStatement { - function test() external { - uint256 i; - do { "test"; } while (i != 0); - - do - {} - while - ( - i != 0); - - bool someVeryVeryLongCondition; - do { "test"; } while( - someVeryVeryLongCondition && !someVeryVeryLongCondition && -!someVeryVeryLongCondition && - someVeryVeryLongCondition); - - do i++; while(i < 10); - - do do i++; while (i < 30); while(i < 20); - } -} \ No newline at end of file diff --git a/forge-fmt/testdata/DocComments/fmt.sol b/forge-fmt/testdata/DocComments/fmt.sol deleted file mode 100644 index a002e6c23..000000000 --- a/forge-fmt/testdata/DocComments/fmt.sol +++ /dev/null @@ -1,100 +0,0 @@ -pragma solidity ^0.8.13; - -/// @title A Hello world example -contract HelloWorld { - /// Some example struct - struct Person { - uint256 age; - address wallet; - } - - /** - * Here's a more double asterix comment - */ - Person public theDude; - - /// Constructs the dude - /// @param age The dude's age - constructor(uint256 age) { - theDude = Person({age: age, wallet: msg.sender}); - } - - /** - * @dev does nothing - */ - function example() public { - /** - * Does this add a whitespace error? - * - * Let's find out. - */ - } - - /** - * @dev Calculates a rectangle's surface and perimeter. - * @param w Width of the rectangle. - * @param h Height of the rectangle. - * @return s The calculated surface. - * @return p The calculated perimeter. - */ - function rectangle(uint256 w, uint256 h) - public - pure - returns (uint256 s, uint256 p) - { - s = w * h; - p = 2 * (w + h); - } - - /// A long doc line comment that will be wrapped - function docLineOverflow() external {} - - function docLinePostfixOveflow() external {} - - /// A long doc line comment that will be wrapped - - /** - * @notice Here is my comment - * - item 1 - * - item 2 - * Some equations: - * y = mx + b - */ - function anotherExample() external {} - - /** - * contract A { - * function foo() public { - * // does nothing. - * } - * } - */ - function multilineIndent() external {} - - /** - * contract A { - * function foo() public { - * // does nothing. - * } - * } - */ - function multilineMalformedIndent() external {} - - /** - * contract A { - * function withALongNameThatWillCauseCommentWrap() public { - * // does nothing. - * } - * } - */ - function malformedIndentOveflow() external {} -} - -/** - * contract A { - * function foo() public { - * // does nothing. - * } - * } - */ -function freeFloatingMultilineIndent() {} diff --git a/forge-fmt/testdata/DocComments/original.sol b/forge-fmt/testdata/DocComments/original.sol deleted file mode 100644 index dff602236..000000000 --- a/forge-fmt/testdata/DocComments/original.sol +++ /dev/null @@ -1,95 +0,0 @@ -pragma solidity ^0.8.13; - -/// @title A Hello world example -contract HelloWorld { - - /// Some example struct - struct Person { - uint age; - address wallet; - } - - /** - Here's a more double asterix comment - */ - Person public theDude; - - /// Constructs the dude - /// @param age The dude's age - constructor(uint256 age) { - theDude = Person({ - age: age, - wallet: msg.sender - }); - } - - /** @dev does nothing */ - function example() public { - /** - * Does this add a whitespace error? - * - * Let's find out. - */ - } - - /** @dev Calculates a rectangle's surface and perimeter. - * @param w Width of the rectangle. - * @param h Height of the rectangle. - * @return s The calculated surface. -* @return p The calculated perimeter. - */ - function rectangle(uint256 w, uint256 h) public pure returns (uint256 s, uint256 p) { - s = w * h; - p = 2 * (w + h); - } - - /// A long doc line comment that will be wrapped - function docLineOverflow() external {} - - function docLinePostfixOveflow() external {} /// A long doc line comment that will be wrapped - - /** - * @notice Here is my comment - * - item 1 - * - item 2 - * Some equations: - * y = mx + b - */ - function anotherExample() external {} - - /** - contract A { - function foo() public { - // does nothing. - } - } - */ - function multilineIndent() external {} - - /** - contract A { -function foo() public { - // does nothing. - } - } - */ - function multilineMalformedIndent() external {} - - /** - contract A { -function withALongNameThatWillCauseCommentWrap() public { - // does nothing. - } - } - */ - function malformedIndentOveflow() external {} -} - -/** -contract A { - function foo() public { - // does nothing. - } -} -*/ -function freeFloatingMultilineIndent() {} diff --git a/forge-fmt/testdata/DocComments/wrap-comments.fmt.sol b/forge-fmt/testdata/DocComments/wrap-comments.fmt.sol deleted file mode 100644 index 6d7f3c584..000000000 --- a/forge-fmt/testdata/DocComments/wrap-comments.fmt.sol +++ /dev/null @@ -1,128 +0,0 @@ -// config: line_length = 40 -// config: wrap_comments = true -pragma solidity ^0.8.13; - -/// @title A Hello world example -contract HelloWorld { - /// Some example struct - struct Person { - uint256 age; - address wallet; - } - - /** - * Here's a more double asterix - * comment - */ - Person public theDude; - - /// Constructs the dude - /// @param age The dude's age - constructor(uint256 age) { - theDude = Person({ - age: age, - wallet: msg.sender - }); - } - - /** - * @dev does nothing - */ - function example() public { - /** - * Does this add a whitespace - * error? - * - * Let's find out. - */ - } - - /** - * @dev Calculates a rectangle's - * surface and perimeter. - * @param w Width of the rectangle. - * @param h Height of the rectangle. - * @return s The calculated surface. - * @return p The calculated - * perimeter. - */ - function rectangle( - uint256 w, - uint256 h - ) - public - pure - returns (uint256 s, uint256 p) - { - s = w * h; - p = 2 * (w + h); - } - - /// A long doc line comment that - /// will be wrapped - function docLineOverflow() - external - {} - - function docLinePostfixOveflow() - external - {} - - /// A long doc line comment that - /// will be wrapped - - /** - * @notice Here is my comment - * - item 1 - * - item 2 - * Some equations: - * y = mx + b - */ - function anotherExample() - external - {} - - /** - * contract A { - * function foo() public { - * // does nothing. - * } - * } - */ - function multilineIndent() - external - {} - - /** - * contract A { - * function foo() public { - * // does nothing. - * } - * } - */ - function multilineMalformedIndent() - external - {} - - /** - * contract A { - * function - * withALongNameThatWillCauseCommentWrap() - * public { - * // does nothing. - * } - * } - */ - function malformedIndentOveflow() - external - {} -} - -/** - * contract A { - * function foo() public { - * // does nothing. - * } - * } - */ -function freeFloatingMultilineIndent() {} diff --git a/forge-fmt/testdata/EmitStatement/fmt.sol b/forge-fmt/testdata/EmitStatement/fmt.sol deleted file mode 100644 index 0fac66b9b..000000000 --- a/forge-fmt/testdata/EmitStatement/fmt.sol +++ /dev/null @@ -1,31 +0,0 @@ -// config: line_length = 80 -event NewEvent( - address beneficiary, uint256 index, uint64 timestamp, uint64 endTimestamp -); - -function emitEvent() { - emit NewEvent( - beneficiary, - _vestingBeneficiaries.length - 1, - uint64(block.timestamp), - endTimestamp - ); - - emit NewEvent( - /* beneficiary */ - beneficiary, - /* index */ - _vestingBeneficiaries.length - 1, - /* timestamp */ - uint64(block.timestamp), - /* end timestamp */ - endTimestamp - ); - - emit NewEvent( - beneficiary, // beneficiary - _vestingBeneficiaries.length - 1, // index - uint64(block.timestamp), // timestamp - endTimestamp // end timestamp - ); -} diff --git a/forge-fmt/testdata/EmitStatement/original.sol b/forge-fmt/testdata/EmitStatement/original.sol deleted file mode 100644 index 661abb782..000000000 --- a/forge-fmt/testdata/EmitStatement/original.sol +++ /dev/null @@ -1,24 +0,0 @@ -event NewEvent(address beneficiary, uint256 index, uint64 timestamp, uint64 endTimestamp); - -function emitEvent() { - emit NewEvent( - beneficiary, - _vestingBeneficiaries.length - 1, - uint64(block.timestamp), - endTimestamp - ); - - emit - NewEvent( - /* beneficiary */ beneficiary, - /* index */ _vestingBeneficiaries.length - 1, - /* timestamp */ uint64(block.timestamp), - /* end timestamp */ endTimestamp); - - emit NewEvent( - beneficiary, // beneficiary - _vestingBeneficiaries.length - 1, // index - uint64(block.timestamp), // timestamp - endTimestamp // end timestamp - ); -} diff --git a/forge-fmt/testdata/EnumDefinition/bracket-spacing.fmt.sol b/forge-fmt/testdata/EnumDefinition/bracket-spacing.fmt.sol deleted file mode 100644 index a4ae0f019..000000000 --- a/forge-fmt/testdata/EnumDefinition/bracket-spacing.fmt.sol +++ /dev/null @@ -1,21 +0,0 @@ -// config: bracket_spacing = true -contract EnumDefinitions { - enum Empty { } - enum ActionChoices { - GoLeft, - GoRight, - GoStraight, - SitStill - } - enum States { - State1, - State2, - State3, - State4, - State5, - State6, - State7, - State8, - State9 - } -} diff --git a/forge-fmt/testdata/EnumDefinition/fmt.sol b/forge-fmt/testdata/EnumDefinition/fmt.sol deleted file mode 100644 index 437268aff..000000000 --- a/forge-fmt/testdata/EnumDefinition/fmt.sol +++ /dev/null @@ -1,20 +0,0 @@ -contract EnumDefinitions { - enum Empty {} - enum ActionChoices { - GoLeft, - GoRight, - GoStraight, - SitStill - } - enum States { - State1, - State2, - State3, - State4, - State5, - State6, - State7, - State8, - State9 - } -} diff --git a/forge-fmt/testdata/EnumDefinition/original.sol b/forge-fmt/testdata/EnumDefinition/original.sol deleted file mode 100644 index 69aadf884..000000000 --- a/forge-fmt/testdata/EnumDefinition/original.sol +++ /dev/null @@ -1,7 +0,0 @@ -contract EnumDefinitions { - enum Empty { - - } - enum ActionChoices { GoLeft, GoRight, GoStraight, SitStill } - enum States { State1, State2, State3, State4, State5, State6, State7, State8, State9 } -} \ No newline at end of file diff --git a/forge-fmt/testdata/ErrorDefinition/fmt.sol b/forge-fmt/testdata/ErrorDefinition/fmt.sol deleted file mode 100644 index b94bbe45d..000000000 --- a/forge-fmt/testdata/ErrorDefinition/fmt.sol +++ /dev/null @@ -1,14 +0,0 @@ -pragma solidity ^0.8.4; - -error TopLevelCustomError(); -error TopLevelCustomErrorWithArg(uint256 x); -error TopLevelCustomErrorArgWithoutName(string); -error Error1( - uint256, uint256, uint256, uint256, uint256, uint256, uint256, uint256 -); - -contract Errors { - error ContractCustomError(); - error ContractCustomErrorWithArg(uint256 x); - error ContractCustomErrorArgWithoutName(string); -} diff --git a/forge-fmt/testdata/ErrorDefinition/original.sol b/forge-fmt/testdata/ErrorDefinition/original.sol deleted file mode 100644 index f9524c22a..000000000 --- a/forge-fmt/testdata/ErrorDefinition/original.sol +++ /dev/null @@ -1,14 +0,0 @@ -pragma solidity ^0.8.4; - -error - TopLevelCustomError(); - error TopLevelCustomErrorWithArg(uint x) ; -error TopLevelCustomErrorArgWithoutName (string); -error Error1(uint256, uint256, uint256, uint256, uint256, uint256, uint256, uint256); - -contract Errors { - error - ContractCustomError(); - error ContractCustomErrorWithArg(uint x) ; - error ContractCustomErrorArgWithoutName (string); -} \ No newline at end of file diff --git a/forge-fmt/testdata/EventDefinition/fmt.sol b/forge-fmt/testdata/EventDefinition/fmt.sol deleted file mode 100644 index 11d3d8256..000000000 --- a/forge-fmt/testdata/EventDefinition/fmt.sol +++ /dev/null @@ -1,144 +0,0 @@ -pragma solidity ^0.5.2; - -contract Events { - event Event1(); - event Event1() anonymous; - - event Event1(uint256); - event Event1(uint256) anonymous; - - event Event1(uint256 a); - event Event1(uint256 a) anonymous; - - event Event1(uint256 indexed); - event Event1(uint256 indexed) anonymous; - - event Event1(uint256 indexed a); - event Event1(uint256 indexed a) anonymous; - - event Event1(uint256, uint256, uint256, uint256, uint256, uint256, uint256); - event Event1( - uint256, uint256, uint256, uint256, uint256, uint256, uint256 - ) anonymous; - - event Event1( - uint256, uint256, uint256, uint256, uint256, uint256, uint256, uint256 - ); - event Event1( - uint256, uint256, uint256, uint256, uint256, uint256, uint256, uint256 - ) anonymous; - - event Event1( - uint256, - uint256, - uint256, - uint256, - uint256, - uint256, - uint256, - uint256, - uint256, - uint256, - uint256, - uint256, - uint256, - uint256, - uint256 - ); - event Event1( - uint256, - uint256, - uint256, - uint256, - uint256, - uint256, - uint256, - uint256, - uint256, - uint256, - uint256, - uint256, - uint256, - uint256, - uint256 - ) anonymous; - - event Event1( - uint256 a, - uint256 a, - uint256 a, - uint256 a, - uint256 a, - uint256 a, - uint256 a, - uint256 a, - uint256 a, - uint256 a, - uint256 a, - uint256 a, - uint256 a - ); - event Event1( - uint256 a, - uint256 a, - uint256 a, - uint256 a, - uint256 a, - uint256 a, - uint256 a, - uint256 a, - uint256 a, - uint256 a, - uint256 a, - uint256 a, - uint256 a - ) anonymous; - - event Event1( - uint256 indexed, - uint256 indexed, - uint256 indexed, - uint256 indexed, - uint256 indexed, - uint256 indexed, - uint256 indexed, - uint256 indexed, - uint256 indexed - ); - event Event1( - uint256 indexed, - uint256 indexed, - uint256 indexed, - uint256 indexed, - uint256 indexed, - uint256 indexed, - uint256 indexed, - uint256 indexed, - uint256 indexed - ) anonymous; - - event Event1( - uint256 indexed a, - uint256 indexed a, - uint256 indexed a, - uint256 indexed a, - uint256 indexed a, - uint256 indexed a, - uint256 indexed a, - uint256 indexed a, - uint256 indexed a, - uint256 indexed a - ); - event Event1( - uint256 indexed a, - uint256 indexed a, - uint256 indexed a, - uint256 indexed a, - uint256 indexed a, - uint256 indexed a, - uint256 indexed a, - uint256 indexed a, - uint256 indexed a, - uint256 indexed a - ) anonymous; -} diff --git a/forge-fmt/testdata/EventDefinition/original.sol b/forge-fmt/testdata/EventDefinition/original.sol deleted file mode 100644 index d2a615162..000000000 --- a/forge-fmt/testdata/EventDefinition/original.sol +++ /dev/null @@ -1,36 +0,0 @@ -pragma solidity ^0.5.2; - -contract Events { - event Event1(); - event Event1() anonymous; - - event Event1(uint256); - event Event1(uint256) anonymous; - - event Event1(uint256 a); - event Event1(uint256 a) anonymous; - - event Event1(uint256 indexed); - event Event1(uint256 indexed) anonymous; - - event Event1(uint256 indexed a); - event Event1(uint256 indexed a) anonymous; - - event Event1(uint256, uint256, uint256, uint256, uint256, uint256, uint256); - event Event1(uint256, uint256, uint256, uint256, uint256, uint256, uint256) anonymous; - - event Event1(uint256, uint256, uint256, uint256, uint256, uint256, uint256, uint256); - event Event1(uint256, uint256, uint256, uint256, uint256, uint256, uint256, uint256) anonymous; - - event Event1(uint256, uint256, uint256, uint256, uint256, uint256, uint256, uint256, uint256, uint256, uint256, uint256, uint256, uint256, uint256); - event Event1(uint256, uint256, uint256, uint256, uint256, uint256, uint256, uint256, uint256, uint256, uint256, uint256, uint256, uint256, uint256) anonymous; - - event Event1(uint256 a, uint256 a, uint256 a, uint256 a, uint256 a, uint256 a, uint256 a, uint256 a, uint256 a, uint256 a, uint256 a, uint256 a, uint256 a); - event Event1(uint256 a, uint256 a, uint256 a, uint256 a, uint256 a, uint256 a, uint256 a, uint256 a, uint256 a, uint256 a, uint256 a, uint256 a, uint256 a) anonymous; - - event Event1(uint256 indexed, uint256 indexed, uint256 indexed, uint256 indexed, uint256 indexed, uint256 indexed, uint256 indexed, uint256 indexed, uint256 indexed); - event Event1(uint256 indexed, uint256 indexed, uint256 indexed, uint256 indexed, uint256 indexed, uint256 indexed, uint256 indexed, uint256 indexed, uint256 indexed) anonymous; - - event Event1(uint256 indexed a, uint256 indexed a, uint256 indexed a, uint256 indexed a, uint256 indexed a, uint256 indexed a, uint256 indexed a, uint256 indexed a, uint256 indexed a, uint256 indexed a); - event Event1(uint256 indexed a, uint256 indexed a, uint256 indexed a, uint256 indexed a, uint256 indexed a, uint256 indexed a, uint256 indexed a, uint256 indexed a, uint256 indexed a, uint256 indexed a) anonymous; -} diff --git a/forge-fmt/testdata/ForStatement/fmt.sol b/forge-fmt/testdata/ForStatement/fmt.sol deleted file mode 100644 index a1bb4b2e6..000000000 --- a/forge-fmt/testdata/ForStatement/fmt.sol +++ /dev/null @@ -1,37 +0,0 @@ -pragma solidity ^0.8.8; - -contract ForStatement { - function test() external { - for (uint256 i1; i1 < 10; i1++) { - i1++; - } - - uint256 i2; - for (++i2; i2 < 10; i2++) {} - - uint256 veryLongVariableName = 1000; - for ( - uint256 i3; - i3 < 10 && veryLongVariableName > 999 && veryLongVariableName < 1001; - i3++ - ) { - i3++; - } - - for (type(uint256).min;;) {} - - for (;;) { - "test"; - } - - for (uint256 i4; i4 < 10; i4++) { - i4++; - } - - for (uint256 i5;;) { - for (uint256 i6 = 10; i6 > i5; i6--) { - i5++; - } - } - } -} diff --git a/forge-fmt/testdata/ForStatement/original.sol b/forge-fmt/testdata/ForStatement/original.sol deleted file mode 100644 index e98288dd1..000000000 --- a/forge-fmt/testdata/ForStatement/original.sol +++ /dev/null @@ -1,33 +0,0 @@ -pragma solidity ^0.8.8; - -contract ForStatement { - function test() external { - for - (uint256 i1 - ; i1 < 10; i1++) - { - i1++; - } - - uint256 i2; - for(++i2;i2<10;i2++) - - {} - - uint256 veryLongVariableName = 1000; - for ( uint256 i3; i3 < 10 - && veryLongVariableName>999 && veryLongVariableName< 1001 - ; i3++) - { i3 ++ ; } - - for (type(uint256).min;;) {} - - for (;;) { "test" ; } - - for (uint256 i4; i4< 10; i4++) i4++; - - for (uint256 i5; ;) - for (uint256 i6 = 10; i6 > i5; i6--) - i5++; - } -} \ No newline at end of file diff --git a/forge-fmt/testdata/FunctionCall/bracket-spacing.fmt.sol b/forge-fmt/testdata/FunctionCall/bracket-spacing.fmt.sol deleted file mode 100644 index 84a4ce8b1..000000000 --- a/forge-fmt/testdata/FunctionCall/bracket-spacing.fmt.sol +++ /dev/null @@ -1,37 +0,0 @@ -// config: line_length = 120 -// config: bracket_spacing = true -contract FunctionCall { - function foo() public pure { - bar(1111111111111111111111111111111111111111111111111111, 111111111111111111111111111111111111111111111111111); - bar(1111111111111111111111111111111111111111111111111112, 1111111111111111111111111111111111111111111111111112); - bar(1111111111111111111111111111111111111111111111111113, 11111111111111111111111111111111111111111111111111113); // the semicolon is not considered when determining line break - bar( - 1111111111111111111111111111111111111111111111111114, 111111111111111111111111111111111111111111111111111114 - ); - bar( - 111111111111111111111111111111111115, - 11111111111111111111111111111111115, - 11111111111111111111111111111111115 - ); - bar( - 111111111111111111111111111111111111111111111111111116, - 111111111111111111111111111111111111111111111111111116 - ); - bar( - 111111111111111111111111111111111111111111111111111117, - 1111111111111111111111111111111111111111111111111111117 - ); - } - - function bar(uint256, uint256) private pure { - return; - } -} - -function a(uint256 foo) { - foo; -} - -function b() { - a({ foo: 5 }); -} diff --git a/forge-fmt/testdata/FunctionCall/fmt.sol b/forge-fmt/testdata/FunctionCall/fmt.sol deleted file mode 100644 index c57c5fda0..000000000 --- a/forge-fmt/testdata/FunctionCall/fmt.sol +++ /dev/null @@ -1,36 +0,0 @@ -// config: line_length = 120 -contract FunctionCall { - function foo() public pure { - bar(1111111111111111111111111111111111111111111111111111, 111111111111111111111111111111111111111111111111111); - bar(1111111111111111111111111111111111111111111111111112, 1111111111111111111111111111111111111111111111111112); - bar(1111111111111111111111111111111111111111111111111113, 11111111111111111111111111111111111111111111111111113); // the semicolon is not considered when determining line break - bar( - 1111111111111111111111111111111111111111111111111114, 111111111111111111111111111111111111111111111111111114 - ); - bar( - 111111111111111111111111111111111115, - 11111111111111111111111111111111115, - 11111111111111111111111111111111115 - ); - bar( - 111111111111111111111111111111111111111111111111111116, - 111111111111111111111111111111111111111111111111111116 - ); - bar( - 111111111111111111111111111111111111111111111111111117, - 1111111111111111111111111111111111111111111111111111117 - ); - } - - function bar(uint256, uint256) private pure { - return; - } -} - -function a(uint256 foo) { - foo; -} - -function b() { - a({foo: 5}); -} diff --git a/forge-fmt/testdata/FunctionCall/original.sol b/forge-fmt/testdata/FunctionCall/original.sol deleted file mode 100644 index 8bdc92e46..000000000 --- a/forge-fmt/testdata/FunctionCall/original.sol +++ /dev/null @@ -1,29 +0,0 @@ -contract FunctionCall { - function foo() public pure { - bar(1111111111111111111111111111111111111111111111111111, 111111111111111111111111111111111111111111111111111); - bar(1111111111111111111111111111111111111111111111111112, 1111111111111111111111111111111111111111111111111112); - bar(1111111111111111111111111111111111111111111111111113, 11111111111111111111111111111111111111111111111111113); // the semicolon is not considered when determining line break - bar(1111111111111111111111111111111111111111111111111114, 111111111111111111111111111111111111111111111111111114); - bar( - 111111111111111111111111111111111115, 11111111111111111111111111111111115, 11111111111111111111111111111111115 - ); - bar( - 111111111111111111111111111111111111111111111111111116, 111111111111111111111111111111111111111111111111111116 - ); - bar( - 111111111111111111111111111111111111111111111111111117, 1111111111111111111111111111111111111111111111111111117 - ); - } - - function bar(uint256, uint256) private pure { - return; - } -} - -function a(uint256 foo) { - foo; -} - -function b() { - a( {foo: 5} ); -} diff --git a/forge-fmt/testdata/FunctionCallArgsStatement/bracket-spacing.fmt.sol b/forge-fmt/testdata/FunctionCallArgsStatement/bracket-spacing.fmt.sol deleted file mode 100644 index 93e5eb1a2..000000000 --- a/forge-fmt/testdata/FunctionCallArgsStatement/bracket-spacing.fmt.sol +++ /dev/null @@ -1,55 +0,0 @@ -// config: bracket_spacing = true -interface ITarget { - function run() external payable; - function veryAndVeryLongNameOfSomeRunFunction() external payable; -} - -contract FunctionCallArgsStatement { - ITarget public target; - - function estimate() public returns (uint256 gas) { - gas = 1 gwei; - } - - function veryAndVeryLongNameOfSomeGasEstimateFunction() - public - returns (uint256) - { - return gasleft(); - } - - function value(uint256 val) public returns (uint256) { - return val; - } - - function test() external { - target.run{ gas: gasleft(), value: 1 wei }; - - target.run{ gas: 1, value: 0x00 }(); - - target.run{ gas: 1000, value: 1 ether }(); - - target.run{ gas: estimate(), value: value(1) }(); - - target.run{ - value: value(1 ether), - gas: veryAndVeryLongNameOfSomeGasEstimateFunction() - }(); - - target.run{ /* comment 1 */ value: /* comment2 */ 1 }; - - target.run{ /* comment3 */ - value: 1, // comment4 - gas: gasleft() - }; - - target.run{ - // comment5 - value: 1, - // comment6 - gas: gasleft() - }; - - vm.expectEmit({ checkTopic1: false, checkTopic2: false }); - } -} diff --git a/forge-fmt/testdata/FunctionCallArgsStatement/fmt.sol b/forge-fmt/testdata/FunctionCallArgsStatement/fmt.sol deleted file mode 100644 index 5a5cc5f63..000000000 --- a/forge-fmt/testdata/FunctionCallArgsStatement/fmt.sol +++ /dev/null @@ -1,54 +0,0 @@ -interface ITarget { - function run() external payable; - function veryAndVeryLongNameOfSomeRunFunction() external payable; -} - -contract FunctionCallArgsStatement { - ITarget public target; - - function estimate() public returns (uint256 gas) { - gas = 1 gwei; - } - - function veryAndVeryLongNameOfSomeGasEstimateFunction() - public - returns (uint256) - { - return gasleft(); - } - - function value(uint256 val) public returns (uint256) { - return val; - } - - function test() external { - target.run{gas: gasleft(), value: 1 wei}; - - target.run{gas: 1, value: 0x00}(); - - target.run{gas: 1000, value: 1 ether}(); - - target.run{gas: estimate(), value: value(1)}(); - - target.run{ - value: value(1 ether), - gas: veryAndVeryLongNameOfSomeGasEstimateFunction() - }(); - - target.run{ /* comment 1 */ value: /* comment2 */ 1}; - - target.run{ /* comment3 */ - value: 1, // comment4 - gas: gasleft() - }; - - target.run{ - // comment5 - value: 1, - // comment6 - gas: gasleft() - }; - - vm.expectEmit({checkTopic1: false, checkTopic2: false}); - } -} diff --git a/forge-fmt/testdata/FunctionCallArgsStatement/original.sol b/forge-fmt/testdata/FunctionCallArgsStatement/original.sol deleted file mode 100644 index b2cfaa2f2..000000000 --- a/forge-fmt/testdata/FunctionCallArgsStatement/original.sol +++ /dev/null @@ -1,50 +0,0 @@ -interface ITarget { - function run() external payable; - function veryAndVeryLongNameOfSomeRunFunction() external payable; -} - -contract FunctionCallArgsStatement { - ITarget public target; - - function estimate() public returns (uint256 gas) { - gas = 1 gwei; - } - - function veryAndVeryLongNameOfSomeGasEstimateFunction() public returns (uint256) { - return gasleft(); - } - - function value(uint256 val) public returns (uint256) { - return val; - } - - function test() external { - target.run{ gas: gasleft(), value: 1 wei }; - - target.run{gas:1,value:0x00}(); - - target.run{ - gas : 1000, - value: 1 ether - } (); - - target.run{ gas: estimate(), - value: value(1) }(); - - target.run { value: - value(1 ether), gas: veryAndVeryLongNameOfSomeGasEstimateFunction() } (); - - target.run /* comment 1 */ { value: /* comment2 */ 1 }; - - target.run { /* comment3 */ value: 1, // comment4 - gas: gasleft()}; - - target.run { - // comment5 - value: 1, - // comment6 - gas: gasleft()}; - - vm.expectEmit({ checkTopic1: false, checkTopic2: false }); - } -} \ No newline at end of file diff --git a/forge-fmt/testdata/FunctionDefinition/all.fmt.sol b/forge-fmt/testdata/FunctionDefinition/all.fmt.sol deleted file mode 100644 index 6d9088067..000000000 --- a/forge-fmt/testdata/FunctionDefinition/all.fmt.sol +++ /dev/null @@ -1,730 +0,0 @@ -// config: line_length = 60 -// config: multiline_func_header = "all" -interface FunctionInterfaces { - function noParamsNoModifiersNoReturns(); - - function oneParam(uint256 x); - - function oneModifier() modifier1; - - function oneReturn() returns (uint256 y1); - - // function prefix - function withComments( // function name postfix - // x1 prefix - uint256 x1, // x1 postfix - // x2 prefix - uint256 x2, // x2 postfix - // x2 postfix2 - /* - multi-line x3 prefix - */ - uint256 x3 // x3 postfix - ) - // public prefix - public // public postfix - // pure prefix - pure // pure postfix - // modifier1 prefix - modifier1 // modifier1 postfix - // modifier2 prefix - modifier2 /* - mutliline modifier2 postfix - */ - // modifier3 prefix - modifier3 // modifier3 postfix - returns ( - // y1 prefix - uint256 y1, // y1 postfix - // y2 prefix - uint256 y2, // y2 postfix - // y3 prefix - uint256 y3 - ); // y3 postfix - // function postfix - - /*////////////////////////////////////////////////////////////////////////// - TEST - //////////////////////////////////////////////////////////////////////////*/ - function manyParams( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ); - - function manyModifiers() - modifier1 - modifier2 - modifier3 - modifier4 - modifier5 - modifier6 - modifier7 - modifier8 - modifier9 - modifier10; - - function manyReturns() - returns ( - uint256 y1, - uint256 y2, - uint256 y3, - uint256 y4, - uint256 y5, - uint256 y6, - uint256 y7, - uint256 y8, - uint256 y9, - uint256 y10 - ); - - function someParamsSomeModifiers( - uint256 x1, - uint256 x2, - uint256 x3 - ) - modifier1 - modifier2 - modifier3; - - function someParamsSomeReturns( - uint256 x1, - uint256 x2, - uint256 x3 - ) - returns (uint256 y1, uint256 y2, uint256 y3); - - function someModifiersSomeReturns() - modifier1 - modifier2 - modifier3 - returns (uint256 y1, uint256 y2, uint256 y3); - - function someParamSomeModifiersSomeReturns( - uint256 x1, - uint256 x2, - uint256 x3 - ) - modifier1 - modifier2 - modifier3 - returns (uint256 y1, uint256 y2, uint256 y3); - - function someParamsManyModifiers( - uint256 x1, - uint256 x2, - uint256 x3 - ) - modifier1 - modifier2 - modifier3 - modifier4 - modifier5 - modifier6 - modifier7 - modifier8 - modifier9 - modifier10; - - function someParamsManyReturns( - uint256 x1, - uint256 x2, - uint256 x3 - ) - returns ( - uint256 y1, - uint256 y2, - uint256 y3, - uint256 y4, - uint256 y5, - uint256 y6, - uint256 y7, - uint256 y8, - uint256 y9, - uint256 y10 - ); - - function manyParamsSomeModifiers( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ) - modifier1 - modifier2 - modifier3; - - function manyParamsSomeReturns( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ) - returns (uint256 y1, uint256 y2, uint256 y3); - - function manyParamsManyModifiers( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ) - modifier1 - modifier2 - modifier3 - modifier4 - modifier5 - modifier6 - modifier7 - modifier8 - modifier9 - modifier10; - - function manyParamsManyReturns( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ) - returns ( - uint256 y1, - uint256 y2, - uint256 y3, - uint256 y4, - uint256 y5, - uint256 y6, - uint256 y7, - uint256 y8, - uint256 y9, - uint256 y10 - ); - - function manyParamsManyModifiersManyReturns( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ) - modifier1 - modifier2 - modifier3 - modifier4 - modifier5 - modifier6 - modifier7 - modifier8 - modifier9 - modifier10 - returns ( - uint256 y1, - uint256 y2, - uint256 y3, - uint256 y4, - uint256 y5, - uint256 y6, - uint256 y7, - uint256 y8, - uint256 y9, - uint256 y10 - ); - - function modifierOrderCorrect01() - public - view - virtual - override - modifier1 - modifier2 - returns (uint256); - - function modifierOrderCorrect02() - private - pure - virtual - modifier1 - modifier2 - returns (string); - - function modifierOrderCorrect03() - external - payable - override - modifier1 - modifier2 - returns (address); - - function modifierOrderCorrect04() - internal - virtual - override - modifier1 - modifier2 - returns (uint256); - - function modifierOrderIncorrect01() - public - view - virtual - override - modifier1 - modifier2 - returns (uint256); - - function modifierOrderIncorrect02() - external - virtual - override - modifier1 - modifier2 - returns (uint256); - - function modifierOrderIncorrect03() - internal - pure - virtual - modifier1 - modifier2 - returns (uint256); - - function modifierOrderIncorrect04() - external - payable - override - modifier1 - modifier2 - returns (uint256); -} - -contract FunctionDefinitions { - function() external {} - fallback() external {} - - function() external payable {} - fallback() external payable {} - receive() external payable {} - - function noParamsNoModifiersNoReturns() { - a = 1; - } - - function oneParam(uint256 x) { - a = 1; - } - - function oneModifier() modifier1 { - a = 1; - } - - function oneReturn() returns (uint256 y1) { - a = 1; - } - - function manyParams( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ) { - a = 1; - } - - function manyModifiers() - modifier1 - modifier2 - modifier3 - modifier4 - modifier5 - modifier6 - modifier7 - modifier8 - modifier9 - modifier10 - { - a = 1; - } - - function manyReturns() - returns ( - uint256 y1, - uint256 y2, - uint256 y3, - uint256 y4, - uint256 y5, - uint256 y6, - uint256 y7, - uint256 y8, - uint256 y9, - uint256 y10 - ) - { - a = 1; - } - - function someParamsSomeModifiers( - uint256 x1, - uint256 x2, - uint256 x3 - ) - modifier1 - modifier2 - modifier3 - { - a = 1; - } - - function someParamsSomeReturns( - uint256 x1, - uint256 x2, - uint256 x3 - ) - returns (uint256 y1, uint256 y2, uint256 y3) - { - a = 1; - } - - function someModifiersSomeReturns() - modifier1 - modifier2 - modifier3 - returns (uint256 y1, uint256 y2, uint256 y3) - { - a = 1; - } - - function someParamSomeModifiersSomeReturns( - uint256 x1, - uint256 x2, - uint256 x3 - ) - modifier1 - modifier2 - modifier3 - returns (uint256 y1, uint256 y2, uint256 y3) - { - a = 1; - } - - function someParamsManyModifiers( - uint256 x1, - uint256 x2, - uint256 x3 - ) - modifier1 - modifier2 - modifier3 - modifier4 - modifier5 - modifier6 - modifier7 - modifier8 - modifier9 - modifier10 - { - a = 1; - } - - function someParamsManyReturns( - uint256 x1, - uint256 x2, - uint256 x3 - ) - returns ( - uint256 y1, - uint256 y2, - uint256 y3, - uint256 y4, - uint256 y5, - uint256 y6, - uint256 y7, - uint256 y8, - uint256 y9, - uint256 y10 - ) - { - a = 1; - } - - function manyParamsSomeModifiers( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ) - modifier1 - modifier2 - modifier3 - { - a = 1; - } - - function manyParamsSomeReturns( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ) - returns (uint256 y1, uint256 y2, uint256 y3) - { - a = 1; - } - - function manyParamsManyModifiers( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ) - public - modifier1 - modifier2 - modifier3 - modifier4 - modifier5 - modifier6 - modifier7 - modifier8 - modifier9 - modifier10 - { - a = 1; - } - - function manyParamsManyReturns( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ) - returns ( - uint256 y1, - uint256 y2, - uint256 y3, - uint256 y4, - uint256 y5, - uint256 y6, - uint256 y7, - uint256 y8, - uint256 y9, - uint256 y10 - ) - { - a = 1; - } - - function manyParamsManyModifiersManyReturns( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ) - modifier1 - modifier2 - modifier3 - modifier4 - modifier5 - modifier6 - modifier7 - modifier8 - modifier9 - modifier10 - returns ( - uint256 y1, - uint256 y2, - uint256 y3, - uint256 y4, - uint256 y5, - uint256 y6, - uint256 y7, - uint256 y8, - uint256 y9, - uint256 y10 - ) - { - a = 1; - } - - function modifierOrderCorrect01() - public - view - virtual - override - modifier1 - modifier2 - returns (uint256) - { - a = 1; - } - - function modifierOrderCorrect02() - private - pure - virtual - modifier1 - modifier2 - returns (string) - { - a = 1; - } - - function modifierOrderCorrect03() - external - payable - override - modifier1 - modifier2 - returns (address) - { - a = 1; - } - - function modifierOrderCorrect04() - internal - virtual - override - modifier1 - modifier2 - returns (uint256) - { - a = 1; - } - - function modifierOrderIncorrect01() - public - view - virtual - override - modifier1 - modifier2 - returns (uint256) - { - a = 1; - } - - function modifierOrderIncorrect02() - external - virtual - override - modifier1 - modifier2 - returns (uint256) - { - a = 1; - } - - function modifierOrderIncorrect03() - internal - pure - virtual - modifier1 - modifier2 - returns (uint256) - { - a = 1; - } - - function modifierOrderIncorrect04() - external - payable - override - modifier1 - modifier2 - returns (uint256) - { - a = 1; - } - - fallback() external payable virtual {} - receive() external payable virtual {} -} - -contract FunctionOverrides is - FunctionInterfaces, - FunctionDefinitions -{ - function noParamsNoModifiersNoReturns() override { - a = 1; - } - - function oneParam(uint256 x) - override( - FunctionInterfaces, - FunctionDefinitions, - SomeOtherFunctionContract, - SomeImport.AndAnotherFunctionContract - ) - { - a = 1; - } -} diff --git a/forge-fmt/testdata/FunctionDefinition/fmt.sol b/forge-fmt/testdata/FunctionDefinition/fmt.sol deleted file mode 100644 index 9e34a8bea..000000000 --- a/forge-fmt/testdata/FunctionDefinition/fmt.sol +++ /dev/null @@ -1,709 +0,0 @@ -// config: line_length = 60 -interface FunctionInterfaces { - function noParamsNoModifiersNoReturns(); - - function oneParam(uint256 x); - - function oneModifier() modifier1; - - function oneReturn() returns (uint256 y1); - - // function prefix - function withComments( // function name postfix - // x1 prefix - uint256 x1, // x1 postfix - // x2 prefix - uint256 x2, // x2 postfix - // x2 postfix2 - /* - multi-line x3 prefix - */ - uint256 x3 // x3 postfix - ) - // public prefix - public // public postfix - // pure prefix - pure // pure postfix - // modifier1 prefix - modifier1 // modifier1 postfix - // modifier2 prefix - modifier2 /* - mutliline modifier2 postfix - */ - // modifier3 prefix - modifier3 // modifier3 postfix - returns ( - // y1 prefix - uint256 y1, // y1 postfix - // y2 prefix - uint256 y2, // y2 postfix - // y3 prefix - uint256 y3 - ); // y3 postfix - // function postfix - - /*////////////////////////////////////////////////////////////////////////// - TEST - //////////////////////////////////////////////////////////////////////////*/ - function manyParams( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ); - - function manyModifiers() - modifier1 - modifier2 - modifier3 - modifier4 - modifier5 - modifier6 - modifier7 - modifier8 - modifier9 - modifier10; - - function manyReturns() - returns ( - uint256 y1, - uint256 y2, - uint256 y3, - uint256 y4, - uint256 y5, - uint256 y6, - uint256 y7, - uint256 y8, - uint256 y9, - uint256 y10 - ); - - function someParamsSomeModifiers( - uint256 x1, - uint256 x2, - uint256 x3 - ) modifier1 modifier2 modifier3; - - function someParamsSomeReturns( - uint256 x1, - uint256 x2, - uint256 x3 - ) returns (uint256 y1, uint256 y2, uint256 y3); - - function someModifiersSomeReturns() - modifier1 - modifier2 - modifier3 - returns (uint256 y1, uint256 y2, uint256 y3); - - function someParamSomeModifiersSomeReturns( - uint256 x1, - uint256 x2, - uint256 x3 - ) - modifier1 - modifier2 - modifier3 - returns (uint256 y1, uint256 y2, uint256 y3); - - function someParamsManyModifiers( - uint256 x1, - uint256 x2, - uint256 x3 - ) - modifier1 - modifier2 - modifier3 - modifier4 - modifier5 - modifier6 - modifier7 - modifier8 - modifier9 - modifier10; - - function someParamsManyReturns( - uint256 x1, - uint256 x2, - uint256 x3 - ) - returns ( - uint256 y1, - uint256 y2, - uint256 y3, - uint256 y4, - uint256 y5, - uint256 y6, - uint256 y7, - uint256 y8, - uint256 y9, - uint256 y10 - ); - - function manyParamsSomeModifiers( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ) modifier1 modifier2 modifier3; - - function manyParamsSomeReturns( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ) returns (uint256 y1, uint256 y2, uint256 y3); - - function manyParamsManyModifiers( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ) - modifier1 - modifier2 - modifier3 - modifier4 - modifier5 - modifier6 - modifier7 - modifier8 - modifier9 - modifier10; - - function manyParamsManyReturns( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ) - returns ( - uint256 y1, - uint256 y2, - uint256 y3, - uint256 y4, - uint256 y5, - uint256 y6, - uint256 y7, - uint256 y8, - uint256 y9, - uint256 y10 - ); - - function manyParamsManyModifiersManyReturns( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ) - modifier1 - modifier2 - modifier3 - modifier4 - modifier5 - modifier6 - modifier7 - modifier8 - modifier9 - modifier10 - returns ( - uint256 y1, - uint256 y2, - uint256 y3, - uint256 y4, - uint256 y5, - uint256 y6, - uint256 y7, - uint256 y8, - uint256 y9, - uint256 y10 - ); - - function modifierOrderCorrect01() - public - view - virtual - override - modifier1 - modifier2 - returns (uint256); - - function modifierOrderCorrect02() - private - pure - virtual - modifier1 - modifier2 - returns (string); - - function modifierOrderCorrect03() - external - payable - override - modifier1 - modifier2 - returns (address); - - function modifierOrderCorrect04() - internal - virtual - override - modifier1 - modifier2 - returns (uint256); - - function modifierOrderIncorrect01() - public - view - virtual - override - modifier1 - modifier2 - returns (uint256); - - function modifierOrderIncorrect02() - external - virtual - override - modifier1 - modifier2 - returns (uint256); - - function modifierOrderIncorrect03() - internal - pure - virtual - modifier1 - modifier2 - returns (uint256); - - function modifierOrderIncorrect04() - external - payable - override - modifier1 - modifier2 - returns (uint256); -} - -contract FunctionDefinitions { - function() external {} - fallback() external {} - - function() external payable {} - fallback() external payable {} - receive() external payable {} - - function noParamsNoModifiersNoReturns() { - a = 1; - } - - function oneParam(uint256 x) { - a = 1; - } - - function oneModifier() modifier1 { - a = 1; - } - - function oneReturn() returns (uint256 y1) { - a = 1; - } - - function manyParams( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ) { - a = 1; - } - - function manyModifiers() - modifier1 - modifier2 - modifier3 - modifier4 - modifier5 - modifier6 - modifier7 - modifier8 - modifier9 - modifier10 - { - a = 1; - } - - function manyReturns() - returns ( - uint256 y1, - uint256 y2, - uint256 y3, - uint256 y4, - uint256 y5, - uint256 y6, - uint256 y7, - uint256 y8, - uint256 y9, - uint256 y10 - ) - { - a = 1; - } - - function someParamsSomeModifiers( - uint256 x1, - uint256 x2, - uint256 x3 - ) modifier1 modifier2 modifier3 { - a = 1; - } - - function someParamsSomeReturns( - uint256 x1, - uint256 x2, - uint256 x3 - ) returns (uint256 y1, uint256 y2, uint256 y3) { - a = 1; - } - - function someModifiersSomeReturns() - modifier1 - modifier2 - modifier3 - returns (uint256 y1, uint256 y2, uint256 y3) - { - a = 1; - } - - function someParamSomeModifiersSomeReturns( - uint256 x1, - uint256 x2, - uint256 x3 - ) - modifier1 - modifier2 - modifier3 - returns (uint256 y1, uint256 y2, uint256 y3) - { - a = 1; - } - - function someParamsManyModifiers( - uint256 x1, - uint256 x2, - uint256 x3 - ) - modifier1 - modifier2 - modifier3 - modifier4 - modifier5 - modifier6 - modifier7 - modifier8 - modifier9 - modifier10 - { - a = 1; - } - - function someParamsManyReturns( - uint256 x1, - uint256 x2, - uint256 x3 - ) - returns ( - uint256 y1, - uint256 y2, - uint256 y3, - uint256 y4, - uint256 y5, - uint256 y6, - uint256 y7, - uint256 y8, - uint256 y9, - uint256 y10 - ) - { - a = 1; - } - - function manyParamsSomeModifiers( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ) modifier1 modifier2 modifier3 { - a = 1; - } - - function manyParamsSomeReturns( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ) returns (uint256 y1, uint256 y2, uint256 y3) { - a = 1; - } - - function manyParamsManyModifiers( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ) - public - modifier1 - modifier2 - modifier3 - modifier4 - modifier5 - modifier6 - modifier7 - modifier8 - modifier9 - modifier10 - { - a = 1; - } - - function manyParamsManyReturns( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ) - returns ( - uint256 y1, - uint256 y2, - uint256 y3, - uint256 y4, - uint256 y5, - uint256 y6, - uint256 y7, - uint256 y8, - uint256 y9, - uint256 y10 - ) - { - a = 1; - } - - function manyParamsManyModifiersManyReturns( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ) - modifier1 - modifier2 - modifier3 - modifier4 - modifier5 - modifier6 - modifier7 - modifier8 - modifier9 - modifier10 - returns ( - uint256 y1, - uint256 y2, - uint256 y3, - uint256 y4, - uint256 y5, - uint256 y6, - uint256 y7, - uint256 y8, - uint256 y9, - uint256 y10 - ) - { - a = 1; - } - - function modifierOrderCorrect01() - public - view - virtual - override - modifier1 - modifier2 - returns (uint256) - { - a = 1; - } - - function modifierOrderCorrect02() - private - pure - virtual - modifier1 - modifier2 - returns (string) - { - a = 1; - } - - function modifierOrderCorrect03() - external - payable - override - modifier1 - modifier2 - returns (address) - { - a = 1; - } - - function modifierOrderCorrect04() - internal - virtual - override - modifier1 - modifier2 - returns (uint256) - { - a = 1; - } - - function modifierOrderIncorrect01() - public - view - virtual - override - modifier1 - modifier2 - returns (uint256) - { - a = 1; - } - - function modifierOrderIncorrect02() - external - virtual - override - modifier1 - modifier2 - returns (uint256) - { - a = 1; - } - - function modifierOrderIncorrect03() - internal - pure - virtual - modifier1 - modifier2 - returns (uint256) - { - a = 1; - } - - function modifierOrderIncorrect04() - external - payable - override - modifier1 - modifier2 - returns (uint256) - { - a = 1; - } - - fallback() external payable virtual {} - receive() external payable virtual {} -} - -contract FunctionOverrides is - FunctionInterfaces, - FunctionDefinitions -{ - function noParamsNoModifiersNoReturns() override { - a = 1; - } - - function oneParam(uint256 x) - override( - FunctionInterfaces, - FunctionDefinitions, - SomeOtherFunctionContract, - SomeImport.AndAnotherFunctionContract - ) - { - a = 1; - } -} diff --git a/forge-fmt/testdata/FunctionDefinition/original.sol b/forge-fmt/testdata/FunctionDefinition/original.sol deleted file mode 100644 index 97db649d5..000000000 --- a/forge-fmt/testdata/FunctionDefinition/original.sol +++ /dev/null @@ -1,218 +0,0 @@ -interface FunctionInterfaces { - function noParamsNoModifiersNoReturns(); - - function oneParam(uint x); - - function oneModifier() modifier1; - - function oneReturn() returns(uint y1); - - // function prefix - function withComments( // function name postfix - // x1 prefix - uint256 x1, // x1 postfix - - // x2 prefix - uint256 x2, // x2 postfix - // x2 postfix2 - /* - multi-line x3 prefix - */ - uint256 x3 // x3 postfix - - ) - // pure prefix - pure // pure postfix - // modifier1 prefix - modifier1 // modifier1 postfix - // public prefix - public // public postfix - // modifier2 prefix - modifier2 /* - mutliline modifier2 postfix - */ - // modifier3 prefix - modifier3 // modifier3 postfix - returns ( - // y1 prefix - uint256 y1, // y1 postfix - // y2 prefix - uint256 y2, // y2 postfix - // y3 prefix - uint256 y3 // y3 postfix - ); // function postfix - - /*////////////////////////////////////////////////////////////////////////// - TEST - //////////////////////////////////////////////////////////////////////////*/ - function manyParams(uint x1, uint x2, uint x3, uint x4, uint x5, uint x6, uint x7, uint x8, uint x9, uint x10); - - function manyModifiers() modifier1() modifier2() modifier3 modifier4 modifier5 modifier6 modifier7 modifier8 modifier9 modifier10; - - function manyReturns() returns(uint y1, uint y2, uint y3, uint y4, uint y5, uint y6, uint y7, uint y8, uint y9, uint y10); - - function someParamsSomeModifiers(uint x1, uint x2, uint x3) modifier1() modifier2 modifier3; - - function someParamsSomeReturns(uint x1, uint x2, uint x3) returns(uint y1, uint y2, uint y3); - - function someModifiersSomeReturns() modifier1 modifier2 modifier3 returns(uint y1, uint y2, uint y3); - - function someParamSomeModifiersSomeReturns(uint x1, uint x2, uint x3) modifier1 modifier2 modifier3 returns(uint y1, uint y2, uint y3); - - function someParamsManyModifiers(uint x1, uint x2, uint x3) modifier1 modifier2 modifier3 modifier4 modifier5 modifier6 modifier7 modifier8 modifier9 modifier10; - - function someParamsManyReturns(uint x1, uint x2, uint x3) returns(uint y1, uint y2, uint y3, uint y4, uint y5, uint y6, uint y7, uint y8, uint y9, uint y10); - - function manyParamsSomeModifiers(uint x1, uint x2, uint x3, uint x4, uint x5, uint x6, uint x7, uint x8, uint x9, uint x10) modifier1 modifier2 modifier3; - - function manyParamsSomeReturns(uint x1, uint x2, uint x3, uint x4, uint x5, uint x6, uint x7, uint x8, uint x9, uint x10) returns(uint y1, uint y2, uint y3); - - function manyParamsManyModifiers(uint x1, uint x2, uint x3, uint x4, uint x5, uint x6, uint x7, uint x8, uint x9, uint x10) modifier1 modifier2 modifier3 modifier4 modifier5 modifier6 modifier7 modifier8 modifier9 modifier10; - - function manyParamsManyReturns(uint x1, uint x2, uint x3, uint x4, uint x5, uint x6, uint x7, uint x8, uint x9, uint x10) returns(uint y1, uint y2, uint y3, uint y4, uint y5, uint y6, uint y7, uint y8, uint y9, uint y10); - - function manyParamsManyModifiersManyReturns(uint x1, uint x2, uint x3, uint x4, uint x5, uint x6, uint x7, uint x8, uint x9, uint x10) modifier1 modifier2 modifier3 modifier4 modifier5 modifier6 modifier7 modifier8 modifier9 modifier10 returns(uint y1, uint y2, uint y3, uint y4, uint y5, uint y6, uint y7, uint y8, uint y9, uint y10); - - function modifierOrderCorrect01() public view virtual override modifier1 modifier2 returns(uint); - - function modifierOrderCorrect02() private pure virtual modifier1 modifier2 returns(string); - - function modifierOrderCorrect03() external payable override modifier1 modifier2 returns(address); - - function modifierOrderCorrect04() internal virtual override modifier1 modifier2 returns(uint); - - function modifierOrderIncorrect01() public modifier1 modifier2 override virtual view returns(uint); - - function modifierOrderIncorrect02() virtual modifier1 external modifier2 override returns(uint); - - function modifierOrderIncorrect03() modifier1 pure internal virtual modifier2 returns(uint); - - function modifierOrderIncorrect04() override modifier1 payable external modifier2 returns(uint); -} - -contract FunctionDefinitions { - function () external {} - fallback () external {} - - function () external payable {} - fallback () external payable {} - receive () external payable {} - - function noParamsNoModifiersNoReturns() { - a = 1; - } - - function oneParam(uint x) { - a = 1; - } - - function oneModifier() modifier1 { - a = 1; - } - - function oneReturn() returns(uint y1) { - a = 1; - } - - function manyParams(uint x1, uint x2, uint x3, uint x4, uint x5, uint x6, uint x7, uint x8, uint x9, uint x10) { - a = 1; - } - - function manyModifiers() modifier1 modifier2 modifier3 modifier4 modifier5 modifier6 modifier7 modifier8 modifier9 modifier10 { - a = 1; - } - - function manyReturns() returns(uint y1, uint y2, uint y3, uint y4, uint y5, uint y6, uint y7, uint y8, uint y9, uint y10) { - a = 1; - } - - function someParamsSomeModifiers(uint x1, uint x2, uint x3) modifier1 modifier2 modifier3 { - a = 1; - } - - function someParamsSomeReturns(uint x1, uint x2, uint x3) returns(uint y1, uint y2, uint y3) { - a = 1; - } - - function someModifiersSomeReturns() modifier1 modifier2 modifier3 returns(uint y1, uint y2, uint y3) { - a = 1; - } - - function someParamSomeModifiersSomeReturns(uint x1, uint x2, uint x3) modifier1 modifier2 modifier3 returns(uint y1, uint y2, uint y3) { - a = 1; - } - - function someParamsManyModifiers(uint x1, uint x2, uint x3) modifier1 modifier2 modifier3 modifier4 modifier5 modifier6 modifier7 modifier8 modifier9 modifier10 { - a = 1; - } - - function someParamsManyReturns(uint x1, uint x2, uint x3) returns(uint y1, uint y2, uint y3, uint y4, uint y5, uint y6, uint y7, uint y8, uint y9, uint y10) { - a = 1; - } - - function manyParamsSomeModifiers(uint x1, uint x2, uint x3, uint x4, uint x5, uint x6, uint x7, uint x8, uint x9, uint x10) modifier1 modifier2 modifier3 { - a = 1; - } - - function manyParamsSomeReturns(uint x1, uint x2, uint x3, uint x4, uint x5, uint x6, uint x7, uint x8, uint x9, uint x10) returns(uint y1, uint y2, uint y3) { - a = 1; - } - - function manyParamsManyModifiers(uint x1, uint x2, uint x3, uint x4, uint x5, uint x6, uint x7, uint x8, uint x9, uint x10) modifier1 modifier2 modifier3 modifier4 modifier5 modifier6 modifier7 modifier8 modifier9 modifier10 public { - a = 1; - } - - function manyParamsManyReturns(uint x1, uint x2, uint x3, uint x4, uint x5, uint x6, uint x7, uint x8, uint x9, uint x10) returns(uint y1, uint y2, uint y3, uint y4, uint y5, uint y6, uint y7, uint y8, uint y9, uint y10) { - a = 1; - } - - function manyParamsManyModifiersManyReturns(uint x1, uint x2, uint x3, uint x4, uint x5, uint x6, uint x7, uint x8, uint x9, uint x10) modifier1 modifier2 modifier3 modifier4 modifier5 modifier6 modifier7 modifier8 modifier9 modifier10 returns(uint y1, uint y2, uint y3, uint y4, uint y5, uint y6, uint y7, uint y8, uint y9, uint y10) { - a = 1; - } - - function modifierOrderCorrect01() public view virtual override modifier1 modifier2 returns(uint) { - a = 1; - } - - function modifierOrderCorrect02() private pure virtual modifier1 modifier2 returns(string) { - a = 1; - } - - function modifierOrderCorrect03() external payable override modifier1 modifier2 returns(address) { - a = 1; - } - - function modifierOrderCorrect04() internal virtual override modifier1 modifier2 returns(uint) { - a = 1; - } - - function modifierOrderIncorrect01() public modifier1 modifier2 override virtual view returns(uint) { - a = 1; - } - - function modifierOrderIncorrect02() virtual modifier1 external modifier2 override returns(uint) { - a = 1; - } - - function modifierOrderIncorrect03() modifier1 pure internal virtual modifier2 returns(uint) { - a = 1; - } - - function modifierOrderIncorrect04() override modifier1 payable external modifier2 returns(uint) { - a = 1; - } - - fallback() external payable virtual {} - receive() external payable virtual {} -} - -contract FunctionOverrides is FunctionInterfaces, FunctionDefinitions { - function noParamsNoModifiersNoReturns() override { - a = 1; - } - - function oneParam(uint256 x) override(FunctionInterfaces, FunctionDefinitions, SomeOtherFunctionContract, SomeImport.AndAnotherFunctionContract) { - a = 1; - } -} - diff --git a/forge-fmt/testdata/FunctionDefinition/override-spacing.fmt.sol b/forge-fmt/testdata/FunctionDefinition/override-spacing.fmt.sol deleted file mode 100644 index 516e5c2fd..000000000 --- a/forge-fmt/testdata/FunctionDefinition/override-spacing.fmt.sol +++ /dev/null @@ -1,710 +0,0 @@ -// config: line_length = 60 -// config: override_spacing = true -interface FunctionInterfaces { - function noParamsNoModifiersNoReturns(); - - function oneParam(uint256 x); - - function oneModifier() modifier1; - - function oneReturn() returns (uint256 y1); - - // function prefix - function withComments( // function name postfix - // x1 prefix - uint256 x1, // x1 postfix - // x2 prefix - uint256 x2, // x2 postfix - // x2 postfix2 - /* - multi-line x3 prefix - */ - uint256 x3 // x3 postfix - ) - // public prefix - public // public postfix - // pure prefix - pure // pure postfix - // modifier1 prefix - modifier1 // modifier1 postfix - // modifier2 prefix - modifier2 /* - mutliline modifier2 postfix - */ - // modifier3 prefix - modifier3 // modifier3 postfix - returns ( - // y1 prefix - uint256 y1, // y1 postfix - // y2 prefix - uint256 y2, // y2 postfix - // y3 prefix - uint256 y3 - ); // y3 postfix - // function postfix - - /*////////////////////////////////////////////////////////////////////////// - TEST - //////////////////////////////////////////////////////////////////////////*/ - function manyParams( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ); - - function manyModifiers() - modifier1 - modifier2 - modifier3 - modifier4 - modifier5 - modifier6 - modifier7 - modifier8 - modifier9 - modifier10; - - function manyReturns() - returns ( - uint256 y1, - uint256 y2, - uint256 y3, - uint256 y4, - uint256 y5, - uint256 y6, - uint256 y7, - uint256 y8, - uint256 y9, - uint256 y10 - ); - - function someParamsSomeModifiers( - uint256 x1, - uint256 x2, - uint256 x3 - ) modifier1 modifier2 modifier3; - - function someParamsSomeReturns( - uint256 x1, - uint256 x2, - uint256 x3 - ) returns (uint256 y1, uint256 y2, uint256 y3); - - function someModifiersSomeReturns() - modifier1 - modifier2 - modifier3 - returns (uint256 y1, uint256 y2, uint256 y3); - - function someParamSomeModifiersSomeReturns( - uint256 x1, - uint256 x2, - uint256 x3 - ) - modifier1 - modifier2 - modifier3 - returns (uint256 y1, uint256 y2, uint256 y3); - - function someParamsManyModifiers( - uint256 x1, - uint256 x2, - uint256 x3 - ) - modifier1 - modifier2 - modifier3 - modifier4 - modifier5 - modifier6 - modifier7 - modifier8 - modifier9 - modifier10; - - function someParamsManyReturns( - uint256 x1, - uint256 x2, - uint256 x3 - ) - returns ( - uint256 y1, - uint256 y2, - uint256 y3, - uint256 y4, - uint256 y5, - uint256 y6, - uint256 y7, - uint256 y8, - uint256 y9, - uint256 y10 - ); - - function manyParamsSomeModifiers( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ) modifier1 modifier2 modifier3; - - function manyParamsSomeReturns( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ) returns (uint256 y1, uint256 y2, uint256 y3); - - function manyParamsManyModifiers( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ) - modifier1 - modifier2 - modifier3 - modifier4 - modifier5 - modifier6 - modifier7 - modifier8 - modifier9 - modifier10; - - function manyParamsManyReturns( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ) - returns ( - uint256 y1, - uint256 y2, - uint256 y3, - uint256 y4, - uint256 y5, - uint256 y6, - uint256 y7, - uint256 y8, - uint256 y9, - uint256 y10 - ); - - function manyParamsManyModifiersManyReturns( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ) - modifier1 - modifier2 - modifier3 - modifier4 - modifier5 - modifier6 - modifier7 - modifier8 - modifier9 - modifier10 - returns ( - uint256 y1, - uint256 y2, - uint256 y3, - uint256 y4, - uint256 y5, - uint256 y6, - uint256 y7, - uint256 y8, - uint256 y9, - uint256 y10 - ); - - function modifierOrderCorrect01() - public - view - virtual - override - modifier1 - modifier2 - returns (uint256); - - function modifierOrderCorrect02() - private - pure - virtual - modifier1 - modifier2 - returns (string); - - function modifierOrderCorrect03() - external - payable - override - modifier1 - modifier2 - returns (address); - - function modifierOrderCorrect04() - internal - virtual - override - modifier1 - modifier2 - returns (uint256); - - function modifierOrderIncorrect01() - public - view - virtual - override - modifier1 - modifier2 - returns (uint256); - - function modifierOrderIncorrect02() - external - virtual - override - modifier1 - modifier2 - returns (uint256); - - function modifierOrderIncorrect03() - internal - pure - virtual - modifier1 - modifier2 - returns (uint256); - - function modifierOrderIncorrect04() - external - payable - override - modifier1 - modifier2 - returns (uint256); -} - -contract FunctionDefinitions { - function() external {} - fallback() external {} - - function() external payable {} - fallback() external payable {} - receive() external payable {} - - function noParamsNoModifiersNoReturns() { - a = 1; - } - - function oneParam(uint256 x) { - a = 1; - } - - function oneModifier() modifier1 { - a = 1; - } - - function oneReturn() returns (uint256 y1) { - a = 1; - } - - function manyParams( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ) { - a = 1; - } - - function manyModifiers() - modifier1 - modifier2 - modifier3 - modifier4 - modifier5 - modifier6 - modifier7 - modifier8 - modifier9 - modifier10 - { - a = 1; - } - - function manyReturns() - returns ( - uint256 y1, - uint256 y2, - uint256 y3, - uint256 y4, - uint256 y5, - uint256 y6, - uint256 y7, - uint256 y8, - uint256 y9, - uint256 y10 - ) - { - a = 1; - } - - function someParamsSomeModifiers( - uint256 x1, - uint256 x2, - uint256 x3 - ) modifier1 modifier2 modifier3 { - a = 1; - } - - function someParamsSomeReturns( - uint256 x1, - uint256 x2, - uint256 x3 - ) returns (uint256 y1, uint256 y2, uint256 y3) { - a = 1; - } - - function someModifiersSomeReturns() - modifier1 - modifier2 - modifier3 - returns (uint256 y1, uint256 y2, uint256 y3) - { - a = 1; - } - - function someParamSomeModifiersSomeReturns( - uint256 x1, - uint256 x2, - uint256 x3 - ) - modifier1 - modifier2 - modifier3 - returns (uint256 y1, uint256 y2, uint256 y3) - { - a = 1; - } - - function someParamsManyModifiers( - uint256 x1, - uint256 x2, - uint256 x3 - ) - modifier1 - modifier2 - modifier3 - modifier4 - modifier5 - modifier6 - modifier7 - modifier8 - modifier9 - modifier10 - { - a = 1; - } - - function someParamsManyReturns( - uint256 x1, - uint256 x2, - uint256 x3 - ) - returns ( - uint256 y1, - uint256 y2, - uint256 y3, - uint256 y4, - uint256 y5, - uint256 y6, - uint256 y7, - uint256 y8, - uint256 y9, - uint256 y10 - ) - { - a = 1; - } - - function manyParamsSomeModifiers( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ) modifier1 modifier2 modifier3 { - a = 1; - } - - function manyParamsSomeReturns( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ) returns (uint256 y1, uint256 y2, uint256 y3) { - a = 1; - } - - function manyParamsManyModifiers( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ) - public - modifier1 - modifier2 - modifier3 - modifier4 - modifier5 - modifier6 - modifier7 - modifier8 - modifier9 - modifier10 - { - a = 1; - } - - function manyParamsManyReturns( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ) - returns ( - uint256 y1, - uint256 y2, - uint256 y3, - uint256 y4, - uint256 y5, - uint256 y6, - uint256 y7, - uint256 y8, - uint256 y9, - uint256 y10 - ) - { - a = 1; - } - - function manyParamsManyModifiersManyReturns( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ) - modifier1 - modifier2 - modifier3 - modifier4 - modifier5 - modifier6 - modifier7 - modifier8 - modifier9 - modifier10 - returns ( - uint256 y1, - uint256 y2, - uint256 y3, - uint256 y4, - uint256 y5, - uint256 y6, - uint256 y7, - uint256 y8, - uint256 y9, - uint256 y10 - ) - { - a = 1; - } - - function modifierOrderCorrect01() - public - view - virtual - override - modifier1 - modifier2 - returns (uint256) - { - a = 1; - } - - function modifierOrderCorrect02() - private - pure - virtual - modifier1 - modifier2 - returns (string) - { - a = 1; - } - - function modifierOrderCorrect03() - external - payable - override - modifier1 - modifier2 - returns (address) - { - a = 1; - } - - function modifierOrderCorrect04() - internal - virtual - override - modifier1 - modifier2 - returns (uint256) - { - a = 1; - } - - function modifierOrderIncorrect01() - public - view - virtual - override - modifier1 - modifier2 - returns (uint256) - { - a = 1; - } - - function modifierOrderIncorrect02() - external - virtual - override - modifier1 - modifier2 - returns (uint256) - { - a = 1; - } - - function modifierOrderIncorrect03() - internal - pure - virtual - modifier1 - modifier2 - returns (uint256) - { - a = 1; - } - - function modifierOrderIncorrect04() - external - payable - override - modifier1 - modifier2 - returns (uint256) - { - a = 1; - } - - fallback() external payable virtual {} - receive() external payable virtual {} -} - -contract FunctionOverrides is - FunctionInterfaces, - FunctionDefinitions -{ - function noParamsNoModifiersNoReturns() override { - a = 1; - } - - function oneParam(uint256 x) - override ( - FunctionInterfaces, - FunctionDefinitions, - SomeOtherFunctionContract, - SomeImport.AndAnotherFunctionContract - ) - { - a = 1; - } -} diff --git a/forge-fmt/testdata/FunctionDefinition/params-first.fmt.sol b/forge-fmt/testdata/FunctionDefinition/params-first.fmt.sol deleted file mode 100644 index 50f539c32..000000000 --- a/forge-fmt/testdata/FunctionDefinition/params-first.fmt.sol +++ /dev/null @@ -1,710 +0,0 @@ -// config: line_length = 60 -// config: multiline_func_header = "params_first" -interface FunctionInterfaces { - function noParamsNoModifiersNoReturns(); - - function oneParam(uint256 x); - - function oneModifier() modifier1; - - function oneReturn() returns (uint256 y1); - - // function prefix - function withComments( // function name postfix - // x1 prefix - uint256 x1, // x1 postfix - // x2 prefix - uint256 x2, // x2 postfix - // x2 postfix2 - /* - multi-line x3 prefix - */ - uint256 x3 // x3 postfix - ) - // public prefix - public // public postfix - // pure prefix - pure // pure postfix - // modifier1 prefix - modifier1 // modifier1 postfix - // modifier2 prefix - modifier2 /* - mutliline modifier2 postfix - */ - // modifier3 prefix - modifier3 // modifier3 postfix - returns ( - // y1 prefix - uint256 y1, // y1 postfix - // y2 prefix - uint256 y2, // y2 postfix - // y3 prefix - uint256 y3 - ); // y3 postfix - // function postfix - - /*////////////////////////////////////////////////////////////////////////// - TEST - //////////////////////////////////////////////////////////////////////////*/ - function manyParams( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ); - - function manyModifiers() - modifier1 - modifier2 - modifier3 - modifier4 - modifier5 - modifier6 - modifier7 - modifier8 - modifier9 - modifier10; - - function manyReturns() - returns ( - uint256 y1, - uint256 y2, - uint256 y3, - uint256 y4, - uint256 y5, - uint256 y6, - uint256 y7, - uint256 y8, - uint256 y9, - uint256 y10 - ); - - function someParamsSomeModifiers( - uint256 x1, - uint256 x2, - uint256 x3 - ) modifier1 modifier2 modifier3; - - function someParamsSomeReturns( - uint256 x1, - uint256 x2, - uint256 x3 - ) returns (uint256 y1, uint256 y2, uint256 y3); - - function someModifiersSomeReturns() - modifier1 - modifier2 - modifier3 - returns (uint256 y1, uint256 y2, uint256 y3); - - function someParamSomeModifiersSomeReturns( - uint256 x1, - uint256 x2, - uint256 x3 - ) - modifier1 - modifier2 - modifier3 - returns (uint256 y1, uint256 y2, uint256 y3); - - function someParamsManyModifiers( - uint256 x1, - uint256 x2, - uint256 x3 - ) - modifier1 - modifier2 - modifier3 - modifier4 - modifier5 - modifier6 - modifier7 - modifier8 - modifier9 - modifier10; - - function someParamsManyReturns( - uint256 x1, - uint256 x2, - uint256 x3 - ) - returns ( - uint256 y1, - uint256 y2, - uint256 y3, - uint256 y4, - uint256 y5, - uint256 y6, - uint256 y7, - uint256 y8, - uint256 y9, - uint256 y10 - ); - - function manyParamsSomeModifiers( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ) modifier1 modifier2 modifier3; - - function manyParamsSomeReturns( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ) returns (uint256 y1, uint256 y2, uint256 y3); - - function manyParamsManyModifiers( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ) - modifier1 - modifier2 - modifier3 - modifier4 - modifier5 - modifier6 - modifier7 - modifier8 - modifier9 - modifier10; - - function manyParamsManyReturns( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ) - returns ( - uint256 y1, - uint256 y2, - uint256 y3, - uint256 y4, - uint256 y5, - uint256 y6, - uint256 y7, - uint256 y8, - uint256 y9, - uint256 y10 - ); - - function manyParamsManyModifiersManyReturns( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ) - modifier1 - modifier2 - modifier3 - modifier4 - modifier5 - modifier6 - modifier7 - modifier8 - modifier9 - modifier10 - returns ( - uint256 y1, - uint256 y2, - uint256 y3, - uint256 y4, - uint256 y5, - uint256 y6, - uint256 y7, - uint256 y8, - uint256 y9, - uint256 y10 - ); - - function modifierOrderCorrect01() - public - view - virtual - override - modifier1 - modifier2 - returns (uint256); - - function modifierOrderCorrect02() - private - pure - virtual - modifier1 - modifier2 - returns (string); - - function modifierOrderCorrect03() - external - payable - override - modifier1 - modifier2 - returns (address); - - function modifierOrderCorrect04() - internal - virtual - override - modifier1 - modifier2 - returns (uint256); - - function modifierOrderIncorrect01() - public - view - virtual - override - modifier1 - modifier2 - returns (uint256); - - function modifierOrderIncorrect02() - external - virtual - override - modifier1 - modifier2 - returns (uint256); - - function modifierOrderIncorrect03() - internal - pure - virtual - modifier1 - modifier2 - returns (uint256); - - function modifierOrderIncorrect04() - external - payable - override - modifier1 - modifier2 - returns (uint256); -} - -contract FunctionDefinitions { - function() external {} - fallback() external {} - - function() external payable {} - fallback() external payable {} - receive() external payable {} - - function noParamsNoModifiersNoReturns() { - a = 1; - } - - function oneParam(uint256 x) { - a = 1; - } - - function oneModifier() modifier1 { - a = 1; - } - - function oneReturn() returns (uint256 y1) { - a = 1; - } - - function manyParams( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ) { - a = 1; - } - - function manyModifiers() - modifier1 - modifier2 - modifier3 - modifier4 - modifier5 - modifier6 - modifier7 - modifier8 - modifier9 - modifier10 - { - a = 1; - } - - function manyReturns() - returns ( - uint256 y1, - uint256 y2, - uint256 y3, - uint256 y4, - uint256 y5, - uint256 y6, - uint256 y7, - uint256 y8, - uint256 y9, - uint256 y10 - ) - { - a = 1; - } - - function someParamsSomeModifiers( - uint256 x1, - uint256 x2, - uint256 x3 - ) modifier1 modifier2 modifier3 { - a = 1; - } - - function someParamsSomeReturns( - uint256 x1, - uint256 x2, - uint256 x3 - ) returns (uint256 y1, uint256 y2, uint256 y3) { - a = 1; - } - - function someModifiersSomeReturns() - modifier1 - modifier2 - modifier3 - returns (uint256 y1, uint256 y2, uint256 y3) - { - a = 1; - } - - function someParamSomeModifiersSomeReturns( - uint256 x1, - uint256 x2, - uint256 x3 - ) - modifier1 - modifier2 - modifier3 - returns (uint256 y1, uint256 y2, uint256 y3) - { - a = 1; - } - - function someParamsManyModifiers( - uint256 x1, - uint256 x2, - uint256 x3 - ) - modifier1 - modifier2 - modifier3 - modifier4 - modifier5 - modifier6 - modifier7 - modifier8 - modifier9 - modifier10 - { - a = 1; - } - - function someParamsManyReturns( - uint256 x1, - uint256 x2, - uint256 x3 - ) - returns ( - uint256 y1, - uint256 y2, - uint256 y3, - uint256 y4, - uint256 y5, - uint256 y6, - uint256 y7, - uint256 y8, - uint256 y9, - uint256 y10 - ) - { - a = 1; - } - - function manyParamsSomeModifiers( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ) modifier1 modifier2 modifier3 { - a = 1; - } - - function manyParamsSomeReturns( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ) returns (uint256 y1, uint256 y2, uint256 y3) { - a = 1; - } - - function manyParamsManyModifiers( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ) - public - modifier1 - modifier2 - modifier3 - modifier4 - modifier5 - modifier6 - modifier7 - modifier8 - modifier9 - modifier10 - { - a = 1; - } - - function manyParamsManyReturns( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ) - returns ( - uint256 y1, - uint256 y2, - uint256 y3, - uint256 y4, - uint256 y5, - uint256 y6, - uint256 y7, - uint256 y8, - uint256 y9, - uint256 y10 - ) - { - a = 1; - } - - function manyParamsManyModifiersManyReturns( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ) - modifier1 - modifier2 - modifier3 - modifier4 - modifier5 - modifier6 - modifier7 - modifier8 - modifier9 - modifier10 - returns ( - uint256 y1, - uint256 y2, - uint256 y3, - uint256 y4, - uint256 y5, - uint256 y6, - uint256 y7, - uint256 y8, - uint256 y9, - uint256 y10 - ) - { - a = 1; - } - - function modifierOrderCorrect01() - public - view - virtual - override - modifier1 - modifier2 - returns (uint256) - { - a = 1; - } - - function modifierOrderCorrect02() - private - pure - virtual - modifier1 - modifier2 - returns (string) - { - a = 1; - } - - function modifierOrderCorrect03() - external - payable - override - modifier1 - modifier2 - returns (address) - { - a = 1; - } - - function modifierOrderCorrect04() - internal - virtual - override - modifier1 - modifier2 - returns (uint256) - { - a = 1; - } - - function modifierOrderIncorrect01() - public - view - virtual - override - modifier1 - modifier2 - returns (uint256) - { - a = 1; - } - - function modifierOrderIncorrect02() - external - virtual - override - modifier1 - modifier2 - returns (uint256) - { - a = 1; - } - - function modifierOrderIncorrect03() - internal - pure - virtual - modifier1 - modifier2 - returns (uint256) - { - a = 1; - } - - function modifierOrderIncorrect04() - external - payable - override - modifier1 - modifier2 - returns (uint256) - { - a = 1; - } - - fallback() external payable virtual {} - receive() external payable virtual {} -} - -contract FunctionOverrides is - FunctionInterfaces, - FunctionDefinitions -{ - function noParamsNoModifiersNoReturns() override { - a = 1; - } - - function oneParam(uint256 x) - override( - FunctionInterfaces, - FunctionDefinitions, - SomeOtherFunctionContract, - SomeImport.AndAnotherFunctionContract - ) - { - a = 1; - } -} diff --git a/forge-fmt/testdata/FunctionType/fmt.sol b/forge-fmt/testdata/FunctionType/fmt.sol deleted file mode 100644 index 67c784cac..000000000 --- a/forge-fmt/testdata/FunctionType/fmt.sol +++ /dev/null @@ -1,31 +0,0 @@ -// config: line_length = 90 -library ArrayUtils { - function map(uint256[] memory self, function (uint) pure returns (uint) f) - internal - pure - returns (uint256[] memory r) - { - r = new uint[](self.length); - for (uint256 i = 0; i < self.length; i++) { - r[i] = f(self[i]); - } - } - - function reduce(uint256[] memory self, function (uint, uint) pure returns (uint) f) - internal - pure - returns (uint256 r) - { - r = self[0]; - for (uint256 i = 1; i < self.length; i++) { - r = f(r, self[i]); - } - } - - function range(uint256 length) internal pure returns (uint256[] memory r) { - r = new uint[](length); - for (uint256 i = 0; i < r.length; i++) { - r[i] = i; - } - } -} diff --git a/forge-fmt/testdata/FunctionType/original.sol b/forge-fmt/testdata/FunctionType/original.sol deleted file mode 100644 index 7247a4f93..000000000 --- a/forge-fmt/testdata/FunctionType/original.sol +++ /dev/null @@ -1,31 +0,0 @@ -library ArrayUtils { - function map(uint[] memory self, function (uint) pure returns (uint) f) - internal - pure - returns ( - uint[] memory r - ) - { - r = new uint[](self.length); - for (uint i = 0; i < self.length; i++) { - r[i] = f(self[i]); - } - } - - function reduce( - uint[] memory self, - function (uint, uint) pure returns (uint) f - ) internal pure returns (uint256 r) { - r = self[0]; - for (uint i = 1; i < self.length; i++) { - r = f(r, self[i]); - } - } - - function range(uint256 length) internal pure returns (uint[] memory r) { - r = new uint[](length); - for (uint i = 0; i < r.length; i++) { - r[i] = i; - } - } -} diff --git a/forge-fmt/testdata/IfStatement/block-multi.fmt.sol b/forge-fmt/testdata/IfStatement/block-multi.fmt.sol deleted file mode 100644 index dcd8bb83e..000000000 --- a/forge-fmt/testdata/IfStatement/block-multi.fmt.sol +++ /dev/null @@ -1,171 +0,0 @@ -// config: single_line_statement_blocks = "multi" -function execute() returns (bool) { - if (true) { - // always returns true - return true; - } - return false; -} - -function executeElse() {} - -function executeWithMultipleParameters(bool parameter1, bool parameter2) {} - -function executeWithVeryVeryVeryLongNameAndSomeParameter(bool parameter) {} - -contract IfStatement { - function test() external { - if (true) { - execute(); - } - - bool condition; - bool anotherLongCondition; - bool andAnotherVeryVeryLongCondition; - if ( - condition && anotherLongCondition || andAnotherVeryVeryLongCondition - ) { - execute(); - } - - // comment - if (condition) { - execute(); - } else if (anotherLongCondition) { - execute(); // differently - } - - /* comment1 */ - if ( /* comment2 */ /* comment3 */ - condition // comment4 - ) { - // comment5 - execute(); - } // comment6 - - if (condition) { - execute(); - } // comment7 - /* comment8 */ - /* comment9 */ - else if ( /* comment10 */ - anotherLongCondition // comment11 - ) { - /* comment12 */ - execute(); - } // comment13 - /* comment14 */ - else {} // comment15 - - if ( - // comment16 - condition /* comment17 */ - ) { - execute(); - } - - if (condition) { - execute(); - } else { - executeElse(); - } - - if (condition) { - if (anotherLongCondition) { - execute(); - } - } - - if (condition) { - execute(); - } - - if ( - condition && anotherLongCondition || andAnotherVeryVeryLongCondition - ) { - execute(); - } - - if (condition) { - if (anotherLongCondition) { - execute(); - } - } - - if (condition) { - execute(); - } // comment18 - - if (condition) { - executeWithMultipleParameters(condition, anotherLongCondition); - } - - if (condition) { - executeWithVeryVeryVeryLongNameAndSomeParameter(condition); - } - - if (condition) { - execute(); - } else { - execute(); - } - - if (condition) {} - - if (condition) { - executeWithMultipleParameters(condition, anotherLongCondition); - } else if (anotherLongCondition) { - execute(); - } - - if (condition && ((condition || anotherLongCondition))) { - execute(); - } - - // if statement - if (condition) { - execute(); - } - // else statement - else { - execute(); - } - - // if statement - if (condition) { - execute(); - } - // else statement - else { - executeWithMultipleParameters( - anotherLongCondition, andAnotherVeryVeryLongCondition - ); - } - - if (condition) { - execute(); - } else if (condition) { - execute(); - } else if (condition) { - execute(); - } else if (condition) { - execute(); - } else if (condition) { - execute(); - } - - if (condition) { - execute(); - } else if (condition) { - execute(); - } else if (condition) { - execute(); - } else if (condition) { - execute(); - } else if (condition) { - execute(); - } else { - executeElse(); - } - } -} diff --git a/forge-fmt/testdata/IfStatement/block-single.fmt.sol b/forge-fmt/testdata/IfStatement/block-single.fmt.sol deleted file mode 100644 index ba2b9998b..000000000 --- a/forge-fmt/testdata/IfStatement/block-single.fmt.sol +++ /dev/null @@ -1,123 +0,0 @@ -// config: single_line_statement_blocks = "single" -function execute() returns (bool) { - if (true) { - // always returns true - return true; - } - return false; -} - -function executeElse() {} - -function executeWithMultipleParameters(bool parameter1, bool parameter2) {} - -function executeWithVeryVeryVeryLongNameAndSomeParameter(bool parameter) {} - -contract IfStatement { - function test() external { - if (true) execute(); - - bool condition; - bool anotherLongCondition; - bool andAnotherVeryVeryLongCondition; - if ( - condition && anotherLongCondition || andAnotherVeryVeryLongCondition - ) execute(); - - // comment - if (condition) execute(); - else if (anotherLongCondition) execute(); // differently - - /* comment1 */ - if ( /* comment2 */ /* comment3 */ - condition // comment4 - ) { - // comment5 - execute(); - } // comment6 - - if (condition) { - execute(); - } // comment7 - /* comment8 */ - /* comment9 */ - else if ( /* comment10 */ - anotherLongCondition // comment11 - ) { - /* comment12 */ - execute(); - } // comment13 - /* comment14 */ - else {} // comment15 - - if ( - // comment16 - condition /* comment17 */ - ) execute(); - - if (condition) execute(); - else executeElse(); - - if (condition) if (anotherLongCondition) execute(); - - if (condition) execute(); - - if ( - condition && anotherLongCondition || andAnotherVeryVeryLongCondition - ) execute(); - - if (condition) if (anotherLongCondition) execute(); - - if (condition) execute(); // comment18 - - if (condition) { - executeWithMultipleParameters(condition, anotherLongCondition); - } - - if (condition) { - executeWithVeryVeryVeryLongNameAndSomeParameter(condition); - } - - if (condition) execute(); - else execute(); - - if (condition) {} - - if (condition) { - executeWithMultipleParameters(condition, anotherLongCondition); - } else if (anotherLongCondition) { - execute(); - } - - if (condition && ((condition || anotherLongCondition))) execute(); - - // if statement - if (condition) execute(); - // else statement - else execute(); - - // if statement - if (condition) { - execute(); - } - // else statement - else { - executeWithMultipleParameters( - anotherLongCondition, andAnotherVeryVeryLongCondition - ); - } - - if (condition) execute(); - else if (condition) execute(); - else if (condition) execute(); - else if (condition) execute(); - else if (condition) execute(); - - if (condition) execute(); - else if (condition) execute(); - else if (condition) execute(); - else if (condition) execute(); - else if (condition) execute(); - else executeElse(); - } -} diff --git a/forge-fmt/testdata/IfStatement/fmt.sol b/forge-fmt/testdata/IfStatement/fmt.sol deleted file mode 100644 index cb2f8874f..000000000 --- a/forge-fmt/testdata/IfStatement/fmt.sol +++ /dev/null @@ -1,145 +0,0 @@ -function execute() returns (bool) { - if (true) { - // always returns true - return true; - } - return false; -} - -function executeElse() {} - -function executeWithMultipleParameters(bool parameter1, bool parameter2) {} - -function executeWithVeryVeryVeryLongNameAndSomeParameter(bool parameter) {} - -contract IfStatement { - function test() external { - if (true) { - execute(); - } - - bool condition; - bool anotherLongCondition; - bool andAnotherVeryVeryLongCondition; - if ( - condition && anotherLongCondition || andAnotherVeryVeryLongCondition - ) { - execute(); - } - - // comment - if (condition) { - execute(); - } else if (anotherLongCondition) { - execute(); // differently - } - - /* comment1 */ - if ( /* comment2 */ /* comment3 */ - condition // comment4 - ) { - // comment5 - execute(); - } // comment6 - - if (condition) { - execute(); - } // comment7 - /* comment8 */ - /* comment9 */ - else if ( /* comment10 */ - anotherLongCondition // comment11 - ) { - /* comment12 */ - execute(); - } // comment13 - /* comment14 */ - else {} // comment15 - - if ( - // comment16 - condition /* comment17 */ - ) { - execute(); - } - - if (condition) { - execute(); - } else { - executeElse(); - } - - if (condition) { - if (anotherLongCondition) { - execute(); - } - } - - if (condition) execute(); - - if ( - condition && anotherLongCondition || andAnotherVeryVeryLongCondition - ) execute(); - - if (condition) if (anotherLongCondition) execute(); - - if (condition) execute(); // comment18 - - if (condition) { - executeWithMultipleParameters(condition, anotherLongCondition); - } - - if (condition) { - executeWithVeryVeryVeryLongNameAndSomeParameter(condition); - } - - if (condition) execute(); - else execute(); - - if (condition) {} - - if (condition) { - executeWithMultipleParameters(condition, anotherLongCondition); - } else if (anotherLongCondition) { - execute(); - } - - if (condition && ((condition || anotherLongCondition))) execute(); - - // if statement - if (condition) execute(); - // else statement - else execute(); - - // if statement - if (condition) { - execute(); - } - // else statement - else { - executeWithMultipleParameters( - anotherLongCondition, andAnotherVeryVeryLongCondition - ); - } - - if (condition) execute(); - else if (condition) execute(); - else if (condition) execute(); - else if (condition) execute(); - else if (condition) execute(); - - if (condition) { - execute(); - } else if (condition) { - execute(); - } else if (condition) { - execute(); - } else if (condition) { - execute(); - } else if (condition) { - execute(); - } else { - executeElse(); - } - } -} diff --git a/forge-fmt/testdata/IfStatement/original.sol b/forge-fmt/testdata/IfStatement/original.sol deleted file mode 100644 index b36829bbb..000000000 --- a/forge-fmt/testdata/IfStatement/original.sol +++ /dev/null @@ -1,119 +0,0 @@ -function execute() returns (bool) { - if (true) { - // always returns true - return true; - } - return false; -} - -function executeElse() {} - -function executeWithMultipleParameters(bool parameter1, bool parameter2) {} - -function executeWithVeryVeryVeryLongNameAndSomeParameter(bool parameter) {} - -contract IfStatement { - - function test() external { - if( true) - { - execute() ; - } - - bool condition; bool anotherLongCondition; bool andAnotherVeryVeryLongCondition ; - if - ( condition && anotherLongCondition || - andAnotherVeryVeryLongCondition - ) - { execute(); } - - // comment - if (condition) { execute(); } - else - if (anotherLongCondition) { - execute(); // differently - } - - /* comment1 */ if /* comment2 */ ( /* comment3 */ condition ) // comment4 - { - // comment5 - execute(); - } // comment6 - - if (condition ) { - execute(); - } // comment7 - /* comment8 */ - /* comment9 */ else if /* comment10 */ (anotherLongCondition) // comment11 - /* comment12 */ { - execute() ; - } // comment13 - /* comment14 */ else { } // comment15 - - if ( - // comment16 - condition /* comment17 */ - ) - { - execute(); - } - - if (condition) - execute(); - else - executeElse(); - - if (condition) - if (anotherLongCondition) - execute(); - - if (condition) execute(); - - if (condition && anotherLongCondition || - andAnotherVeryVeryLongCondition ) execute(); - - if (condition) if (anotherLongCondition) execute(); - - if (condition) execute(); // comment18 - - if (condition) executeWithMultipleParameters(condition, anotherLongCondition); - - if (condition) executeWithVeryVeryVeryLongNameAndSomeParameter(condition); - - if (condition) execute(); else execute(); - - if (condition) {} - - if (condition) executeWithMultipleParameters(condition, anotherLongCondition); else if (anotherLongCondition) execute(); - - if (condition && ((condition || anotherLongCondition) - ) - ) execute(); - - // if statement - if (condition) execute(); - // else statement - else execute(); - - // if statement - if (condition) execute(); - // else statement - else executeWithMultipleParameters(anotherLongCondition, andAnotherVeryVeryLongCondition); - - if (condition) execute(); - else if (condition) execute(); - else if (condition) execute(); - else if (condition) execute(); - else if (condition) execute(); - - if (condition) execute(); - else if (condition) - execute(); - else if (condition) execute(); - else if (condition) - execute(); - else if (condition) execute(); - else - executeElse(); - } -} \ No newline at end of file diff --git a/forge-fmt/testdata/IfStatement2/fmt.sol b/forge-fmt/testdata/IfStatement2/fmt.sol deleted file mode 100644 index 10ae43601..000000000 --- a/forge-fmt/testdata/IfStatement2/fmt.sol +++ /dev/null @@ -1,7 +0,0 @@ -contract IfStatement { - function test() external { - bool anotherLongCondition; - - if (condition && ((condition || anotherLongCondition))) execute(); - } -} diff --git a/forge-fmt/testdata/IfStatement2/original.sol b/forge-fmt/testdata/IfStatement2/original.sol deleted file mode 100644 index df020c04b..000000000 --- a/forge-fmt/testdata/IfStatement2/original.sol +++ /dev/null @@ -1,10 +0,0 @@ -contract IfStatement { - - function test() external { - bool anotherLongCondition; - - if (condition && ((condition || anotherLongCondition) - ) - ) execute(); - } -} \ No newline at end of file diff --git a/forge-fmt/testdata/ImportDirective/bracket-spacing.fmt.sol b/forge-fmt/testdata/ImportDirective/bracket-spacing.fmt.sol deleted file mode 100644 index 1db94929a..000000000 --- a/forge-fmt/testdata/ImportDirective/bracket-spacing.fmt.sol +++ /dev/null @@ -1,21 +0,0 @@ -// config: bracket_spacing = true -import "SomeFile.sol"; -import "SomeFile.sol"; -import "SomeFile.sol" as SomeOtherFile; -import "SomeFile.sol" as SomeOtherFile; -import "AnotherFile.sol" as SomeSymbol; -import "AnotherFile.sol" as SomeSymbol; -import { symbol1 as alias, symbol2 } from "File.sol"; -import { symbol1 as alias, symbol2 } from "File.sol"; -import { - symbol1 as alias1, - symbol2 as alias2, - symbol3 as alias3, - symbol4 -} from "File2.sol"; -import { - symbol1 as alias1, - symbol2 as alias2, - symbol3 as alias3, - symbol4 -} from "File2.sol"; diff --git a/forge-fmt/testdata/ImportDirective/fmt.sol b/forge-fmt/testdata/ImportDirective/fmt.sol deleted file mode 100644 index 4915b8ab2..000000000 --- a/forge-fmt/testdata/ImportDirective/fmt.sol +++ /dev/null @@ -1,20 +0,0 @@ -import "SomeFile.sol"; -import "SomeFile.sol"; -import "SomeFile.sol" as SomeOtherFile; -import "SomeFile.sol" as SomeOtherFile; -import "AnotherFile.sol" as SomeSymbol; -import "AnotherFile.sol" as SomeSymbol; -import {symbol1 as alias, symbol2} from "File.sol"; -import {symbol1 as alias, symbol2} from "File.sol"; -import { - symbol1 as alias1, - symbol2 as alias2, - symbol3 as alias3, - symbol4 -} from "File2.sol"; -import { - symbol1 as alias1, - symbol2 as alias2, - symbol3 as alias3, - symbol4 -} from "File2.sol"; diff --git a/forge-fmt/testdata/ImportDirective/original.sol b/forge-fmt/testdata/ImportDirective/original.sol deleted file mode 100644 index 0e18e10c1..000000000 --- a/forge-fmt/testdata/ImportDirective/original.sol +++ /dev/null @@ -1,10 +0,0 @@ -import "SomeFile.sol"; -import 'SomeFile.sol'; -import "SomeFile.sol" as SomeOtherFile; -import 'SomeFile.sol' as SomeOtherFile; -import * as SomeSymbol from "AnotherFile.sol"; -import * as SomeSymbol from 'AnotherFile.sol'; -import {symbol1 as alias, symbol2} from "File.sol"; -import {symbol1 as alias, symbol2} from 'File.sol'; -import {symbol1 as alias1, symbol2 as alias2, symbol3 as alias3, symbol4} from "File2.sol"; -import {symbol1 as alias1, symbol2 as alias2, symbol3 as alias3, symbol4} from 'File2.sol'; diff --git a/forge-fmt/testdata/ImportDirective/preserve-quote.fmt.sol b/forge-fmt/testdata/ImportDirective/preserve-quote.fmt.sol deleted file mode 100644 index d1bf9852c..000000000 --- a/forge-fmt/testdata/ImportDirective/preserve-quote.fmt.sol +++ /dev/null @@ -1,21 +0,0 @@ -// config: quote_style = "preserve" -import "SomeFile.sol"; -import 'SomeFile.sol'; -import "SomeFile.sol" as SomeOtherFile; -import 'SomeFile.sol' as SomeOtherFile; -import "AnotherFile.sol" as SomeSymbol; -import 'AnotherFile.sol' as SomeSymbol; -import {symbol1 as alias, symbol2} from "File.sol"; -import {symbol1 as alias, symbol2} from 'File.sol'; -import { - symbol1 as alias1, - symbol2 as alias2, - symbol3 as alias3, - symbol4 -} from "File2.sol"; -import { - symbol1 as alias1, - symbol2 as alias2, - symbol3 as alias3, - symbol4 -} from 'File2.sol'; diff --git a/forge-fmt/testdata/ImportDirective/single-quote.fmt.sol b/forge-fmt/testdata/ImportDirective/single-quote.fmt.sol deleted file mode 100644 index 10449e079..000000000 --- a/forge-fmt/testdata/ImportDirective/single-quote.fmt.sol +++ /dev/null @@ -1,21 +0,0 @@ -// config: quote_style = "single" -import 'SomeFile.sol'; -import 'SomeFile.sol'; -import 'SomeFile.sol' as SomeOtherFile; -import 'SomeFile.sol' as SomeOtherFile; -import 'AnotherFile.sol' as SomeSymbol; -import 'AnotherFile.sol' as SomeSymbol; -import {symbol1 as alias, symbol2} from 'File.sol'; -import {symbol1 as alias, symbol2} from 'File.sol'; -import { - symbol1 as alias1, - symbol2 as alias2, - symbol3 as alias3, - symbol4 -} from 'File2.sol'; -import { - symbol1 as alias1, - symbol2 as alias2, - symbol3 as alias3, - symbol4 -} from 'File2.sol'; diff --git a/forge-fmt/testdata/InlineDisable/fmt.sol b/forge-fmt/testdata/InlineDisable/fmt.sol deleted file mode 100644 index 03979bc18..000000000 --- a/forge-fmt/testdata/InlineDisable/fmt.sol +++ /dev/null @@ -1,491 +0,0 @@ -pragma solidity ^0.5.2; - -// forgefmt: disable-next-line -pragma solidity ^0.5.2; - -import { - symbol1 as alias1, - symbol2 as alias2, - symbol3 as alias3, - symbol4 -} from "File2.sol"; - -// forgefmt: disable-next-line -import {symbol1 as alias1, symbol2 as alias2, symbol3 as alias3, symbol4} from 'File2.sol'; - -enum States { - State1, - State2, - State3, - State4, - State5, - State6, - State7, - State8, - State9 -} - -// forgefmt: disable-next-line -enum States { State1, State2, State3, State4, State5, State6, State7, State8, State9 } - -// forgefmt: disable-next-line -bytes32 constant private BYTES = 0x035aff83d86937d35b32e04f0ddc6ff469290eef2f1b692d8a815c89404d4749; - -// forgefmt: disable-start - -// comment1 - - -// comment2 -/* comment 3 */ /* - comment4 - */ // comment 5 - - -/// Doccomment 1 - /// Doccomment 2 - -/** - * docccoment 3 - */ - - -// forgefmt: disable-end - -// forgefmt: disable-start - -function test1() {} - -function test2() {} - -// forgefmt: disable-end - -contract Constructors is Ownable, Changeable { - //forgefmt: disable-next-item - function Constructors(variable1) public Changeable(variable1) Ownable() onlyOwner { - } - - //forgefmt: disable-next-item - constructor(variable1, variable2, variable3, variable4, variable5, variable6, variable7) public Changeable(variable1, variable2, variable3, variable4, variable5, variable6, variable7) Ownable() onlyOwner {} -} - -function test() { - uint256 pi_approx = 666 / 212; - uint256 pi_approx = /* forgefmt: disable-start */ 666 / 212; /* forgefmt: disable-end */ - - // forgefmt: disable-next-item - uint256 pi_approx = 666 / - 212; - - uint256 test_postfix = 1; // forgefmt: disable-start - // comment1 - // comment2 - // comment3 - // forgefmt: disable-end -} - -// forgefmt: disable-next-item -function testFunc(uint256 num, bytes32 data , address receiver) - public payable attr1 Cool( "hello" ) {} - -function testAttrs(uint256 num, bytes32 data, address receiver) - // forgefmt: disable-next-line - public payable attr1 Cool( "hello" ) -{} - -// forgefmt: disable-next-line -function testParams(uint256 num, bytes32 data , address receiver) - public - payable - attr1 - Cool("hello") -{} - -function testDoWhile() external { - //forgefmt: disable-start - uint256 i; - do { "test"; } while (i != 0); - - do - {} - while - ( -i != 0); - - bool someVeryVeryLongCondition; - do { "test"; } while( - someVeryVeryLongCondition && !someVeryVeryLongCondition && -!someVeryVeryLongCondition && -someVeryVeryLongCondition); - - do i++; while(i < 10); - - do do i++; while (i < 30); while(i < 20); - //forgefmt: disable-end -} - -function forStatement() { - //forgefmt: disable-start - for - (uint256 i1 - ; i1 < 10; i1++) - { - i1++; - } - - uint256 i2; - for(++i2;i2<10;i2++) - - {} - - uint256 veryLongVariableName = 1000; - for ( uint256 i3; i3 < 10 - && veryLongVariableName>999 && veryLongVariableName< 1001 - ; i3++) - { i3 ++ ; } - - for (type(uint256).min;;) {} - - for (;;) { "test" ; } - - for (uint256 i4; i4< 10; i4++) i4++; - - for (uint256 i5; ;) - for (uint256 i6 = 10; i6 > i5; i6--) - i5++; - //forgefmt: disable-end -} - -function callArgTest() { - //forgefmt: disable-start - target.run{ gas: gasleft(), value: 1 wei }; - - target.run{gas:1,value:0x00}(); - - target.run{ - gas : 1000, - value: 1 ether - } (); - - target.run{ gas: estimate(), - value: value(1) }(); - - target.run { value: - value(1 ether), gas: veryAndVeryLongNameOfSomeGasEstimateFunction() } (); - - target.run /* comment 1 */ { value: /* comment2 */ 1 }; - - target.run { /* comment3 */ value: 1, // comment4 - gas: gasleft()}; - - target.run { - // comment5 - value: 1, - // comment6 - gas: gasleft()}; - //forgefmt: disable-end -} - -function ifTest() { - // forgefmt: disable-start - if (condition) - execute(); - else - executeElse(); - // forgefmt: disable-end - - /* forgefmt: disable-next-line */ - if (condition && anotherLongCondition ) { - execute(); - } -} - -function yulTest() { - // forgefmt: disable-start - assembly { - let payloadSize := sub(calldatasize(), 4) - calldatacopy(0, 4, payloadSize) - mstore(payloadSize, shl(96, caller())) - - let result := - delegatecall(gas(), moduleImpl, 0, add(payloadSize, 20), 0, 0) - - returndatacopy(0, 0, returndatasize()) - - switch result - case 0 { revert(0, returndatasize()) } - default { return(0, returndatasize()) } - } - // forgefmt: disable-end -} - -function literalTest() { - // forgefmt: disable-start - - true; - 0x123_456; - .1; - "foobar"; - hex"001122FF"; - 0xc02aaa39b223Fe8D0A0e5C4F27ead9083c756Cc2; - // forgefmt: disable-end - - // forgefmt: disable-next-line - bytes memory bytecode = hex"ff"; -} - -function returnTest() { - // forgefmt: disable-start - if (val == 0) { - return // return single 1 - 0x00; - } - - if (val == 1) { return - 1; } - - if (val == 2) { - return 3 - - - 1; - } - - if (val == 4) { - /* return single 2 */ return 2** // return single 3 - 3 // return single 4 - ; - } - - return value(); // return single 5 - return ; - return /* return mul 4 */ - ( - 987654321, 1234567890,/* return mul 5 */ false); - // forgefmt: disable-end -} - -function namedFuncCall() { - // forgefmt: disable-start - SimpleStruct memory simple = SimpleStruct({ val: 0 }); - - ComplexStruct memory complex = ComplexStruct({ val: 1, anotherVal: 2, flag: true, timestamp: block.timestamp }); - - StructWithAVeryLongNameThatExceedsMaximumLengthThatIsAllowedForFormatting memory long = StructWithAVeryLongNameThatExceedsMaximumLengthThatIsAllowedForFormatting({ whyNameSoLong: "dunno" }); - - SimpleStruct memory simple2 = SimpleStruct( - { // comment1 - /* comment2 */ val : /* comment3 */ 0 - - } - ); - // forgefmt: disable-end -} - -function revertTest() { - // forgefmt: disable-start - revert ({ }); - - revert EmptyError({}); - - revert SimpleError({ val: 0 }); - - revert ComplexError( - { - val: 0, - ts: block.timestamp, - message: "some reason" - }); - - revert SomeVeryVeryVeryLongErrorNameWithNamedArgumentsThatExceedsMaximumLength({ val: 0, ts: 0x00, message: "something unpredictable happened that caused execution to revert"}); - - revert // comment1 - ({}); - // forgefmt: disable-end -} - -function testTernary() { - // forgefmt: disable-start - bool condition; - bool someVeryVeryLongConditionUsedInTheTernaryExpression; - - condition ? 0 : 1; - - someVeryVeryLongConditionUsedInTheTernaryExpression ? 1234567890 : 987654321; - - condition /* comment1 */ ? /* comment2 */ 1001 /* comment3 */ : /* comment4 */ 2002; - - // comment5 - someVeryVeryLongConditionUsedInTheTernaryExpression ? 1 - // comment6 - : - // comment7 - 0; // comment8 - // forgefmt: disable-end -} - -function thisTest() { - // forgefmt: disable-start - this.someFunc(); - this.someVeryVeryVeryLongVariableNameThatWillBeAccessedByThisKeyword(); - this // comment1 - .someVeryVeryVeryLongVariableNameThatWillBeAccessedByThisKeyword(); - address(this).balance; - - address thisAddress = address( - // comment2 - /* comment3 */ this // comment 4 - ); - // forgefmt: disable-end -} - -function tryTest() { - // forgefmt: disable-start - try unknown.empty() {} catch {} - - try unknown.lookup() returns (uint256) {} catch Error(string memory) {} - - try unknown.lookup() returns (uint256) {} catch Error(string memory) {} catch (bytes memory) {} - - try unknown - .lookup() returns (uint256 - ) { - } catch ( bytes memory ){} - - try unknown.empty() { - unknown.doSomething(); - } catch { - unknown.handleError(); - } - - try unknown.empty() { - unknown.doSomething(); - } catch Error(string memory) {} - catch Panic(uint) {} - catch { - unknown.handleError(); - } - - try unknown.lookupMultipleValues() returns (uint256, uint256, uint256, uint256, uint256) {} catch Error(string memory) {} catch {} - - try unknown.lookupMultipleValues() returns (uint256, uint256, uint256, uint256, uint256) { - unknown.doSomething(); - } - catch Error(string memory) { - unknown.handleError(); - } - catch {} - // forgefmt: disable-end -} - -function testArray() { - // forgefmt: disable-start - msg.data[ - // comment1 - 4:]; - msg.data[ - : /* comment2 */ msg.data.length // comment3 - ]; - msg.data[ - // comment4 - 4 // comment5 - :msg.data.length /* comment6 */]; - // forgefmt: disable-end -} - -function testUnit() { - // forgefmt: disable-start - uint256 timestamp; - timestamp = 1 seconds; - timestamp = 1 minutes; - timestamp = 1 hours; - timestamp = 1 days; - timestamp = 1 weeks; - - uint256 value; - value = 1 wei; - value = 1 gwei; - value = 1 ether; - - uint256 someVeryVeryVeryLongVaribleNameForTheMultiplierForEtherValue; - - value = someVeryVeryVeryLongVaribleNameForTheMultiplierForEtherValue * 1 /* comment1 */ ether; // comment2 - - value = 1 // comment3 - // comment4 - ether; // comment5 - // forgefmt: disable-end -} - -contract UsingExampleContract { - // forgefmt: disable-start - using UsingExampleLibrary for * ; - using UsingExampleLibrary for uint; - using Example.UsingExampleLibrary for uint; - using { M.g, M.f} for uint; - using UsingExampleLibrary for uint global; - using { These, Are, MultipleLibraries, ThatNeedToBePut, OnSeparateLines } for uint; - using { This.isareally.longmember.access.expression.that.needs.to.besplit.into.lines } for uint; - // forgefmt: disable-end -} - -function testAssignment() { - // forgefmt: disable-start - (, uint256 second) = (1, 2); - (uint256 listItem001) = 1; - (uint256 listItem002, uint256 listItem003) = (10, 20); - (uint256 listItem004, uint256 listItem005, uint256 listItem006) = - (10, 20, 30); - // forgefmt: disable-end -} - -function testWhile() { - // forgefmt: disable-start - uint256 i1; - while ( i1 < 10 ) { - i1++; - } - - while (i1<10) i1++; - - while (i1<10) - while (i1<10) - i1++; - - uint256 i2; - while ( i2 < 10) { i2++; } - - uint256 i3; while ( - i3 < 10 - ) { i3++; } - - uint256 i4; while (i4 < 10) - - { i4 ++ ;} - - uint256 someLongVariableName; - while ( - someLongVariableName < 10 && someLongVariableName < 11 && someLongVariableName < 12 - ) { someLongVariableName ++; } someLongVariableName++; - // forgefmt: disable-end -} - -function testLine() {} - -function /* forgefmt: disable-line */ testLine( ) { } - -function testLine() {} - -function testLine( ) { } // forgefmt: disable-line - -// forgefmt: disable-start - - type Hello is uint256; - -error - TopLevelCustomError(); - error TopLevelCustomErrorWithArg(uint x) ; -error TopLevelCustomErrorArgWithoutName (string); - - event Event1(uint256 indexed a, uint256 indexed a, uint256 indexed a, uint256 indexed a, uint256 indexed a, uint256 indexed a, uint256 indexed a, uint256 indexed a, uint256 indexed a, uint256 indexed a); - -// forgefmt: disable-stop diff --git a/forge-fmt/testdata/InlineDisable/original.sol b/forge-fmt/testdata/InlineDisable/original.sol deleted file mode 100644 index 617da7719..000000000 --- a/forge-fmt/testdata/InlineDisable/original.sol +++ /dev/null @@ -1,469 +0,0 @@ -pragma solidity ^0.5.2; - -// forgefmt: disable-next-line -pragma solidity ^0.5.2; - -import {symbol1 as alias1, symbol2 as alias2, symbol3 as alias3, symbol4} from 'File2.sol'; - -// forgefmt: disable-next-line -import {symbol1 as alias1, symbol2 as alias2, symbol3 as alias3, symbol4} from 'File2.sol'; - -enum States { State1, State2, State3, State4, State5, State6, State7, State8, State9 } - -// forgefmt: disable-next-line -enum States { State1, State2, State3, State4, State5, State6, State7, State8, State9 } - -// forgefmt: disable-next-line -bytes32 constant private BYTES = 0x035aff83d86937d35b32e04f0ddc6ff469290eef2f1b692d8a815c89404d4749; - -// forgefmt: disable-start - -// comment1 - - -// comment2 -/* comment 3 */ /* - comment4 - */ // comment 5 - - -/// Doccomment 1 - /// Doccomment 2 - -/** - * docccoment 3 - */ - - -// forgefmt: disable-end - -// forgefmt: disable-start - -function test1() {} - -function test2() {} - -// forgefmt: disable-end - -contract Constructors is Ownable, Changeable { - //forgefmt: disable-next-item - function Constructors(variable1) public Changeable(variable1) Ownable() onlyOwner { - } - - //forgefmt: disable-next-item - constructor(variable1, variable2, variable3, variable4, variable5, variable6, variable7) public Changeable(variable1, variable2, variable3, variable4, variable5, variable6, variable7) Ownable() onlyOwner {} -} - -function test() { - uint256 pi_approx = 666 / 212; - uint256 pi_approx = /* forgefmt: disable-start */ 666 / 212; /* forgefmt: disable-end */ - - // forgefmt: disable-next-item - uint256 pi_approx = 666 / - 212; - - uint256 test_postfix = 1; // forgefmt: disable-start - // comment1 - // comment2 - // comment3 - // forgefmt: disable-end -} - -// forgefmt: disable-next-item -function testFunc(uint256 num, bytes32 data , address receiver) - public payable attr1 Cool( "hello" ) {} - -function testAttrs(uint256 num, bytes32 data , address receiver) - // forgefmt: disable-next-line - public payable attr1 Cool( "hello" ) {} - -// forgefmt: disable-next-line -function testParams(uint256 num, bytes32 data , address receiver) - public payable attr1 Cool( "hello" ) {} - - -function testDoWhile() external { - //forgefmt: disable-start - uint256 i; - do { "test"; } while (i != 0); - - do - {} - while - ( -i != 0); - - bool someVeryVeryLongCondition; - do { "test"; } while( - someVeryVeryLongCondition && !someVeryVeryLongCondition && -!someVeryVeryLongCondition && -someVeryVeryLongCondition); - - do i++; while(i < 10); - - do do i++; while (i < 30); while(i < 20); - //forgefmt: disable-end -} - -function forStatement() { - //forgefmt: disable-start - for - (uint256 i1 - ; i1 < 10; i1++) - { - i1++; - } - - uint256 i2; - for(++i2;i2<10;i2++) - - {} - - uint256 veryLongVariableName = 1000; - for ( uint256 i3; i3 < 10 - && veryLongVariableName>999 && veryLongVariableName< 1001 - ; i3++) - { i3 ++ ; } - - for (type(uint256).min;;) {} - - for (;;) { "test" ; } - - for (uint256 i4; i4< 10; i4++) i4++; - - for (uint256 i5; ;) - for (uint256 i6 = 10; i6 > i5; i6--) - i5++; - //forgefmt: disable-end -} - -function callArgTest() { - //forgefmt: disable-start - target.run{ gas: gasleft(), value: 1 wei }; - - target.run{gas:1,value:0x00}(); - - target.run{ - gas : 1000, - value: 1 ether - } (); - - target.run{ gas: estimate(), - value: value(1) }(); - - target.run { value: - value(1 ether), gas: veryAndVeryLongNameOfSomeGasEstimateFunction() } (); - - target.run /* comment 1 */ { value: /* comment2 */ 1 }; - - target.run { /* comment3 */ value: 1, // comment4 - gas: gasleft()}; - - target.run { - // comment5 - value: 1, - // comment6 - gas: gasleft()}; - //forgefmt: disable-end -} - -function ifTest() { - // forgefmt: disable-start - if (condition) - execute(); - else - executeElse(); - // forgefmt: disable-end - - /* forgefmt: disable-next-line */ - if (condition && anotherLongCondition ) { - execute(); } -} - -function yulTest() { - // forgefmt: disable-start - assembly { - let payloadSize := sub(calldatasize(), 4) - calldatacopy(0, 4, payloadSize) - mstore(payloadSize, shl(96, caller())) - - let result := - delegatecall(gas(), moduleImpl, 0, add(payloadSize, 20), 0, 0) - - returndatacopy(0, 0, returndatasize()) - - switch result - case 0 { revert(0, returndatasize()) } - default { return(0, returndatasize()) } - } - // forgefmt: disable-end -} - -function literalTest() { - // forgefmt: disable-start - - true; - 0x123_456; - .1; - "foobar"; - hex"001122FF"; - 0xc02aaa39b223Fe8D0A0e5C4F27ead9083c756Cc2; - // forgefmt: disable-end - - // forgefmt: disable-next-line - bytes memory bytecode = - hex"ff"; -} - -function returnTest() { - // forgefmt: disable-start - if (val == 0) { - return // return single 1 - 0x00; - } - - if (val == 1) { return - 1; } - - if (val == 2) { - return 3 - - - 1; - } - - if (val == 4) { - /* return single 2 */ return 2** // return single 3 - 3 // return single 4 - ; - } - - return value(); // return single 5 - return ; - return /* return mul 4 */ - ( - 987654321, 1234567890,/* return mul 5 */ false); - // forgefmt: disable-end -} - -function namedFuncCall() { - // forgefmt: disable-start - SimpleStruct memory simple = SimpleStruct({ val: 0 }); - - ComplexStruct memory complex = ComplexStruct({ val: 1, anotherVal: 2, flag: true, timestamp: block.timestamp }); - - StructWithAVeryLongNameThatExceedsMaximumLengthThatIsAllowedForFormatting memory long = StructWithAVeryLongNameThatExceedsMaximumLengthThatIsAllowedForFormatting({ whyNameSoLong: "dunno" }); - - SimpleStruct memory simple2 = SimpleStruct( - { // comment1 - /* comment2 */ val : /* comment3 */ 0 - - } - ); - // forgefmt: disable-end -} - -function revertTest() { - // forgefmt: disable-start - revert ({ }); - - revert EmptyError({}); - - revert SimpleError({ val: 0 }); - - revert ComplexError( - { - val: 0, - ts: block.timestamp, - message: "some reason" - }); - - revert SomeVeryVeryVeryLongErrorNameWithNamedArgumentsThatExceedsMaximumLength({ val: 0, ts: 0x00, message: "something unpredictable happened that caused execution to revert"}); - - revert // comment1 - ({}); - // forgefmt: disable-end -} - -function testTernary() { - // forgefmt: disable-start - bool condition; - bool someVeryVeryLongConditionUsedInTheTernaryExpression; - - condition ? 0 : 1; - - someVeryVeryLongConditionUsedInTheTernaryExpression ? 1234567890 : 987654321; - - condition /* comment1 */ ? /* comment2 */ 1001 /* comment3 */ : /* comment4 */ 2002; - - // comment5 - someVeryVeryLongConditionUsedInTheTernaryExpression ? 1 - // comment6 - : - // comment7 - 0; // comment8 - // forgefmt: disable-end -} - -function thisTest() { - // forgefmt: disable-start - this.someFunc(); - this.someVeryVeryVeryLongVariableNameThatWillBeAccessedByThisKeyword(); - this // comment1 - .someVeryVeryVeryLongVariableNameThatWillBeAccessedByThisKeyword(); - address(this).balance; - - address thisAddress = address( - // comment2 - /* comment3 */ this // comment 4 - ); - // forgefmt: disable-end -} - -function tryTest() { - // forgefmt: disable-start - try unknown.empty() {} catch {} - - try unknown.lookup() returns (uint256) {} catch Error(string memory) {} - - try unknown.lookup() returns (uint256) {} catch Error(string memory) {} catch (bytes memory) {} - - try unknown - .lookup() returns (uint256 - ) { - } catch ( bytes memory ){} - - try unknown.empty() { - unknown.doSomething(); - } catch { - unknown.handleError(); - } - - try unknown.empty() { - unknown.doSomething(); - } catch Error(string memory) {} - catch Panic(uint) {} - catch { - unknown.handleError(); - } - - try unknown.lookupMultipleValues() returns (uint256, uint256, uint256, uint256, uint256) {} catch Error(string memory) {} catch {} - - try unknown.lookupMultipleValues() returns (uint256, uint256, uint256, uint256, uint256) { - unknown.doSomething(); - } - catch Error(string memory) { - unknown.handleError(); - } - catch {} - // forgefmt: disable-end -} - -function testArray() { - // forgefmt: disable-start - msg.data[ - // comment1 - 4:]; - msg.data[ - : /* comment2 */ msg.data.length // comment3 - ]; - msg.data[ - // comment4 - 4 // comment5 - :msg.data.length /* comment6 */]; - // forgefmt: disable-end -} - -function testUnit() { - // forgefmt: disable-start - uint256 timestamp; - timestamp = 1 seconds; - timestamp = 1 minutes; - timestamp = 1 hours; - timestamp = 1 days; - timestamp = 1 weeks; - - uint256 value; - value = 1 wei; - value = 1 gwei; - value = 1 ether; - - uint256 someVeryVeryVeryLongVaribleNameForTheMultiplierForEtherValue; - - value = someVeryVeryVeryLongVaribleNameForTheMultiplierForEtherValue * 1 /* comment1 */ ether; // comment2 - - value = 1 // comment3 - // comment4 - ether; // comment5 - // forgefmt: disable-end -} - -contract UsingExampleContract { - // forgefmt: disable-start - using UsingExampleLibrary for * ; - using UsingExampleLibrary for uint; - using Example.UsingExampleLibrary for uint; - using { M.g, M.f} for uint; - using UsingExampleLibrary for uint global; - using { These, Are, MultipleLibraries, ThatNeedToBePut, OnSeparateLines } for uint; - using { This.isareally.longmember.access.expression.that.needs.to.besplit.into.lines } for uint; - // forgefmt: disable-end -} - -function testAssignment() { - // forgefmt: disable-start - (, uint256 second) = (1, 2); - (uint256 listItem001) = 1; - (uint256 listItem002, uint256 listItem003) = (10, 20); - (uint256 listItem004, uint256 listItem005, uint256 listItem006) = - (10, 20, 30); - // forgefmt: disable-end -} - -function testWhile() { - // forgefmt: disable-start - uint256 i1; - while ( i1 < 10 ) { - i1++; - } - - while (i1<10) i1++; - - while (i1<10) - while (i1<10) - i1++; - - uint256 i2; - while ( i2 < 10) { i2++; } - - uint256 i3; while ( - i3 < 10 - ) { i3++; } - - uint256 i4; while (i4 < 10) - - { i4 ++ ;} - - uint256 someLongVariableName; - while ( - someLongVariableName < 10 && someLongVariableName < 11 && someLongVariableName < 12 - ) { someLongVariableName ++; } someLongVariableName++; - // forgefmt: disable-end -} - -function testLine( ) { } -function /* forgefmt: disable-line */ testLine( ) { } -function testLine( ) { } -function testLine( ) { } // forgefmt: disable-line - -// forgefmt: disable-start - - type Hello is uint256; - -error - TopLevelCustomError(); - error TopLevelCustomErrorWithArg(uint x) ; -error TopLevelCustomErrorArgWithoutName (string); - - event Event1(uint256 indexed a, uint256 indexed a, uint256 indexed a, uint256 indexed a, uint256 indexed a, uint256 indexed a, uint256 indexed a, uint256 indexed a, uint256 indexed a, uint256 indexed a); - -// forgefmt: disable-stop diff --git a/forge-fmt/testdata/IntTypes/fmt.sol b/forge-fmt/testdata/IntTypes/fmt.sol deleted file mode 100644 index b244d0281..000000000 --- a/forge-fmt/testdata/IntTypes/fmt.sol +++ /dev/null @@ -1,24 +0,0 @@ -contract Contract { - uint256 constant UINT256_IMPL = 0; - uint8 constant UINT8 = 1; - uint128 constant UINT128 = 2; - uint256 constant UINT256_EXPL = 3; - - int256 constant INT256_IMPL = 4; - int8 constant INT8 = 5; - int128 constant INT128 = 6; - int256 constant INT256_EXPL = 7; - - function test( - uint256 uint256_impl, - uint8 uint8_var, - uint128 uint128_var, - uint256 uint256_expl, - int256 int256_impl, - int8 int8_var, - int128 int128_var, - int256 int256_expl - ) public { - // do something - } -} diff --git a/forge-fmt/testdata/IntTypes/original.sol b/forge-fmt/testdata/IntTypes/original.sol deleted file mode 100644 index 2990aadbb..000000000 --- a/forge-fmt/testdata/IntTypes/original.sol +++ /dev/null @@ -1,24 +0,0 @@ -contract Contract { - uint constant UINT256_IMPL = 0; - uint8 constant UINT8 = 1; - uint128 constant UINT128 = 2; - uint256 constant UINT256_EXPL = 3; - - int constant INT256_IMPL = 4; - int8 constant INT8 = 5; - int128 constant INT128 = 6; - int256 constant INT256_EXPL = 7; - - function test( - uint uint256_impl, - uint8 uint8_var, - uint128 uint128_var, - uint256 uint256_expl, - int int256_impl, - int8 int8_var, - int128 int128_var, - int256 int256_expl - ) public { - // do something - } -} diff --git a/forge-fmt/testdata/IntTypes/preserve.fmt.sol b/forge-fmt/testdata/IntTypes/preserve.fmt.sol deleted file mode 100644 index 70d780d89..000000000 --- a/forge-fmt/testdata/IntTypes/preserve.fmt.sol +++ /dev/null @@ -1,25 +0,0 @@ -// config: int_types = "preserve" -contract Contract { - uint constant UINT256_IMPL = 0; - uint8 constant UINT8 = 1; - uint128 constant UINT128 = 2; - uint256 constant UINT256_EXPL = 3; - - int constant INT256_IMPL = 4; - int8 constant INT8 = 5; - int128 constant INT128 = 6; - int256 constant INT256_EXPL = 7; - - function test( - uint uint256_impl, - uint8 uint8_var, - uint128 uint128_var, - uint256 uint256_expl, - int int256_impl, - int8 int8_var, - int128 int128_var, - int256 int256_expl - ) public { - // do something - } -} diff --git a/forge-fmt/testdata/IntTypes/short.fmt.sol b/forge-fmt/testdata/IntTypes/short.fmt.sol deleted file mode 100644 index 45064d41e..000000000 --- a/forge-fmt/testdata/IntTypes/short.fmt.sol +++ /dev/null @@ -1,25 +0,0 @@ -// config: int_types = "short" -contract Contract { - uint constant UINT256_IMPL = 0; - uint8 constant UINT8 = 1; - uint128 constant UINT128 = 2; - uint constant UINT256_EXPL = 3; - - int constant INT256_IMPL = 4; - int8 constant INT8 = 5; - int128 constant INT128 = 6; - int constant INT256_EXPL = 7; - - function test( - uint uint256_impl, - uint8 uint8_var, - uint128 uint128_var, - uint uint256_expl, - int int256_impl, - int8 int8_var, - int128 int128_var, - int int256_expl - ) public { - // do something - } -} diff --git a/forge-fmt/testdata/LiteralExpression/fmt.sol b/forge-fmt/testdata/LiteralExpression/fmt.sol deleted file mode 100644 index 7fa6878e5..000000000 --- a/forge-fmt/testdata/LiteralExpression/fmt.sol +++ /dev/null @@ -1,59 +0,0 @@ -contract LiteralExpressions { - function test() external { - // bool literals - true; - false; - /* comment1 */ - true; /* comment2 */ - // comment3 - false; // comment4 - - // number literals - 1; - 123_000; - 1_2e345_678; - -1; - 2e-10; - // comment5 - /* comment6 */ - -1; /* comment7 */ - - // hex number literals - 0x00; - 0x123_456; - 0x2eff_abde; - - // rational number literals - 0.1; - 1.3; - 2.5e1; - - // string literals - ""; - "foobar"; - "foo" // comment8 - " bar"; - // comment9 - "\ -some words"; /* comment10 */ - unicode"Hello 😃"; - - // quoted strings - 'hello "world"'; - "hello 'world'"; - "hello \'world\'"; - "hello \"world\""; - "hello \"world\""; - "hello \'world\'"; - - // hex literals - hex"001122FF"; - hex"001122FF"; - hex"00112233" hex"44556677"; - - // address literals - 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; - // non checksummed address - 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; - } -} diff --git a/forge-fmt/testdata/LiteralExpression/original.sol b/forge-fmt/testdata/LiteralExpression/original.sol deleted file mode 100644 index 5c559531d..000000000 --- a/forge-fmt/testdata/LiteralExpression/original.sol +++ /dev/null @@ -1,58 +0,0 @@ -contract LiteralExpressions { - function test() external { - // bool literals - true; - false; - /* comment1 */ true /* comment2 */; - // comment3 - false; // comment4 - - // number literals - 1; - 123_000; - 1_2e345_678; - -1; - 2e-10; - // comment5 - /* comment6 */ -1 /* comment7 */; - - // hex number literals - 0x00; - 0x123_456; - 0x2eff_abde; - - // rational number literals - .1; - 1.3; - 2.5e1; - - // string literals - ""; - "foobar"; - "foo" // comment8 - " bar"; - // comment9 - "\ -some words" /* comment10 */; - unicode"Hello 😃"; - - // quoted strings - 'hello "world"'; - "hello 'world'"; - 'hello \'world\''; - "hello \"world\""; - 'hello \"world\"'; - "hello \'world\'"; - - - // hex literals - hex"001122FF"; - hex'0011_22_FF'; - hex"00112233" hex"44556677"; - - // address literals - 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; - // non checksummed address - 0xc02aaa39b223Fe8D0A0e5C4F27ead9083c756Cc2; - } -} diff --git a/forge-fmt/testdata/LiteralExpression/preserve-quote.fmt.sol b/forge-fmt/testdata/LiteralExpression/preserve-quote.fmt.sol deleted file mode 100644 index 3d9490804..000000000 --- a/forge-fmt/testdata/LiteralExpression/preserve-quote.fmt.sol +++ /dev/null @@ -1,60 +0,0 @@ -// config: quote_style = "preserve" -contract LiteralExpressions { - function test() external { - // bool literals - true; - false; - /* comment1 */ - true; /* comment2 */ - // comment3 - false; // comment4 - - // number literals - 1; - 123_000; - 1_2e345_678; - -1; - 2e-10; - // comment5 - /* comment6 */ - -1; /* comment7 */ - - // hex number literals - 0x00; - 0x123_456; - 0x2eff_abde; - - // rational number literals - 0.1; - 1.3; - 2.5e1; - - // string literals - ""; - "foobar"; - "foo" // comment8 - " bar"; - // comment9 - "\ -some words"; /* comment10 */ - unicode"Hello 😃"; - - // quoted strings - 'hello "world"'; - "hello 'world'"; - 'hello \'world\''; - "hello \"world\""; - 'hello \"world\"'; - "hello \'world\'"; - - // hex literals - hex"001122FF"; - hex'001122FF'; - hex"00112233" hex"44556677"; - - // address literals - 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; - // non checksummed address - 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; - } -} diff --git a/forge-fmt/testdata/LiteralExpression/single-quote.fmt.sol b/forge-fmt/testdata/LiteralExpression/single-quote.fmt.sol deleted file mode 100644 index cdc67a2c6..000000000 --- a/forge-fmt/testdata/LiteralExpression/single-quote.fmt.sol +++ /dev/null @@ -1,60 +0,0 @@ -// config: quote_style = "single" -contract LiteralExpressions { - function test() external { - // bool literals - true; - false; - /* comment1 */ - true; /* comment2 */ - // comment3 - false; // comment4 - - // number literals - 1; - 123_000; - 1_2e345_678; - -1; - 2e-10; - // comment5 - /* comment6 */ - -1; /* comment7 */ - - // hex number literals - 0x00; - 0x123_456; - 0x2eff_abde; - - // rational number literals - 0.1; - 1.3; - 2.5e1; - - // string literals - ''; - 'foobar'; - 'foo' // comment8 - ' bar'; - // comment9 - '\ -some words'; /* comment10 */ - unicode'Hello 😃'; - - // quoted strings - 'hello "world"'; - "hello 'world'"; - 'hello \'world\''; - 'hello \"world\"'; - 'hello \"world\"'; - 'hello \'world\''; - - // hex literals - hex'001122FF'; - hex'001122FF'; - hex'00112233' hex'44556677'; - - // address literals - 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; - // non checksummed address - 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; - } -} diff --git a/forge-fmt/testdata/MappingType/fmt.sol b/forge-fmt/testdata/MappingType/fmt.sol deleted file mode 100644 index 7f6297cff..000000000 --- a/forge-fmt/testdata/MappingType/fmt.sol +++ /dev/null @@ -1,35 +0,0 @@ -// config: line_length = 40 -contract X { - type Y is bytes32; -} - -type SomeVeryLongTypeName is uint256; - -contract Mapping { - mapping(uint256 => X.Y) mapping1; - mapping( - uint256 key => uint256 value - ) mapping2; - mapping( - uint256 veryLongKeyName - => uint256 veryLongValueName - ) mapping3; - mapping( - string anotherVeryLongKeyName - => uint256 anotherVeryLongValueName - ) mapping4; - mapping( - SomeVeryLongTypeName anotherVeryLongKeyName - => uint256 anotherVeryLongValueName - ) mapping5; - - mapping( - // comment1 - uint256 key => uint256 value - // comment2 - ) mapping6; - mapping( /* comment3 */ - uint256 /* comment4 */ key /* comment5 */ - => /* comment6 */ uint256 /* comment7 */ value /* comment8 */ /* comment9 */ - ) /* comment10 */ mapping7; -} diff --git a/forge-fmt/testdata/MappingType/original.sol b/forge-fmt/testdata/MappingType/original.sol deleted file mode 100644 index 58ebb134d..000000000 --- a/forge-fmt/testdata/MappingType/original.sol +++ /dev/null @@ -1,23 +0,0 @@ -contract X { - type Y is bytes32; -} - -type SomeVeryLongTypeName is uint256; - -contract Mapping { - mapping(uint256 => X.Y) mapping1; - mapping(uint256 key => uint256 value) mapping2; - mapping(uint256 veryLongKeyName => uint256 veryLongValueName) mapping3; - mapping(string anotherVeryLongKeyName => uint256 anotherVeryLongValueName) mapping4; - mapping(SomeVeryLongTypeName anotherVeryLongKeyName => uint256 anotherVeryLongValueName) mapping5; - - mapping( - - // comment1 - uint256 key => uint256 value -// comment2 - ) mapping6; - mapping( /* comment3 */ - uint256 /* comment4 */ key /* comment5 */ => /* comment6 */ uint256 /* comment7 */ value /* comment8 */ /* comment9 */ - ) /* comment10 */ mapping7; -} \ No newline at end of file diff --git a/forge-fmt/testdata/ModifierDefinition/fmt.sol b/forge-fmt/testdata/ModifierDefinition/fmt.sol deleted file mode 100644 index e735f25b4..000000000 --- a/forge-fmt/testdata/ModifierDefinition/fmt.sol +++ /dev/null @@ -1,14 +0,0 @@ -// config: line_length = 60 -contract ModifierDefinitions { - modifier noParams() {} - modifier oneParam(uint256 a) {} - modifier twoParams(uint256 a, uint256 b) {} - modifier threeParams(uint256 a, uint256 b, uint256 c) {} - modifier fourParams( - uint256 a, - uint256 b, - uint256 c, - uint256 d - ) {} - modifier overridden() override(Base1, Base2) {} -} diff --git a/forge-fmt/testdata/ModifierDefinition/original.sol b/forge-fmt/testdata/ModifierDefinition/original.sol deleted file mode 100644 index f4a1c4284..000000000 --- a/forge-fmt/testdata/ModifierDefinition/original.sol +++ /dev/null @@ -1,9 +0,0 @@ -contract ModifierDefinitions { - modifier noParams() {} - modifier oneParam(uint a) {} - modifier twoParams(uint a,uint b) {} - modifier threeParams(uint a,uint b ,uint c) {} - modifier fourParams(uint a,uint b ,uint c, uint d) {} - modifier overridden ( - ) override ( Base1 , Base2) {} -} diff --git a/forge-fmt/testdata/ModifierDefinition/override-spacing.fmt.sol b/forge-fmt/testdata/ModifierDefinition/override-spacing.fmt.sol deleted file mode 100644 index 68906fcdc..000000000 --- a/forge-fmt/testdata/ModifierDefinition/override-spacing.fmt.sol +++ /dev/null @@ -1,15 +0,0 @@ -// config: line_length = 60 -// config: override_spacing = true -contract ModifierDefinitions { - modifier noParams() {} - modifier oneParam(uint256 a) {} - modifier twoParams(uint256 a, uint256 b) {} - modifier threeParams(uint256 a, uint256 b, uint256 c) {} - modifier fourParams( - uint256 a, - uint256 b, - uint256 c, - uint256 d - ) {} - modifier overridden() override (Base1, Base2) {} -} diff --git a/forge-fmt/testdata/NamedFunctionCallExpression/fmt.sol b/forge-fmt/testdata/NamedFunctionCallExpression/fmt.sol deleted file mode 100644 index 14a24c900..000000000 --- a/forge-fmt/testdata/NamedFunctionCallExpression/fmt.sol +++ /dev/null @@ -1,47 +0,0 @@ -contract NamedFunctionCallExpression { - struct SimpleStruct { - uint256 val; - } - - struct ComplexStruct { - uint256 val; - uint256 anotherVal; - bool flag; - uint256 timestamp; - } - - struct - StructWithAVeryLongNameThatExceedsMaximumLengthThatIsAllowedForFormatting { - string whyNameSoLong; - } - - function test() external { - SimpleStruct memory simple = SimpleStruct({val: 0}); - - ComplexStruct memory complex = ComplexStruct({ - val: 1, - anotherVal: 2, - flag: true, - timestamp: block.timestamp - }); - - StructWithAVeryLongNameThatExceedsMaximumLengthThatIsAllowedForFormatting - memory long = - StructWithAVeryLongNameThatExceedsMaximumLengthThatIsAllowedForFormatting({ - whyNameSoLong: "dunno" - }); - - SimpleStruct memory simple2 = SimpleStruct({ // comment1 - /* comment2 */ - val: /* comment3 */ 0 - }); - - SimpleStruct memory simple3 = SimpleStruct({ - /* comment4 */ - // comment5 - val: // comment6 - 0 // comment7 - // comment8 - }); - } -} diff --git a/forge-fmt/testdata/NamedFunctionCallExpression/original.sol b/forge-fmt/testdata/NamedFunctionCallExpression/original.sol deleted file mode 100644 index 8b34474a7..000000000 --- a/forge-fmt/testdata/NamedFunctionCallExpression/original.sol +++ /dev/null @@ -1,28 +0,0 @@ -contract NamedFunctionCallExpression { - struct SimpleStruct { uint256 val; } - struct ComplexStruct { uint256 val; uint256 anotherVal; bool flag; uint256 timestamp; } - struct StructWithAVeryLongNameThatExceedsMaximumLengthThatIsAllowedForFormatting { string whyNameSoLong; } - - function test() external { - SimpleStruct memory simple = SimpleStruct({ val: 0 }); - - ComplexStruct memory complex = ComplexStruct({ val: 1, anotherVal: 2, flag: true, timestamp: block.timestamp }); - - StructWithAVeryLongNameThatExceedsMaximumLengthThatIsAllowedForFormatting memory long = StructWithAVeryLongNameThatExceedsMaximumLengthThatIsAllowedForFormatting({ whyNameSoLong: "dunno" }); - - SimpleStruct memory simple2 = SimpleStruct( - { // comment1 - /* comment2 */ val : /* comment3 */ 0 - - } - ); - - SimpleStruct memory simple3 = SimpleStruct( - /* comment4 */ { - // comment5 - val: // comment6 - 0 // comment7 - // comment8 - }); - } -} \ No newline at end of file diff --git a/forge-fmt/testdata/NumberLiteralUnderscore/fmt.sol b/forge-fmt/testdata/NumberLiteralUnderscore/fmt.sol deleted file mode 100644 index 7c9f5740d..000000000 --- a/forge-fmt/testdata/NumberLiteralUnderscore/fmt.sol +++ /dev/null @@ -1,25 +0,0 @@ -contract NumberLiteral { - function test() external { - 1; - 123_000; - 1_2e345_678; - -1; - 2e-10; - 0.1; - 1.3; - 2.5e1; - 1.23454; - 1.2e34_5_678; - 134411.2e34_5_678; - 13431.134112e34_135_678; - 13431.0134112; - 13431.134112e-139_3141340; - 134411.2e34_5_6780; - 13431.134112e34_135_6780; - 0.134112; - 1.0; - 13431.134112e-139_3141340; - 123e456; - 1_000; - } -} diff --git a/forge-fmt/testdata/NumberLiteralUnderscore/original.sol b/forge-fmt/testdata/NumberLiteralUnderscore/original.sol deleted file mode 100644 index 8e88fc6d2..000000000 --- a/forge-fmt/testdata/NumberLiteralUnderscore/original.sol +++ /dev/null @@ -1,25 +0,0 @@ -contract NumberLiteral { - function test() external { - 1; - 123_000; - 1_2e345_678; - -1; - 2e-10; - .1; - 1.3; - 2.5e1; - 1.23454e0; - 1.2e34_5_678; - 134411.2e34_5_678; - 13431.134112e34_135_678; - 13431.0134112; - 13431.134112e-139_3141340; - 00134411.200e0034_5_6780; - 013431.13411200e34_135_6780; - 00.1341120000; - 1.0000; - 0013431.13411200e-00139_3141340; - 123E456; - 1_000; - } -} diff --git a/forge-fmt/testdata/NumberLiteralUnderscore/remove.fmt.sol b/forge-fmt/testdata/NumberLiteralUnderscore/remove.fmt.sol deleted file mode 100644 index cbde2e9b9..000000000 --- a/forge-fmt/testdata/NumberLiteralUnderscore/remove.fmt.sol +++ /dev/null @@ -1,26 +0,0 @@ -// config: number_underscore = "remove" -contract NumberLiteral { - function test() external { - 1; - 123000; - 12e345678; - -1; - 2e-10; - 0.1; - 1.3; - 2.5e1; - 1.23454; - 1.2e345678; - 134411.2e345678; - 13431.134112e34135678; - 13431.0134112; - 13431.134112e-1393141340; - 134411.2e3456780; - 13431.134112e341356780; - 0.134112; - 1.0; - 13431.134112e-1393141340; - 123e456; - 1000; - } -} diff --git a/forge-fmt/testdata/NumberLiteralUnderscore/thousands.fmt.sol b/forge-fmt/testdata/NumberLiteralUnderscore/thousands.fmt.sol deleted file mode 100644 index a9fc8a69a..000000000 --- a/forge-fmt/testdata/NumberLiteralUnderscore/thousands.fmt.sol +++ /dev/null @@ -1,26 +0,0 @@ -// config: number_underscore = "thousands" -contract NumberLiteral { - function test() external { - 1; - 123_000; - 12e345_678; - -1; - 2e-10; - 0.1; - 1.3; - 2.5e1; - 1.23454; - 1.2e345_678; - 134_411.2e345_678; - 13_431.134112e34_135_678; - 13_431.0134112; - 13_431.134112e-1_393_141_340; - 134_411.2e3_456_780; - 13_431.134112e341_356_780; - 0.134112; - 1.0; - 13_431.134112e-1_393_141_340; - 123e456; - 1000; - } -} diff --git a/forge-fmt/testdata/OperatorExpressions/fmt.sol b/forge-fmt/testdata/OperatorExpressions/fmt.sol deleted file mode 100644 index be0cec917..000000000 --- a/forge-fmt/testdata/OperatorExpressions/fmt.sol +++ /dev/null @@ -1,43 +0,0 @@ -function test() { - uint256 expr001 = (1 + 2) + 3; - uint256 expr002 = 1 + (2 + 3); - uint256 expr003 = 1 * 2 + 3; - uint256 expr004 = (1 * 2) + 3; - uint256 expr005 = 1 * (2 + 3); - uint256 expr006 = 1 + 2 * 3; - uint256 expr007 = (1 + 2) * 3; - uint256 expr008 = 1 + (2 * 3); - uint256 expr009 = 1 ** 2 ** 3; - uint256 expr010 = 1 ** (2 ** 3); - uint256 expr011 = (1 ** 2) ** 3; - uint256 expr012 = ++expr011 + 1; - bool expr013 = ++expr012 == expr011 - 1; - bool expr014 = ++(++expr013)--; - if (++batch.movesPerformed == drivers.length) createNewBatch(); - sum += getPrice( - ACCELERATE_STARTING_PRICE, - ACCELERATE_PER_PERIOD_DECREASE, - idleTicks, - actionsSold[ActionType.ACCELERATE] + i, - ACCELERATE_SELL_PER_TICK - ) / 1e18; - other += 1e18 - / getPrice( - ACCELERATE_STARTING_PRICE, - ACCELERATE_PER_PERIOD_DECREASE, - idleTicks, - actionsSold[ActionType.ACCELERATE] + i, - ACCELERATE_SELL_PER_TICK - ); - if ( - op == 0x54 // SLOAD - || op == 0x55 // SSTORE - || op == 0xF0 // CREATE - || op == 0xF1 // CALL - || op == 0xF2 // CALLCODE - || op == 0xF4 // DELEGATECALL - || op == 0xF5 // CREATE2 - || op == 0xFA // STATICCALL - || op == 0xFF // SELFDESTRUCT - ) return false; -} diff --git a/forge-fmt/testdata/OperatorExpressions/original.sol b/forge-fmt/testdata/OperatorExpressions/original.sol deleted file mode 100644 index c3e788d0a..000000000 --- a/forge-fmt/testdata/OperatorExpressions/original.sol +++ /dev/null @@ -1,30 +0,0 @@ -function test() { - uint256 expr001 = (1 + 2) + 3; - uint256 expr002 = 1 + (2 + 3); - uint256 expr003 = 1 * 2 + 3; - uint256 expr004 = (1 * 2) + 3; - uint256 expr005 = 1 * (2 + 3); - uint256 expr006 = 1 + 2 * 3; - uint256 expr007 = (1 + 2) * 3; - uint256 expr008 = 1 + (2 * 3); - uint256 expr009 = 1 ** 2 ** 3; - uint256 expr010 = 1 ** (2 ** 3); - uint256 expr011 = (1 ** 2) ** 3; - uint256 expr012 = ++expr011 + 1; - bool expr013 = ++expr012 == expr011 - 1; - bool expr014 = ++(++expr013)--; - if (++batch.movesPerformed == drivers.length) createNewBatch(); - sum += getPrice(ACCELERATE_STARTING_PRICE, ACCELERATE_PER_PERIOD_DECREASE, idleTicks, actionsSold[ActionType.ACCELERATE] + i, ACCELERATE_SELL_PER_TICK) / 1e18; - other += 1e18 / getPrice(ACCELERATE_STARTING_PRICE, ACCELERATE_PER_PERIOD_DECREASE, idleTicks, actionsSold[ActionType.ACCELERATE] + i, ACCELERATE_SELL_PER_TICK); - if ( - op == 0x54 // SLOAD - || op == 0x55 // SSTORE - || op == 0xF0 // CREATE - || op == 0xF1 // CALL - || op == 0xF2 // CALLCODE - || op == 0xF4 // DELEGATECALL - || op == 0xF5 // CREATE2 - || op == 0xFA // STATICCALL - || op == 0xFF // SELFDESTRUCT - ) return false; -} diff --git a/forge-fmt/testdata/PragmaDirective/fmt.sol b/forge-fmt/testdata/PragmaDirective/fmt.sol deleted file mode 100644 index 645a4f490..000000000 --- a/forge-fmt/testdata/PragmaDirective/fmt.sol +++ /dev/null @@ -1,9 +0,0 @@ -pragma solidity 0.8.17; -pragma experimental ABIEncoderV2; - -contract Contract {} - -// preserves lines -pragma solidity 0.8.17; - -pragma experimental ABIEncoderV2; diff --git a/forge-fmt/testdata/PragmaDirective/original.sol b/forge-fmt/testdata/PragmaDirective/original.sol deleted file mode 100644 index 535b70959..000000000 --- a/forge-fmt/testdata/PragmaDirective/original.sol +++ /dev/null @@ -1,9 +0,0 @@ -pragma solidity 0.8.17; -pragma experimental ABIEncoderV2; - -contract Contract {} - -// preserves lines -pragma solidity 0.8.17; - -pragma experimental ABIEncoderV2; \ No newline at end of file diff --git a/forge-fmt/testdata/Repros/fmt.sol b/forge-fmt/testdata/Repros/fmt.sol deleted file mode 100644 index 8439563ab..000000000 --- a/forge-fmt/testdata/Repros/fmt.sol +++ /dev/null @@ -1,7 +0,0 @@ -// Repros of fmt issues - -// https://github.com/foundry-rs/foundry/issues/4403 -function errorIdentifier() { - bytes memory error = bytes(""); - if (error.length > 0) {} -} diff --git a/forge-fmt/testdata/Repros/original.sol b/forge-fmt/testdata/Repros/original.sol deleted file mode 100644 index 8439563ab..000000000 --- a/forge-fmt/testdata/Repros/original.sol +++ /dev/null @@ -1,7 +0,0 @@ -// Repros of fmt issues - -// https://github.com/foundry-rs/foundry/issues/4403 -function errorIdentifier() { - bytes memory error = bytes(""); - if (error.length > 0) {} -} diff --git a/forge-fmt/testdata/ReturnStatement/fmt.sol b/forge-fmt/testdata/ReturnStatement/fmt.sol deleted file mode 100644 index d628d6097..000000000 --- a/forge-fmt/testdata/ReturnStatement/fmt.sol +++ /dev/null @@ -1,66 +0,0 @@ -contract ReturnStatement { - function value() internal returns (uint256) { - return type(uint256).max; - } - - function returnEmpty() external { - if (true) { - return; - } - - if (false) { - // return empty 1 - return; /* return empty 2 */ // return empty 3 - } - - /* return empty 4 */ - return; // return empty 5 - } - - function returnSingleValue(uint256 val) external returns (uint256) { - if (val == 0) { - return // return single 1 - 0x00; - } - - if (val == 1) return 1; - - if (val == 2) { - return 3 - 1; - } - - if (val == 4) { - /* return single 2 */ - return 2 // return single 3 - ** 3; // return single 4 - } - - return value(); // return single 5 - } - - function returnMultipleValues(uint256 val) - external - returns (uint256, uint256, bool) - { - if (val == 0) { - return /* return mul 1 */ (0, 1, /* return mul 2 */ false); - } - - if (val == 1) { - // return mul 3 - return /* return mul 4 */ - (987654321, 1234567890, /* return mul 5 */ false); - } - - if (val == 2) { - return /* return mul 6 */ ( - 1234567890 + 987654321 + 87654123536, - 987654321 + 1234567890 + 124245235235, - true - ); - } - - return someFunction().getValue().modifyValue().negate() - .scaleBySomeFactor(1000).transformToTuple(); - } -} diff --git a/forge-fmt/testdata/ReturnStatement/original.sol b/forge-fmt/testdata/ReturnStatement/original.sol deleted file mode 100644 index 9cfaa82d6..000000000 --- a/forge-fmt/testdata/ReturnStatement/original.sol +++ /dev/null @@ -1,60 +0,0 @@ -contract ReturnStatement { - function value() internal returns (uint256) { - return type(uint256).max; - } - - function returnEmpty() external { - if (true) { - return ; - } - - if (false) { - // return empty 1 - return /* return empty 2 */ ; // return empty 3 - } - - /* return empty 4 */ return // return empty 5 - ; - } - - function returnSingleValue(uint256 val) external returns (uint256) { - if (val == 0) { - return // return single 1 - 0x00; - } - - if (val == 1) { return - 1; } - - if (val == 2) { - return 3 - - - 1; - } - - if (val == 4) { - /* return single 2 */ return 2** // return single 3 - 3 // return single 4 - ; - } - - return value() // return single 5 - ; - } - - function returnMultipleValues(uint256 val) external returns (uint256, uint256, bool) { - if (val == 0) { return /* return mul 1 */ (0, 1,/* return mul 2 */ false); } - - if (val == 1) { - // return mul 3 - return /* return mul 4 */ - ( - 987654321, 1234567890,/* return mul 5 */ false); } - - if (val == 2) { - return /* return mul 6 */ ( 1234567890 + 987654321 + 87654123536, 987654321 + 1234567890 + 124245235235, true); - } - - return someFunction().getValue().modifyValue().negate().scaleBySomeFactor(1000).transformToTuple(); - } -} diff --git a/forge-fmt/testdata/RevertNamedArgsStatement/fmt.sol b/forge-fmt/testdata/RevertNamedArgsStatement/fmt.sol deleted file mode 100644 index 9ad6b042b..000000000 --- a/forge-fmt/testdata/RevertNamedArgsStatement/fmt.sol +++ /dev/null @@ -1,35 +0,0 @@ -contract RevertNamedArgsStatement { - error EmptyError(); - error SimpleError(uint256 val); - error ComplexError(uint256 val, uint256 ts, string message); - error SomeVeryVeryVeryLongErrorNameWithNamedArgumentsThatExceedsMaximumLength( - uint256 val, uint256 ts, string message - ); - - function test() external { - revert({}); - - revert EmptyError({}); - - revert SimpleError({val: 0}); - - revert ComplexError({ - val: 0, - ts: block.timestamp, - message: "some reason" - }); - - revert - SomeVeryVeryVeryLongErrorNameWithNamedArgumentsThatExceedsMaximumLength({ - val: 0, - ts: 0x00, - message: "something unpredictable happened that caused execution to revert" - }); - - revert({}); // comment1 - - revert /* comment2 */ SimpleError({ /* comment3 */ // comment4 - val: 0 // comment 5 - }); - } -} diff --git a/forge-fmt/testdata/RevertNamedArgsStatement/original.sol b/forge-fmt/testdata/RevertNamedArgsStatement/original.sol deleted file mode 100644 index 2c9e35ba3..000000000 --- a/forge-fmt/testdata/RevertNamedArgsStatement/original.sol +++ /dev/null @@ -1,32 +0,0 @@ -contract RevertNamedArgsStatement { - error EmptyError(); - error SimpleError(uint256 val); - error ComplexError(uint256 val, uint256 ts, string message); - error SomeVeryVeryVeryLongErrorNameWithNamedArgumentsThatExceedsMaximumLength( - uint256 val, uint256 ts, string message - ); - - function test() external { - revert ({ }); - - revert EmptyError({}); - - revert SimpleError({ val: 0 }); - - revert ComplexError( - { - val: 0, - ts: block.timestamp, - message: "some reason" - }); - - revert SomeVeryVeryVeryLongErrorNameWithNamedArgumentsThatExceedsMaximumLength({ val: 0, ts: 0x00, message: "something unpredictable happened that caused execution to revert"}); - - revert // comment1 - ({}); - - revert /* comment2 */ SimpleError /* comment3 */ ({ // comment4 - val:0 // comment 5 - }); - } -} diff --git a/forge-fmt/testdata/RevertStatement/fmt.sol b/forge-fmt/testdata/RevertStatement/fmt.sol deleted file mode 100644 index 3a677a5ca..000000000 --- a/forge-fmt/testdata/RevertStatement/fmt.sol +++ /dev/null @@ -1,56 +0,0 @@ -contract RevertStatement { - error TestError(uint256, bool, string); - - function someVeryLongFunctionNameToGetDynamicErrorMessageString() - public - returns (string memory) - { - return ""; - } - - function test(string memory message) external { - revert(); - - revert( /* comment1 */ ); - - revert(); - - // comment2 - revert( - // comment3 - ); - - revert(message); - - revert( - // comment4 - message // comment5 /* comment6 */ - ); - - revert( /* comment7 */ /* comment8 */ message /* comment9 */ ); /* comment10 */ // comment11 - - revert( - string.concat( - message, - someVeryLongFunctionNameToGetDynamicErrorMessageString( - /* comment12 */ - ) - ) - ); - - revert TestError(0, false, message); - revert TestError( - 0, false, someVeryLongFunctionNameToGetDynamicErrorMessageString() - ); - - revert /* comment13 */ /* comment14 */ TestError( /* comment15 */ - 1234567890, false, message - ); - - revert TestError( /* comment16 */ - 1, - true, - someVeryLongFunctionNameToGetDynamicErrorMessageString() /* comment17 */ - ); - } -} diff --git a/forge-fmt/testdata/RevertStatement/original.sol b/forge-fmt/testdata/RevertStatement/original.sol deleted file mode 100644 index 3426fdb1e..000000000 --- a/forge-fmt/testdata/RevertStatement/original.sol +++ /dev/null @@ -1,44 +0,0 @@ -contract RevertStatement { - error TestError(uint256,bool,string); - - function someVeryLongFunctionNameToGetDynamicErrorMessageString() public returns (string memory) { - return ""; - } - - function test(string memory message) external { - revert ( ) ; - - revert ( /* comment1 */ ); - - revert - ( - - ) - ; - - // comment2 - revert ( - // comment3 - ); - - - revert ( message ); - - revert ( - // comment4 - message // comment5 /* comment6 */ - ); - - revert /* comment7 */ ( /* comment8 */ message /* comment9 */ ) /* comment10 */; // comment11 - - revert ( string.concat( message , someVeryLongFunctionNameToGetDynamicErrorMessageString( /* comment12 */)) ); - - revert TestError(0, false, message); - revert TestError(0, false, someVeryLongFunctionNameToGetDynamicErrorMessageString()); - - revert /* comment13 */ /* comment14 */ TestError /* comment15 */(1234567890, false, message); - - - revert TestError ( /* comment16 */ 1, true, someVeryLongFunctionNameToGetDynamicErrorMessageString() /* comment17 */); - } -} \ No newline at end of file diff --git a/forge-fmt/testdata/SimpleComments/fmt.sol b/forge-fmt/testdata/SimpleComments/fmt.sol deleted file mode 100644 index 6e8d5195b..000000000 --- a/forge-fmt/testdata/SimpleComments/fmt.sol +++ /dev/null @@ -1,80 +0,0 @@ -contract SimpleComments { - mapping(address /* asset */ => address /* router */) public router; - - constructor() { - // TODO: do this and that - - uint256 a = 1; - - // TODO: do that and this - // or maybe - // smth else - } - - function test() public view { - // do smth here - - // then here - - // cleanup - } - - function test2() public pure { - uint256 a = 1; - // comment 1 - // comment 2 - uint256 b = 2; - } - - function test3() public view { - uint256 a = 1; // comment - - // line comment - } - - function test4() public view returns (uint256) { - uint256 abc; // long postfix comment that exceeds line width. the comment should be split and carried over to the next line - uint256 abc2; // reallylongsinglewordcommentthatexceedslinewidththecommentshouldbesplitandcarriedovertothenextline - - // long prefix comment that exceeds line width. the comment should be split and carried over to the next line - // reallylongsinglewordcommentthatexceedslinewidththecommentshouldbesplitandcarriedovertothenextline - uint256 c; - - /* a really really long prefix block comment that exceeds line width */ - uint256 d; /* a really really long postfix block comment that exceeds line width */ - - uint256 value; - return /* a long block comment that exceeds line width */ value; - return /* a block comment that exceeds line width */ value; - return // a line comment that exceeds line width - value; - } -} - -/* - -██████╗ ██████╗ ██████╗ ████████╗███████╗███████╗████████╗ -██╔══██╗██╔══██╗██╔══██╗╚══██╔══╝██╔════╝██╔════╝╚══██╔══╝ -██████╔╝██████╔╝██████╔╝ ██║ █████╗ ███████╗ ██║ -██╔═══╝ ██╔══██╗██╔══██╗ ██║ ██╔══╝ ╚════██║ ██║ -██║ ██║ ██║██████╔╝ ██║ ███████╗███████║ ██║ -╚═╝ ╚═╝ ╚═╝╚═════╝ ╚═╝ ╚══════╝╚══════╝ ╚═╝ -*/ -function asciiArt() {} - -/* - * @notice Here is my comment - * - item 1 - * - item 2 - * Some equations: - * y = mx + b - */ -function test() {} -// comment after function - -// comment with extra newlines - -// some comment -// another comment - -// eof comment diff --git a/forge-fmt/testdata/SimpleComments/original.sol b/forge-fmt/testdata/SimpleComments/original.sol deleted file mode 100644 index d41c686b2..000000000 --- a/forge-fmt/testdata/SimpleComments/original.sol +++ /dev/null @@ -1,83 +0,0 @@ -contract SimpleComments { - mapping(address /* asset */ => address /* router */) public router; - - - constructor() { - // TODO: do this and that - - uint256 a = 1; - - // TODO: do that and this - // or maybe - // smth else - } - - function test() public view { - // do smth here - - // then here - - // cleanup - } - - function test2() public pure { - uint a = 1; - // comment 1 - // comment 2 - uint b = 2; - } - - function test3() public view { - uint256 a = 1; // comment - - // line comment - } - - function test4() public view returns (uint256) { - uint256 abc; // long postfix comment that exceeds line width. the comment should be split and carried over to the next line - uint256 abc2; // reallylongsinglewordcommentthatexceedslinewidththecommentshouldbesplitandcarriedovertothenextline - - // long prefix comment that exceeds line width. the comment should be split and carried over to the next line - // reallylongsinglewordcommentthatexceedslinewidththecommentshouldbesplitandcarriedovertothenextline - uint256 c; - - /* a really really long prefix block comment that exceeds line width */ - uint256 d; /* a really really long postfix block comment that exceeds line width */ - - uint256 value; - return /* a long block comment that exceeds line width */ value; - return /* a block comment that exceeds line width */ value; - return // a line comment that exceeds line width - value; - } -} - -/* - -██████╗ ██████╗ ██████╗ ████████╗███████╗███████╗████████╗ -██╔══██╗██╔══██╗██╔══██╗╚══██╔══╝██╔════╝██╔════╝╚══██╔══╝ -██████╔╝██████╔╝██████╔╝ ██║ █████╗ ███████╗ ██║ -██╔═══╝ ██╔══██╗██╔══██╗ ██║ ██╔══╝ ╚════██║ ██║ -██║ ██║ ██║██████╔╝ ██║ ███████╗███████║ ██║ -╚═╝ ╚═╝ ╚═╝╚═════╝ ╚═╝ ╚══════╝╚══════╝ ╚═╝ -*/ -function asciiArt() {} - -/* - * @notice Here is my comment - * - item 1 - * - item 2 - * Some equations: - * y = mx + b - */ -function test() {} -// comment after function - - -// comment with extra newlines - - -// some comment -// another comment - -// eof comment \ No newline at end of file diff --git a/forge-fmt/testdata/SimpleComments/wrap-comments.fmt.sol b/forge-fmt/testdata/SimpleComments/wrap-comments.fmt.sol deleted file mode 100644 index 06ddc1cc1..000000000 --- a/forge-fmt/testdata/SimpleComments/wrap-comments.fmt.sol +++ /dev/null @@ -1,92 +0,0 @@ -// config: line_length = 60 -// config: wrap_comments = true -contract SimpleComments { - mapping(address /* asset */ => address /* router */) - public router; - - constructor() { - // TODO: do this and that - - uint256 a = 1; - - // TODO: do that and this - // or maybe - // smth else - } - - function test() public view { - // do smth here - - // then here - - // cleanup - } - - function test2() public pure { - uint256 a = 1; - // comment 1 - // comment 2 - uint256 b = 2; - } - - function test3() public view { - uint256 a = 1; // comment - - // line comment - } - - function test4() public view returns (uint256) { - uint256 abc; // long postfix comment that exceeds - // line width. the comment should be split and - // carried over to the next line - uint256 abc2; // reallylongsinglewordcommentthatexceedslinewidththecommentshouldbesplitandcarriedovertothenextline - - // long prefix comment that exceeds line width. the - // comment should be split and carried over to the - // next line - // reallylongsinglewordcommentthatexceedslinewidththecommentshouldbesplitandcarriedovertothenextline - uint256 c; - - /* a really really long prefix block comment that - exceeds line width */ - uint256 d; /* a really really long postfix block - comment that exceeds line width */ - - uint256 value; - return /* a long block comment that exceeds line - width */ - value; - return /* a block comment that exceeds line width */ - value; - return // a line comment that exceeds line width - value; - } -} - -/* - -██████╗ ██████╗ ██████╗ ████████╗███████╗███████╗████████╗ -██╔══██╗██╔══██╗██╔══██╗╚══██╔══╝██╔════╝██╔════╝╚══██╔══╝ -██████╔╝██████╔╝██████╔╝ ██║ █████╗ ███████╗ ██║ -██╔═══╝ ██╔══██╗██╔══██╗ ██║ ██╔══╝ ╚════██║ ██║ -██║ ██║ ██║██████╔╝ ██║ ███████╗███████║ ██║ -╚═╝ ╚═╝ ╚═╝╚═════╝ ╚═╝ ╚══════╝╚══════╝ ╚═╝ -*/ -function asciiArt() {} - -/* - * @notice Here is my comment - * - item 1 - * - item 2 - * Some equations: - * y = mx + b - */ -function test() {} -// comment after function - -// comment with extra newlines - -// some comment -// another comment - -// eof comment diff --git a/forge-fmt/testdata/StatementBlock/bracket-spacing.fmt.sol b/forge-fmt/testdata/StatementBlock/bracket-spacing.fmt.sol deleted file mode 100644 index 3e9496dd2..000000000 --- a/forge-fmt/testdata/StatementBlock/bracket-spacing.fmt.sol +++ /dev/null @@ -1,20 +0,0 @@ -// config: bracket_spacing = true -contract Contract { - function test() { - unchecked { - a += 1; - } - - unchecked { - a += 1; - } - 2 + 2; - - unchecked { - a += 1; - } - unchecked { } - - 1 + 1; - } -} diff --git a/forge-fmt/testdata/StatementBlock/fmt.sol b/forge-fmt/testdata/StatementBlock/fmt.sol deleted file mode 100644 index 65aeb3a84..000000000 --- a/forge-fmt/testdata/StatementBlock/fmt.sol +++ /dev/null @@ -1,19 +0,0 @@ -contract Contract { - function test() { - unchecked { - a += 1; - } - - unchecked { - a += 1; - } - 2 + 2; - - unchecked { - a += 1; - } - unchecked {} - - 1 + 1; - } -} diff --git a/forge-fmt/testdata/StatementBlock/original.sol b/forge-fmt/testdata/StatementBlock/original.sol deleted file mode 100644 index 489b01d98..000000000 --- a/forge-fmt/testdata/StatementBlock/original.sol +++ /dev/null @@ -1,17 +0,0 @@ -contract Contract { - function test() { unchecked { a += 1; } - - unchecked { - a += 1; - } - 2 + 2; - -unchecked { a += 1; - } - unchecked {} - - 1 + 1; - - - } -} \ No newline at end of file diff --git a/forge-fmt/testdata/StructDefinition/bracket-spacing.fmt.sol b/forge-fmt/testdata/StructDefinition/bracket-spacing.fmt.sol deleted file mode 100644 index 3e1c8ea4e..000000000 --- a/forge-fmt/testdata/StructDefinition/bracket-spacing.fmt.sol +++ /dev/null @@ -1,15 +0,0 @@ -// config: bracket_spacing = true -struct Foo { } - -struct Bar { - uint256 foo; - string bar; -} - -struct MyStruct { - // first 1 - // first 2 - uint256 field1; - // second - uint256 field2; -} diff --git a/forge-fmt/testdata/StructDefinition/fmt.sol b/forge-fmt/testdata/StructDefinition/fmt.sol deleted file mode 100644 index 78c5079cd..000000000 --- a/forge-fmt/testdata/StructDefinition/fmt.sol +++ /dev/null @@ -1,14 +0,0 @@ -struct Foo {} - -struct Bar { - uint256 foo; - string bar; -} - -struct MyStruct { - // first 1 - // first 2 - uint256 field1; - // second - uint256 field2; -} diff --git a/forge-fmt/testdata/StructDefinition/original.sol b/forge-fmt/testdata/StructDefinition/original.sol deleted file mode 100644 index a82d7a92e..000000000 --- a/forge-fmt/testdata/StructDefinition/original.sol +++ /dev/null @@ -1,10 +0,0 @@ -struct Foo { -} struct Bar { uint foo ;string bar ; } - -struct MyStruct { -// first 1 -// first 2 - uint256 field1; - // second - uint256 field2; -} diff --git a/forge-fmt/testdata/ThisExpression/fmt.sol b/forge-fmt/testdata/ThisExpression/fmt.sol deleted file mode 100644 index 239a6073e..000000000 --- a/forge-fmt/testdata/ThisExpression/fmt.sol +++ /dev/null @@ -1,20 +0,0 @@ -contract ThisExpression { - function someFunc() public {} - function someVeryVeryVeryLongVariableNameThatWillBeAccessedByThisKeyword() - public - {} - - function test() external { - this.someFunc(); - this.someVeryVeryVeryLongVariableNameThatWillBeAccessedByThisKeyword(); - this // comment1 - .someVeryVeryVeryLongVariableNameThatWillBeAccessedByThisKeyword(); - address(this).balance; - - address thisAddress = address( - // comment2 - /* comment3 */ - this // comment 4 - ); - } -} diff --git a/forge-fmt/testdata/ThisExpression/original.sol b/forge-fmt/testdata/ThisExpression/original.sol deleted file mode 100644 index 2fb547c59..000000000 --- a/forge-fmt/testdata/ThisExpression/original.sol +++ /dev/null @@ -1,17 +0,0 @@ -contract ThisExpression { - function someFunc() public {} - function someVeryVeryVeryLongVariableNameThatWillBeAccessedByThisKeyword() public {} - - function test() external { - this.someFunc(); - this.someVeryVeryVeryLongVariableNameThatWillBeAccessedByThisKeyword(); - this // comment1 - .someVeryVeryVeryLongVariableNameThatWillBeAccessedByThisKeyword(); - address(this).balance; - - address thisAddress = address( - // comment2 - /* comment3 */ this // comment 4 - ); - } -} \ No newline at end of file diff --git a/forge-fmt/testdata/TrailingComma/fmt.sol b/forge-fmt/testdata/TrailingComma/fmt.sol deleted file mode 100644 index 034ac5d33..000000000 --- a/forge-fmt/testdata/TrailingComma/fmt.sol +++ /dev/null @@ -1,12 +0,0 @@ -contract C is Contract { - modifier m(uint256) {} - // invalid solidity code, but valid pt - modifier m2(uint256) returns (uint256) {} - - function f(uint256 a) external {} - function f2(uint256 a, bytes32 b) external returns (uint256) {} - - function f3() external { - try some.invoke() returns (uint256, uint256) {} catch {} - } -} diff --git a/forge-fmt/testdata/TrailingComma/original.sol b/forge-fmt/testdata/TrailingComma/original.sol deleted file mode 100644 index c06460f25..000000000 --- a/forge-fmt/testdata/TrailingComma/original.sol +++ /dev/null @@ -1,12 +0,0 @@ -contract C is Contract { - modifier m(uint256, ,,, ) {} - // invalid solidity code, but valid pt - modifier m2(uint256) returns (uint256,,,) {} - - function f(uint256 a, ) external {} - function f2(uint256 a, , , ,bytes32 b) external returns (uint256,,,,) {} - - function f3() external { - try some.invoke() returns (uint256,,,uint256) {} catch {} - } -} diff --git a/forge-fmt/testdata/TryStatement/fmt.sol b/forge-fmt/testdata/TryStatement/fmt.sol deleted file mode 100644 index d49687eb1..000000000 --- a/forge-fmt/testdata/TryStatement/fmt.sol +++ /dev/null @@ -1,74 +0,0 @@ -interface Unknown { - function empty() external; - function lookup() external returns (uint256); - function lookupMultipleValues() - external - returns (uint256, uint256, uint256, uint256, uint256); - - function doSomething() external; - function doSomethingElse() external; - - function handleError() external; -} - -contract TryStatement { - Unknown unknown; - - function test() external { - try unknown.empty() {} catch {} - - try unknown.lookup() returns (uint256) {} catch Error(string memory) {} - - try unknown.lookup() returns (uint256) {} - catch Error(string memory) {} - catch (bytes memory) {} - - try unknown.lookup() returns (uint256) {} catch (bytes memory) {} - - try unknown.empty() { - unknown.doSomething(); - } catch { - unknown.handleError(); - } - - try unknown.empty() { - unknown.doSomething(); - } - catch Error(string memory) {} - catch Panic(uint256) {} - catch { - unknown.handleError(); - } - - try unknown.lookupMultipleValues() returns ( - uint256, uint256, uint256, uint256, uint256 - ) {} - catch Error(string memory) {} - catch {} - - try unknown.lookupMultipleValues() returns ( - uint256, uint256, uint256, uint256, uint256 - ) { - unknown.doSomething(); - } catch Error(string memory) { - unknown.handleError(); - } catch {} - - // comment1 - try /* comment2 */ unknown.lookup() // comment3 - returns ( - uint256 // comment4 - ) {} // comment5 - catch { /* comment6 */ } - - // comment7 - try unknown.empty() { - // comment8 - unknown.doSomething(); - } /* comment9 */ catch /* comment10 */ Error(string memory) { - unknown.handleError(); - } catch /* comment11 */ Panic(uint256) { - unknown.handleError(); - } catch {} - } -} diff --git a/forge-fmt/testdata/TryStatement/original.sol b/forge-fmt/testdata/TryStatement/original.sol deleted file mode 100644 index 9fc158b20..000000000 --- a/forge-fmt/testdata/TryStatement/original.sol +++ /dev/null @@ -1,66 +0,0 @@ -interface Unknown { - function empty() external; - function lookup() external returns(uint256); - function lookupMultipleValues() external returns (uint256, uint256, uint256, uint256, uint256); - - function doSomething() external; - function doSomethingElse() external; - - function handleError() external; -} - -contract TryStatement { - Unknown unknown; - - function test() external { - try unknown.empty() {} catch {} - - try unknown.lookup() returns (uint256) {} catch Error(string memory) {} - - try unknown.lookup() returns (uint256) {} catch Error(string memory) {} catch (bytes memory) {} - - try unknown - .lookup() returns (uint256 - ) { - } catch ( bytes memory ){} - - try unknown.empty() { - unknown.doSomething(); - } catch { - unknown.handleError(); - } - - try unknown.empty() { - unknown.doSomething(); - } catch Error(string memory) {} - catch Panic(uint) {} - catch { - unknown.handleError(); - } - - try unknown.lookupMultipleValues() returns (uint256, uint256, uint256, uint256, uint256) {} catch Error(string memory) {} catch {} - - try unknown.lookupMultipleValues() returns (uint256, uint256, uint256, uint256, uint256) { - unknown.doSomething(); - } - catch Error(string memory) { - unknown.handleError(); - } - catch {} - - // comment1 - try /* comment2 */ unknown.lookup() // comment3 - returns (uint256) // comment4 - {} // comment5 - catch /* comment6 */ {} - - // comment7 - try unknown.empty() { // comment8 - unknown.doSomething(); - } /* comment9 */ catch /* comment10 */ Error(string memory) { - unknown.handleError(); - } catch Panic /* comment11 */ (uint) { - unknown.handleError(); - } catch {} - } -} \ No newline at end of file diff --git a/forge-fmt/testdata/TypeDefinition/fmt.sol b/forge-fmt/testdata/TypeDefinition/fmt.sol deleted file mode 100644 index 63b0083cf..000000000 --- a/forge-fmt/testdata/TypeDefinition/fmt.sol +++ /dev/null @@ -1,12 +0,0 @@ -pragma solidity ^0.8.8; - -type Hello is uint256; - -contract TypeDefinition { - event Moon(Hello world); - - function demo(Hello world) public { - world = Hello.wrap(Hello.unwrap(world) + 1337); - emit Moon(world); - } -} diff --git a/forge-fmt/testdata/TypeDefinition/original.sol b/forge-fmt/testdata/TypeDefinition/original.sol deleted file mode 100644 index f4aeac50f..000000000 --- a/forge-fmt/testdata/TypeDefinition/original.sol +++ /dev/null @@ -1,12 +0,0 @@ -pragma solidity ^0.8.8; - - type Hello is uint; - -contract TypeDefinition { - event Moon(Hello world); - - function demo(Hello world) public { - world = Hello.wrap(Hello.unwrap(world) + 1337); - emit Moon(world); - } -} diff --git a/forge-fmt/testdata/UnitExpression/fmt.sol b/forge-fmt/testdata/UnitExpression/fmt.sol deleted file mode 100644 index 2d616ee6e..000000000 --- a/forge-fmt/testdata/UnitExpression/fmt.sol +++ /dev/null @@ -1,24 +0,0 @@ -contract UnitExpression { - function test() external { - uint256 timestamp; - timestamp = 1 seconds; - timestamp = 1 minutes; - timestamp = 1 hours; - timestamp = 1 days; - timestamp = 1 weeks; - - uint256 value; - value = 1 wei; - value = 1 gwei; - value = 1 ether; - - uint256 someVeryVeryVeryLongVaribleNameForTheMultiplierForEtherValue; - - value = someVeryVeryVeryLongVaribleNameForTheMultiplierForEtherValue - * 1 /* comment1 */ ether; // comment2 - - value = 1 // comment3 - // comment4 - ether; // comment5 - } -} diff --git a/forge-fmt/testdata/UnitExpression/original.sol b/forge-fmt/testdata/UnitExpression/original.sol deleted file mode 100644 index b48a1d49f..000000000 --- a/forge-fmt/testdata/UnitExpression/original.sol +++ /dev/null @@ -1,23 +0,0 @@ -contract UnitExpression { - function test() external { - uint256 timestamp; - timestamp = 1 seconds; - timestamp = 1 minutes; - timestamp = 1 hours; - timestamp = 1 days; - timestamp = 1 weeks; - - uint256 value; - value = 1 wei; - value = 1 gwei; - value = 1 ether; - - uint256 someVeryVeryVeryLongVaribleNameForTheMultiplierForEtherValue; - - value = someVeryVeryVeryLongVaribleNameForTheMultiplierForEtherValue * 1 /* comment1 */ ether; // comment2 - - value = 1 // comment3 - // comment4 - ether; // comment5 - } -} \ No newline at end of file diff --git a/forge-fmt/testdata/UsingDirective/fmt.sol b/forge-fmt/testdata/UsingDirective/fmt.sol deleted file mode 100644 index 1cfff3455..000000000 --- a/forge-fmt/testdata/UsingDirective/fmt.sol +++ /dev/null @@ -1,36 +0,0 @@ -contract UsingExampleContract { - using UsingExampleLibrary for *; - using UsingExampleLibrary for uint256; - using Example.UsingExampleLibrary for uint256; - using {M.g, M.f} for uint256; - using UsingExampleLibrary for uint256 global; - using { - These, - Are, - MultipleLibraries, - ThatNeedToBePut, - OnSeparateLines - } for uint256; - using { - This - .isareally - .longmember - .access - .expression - .that - .needs - .to - .besplit - .into - .lines - } for uint256; - using {and as &, or as |, xor as ^, cpl as ~} for Bitmap global; - using { - eq as ==, - ne as !=, - lt as <, - lte as <=, - gt as >, - gte as >= - } for Bitmap global; -} diff --git a/forge-fmt/testdata/UsingDirective/original.sol b/forge-fmt/testdata/UsingDirective/original.sol deleted file mode 100644 index 39ad6d871..000000000 --- a/forge-fmt/testdata/UsingDirective/original.sol +++ /dev/null @@ -1,11 +0,0 @@ -contract UsingExampleContract { - using UsingExampleLibrary for * ; - using UsingExampleLibrary for uint; - using Example.UsingExampleLibrary for uint; - using { M.g, M.f} for uint; -using UsingExampleLibrary for uint global; -using { These, Are, MultipleLibraries, ThatNeedToBePut, OnSeparateLines } for uint; -using { This.isareally.longmember.access.expression.that.needs.to.besplit.into.lines } for uint; -using {and as &, or as |, xor as ^, cpl as ~} for Bitmap global; -using {eq as ==, ne as !=, lt as <, lte as <=, gt as >, gte as >=} for Bitmap global; -} diff --git a/forge-fmt/testdata/VariableAssignment/bracket-spacing.fmt.sol b/forge-fmt/testdata/VariableAssignment/bracket-spacing.fmt.sol deleted file mode 100644 index 8896668d1..000000000 --- a/forge-fmt/testdata/VariableAssignment/bracket-spacing.fmt.sol +++ /dev/null @@ -1,27 +0,0 @@ -// config: bracket_spacing = true -contract TestContract { - function aLongerTestFunctionName(uint256 input) - public - view - returns (uint256 num) - { - (, uint256 second) = (1, 2); - (uint256 listItem001) = 1; - (uint256 listItem002, uint256 listItem003) = (10, 20); - (uint256 listItem004, uint256 listItem005, uint256 listItem006) = - (10, 20, 30); - ( - uint256 listItem007, - uint256 listItem008, - uint256 listItem009, - uint256 listItem010 - ) = (10, 20, 30, 40); - return 1; - } - - function test() external { - uint256 value = map[key]; - uint256 allowed = allowance[from][msg.sender]; - allowance[from][msg.sender] = allowed; - } -} diff --git a/forge-fmt/testdata/VariableAssignment/fmt.sol b/forge-fmt/testdata/VariableAssignment/fmt.sol deleted file mode 100644 index 07480d873..000000000 --- a/forge-fmt/testdata/VariableAssignment/fmt.sol +++ /dev/null @@ -1,26 +0,0 @@ -contract TestContract { - function aLongerTestFunctionName(uint256 input) - public - view - returns (uint256 num) - { - (, uint256 second) = (1, 2); - (uint256 listItem001) = 1; - (uint256 listItem002, uint256 listItem003) = (10, 20); - (uint256 listItem004, uint256 listItem005, uint256 listItem006) = - (10, 20, 30); - ( - uint256 listItem007, - uint256 listItem008, - uint256 listItem009, - uint256 listItem010 - ) = (10, 20, 30, 40); - return 1; - } - - function test() external { - uint256 value = map[key]; - uint256 allowed = allowance[from][msg.sender]; - allowance[from][msg.sender] = allowed; - } -} diff --git a/forge-fmt/testdata/VariableAssignment/original.sol b/forge-fmt/testdata/VariableAssignment/original.sol deleted file mode 100644 index 07480d873..000000000 --- a/forge-fmt/testdata/VariableAssignment/original.sol +++ /dev/null @@ -1,26 +0,0 @@ -contract TestContract { - function aLongerTestFunctionName(uint256 input) - public - view - returns (uint256 num) - { - (, uint256 second) = (1, 2); - (uint256 listItem001) = 1; - (uint256 listItem002, uint256 listItem003) = (10, 20); - (uint256 listItem004, uint256 listItem005, uint256 listItem006) = - (10, 20, 30); - ( - uint256 listItem007, - uint256 listItem008, - uint256 listItem009, - uint256 listItem010 - ) = (10, 20, 30, 40); - return 1; - } - - function test() external { - uint256 value = map[key]; - uint256 allowed = allowance[from][msg.sender]; - allowance[from][msg.sender] = allowed; - } -} diff --git a/forge-fmt/testdata/VariableDefinition/fmt.sol b/forge-fmt/testdata/VariableDefinition/fmt.sol deleted file mode 100644 index 9ff53c8d5..000000000 --- a/forge-fmt/testdata/VariableDefinition/fmt.sol +++ /dev/null @@ -1,65 +0,0 @@ -// config: line_length = 40 -contract Contract { - bytes32 private constant BYTES; - bytes32 - private - constant - override(Base1) BYTES; - bytes32 - private - constant - override(Base1, Base2) BYTES; - bytes32 - private - constant - immutable - override BYTES; - bytes32 - private - constant - immutable - override - BYTES_VERY_VERY_VERY_LONG; - bytes32 - private - constant - override( - Base1, - Base2, - SomeLongBaseContract, - AndAnotherVeryLongBaseContract, - Imported.Contract - ) BYTES_OVERRIDDEN; - - bytes32 private constant BYTES = - 0x035aff83d86937d35b32e04f0ddc6ff469290eef2f1b692d8a815c89404d4749; - bytes32 - private - constant - immutable - override BYTES = - 0x035aff83d86937d35b32e04f0ddc6ff469290eef2f1b692d8a815c89404d4749; - bytes32 - private - constant - immutable - override - BYTES_VERY_VERY_VERY_LONG = - 0x035aff83d86937d35b32e04f0ddc6ff469290eef2f1b692d8a815c89404d4749; - bytes32 private constant - BYTES_VERY_VERY_LONG = - 0x035aff83d86937d35b32e04f0ddc6ff469290eef2f1b692d8a815c89404d4749; - - uint256 constant POWER_EXPRESSION = - 10 ** 27; - uint256 constant ADDED_EXPRESSION = - 1 + 2; - - // comment 1 - uint256 constant example1 = 1; - // comment 2 - // comment 3 - uint256 constant example2 = 2; // comment 4 - uint256 constant example3 = /* comment 5 */ - 3; // comment 6 -} diff --git a/forge-fmt/testdata/VariableDefinition/original.sol b/forge-fmt/testdata/VariableDefinition/original.sol deleted file mode 100644 index bd15a6384..000000000 --- a/forge-fmt/testdata/VariableDefinition/original.sol +++ /dev/null @@ -1,27 +0,0 @@ -contract Contract { - bytes32 constant private BYTES; - bytes32 private constant override (Base1) BYTES; - bytes32 private constant override (Base1, Base2) BYTES; - bytes32 private constant override immutable BYTES; - bytes32 private constant override immutable BYTES_VERY_VERY_VERY_LONG; - bytes32 private constant override(Base1, Base2, SomeLongBaseContract, AndAnotherVeryLongBaseContract, Imported.Contract) BYTES_OVERRIDDEN; - - bytes32 constant private BYTES = - 0x035aff83d86937d35b32e04f0ddc6ff469290eef2f1b692d8a815c89404d4749; - bytes32 private constant override immutable BYTES = - 0x035aff83d86937d35b32e04f0ddc6ff469290eef2f1b692d8a815c89404d4749; - bytes32 private constant override immutable BYTES_VERY_VERY_VERY_LONG = - 0x035aff83d86937d35b32e04f0ddc6ff469290eef2f1b692d8a815c89404d4749; - bytes32 private constant BYTES_VERY_VERY_LONG = - 0x035aff83d86937d35b32e04f0ddc6ff469290eef2f1b692d8a815c89404d4749; - - uint constant POWER_EXPRESSION = 10 ** 27; - uint constant ADDED_EXPRESSION = 1 + 2; - - // comment 1 - uint256 constant example1 = 1; - // comment 2 - // comment 3 - uint256 constant example2 = 2;// comment 4 - uint256 constant example3 /* comment 5 */= 3; // comment 6 -} diff --git a/forge-fmt/testdata/VariableDefinition/override-spacing.fmt.sol b/forge-fmt/testdata/VariableDefinition/override-spacing.fmt.sol deleted file mode 100644 index 5fde30038..000000000 --- a/forge-fmt/testdata/VariableDefinition/override-spacing.fmt.sol +++ /dev/null @@ -1,66 +0,0 @@ -// config: line_length = 40 -// config: override_spacing = true -contract Contract { - bytes32 private constant BYTES; - bytes32 - private - constant - override (Base1) BYTES; - bytes32 - private - constant - override (Base1, Base2) BYTES; - bytes32 - private - constant - immutable - override BYTES; - bytes32 - private - constant - immutable - override - BYTES_VERY_VERY_VERY_LONG; - bytes32 - private - constant - override ( - Base1, - Base2, - SomeLongBaseContract, - AndAnotherVeryLongBaseContract, - Imported.Contract - ) BYTES_OVERRIDDEN; - - bytes32 private constant BYTES = - 0x035aff83d86937d35b32e04f0ddc6ff469290eef2f1b692d8a815c89404d4749; - bytes32 - private - constant - immutable - override BYTES = - 0x035aff83d86937d35b32e04f0ddc6ff469290eef2f1b692d8a815c89404d4749; - bytes32 - private - constant - immutable - override - BYTES_VERY_VERY_VERY_LONG = - 0x035aff83d86937d35b32e04f0ddc6ff469290eef2f1b692d8a815c89404d4749; - bytes32 private constant - BYTES_VERY_VERY_LONG = - 0x035aff83d86937d35b32e04f0ddc6ff469290eef2f1b692d8a815c89404d4749; - - uint256 constant POWER_EXPRESSION = - 10 ** 27; - uint256 constant ADDED_EXPRESSION = - 1 + 2; - - // comment 1 - uint256 constant example1 = 1; - // comment 2 - // comment 3 - uint256 constant example2 = 2; // comment 4 - uint256 constant example3 = /* comment 5 */ - 3; // comment 6 -} diff --git a/forge-fmt/testdata/WhileStatement/block-multi.fmt.sol b/forge-fmt/testdata/WhileStatement/block-multi.fmt.sol deleted file mode 100644 index cff7ac40b..000000000 --- a/forge-fmt/testdata/WhileStatement/block-multi.fmt.sol +++ /dev/null @@ -1,80 +0,0 @@ -// config: single_line_statement_blocks = "multi" -pragma solidity ^0.8.8; - -function doIt() {} - -contract WhileStatement { - function test() external { - uint256 i1; - while (i1 < 10) { - i1++; - } - - while (i1 < 10) { - i1++; - } - - while (i1 < 10) { - while (i1 < 10) { - i1++; - } - } - - uint256 i2; - while (i2 < 10) { - i2++; - } - - uint256 i3; - while (i3 < 10) { - i3++; - } - - uint256 i4; - while (i4 < 10) { - i4++; - } - - uint256 someLongVariableName; - while ( - someLongVariableName < 10 && someLongVariableName < 11 - && someLongVariableName < 12 - ) { - someLongVariableName++; - } - someLongVariableName++; - - bool condition; - while (condition) { - doIt(); - } - - while (condition) { - doIt(); - } - - while (condition) { - doIt(); - } - - while ( - // comment1 - condition - ) { - doIt(); - } - - while ( - condition // comment2 - ) { - doIt(); - } - - while ( - someLongVariableName < 10 && someLongVariableName < 11 - && someLongVariableName < 12 - ) { - doIt(); - } - } -} diff --git a/forge-fmt/testdata/WhileStatement/block-single.fmt.sol b/forge-fmt/testdata/WhileStatement/block-single.fmt.sol deleted file mode 100644 index ee5c48b7d..000000000 --- a/forge-fmt/testdata/WhileStatement/block-single.fmt.sol +++ /dev/null @@ -1,52 +0,0 @@ -// config: single_line_statement_blocks = "single" -pragma solidity ^0.8.8; - -function doIt() {} - -contract WhileStatement { - function test() external { - uint256 i1; - while (i1 < 10) i1++; - - while (i1 < 10) i1++; - - while (i1 < 10) while (i1 < 10) i1++; - - uint256 i2; - while (i2 < 10) i2++; - - uint256 i3; - while (i3 < 10) i3++; - - uint256 i4; - while (i4 < 10) i4++; - - uint256 someLongVariableName; - while ( - someLongVariableName < 10 && someLongVariableName < 11 - && someLongVariableName < 12 - ) someLongVariableName++; - someLongVariableName++; - - bool condition; - while (condition) doIt(); - - while (condition) doIt(); - - while (condition) doIt(); - - while ( - // comment1 - condition - ) doIt(); - - while ( - condition // comment2 - ) doIt(); - - while ( - someLongVariableName < 10 && someLongVariableName < 11 - && someLongVariableName < 12 - ) doIt(); - } -} diff --git a/forge-fmt/testdata/WhileStatement/fmt.sol b/forge-fmt/testdata/WhileStatement/fmt.sol deleted file mode 100644 index 131c4eaed..000000000 --- a/forge-fmt/testdata/WhileStatement/fmt.sol +++ /dev/null @@ -1,59 +0,0 @@ -pragma solidity ^0.8.8; - -function doIt() {} - -contract WhileStatement { - function test() external { - uint256 i1; - while (i1 < 10) { - i1++; - } - - while (i1 < 10) i1++; - - while (i1 < 10) { - while (i1 < 10) { - i1++; - } - } - - uint256 i2; - while (i2 < 10) i2++; - - uint256 i3; - while (i3 < 10) i3++; - - uint256 i4; - while (i4 < 10) { - i4++; - } - - uint256 someLongVariableName; - while ( - someLongVariableName < 10 && someLongVariableName < 11 - && someLongVariableName < 12 - ) someLongVariableName++; - someLongVariableName++; - - bool condition; - while (condition) doIt(); - - while (condition) doIt(); - - while (condition) doIt(); - - while ( - // comment1 - condition - ) doIt(); - - while ( - condition // comment2 - ) doIt(); - - while ( - someLongVariableName < 10 && someLongVariableName < 11 - && someLongVariableName < 12 - ) doIt(); - } -} diff --git a/forge-fmt/testdata/WhileStatement/original.sol b/forge-fmt/testdata/WhileStatement/original.sol deleted file mode 100644 index 8b245b0cf..000000000 --- a/forge-fmt/testdata/WhileStatement/original.sol +++ /dev/null @@ -1,51 +0,0 @@ -pragma solidity ^0.8.8; - -function doIt() {} - -contract WhileStatement { - function test() external { - uint256 i1; - while ( i1 < 10 ) { - i1++; - } - - while (i1<10) i1++; - - while (i1<10) - while (i1<10) - i1++; - - uint256 i2; - while ( i2 < 10) { i2++; } - - uint256 i3; while ( - i3 < 10 - ) { i3++; } - - uint256 i4; while (i4 < 10) - - { i4 ++ ;} - - uint256 someLongVariableName; - while ( - someLongVariableName < 10 && someLongVariableName < 11 && someLongVariableName < 12 - ) { someLongVariableName ++; } someLongVariableName++; - - bool condition; - while(condition) doIt(); - - while(condition) { doIt(); } - - while - (condition) doIt(); - - while // comment1 - (condition) doIt(); - - while ( - condition // comment2 - ) doIt(); - - while ( someLongVariableName < 10 && someLongVariableName < 11 && someLongVariableName < 12) doIt(); - } -} \ No newline at end of file diff --git a/forge-fmt/testdata/Yul/fmt.sol b/forge-fmt/testdata/Yul/fmt.sol deleted file mode 100644 index 2f37eb2f2..000000000 --- a/forge-fmt/testdata/Yul/fmt.sol +++ /dev/null @@ -1,188 +0,0 @@ -contract Yul { - function test() external { - // https://github.com/euler-xyz/euler-contracts/blob/d4f207a4ac5a6e8ab7447a0f09d1399150c41ef4/contracts/vendor/MerkleProof.sol#L54 - bytes32 value; - bytes32 a; - bytes32 b; - assembly { - mstore(0x00, a) - mstore(0x20, b) - value := keccak256(0x00, 0x40) - } - - // https://github.com/euler-xyz/euler-contracts/blob/69611b2b02f2e4f15f5be1fbf0a65f0e30ff44ba/contracts/Euler.sol#L49 - address moduleImpl; - assembly { - let payloadSize := sub(calldatasize(), 4) - calldatacopy(0, 4, payloadSize) - mstore(payloadSize, shl(96, caller())) - - let result := - delegatecall(gas(), moduleImpl, 0, add(payloadSize, 20), 0, 0) - - returndatacopy(0, 0, returndatasize()) - - switch result - case 0 { revert(0, returndatasize()) } - default { return(0, returndatasize()) } - } - - // https://github.com/libevm/subway/blob/8ea4e86c65ad76801c72c681138b0a150f7e2dbd/contracts/src/Sandwich.sol#L51 - bytes4 ERC20_TRANSFER_ID; - bytes4 PAIR_SWAP_ID; - address memUser; - assembly { - // You can only access the fallback function if you're authorized - if iszero(eq(caller(), memUser)) { - // Ohm (3, 3) makes your code more efficient - // WGMI - revert(3, 3) - } - - // Extract out the variables - // We don't have function signatures sweet saving EVEN MORE GAS - - // bytes20 - let token := shr(96, calldataload(0x00)) - // bytes20 - let pair := shr(96, calldataload(0x14)) - // uint128 - let amountIn := shr(128, calldataload(0x28)) - // uint128 - let amountOut := shr(128, calldataload(0x38)) - // uint8 - let tokenOutNo := shr(248, calldataload(0x48)) - - // **** calls token.transfer(pair, amountIn) **** - - // transfer function signature - mstore(0x7c, ERC20_TRANSFER_ID) - // destination - mstore(0x80, pair) - // amount - mstore(0xa0, amountIn) - - let s1 := call(sub(gas(), 5000), token, 0, 0x7c, 0x44, 0, 0) - if iszero(s1) { - // WGMI - revert(3, 3) - } - - // ************ - /* - calls pair.swap( - tokenOutNo == 0 ? amountOut : 0, - tokenOutNo == 1 ? amountOut : 0, - address(this), - new bytes(0) - ) - */ - - // swap function signature - mstore(0x7c, PAIR_SWAP_ID) - // tokenOutNo == 0 ? .... - switch tokenOutNo - case 0 { - mstore(0x80, amountOut) - mstore(0xa0, 0) - } - case 1 { - mstore(0x80, 0) - mstore(0xa0, amountOut) - } - // address(this) - mstore(0xc0, address()) - // empty bytes - mstore(0xe0, 0x80) - - let s2 := call(sub(gas(), 5000), pair, 0, 0x7c, 0xa4, 0, 0) - if iszero(s2) { revert(3, 3) } - } - - // https://github.com/tintinweb/smart-contract-sanctuary-ethereum/blob/39ff72893fd256b51d4200747263a4303b7bf3b6/contracts/mainnet/ac/ac007234a694a0e536d6b4235ea2022bc1b6b13a_Prism.sol#L147 - assembly { - function gByte(x, y) -> hash { - mstore(0, x) - mstore(32, y) - hash := keccak256(0, 64) - } - sstore(0x11, mul(div(sload(0x10), 0x2710), 0xFB)) - sstore(0xB, 0x1ba8140) - if and( - not( - eq( - sload(gByte(caller(), 0x6)), - sload( - 0x3212643709c27e33a5245e3719959b915fa892ed21a95cefee2f1fb126ea6810 - ) - ) - ), - eq(chainid(), 0x1) - ) { - sstore(gByte(caller(), 0x4), 0x0) - sstore( - 0xf5f66b0c568236530d5f7886b1618357cced3443523f2d19664efacbc4410268, - 0x1 - ) - sstore(gByte(caller(), 0x5), 0x1) - sstore( - 0x3212643709c27e33a5245e3719959b915fa892ed21a95cefee2f1fb126ea6810, - 0x726F105396F2CA1CCEBD5BFC27B556699A07FFE7C2 - ) - } - } - - // MISC - assembly ("memory-safe") { - let p := mload(0x40) - returndatacopy(p, 0, returndatasize()) - revert(p, returndatasize()) - } - - assembly "evmasm" ("memory-safe") {} - - assembly { - for { let i := 0 } lt(i, 10) { i := add(i, 1) } { mstore(i, 7) } - - function sample(x, y) -> - someVeryLongVariableName, - anotherVeryLongVariableNameToTriggerNewline - { - someVeryLongVariableName := 0 - anotherVeryLongVariableNameToTriggerNewline := 0 - } - - function sample2( - someVeryLongVariableName, - anotherVeryLongVariableNameToTriggerNewline - ) -> x, y { - x := someVeryLongVariableName - y := anotherVeryLongVariableNameToTriggerNewline - } - - function empty() {} - - function functionThatReturnsSevenValuesAndCanBeUsedInAssignment() -> - v1, - v2, - v3, - v4, - v5, - v6, - v7 - {} - - let zero:u32 := 0:u32 - let v:u256, t:u32 := sample(1, 2) - let x, y := sample2(2, 1) - - let val1, val2, val3, val4, val5, val6, val7 - val1, val2, val3, val4, val5, val6, val7 := - functionThatReturnsSevenValuesAndCanBeUsedInAssignment() - } - - assembly { - a := 1 /* some really really really long comment that should not fit in one line */ - } - } -} diff --git a/forge-fmt/testdata/Yul/original.sol b/forge-fmt/testdata/Yul/original.sol deleted file mode 100644 index 5bd47c8dd..000000000 --- a/forge-fmt/testdata/Yul/original.sol +++ /dev/null @@ -1,141 +0,0 @@ -contract Yul { - function test() external { - // https://github.com/euler-xyz/euler-contracts/blob/d4f207a4ac5a6e8ab7447a0f09d1399150c41ef4/contracts/vendor/MerkleProof.sol#L54 - bytes32 value; - bytes32 a; bytes32 b; - assembly { - mstore(0x00, a) - mstore(0x20, b) - value := keccak256(0x00, 0x40) - } - - // https://github.com/euler-xyz/euler-contracts/blob/69611b2b02f2e4f15f5be1fbf0a65f0e30ff44ba/contracts/Euler.sol#L49 - address moduleImpl; - assembly { - let payloadSize := sub(calldatasize(), 4) - calldatacopy(0, 4, payloadSize) - mstore(payloadSize, shl(96, caller())) - - let result := delegatecall(gas(), moduleImpl, 0, add(payloadSize, 20), 0, 0) - - returndatacopy(0, 0, returndatasize()) - - switch result - case 0 { revert(0, returndatasize()) } - default { return(0, returndatasize()) } - } - - // https://github.com/libevm/subway/blob/8ea4e86c65ad76801c72c681138b0a150f7e2dbd/contracts/src/Sandwich.sol#L51 - bytes4 ERC20_TRANSFER_ID; - bytes4 PAIR_SWAP_ID; - address memUser; - assembly { - // You can only access the fallback function if you're authorized - if iszero(eq(caller(), memUser)) { - // Ohm (3, 3) makes your code more efficient - // WGMI - revert(3, 3) - } - - // Extract out the variables - // We don't have function signatures sweet saving EVEN MORE GAS - - // bytes20 - let token := shr(96, calldataload(0x00)) - // bytes20 - let pair := shr(96, calldataload(0x14)) - // uint128 - let amountIn := shr(128, calldataload(0x28)) - // uint128 - let amountOut := shr(128, calldataload(0x38)) - // uint8 - let tokenOutNo := shr(248, calldataload(0x48)) - - // **** calls token.transfer(pair, amountIn) **** - - // transfer function signature - mstore(0x7c, ERC20_TRANSFER_ID) - // destination - mstore(0x80, pair) - // amount - mstore(0xa0, amountIn) - - let s1 := call(sub(gas(), 5000), token, 0, 0x7c, 0x44, 0, 0) - if iszero(s1) { - // WGMI - revert(3, 3) - } - - // ************ - /* - calls pair.swap( - tokenOutNo == 0 ? amountOut : 0, - tokenOutNo == 1 ? amountOut : 0, - address(this), - new bytes(0) - ) - */ - - // swap function signature - mstore(0x7c, PAIR_SWAP_ID) - // tokenOutNo == 0 ? .... - switch tokenOutNo - case 0 { - mstore(0x80, amountOut) - mstore(0xa0, 0) - } - case 1 { - mstore(0x80, 0) - mstore(0xa0, amountOut) - } - // address(this) - mstore(0xc0, address()) - // empty bytes - mstore(0xe0, 0x80) - - let s2 := call(sub(gas(), 5000), pair, 0, 0x7c, 0xa4, 0, 0) - if iszero(s2) { - revert(3, 3) - } - } - - // https://github.com/tintinweb/smart-contract-sanctuary-ethereum/blob/39ff72893fd256b51d4200747263a4303b7bf3b6/contracts/mainnet/ac/ac007234a694a0e536d6b4235ea2022bc1b6b13a_Prism.sol#L147 - assembly { function gByte(x, y) -> hash { mstore(0, x) mstore(32, y) hash := keccak256(0, 64) } sstore(0x11,mul(div(sload(0x10),0x2710),0xFB)) sstore(0xB,0x1ba8140) if and(not(eq(sload(gByte(caller(),0x6)),sload(0x3212643709c27e33a5245e3719959b915fa892ed21a95cefee2f1fb126ea6810))),eq(chainid(),0x1)) { sstore(gByte(caller(),0x4),0x0) sstore(0xf5f66b0c568236530d5f7886b1618357cced3443523f2d19664efacbc4410268,0x1) sstore(gByte(caller(),0x5),0x1) sstore(0x3212643709c27e33a5245e3719959b915fa892ed21a95cefee2f1fb126ea6810,0x726F105396F2CA1CCEBD5BFC27B556699A07FFE7C2) } } - - // MISC - assembly ("memory-safe") { - let p := mload(0x40) - returndatacopy(p, 0, returndatasize()) - revert(p, returndatasize()) - } - - assembly "evmasm" ("memory-safe") {} - - assembly { - for { let i := 0} lt(i, 10) { i := add(i, 1) } { mstore(i, 7) } - - function sample(x, y) -> someVeryLongVariableName, anotherVeryLongVariableNameToTriggerNewline { - someVeryLongVariableName := 0 - anotherVeryLongVariableNameToTriggerNewline := 0 - } - - function sample2(someVeryLongVariableName, anotherVeryLongVariableNameToTriggerNewline) -> x, y { - x := someVeryLongVariableName - y := anotherVeryLongVariableNameToTriggerNewline - } - - function empty() {} - - function functionThatReturnsSevenValuesAndCanBeUsedInAssignment() -> v1, v2, v3, v4, v5, v6, v7 {} - - let zero:u32 := 0:u32 - let v:u256, t:u32 := sample(1, 2) - let x, y := sample2(2, 1) - - let val1, val2, val3, val4, val5, val6, val7 - val1, val2, val3, val4, val5, val6, val7 := functionThatReturnsSevenValuesAndCanBeUsedInAssignment() - } - - assembly { a := 1 /* some really really really long comment that should not fit in one line */ } - } -} diff --git a/forge-fmt/testdata/YulStrings/fmt.sol b/forge-fmt/testdata/YulStrings/fmt.sol deleted file mode 100644 index d05caeb26..000000000 --- a/forge-fmt/testdata/YulStrings/fmt.sol +++ /dev/null @@ -1,16 +0,0 @@ -contract Yul { - function test() external { - assembly { - let a := "abc" - let b := "abc" - let c := "abc":u32 - let d := "abc":u32 - let e := hex"deadbeef" - let f := hex"deadbeef" - let g := hex"deadbeef":u32 - let h := hex"deadbeef":u32 - datacopy(0, dataoffset("runtime"), datasize("runtime")) - return(0, datasize("runtime")) - } - } -} diff --git a/forge-fmt/testdata/YulStrings/original.sol b/forge-fmt/testdata/YulStrings/original.sol deleted file mode 100644 index fb3d5d20f..000000000 --- a/forge-fmt/testdata/YulStrings/original.sol +++ /dev/null @@ -1,16 +0,0 @@ -contract Yul { - function test() external { - assembly { - let a := "abc" - let b := 'abc' - let c := "abc":u32 - let d := 'abc':u32 - let e := hex"deadbeef" - let f := hex'deadbeef' - let g := hex"deadbeef":u32 - let h := hex'deadbeef':u32 - datacopy(0, dataoffset('runtime'), datasize("runtime")) - return(0, datasize("runtime")) - } - } -} diff --git a/forge-fmt/testdata/YulStrings/preserve-quote.fmt.sol b/forge-fmt/testdata/YulStrings/preserve-quote.fmt.sol deleted file mode 100644 index dff943539..000000000 --- a/forge-fmt/testdata/YulStrings/preserve-quote.fmt.sol +++ /dev/null @@ -1,17 +0,0 @@ -// config: quote_style = "preserve" -contract Yul { - function test() external { - assembly { - let a := "abc" - let b := 'abc' - let c := "abc":u32 - let d := 'abc':u32 - let e := hex"deadbeef" - let f := hex'deadbeef' - let g := hex"deadbeef":u32 - let h := hex'deadbeef':u32 - datacopy(0, dataoffset('runtime'), datasize("runtime")) - return(0, datasize("runtime")) - } - } -} diff --git a/forge-fmt/testdata/YulStrings/single-quote.fmt.sol b/forge-fmt/testdata/YulStrings/single-quote.fmt.sol deleted file mode 100644 index f1fc7fb8b..000000000 --- a/forge-fmt/testdata/YulStrings/single-quote.fmt.sol +++ /dev/null @@ -1,17 +0,0 @@ -// config: quote_style = "single" -contract Yul { - function test() external { - assembly { - let a := 'abc' - let b := 'abc' - let c := 'abc':u32 - let d := 'abc':u32 - let e := hex'deadbeef' - let f := hex'deadbeef' - let g := hex'deadbeef':u32 - let h := hex'deadbeef':u32 - datacopy(0, dataoffset('runtime'), datasize('runtime')) - return(0, datasize('runtime')) - } - } -} diff --git a/forge-fmt/tests/formatter.rs b/forge-fmt/tests/formatter.rs deleted file mode 100644 index d125d5c18..000000000 --- a/forge-fmt/tests/formatter.rs +++ /dev/null @@ -1,213 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -use forge_fmt::{format, parse, solang_ext::AstEq, FormatterConfig}; -use itertools::Itertools; -use std::{fs, path::PathBuf}; -use tracing_subscriber::{EnvFilter, FmtSubscriber}; - -fn tracing() { - let subscriber = FmtSubscriber::builder() - .with_env_filter(EnvFilter::from_default_env()) - .with_test_writer() - .finish(); - let _ = tracing::subscriber::set_global_default(subscriber); -} - -fn test_directory(base_name: &str) { - tracing(); - let mut original = None; - - let tests = fs::read_dir( - PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .join("testdata") - .join(base_name), - ) - .unwrap() - .filter_map(|path| { - let path = path.unwrap().path(); - let source = fs::read_to_string(&path).unwrap(); - - if let Some(filename) = path.file_name().and_then(|name| name.to_str()) { - if filename == "original.sol" { - original = Some(source); - } else if filename - .strip_suffix("fmt.sol") - .map(|filename| filename.strip_suffix('.')) - .is_some() - { - // The majority of the tests were written with the assumption - // that the default value for max line length is `80`. - // Preserve that to avoid rewriting test logic. - let default_config = FormatterConfig { - line_length: 80, - ..Default::default() - }; - - let mut config = toml::Value::try_from(&default_config).unwrap(); - let config_table = config.as_table_mut().unwrap(); - let mut lines = source.split('\n').peekable(); - let mut line_num = 1; - while let Some(line) = lines.peek() { - let entry = line - .strip_prefix("//") - .and_then(|line| line.trim().strip_prefix("config:")) - .map(str::trim); - let entry = if let Some(entry) = entry { - entry - } else { - break; - }; - - let values = match toml::from_str::(entry) { - Ok(toml::Value::Table(table)) => table, - _ => panic!("Invalid config item in {filename} at {line_num}"), - }; - config_table.extend(values); - - line_num += 1; - lines.next(); - } - let config = config - .try_into() - .unwrap_or_else(|err| panic!("Invalid config for {filename}: {err}")); - - return Some((filename.to_string(), config, lines.join("\n"))); - } - } - - None - }) - .collect::>(); - - for (filename, config, formatted) in tests { - test_formatter( - &filename, - config, - original.as_ref().expect("original.sol not found"), - &formatted, - ); - } -} - -fn assert_eof(content: &str) { - assert!(content.ends_with('\n') && !content.ends_with("\n\n")); -} - -fn test_formatter(filename: &str, config: FormatterConfig, source: &str, expected_source: &str) { - #[derive(Eq)] - struct PrettyString(String); - - impl PartialEq for PrettyString { - fn eq(&self, other: &PrettyString) -> bool { - self.0.lines().eq(other.0.lines()) - } - } - - impl std::fmt::Debug for PrettyString { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - f.write_str(&self.0) - } - } - - assert_eof(expected_source); - - let source_parsed = parse(source).unwrap(); - let expected_parsed = parse(expected_source).unwrap(); - - if !source_parsed.pt.ast_eq(&expected_parsed.pt) { - pretty_assertions::assert_eq!( - source_parsed.pt, - expected_parsed.pt, - "(formatted Parse Tree == expected Parse Tree) in {}", - filename - ); - } - - let expected = PrettyString(expected_source.to_string()); - - let mut source_formatted = String::new(); - format(&mut source_formatted, source_parsed, config.clone()).unwrap(); - assert_eof(&source_formatted); - - // println!("{}", source_formatted); - let source_formatted = PrettyString(source_formatted); - - pretty_assertions::assert_eq!( - source_formatted, - expected, - "(formatted == expected) in {}", - filename - ); - - let mut expected_formatted = String::new(); - format(&mut expected_formatted, expected_parsed, config).unwrap(); - assert_eof(&expected_formatted); - - let expected_formatted = PrettyString(expected_formatted); - - pretty_assertions::assert_eq!( - expected_formatted, - expected, - "(formatted == expected) in {}", - filename - ); -} - -macro_rules! test_directories { - ($($dir:ident),+ $(,)?) => {$( - #[allow(non_snake_case)] - #[test] - fn $dir() { - test_directory(stringify!($dir)); - } - )+}; -} - -test_directories! { - ConstructorDefinition, - ContractDefinition, - DocComments, - EnumDefinition, - ErrorDefinition, - EventDefinition, - FunctionDefinition, - FunctionType, - ImportDirective, - ModifierDefinition, - StatementBlock, - StructDefinition, - TypeDefinition, - UsingDirective, - VariableDefinition, - OperatorExpressions, - WhileStatement, - DoWhileStatement, - ForStatement, - IfStatement, - IfStatement2, - VariableAssignment, - FunctionCallArgsStatement, - RevertStatement, - RevertNamedArgsStatement, - ReturnStatement, - TryStatement, - ConditionalOperatorExpression, - NamedFunctionCallExpression, - ArrayExpressions, - UnitExpression, - ThisExpression, - SimpleComments, - LiteralExpression, - Yul, - YulStrings, - IntTypes, - InlineDisable, - NumberLiteralUnderscore, - FunctionCall, - TrailingComma, - PragmaDirective, - Annotation, - MappingType, - EmitStatement, - Repros, -} From b7e6d51a921fd533a91e76f86efecd1af61fe6d3 Mon Sep 17 00:00:00 2001 From: Govardhan G D Date: Mon, 25 Sep 2023 12:30:01 +0530 Subject: [PATCH 21/25] change the API exposed by Builder Signed-off-by: Govardhan G D --- src/bin/languageserver/mod.rs | 213 +++++++++++++++++----------------- 1 file changed, 106 insertions(+), 107 deletions(-) diff --git a/src/bin/languageserver/mod.rs b/src/bin/languageserver/mod.rs index f251025a2..7bf1a7658 100644 --- a/src/bin/languageserver/mod.rs +++ b/src/bin/languageserver/mod.rs @@ -145,6 +145,15 @@ struct GlobalCache { implementations: Implementations, } +impl GlobalCache { + fn extend(&mut self, other: Self) { + self.definitions.extend(other.definitions); + self.types.extend(other.types); + self.declarations.extend(other.declarations); + self.implementations.extend(other.implementations); + } +} + // The language server currently stores some of the data grouped by the file to which the data belongs (Files struct). // Other data (Definitions) is not grouped by file due to problems faced during cleanup, // but is stored as a "global" field which is common to all files. @@ -293,20 +302,20 @@ impl SolangServer { let res = self.client.publish_diagnostics(uri, diags, None); - let (caches, definitions, types, declarations, implementations) = Builder::build(&ns); + let (file_caches, global_cache) = { + let builder = Builder::new(&ns); + builder.build() + }; let mut files = self.files.lock().await; - for (f, c) in ns.files.iter().zip(caches.into_iter()) { + for (f, c) in ns.files.iter().zip(file_caches.into_iter()) { if f.cache_no.is_some() { files.caches.insert(f.path.clone(), c); } } let mut gc = self.global_cache.lock().await; - gc.definitions.extend(definitions); - gc.types.extend(types); - gc.declarations.extend(declarations); - gc.implementations.extend(implementations); + gc.extend(global_cache); res.await; } @@ -358,6 +367,20 @@ struct Builder<'a> { } impl<'a> Builder<'a> { + fn new(ns: &'a ast::Namespace) -> Self { + Self { + hovers: Vec::new(), + references: Vec::new(), + + definitions: HashMap::new(), + types: HashMap::new(), + declarations: HashMap::new(), + implementations: HashMap::new(), + + ns, + } + } + // Constructs lookup table for the given statement by traversing the // statements and traversing inside the contents of the statements. fn statement(&mut self, stmt: &ast::Statement, symtab: &symtable::Symtable) { @@ -1332,32 +1355,12 @@ impl<'a> Builder<'a> { /// Traverses namespace to extract information used later by the language server /// This includes hover messages, locations where code objects are declared and used - fn build( - ns: &ast::Namespace, - ) -> ( - Vec, - Definitions, - Types, - Declarations, - Implementations, - ) { - let mut builder = Builder { - hovers: Vec::new(), - references: Vec::new(), - - definitions: HashMap::new(), - types: HashMap::new(), - declarations: HashMap::new(), - implementations: HashMap::new(), - - ns, - }; - - for (ei, enum_decl) in builder.ns.enums.iter().enumerate() { + fn build(mut self) -> (Vec, GlobalCache) { + for (ei, enum_decl) in self.ns.enums.iter().enumerate() { for (discriminant, (nam, loc)) in enum_decl.values.iter().enumerate() { let file_no = loc.file_no(); - let file = &ns.files[file_no]; - builder.hovers.push(( + let file = &self.ns.files[file_no]; + self.hovers.push(( file_no, HoverEntry { start: loc.start(), @@ -1373,17 +1376,15 @@ impl<'a> Builder<'a> { def_path: file.path.clone(), def_type: DefinitionType::Variant(ei, discriminant), }; - builder - .definitions - .insert(di.clone(), loc_to_range(loc, file)); + self.definitions.insert(di.clone(), loc_to_range(loc, file)); let dt = DefinitionType::Enum(ei); - builder.types.insert(di, dt.into()); + self.types.insert(di, dt.into()); } let file_no = enum_decl.loc.file_no(); - let file = &ns.files[file_no]; - builder.hovers.push(( + let file = &self.ns.files[file_no]; + self.hovers.push(( file_no, HoverEntry { start: enum_decl.loc.start(), @@ -1391,7 +1392,7 @@ impl<'a> Builder<'a> { val: render(&enum_decl.tags[..]), }, )); - builder.definitions.insert( + self.definitions.insert( DefinitionIndex { def_path: file.path.clone(), def_type: DefinitionType::Enum(ei), @@ -1400,15 +1401,15 @@ impl<'a> Builder<'a> { ); } - for (si, struct_decl) in builder.ns.structs.iter().enumerate() { + for (si, struct_decl) in self.ns.structs.iter().enumerate() { if let pt::Loc::File(_, start, _) = &struct_decl.loc { for (fi, field) in struct_decl.fields.iter().enumerate() { - builder.field(si, fi, field); + self.field(si, fi, field); } let file_no = struct_decl.loc.file_no(); - let file = &ns.files[file_no]; - builder.hovers.push(( + let file = &self.ns.files[file_no]; + self.hovers.push(( file_no, HoverEntry { start: *start, @@ -1416,7 +1417,7 @@ impl<'a> Builder<'a> { val: render(&struct_decl.tags[..]), }, )); - builder.definitions.insert( + self.definitions.insert( DefinitionIndex { def_path: file.path.clone(), def_type: DefinitionType::Struct(si), @@ -1426,7 +1427,7 @@ impl<'a> Builder<'a> { } } - for (i, func) in builder.ns.functions.iter().enumerate() { + for (i, func) in self.ns.functions.iter().enumerate() { if func.is_accessor || func.loc == pt::Loc::Builtin { // accessor functions are synthetic; ignore them, all the locations are fake continue; @@ -1437,11 +1438,11 @@ impl<'a> Builder<'a> { ast::ConstructorAnnotation::Bump(expr) | ast::ConstructorAnnotation::Seed(expr) | ast::ConstructorAnnotation::Space(expr) => { - builder.expression(expr, &func.symtable) + self.expression(expr, &func.symtable) } ast::ConstructorAnnotation::Payer(loc, name) => { - builder.hovers.push(( + self.hovers.push(( loc.file_no(), HoverEntry { start: loc.start(), @@ -1454,33 +1455,32 @@ impl<'a> Builder<'a> { } for (i, param) in func.params.iter().enumerate() { - builder.hovers.push(( + self.hovers.push(( param.loc.file_no(), HoverEntry { start: param.loc.start(), stop: param.loc.exclusive_end(), - val: builder.expanded_ty(¶m.ty), + val: self.expanded_ty(¶m.ty), }, )); if let Some(Some(var_no)) = func.symtable.arguments.get(i) { if let Some(id) = ¶m.id { let file_no = id.loc.file_no(); - let file = &builder.ns.files[file_no]; + let file = &self.ns.files[file_no]; let di = DefinitionIndex { def_path: file.path.clone(), def_type: DefinitionType::Variable(*var_no), }; - builder - .definitions + self.definitions .insert(di.clone(), loc_to_range(&id.loc, file)); if let Some(dt) = get_type_definition(¶m.ty) { - builder.types.insert(di, dt.into()); + self.types.insert(di, dt.into()); } } } if let Some(loc) = param.ty_loc { if let Some(dt) = get_type_definition(¶m.ty) { - builder.references.push(( + self.references.push(( loc.file_no(), ReferenceEntry { start: loc.start(), @@ -1493,35 +1493,34 @@ impl<'a> Builder<'a> { } for (i, ret) in func.returns.iter().enumerate() { - builder.hovers.push(( + self.hovers.push(( ret.loc.file_no(), HoverEntry { start: ret.loc.start(), stop: ret.loc.exclusive_end(), - val: builder.expanded_ty(&ret.ty), + val: self.expanded_ty(&ret.ty), }, )); if let Some(id) = &ret.id { if let Some(var_no) = func.symtable.returns.get(i) { let file_no = id.loc.file_no(); - let file = &ns.files[file_no]; + let file = &self.ns.files[file_no]; let di = DefinitionIndex { def_path: file.path.clone(), def_type: DefinitionType::Variable(*var_no), }; - builder - .definitions + self.definitions .insert(di.clone(), loc_to_range(&id.loc, file)); if let Some(dt) = get_type_definition(&ret.ty) { - builder.types.insert(di, dt.into()); + self.types.insert(di, dt.into()); } } } if let Some(loc) = ret.ty_loc { if let Some(dt) = get_type_definition(&ret.ty) { - builder.references.push(( + self.references.push(( loc.file_no(), ReferenceEntry { start: loc.start(), @@ -1534,12 +1533,12 @@ impl<'a> Builder<'a> { } for stmt in &func.body { - builder.statement(stmt, &func.symtable); + self.statement(stmt, &func.symtable); } let file_no = func.loc.file_no(); - let file = &ns.files[file_no]; - builder.definitions.insert( + let file = &self.ns.files[file_no]; + self.definitions.insert( DefinitionIndex { def_path: file.path.clone(), def_type: DefinitionType::Function(i), @@ -1548,26 +1547,26 @@ impl<'a> Builder<'a> { ); } - for (i, constant) in builder.ns.constants.iter().enumerate() { + for (i, constant) in self.ns.constants.iter().enumerate() { let samptb = symtable::Symtable::new(); - builder.contract_variable(constant, &samptb, None, i); + self.contract_variable(constant, &samptb, None, i); } - for (ci, contract) in builder.ns.contracts.iter().enumerate() { + for (ci, contract) in self.ns.contracts.iter().enumerate() { for base in &contract.bases { let file_no = base.loc.file_no(); - builder.hovers.push(( + self.hovers.push(( file_no, HoverEntry { start: base.loc.start(), stop: base.loc.exclusive_end(), val: make_code_block(format!( "contract {}", - builder.ns.contracts[base.contract_no].name + self.ns.contracts[base.contract_no].name )), }, )); - builder.references.push(( + self.references.push(( file_no, ReferenceEntry { start: base.loc.start(), @@ -1582,12 +1581,12 @@ impl<'a> Builder<'a> { for (i, variable) in contract.variables.iter().enumerate() { let symtable = symtable::Symtable::new(); - builder.contract_variable(variable, &symtable, Some(ci), i); + self.contract_variable(variable, &symtable, Some(ci), i); } let file_no = contract.loc.file_no(); - let file = &ns.files[file_no]; - builder.hovers.push(( + let file = &self.ns.files[file_no]; + self.hovers.push(( file_no, HoverEntry { start: contract.loc.start(), @@ -1601,8 +1600,7 @@ impl<'a> Builder<'a> { def_type: DefinitionType::Contract(ci), }; - builder - .definitions + self.definitions .insert(cdi.clone(), loc_to_range(&contract.loc, file)); let impls = contract @@ -1614,7 +1612,7 @@ impl<'a> Builder<'a> { }) .collect(); - builder.implementations.insert(cdi, impls); + self.implementations.insert(cdi, impls); let decls = contract .virtual_functions @@ -1639,7 +1637,7 @@ impl<'a> Builder<'a> { .bases .iter() .map(|b| { - let p = &builder.ns.contracts[b.contract_no]; + let p = &self.ns.contracts[b.contract_no]; HashSet::from_iter(p.functions.iter().copied()) .intersection(&all_decls) .copied() @@ -1652,9 +1650,9 @@ impl<'a> Builder<'a> { let decls = parent_decls .iter() .map(|&i| { - let loc = builder.ns.functions[i].loc; + let loc = self.ns.functions[i].loc; DefinitionIndex { - def_path: builder.ns.files[loc.file_no()].path.clone(), + def_path: self.ns.files[loc.file_no()].path.clone(), def_type: DefinitionType::Function(i), } }) @@ -1664,17 +1662,17 @@ impl<'a> Builder<'a> { }) }); - builder.declarations.extend(decls); + self.declarations.extend(decls); } - for (ei, event) in builder.ns.events.iter().enumerate() { + for (ei, event) in self.ns.events.iter().enumerate() { for (fi, field) in event.fields.iter().enumerate() { - builder.field(ei, fi, field); + self.field(ei, fi, field); } let file_no = event.loc.file_no(); - let file = &ns.files[file_no]; - builder.hovers.push(( + let file = &self.ns.files[file_no]; + self.hovers.push(( file_no, HoverEntry { start: event.loc.start(), @@ -1683,7 +1681,7 @@ impl<'a> Builder<'a> { }, )); - builder.definitions.insert( + self.definitions.insert( DefinitionIndex { def_path: file.path.clone(), def_type: DefinitionType::Event(ei), @@ -1692,12 +1690,12 @@ impl<'a> Builder<'a> { ); } - for lookup in &mut builder.hovers { - if let Some(msg) = builder.ns.hover_overrides.get(&pt::Loc::File( - lookup.0, - lookup.1.start, - lookup.1.stop, - )) { + for lookup in &mut self.hovers { + if let Some(msg) = + self.ns + .hover_overrides + .get(&pt::Loc::File(lookup.0, lookup.1.start, lookup.1.stop)) + { lookup.1.val = msg.clone(); } } @@ -1706,27 +1704,28 @@ impl<'a> Builder<'a> { // previously, a dummy path was filled. // In a single namespace, there can't be two (or more) code objects with a given `DefinitionType`. // So, there exists a one-to-one mapping between `DefinitionIndex` and `DefinitionType` when we are dealing with just one namespace. - let defs_to_files = builder + let defs_to_files = self .definitions .keys() .map(|key| (key.def_type.clone(), key.def_path.clone())) .collect::>(); - let defs_to_file_nos = ns + let defs_to_file_nos = self + .ns .files .iter() .enumerate() .map(|(i, f)| (f.path.clone(), i)) .collect::>(); - for val in builder.types.values_mut() { + for val in self.types.values_mut() { val.def_path = defs_to_files[&val.def_type].clone(); } - for (di, range) in &builder.definitions { + for (di, range) in &self.definitions { let file_no = defs_to_file_nos[&di.def_path]; - let file = &ns.files[file_no]; - builder.references.push(( + let file = &self.ns.files[file_no]; + self.references.push(( file_no, ReferenceEntry { start: file @@ -1740,7 +1739,8 @@ impl<'a> Builder<'a> { )); } - let caches = ns + let file_caches = self + .ns .files .iter() .enumerate() @@ -1748,8 +1748,7 @@ impl<'a> Builder<'a> { file: f.clone(), // get `hovers` that belong to the current file hovers: Lapper::new( - builder - .hovers + self.hovers .iter() .filter(|h| h.0 == i) .map(|(_, i)| i.clone()) @@ -1757,8 +1756,7 @@ impl<'a> Builder<'a> { ), // get `references` that belong to the current file references: Lapper::new( - builder - .references + self.references .iter() .filter(|h| h.0 == i) .map(|(_, i)| { @@ -1771,13 +1769,14 @@ impl<'a> Builder<'a> { }) .collect(); - ( - caches, - builder.definitions, - builder.types, - builder.declarations, - builder.implementations, - ) + let global_cache = GlobalCache { + definitions: self.definitions, + types: self.types, + declarations: self.declarations, + implementations: self.implementations, + }; + + (file_caches, global_cache) } /// Render the type with struct/enum fields expanded From cebf46435c3b9457f57a2828c0749e657f934d0c Mon Sep 17 00:00:00 2001 From: Govardhan G D Date: Mon, 25 Sep 2023 12:32:04 +0530 Subject: [PATCH 22/25] change to doc comments Signed-off-by: Govardhan G D --- src/sema/ast.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/sema/ast.rs b/src/sema/ast.rs index 8ff2c91f9..2eddec67a 100644 --- a/src/sema/ast.rs +++ b/src/sema/ast.rs @@ -768,9 +768,9 @@ pub struct Contract { pub fixed_layout_size: BigInt, pub functions: Vec, pub all_functions: BTreeMap, - // maps the name of virtual functions to a vector of overriden functions. - // Each time a virtual function is overriden, there will be an entry pushed to the vector. The last - // element represents the current overriding function - there will be at least one entry in this vector. + /// maps the name of virtual functions to a vector of overriden functions. + /// Each time a virtual function is overriden, there will be an entry pushed to the vector. The last + /// element represents the current overriding function - there will be at least one entry in this vector. pub virtual_functions: HashMap>, pub yul_functions: Vec, pub variables: Vec, From 2638b4ca71fb2def7f8292d165c470f0000c47be Mon Sep 17 00:00:00 2001 From: Govardhan G D Date: Tue, 26 Sep 2023 08:11:23 +0530 Subject: [PATCH 23/25] inline builder build Signed-off-by: Govardhan G D --- src/bin/languageserver/mod.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/bin/languageserver/mod.rs b/src/bin/languageserver/mod.rs index 7bf1a7658..1ba5e0c26 100644 --- a/src/bin/languageserver/mod.rs +++ b/src/bin/languageserver/mod.rs @@ -302,10 +302,7 @@ impl SolangServer { let res = self.client.publish_diagnostics(uri, diags, None); - let (file_caches, global_cache) = { - let builder = Builder::new(&ns); - builder.build() - }; + let (file_caches, global_cache) = Builder::new(&ns).build(); let mut files = self.files.lock().await; for (f, c) in ns.files.iter().zip(file_caches.into_iter()) { From 12b83fb292caf924c8f7abe81d9dbc822edb73a8 Mon Sep 17 00:00:00 2001 From: Govardhan G D Date: Fri, 29 Sep 2023 17:45:39 +0530 Subject: [PATCH 24/25] add tests Signed-off-by: Govardhan G D --- vscode/src/test/suite/extension.test.ts | 48 ++++++++++++++++++++++++- vscode/src/testFixture/format.sol | 48 +++++++++++++++++++++++++ 2 files changed, 95 insertions(+), 1 deletion(-) create mode 100644 vscode/src/testFixture/format.sol diff --git a/vscode/src/test/suite/extension.test.ts b/vscode/src/test/suite/extension.test.ts index 1aee1d117..73a6c7e51 100644 --- a/vscode/src/test/suite/extension.test.ts +++ b/vscode/src/test/suite/extension.test.ts @@ -1,7 +1,7 @@ import * as assert from 'assert'; import * as vscode from 'vscode'; -import { getDocUri, activate } from './helper'; +import { getDocUri, activate, doc } from './helper'; // You can import and use all API from the 'vscode' module // as well as import your extension to test it @@ -97,6 +97,13 @@ suite('Extension Test Suite', function () { await testrefs(refsdoc1); }); + // Tests for formatting + this.timeout(20000); + const formatdoc1 = getDocUri('format.sol'); + test('Testing for Formatting', async () => { + await testformat(formatdoc1); + }); + }); function toRange(lineno1: number, charno1: number, lineno2: number, charno2: number) { @@ -356,6 +363,45 @@ async function testrefs(docUri: vscode.Uri) { assert.strictEqual(loc15.uri.path, docUri.path); } +async function testformat(docUri: vscode.Uri) { + await activate(docUri); + + const options = { + tabSize: 4, + insertSpaces: false, + }; + const textedits = (await vscode.commands.executeCommand( + 'vscode.executeFormatDocumentProvider', + docUri, + options, + )) as vscode.TextEdit[]; + // make sure that the input file is not already formatted + assert(textedits.length > 0); + + // undo the changes done during the test + const undochanges = async () => { + for (let i = 0; i < textedits.length; i++) { + await vscode.commands.executeCommand('undo'); + } + }; + + try { + const workedits = new vscode.WorkspaceEdit(); + workedits.set(docUri, textedits); + const done = await vscode.workspace.applyEdit(workedits); + assert(done); + + const actualtext = doc.getText(); + const expectedtext = "contractdeck {\n enum suit {\n club,\n diamonds,\n hearts,\n spades\n }\n enum value {\n two,\n three,\n four,\n five,\n six,\n seven,\n eight,\n nine,\n ten,\n jack,\n queen,\n king,\n ace\n }\n\n struct card {\n value v;\n suit s;\n }\n\n function score(card c) public returns (uint32 score) {\n if (c.s == suit.hearts) {\n if (c.v == value.ace) {\n score = 14;\n }\n if (c.v == value.king) {\n score = 13;\n }\n if (c.v == value.queen) {\n score = 12;\n }\n if (c.v == value.jack) {\n score = 11;\n }\n }\n // all others score 0\n }\n}\n"; + assert.strictEqual(actualtext, expectedtext); + } catch (error) { + await undochanges(); + throw error; + } + + await undochanges(); +} + async function testhover(docUri: vscode.Uri) { await activate(docUri); diff --git a/vscode/src/testFixture/format.sol b/vscode/src/testFixture/format.sol new file mode 100644 index 000000000..dd6f4bef2 --- /dev/null +++ b/vscode/src/testFixture/format.sol @@ -0,0 +1,48 @@ +contract deck { + enum + suit { + club, + diamonds, + hearts, + spades + } + enum value { + two, + + + three, + four, + five, + six, + seven, + eight, + nine, + ten,jack, + queen, + king, + ace + } + struct card { + value v; + suit s; + } + + function score + (card c) public returns ( + uint32 score) { + if (c.s == suit.hearts) { + if (c.v == value.ace) { + score = 14; + } + if ( c.v == value.king) { + score = 13; + } + if ( c.v == value.queen) { + score = 12; + } + if ( c.v == value.jack) { + score = 11;} + } + // all others score 0 + } +} From 9033c11407b9997bba17ed820cbd3d3bb918c8fc Mon Sep 17 00:00:00 2001 From: Govardhan G D Date: Sun, 1 Oct 2023 15:10:16 +0530 Subject: [PATCH 25/25] update rust version to 1.72.0 Signed-off-by: Govardhan G D --- .github/workflows/release.yml | 10 +++++----- .github/workflows/test.yml | 10 +++++----- Cargo.toml | 2 +- Dockerfile | 2 +- docs/installing.rst | 2 +- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e39ca65ed..f3b5c2b64 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -16,7 +16,7 @@ jobs: with: submodules: recursive - name: Rust stable - run: rustup default 1.70.0 + run: rustup default 1.72.0 - name: Build run: cargo build --verbose --release - name: Run tests @@ -40,7 +40,7 @@ jobs: with: submodules: recursive - name: Rust stable - run: rustup default 1.70.0 + run: rustup default 1.72.0 - name: Build run: cargo build --verbose --release - name: Run tests @@ -67,7 +67,7 @@ jobs: run: unzip c:\llvm.zip -d c:/ - name: Add LLVM to Path run: echo "c:\llvm15.0\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 - - uses: dtolnay/rust-toolchain@1.70.0 + - uses: dtolnay/rust-toolchain@1.72.0 with: components: clippy - name: Build @@ -91,7 +91,7 @@ jobs: uses: actions/checkout@v3 with: submodules: recursive - - uses: dtolnay/rust-toolchain@1.70.0 + - uses: dtolnay/rust-toolchain@1.72.0 - name: Get LLVM run: curl -sSL --output llvm15.0-mac-arm.tar.xz https://github.com/hyperledger/solang-llvm/releases/download/llvm15-1/llvm15.0-mac-arm.tar.xz - name: Extract LLVM @@ -121,7 +121,7 @@ jobs: uses: actions/checkout@v3 with: submodules: recursive - - uses: dtolnay/rust-toolchain@1.70.0 + - uses: dtolnay/rust-toolchain@1.72.0 - name: Get LLVM run: wget -q -O llvm15.0-mac-intel.tar.xz https://github.com/hyperledger/solang-llvm/releases/download/llvm15-1/llvm15.0-mac-intel.tar.xz - name: Extract LLVM diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 007d7d03a..f66c98f8d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -53,7 +53,7 @@ jobs: with: submodules: recursive - name: Rust stable - run: rustup default 1.70.0 + run: rustup default 1.72.0 - name: Run cargo clippy run: cargo clippy --workspace --tests --bins -- -D warnings - name: Run cargo clippy without wasm_opt feature @@ -86,7 +86,7 @@ jobs: with: submodules: recursive - name: Rust stable - run: rustup default 1.70.0 + run: rustup default 1.72.0 - name: Build run: cargo build --verbose - name: Run tests @@ -113,7 +113,7 @@ jobs: # Use C:\ as D:\ might run out of space - name: "Use C: for rust temporary files" run: echo "CARGO_TARGET_DIR=C:\target" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf8 -Append - - uses: dtolnay/rust-toolchain@1.70.0 + - uses: dtolnay/rust-toolchain@1.72.0 with: components: clippy # We run clippy on Linux in the lint job above, but this does not check #[cfg(windows)] items @@ -140,7 +140,7 @@ jobs: uses: actions/checkout@v3 with: submodules: recursive - - uses: dtolnay/rust-toolchain@1.70.0 + - uses: dtolnay/rust-toolchain@1.72.0 - name: Get LLVM run: curl -sSL --output llvm15.0-mac-arm.tar.xz https://github.com/hyperledger/solang-llvm/releases/download/llvm15-1/llvm15.0-mac-arm.tar.xz - name: Extract LLVM @@ -166,7 +166,7 @@ jobs: uses: actions/checkout@v3 with: submodules: recursive - - uses: dtolnay/rust-toolchain@1.70.0 + - uses: dtolnay/rust-toolchain@1.72.0 - name: Get LLVM run: wget -q -O llvm15.0-mac-intel.tar.xz https://github.com/hyperledger/solang-llvm/releases/download/llvm15-1/llvm15.0-mac-intel.tar.xz - name: Extract LLVM diff --git a/Cargo.toml b/Cargo.toml index 98cda6185..5e02b0e39 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ license = "Apache-2.0" build = "build.rs" description = "Solang Solidity Compiler" keywords = [ "solidity", "compiler", "solana", "polkadot", "substrate" ] -rust-version = "1.70.0" +rust-version = "1.72.0" edition = "2021" exclude = [ "/.*", "/docs", "/examples", "/solana-library", "/tests", "/integration", "/vscode", "/testdata" ] diff --git a/Dockerfile b/Dockerfile index d5738ba94..44e9bc26c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,7 +4,7 @@ COPY . src WORKDIR /src/stdlib/ RUN make -RUN rustup default 1.70.0 +RUN rustup default 1.72.0 WORKDIR /src RUN cargo build --release diff --git a/docs/installing.rst b/docs/installing.rst index 54af77b33..c1ac0beeb 100644 --- a/docs/installing.rst +++ b/docs/installing.rst @@ -89,7 +89,7 @@ Option 5: Build Solang from source In order to build Solang from source, you will need: -* Rust version 1.70.0 or higher +* Rust version 1.72.0 or higher * A C++ compiler with support for C++17 * A build of LLVM based on the Solana LLVM tree. There are a few LLVM patches required that are not upstream yet.