bevy_lint/lints/suspicious/
insert_event_resource.rs1use std::borrow::Cow;
43
44use clippy_utils::{
45 diagnostics::span_lint_and_sugg,
46 source::{snippet, snippet_with_applicability},
47 ty::ty_from_hir_ty,
48};
49use rustc_errors::Applicability;
50use rustc_hir::{Expr, GenericArg, GenericArgs, Path, PathSegment, QPath};
51use rustc_lint::{LateContext, LateLintPass};
52use rustc_middle::ty::{Ty, TyKind};
53
54use crate::{
55 declare_bevy_lint, declare_bevy_lint_pass, sym,
56 utils::{
57 hir_parse::{generic_args_snippet, span_args},
58 method_call::MethodCall,
59 },
60};
61
62declare_bevy_lint! {
63 pub(crate) INSERT_EVENT_RESOURCE,
64 super::Suspicious,
65 "called `App::insert_resource(Events<T>)` or `App::init_resource::<Events<T>>()` instead of `App::add_event::<T>()`",
66}
67
68declare_bevy_lint_pass! {
69 pub(crate) InsertEventResource => [INSERT_EVENT_RESOURCE],
70}
71
72const HELP_MESSAGE: &str = "inserting an `Events` resource does not fully setup that event";
73
74impl<'tcx> LateLintPass<'tcx> for InsertEventResource {
75 fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
76 if expr.span.in_external_macro(cx.tcx.sess.source_map()) {
78 return;
79 }
80
81 if let Some(method_call) = MethodCall::try_from(cx, expr) {
83 let src_ty = cx
86 .typeck_results()
87 .expr_ty_adjusted(method_call.receiver)
88 .peel_refs();
89
90 if !crate::paths::APP.matches_ty(cx, src_ty) {
92 return;
93 }
94
95 match method_call.method_path.ident.name {
98 symbol if symbol == sym::insert_resource => {
99 check_insert_resource(cx, &method_call);
100 }
101 symbol if symbol == sym::init_resource => {
102 check_init_resource(cx, &method_call);
103 }
104 _ => {}
105 }
106 }
107 }
108}
109
110fn check_insert_resource(cx: &LateContext<'_>, method_call: &MethodCall) {
112 let [arg] = method_call.args else {
114 return;
115 };
116
117 let ty = cx.typeck_results().expr_ty_adjusted(arg);
119
120 if crate::paths::EVENTS.matches_ty(cx, ty) {
122 let mut applicability = Applicability::MachineApplicable;
123
124 let event_ty_snippet = extract_ty_event_snippet(ty, &mut applicability);
125 let args_snippet = snippet(cx, span_args(method_call.args), "");
126 let generics_snippet = generic_args_snippet(cx, method_call.method_path);
127
128 if method_call.is_fully_qulified {
129 let receiver_snippet = snippet(cx, method_call.receiver.span, "");
130 span_lint_and_sugg(
131 cx,
132 INSERT_EVENT_RESOURCE,
133 method_call.span,
134 format!(
135 "called `App::insert_resource{generics_snippet}({receiver_snippet}, {args_snippet})` instead of `App::add_event::<{event_ty_snippet}>({receiver_snippet})`"
136 ),
137 HELP_MESSAGE,
138 format!("App::add_event::<{event_ty_snippet}>({receiver_snippet})"),
139 applicability,
140 );
141 } else {
142 span_lint_and_sugg(
143 cx,
144 INSERT_EVENT_RESOURCE,
145 method_call.span,
146 format!(
147 "called `App::insert_resource{generics_snippet}({args_snippet})` instead of `App::add_event::<{event_ty_snippet}>()`"
148 ),
149 HELP_MESSAGE,
150 format!("add_event::<{event_ty_snippet}>()"),
151 applicability,
152 );
153 }
154 }
155}
156
157fn extract_ty_event_snippet<'tcx>(
162 events_ty: Ty<'tcx>,
163 applicability: &mut Applicability,
164) -> Cow<'tcx, str> {
165 const DEFAULT: Cow<str> = Cow::Borrowed("T");
166
167 let TyKind::Adt(_, events_arguments) = events_ty.kind() else {
168 if let Applicability::MachineApplicable = applicability {
169 *applicability = Applicability::HasPlaceholders;
170 }
171
172 return DEFAULT;
173 };
174
175 let Some(event_snippet) = events_arguments.iter().next() else {
176 if let Applicability::MachineApplicable = applicability {
177 *applicability = Applicability::HasPlaceholders;
178 }
179
180 return DEFAULT;
181 };
182
183 format!("{event_snippet:?}").into()
184}
185
186fn check_init_resource<'tcx>(cx: &LateContext<'tcx>, method_call: &MethodCall<'tcx>) {
188 if let Some(&GenericArgs {
189 args: &[GenericArg::Type(resource_hir_ty)],
191 ..
192 }) = method_call.method_path.args
193 {
194 let resource_ty = ty_from_hir_ty(cx, resource_hir_ty.as_unambig_ty());
197
198 if crate::paths::EVENTS.matches_ty(cx, resource_ty) {
200 let mut applicability = Applicability::MachineApplicable;
201
202 let event_ty_snippet =
203 extract_hir_event_snippet(cx, resource_hir_ty.as_unambig_ty(), &mut applicability);
204
205 let args_snippet = snippet(cx, span_args(method_call.args), "");
206 let generics_snippet = generic_args_snippet(cx, method_call.method_path);
207
208 if method_call.is_fully_qulified {
209 let receiver_snippet = snippet(cx, method_call.receiver.span, "");
210 span_lint_and_sugg(
211 cx,
212 INSERT_EVENT_RESOURCE,
213 method_call.span,
214 format!(
215 "called `App::init_resource{generics_snippet}({receiver_snippet})` instead of `App::add_event::<{event_ty_snippet}>({receiver_snippet})`"
216 ),
217 HELP_MESSAGE,
218 format!("App::add_event::<{event_ty_snippet}>({receiver_snippet})"),
219 applicability,
220 );
221 } else {
222 span_lint_and_sugg(
223 cx,
224 INSERT_EVENT_RESOURCE,
225 method_call.span,
226 format!(
227 "called `App::init_resource{generics_snippet}({args_snippet})` instead of `App::add_event::<{event_ty_snippet}>()`"
228 ),
229 HELP_MESSAGE,
230 format!("add_event::<{event_ty_snippet}>()"),
231 applicability,
232 );
233 }
234 }
235 }
236}
237
238fn extract_hir_event_snippet<'tcx>(
245 cx: &LateContext<'tcx>,
246 events_hir_ty: &rustc_hir::Ty<'tcx>,
247 applicability: &mut Applicability,
248) -> Cow<'static, str> {
249 const DEFAULT: Cow<str> = Cow::Borrowed("T");
250
251 let event_span = match events_hir_ty.kind {
253 rustc_hir::TyKind::Path(QPath::Resolved(
257 _,
258 &Path {
259 segments:
262 &[
263 ..,
264 PathSegment {
265 args:
267 Some(&GenericArgs {
268 args: &[GenericArg::Type(ty)],
269 ..
270 }),
271 ..
272 },
273 ],
274 ..
275 },
276 )) => {
277 ty.span
279 }
280 _ => {
283 if let Applicability::MachineApplicable = applicability {
284 *applicability = Applicability::HasPlaceholders;
285 }
286
287 return DEFAULT;
288 }
289 };
290
291 snippet_with_applicability(cx, event_span, &DEFAULT, applicability)
293}