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::EVENTS.matches_ty(cx, ty) || crate::paths::MESSAGES.matches_ty(cx, ty) {
124 let mut applicability = Applicability::MachineApplicable;
125
126 let message_ty_snippet = extract_ty_message_snippet(ty, &mut applicability);
127 let args_snippet = snippet(cx, span_args(method_call.args), "");
128 let generics_snippet = generic_args_snippet(cx, method_call.method_path);
129
130 if method_call.is_fully_qualified {
131 let receiver_snippet = snippet(cx, method_call.receiver.span, "");
132 span_lint_and_sugg(
133 cx,
134 INSERT_MESSAGE_RESOURCE,
135 method_call.span,
136 format!(
137 "called `App::insert_resource{generics_snippet}({receiver_snippet}, {args_snippet})` instead of `App::add_message::<{message_ty_snippet}>({receiver_snippet})`"
138 ),
139 HELP_MESSAGE,
140 format!("App::add_message::<{message_ty_snippet}>({receiver_snippet})"),
141 applicability,
142 );
143 } else {
144 span_lint_and_sugg(
145 cx,
146 INSERT_MESSAGE_RESOURCE,
147 method_call.span,
148 format!(
149 "called `App::insert_resource{generics_snippet}({args_snippet})` instead of `App::add_message::<{message_ty_snippet}>()`"
150 ),
151 HELP_MESSAGE,
152 format!("add_message::<{message_ty_snippet}>()"),
153 applicability,
154 );
155 }
156 }
157}
158
159fn extract_ty_message_snippet<'tcx>(
164 messages_ty: Ty<'tcx>,
165 applicability: &mut Applicability,
166) -> Cow<'tcx, str> {
167 const DEFAULT: Cow<str> = Cow::Borrowed("T");
168
169 let TyKind::Adt(_, messages_arguments) = messages_ty.kind() else {
170 if let Applicability::MachineApplicable = applicability {
171 *applicability = Applicability::HasPlaceholders;
172 }
173
174 return DEFAULT;
175 };
176
177 let Some(message_snippet) = messages_arguments.iter().next() else {
178 if let Applicability::MachineApplicable = applicability {
179 *applicability = Applicability::HasPlaceholders;
180 }
181
182 return DEFAULT;
183 };
184
185 format!("{message_snippet:?}").into()
186}
187
188fn check_init_resource<'tcx>(cx: &LateContext<'tcx>, method_call: &MethodCall<'tcx>) {
190 if let Some(&GenericArgs {
191 args: &[GenericArg::Type(resource_hir_ty)],
193 ..
194 }) = method_call.method_path.args
195 {
196 let resource_ty = ty_from_hir_ty(cx, resource_hir_ty.as_unambig_ty());
199
200 if crate::paths::EVENTS.matches_ty(cx, resource_ty)
203 || crate::paths::MESSAGES.matches_ty(cx, resource_ty)
204 {
205 let mut applicability = Applicability::MachineApplicable;
206
207 let message_ty_snippet = extract_hir_message_snippet(
208 cx,
209 resource_hir_ty.as_unambig_ty(),
210 &mut applicability,
211 );
212
213 let args_snippet = snippet(cx, span_args(method_call.args), "");
214 let generics_snippet = generic_args_snippet(cx, method_call.method_path);
215
216 if method_call.is_fully_qualified {
217 let receiver_snippet = snippet(cx, method_call.receiver.span, "");
218 span_lint_and_sugg(
219 cx,
220 INSERT_MESSAGE_RESOURCE,
221 method_call.span,
222 format!(
223 "called `App::init_resource{generics_snippet}({receiver_snippet})` instead of `App::add_message::<{message_ty_snippet}>({receiver_snippet})`"
224 ),
225 HELP_MESSAGE,
226 format!("App::add_message::<{message_ty_snippet}>({receiver_snippet})"),
227 applicability,
228 );
229 } else {
230 span_lint_and_sugg(
231 cx,
232 INSERT_MESSAGE_RESOURCE,
233 method_call.span,
234 format!(
235 "called `App::init_resource{generics_snippet}({args_snippet})` instead of `App::add_message::<{message_ty_snippet}>()`"
236 ),
237 HELP_MESSAGE,
238 format!("add_message::<{message_ty_snippet}>()"),
239 applicability,
240 );
241 }
242 }
243 }
244}
245
246fn extract_hir_message_snippet<'tcx>(
253 cx: &LateContext<'tcx>,
254 messages_hir_ty: &rustc_hir::Ty<'tcx>,
255 applicability: &mut Applicability,
256) -> Cow<'static, str> {
257 const DEFAULT: Cow<str> = Cow::Borrowed("T");
258
259 let message_span = match messages_hir_ty.kind {
261 rustc_hir::TyKind::Path(QPath::Resolved(
265 _,
266 &Path {
267 segments:
271 &[
272 ..,
273 PathSegment {
274 args:
276 Some(&GenericArgs {
277 args: &[GenericArg::Type(ty)],
278 ..
279 }),
280 ..
281 },
282 ],
283 ..
284 },
285 )) => {
286 ty.span
288 }
289 _ => {
292 if let Applicability::MachineApplicable = applicability {
293 *applicability = Applicability::HasPlaceholders;
294 }
295
296 return DEFAULT;
297 }
298 };
299
300 snippet_with_applicability(cx, message_span, &DEFAULT, applicability)
302}