Skip to main content

forge_lint/sol/info/
named_struct_fields.rs

1use super::NamedStructFields;
2use crate::{
3    linter::{LateLintPass, LintContext, Suggestion},
4    sol::{Severity, SolLint},
5};
6use solar::{
7    interface::diagnostics::Applicability,
8    sema::{
9        Gcx,
10        hir::{CallArgs, CallArgsKind, Expr, ExprKind, ItemId, Res},
11    },
12};
13
14declare_forge_lint!(
15    NAMED_STRUCT_FIELDS,
16    Severity::Info,
17    "named-struct-fields",
18    "struct is initialized with positional fields"
19);
20
21impl<'gcx> LateLintPass<'gcx> for NamedStructFields {
22    fn check_expr(&mut self, ctx: &LintContext, gcx: Gcx<'gcx>, expr: &'gcx Expr<'gcx>) {
23        let ExprKind::Call(callee, CallArgs { kind: CallArgsKind::Unnamed(args), .. }) = &expr.kind
24        else {
25            return;
26        };
27        let Some(Res::Item(ItemId::Struct(struct_id))) = gcx.resolved_expr(callee) else { return };
28        // A fix needs one argument per field and every snippet available; otherwise the
29        // diagnostic is emitted without a suggestion.
30        let fields = gcx.hir.strukt(struct_id).fields;
31        let fix = (!fields.is_empty() && fields.len() == args.len()).then(|| {
32            let assignments = fields
33                .iter()
34                .zip(*args)
35                .map(|(field, arg)| {
36                    Some(format!(
37                        "{}: {}",
38                        gcx.hir.variable(*field).name?,
39                        ctx.span_to_snippet(arg.span)?
40                    ))
41                })
42                .collect::<Option<Vec<_>>>()?;
43            Some(format!("{}({{ {} }})", ctx.span_to_snippet(callee.span)?, assignments.join(", ")))
44        });
45        match fix.flatten() {
46            Some(fix) => ctx.emit_with_suggestion(
47                &NAMED_STRUCT_FIELDS,
48                expr.span,
49                Suggestion::fix(fix, Applicability::MachineApplicable)
50                    .with_desc("consider using named fields"),
51            ),
52            None => ctx.emit(&NAMED_STRUCT_FIELDS, expr.span),
53        }
54    }
55}