Skip to content

feat(fmt): rewrite formatter using Solar and a structured algorithm #10907

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 109 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
109 commits
Select commit Hold shift + click to select a range
98fc4a2
init
DaniPopes Apr 9, 2025
4fe995e
wip
DaniPopes Apr 14, 2025
8face95
wip
DaniPopes Apr 14, 2025
4e7abc1
add dbg from prettyplease
DaniPopes Apr 14, 2025
350332a
wip
DaniPopes Apr 15, 2025
4b54bf9
fixes, pragma&imports
DaniPopes Apr 15, 2025
7ed556b
feat: using, types, literals
DaniPopes Apr 16, 2025
b99ee19
feat: contract
DaniPopes Apr 16, 2025
5e21a4c
rm duplicate testdata
DaniPopes Apr 16, 2025
8dc7b98
wip: finish items; exprs, stmts
DaniPopes Apr 16, 2025
a6e1930
wips
DaniPopes Apr 17, 2025
c4706a8
feat: line_length, tab_width
DaniPopes Apr 17, 2025
fb15641
feat: contract_new_lines
DaniPopes Apr 17, 2025
e03b056
wip: single_line_statement_blocks
DaniPopes Apr 17, 2025
edcc467
tweaks
DaniPopes Apr 18, 2025
2878e87
chore: bump solar to latest main
DaniPopes Apr 19, 2025
ffe3b65
fix: test dir
DaniPopes Apr 19, 2025
b55e0e7
Merge branch 'master' into dani/fmt-solar
DaniPopes Apr 22, 2025
9762203
bump
DaniPopes Apr 22, 2025
ff3b76b
fix docs
DaniPopes Apr 22, 2025
e4e069e
fix: adjust '()' for modifier calls
DaniPopes Apr 22, 2025
3e0ece8
test: typed yul does not exist anymore
DaniPopes Apr 22, 2025
872da09
test: function parameters cannot be empty
DaniPopes Apr 22, 2025
b38a820
fix: adjust '()' for modifier calls for real
DaniPopes Apr 22, 2025
2f7400b
fix: forge fmt hates '*' imports
DaniPopes Apr 22, 2025
967b026
test: fix some invalid syntax
DaniPopes Apr 22, 2025
100f8e0
test: disable-stop does not exist
DaniPopes Apr 22, 2025
6c53373
test: fix all parse errors
DaniPopes Apr 22, 2025
da745a7
fix: literal touchups
DaniPopes Apr 23, 2025
22741de
bump
DaniPopes Apr 23, 2025
357d6ab
Merge branch 'master' into dani/fmt-solar
DaniPopes Apr 28, 2025
7b8b919
test: add snapshotting
DaniPopes Apr 28, 2025
19a5402
test: update NumberLiteralUnderscore
DaniPopes Apr 28, 2025
3558807
fixes
DaniPopes Apr 28, 2025
6b45260
struct space
DaniPopes Apr 28, 2025
1858c04
test: fix StructDefinition; empty structs are not allowed anyway
DaniPopes Apr 28, 2025
79bc94c
test: update EventDefinition; matches Solidity style guide
DaniPopes Apr 28, 2025
d00bc3f
test: update EnumDefinition; same as StructDefinition
DaniPopes Apr 28, 2025
2979b00
test: update StructDefinition 2
DaniPopes Apr 28, 2025
4f287e7
fix: comments in structs/enums
DaniPopes Apr 28, 2025
dc47584
test: update ErrorDefinition; matches Solidity style guide
DaniPopes Apr 28, 2025
cf5e826
feat: print docs with other comments; update EnumVariants
DaniPopes Apr 28, 2025
91b608a
chore: update EnumDefinition, StructDefinition
DaniPopes Apr 28, 2025
15bbbf2
chore: readd post_break
DaniPopes Apr 28, 2025
d331896
chore: rename is_hardbreak_tok
DaniPopes Apr 28, 2025
ccf4da9
Merge branch 'master' into dani/fmt-solar
DaniPopes Apr 30, 2025
2ed2a07
feat: cleanups, more impls
DaniPopes Apr 30, 2025
8b350b9
test: fix some compile errors
DaniPopes Apr 30, 2025
7009742
feat: add FormatterResult with more variants
DaniPopes May 1, 2025
ecef2bd
stuff
DaniPopes May 2, 2025
1788a63
refactor: move print_item arms into their own functions
DaniPopes May 2, 2025
4a5ff36
chore: consolidate item hardbreaks
DaniPopes May 2, 2025
adba140
Merge branch 'master' into dani/fmt-solar
DaniPopes May 5, 2025
b9f2cbe
fix: inline config parsing for block comments
DaniPopes May 5, 2025
bc4093c
wip: rm FunctionLike, wip functions
DaniPopes May 5, 2025
d6ff628
fix: clamp margin to max as well
DaniPopes May 5, 2025
1a827c5
megawip
DaniPopes May 8, 2025
1b2a400
feat: most of yul
DaniPopes May 13, 2025
88d32ec
wip: try-catch
0xrusowsky Jun 18, 2025
aa1a0a5
wip: try-catch
0xrusowsky Jun 18, 2025
e945d91
feat: print compact tuple
0xrusowsky Jun 18, 2025
0189490
wip: inline comments
0xrusowsky Jun 18, 2025
3bde150
wip: try-cactch
0xrusowsky Jun 19, 2025
bc59dc5
bump solar to have try-catch spans (#10832)
0xrusowsky Jun 22, 2025
6f86df2
wip comment fmt
0xrusowsky Jun 23, 2025
43e1fc2
wip: array expr
0xrusowsky Jun 23, 2025
9694fa1
finish arrays
0xrusowsky Jun 23, 2025
2322863
block comments
0xrusowsky Jun 23, 2025
e174b3c
doc block comments
0xrusowsky Jun 23, 2025
20d91e2
ternary operators
0xrusowsky Jun 24, 2025
361db14
wip: fn header
0xrusowsky Jun 27, 2025
e2a4055
wip: fn header
0xrusowsky Jun 27, 2025
38f6f7c
fix: doc block comments + block braces
0xrusowsky Jul 1, 2025
6d178bb
refactor state to organize helpers
0xrusowsky Jul 2, 2025
66f9c6c
fix commasep with initial trailing cmnt
0xrusowsky Jul 2, 2025
7ba3c5f
fix: improve contract fmt
0xrusowsky Jul 3, 2025
729e250
fix: block comments + contract definition
0xrusowsky Jul 3, 2025
3c518a1
fix: wrap trailing comments
0xrusowsky Jul 3, 2025
d4c9403
fix fn alingment
0xrusowsky Jul 3, 2025
b06a67b
fix: rmv unecessary check
0xrusowsky Jul 3, 2025
c156ebf
working fn headers!!!
0xrusowsky Jul 4, 2025
a6afa14
block with comments at the beginning
0xrusowsky Jul 4, 2025
16aa884
bump solar
0xrusowsky Jul 6, 2025
a7382ef
inline if statements based on user config
0xrusowsky Jul 11, 2025
3162a89
operator expr
0xrusowsky Jul 13, 2025
5c2e8d6
finish binary operators + housekeeping
0xrusowsky Jul 14, 2025
3105192
housekeeping
0xrusowsky Jul 14, 2025
09dc9ff
feat: binary expressions
0xrusowsky Jul 15, 2025
57aff04
fix: string literals
0xrusowsky Jul 15, 2025
3b0a10f
refactor comments + finish mappings
0xrusowsky Jul 16, 2025
81d1867
named functions
0xrusowsky Jul 16, 2025
c617d1b
item spacing
0xrusowsky Jul 17, 2025
fc5ae7f
more flexible comments + return stmts
0xrusowsky Jul 17, 2025
0af1bd6
var definition and flexible comments (#11093)
0xrusowsky Jul 24, 2025
16a0a81
comment wrapping
0xrusowsky Jul 26, 2025
e394ae3
sorted imports
0xrusowsky Jul 27, 2025
7cb9f70
middle cmnts for arrays and literals with subdenominations
0xrusowsky Jul 29, 2025
a499df4
revert: solar won't have spanned dataloc + subdenom
0xrusowsky Jul 30, 2025
f063eeb
Merge branch 'master' of github.com:foundry-rs/foundry into rusowsky/…
0xrusowsky Jul 30, 2025
ca5e843
refactor inline config + almost finished impl
0xrusowsky Aug 1, 2025
9338a6c
finish inline disable
0xrusowsky Aug 2, 2025
2799c32
finish inline disable
0xrusowsky Aug 3, 2025
e79dce8
wip inline disable for repros
0xrusowsky Aug 5, 2025
3c9235b
passing repros
0xrusowsky Aug 5, 2025
5507414
almost working yul
0xrusowsky Aug 5, 2025
39d25d7
Merge branch 'master' of github.com:foundry-rs/foundry into rusowsky/…
0xrusowsky Aug 6, 2025
b474d7a
chore: remove unrelated changes / merge artifacts
DaniPopes Aug 6, 2025
80af920
chore: remove unrelated changes / merge artifacts 2
DaniPopes Aug 6, 2025
b67cc70
chore: remove unrelated changes / merge artifacts 3
DaniPopes Aug 6, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
.DS_STORE
/target*
/*.sol
out/
snapshots/
out.json
Expand All @@ -11,4 +12,4 @@ CLAUDE.md
node_modules
dist
bin
_
_
17 changes: 17 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 5 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ members = [
"crates/evm/fuzz/",
"crates/evm/traces/",
"crates/fmt/",
"crates/fmt-2/",
"crates/forge/",
"crates/script-sequence/",
"crates/macros/",
Expand Down Expand Up @@ -295,10 +296,7 @@ bytes = "1.10"
walkdir = "2"
prettyplease = "0.2"
base64 = "0.22"
chrono = { version = "0.4", default-features = false, features = [
"clock",
"std",
] }
chrono = { version = "0.4", default-features = false, features = ["clock", "std"] }
axum = "0.8"
ciborium = "0.2"
color-eyre = "0.6"
Expand Down Expand Up @@ -354,6 +352,9 @@ flate2 = "1.1"

## Pinned dependencies. Enabled for the workspace in crates/test-utils.

# testing
snapbox = { version = "0.6", features = ["json", "regex", "term-svg"] }

# Use unicode-rs which has a smaller binary size than the default ICU4X as the IDNA backend, used
# by the `url` crate.
# See the `idna_adapter` README.md for more details: https://docs.rs/crate/idna_adapter/latest
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -150,4 +150,4 @@ dprint-check: ## Check formatting with dprint
echo "Installing dprint..."; \
cargo install dprint; \
fi
dprint check
dprint check
1 change: 1 addition & 0 deletions crates/common/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ alloy-transport.workspace = true
alloy-consensus = { workspace = true, features = ["k256"] }
alloy-network.workspace = true

solar-interface.workspace = true
solar-parse.workspace = true
solar-sema.workspace = true

Expand Down
192 changes: 191 additions & 1 deletion crates/common/src/comments/comment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

use solar_parse::{
ast::{CommentKind, Span},
interface::BytePos,
interface::{BytePos, Symbol},
};

#[derive(Clone, Copy, PartialEq, Debug)]
Expand All @@ -17,6 +17,21 @@ pub enum CommentStyle {
BlankLine,
}

impl CommentStyle {
pub fn is_mixed(&self) -> bool {
matches!(self, Self::Mixed)
}
pub fn is_trailing(&self) -> bool {
matches!(self, Self::Trailing)
}
pub fn is_isolated(&self) -> bool {
matches!(self, Self::Isolated)
}
pub fn is_blank(&self) -> bool {
matches!(self, Self::BlankLine)
}
}

#[derive(Clone, Debug)]
pub struct Comment {
pub lines: Vec<String>,
Expand Down Expand Up @@ -53,3 +68,178 @@ impl Comment {
}
}
}

/// A fast conservative estimate on whether the string can contain documentation links.
/// A pair of square brackets `[]` must exist in the string, but we only search for the
/// opening bracket because brackets always go in pairs in practice.
#[inline]
pub fn may_have_doc_links(s: &str) -> bool {
s.contains('[')
}

/// Makes a doc string more presentable to users.
/// Used by rustdoc and perhaps other tools, but not by rustc.
pub fn beautify_doc_string(data: Symbol, kind: CommentKind) -> Symbol {
fn get_vertical_trim(lines: &[&str]) -> Option<(usize, usize)> {
let mut i = 0;
let mut j = lines.len();
// first line of all-stars should be omitted
if lines.first().is_some_and(|line| line.chars().all(|c| c == '*')) {
i += 1;
}

// like the first, a last line of all stars should be omitted
if j > i && !lines[j - 1].is_empty() && lines[j - 1].chars().all(|c| c == '*') {
j -= 1;
}

if i != 0 || j != lines.len() { Some((i, j)) } else { None }
}

fn get_horizontal_trim(lines: &[&str], kind: CommentKind) -> Option<String> {
let mut i = usize::MAX;
let mut first = true;

// In case we have doc comments like `/**` or `/*!`, we want to remove stars if they are
// present. However, we first need to strip the empty lines so they don't get in the middle
// when we try to compute the "horizontal trim".
let lines = match kind {
CommentKind::Block => {
// Whatever happens, we skip the first line.
let mut i = lines
.first()
.map(|l| if l.trim_start().starts_with('*') { 0 } else { 1 })
.unwrap_or(0);
let mut j = lines.len();

while i < j && lines[i].trim().is_empty() {
i += 1;
}
while j > i && lines[j - 1].trim().is_empty() {
j -= 1;
}
&lines[i..j]
}
CommentKind::Line => lines,
};

for line in lines {
for (j, c) in line.chars().enumerate() {
if j > i || !"* \t".contains(c) {
return None;
}
if c == '*' {
if first {
i = j;
first = false;
} else if i != j {
return None;
}
break;
}
}
if i >= line.len() {
return None;
}
}
Some(lines.first()?[..i].to_string())
}

let data_s = data.as_str();
if data_s.contains('\n') {
let mut lines = data_s.lines().collect::<Vec<&str>>();
let mut changes = false;
let lines = if let Some((i, j)) = get_vertical_trim(&lines) {
changes = true;
// remove whitespace-only lines from the start/end of lines
&mut lines[i..j]
} else {
&mut lines
};
if let Some(horizontal) = get_horizontal_trim(lines, kind) {
changes = true;
// remove a "[ \t]*\*" block from each line, if possible
for line in lines.iter_mut() {
if let Some(tmp) = line.strip_prefix(&horizontal) {
*line = tmp;
if kind == CommentKind::Block
&& (*line == "*" || line.starts_with("* ") || line.starts_with("**"))
{
*line = &line[1..];
}
}
}
}
if changes {
return Symbol::intern(&lines.join("\n"));
}
}
data
}

#[cfg(test)]
mod tests {
use super::*;
use solar_parse::interface::enter;

#[test]
fn test_block_doc_comment_1() {
enter(|| {
let comment = "\n * Test \n ** Test\n * Test\n";
let stripped = beautify_doc_string(Symbol::intern(comment), CommentKind::Block);
assert_eq!(stripped.as_str(), " Test \n* Test\n Test");
})
}

#[test]
fn test_block_doc_comment_2() {
enter(|| {
let comment = "\n * Test\n * Test\n";
let stripped = beautify_doc_string(Symbol::intern(comment), CommentKind::Block);
assert_eq!(stripped.as_str(), " Test\n Test");
})
}

#[test]
fn test_block_doc_comment_3() {
enter(|| {
let comment = "\n let a: *i32;\n *a = 5;\n";
let stripped = beautify_doc_string(Symbol::intern(comment), CommentKind::Block);
assert_eq!(stripped.as_str(), "let a: *i32;\n*a = 5;");
})
}

#[test]
fn test_line_doc_comment() {
enter(|| {
let stripped = beautify_doc_string(Symbol::intern(" test"), CommentKind::Line);
assert_eq!(stripped.as_str(), " test");
let stripped = beautify_doc_string(Symbol::intern("! test"), CommentKind::Line);
assert_eq!(stripped.as_str(), "! test");
let stripped = beautify_doc_string(Symbol::intern("test"), CommentKind::Line);
assert_eq!(stripped.as_str(), "test");
let stripped = beautify_doc_string(Symbol::intern("!test"), CommentKind::Line);
assert_eq!(stripped.as_str(), "!test");
})
}

#[test]
fn test_doc_blocks() {
enter(|| {
let stripped = beautify_doc_string(
Symbol::intern(" # Returns\n *\n "),
CommentKind::Block,
);
assert_eq!(stripped.as_str(), " # Returns\n\n");

let stripped = beautify_doc_string(
Symbol::intern("\n * # Returns\n *\n "),
CommentKind::Block,
);
assert_eq!(stripped.as_str(), " # Returns\n\n");

let stripped = beautify_doc_string(Symbol::intern("\n * a\n "), CommentKind::Block);
assert_eq!(stripped.as_str(), " a\n");
})
}
}
Loading
Loading