bevy_lint/lints/suspicious/
insert_message_resource.rs1use std::borrow::Cow;
44
45use clippy_utils::{
46 diagnostics::span_lint_and_sugg,
47 source::{snippet, snippet_with_applicability},
48 ty::ty_from_hir_ty,
49};
50use rustc_errors::Applicability;
51use rustc_hir::{Expr, GenericArg, GenericArgs, Path, PathSegment, QPath};
52use rustc_lint::{LateContext, LateLintPass};
53use rustc_middle::ty::{Ty, TyKind};
54
55use crate::{
56 declare_bevy_lint, declare_bevy_lint_pass, sym,
57 utils::{
58 hir_parse::{generic_args_snippet, span_args},
59 method_call::MethodCall,
60 },
61};
62
63declare_bevy_lint! {
64 pub(crate) INSERT_MESSAGE_RESOURCE,
65 super::Suspicious,
66 "called `App::insert_resource(Messages<T>)` or `App::init_resource::<Messages<T>>()` instead of `App::add_message::<T>()`",
67}
68
69declare_bevy_lint_pass! {
70 pub(crate) InsertMessageResource => [INSERT_MESSAGE_RESOURCE],
71}
72
73const HELP_MESSAGE: &str = "inserting an `Messages` resource does not fully setup that message";
74
75impl<'tcx> LateLintPass<'tcx> for InsertMessageResource {
76 fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
77 if expr.span.in_external_macro(cx.tcx.sess.source_map()) {
79 return;
80 }
81
82 if let Some(method_call) = MethodCall::try_from(cx, expr) {
84 let src_ty = cx
87 .typeck_results()
88 .expr_ty_adjusted(method_call.receiver)
89 .peel_refs();
90
91 if !crate::paths::APP.matches_ty(cx, src_ty) {
93 return;
94 }
95
96 match method_call.method_path.ident.name {
99 symbol if symbol == sym::insert_resource => {
100 check_insert_resource(cx, &method_call);
101 }
102 symbol if symbol == sym::init_resource => {
103 check_init_resource(cx, &method_call);
104 }
105 _ => {}
106 }
107 }
108 }
109}
110
111fn check_insert_resource(cx: &LateContext<'_>, method_call: &MethodCall) {
113 let [arg] = method_call.args else {
115 return;
116 };
117
118 let ty = cx.typeck_results().expr_ty_adjusted(arg);
120
121 if crate::paths::MESSAGES.matches_ty(cx, ty) {
123 let mut applicability = Applicability::MachineApplicable;
124
125 let message_ty_snippet = extract_ty_message_snippet(ty, &mut applicability);
126 let args_snippet = snippet(cx, span_args(method_call.args), "");
127 let generics_snippet = generic_args_snippet(cx, method_call.method_path);
128
129 if method_call.is_fully_qualified {
130 let receiver_snippet = snippet(cx, method_call.receiver.span, "");
131 span_lint_and_sugg(
132 cx,
133 INSERT_MESSAGE_RESOURCE,
134 method_call.span,
135 format!(
136 "called `App::insert_resource{generics_snippet}({receiver_snippet}, {args_snippet})` instead of `App::add_message::<{message_ty_snippet}>({receiver_snippet})`"
137 ),
138 HELP_MESSAGE,
139 format!("App::add_message::<{message_ty_snippet}>({receiver_snippet})"),
140 applicability,
141 );
142 } else {
143 span_lint_and_sugg(
144 cx,
145 INSERT_MESSAGE_RESOURCE,
146 method_call.span,
147 format!(
148 "called `App::insert_resource{generics_snippet}({args_snippet})` instead of `App::add_message::<{message_ty_snippet}>()`"
149 ),
150 HELP_MESSAGE,
151 format!("add_message::<{message_ty_snippet}>()"),
152 applicability,
153 );
154 }
155 }
156}
157
158fn extract_ty_message_snippet<'tcx>(
163 messages_ty: Ty<'tcx>,
164 applicability: &mut Applicability,
165) -> Cow<'tcx, str> {
166 const DEFAULT: Cow<str> = Cow::Borrowed("T");
167
168 let TyKind::Adt(_, messages_arguments) = messages_ty.kind() else {
169 if let Applicability::MachineApplicable = applicability {
170 *applicability = Applicability::HasPlaceholders;
171 }
172
173 return DEFAULT;
174 };
175
176 let Some(message_snippet) = messages_arguments.iter().next() else {
177 if let Applicability::MachineApplicable = applicability {
178 *applicability = Applicability::HasPlaceholders;
179 }
180
181 return DEFAULT;
182 };
183
184 format!("{message_snippet:?}").into()
185}
186
187fn check_init_resource<'tcx>(cx: &LateContext<'tcx>, method_call: &MethodCall<'tcx>) {
189 if let Some(&GenericArgs {
190 args: &[GenericArg::Type(resource_hir_ty)],
192 ..
193 }) = method_call.method_path.args
194 {
195 let resource_ty = ty_from_hir_ty(cx, resource_hir_ty.as_unambig_ty());
198
199 if crate::paths::MESSAGES.matches_ty(cx, resource_ty) {
201 let mut applicability = Applicability::MachineApplicable;
202
203 let message_ty_snippet = extract_hir_message_snippet(
204 cx,
205 resource_hir_ty.as_unambig_ty(),
206 &mut applicability,
207 );
208
209 let args_snippet = snippet(cx, span_args(method_call.args), "");
210 let generics_snippet = generic_args_snippet(cx, method_call.method_path);
211
212 if method_call.is_fully_qualified {
213 let receiver_snippet = snippet(cx, method_call.receiver.span, "");
214 span_lint_and_sugg(
215 cx,
216 INSERT_MESSAGE_RESOURCE,
217 method_call.span,
218 format!(
219 "called `App::init_resource{generics_snippet}({receiver_snippet})` instead of `App::add_message::<{message_ty_snippet}>({receiver_snippet})`"
220 ),
221 HELP_MESSAGE,
222 format!("App::add_message::<{message_ty_snippet}>({receiver_snippet})"),
223 applicability,
224 );
225 } else {
226 span_lint_and_sugg(
227 cx,
228 INSERT_MESSAGE_RESOURCE,
229 method_call.span,
230 format!(
231 "called `App::init_resource{generics_snippet}({args_snippet})` instead of `App::add_message::<{message_ty_snippet}>()`"
232 ),
233 HELP_MESSAGE,
234 format!("add_message::<{message_ty_snippet}>()"),
235 applicability,
236 );
237 }
238 }
239 }
240}
241
242fn extract_hir_message_snippet<'tcx>(
249 cx: &LateContext<'tcx>,
250 messages_hir_ty: &rustc_hir::Ty<'tcx>,
251 applicability: &mut Applicability,
252) -> Cow<'static, str> {
253 const DEFAULT: Cow<str> = Cow::Borrowed("T");
254
255 let message_span = match messages_hir_ty.kind {
257 rustc_hir::TyKind::Path(QPath::Resolved(
261 _,
262 &Path {
263 segments:
267 &[
268 ..,
269 PathSegment {
270 args:
272 Some(&GenericArgs {
273 args: &[GenericArg::Type(ty)],
274 ..
275 }),
276 ..
277 },
278 ],
279 ..
280 },
281 )) => {
282 ty.span
284 }
285 _ => {
288 if let Applicability::MachineApplicable = applicability {
289 *applicability = Applicability::HasPlaceholders;
290 }
291
292 return DEFAULT;
293 }
294 };
295
296 snippet_with_applicability(cx, message_span, &DEFAULT, applicability)
298}