Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
55 changes: 49 additions & 6 deletions crates/lint/src/sol/info/named_struct_fields.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use solar::sema::hir::{CallArgs, CallArgsKind, Expr, ExprKind, ItemId, Res};

use crate::{
linter::{LateLintPass, LintContext},
linter::{LateLintPass, LintContext, Suggestion},
sol::{Severity, SolLint, info::NamedStructFields},
};

Expand All @@ -16,16 +16,59 @@ impl<'hir> LateLintPass<'hir> for NamedStructFields {
fn check_expr(
&mut self,
ctx: &LintContext,
_hir: &'hir solar::sema::hir::Hir<'hir>,
hir: &'hir solar::sema::hir::Hir<'hir>,
expr: &'hir solar::sema::hir::Expr<'hir>,
) {
if let ExprKind::Call(
Expr { kind: ExprKind::Ident([Res::Item(ItemId::Struct(_struct_id))]), .. },
CallArgs { kind: CallArgsKind::Unnamed(_args), .. },
let ExprKind::Call(
Expr { kind: ExprKind::Ident([Res::Item(ItemId::Struct(struct_id))]), span, .. },
CallArgs { kind: CallArgsKind::Unnamed(args), .. },
_,
) = &expr.kind
{
else {
return;
};

let strukt = hir.strukt(*struct_id);
let fields = &strukt.fields;

// Basic sanity conditions for a consistent auto-fix
if fields.len() != args.len() || fields.is_empty() {
// Emit without suggestion
ctx.emit(&NAMED_STRUCT_FIELDS, expr.span);
return;
}

// Get struct name snippet and emit without suggestion if we can't get it
let Some(struct_name_snippet) = ctx.span_to_snippet(*span) else {
// Emit without suggestion if we can't get the struct name snippet
ctx.emit(&NAMED_STRUCT_FIELDS, expr.span);
return;
};

// Collect field names and corresponding argument source snippets
let mut field_assignments = Vec::new();
for (field_id, arg) in fields.iter().zip(args.iter()) {
let field = hir.variable(*field_id);

let Some((arg_snippet, field_name)) =
ctx.span_to_snippet(arg.span).zip(field.name.map(|n| n.to_string()))
else {
// Emit without suggestion if we can't get argument snippet
ctx.emit(&NAMED_STRUCT_FIELDS, expr.span);
return;
};

field_assignments.push(format!("{field_name}: {arg_snippet}"));
}

ctx.emit_with_suggestion(
&NAMED_STRUCT_FIELDS,
expr.span,
Suggestion::fix(
format!("{}({{ {} }})", struct_name_snippet, field_assignments.join(", ")),
solar::interface::diagnostics::Applicability::MachineApplicable,
)
.with_desc("consider using named fields"),
);
}
}
2 changes: 1 addition & 1 deletion crates/lint/testdata/NamedStructFields.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ note[named-struct-fields]: prefer initializing structs with named fields
--> ROOT/testdata/NamedStructFields.sol:LL:CC
|
LL | Person memory person = Person("Alice", 25, address(0));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using named fields: `Person({ name: "Alice", age: 25, wallet: address(0) })`
|
= help: https://book.getfoundry.sh/reference/forge/forge-lint#named-struct-fields

Loading