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::hir_parse::{MethodCall, generic_args_snippet, span_args},
57};
58
59declare_bevy_lint! {
60 pub(crate) INSERT_EVENT_RESOURCE,
61 super::Suspicious,
62 "called `App::insert_resource(Events<T>)` or `App::init_resource::<Events<T>>()` instead of `App::add_event::<T>()`",
63}
64
65declare_bevy_lint_pass! {
66 pub(crate) InsertEventResource => [INSERT_EVENT_RESOURCE],
67}
68
69const HELP_MESSAGE: &str = "inserting an `Events` resource does not fully setup that event";
70
71impl<'tcx> LateLintPass<'tcx> for InsertEventResource {
72 fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
73 if expr.span.in_external_macro(cx.tcx.sess.source_map()) {
75 return;
76 }
77
78 if let Some(method_call) = MethodCall::try_from(cx, expr) {
80 let src_ty = cx
83 .typeck_results()
84 .expr_ty_adjusted(method_call.receiver)
85 .peel_refs();
86
87 if !crate::paths::APP.matches_ty(cx, src_ty) {
89 return;
90 }
91
92 match method_call.method_path.ident.name {
95 symbol if symbol == sym::insert_resource => {
96 check_insert_resource(cx, &method_call);
97 }
98 symbol if symbol == sym::init_resource => {
99 check_init_resource(cx, &method_call);
100 }
101 _ => {}
102 }
103 }
104 }
105}
106
107fn check_insert_resource(cx: &LateContext<'_>, method_call: &MethodCall) {
109 let [arg] = method_call.args else {
111 return;
112 };
113
114 let ty = cx.typeck_results().expr_ty_adjusted(arg);
116
117 if crate::paths::EVENTS.matches_ty(cx, ty) {
119 let mut applicability = Applicability::MachineApplicable;
120
121 let event_ty_snippet = extract_ty_event_snippet(ty, &mut applicability);
122 let args_snippet = snippet(cx, span_args(method_call.args), "");
123 let generics_snippet = generic_args_snippet(cx, method_call.method_path);
124
125 if method_call.is_fully_qulified {
126 let receiver_snippet = snippet(cx, method_call.receiver.span, "");
127 span_lint_and_sugg(
128 cx,
129 INSERT_EVENT_RESOURCE,
130 method_call.span,
131 format!(
132 "called `App::insert_resource{generics_snippet}({receiver_snippet}, {args_snippet})` instead of `App::add_event::<{event_ty_snippet}>({receiver_snippet})`"
133 ),
134 HELP_MESSAGE,
135 format!("App::add_event::<{event_ty_snippet}>({receiver_snippet})"),
136 applicability,
137 );
138 } else {
139 span_lint_and_sugg(
140 cx,
141 INSERT_EVENT_RESOURCE,
142 method_call.span,
143 format!(
144 "called `App::insert_resource{generics_snippet}({args_snippet})` instead of `App::add_event::<{event_ty_snippet}>()`"
145 ),
146 HELP_MESSAGE,
147 format!("add_event::<{event_ty_snippet}>()"),
148 applicability,
149 );
150 }
151 }
152}
153
154fn extract_ty_event_snippet<'tcx>(
159 events_ty: Ty<'tcx>,
160 applicability: &mut Applicability,
161) -> Cow<'tcx, str> {
162 const DEFAULT: Cow<str> = Cow::Borrowed("T");
163
164 let TyKind::Adt(_, events_arguments) = events_ty.kind() else {
165 if let Applicability::MachineApplicable = applicability {
166 *applicability = Applicability::HasPlaceholders;
167 }
168
169 return DEFAULT;
170 };
171
172 let Some(event_snippet) = events_arguments.iter().next() else {
173 if let Applicability::MachineApplicable = applicability {
174 *applicability = Applicability::HasPlaceholders;
175 }
176
177 return DEFAULT;
178 };
179
180 format!("{event_snippet:?}").into()
181}
182
183fn check_init_resource<'tcx>(cx: &LateContext<'tcx>, method_call: &MethodCall<'tcx>) {
185 if let Some(&GenericArgs {
186 args: &[GenericArg::Type(resource_hir_ty)],
188 ..
189 }) = method_call.method_path.args
190 {
191 let resource_ty = ty_from_hir_ty(cx, resource_hir_ty.as_unambig_ty());
194
195 if crate::paths::EVENTS.matches_ty(cx, resource_ty) {
197 let mut applicability = Applicability::MachineApplicable;
198
199 let event_ty_snippet =
200 extract_hir_event_snippet(cx, resource_hir_ty.as_unambig_ty(), &mut applicability);
201
202 let args_snippet = snippet(cx, span_args(method_call.args), "");
203 let generics_snippet = generic_args_snippet(cx, method_call.method_path);
204
205 if method_call.is_fully_qulified {
206 let receiver_snippet = snippet(cx, method_call.receiver.span, "");
207 span_lint_and_sugg(
208 cx,
209 INSERT_EVENT_RESOURCE,
210 method_call.span,
211 format!(
212 "called `App::init_resource{generics_snippet}({receiver_snippet})` instead of `App::add_event::<{event_ty_snippet}>({receiver_snippet})`"
213 ),
214 HELP_MESSAGE,
215 format!("App::add_event::<{event_ty_snippet}>({receiver_snippet})"),
216 applicability,
217 );
218 } else {
219 span_lint_and_sugg(
220 cx,
221 INSERT_EVENT_RESOURCE,
222 method_call.span,
223 format!(
224 "called `App::init_resource{generics_snippet}({args_snippet})` instead of `App::add_event::<{event_ty_snippet}>()`"
225 ),
226 HELP_MESSAGE,
227 format!("add_event::<{event_ty_snippet}>()"),
228 applicability,
229 );
230 }
231 }
232 }
233}
234
235fn extract_hir_event_snippet<'tcx>(
242 cx: &LateContext<'tcx>,
243 events_hir_ty: &rustc_hir::Ty<'tcx>,
244 applicability: &mut Applicability,
245) -> Cow<'static, str> {
246 const DEFAULT: Cow<str> = Cow::Borrowed("T");
247
248 let event_span = match events_hir_ty.kind {
250 rustc_hir::TyKind::Path(QPath::Resolved(
254 _,
255 &Path {
256 segments:
259 &[
260 ..,
261 PathSegment {
262 args:
264 Some(&GenericArgs {
265 args: &[GenericArg::Type(ty)],
266 ..
267 }),
268 ..
269 },
270 ],
271 ..
272 },
273 )) => {
274 ty.span
276 }
277 _ => {
280 if let Applicability::MachineApplicable = applicability {
281 *applicability = Applicability::HasPlaceholders;
282 }
283
284 return DEFAULT;
285 }
286 };
287
288 snippet_with_applicability(cx, event_span, &DEFAULT, applicability)
290}