Crates.io | forge-fmt |
lib.rs | forge-fmt |
version | 0.2.0 |
source | src |
created_at | 2023-09-20 08:59:05.60353 |
updated_at | 2023-09-21 12:56:30.804414 |
description | solidity formatting |
homepage | https://github.com/foundry-rs/foundry |
repository | https://github.com/foundry-rs/foundry |
max_upload_size | |
id | 977939 |
size | 464,278 |
fmt
)Solidity formatter that respects (some parts of) the Style Guide and is tested on the Prettier Solidity Plugin cases.
The formatter works in two steps:
The technique for walking the tree is based on Visitor Pattern and works as following:
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.Visitable
trait and its visit
function for each PT node type. Every visit
function should call corresponding Formatter
's callback function.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.
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:
// 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.
Source code
pragma solidity ^0.8.10 ;
contract HelloWorld {
string public message;
constructor( string memory initMessage) { message = initMessage;}
}
event Greet( string indexed name) ;
Parse Tree (simplified)
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
pragma solidity ^0.8.10;
contract HelloWorld {
string public message;
constructor(string memory initMessage) {
message = initMessage;
}
}
event Greet(string indexed name);
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 ^
The formatter can be disabled on specific lines by adding a comment // forgefmt: disable-next-line
, like this:
// 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:
uint x = 100; // forgefmt: disable-line
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:
// 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:
AstEq
trait defines the comparison rules for the AST nodesCheck out the foundry contribution guide.
Guidelines for contributing to forge fmt
:
Forge fmt does not work
Forge fmt breaks
Forge fmt unexpected behavior
Forge fmt postfix comment misplaced
Forge fmt does not inline short yul blocks
T-Bug
for bugs or T-feature
for features), add C-forge
and Cmd-forge-fmt
labels.fmt/testdata/$dir/