Skip to main content

forge_lint/sol/info/
named_struct_fields.rs

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