forge_lint/sol/info/
named_struct_fields.rs1use 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 if fields.len() != args.len() || fields.is_empty() {
40 ctx.emit(&NAMED_STRUCT_FIELDS, expr.span);
42 return;
43 }
44
45 let Some(struct_name_snippet) = ctx.span_to_snippet(*span) else {
47 ctx.emit(&NAMED_STRUCT_FIELDS, expr.span);
49 return;
50 };
51
52 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 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}