Skip to content

feat(forge-lint): new LateLintPass + support code snippets #10846

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

Merged
merged 56 commits into from
Jul 16, 2025
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
f4e260c
feat: add example support for lints
0xrusowsky Jun 25, 2025
0dd20b2
bump solar
0xrusowsky Jun 25, 2025
0984def
style: clickable links
0xrusowsky Jun 25, 2025
714f023
wip: dynamic code diffs
0xrusowsky Jun 26, 2025
3f2032a
style
0xrusowsky Jun 26, 2025
7007965
Merge branch 'master' into rusowsky/lints-with-examples
0xrusowsky Jun 26, 2025
a8a2de9
Merge branch 'master' of github.com:foundry-rs/foundry into rusowsky/…
0xrusowsky Jul 2, 2025
41db76f
wip: setup `LateLintPass`
0xrusowsky Jul 7, 2025
42cecbc
wip: late lint pass
0xrusowsky Jul 8, 2025
2c4b0ad
fix: parse inline config with all lints
0xrusowsky Jul 8, 2025
a3cc0b2
ui runner: parse contract deps
0xrusowsky Jul 8, 2025
bfd30a0
Merge branch 'rusowsky/lints-with-examples' of github.com:foundry-rs/…
0xrusowsky Jul 8, 2025
0349846
dynamic assembly using HIR
0xrusowsky Jul 8, 2025
b956eb3
style: clippy
0xrusowsky Jul 9, 2025
489418c
Merge branch 'master' of github.com:foundry-rs/foundry into rusowsky/…
0xrusowsky Jul 9, 2025
d664249
style: cleanup + update blessed files
0xrusowsky Jul 9, 2025
39b37e3
Merge branch 'master' into rusowsky/lints-with-examples
0xrusowsky Jul 9, 2025
fee148d
style: cleanup
0xrusowsky Jul 9, 2025
8e10136
Merge branch 'rusowsky/lints-with-examples' of github.com:foundry-rs/…
0xrusowsky Jul 9, 2025
0ea9675
fix: peel type conversions `bytes32(c)` -> `c`
0xrusowsky Jul 9, 2025
68d287b
style: let chains 🙂
0xrusowsky Jul 9, 2025
308a1e1
fix: failing test
0xrusowsky Jul 9, 2025
7603363
Merge branch 'master' into rusowsky/lints-with-examples
0xrusowsky Jul 9, 2025
6475290
style: clippy
0xrusowsky Jul 9, 2025
6505894
Merge branch 'rusowsky/lints-with-examples' of github.com:foundry-rs/…
0xrusowsky Jul 9, 2025
9da8d84
docs: update dev docs
0xrusowsky Jul 9, 2025
e253cb3
fix: forge build integration
0xrusowsky Jul 9, 2025
e2f9fa6
fix: don't rmv artifacts on solar_pcx_from_build_opts
0xrusowsky Jul 10, 2025
1d49407
style: PR feedback
0xrusowsky Jul 10, 2025
7b3703a
docs: helper fns
0xrusowsky Jul 10, 2025
31922be
fix: rmv redundant `fn get_var_type`
0xrusowsky Jul 10, 2025
c453ae7
fix: ignore forge-std lib
0xrusowsky Jul 10, 2025
636334e
fix: ignore libs
0xrusowsky Jul 10, 2025
eed716a
Merge branch 'master' into rusowsky/lints-with-examples
grandizzy Jul 10, 2025
05b2a07
fix: don't lint all prjct source when using HIR
0xrusowsky Jul 10, 2025
f63a704
Merge branch 'rusowsky/lints-with-examples' of github.com:foundry-rs/…
0xrusowsky Jul 10, 2025
6d3ec60
fix: don't lint all project sources when using AST
0xrusowsky Jul 10, 2025
d6c729c
fix: use pcx for early lints
0xrusowsky Jul 10, 2025
5aaafb4
fix: revert manual imports ui runner + use `aux/` for deps + expand
0xrusowsky Jul 11, 2025
f388ec1
Merge branch 'master' into rusowsky/lints-with-examples
0xrusowsky Jul 11, 2025
30f01d1
fix: source filter
0xrusowsky Jul 11, 2025
fa197e1
Merge branch 'rusowsky/lints-with-examples' of github.com:foundry-rs/…
0xrusowsky Jul 11, 2025
ed4aa63
style: clippy + docs
0xrusowsky Jul 11, 2025
2559d25
fix(asm): clean args when loading them to memory
0xrusowsky Jul 15, 2025
a10f03b
Merge branch 'master' of github.com:foundry-rs/foundry into rusowsky/…
0xrusowsky Jul 15, 2025
d1b40a5
chore: temporarily disable assembly
0xrusowsky Jul 15, 2025
f7d3bb6
Merge branch 'master' into rusowsky/lints-with-examples
0xrusowsky Jul 15, 2025
2629815
style: clippy
0xrusowsky Jul 15, 2025
602b95b
Merge branch 'master' of github.com:foundry-rs/foundry into rusowsky/…
0xrusowsky Jul 15, 2025
6e00450
Merge branch 'rusowsky/lints-with-examples' of github.com:foundry-rs/…
0xrusowsky Jul 15, 2025
2df9cb3
fix: block-explorers patch
0xrusowsky Jul 15, 2025
1fcb45c
fix: remove todos
0xrusowsky Jul 15, 2025
b9e6e5a
Merge branch 'master' into rusowsky/lints-with-examples
grandizzy Jul 16, 2025
0d7971c
refactor: early.rs + late.rs
0xrusowsky Jul 16, 2025
16e8a6d
Merge branch 'master' of github.com:foundry-rs/foundry into rusowsky/…
0xrusowsky Jul 16, 2025
e14feb4
fix: failing test
0xrusowsky Jul 16, 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
62 changes: 30 additions & 32 deletions Cargo.lock

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

6 changes: 6 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -410,3 +410,9 @@ zip-extract = "=0.2.1"
## foundry
# foundry-compilers = { git = "https://github.com/foundry-rs/compilers.git", rev = "e4a9b04" }
# foundry-fork-db = { git = "https://github.com/foundry-rs/foundry-fork-db", rev = "811a61a" }

# solar
solar-ast = { git = "https://github.com/0xrusowsky/solar.git", branch = "rusowsky/flatten-with-style" }
solar-parse = { git = "https://github.com/0xrusowsky/solar.git", branch = "rusowsky/flatten-with-style" }
solar-interface = { git = "https://github.com/0xrusowsky/solar.git", branch = "rusowsky/flatten-with-style" }
solar-sema = { git = "https://github.com/0xrusowsky/solar.git", branch = "rusowsky/flatten-with-style" }
6 changes: 4 additions & 2 deletions crates/lint/src/linter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use foundry_config::lint::Severity;
use solar_ast::{visit::Visit, Expr, ItemFunction, ItemStruct, VariableDefinition};
use solar_interface::{
data_structures::Never,
diagnostics::{DiagBuilder, DiagId, MultiSpan},
diagnostics::{DiagBuilder, DiagId, DiagMsg, MultiSpan, Style},
Session, Span,
};
use std::{ops::ControlFlow, path::PathBuf};
Expand Down Expand Up @@ -32,6 +32,7 @@ pub trait Lint {
fn severity(&self) -> Severity;
fn description(&self) -> &'static str;
fn help(&self) -> &'static str;
fn example(&self) -> Vec<(DiagMsg, Style)>;
}

pub struct LintContext<'s> {
Expand All @@ -44,7 +45,7 @@ impl<'s> LintContext<'s> {
Self { sess, desc: with_description }
}

// Helper method to emit diagnostics easily from passes
/// Helper method to emit diagnostics easily from passes
pub fn emit<L: Lint>(&self, lint: &'static L, span: Span) {
let desc = if self.desc { lint.description() } else { "" };
let diag: DiagBuilder<'_, ()> = self
Expand All @@ -53,6 +54,7 @@ impl<'s> LintContext<'s> {
.diag(lint.severity().into(), desc)
.code(DiagId::new_str(lint.id()))
.span(MultiSpan::from_span(span))
.highlighted_note(lint.example())
.help(lint.help());

diag.emit();
Expand Down
8 changes: 6 additions & 2 deletions crates/lint/src/sol/gas/keccak.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use super::AsmKeccak256;
use crate::{
declare_forge_lint,
linter::EarlyLintPass,
sol::{Severity, SolLint},
};
Expand All @@ -11,7 +10,12 @@ declare_forge_lint!(
ASM_KECCAK256,
Severity::Gas,
"asm-keccak256",
"hash using inline assembly to save gas"
"inefficient hashing mechanism",
diff: {
bad: "bytes32 hash = keccak256(abi.encodePacked(a, b));",
good: "bytes32 hash;\nassembly {\n mstore(0x00, a)\n mstore(0x20, b)\n hash := keccak256(0x00, 0x40)\n}",
desc: "consider using inline assembly to reduce gas usage, like shown in this example:"
}
);

impl<'ast> EarlyLintPass<'ast> for AsmKeccak256 {
Expand Down
5 changes: 1 addition & 4 deletions crates/lint/src/sol/gas/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
use crate::{
register_lints,
sol::{EarlyLintPass, SolLint},
};
use crate::sol::{EarlyLintPass, SolLint};

mod keccak;
use keccak::ASM_KECCAK256;
Expand Down
1 change: 0 additions & 1 deletion crates/lint/src/sol/high/incorrect_shift.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use super::IncorrectShift;
use crate::{
declare_forge_lint,
linter::{EarlyLintPass, LintContext},
sol::{Severity, SolLint},
};
Expand Down
5 changes: 1 addition & 4 deletions crates/lint/src/sol/high/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
use crate::{
register_lints,
sol::{EarlyLintPass, SolLint},
};
use crate::sol::{EarlyLintPass, SolLint};

mod incorrect_shift;
use incorrect_shift::INCORRECT_SHIFT;
Expand Down
1 change: 0 additions & 1 deletion crates/lint/src/sol/info/mixed_case.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use super::{MixedCaseFunction, MixedCaseVariable};
use crate::{
declare_forge_lint,
linter::{EarlyLintPass, LintContext},
sol::{Severity, SolLint},
};
Expand Down
5 changes: 1 addition & 4 deletions crates/lint/src/sol/info/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
use crate::{
register_lints,
sol::{EarlyLintPass, SolLint},
};
use crate::sol::{EarlyLintPass, SolLint};

mod mixed_case;
use mixed_case::{MIXED_CASE_FUNCTION, MIXED_CASE_VARIABLE};
Expand Down
1 change: 0 additions & 1 deletion crates/lint/src/sol/info/pascal_case.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use super::PascalCaseStruct;
use crate::{
declare_forge_lint,
linter::{EarlyLintPass, LintContext},
sol::{Severity, SolLint},
};
Expand Down
1 change: 0 additions & 1 deletion crates/lint/src/sol/info/screaming_snake_case.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use super::ScreamingSnakeCase;
use crate::{
declare_forge_lint,
linter::{EarlyLintPass, LintContext},
sol::{Severity, SolLint},
};
Expand Down
78 changes: 73 additions & 5 deletions crates/lint/src/sol/macros.rs
Original file line number Diff line number Diff line change
@@ -1,31 +1,99 @@
#[macro_export]
macro_rules! link {
($url:expr) => {
concat!("\x1b]8;;", $url, "\x1b\\", $url, "\x1b]8;;\x1b\\")
};
}

/// Macro for defining lints and relevant metadata for the Solidity linter.
///
/// # Parameters
///
/// Each lint requires the following input fields:
/// - `$id`: Identitifier of the generated `SolLint` constant.
/// - `$id`: Identifier of the generated `SolLint` constant.
/// - `$severity`: The `Severity` of the lint (e.g. `High`, `Med`, `Low`, `Info`, `Gas`).
/// - `$str_id`: A unique identifier used to reference a specific lint during configuration.
/// - `$desc`: A short description of the lint.
/// - `$example` (optional): Can be omitted, a generic message string, or a `diff: { bad: "...",
/// good: "...", desc: "..." }` block with optional description.
///
/// # Note
/// Each lint must have a `help` section in the foundry book. This help field is auto-generated by
/// the macro. Because of that, to ensure that new lint rules have their corresponding docs in the
/// book, the existence of the lint rule's help section is validated with a unit test.
#[macro_export]
macro_rules! declare_forge_lint {
($id:ident, $severity:expr, $str_id:expr, $desc:expr) => {
// Declare the static `Lint` metadata
// Form with diff examples and an example description
(
$id:ident,
$severity:expr,
$str_id:expr,
$desc:expr,
diff: {
bad: $bad:expr,
good: $good:expr,
desc: $desc_:expr
}
) => {
pub static $id: SolLint = SolLint {
id: $str_id,
severity: $severity,
description: $desc,
help: link!(concat!("https://book.getfoundry.sh/reference/forge/forge-lint#", $str_id)),
example: Some($crate::sol::LintExample::Diff {
bad: $bad,
good: $good,
desc: Some($desc_),
}),
};
};

// Form with diff examples but without an example description
(
$id:ident,
$severity:expr,
$str_id:expr,
$desc:expr,
diff: {
bad: $bad:expr,
good: $good:expr
}
) => {
pub static $id: SolLint = SolLint {
id: $str_id,
severity: $severity,
description: $desc,
help: concat!("https://book.getfoundry.sh/reference/forge/forge-lint#", $str_id),
help: link!(concat!("https://book.getfoundry.sh/reference/forge/forge-lint#", $str_id)),
example: Some($crate::sol::LintExample::Diff { bad: $bad, good: $good, context: None }),
};
};

// Form with a general purpose example
(
$id:ident,
$severity:expr,
$str_id:expr,
$desc:expr,
$example:expr
) => {
pub static $id: SolLint = SolLint {
id: $str_id,
severity: $severity,
description: $desc,
help: link!(concat!("https://book.getfoundry.sh/reference/forge/forge-lint#", $str_id)),
example: Some($crate::sol::LintExample::General($examples)),
};
};

// Basic form without examples
($id:ident, $severity:expr, $str_id:expr, $desc:expr) => {
$crate::declare_forge_lint!($id, $severity, $str_id, $desc, "");
pub static $id: SolLint = SolLint {
id: $str_id,
severity: $severity,
description: $desc,
help: link!(concat!("https://book.getfoundry.sh/reference/forge/forge-lint#", $str_id)),
example: None,
};
};
}

Expand Down
1 change: 0 additions & 1 deletion crates/lint/src/sol/med/div_mul.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use super::DivideBeforeMultiply;
use crate::{
declare_forge_lint,
linter::{EarlyLintPass, LintContext},
sol::{Severity, SolLint},
};
Expand Down
5 changes: 1 addition & 4 deletions crates/lint/src/sol/med/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
use crate::{
register_lints,
sol::{EarlyLintPass, SolLint},
};
use crate::sol::{EarlyLintPass, SolLint};

mod div_mul;
use div_mul::DIVIDE_BEFORE_MULTIPLY;
Expand Down
Loading
Loading