diff --git a/Cargo.toml b/Cargo.toml index 3b4082cdc..a70ee83d9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -67,7 +67,7 @@ primitive-types = { version = "0.12", features = ["codec"] } normalize-path = "0.2.1" bitflags = "2.4" scopeguard = "1.2.0" -forge-fmt = { version = "0.2.0", optional = true } +forge-fmt = { path = "fmt", optional = true } # We don't use ethers-core directly, but need the correct version for the # build to work. ethers-core = { version = "2.0.10", optional = true } @@ -107,4 +107,4 @@ wasm_opt = ["llvm", "wasm-opt", "contract-build"] language_server = ["tower-lsp", "forge-fmt", "ethers-core", "tokio", "rust-lapper"] [workspace] -members = ["solang-parser", "tests/wasm_host_attr"] +members = ["solang-parser", "fmt", "tests/wasm_host_attr"] diff --git a/fmt/Cargo.toml b/fmt/Cargo.toml new file mode 100644 index 000000000..6b8f328b5 --- /dev/null +++ b/fmt/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "forge-fmt" +version = "0.2.0" +edition = "2021" +authors = ["Foundry Contributors"] +license = "MIT OR Apache-2.0" +homepage = "https://github.com/foundry-rs/foundry" +repository = "https://github.com/foundry-rs/foundry" + +[dependencies] +ariadne = "0.4" +itertools = "0.12" +solang-parser = { path = "../solang-parser" } +thiserror = "1" +serde = "1.0" +tracing = "0.1" +alloy-primitives = "0.7" + +[dev-dependencies] +similar-asserts = "1.5" +toml = "0.8" +tracing-subscriber = { version = "0.3", features = ["env-filter"] } diff --git a/fmt/README.md b/fmt/README.md new file mode 100644 index 000000000..11e0c5ea3 --- /dev/null +++ b/fmt/README.md @@ -0,0 +1,204 @@ +# 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 +``` + +### Disable Block + +The formatter can be disabled for a section of code by adding a comment `// forgefmt: disable-start` before and a comment `// forgefmt: disable-end` after, like this: + +```solidity +// forgefmt: disable-start +uint x = 100; +uint y = 101; +// forgefmt: disable-end +``` + +### 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/fmt/src/buffer.rs b/fmt/src/buffer.rs new file mode 100644 index 000000000..60126b4a1 --- /dev/null +++ b/fmt/src/buffer.rs @@ -0,0 +1,448 @@ +// SPDX-License-Identifier: Apache-2.0 + +//! Format buffer. + +use crate::{ + comments::{CommentState, CommentStringExt}, + string::{QuoteState, QuotedStringExt}, +}; +use std::fmt::Write; + +/// An indent group. The group may optionally skip the first line +#[derive(Clone, Debug, Default)] +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 { + Self::LineStart(state) => *state, + Self::WriteTokens(state) => *state, + Self::WriteString(_) => CommentState::None, + } + } +} + +impl Default for WriteState { + fn default() -> Self { + Self::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/fmt/src/chunk.rs b/fmt/src/chunk.rs new file mode 100644 index 000000000..c2fec9711 --- /dev/null +++ b/fmt/src/chunk.rs @@ -0,0 +1,73 @@ +// 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 { + Self { + content: string, + ..Default::default() + } + } +} + +impl From<&str> for Chunk { + fn from(string: &str) -> Self { + Self { + 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 { + Self { + 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/fmt/src/comments.rs b/fmt/src/comments.rs new file mode 100644 index 000000000..e51d8cc12 --- /dev/null +++ b/fmt/src/comments.rs @@ -0,0 +1,481 @@ +// 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(Clone, Copy, Debug, 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(Clone, Copy, Debug, PartialEq, Eq)] +pub enum CommentPosition { + /// Comes before the code it describes + Prefix, + /// Comes after the code it describes + Postfix, +} + +/// Comment with additional metadata +#[derive(Clone, Debug, 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<&Self>) -> 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_doc_block(&self) -> bool { + matches!(self.ty, CommentType::DocBlock) + } + + 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(Clone, Debug, Default)] +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, + } + } + + /// Helper 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(Clone, Copy, Debug, Default, PartialEq, Eq)] +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 size_hint(&self) -> (usize, Option) { + self.iter.size_hint() + } + + #[inline] + fn count(self) -> usize { + self.iter.count() + } +} + +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/fmt/src/config.rs b/fmt/src/config.rs new file mode 100644 index 000000000..66e95852d --- /dev/null +++ b/fmt/src/config.rs @@ -0,0 +1,186 @@ +// 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(Clone, Debug, 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 underscores in hex literals + pub hex_underscore: HexUnderscore, + /// 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, + /// Sort import statements alphabetically in groups (a group is separated by a newline). + pub sort_imports: bool, +} + +/// Style of uint/int256 types +#[derive(Clone, Copy, Debug, 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(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum NumberUnderscore { + /// Use the underscores defined in the source code + Preserve, + /// Remove all underscores + #[default] + Remove, + /// Add an underscore every thousand, if greater than 9999 + /// e.g. 1000 -> 1000 and 10000 -> 10_000 + Thousands, +} + +impl NumberUnderscore { + /// Returns true if the option is `Preserve` + #[inline] + pub fn is_preserve(self) -> bool { + matches!(self, NumberUnderscore::Preserve) + } + + /// Returns true if the option is `Remove` + #[inline] + pub fn is_remove(self) -> bool { + matches!(self, NumberUnderscore::Remove) + } + + /// Returns true if the option is `Remove` + #[inline] + pub fn is_thousands(self) -> bool { + matches!(self, NumberUnderscore::Thousands) + } +} + +/// Style of underscores in hex literals +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum HexUnderscore { + /// Use the underscores defined in the source code + Preserve, + /// Remove all underscores + #[default] + Remove, + /// Add underscore as separator between byte boundaries + Bytes, +} + +impl HexUnderscore { + /// Returns true if the option is `Preserve` + #[inline] + pub fn is_preserve(self) -> bool { + matches!(self, HexUnderscore::Preserve) + } + + /// Returns true if the option is `Remove` + #[inline] + pub fn is_remove(self) -> bool { + matches!(self, HexUnderscore::Remove) + } + + /// Returns true if the option is `Remove` + #[inline] + pub fn is_bytes(self) -> bool { + matches!(self, HexUnderscore::Bytes) + } +} + +/// Style of string quotes +#[derive(Clone, Copy, Debug, 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(Clone, Copy, Debug, 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(Clone, Copy, Debug, 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, + hex_underscore: HexUnderscore::Remove, + single_line_statement_blocks: SingleLineBlockStyle::Preserve, + override_spacing: false, + wrap_comments: false, + ignore: vec![], + contract_new_lines: false, + sort_imports: false, + } + } +} diff --git a/fmt/src/formatter.rs b/fmt/src/formatter.rs new file mode 100644 index 000000000..674127642 --- /dev/null +++ b/fmt/src/formatter.rs @@ -0,0 +1,3937 @@ +// SPDX-License-Identifier: Apache-2.0 + +//! A Solidity formatter + +use crate::{ + buffer::*, + chunk::*, + comments::{ + CommentPosition, CommentState, CommentStringExt, CommentType, CommentWithMetadata, Comments, + }, + config::{ + FormatterConfig, HexUnderscore, IntTypes, MultilineFuncHeaderStyle, SingleLineBlockStyle, + }, + helpers::import_path_string, + macros::*, + solang_ext::{pt::*, *}, + string::{QuoteState, QuotedStringExt}, + visit::{Visitable, Visitor}, + InlineConfig, +}; +use alloy_primitives::Address; +use itertools::{Either, Itertools}; +use solang_parser::pt::{PragmaDirective, VersionComparator}; +use std::{fmt::Write, str::FromStr}; +use thiserror::Error; + +type Result = std::result::Result; + +/// A custom Error thrown by the Formatter +#[derive(Debug, Error)] +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 + Send + Sync + '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(Debug, Default)] +struct Context { + contract: Option, + function: Option, + if_stmt_single_line: Option, +} + +impl Context { + /// Returns true if the current function context is the constructor + pub(crate) fn is_constructor_function(&self) -> bool { + self.function + .as_ref() + .map_or(false, |f| matches!(f.ty, FunctionTy::Constructor)) + } +} + +/// 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, +} + +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 { + // because of sorting import statements, start can be greater than end + if start > end { + return 0; + } + 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()] == *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. + 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(), " *")?; + let content = &line[to_skip..]; + if !content.trim().is_empty() { + 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`](Self::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 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.indented(1, |fmt| fmt.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 visited_locs: Vec = Vec::new(); + + // marker for whether the next item needs additional space + let mut needs_space = false; + let mut last_comment = None; + + 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(mut last_loc) = last_loc { + // here's an edge case when we reordered items so the last_loc isn't + // necessarily the item that directly precedes the current item because + // the order might have changed, so we need to find the last item that + // is before the current item by checking the recorded locations + if let Some(last_item) = visited_locs + .iter() + .rev() + .find(|prev_item| prev_item.start() > last_loc.end()) + { + last_loc = *last_item; + } + + // The blank lines check is susceptible additional trailing new lines + // because the block docs can contain + // multiple lines, but the function def should follow directly after the + // block comment + let is_last_doc_comment = matches!( + last_comment, + Some(CommentWithMetadata { + ty: CommentType::DocBlock, + .. + }) + ); + + if needs_space + || (!is_last_doc_comment + && 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); + visited_locs.push(loc); + + last_comment = None; + + last_byte_written = loc.end(); + if let Some(comment) = line_item.left() { + if comment.is_line() { + last_byte_written = self + .find_next_line(last_byte_written) + .unwrap_or(last_byte_written); + } + last_comment = Some(comment); + } + } + + // 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 itself 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 [T], + 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 [T], + 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 [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: + 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 { + String::new() + }; + 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 config.is_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 !config.is_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 and hex literals according to the configuration. + fn write_hex_literal(&mut self, lit: &HexLiteral) -> Result<()> { + let HexLiteral { loc, hex } = lit; + match self.config.hex_underscore { + HexUnderscore::Remove => self.write_quoted_str(*loc, Some("hex"), hex), + HexUnderscore::Preserve => { + let quote = &self.source[loc.start()..loc.end()].trim_start_matches("hex"); + // source is always quoted so we remove the quotes first so we can adhere to the + // configured quoting style + let hex = "e[1..quote.len() - 1]; + self.write_quoted_str(*loc, Some("hex"), hex) + } + HexUnderscore::Bytes => { + // split all bytes + let hex = hex + .chars() + .chunks(2) + .into_iter() + .map(|chunk| chunk.collect::()) + .collect::>() + .join("_"); + self.write_quoted_str(*loc, Some("hex"), &hex) + } + } + } + + /// 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 mut returns = fmt.items_to_chunks( + returns_end, + func.returns + .iter_mut() + .filter_map(|(loc, param)| param.as_mut().map(|param| (*loc, param))), + )?; + + // there's an issue with function return value that would lead to indent issues because those can be formatted with line breaks + for function_chunk in returns + .iter_mut() + .filter(|chunk| chunk.content.starts_with("function(")) + { + // this will bypass the recursive indent that was applied when the function + // content was formatted in the chunk + function_chunk.content = function_chunk + .content + .split('\n') + .map(|s| s.trim_start()) + .collect::>() + .join("\n"); + } + + 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(()) + } + + /// Sorts grouped import statement alphabetically. + fn sort_imports(&self, source_unit: &mut SourceUnit) { + // first we need to find the grouped import statements + // A group is defined as a set of import statements that are separated by a blank line + let mut import_groups = Vec::new(); + let mut current_group = Vec::new(); + let mut source_unit_parts = source_unit.0.iter().enumerate().peekable(); + while let Some((i, part)) = source_unit_parts.next() { + if let SourceUnitPart::ImportDirective(_) = part { + current_group.push(i); + let current_loc = part.loc(); + if let Some((_, next_part)) = source_unit_parts.peek() { + let next_loc = next_part.loc(); + // import statements are followed by a new line, so if there are more than one + // we have a group + if self.blank_lines(current_loc.end(), next_loc.start()) > 1 { + import_groups.push(std::mem::take(&mut current_group)); + } + } + } else if !current_group.is_empty() { + import_groups.push(std::mem::take(&mut current_group)); + } + } + + if !current_group.is_empty() { + import_groups.push(current_group); + } + + if import_groups.is_empty() { + // nothing to sort + return; + } + + // order all groups alphabetically + for group in import_groups.iter() { + // SAFETY: group is not empty + let first = group[0]; + let last = group.last().copied().expect("group is not empty"); + let import_directives = &mut source_unit.0[first..=last]; + + // sort rename style imports alphabetically based on the actual import and not the + // rename + for source_unit_part in import_directives.iter_mut() { + if let SourceUnitPart::ImportDirective(Import::Rename(_, renames, _)) = + source_unit_part + { + renames.sort_by_cached_key(|(og_ident, _)| og_ident.name.clone()); + } + } + + import_directives.sort_by_cached_key(|item| match item { + SourceUnitPart::ImportDirective(import) => match import { + Import::Plain(path, _) => path.to_string(), + Import::GlobalSymbol(path, _, _) => path.to_string(), + Import::Rename(path, _, _) => path.to_string(), + }, + _ => { + unreachable!("import group contains non-import statement") + } + }); + } + } +} + +// 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<()> { + if self.config.sort_imports { + self.sort_imports(source_unit); + } + // 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(()) + } + + // 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 = "pragma", skip_all)] + fn visit_pragma(&mut self, pragma: &PragmaDirective) -> Result<()> { + match pragma { + PragmaDirective::Identifier(loc, ident, string) => { + let (ident, string) = (ident.safe_unwrap(), string.safe_unwrap()); + return_source_if_disabled!(self, *loc, ';'); + + write_chunk!( + self, + string.loc.end(), + "pragma {} {};", + &ident.name, + &string.name + )?; + } + PragmaDirective::StringLiteral(loc, ident, string) => { + return_source_if_disabled!(self, *loc, ';'); + + write_chunk!( + self, + string.loc.end(), + "pragma {} \"{}\";", + &ident.name, + &string.string + )?; + } + PragmaDirective::Version(loc, ident, versions) => { + return_source_if_disabled!(self, *loc, ';'); + + write_chunk!(self, ident.loc.end(), "pragma {}", &ident.name)?; + + for version in versions.iter() { + let loc = version.loc(); + + write_chunk!(self, loc.end(), " ")?; + + self.visit_version(version.loc(), version)?; + } + + self.write_semicolon()?; + } + } + Ok(()) + } + + #[instrument(name = "version", skip_all)] + fn visit_version(&mut self, loc: Loc, version: &VersionComparator) -> Result<()> { + return_source_if_disabled!(self, loc, ';'); + + match version { + VersionComparator::Plain { loc, version } => { + write_chunk!(self, loc.start(), loc.end(), "{}", version.iter().join("."))?; + } + VersionComparator::Operator { loc, op, version } => { + write_chunk!( + self, + loc.start(), + loc.end(), + "{op}{}", + version.iter().join(".") + )?; + } + VersionComparator::Or { loc, left, right } => { + self.visit_version(*loc, left)?; + write_chunk!(self, loc.start(), loc.end(), " || ")?; + self.visit_version(*loc, right)?; + } + VersionComparator::Range { loc, from, to } => { + write_chunk!( + self, + loc.start(), + loc.end(), + "{} - {}", + from.iter().join("."), + to.iter().join("."), + )?; + } + } + + 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); + if enumeration.values.is_empty() { + self.write_chunk(&name)?; + self.write_empty_brackets()?; + } else { + name.content.push('{'); + self.write_chunk(&name)?; + + self.indented(1, |fmt| { + 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)?; + writeln!(fmt.buf())?; + Ok(()) + })?; + write_chunk!(self, "}}")?; + } + + 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 = "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 = "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 = "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 { + Address::from_str(val).expect("").to_string() + } 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 val in vals { + self.write_hex_literal(val)?; + } + } + 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)?; + } + Expression::New(_, expr) => { + write_chunk!(self, "new ")?; + self.visit_expr(expr.loc(), expr)?; + } + _ => 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_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 = "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 = "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 = "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 = "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 = "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 = "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 = "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 = "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 = "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 = "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) => { + // here we need to find out if this attribute belongs to the constructor because the + // modifier need to include the trailing parenthesis + // This is very ambiguous because the modifier can either by an inherited contract + // or a modifier here: e.g.: This is valid constructor: + // `constructor() public Ownable() OnlyOwner {}` + let is_constructor = self.context.is_constructor_function(); + // we can't make any decisions here regarding trailing `()` because we'd need to + // find out if the `base` is a solidity modifier or an + // interface/contract therefor we we its raw content. + + // we can however check if the contract `is` the `base`, this however also does + // not cover all cases + 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 if is_constructor { + // This is ambiguous because the modifier can either by an inherited + // contract modifiers with empty parenthesis are + // valid, but not required so we make the assumption + // here that modifiers are lowercase + let mut base_or_modifier = + self.visit_to_chunk(loc.start(), Some(loc.end()), base)?; + let is_lowercase = base_or_modifier + .content + .chars() + .next() + .map_or(false, |c| c.is_lowercase()); + if is_lowercase && base_or_modifier.content.ends_with("()") { + base_or_modifier + .content + .truncate(base_or_modifier.content.len() - 2); + } + + self.write_chunk(&base_or_modifier)?; + } 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 = "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 = "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() { + // This is ambiguous because the modifier can either by an inherited contract or a + // modifier + 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 = "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 = "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 = "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 = "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 = "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_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_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_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_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(()) + } + + #[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 = "parser_error", skip_all)] + fn visit_parser_error(&mut self, loc: Loc) -> Result<()> { + Err(FormatterError::InvalidParsedItem(loc)) + } +} + +/// 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) + } +} diff --git a/fmt/src/helpers.rs b/fmt/src/helpers.rs new file mode 100644 index 000000000..5b7cdcb5a --- /dev/null +++ b/fmt/src/helpers.rs @@ -0,0 +1,141 @@ +// SPDX-License-Identifier: Apache-2.0 + +use crate::{ + config::FormatterConfig, + inline_config::{InlineConfig, InvalidInlineConfigItem}, + Comments, Formatter, 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. + 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, Vec> { + 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_to( + 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 format(src: &str) -> Result { + let parsed = parse(src).map_err(|err| { + debug!(?err, "Parse error"); + FormatterError::Fmt(std::fmt::Error) + })?; + + let mut output = String::new(); + format_to(&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<()> { + if diagnostics.is_empty() { + return Ok(()); + } + + 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(), + } +} + +#[cfg(test)] +mod tests { + use super::*; + + // + #[test] + fn test_interface_format() { + let s = "interface I {\n function increment() external;\n function number() external view returns (uint256);\n function setNumber(uint256 newNumber) external;\n}"; + let _formatted = format(s).unwrap(); + } +} diff --git a/fmt/src/inline_config.rs b/fmt/src/inline_config.rs new file mode 100644 index 000000000..0e1c7b0a5 --- /dev/null +++ b/fmt/src/inline_config.rs @@ -0,0 +1,179 @@ +// 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(Clone, Copy, Debug)] +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" => Self::DisableNextItem, + "disable-line" => Self::DisableLine, + "disable-next-line" => Self::DisableNextLine, + "disable-start" => Self::DisableStart, + "disable-end" => Self::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`](crate::Comments::parse_inline_config_items) for +/// details. +#[derive(Debug, Default)] +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/fmt/src/lib.rs b/fmt/src/lib.rs new file mode 100644 index 000000000..feb61cdab --- /dev/null +++ b/fmt/src/lib.rs @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: Apache-2.0 + +#![doc = include_str!("../README.md")] +#![cfg_attr(not(test), warn(unused_crate_dependencies))] +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] + +#[macro_use] +extern crate tracing; + +mod buffer; +pub mod chunk; +mod comments; +mod config; +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 config::FormatterConfig; +pub use formatter::{Formatter, FormatterError}; +pub use helpers::{ + format, format_to, offset_to_line_column, parse, print_diagnostics_report, Parsed, +}; +pub use inline_config::InlineConfig; +pub use visit::{Visitable, Visitor}; diff --git a/fmt/src/macros.rs b/fmt/src/macros.rs new file mode 100644 index 000000000..c7ca59efd --- /dev/null +++ b/fmt/src/macros.rs @@ -0,0 +1,127 @@ +// 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/fmt/src/solang_ext/ast_eq.rs b/fmt/src/solang_ext/ast_eq.rs new file mode 100644 index 000000000..d7e3a134c --- /dev/null +++ b/fmt/src/solang_ext/ast_eq.rs @@ -0,0 +1,730 @@ +// SPDX-License-Identifier: Apache-2.0 + +use alloy_primitives::{Address, 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::ZERO; + } + 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 (Address::from_str(self), Address::from_str(other)) { + (Ok(left), Ok(right)) => left == 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 { + Self::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, Self::If(..)) { + stmt2.clone() + } else { + wrap_in_box!(stmt2, *loc) + } + }), + ) + }; + let (stmt1, stmt2) = wrap_if(stmt1, stmt2); + let left = (loc, expr, &stmt1, &stmt2); + if let Self::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 + } + } + Self::While(loc, expr, stmt1) => { + let stmt1 = wrap_in_box!(stmt1, *loc); + let left = (loc, expr, &stmt1); + if let Self::While(loc, expr, stmt1) = other { + let stmt1 = wrap_in_box!(stmt1, *loc); + let right = (loc, expr, &stmt1); + left.ast_eq(&right) + } else { + false + } + } + Self::DoWhile(loc, stmt1, expr) => { + let stmt1 = wrap_in_box!(stmt1, *loc); + let left = (loc, &stmt1, expr); + if let Self::DoWhile(loc, stmt1, expr) = other { + let stmt1 = wrap_in_box!(stmt1, *loc); + let right = (loc, &stmt1, expr); + left.ast_eq(&right) + } else { + false + } + } + Self::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 Self::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 + } + } + Self::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 Self::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, clause), + 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 PragmaDirective { + _ + Identifier(loc, ident, string), + StringLiteral(loc, ident, string), + Version(loc, ident, versions), + _ +}} +derive_ast_eq! { enum VersionComparator { + _ + _ + Plain{ loc, version }, + Operator{ loc, op, version }, + Or{ loc, left, right }, + Range{ loc, from, to }, +}} +derive_ast_eq! { enum VersionOp { + Exact, + Greater, + GreaterEq, + Less, + LessEq, + Tilde, + Caret, + Wildcard, + _ + _ +}} +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(def), + 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/fmt/src/solang_ext/loc.rs b/fmt/src/solang_ext/loc.rs new file mode 100644 index 000000000..7d100029d --- /dev/null +++ b/fmt/src/solang_ext/loc.rs @@ -0,0 +1,168 @@ +// 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(), + } + } +} + +impl CodeLocationExt for pt::VersionComparator { + fn loc(&self) -> pt::Loc { + match self { + Self::Range { loc, .. } => *loc, + Self::Or { loc, .. } => *loc, + Self::Plain { loc, .. } => *loc, + Self::Operator { loc, .. } => *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/fmt/src/solang_ext/mod.rs b/fmt/src/solang_ext/mod.rs new file mode 100644 index 000000000..5ca238473 --- /dev/null +++ b/fmt/src/solang_ext/mod.rs @@ -0,0 +1,30 @@ +// 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/fmt/src/solang_ext/safe_unwrap.rs b/fmt/src/solang_ext/safe_unwrap.rs new file mode 100644 index 000000000..f3ec77f8c --- /dev/null +++ b/fmt/src/solang_ext/safe_unwrap.rs @@ -0,0 +1,54 @@ +// 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/fmt/src/string.rs b/fmt/src/string.rs new file mode 100644 index 000000000..33413b808 --- /dev/null +++ b/fmt/src/string.rs @@ -0,0 +1,186 @@ +// SPDX-License-Identifier: Apache-2.0 + +//! Helpers 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(Clone, Copy, Debug, Default, PartialEq, Eq)] +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 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 { + /// Returns an iterator of characters, indices and their quoted string state. + fn quote_state_char_indices(&self) -> QuoteStateCharIndices<'_>; + + /// Returns 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 similar_asserts::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/fmt/src/visit.rs b/fmt/src/visit.rs new file mode 100644 index 000000000..d9e1f8bd4 --- /dev/null +++ b/fmt/src/visit.rs @@ -0,0 +1,654 @@ +// SPDX-License-Identifier: Apache-2.0 + +//! Visitor helpers to traverse the [solang Solidity Parse Tree](solang_parser::pt). + +use solang_parser::pt::{PragmaDirective, VersionComparator}; + +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, pragma: &PragmaDirective) -> Result<(), Self::Error> { + let loc = match pragma { + PragmaDirective::Identifier(loc, ..) => loc, + PragmaDirective::StringLiteral(loc, ..) => loc, + PragmaDirective::Version(loc, ..) => loc, + }; + + 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_version(&mut self, loc: Loc, _version: &VersionComparator) -> 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 { + Self::ContractDefinition(contract) => v.visit_contract(contract), + Self::PragmaDirective(pragma) => v.visit_pragma(pragma), + Self::ImportDirective(import) => import.visit(v), + Self::EnumDefinition(enumeration) => v.visit_enum(enumeration), + Self::StructDefinition(structure) => v.visit_struct(structure), + Self::EventDefinition(event) => v.visit_event(event), + Self::ErrorDefinition(error) => v.visit_error(error), + Self::FunctionDefinition(function) => v.visit_function(function), + Self::VariableDefinition(variable) => v.visit_var_definition(variable), + Self::TypeDefinition(def) => v.visit_type_definition(def), + Self::StraySemicolon(_) => v.visit_stray_semicolon(), + Self::Using(using) => v.visit_using(using), + Self::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 { + Self::Plain(import, loc) => v.visit_import_plain(*loc, import), + Self::GlobalSymbol(global, import_as, loc) => { + v.visit_import_global(*loc, global, import_as) + } + Self::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 { + Self::StructDefinition(structure) => v.visit_struct(structure), + Self::EventDefinition(event) => v.visit_event(event), + Self::ErrorDefinition(error) => v.visit_error(error), + Self::EnumDefinition(enumeration) => v.visit_enum(enumeration), + Self::VariableDefinition(variable) => v.visit_var_definition(variable), + Self::FunctionDefinition(function) => v.visit_function(function), + Self::TypeDefinition(def) => v.visit_type_definition(def), + Self::StraySemicolon(_) => v.visit_stray_semicolon(), + Self::Using(using) => v.visit_using(using), + Self::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 { + Self::Block { + loc, + unchecked, + statements, + } => v.visit_block(*loc, *unchecked, statements), + Self::Assembly { + loc, + dialect, + block, + flags, + } => v.visit_assembly(*loc, dialect, block, flags), + Self::Args(loc, args) => v.visit_args(*loc, args), + Self::If(loc, cond, if_branch, else_branch) => { + v.visit_if(*loc, cond, if_branch, else_branch, true) + } + Self::While(loc, cond, body) => v.visit_while(*loc, cond, body), + Self::Expression(loc, expr) => { + v.visit_expr(*loc, expr)?; + v.visit_stray_semicolon() + } + Self::VariableDefinition(loc, declaration, expr) => { + v.visit_var_definition_stmt(*loc, declaration, expr) + } + Self::For(loc, init, cond, update, body) => v.visit_for(*loc, init, cond, update, body), + Self::DoWhile(loc, body, cond) => v.visit_do_while(*loc, body, cond), + Self::Continue(loc) => v.visit_continue(*loc, true), + Self::Break(loc) => v.visit_break(*loc, true), + Self::Return(loc, expr) => v.visit_return(*loc, expr), + Self::Revert(loc, error, args) => v.visit_revert(*loc, error, args), + Self::RevertNamedArgs(loc, error, args) => v.visit_revert_named_args(*loc, error, args), + Self::Emit(loc, event) => v.visit_emit(*loc, event), + Self::Try(loc, expr, returns, clauses) => v.visit_try(*loc, expr, returns, clauses), + Self::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 { + Self::Assign(loc, exprs, expr) => v.visit_yul_assignment(*loc, exprs, &mut Some(expr)), + Self::Block(block) => v.visit_yul_block(block.loc, block.statements.as_mut(), false), + Self::Break(loc) => v.visit_break(*loc, false), + Self::Continue(loc) => v.visit_continue(*loc, false), + Self::For(stmt) => v.visit_yul_for(stmt), + Self::FunctionCall(stmt) => v.visit_yul_function_call(stmt), + Self::FunctionDefinition(stmt) => v.visit_yul_fun_def(stmt), + Self::If(loc, expr, block) => v.visit_yul_if(*loc, expr, block), + Self::Leave(loc) => v.visit_yul_leave(*loc), + Self::Switch(stmt) => v.visit_yul_switch(stmt), + Self::VariableDeclaration(loc, idents, expr) => { + v.visit_yul_var_declaration(*loc, idents, expr) + } + Self::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/fmt/testdata/Annotation/fmt.sol b/fmt/testdata/Annotation/fmt.sol new file mode 100644 index 000000000..75bbcf2dd --- /dev/null +++ b/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/fmt/testdata/Annotation/original.sol b/fmt/testdata/Annotation/original.sol new file mode 100644 index 000000000..4551f7d1e --- /dev/null +++ b/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/fmt/testdata/ArrayExpressions/fmt.sol b/fmt/testdata/ArrayExpressions/fmt.sol new file mode 100644 index 000000000..adda7a30e --- /dev/null +++ b/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 uint256[](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/fmt/testdata/ArrayExpressions/original.sol b/fmt/testdata/ArrayExpressions/original.sol new file mode 100644 index 000000000..919cd241f --- /dev/null +++ b/fmt/testdata/ArrayExpressions/original.sol @@ -0,0 +1,47 @@ +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/fmt/testdata/BlockComments/fmt.sol b/fmt/testdata/BlockComments/fmt.sol new file mode 100644 index 000000000..1d7025f2a --- /dev/null +++ b/fmt/testdata/BlockComments/fmt.sol @@ -0,0 +1,25 @@ +contract CounterTest is Test { + /** + * @dev Initializes the contract by setting a `name` and a `symbol` to the token collection. + */ + constructor(string memory name_, string memory symbol_) { + _name = name_; + _symbol = symbol_; + } + + /** + * @dev See {IERC721-balanceOf}. + */ + function test_Increment() public { + counter.increment(); + assertEq(counter.number(), 1); + } + + /** + * @dev See {IERC165-supportsInterface}. + */ + function test_Increment() public { + counter.increment(); + assertEq(counter.number(), 1); + } +} diff --git a/fmt/testdata/BlockComments/original.sol b/fmt/testdata/BlockComments/original.sol new file mode 100644 index 000000000..b91934bf7 --- /dev/null +++ b/fmt/testdata/BlockComments/original.sol @@ -0,0 +1,26 @@ +contract CounterTest is Test { + /** + * @dev Initializes the contract by setting a `name` and a `symbol` to the token collection. + */ + + constructor(string memory name_, string memory symbol_) { + _name = name_; + _symbol = symbol_; + } + + /** + * @dev See {IERC721-balanceOf}. + */ + function test_Increment() public { + counter.increment(); + assertEq(counter.number(), 1); + } + + /** + * @dev See {IERC165-supportsInterface}. + */ function test_Increment() public { + counter.increment(); + assertEq(counter.number(), 1); +} + +} \ No newline at end of file diff --git a/fmt/testdata/BlockCommentsFunction/fmt.sol b/fmt/testdata/BlockCommentsFunction/fmt.sol new file mode 100644 index 000000000..368749bf4 --- /dev/null +++ b/fmt/testdata/BlockCommentsFunction/fmt.sol @@ -0,0 +1,20 @@ +contract A { + Counter public counter; + /** + * TODO: this fuzz use too much time to execute + * function testGetFuzz(bytes[2][] memory kvs) public { + * for (uint256 i = 0; i < kvs.length; i++) { + * bytes32 root = trie.update(kvs[i][0], kvs[i][1]); + * console.logBytes32(root); + * } + * + * for (uint256 i = 0; i < kvs.length; i++) { + * (bool exist, bytes memory value) = trie.get(kvs[i][0]); + * console.logBool(exist); + * console.logBytes(value); + * require(exist); + * require(BytesSlice.equal(value, trie.getRaw(kvs[i][0]))); + * } + * } + */ +} diff --git a/fmt/testdata/BlockCommentsFunction/original.sol b/fmt/testdata/BlockCommentsFunction/original.sol new file mode 100644 index 000000000..089f1bac4 --- /dev/null +++ b/fmt/testdata/BlockCommentsFunction/original.sol @@ -0,0 +1,20 @@ +contract A { + Counter public counter; + /** + * TODO: this fuzz use too much time to execute + function testGetFuzz(bytes[2][] memory kvs) public { + for (uint256 i = 0; i < kvs.length; i++) { + bytes32 root = trie.update(kvs[i][0], kvs[i][1]); + console.logBytes32(root); + } + + for (uint256 i = 0; i < kvs.length; i++) { + (bool exist, bytes memory value) = trie.get(kvs[i][0]); + console.logBool(exist); + console.logBytes(value); + require(exist); + require(BytesSlice.equal(value, trie.getRaw(kvs[i][0]))); + } + } + */ +} \ No newline at end of file diff --git a/fmt/testdata/ConditionalOperatorExpression/fmt.sol b/fmt/testdata/ConditionalOperatorExpression/fmt.sol new file mode 100644 index 000000000..733ae2d8c --- /dev/null +++ b/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/fmt/testdata/ConditionalOperatorExpression/original.sol b/fmt/testdata/ConditionalOperatorExpression/original.sol new file mode 100644 index 000000000..f03328873 --- /dev/null +++ b/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/fmt/testdata/ConstructorDefinition/fmt.sol b/fmt/testdata/ConstructorDefinition/fmt.sol new file mode 100644 index 000000000..dd62a0cf4 --- /dev/null +++ b/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/fmt/testdata/ConstructorDefinition/original.sol b/fmt/testdata/ConstructorDefinition/original.sol new file mode 100644 index 000000000..f69205196 --- /dev/null +++ b/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/fmt/testdata/ConstructorModifierStyle/fmt.sol b/fmt/testdata/ConstructorModifierStyle/fmt.sol new file mode 100644 index 000000000..88694860a --- /dev/null +++ b/fmt/testdata/ConstructorModifierStyle/fmt.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.5.2; + +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; +import {ERC1155} from "solmate/tokens/ERC1155.sol"; + +import {IAchievements} from "./interfaces/IAchievements.sol"; +import {SoulBound1155} from "./abstracts/SoulBound1155.sol"; + +contract Achievements is IAchievements, SoulBound1155, Ownable { + constructor(address owner) Ownable() ERC1155() {} +} diff --git a/fmt/testdata/ConstructorModifierStyle/original.sol b/fmt/testdata/ConstructorModifierStyle/original.sol new file mode 100644 index 000000000..88694860a --- /dev/null +++ b/fmt/testdata/ConstructorModifierStyle/original.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.5.2; + +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; +import {ERC1155} from "solmate/tokens/ERC1155.sol"; + +import {IAchievements} from "./interfaces/IAchievements.sol"; +import {SoulBound1155} from "./abstracts/SoulBound1155.sol"; + +contract Achievements is IAchievements, SoulBound1155, Ownable { + constructor(address owner) Ownable() ERC1155() {} +} diff --git a/fmt/testdata/ContractDefinition/bracket-spacing.fmt.sol b/fmt/testdata/ContractDefinition/bracket-spacing.fmt.sol new file mode 100644 index 000000000..dca4e325d --- /dev/null +++ b/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/fmt/testdata/ContractDefinition/contract-new-lines.fmt.sol b/fmt/testdata/ContractDefinition/contract-new-lines.fmt.sol new file mode 100644 index 000000000..2e9661f95 --- /dev/null +++ b/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/fmt/testdata/ContractDefinition/fmt.sol b/fmt/testdata/ContractDefinition/fmt.sol new file mode 100644 index 000000000..551e84dec --- /dev/null +++ b/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/fmt/testdata/ContractDefinition/original.sol b/fmt/testdata/ContractDefinition/original.sol new file mode 100644 index 000000000..4c671985b --- /dev/null +++ b/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/fmt/testdata/DoWhileStatement/fmt.sol b/fmt/testdata/DoWhileStatement/fmt.sol new file mode 100644 index 000000000..c3c8c71c5 --- /dev/null +++ b/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/fmt/testdata/DoWhileStatement/original.sol b/fmt/testdata/DoWhileStatement/original.sol new file mode 100644 index 000000000..51063c878 --- /dev/null +++ b/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/fmt/testdata/DocComments/fmt.sol b/fmt/testdata/DocComments/fmt.sol new file mode 100644 index 000000000..4248f0fe5 --- /dev/null +++ b/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 docLinePostfixOverflow() 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 malformedIndentOverflow() external {} +} + +/** + * contract A { + * function foo() public { + * // does nothing. + * } + * } + */ +function freeFloatingMultilineIndent() {} diff --git a/fmt/testdata/DocComments/original.sol b/fmt/testdata/DocComments/original.sol new file mode 100644 index 000000000..28f654b57 --- /dev/null +++ b/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 docLinePostfixOverflow() 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 malformedIndentOverflow() external {} +} + +/** +contract A { + function foo() public { + // does nothing. + } +} +*/ +function freeFloatingMultilineIndent() {} diff --git a/fmt/testdata/DocComments/wrap-comments.fmt.sol b/fmt/testdata/DocComments/wrap-comments.fmt.sol new file mode 100644 index 000000000..c3c7fe00c --- /dev/null +++ b/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 docLinePostfixOverflow() + 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 malformedIndentOverflow() + external + {} +} + +/** + * contract A { + * function foo() public { + * // does nothing. + * } + * } + */ +function freeFloatingMultilineIndent() {} diff --git a/fmt/testdata/EmitStatement/fmt.sol b/fmt/testdata/EmitStatement/fmt.sol new file mode 100644 index 000000000..0fac66b9b --- /dev/null +++ b/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/fmt/testdata/EmitStatement/original.sol b/fmt/testdata/EmitStatement/original.sol new file mode 100644 index 000000000..661abb782 --- /dev/null +++ b/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/fmt/testdata/EnumDefinition/bracket-spacing.fmt.sol b/fmt/testdata/EnumDefinition/bracket-spacing.fmt.sol new file mode 100644 index 000000000..a4ae0f019 --- /dev/null +++ b/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/fmt/testdata/EnumDefinition/fmt.sol b/fmt/testdata/EnumDefinition/fmt.sol new file mode 100644 index 000000000..437268aff --- /dev/null +++ b/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/fmt/testdata/EnumDefinition/original.sol b/fmt/testdata/EnumDefinition/original.sol new file mode 100644 index 000000000..69aadf884 --- /dev/null +++ b/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/fmt/testdata/EnumVariants/fmt.sol b/fmt/testdata/EnumVariants/fmt.sol new file mode 100644 index 000000000..b33b88469 --- /dev/null +++ b/fmt/testdata/EnumVariants/fmt.sol @@ -0,0 +1,19 @@ +interface I { + enum Empty {} + + /// A modification applied to either `msg.sender` or `tx.origin`. Returned by `readCallers`. + enum CallerMode { + /// No caller modification is currently active. + None + } + + /// A modification applied to either `msg.sender` or `tx.origin`. Returned by `readCallers`. + enum CallerMode2 { + /// No caller modification is currently active. + None, + /// No caller modification is currently active2. + Some + } + + function bar() public {} +} diff --git a/fmt/testdata/EnumVariants/original.sol b/fmt/testdata/EnumVariants/original.sol new file mode 100644 index 000000000..8e146ae0f --- /dev/null +++ b/fmt/testdata/EnumVariants/original.sol @@ -0,0 +1,23 @@ +interface I { + enum Empty { + + } + + /// A modification applied to either `msg.sender` or `tx.origin`. Returned by `readCallers`. + enum CallerMode + {/// No caller modification is currently active. + None + } + + /// A modification applied to either `msg.sender` or `tx.origin`. Returned by `readCallers`. + enum CallerMode2 + {/// No caller modification is currently active. + None,/// No caller modification is currently active2. + + Some + } + + function bar() public { + + } +} \ No newline at end of file diff --git a/fmt/testdata/ErrorDefinition/fmt.sol b/fmt/testdata/ErrorDefinition/fmt.sol new file mode 100644 index 000000000..b94bbe45d --- /dev/null +++ b/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/fmt/testdata/ErrorDefinition/original.sol b/fmt/testdata/ErrorDefinition/original.sol new file mode 100644 index 000000000..f9524c22a --- /dev/null +++ b/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/fmt/testdata/EventDefinition/fmt.sol b/fmt/testdata/EventDefinition/fmt.sol new file mode 100644 index 000000000..11d3d8256 --- /dev/null +++ b/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/fmt/testdata/EventDefinition/original.sol b/fmt/testdata/EventDefinition/original.sol new file mode 100644 index 000000000..d2a615162 --- /dev/null +++ b/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/fmt/testdata/ForStatement/fmt.sol b/fmt/testdata/ForStatement/fmt.sol new file mode 100644 index 000000000..a1bb4b2e6 --- /dev/null +++ b/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/fmt/testdata/ForStatement/original.sol b/fmt/testdata/ForStatement/original.sol new file mode 100644 index 000000000..e98288dd1 --- /dev/null +++ b/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/fmt/testdata/FunctionCall/bracket-spacing.fmt.sol b/fmt/testdata/FunctionCall/bracket-spacing.fmt.sol new file mode 100644 index 000000000..2bfe9798c --- /dev/null +++ b/fmt/testdata/FunctionCall/bracket-spacing.fmt.sol @@ -0,0 +1,42 @@ +// 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; + MyContract c = new MyContract(address(0), hex"beef"); +} + +function b() { + a({ foo: 5 }); +} + +contract MyContract { + constructor(address arg, bytes memory data) { } +} diff --git a/fmt/testdata/FunctionCall/fmt.sol b/fmt/testdata/FunctionCall/fmt.sol new file mode 100644 index 000000000..ff22f63aa --- /dev/null +++ b/fmt/testdata/FunctionCall/fmt.sol @@ -0,0 +1,41 @@ +// 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; + MyContract c = new MyContract(address(0), hex"beef"); +} + +function b() { + a({foo: 5}); +} + +contract MyContract { + constructor(address arg, bytes memory data) {} +} diff --git a/fmt/testdata/FunctionCall/original.sol b/fmt/testdata/FunctionCall/original.sol new file mode 100644 index 000000000..ea0385055 --- /dev/null +++ b/fmt/testdata/FunctionCall/original.sol @@ -0,0 +1,34 @@ +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; + MyContract c = new MyContract(address( 0),hex"beef"); +} + +function b() { + a( {foo: 5} ); +} + +contract MyContract { + constructor(address arg, bytes memory data) {} +} diff --git a/fmt/testdata/FunctionCallArgsStatement/bracket-spacing.fmt.sol b/fmt/testdata/FunctionCallArgsStatement/bracket-spacing.fmt.sol new file mode 100644 index 000000000..93e5eb1a2 --- /dev/null +++ b/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/fmt/testdata/FunctionCallArgsStatement/fmt.sol b/fmt/testdata/FunctionCallArgsStatement/fmt.sol new file mode 100644 index 000000000..5a5cc5f63 --- /dev/null +++ b/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/fmt/testdata/FunctionCallArgsStatement/original.sol b/fmt/testdata/FunctionCallArgsStatement/original.sol new file mode 100644 index 000000000..b2cfaa2f2 --- /dev/null +++ b/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/fmt/testdata/FunctionDefinition/all.fmt.sol b/fmt/testdata/FunctionDefinition/all.fmt.sol new file mode 100644 index 000000000..6d9088067 --- /dev/null +++ b/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/fmt/testdata/FunctionDefinition/fmt.sol b/fmt/testdata/FunctionDefinition/fmt.sol new file mode 100644 index 000000000..9e34a8bea --- /dev/null +++ b/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/fmt/testdata/FunctionDefinition/original.sol b/fmt/testdata/FunctionDefinition/original.sol new file mode 100644 index 000000000..97db649d5 --- /dev/null +++ b/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/fmt/testdata/FunctionDefinition/override-spacing.fmt.sol b/fmt/testdata/FunctionDefinition/override-spacing.fmt.sol new file mode 100644 index 000000000..516e5c2fd --- /dev/null +++ b/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/fmt/testdata/FunctionDefinition/params-first.fmt.sol b/fmt/testdata/FunctionDefinition/params-first.fmt.sol new file mode 100644 index 000000000..50f539c32 --- /dev/null +++ b/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/fmt/testdata/FunctionDefinitionWithFunctionReturns/fmt.sol b/fmt/testdata/FunctionDefinitionWithFunctionReturns/fmt.sol new file mode 100644 index 000000000..556db3698 --- /dev/null +++ b/fmt/testdata/FunctionDefinitionWithFunctionReturns/fmt.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.17; + +contract ReturnFnFormat { + function returnsFunction() + internal + pure + returns ( + function() + internal pure returns (uint256) + ) + {} +} diff --git a/fmt/testdata/FunctionDefinitionWithFunctionReturns/original.sol b/fmt/testdata/FunctionDefinitionWithFunctionReturns/original.sol new file mode 100644 index 000000000..79008d44a --- /dev/null +++ b/fmt/testdata/FunctionDefinitionWithFunctionReturns/original.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.17; + +contract ReturnFnFormat { + function returnsFunction() + internal + pure + returns ( + function() + internal pure returns (uint256) + ) + {} +} \ No newline at end of file diff --git a/fmt/testdata/FunctionType/fmt.sol b/fmt/testdata/FunctionType/fmt.sol new file mode 100644 index 000000000..39053d816 --- /dev/null +++ b/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 uint256[](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 uint256[](length); + for (uint256 i = 0; i < r.length; i++) { + r[i] = i; + } + } +} diff --git a/fmt/testdata/FunctionType/original.sol b/fmt/testdata/FunctionType/original.sol new file mode 100644 index 000000000..27b402d85 --- /dev/null +++ b/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 uint256[](length ); + for (uint i = 0; i < r.length; i++) { + r[i] = i; + } + } +} diff --git a/fmt/testdata/HexUnderscore/bytes.fmt.sol b/fmt/testdata/HexUnderscore/bytes.fmt.sol new file mode 100644 index 000000000..b3be2a865 --- /dev/null +++ b/fmt/testdata/HexUnderscore/bytes.fmt.sol @@ -0,0 +1,10 @@ +// config: hex_underscore = "bytes" +contract HexLiteral { + function test() external { + hex"01_23_00_00"; + hex"01_23_00_00"; + hex"01_23_00_00"; + hex""; + hex"60_01_60_02_53"; + } +} diff --git a/fmt/testdata/HexUnderscore/fmt.sol b/fmt/testdata/HexUnderscore/fmt.sol new file mode 100644 index 000000000..0c8710a92 --- /dev/null +++ b/fmt/testdata/HexUnderscore/fmt.sol @@ -0,0 +1,9 @@ +contract HexLiteral { + function test() external { + hex"01230000"; + hex"01230000"; + hex"01230000"; + hex""; + hex"6001600253"; + } +} diff --git a/fmt/testdata/HexUnderscore/original.sol b/fmt/testdata/HexUnderscore/original.sol new file mode 100644 index 000000000..5c2918747 --- /dev/null +++ b/fmt/testdata/HexUnderscore/original.sol @@ -0,0 +1,9 @@ +contract HexLiteral { + function test() external { + hex"0123_0000"; + hex"01230000"; + hex"0123_00_00"; + hex""; + hex"6001_6002_53"; + } +} \ No newline at end of file diff --git a/fmt/testdata/HexUnderscore/preserve.fmt.sol b/fmt/testdata/HexUnderscore/preserve.fmt.sol new file mode 100644 index 000000000..0f5db52e3 --- /dev/null +++ b/fmt/testdata/HexUnderscore/preserve.fmt.sol @@ -0,0 +1,10 @@ +// config: hex_underscore = "preserve" +contract HexLiteral { + function test() external { + hex"0123_0000"; + hex"01230000"; + hex"0123_00_00"; + hex""; + hex"6001_6002_53"; + } +} diff --git a/fmt/testdata/HexUnderscore/remove.fmt.sol b/fmt/testdata/HexUnderscore/remove.fmt.sol new file mode 100644 index 000000000..39aae1465 --- /dev/null +++ b/fmt/testdata/HexUnderscore/remove.fmt.sol @@ -0,0 +1,10 @@ +// config: hex_underscore = "remove" +contract HexLiteral { + function test() external { + hex"01230000"; + hex"01230000"; + hex"01230000"; + hex""; + hex"6001600253"; + } +} diff --git a/fmt/testdata/IfStatement/block-multi.fmt.sol b/fmt/testdata/IfStatement/block-multi.fmt.sol new file mode 100644 index 000000000..dcd8bb83e --- /dev/null +++ b/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/fmt/testdata/IfStatement/block-single.fmt.sol b/fmt/testdata/IfStatement/block-single.fmt.sol new file mode 100644 index 000000000..ba2b9998b --- /dev/null +++ b/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/fmt/testdata/IfStatement/fmt.sol b/fmt/testdata/IfStatement/fmt.sol new file mode 100644 index 000000000..cb2f8874f --- /dev/null +++ b/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/fmt/testdata/IfStatement/original.sol b/fmt/testdata/IfStatement/original.sol new file mode 100644 index 000000000..b36829bbb --- /dev/null +++ b/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/fmt/testdata/IfStatement2/fmt.sol b/fmt/testdata/IfStatement2/fmt.sol new file mode 100644 index 000000000..10ae43601 --- /dev/null +++ b/fmt/testdata/IfStatement2/fmt.sol @@ -0,0 +1,7 @@ +contract IfStatement { + function test() external { + bool anotherLongCondition; + + if (condition && ((condition || anotherLongCondition))) execute(); + } +} diff --git a/fmt/testdata/IfStatement2/original.sol b/fmt/testdata/IfStatement2/original.sol new file mode 100644 index 000000000..df020c04b --- /dev/null +++ b/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/fmt/testdata/ImportDirective/bracket-spacing.fmt.sol b/fmt/testdata/ImportDirective/bracket-spacing.fmt.sol new file mode 100644 index 000000000..1db94929a --- /dev/null +++ b/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/fmt/testdata/ImportDirective/fmt.sol b/fmt/testdata/ImportDirective/fmt.sol new file mode 100644 index 000000000..4915b8ab2 --- /dev/null +++ b/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/fmt/testdata/ImportDirective/original.sol b/fmt/testdata/ImportDirective/original.sol new file mode 100644 index 000000000..0e18e10c1 --- /dev/null +++ b/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/fmt/testdata/ImportDirective/preserve-quote.fmt.sol b/fmt/testdata/ImportDirective/preserve-quote.fmt.sol new file mode 100644 index 000000000..d1bf9852c --- /dev/null +++ b/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/fmt/testdata/ImportDirective/single-quote.fmt.sol b/fmt/testdata/ImportDirective/single-quote.fmt.sol new file mode 100644 index 000000000..10449e079 --- /dev/null +++ b/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/fmt/testdata/InlineDisable/fmt.sol b/fmt/testdata/InlineDisable/fmt.sol new file mode 100644 index 000000000..31667c5e9 --- /dev/null +++ b/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 someVeryVeryVeryLongVariableNameForTheMultiplierForEtherValue; + + value = someVeryVeryVeryLongVariableNameForTheMultiplierForEtherValue * 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/fmt/testdata/InlineDisable/original.sol b/fmt/testdata/InlineDisable/original.sol new file mode 100644 index 000000000..83d4ee172 --- /dev/null +++ b/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 someVeryVeryVeryLongVariableNameForTheMultiplierForEtherValue; + + value = someVeryVeryVeryLongVariableNameForTheMultiplierForEtherValue * 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/fmt/testdata/IntTypes/fmt.sol b/fmt/testdata/IntTypes/fmt.sol new file mode 100644 index 000000000..b244d0281 --- /dev/null +++ b/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/fmt/testdata/IntTypes/original.sol b/fmt/testdata/IntTypes/original.sol new file mode 100644 index 000000000..2990aadbb --- /dev/null +++ b/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/fmt/testdata/IntTypes/preserve.fmt.sol b/fmt/testdata/IntTypes/preserve.fmt.sol new file mode 100644 index 000000000..70d780d89 --- /dev/null +++ b/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/fmt/testdata/IntTypes/short.fmt.sol b/fmt/testdata/IntTypes/short.fmt.sol new file mode 100644 index 000000000..45064d41e --- /dev/null +++ b/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/fmt/testdata/LiteralExpression/fmt.sol b/fmt/testdata/LiteralExpression/fmt.sol new file mode 100644 index 000000000..7fa6878e5 --- /dev/null +++ b/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/fmt/testdata/LiteralExpression/original.sol b/fmt/testdata/LiteralExpression/original.sol new file mode 100644 index 000000000..5c559531d --- /dev/null +++ b/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/fmt/testdata/LiteralExpression/preserve-quote.fmt.sol b/fmt/testdata/LiteralExpression/preserve-quote.fmt.sol new file mode 100644 index 000000000..3d9490804 --- /dev/null +++ b/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/fmt/testdata/LiteralExpression/single-quote.fmt.sol b/fmt/testdata/LiteralExpression/single-quote.fmt.sol new file mode 100644 index 000000000..cdc67a2c6 --- /dev/null +++ b/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/fmt/testdata/MappingType/fmt.sol b/fmt/testdata/MappingType/fmt.sol new file mode 100644 index 000000000..7f6297cff --- /dev/null +++ b/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/fmt/testdata/MappingType/original.sol b/fmt/testdata/MappingType/original.sol new file mode 100644 index 000000000..58ebb134d --- /dev/null +++ b/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/fmt/testdata/ModifierDefinition/fmt.sol b/fmt/testdata/ModifierDefinition/fmt.sol new file mode 100644 index 000000000..e735f25b4 --- /dev/null +++ b/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/fmt/testdata/ModifierDefinition/original.sol b/fmt/testdata/ModifierDefinition/original.sol new file mode 100644 index 000000000..f4a1c4284 --- /dev/null +++ b/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/fmt/testdata/ModifierDefinition/override-spacing.fmt.sol b/fmt/testdata/ModifierDefinition/override-spacing.fmt.sol new file mode 100644 index 000000000..68906fcdc --- /dev/null +++ b/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/fmt/testdata/NamedFunctionCallExpression/fmt.sol b/fmt/testdata/NamedFunctionCallExpression/fmt.sol new file mode 100644 index 000000000..14a24c900 --- /dev/null +++ b/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/fmt/testdata/NamedFunctionCallExpression/original.sol b/fmt/testdata/NamedFunctionCallExpression/original.sol new file mode 100644 index 000000000..8b34474a7 --- /dev/null +++ b/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/fmt/testdata/NumberLiteralUnderscore/fmt.sol b/fmt/testdata/NumberLiteralUnderscore/fmt.sol new file mode 100644 index 000000000..7c9f5740d --- /dev/null +++ b/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/fmt/testdata/NumberLiteralUnderscore/original.sol b/fmt/testdata/NumberLiteralUnderscore/original.sol new file mode 100644 index 000000000..8e88fc6d2 --- /dev/null +++ b/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/fmt/testdata/NumberLiteralUnderscore/preserve.fmt.sol b/fmt/testdata/NumberLiteralUnderscore/preserve.fmt.sol new file mode 100644 index 000000000..d87dc99d9 --- /dev/null +++ b/fmt/testdata/NumberLiteralUnderscore/preserve.fmt.sol @@ -0,0 +1,26 @@ +// config: number_underscore = "preserve" +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/fmt/testdata/NumberLiteralUnderscore/remove.fmt.sol b/fmt/testdata/NumberLiteralUnderscore/remove.fmt.sol new file mode 100644 index 000000000..cbde2e9b9 --- /dev/null +++ b/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/fmt/testdata/NumberLiteralUnderscore/thousands.fmt.sol b/fmt/testdata/NumberLiteralUnderscore/thousands.fmt.sol new file mode 100644 index 000000000..a9fc8a69a --- /dev/null +++ b/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/fmt/testdata/OperatorExpressions/fmt.sol b/fmt/testdata/OperatorExpressions/fmt.sol new file mode 100644 index 000000000..be0cec917 --- /dev/null +++ b/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/fmt/testdata/OperatorExpressions/original.sol b/fmt/testdata/OperatorExpressions/original.sol new file mode 100644 index 000000000..c3e788d0a --- /dev/null +++ b/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/fmt/testdata/PragmaDirective/fmt.sol b/fmt/testdata/PragmaDirective/fmt.sol new file mode 100644 index 000000000..645a4f490 --- /dev/null +++ b/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/fmt/testdata/PragmaDirective/original.sol b/fmt/testdata/PragmaDirective/original.sol new file mode 100644 index 000000000..535b70959 --- /dev/null +++ b/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/fmt/testdata/Repros/fmt.sol b/fmt/testdata/Repros/fmt.sol new file mode 100644 index 000000000..dc1ac24eb --- /dev/null +++ b/fmt/testdata/Repros/fmt.sol @@ -0,0 +1,19 @@ +// Repros of fmt issues + +// https://github.com/foundry-rs/foundry/issues/4403 +function errorIdentifier() { + bytes memory error = bytes(""); + if (error.length > 0) {} +} + +// https://github.com/foundry-rs/foundry/issues/7549 +function one() external { + this.other({ + data: abi.encodeCall( + this.other, + ( + "bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla" + ) + ) + }); +} diff --git a/fmt/testdata/Repros/original.sol b/fmt/testdata/Repros/original.sol new file mode 100644 index 000000000..cee4fc97a --- /dev/null +++ b/fmt/testdata/Repros/original.sol @@ -0,0 +1,19 @@ +// Repros of fmt issues + +// https://github.com/foundry-rs/foundry/issues/4403 +function errorIdentifier() { + bytes memory error = bytes(""); + if (error.length > 0) {} +} + +// https://github.com/foundry-rs/foundry/issues/7549 +function one() external { + this.other({ + data: abi.encodeCall( + this.other, + ( + "bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla" + ) + ) + }); +} diff --git a/fmt/testdata/ReturnStatement/fmt.sol b/fmt/testdata/ReturnStatement/fmt.sol new file mode 100644 index 000000000..d628d6097 --- /dev/null +++ b/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/fmt/testdata/ReturnStatement/original.sol b/fmt/testdata/ReturnStatement/original.sol new file mode 100644 index 000000000..9cfaa82d6 --- /dev/null +++ b/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/fmt/testdata/RevertNamedArgsStatement/fmt.sol b/fmt/testdata/RevertNamedArgsStatement/fmt.sol new file mode 100644 index 000000000..9ad6b042b --- /dev/null +++ b/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/fmt/testdata/RevertNamedArgsStatement/original.sol b/fmt/testdata/RevertNamedArgsStatement/original.sol new file mode 100644 index 000000000..2c9e35ba3 --- /dev/null +++ b/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/fmt/testdata/RevertStatement/fmt.sol b/fmt/testdata/RevertStatement/fmt.sol new file mode 100644 index 000000000..3a677a5ca --- /dev/null +++ b/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/fmt/testdata/RevertStatement/original.sol b/fmt/testdata/RevertStatement/original.sol new file mode 100644 index 000000000..3426fdb1e --- /dev/null +++ b/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/fmt/testdata/SimpleComments/fmt.sol b/fmt/testdata/SimpleComments/fmt.sol new file mode 100644 index 000000000..6e8d5195b --- /dev/null +++ b/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/fmt/testdata/SimpleComments/original.sol b/fmt/testdata/SimpleComments/original.sol new file mode 100644 index 000000000..d41c686b2 --- /dev/null +++ b/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/fmt/testdata/SimpleComments/wrap-comments.fmt.sol b/fmt/testdata/SimpleComments/wrap-comments.fmt.sol new file mode 100644 index 000000000..06ddc1cc1 --- /dev/null +++ b/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/fmt/testdata/SortedImports/fmt.sol b/fmt/testdata/SortedImports/fmt.sol new file mode 100644 index 000000000..f9b2c0ee2 --- /dev/null +++ b/fmt/testdata/SortedImports/fmt.sol @@ -0,0 +1,34 @@ +// config: sort_imports = true +import "SomeFile0.sol" as SomeOtherFile; +import "SomeFile1.sol" as SomeOtherFile; +import "SomeFile2.sol"; +import "SomeFile3.sol"; + +import "AnotherFile1.sol" as SomeSymbol; +import "AnotherFile2.sol" as SomeSymbol; + +import { + symbol1 as alias3, + symbol2 as alias2, + symbol3 as alias1, + symbol4 +} from "File0.sol"; +import {symbol1 as alias, symbol2} from "File2.sol"; +import {symbol1 as alias, symbol2} from "File3.sol"; +import { + symbol1 as alias1, + symbol2 as alias2, + symbol3 as alias3, + symbol4 +} from "File6.sol"; + +uint256 constant someConstant = 10; + +import {Something2, Something3} from "someFile.sol"; + +// This is a comment +import {Something2, Something3} from "someFile.sol"; + +import {symbol1 as alias, symbol2} from "File3.sol"; +// comment inside group is treated as a separator for now +import {symbol1 as alias, symbol2} from "File2.sol"; diff --git a/fmt/testdata/SortedImports/original.sol b/fmt/testdata/SortedImports/original.sol new file mode 100644 index 000000000..54b3ca3b5 --- /dev/null +++ b/fmt/testdata/SortedImports/original.sol @@ -0,0 +1,23 @@ +import "SomeFile3.sol"; +import "SomeFile2.sol"; +import "SomeFile1.sol" as SomeOtherFile; +import "SomeFile0.sol" as SomeOtherFile; + +import "AnotherFile2.sol" as SomeSymbol; +import "AnotherFile1.sol" as SomeSymbol; + +import {symbol2, symbol1 as alias} from "File3.sol"; +import {symbol2, symbol1 as alias} from "File2.sol"; +import {symbol2 as alias2, symbol1 as alias1, symbol3 as alias3, symbol4} from "File6.sol"; +import {symbol3 as alias1, symbol2 as alias2, symbol1 as alias3, symbol4} from "File0.sol"; + +uint256 constant someConstant = 10; + +import {Something3, Something2} from "someFile.sol"; + +// This is a comment +import {Something3, Something2} from "someFile.sol"; + +import {symbol2, symbol1 as alias} from "File3.sol"; +// comment inside group is treated as a separator for now +import {symbol2, symbol1 as alias} from "File2.sol"; \ No newline at end of file diff --git a/fmt/testdata/StatementBlock/bracket-spacing.fmt.sol b/fmt/testdata/StatementBlock/bracket-spacing.fmt.sol new file mode 100644 index 000000000..3e9496dd2 --- /dev/null +++ b/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/fmt/testdata/StatementBlock/fmt.sol b/fmt/testdata/StatementBlock/fmt.sol new file mode 100644 index 000000000..65aeb3a84 --- /dev/null +++ b/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/fmt/testdata/StatementBlock/original.sol b/fmt/testdata/StatementBlock/original.sol new file mode 100644 index 000000000..489b01d98 --- /dev/null +++ b/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/fmt/testdata/StructDefinition/bracket-spacing.fmt.sol b/fmt/testdata/StructDefinition/bracket-spacing.fmt.sol new file mode 100644 index 000000000..3e1c8ea4e --- /dev/null +++ b/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/fmt/testdata/StructDefinition/fmt.sol b/fmt/testdata/StructDefinition/fmt.sol new file mode 100644 index 000000000..78c5079cd --- /dev/null +++ b/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/fmt/testdata/StructDefinition/original.sol b/fmt/testdata/StructDefinition/original.sol new file mode 100644 index 000000000..a82d7a92e --- /dev/null +++ b/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/fmt/testdata/ThisExpression/fmt.sol b/fmt/testdata/ThisExpression/fmt.sol new file mode 100644 index 000000000..239a6073e --- /dev/null +++ b/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/fmt/testdata/ThisExpression/original.sol b/fmt/testdata/ThisExpression/original.sol new file mode 100644 index 000000000..2fb547c59 --- /dev/null +++ b/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/fmt/testdata/TrailingComma/fmt.sol b/fmt/testdata/TrailingComma/fmt.sol new file mode 100644 index 000000000..034ac5d33 --- /dev/null +++ b/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/fmt/testdata/TrailingComma/original.sol b/fmt/testdata/TrailingComma/original.sol new file mode 100644 index 000000000..c06460f25 --- /dev/null +++ b/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/fmt/testdata/TryStatement/fmt.sol b/fmt/testdata/TryStatement/fmt.sol new file mode 100644 index 000000000..d49687eb1 --- /dev/null +++ b/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/fmt/testdata/TryStatement/original.sol b/fmt/testdata/TryStatement/original.sol new file mode 100644 index 000000000..9fc158b20 --- /dev/null +++ b/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/fmt/testdata/TypeDefinition/fmt.sol b/fmt/testdata/TypeDefinition/fmt.sol new file mode 100644 index 000000000..63b0083cf --- /dev/null +++ b/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/fmt/testdata/TypeDefinition/original.sol b/fmt/testdata/TypeDefinition/original.sol new file mode 100644 index 000000000..f4aeac50f --- /dev/null +++ b/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/fmt/testdata/UnitExpression/fmt.sol b/fmt/testdata/UnitExpression/fmt.sol new file mode 100644 index 000000000..ceb16c86c --- /dev/null +++ b/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 someVeryVeryVeryLongVariableNameForTheMultiplierForEtherValue; + + value = someVeryVeryVeryLongVariableNameForTheMultiplierForEtherValue + * 1 /* comment1 */ ether; // comment2 + + value = 1 // comment3 + // comment4 + ether; // comment5 + } +} diff --git a/fmt/testdata/UnitExpression/original.sol b/fmt/testdata/UnitExpression/original.sol new file mode 100644 index 000000000..f85af34fe --- /dev/null +++ b/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 someVeryVeryVeryLongVariableNameForTheMultiplierForEtherValue; + + value = someVeryVeryVeryLongVariableNameForTheMultiplierForEtherValue * 1 /* comment1 */ ether; // comment2 + + value = 1 // comment3 + // comment4 + ether; // comment5 + } +} \ No newline at end of file diff --git a/fmt/testdata/UsingDirective/fmt.sol b/fmt/testdata/UsingDirective/fmt.sol new file mode 100644 index 000000000..1cfff3455 --- /dev/null +++ b/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/fmt/testdata/UsingDirective/original.sol b/fmt/testdata/UsingDirective/original.sol new file mode 100644 index 000000000..39ad6d871 --- /dev/null +++ b/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/fmt/testdata/VariableAssignment/bracket-spacing.fmt.sol b/fmt/testdata/VariableAssignment/bracket-spacing.fmt.sol new file mode 100644 index 000000000..8896668d1 --- /dev/null +++ b/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/fmt/testdata/VariableAssignment/fmt.sol b/fmt/testdata/VariableAssignment/fmt.sol new file mode 100644 index 000000000..07480d873 --- /dev/null +++ b/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/fmt/testdata/VariableAssignment/original.sol b/fmt/testdata/VariableAssignment/original.sol new file mode 100644 index 000000000..07480d873 --- /dev/null +++ b/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/fmt/testdata/VariableDefinition/fmt.sol b/fmt/testdata/VariableDefinition/fmt.sol new file mode 100644 index 000000000..9ff53c8d5 --- /dev/null +++ b/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/fmt/testdata/VariableDefinition/original.sol b/fmt/testdata/VariableDefinition/original.sol new file mode 100644 index 000000000..bd15a6384 --- /dev/null +++ b/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/fmt/testdata/VariableDefinition/override-spacing.fmt.sol b/fmt/testdata/VariableDefinition/override-spacing.fmt.sol new file mode 100644 index 000000000..5fde30038 --- /dev/null +++ b/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/fmt/testdata/WhileStatement/block-multi.fmt.sol b/fmt/testdata/WhileStatement/block-multi.fmt.sol new file mode 100644 index 000000000..cff7ac40b --- /dev/null +++ b/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/fmt/testdata/WhileStatement/block-single.fmt.sol b/fmt/testdata/WhileStatement/block-single.fmt.sol new file mode 100644 index 000000000..ee5c48b7d --- /dev/null +++ b/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/fmt/testdata/WhileStatement/fmt.sol b/fmt/testdata/WhileStatement/fmt.sol new file mode 100644 index 000000000..131c4eaed --- /dev/null +++ b/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/fmt/testdata/WhileStatement/original.sol b/fmt/testdata/WhileStatement/original.sol new file mode 100644 index 000000000..8b245b0cf --- /dev/null +++ b/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/fmt/testdata/Yul/fmt.sol b/fmt/testdata/Yul/fmt.sol new file mode 100644 index 000000000..2f37eb2f2 --- /dev/null +++ b/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/fmt/testdata/Yul/original.sol b/fmt/testdata/Yul/original.sol new file mode 100644 index 000000000..5bd47c8dd --- /dev/null +++ b/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/fmt/testdata/YulStrings/fmt.sol b/fmt/testdata/YulStrings/fmt.sol new file mode 100644 index 000000000..d05caeb26 --- /dev/null +++ b/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/fmt/testdata/YulStrings/original.sol b/fmt/testdata/YulStrings/original.sol new file mode 100644 index 000000000..fb3d5d20f --- /dev/null +++ b/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/fmt/testdata/YulStrings/preserve-quote.fmt.sol b/fmt/testdata/YulStrings/preserve-quote.fmt.sol new file mode 100644 index 000000000..dff943539 --- /dev/null +++ b/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/fmt/testdata/YulStrings/single-quote.fmt.sol b/fmt/testdata/YulStrings/single-quote.fmt.sol new file mode 100644 index 000000000..f1fc7fb8b --- /dev/null +++ b/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/fmt/tests/formatter.rs b/fmt/tests/formatter.rs new file mode 100644 index 000000000..676a396dc --- /dev/null +++ b/fmt/tests/formatter.rs @@ -0,0 +1,250 @@ +// SPDX-License-Identifier: Apache-2.0 + +use forge_fmt::{format_to, 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, test_config: TestConfig) { + 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, + test_config, + ); + } +} + +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, + test_config: TestConfig, +) { + #[derive(Eq)] + struct PrettyString(String); + + impl PartialEq for PrettyString { + fn eq(&self, other: &Self) -> 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 !test_config.skip_compare_ast_eq && !source_parsed.pt.ast_eq(&expected_parsed.pt) { + similar_asserts::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_to(&mut source_formatted, source_parsed, config.clone()).unwrap(); + assert_eof(&source_formatted); + + let source_formatted = PrettyString(source_formatted); + + similar_asserts::assert_eq!( + source_formatted, + expected, + "(formatted == expected) in {}", + filename + ); + + let mut expected_formatted = String::new(); + format_to(&mut expected_formatted, expected_parsed, config).unwrap(); + assert_eof(&expected_formatted); + + let expected_formatted = PrettyString(expected_formatted); + + similar_asserts::assert_eq!( + expected_formatted, + expected, + "(formatted == expected) in {}", + filename + ); +} + +#[derive(Clone, Copy, Default)] +struct TestConfig { + /// Whether to compare the formatted source code AST with the original AST + skip_compare_ast_eq: bool, +} + +impl TestConfig { + fn skip_compare_ast_eq() -> Self { + Self { + skip_compare_ast_eq: true, + } + } +} + +macro_rules! test_dir { + ($dir:ident $(,)?) => { + test_dir!($dir, Default::default()); + }; + ($dir:ident, $config:expr $(,)?) => { + #[allow(non_snake_case)] + #[test] + fn $dir() { + test_directory(stringify!($dir), $config); + } + }; +} + +macro_rules! test_directories { + ($($dir:ident),+ $(,)?) => {$( + test_dir!($dir); + )+}; +} + +test_directories! { + ConstructorDefinition, + ConstructorModifierStyle, + ContractDefinition, + DocComments, + EnumDefinition, + ErrorDefinition, + EventDefinition, + FunctionDefinition, + FunctionDefinitionWithFunctionReturns, + 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, + HexUnderscore, + FunctionCall, + TrailingComma, + PragmaDirective, + Annotation, + MappingType, + EmitStatement, + Repros, + BlockComments, + BlockCommentsFunction, + EnumVariants, +} + +test_dir!(SortedImports, TestConfig::skip_compare_ast_eq()); diff --git a/src/bin/languageserver/mod.rs b/src/bin/languageserver/mod.rs index eb09dfebf..df066d6b3 100644 --- a/src/bin/languageserver/mod.rs +++ b/src/bin/languageserver/mod.rs @@ -1,6 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 -use forge_fmt::{format, parse, FormatterConfig}; +use forge_fmt::{format_to, parse, FormatterConfig}; use itertools::Itertools; use num_traits::ToPrimitive; use rust_lapper::{Interval, Lapper}; @@ -2760,7 +2760,7 @@ impl LanguageServer for SolangServer { ..Default::default() }; let mut source_formatted = String::new(); - format(&mut source_formatted, source_parsed, config).map_err(|err| Error { + format_to(&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))),