forge_lint/sol/info/
named_struct_fields.rs

1use solar::sema::hir::{CallArgs, CallArgsKind, Expr, ExprKind, ItemId, Res};
2
3use crate::{
4    linter::{LateLintPass, LintContext, Suggestion},
5    sol::{Severity, SolLint, info::NamedStructFields},
6};
7
8declare_forge_lint!(
9    NAMED_STRUCT_FIELDS,
10    Severity::Info,
11    "named-struct-fields",
12    "prefer initializing structs with named fields"
13);
14
15impl<'hir> LateLintPass<'hir> for NamedStructFields {
16    fn check_expr(
17        &mut self,
18        ctx: &LintContext,
19        hir: &'hir solar::sema::hir::Hir<'hir>,
20        expr: &'hir solar::sema::hir::Expr<'hir>,
21    ) {
22        let ExprKind::Call(
23            Expr { kind: ExprKind::Ident([Res::Item(ItemId::Struct(struct_id))]), span, .. },
24            CallArgs { kind: CallArgsKind::Unnamed(args), .. },
25            _,
26        ) = &expr.kind
27        else {
28            return;
29        };
30
31        let strukt = hir.strukt(*struct_id);
32        let fields = &strukt.fields;
33
34        // Basic sanity conditions for a consistent auto-fix
35        if fields.len() != args.len() || fields.is_empty() {
36            // Emit without suggestion
37            ctx.emit(&NAMED_STRUCT_FIELDS, expr.span);
38            return;
39        }
40
41        // Get struct name snippet and emit without suggestion if we can't get it
42        let Some(struct_name_snippet) = ctx.span_to_snippet(*span) else {
43            // Emit without suggestion if we can't get the struct name snippet
44            ctx.emit(&NAMED_STRUCT_FIELDS, expr.span);
45            return;
46        };
47
48        // Collect field names and corresponding argument source snippets
49        let mut field_assignments = Vec::new();
50        for (field_id, arg) in fields.iter().zip(args.iter()) {
51            let field = hir.variable(*field_id);
52
53            let Some((arg_snippet, field_name)) =
54                ctx.span_to_snippet(arg.span).zip(field.name.map(|n| n.to_string()))
55            else {
56                // Emit without suggestion if we can't get argument snippet
57                ctx.emit(&NAMED_STRUCT_FIELDS, expr.span);
58                return;
59            };
60
61            field_assignments.push(format!("{field_name}: {arg_snippet}"));
62        }
63
64        ctx.emit_with_suggestion(
65            &NAMED_STRUCT_FIELDS,
66            expr.span,
67            Suggestion::fix(
68                format!("{}({{ {} }})", struct_name_snippet, field_assignments.join(", ")),
69                solar::interface::diagnostics::Applicability::MachineApplicable,
70            )
71            .with_desc("consider using named fields"),
72        );
73    }
74}