1use mago_atom::Atom;
4use mago_atom::AtomMap;
5use mago_atom::AtomSet;
6use mago_atom::ascii_lowercase_atom;
7use mago_codex::identifier::function_like::FunctionLikeIdentifier;
8use mago_codex::metadata::CodebaseMetadata;
9use mago_codex::metadata::class_like::ClassLikeMetadata;
10use mago_codex::metadata::function_like::FunctionLikeMetadata;
11use mago_codex::metadata::property::PropertyMetadata;
12use mago_codex::ttype::union::TUnion;
13use mago_database::file::File;
14use mago_syntax::ast::Class;
15use mago_syntax::ast::Enum;
16use mago_syntax::ast::Expression;
17use mago_syntax::ast::Function;
18use mago_syntax::ast::FunctionCall;
19use mago_syntax::ast::Interface;
20use mago_syntax::ast::MethodCall;
21use mago_syntax::ast::NullSafeMethodCall;
22use mago_syntax::ast::Program;
23use mago_syntax::ast::Statement;
24use mago_syntax::ast::StaticMethodCall;
25use mago_syntax::ast::Trait;
26
27use crate::artifacts::AnalysisArtifacts;
28use crate::context::block::BlockContext;
29use crate::invocation::Invocation;
30use crate::plugin::context::HookContext;
31use crate::plugin::context::InvocationInfo;
32use crate::plugin::context::ProviderContext;
33use crate::plugin::context::ReportedIssue;
34use crate::plugin::error::PluginResult;
35use crate::plugin::hook::ClassDeclarationHook;
36use crate::plugin::hook::EnumDeclarationHook;
37use crate::plugin::hook::ExpressionHook;
38use crate::plugin::hook::ExpressionHookResult;
39use crate::plugin::hook::FunctionCallHook;
40use crate::plugin::hook::FunctionDeclarationHook;
41use crate::plugin::hook::HookAction;
42use crate::plugin::hook::InterfaceDeclarationHook;
43use crate::plugin::hook::IssueFilterDecision;
44use crate::plugin::hook::IssueFilterHook;
45use crate::plugin::hook::MethodCallHook;
46use crate::plugin::hook::NullSafeMethodCallHook;
47use crate::plugin::hook::ProgramHook;
48use crate::plugin::hook::StatementHook;
49use crate::plugin::hook::StaticMethodCallHook;
50use crate::plugin::hook::TraitDeclarationHook;
51use crate::plugin::provider::assertion::FunctionAssertionProvider;
52use crate::plugin::provider::assertion::InvocationAssertions;
53use crate::plugin::provider::assertion::MethodAssertionProvider;
54use crate::plugin::provider::function::FunctionReturnTypeProvider;
55use crate::plugin::provider::function::FunctionTarget;
56use crate::plugin::provider::method::MethodReturnTypeProvider;
57use crate::plugin::provider::method::MethodTarget;
58use crate::plugin::provider::property::PropertyInitializationProvider;
59use crate::plugin::provider::throw::ExpressionThrowTypeProvider;
60use crate::plugin::provider::throw::FunctionThrowTypeProvider;
61use crate::plugin::provider::throw::MethodThrowTypeProvider;
62
63use mago_reporting::IssueCollection;
64
65pub struct ProviderResult {
66 pub return_type: Option<TUnion>,
67 pub issues: Vec<ReportedIssue>,
68}
69
70#[derive(Default)]
71pub struct PluginRegistry {
72 function_exact: AtomMap<Vec<usize>>,
73 function_prefix: Vec<(Atom, usize)>,
74 function_namespace: Vec<(Atom, usize)>,
75 function_providers: Vec<Box<dyn FunctionReturnTypeProvider>>,
76 method_exact: AtomMap<Vec<usize>>,
77 method_wildcard: Vec<(Vec<MethodTarget>, usize)>,
78 method_providers: Vec<Box<dyn MethodReturnTypeProvider>>,
79 program_hooks: Vec<Box<dyn ProgramHook>>,
80 statement_hooks: Vec<Box<dyn StatementHook>>,
81 expression_hooks: Vec<Box<dyn ExpressionHook>>,
82 function_call_hooks: Vec<Box<dyn FunctionCallHook>>,
83 method_call_hooks: Vec<Box<dyn MethodCallHook>>,
84 static_method_call_hooks: Vec<Box<dyn StaticMethodCallHook>>,
85 nullsafe_method_call_hooks: Vec<Box<dyn NullSafeMethodCallHook>>,
86 class_hooks: Vec<Box<dyn ClassDeclarationHook>>,
87 interface_hooks: Vec<Box<dyn InterfaceDeclarationHook>>,
88 trait_hooks: Vec<Box<dyn TraitDeclarationHook>>,
89 enum_hooks: Vec<Box<dyn EnumDeclarationHook>>,
90 function_decl_hooks: Vec<Box<dyn FunctionDeclarationHook>>,
91 property_initialization_providers: Vec<Box<dyn PropertyInitializationProvider>>,
92 issue_filter_hooks: Vec<Box<dyn IssueFilterHook>>,
93 function_assertion_exact: AtomMap<Vec<usize>>,
94 function_assertion_prefix: Vec<(Atom, usize)>,
95 function_assertion_namespace: Vec<(Atom, usize)>,
96 function_assertion_providers: Vec<Box<dyn FunctionAssertionProvider>>,
97 method_assertion_exact: AtomMap<Vec<usize>>,
98 method_assertion_wildcard: Vec<(Vec<MethodTarget>, usize)>,
99 method_assertion_providers: Vec<Box<dyn MethodAssertionProvider>>,
100 expression_throw_providers: Vec<Box<dyn ExpressionThrowTypeProvider>>,
101 function_throw_exact: AtomMap<Vec<usize>>,
102 function_throw_prefix: Vec<(Atom, usize)>,
103 function_throw_namespace: Vec<(Atom, usize)>,
104 function_throw_providers: Vec<Box<dyn FunctionThrowTypeProvider>>,
105 method_throw_exact: AtomMap<Vec<usize>>,
106 method_throw_wildcard: Vec<(Vec<MethodTarget>, usize)>,
107 method_throw_providers: Vec<Box<dyn MethodThrowTypeProvider>>,
108}
109
110impl std::fmt::Debug for PluginRegistry {
111 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
112 f.debug_struct("PluginRegistry")
113 .field("function_providers", &self.function_providers.len())
114 .field("method_providers", &self.method_providers.len())
115 .field("program_hooks", &self.program_hooks.len())
116 .field("statement_hooks", &self.statement_hooks.len())
117 .field("expression_hooks", &self.expression_hooks.len())
118 .field("function_call_hooks", &self.function_call_hooks.len())
119 .field("method_call_hooks", &self.method_call_hooks.len())
120 .field("static_method_call_hooks", &self.static_method_call_hooks.len())
121 .field("nullsafe_method_call_hooks", &self.nullsafe_method_call_hooks.len())
122 .field("class_hooks", &self.class_hooks.len())
123 .field("interface_hooks", &self.interface_hooks.len())
124 .field("trait_hooks", &self.trait_hooks.len())
125 .field("enum_hooks", &self.enum_hooks.len())
126 .field("function_decl_hooks", &self.function_decl_hooks.len())
127 .field("property_initialization_providers", &self.property_initialization_providers.len())
128 .field("issue_filter_hooks", &self.issue_filter_hooks.len())
129 .field("function_assertion_providers", &self.function_assertion_providers.len())
130 .field("method_assertion_providers", &self.method_assertion_providers.len())
131 .field("expression_throw_providers", &self.expression_throw_providers.len())
132 .field("function_throw_providers", &self.function_throw_providers.len())
133 .field("method_throw_providers", &self.method_throw_providers.len())
134 .finish()
135 }
136}
137
138impl PluginRegistry {
139 #[inline]
140 #[must_use]
141 pub fn new() -> Self {
142 Self::default()
143 }
144
145 #[must_use]
146 pub fn with_library_providers() -> Self {
147 crate::plugin::create_registry()
148 }
149
150 pub fn register_function_provider<P: FunctionReturnTypeProvider + 'static>(&mut self, provider: P) {
151 let index = self.function_providers.len();
152
153 match P::targets() {
154 FunctionTarget::Exact(name) => {
155 self.function_exact.entry(ascii_lowercase_atom(name)).or_default().push(index);
156 }
157 FunctionTarget::ExactMultiple(names) => {
158 for name in names {
159 self.function_exact.entry(ascii_lowercase_atom(name)).or_default().push(index);
160 }
161 }
162 FunctionTarget::Prefix(prefix) => {
163 self.function_prefix.push((ascii_lowercase_atom(prefix), index));
164 }
165 FunctionTarget::Namespace(ns) => {
166 let ns_lower = ns.to_lowercase();
167 let ns_pattern = if ns_lower.ends_with('\\') { ns_lower } else { format!("{ns_lower}\\") };
168 self.function_namespace.push((ascii_lowercase_atom(&ns_pattern), index));
169 }
170 }
171
172 self.function_providers.push(Box::new(provider));
173 }
174
175 pub fn register_method_provider<P: MethodReturnTypeProvider + 'static>(&mut self, provider: P) {
176 let index = self.method_providers.len();
177 let targets = P::targets();
178
179 let mut has_wildcards = false;
180 let mut wildcard_targets = Vec::new();
181
182 for target in targets {
183 if let Some(key) = target.index_key() {
184 self.method_exact.entry(key).or_default().push(index);
185 } else {
186 has_wildcards = true;
187 wildcard_targets.push(*target);
188 }
189 }
190
191 if has_wildcards {
192 self.method_wildcard.push((wildcard_targets, index));
193 }
194
195 self.method_providers.push(Box::new(provider));
196 }
197
198 pub fn register_program_hook<H: ProgramHook + 'static>(&mut self, hook: H) {
199 self.program_hooks.push(Box::new(hook));
200 }
201
202 pub fn register_statement_hook<H: StatementHook + 'static>(&mut self, hook: H) {
203 self.statement_hooks.push(Box::new(hook));
204 }
205
206 pub fn register_expression_hook<H: ExpressionHook + 'static>(&mut self, hook: H) {
207 self.expression_hooks.push(Box::new(hook));
208 }
209
210 pub fn register_function_call_hook<H: FunctionCallHook + 'static>(&mut self, hook: H) {
211 self.function_call_hooks.push(Box::new(hook));
212 }
213
214 pub fn register_method_call_hook<H: MethodCallHook + 'static>(&mut self, hook: H) {
215 self.method_call_hooks.push(Box::new(hook));
216 }
217
218 pub fn register_static_method_call_hook<H: StaticMethodCallHook + 'static>(&mut self, hook: H) {
219 self.static_method_call_hooks.push(Box::new(hook));
220 }
221
222 pub fn register_nullsafe_method_call_hook<H: NullSafeMethodCallHook + 'static>(&mut self, hook: H) {
223 self.nullsafe_method_call_hooks.push(Box::new(hook));
224 }
225
226 pub fn register_class_hook<H: ClassDeclarationHook + 'static>(&mut self, hook: H) {
227 self.class_hooks.push(Box::new(hook));
228 }
229
230 pub fn register_interface_hook<H: InterfaceDeclarationHook + 'static>(&mut self, hook: H) {
231 self.interface_hooks.push(Box::new(hook));
232 }
233
234 pub fn register_trait_hook<H: TraitDeclarationHook + 'static>(&mut self, hook: H) {
235 self.trait_hooks.push(Box::new(hook));
236 }
237
238 pub fn register_enum_hook<H: EnumDeclarationHook + 'static>(&mut self, hook: H) {
239 self.enum_hooks.push(Box::new(hook));
240 }
241
242 pub fn register_function_decl_hook<H: FunctionDeclarationHook + 'static>(&mut self, hook: H) {
243 self.function_decl_hooks.push(Box::new(hook));
244 }
245
246 pub fn register_property_initialization_provider<P: PropertyInitializationProvider + 'static>(
247 &mut self,
248 provider: P,
249 ) {
250 self.property_initialization_providers.push(Box::new(provider));
251 }
252
253 pub fn register_issue_filter_hook<H: IssueFilterHook + 'static>(&mut self, hook: H) {
254 self.issue_filter_hooks.push(Box::new(hook));
255 }
256
257 pub fn register_function_assertion_provider<P: FunctionAssertionProvider + 'static>(&mut self, provider: P) {
258 let index = self.function_assertion_providers.len();
259
260 match P::targets() {
261 FunctionTarget::Exact(name) => {
262 self.function_assertion_exact.entry(ascii_lowercase_atom(name)).or_default().push(index);
263 }
264 FunctionTarget::ExactMultiple(names) => {
265 for name in names {
266 self.function_assertion_exact.entry(ascii_lowercase_atom(name)).or_default().push(index);
267 }
268 }
269 FunctionTarget::Prefix(prefix) => {
270 self.function_assertion_prefix.push((ascii_lowercase_atom(prefix), index));
271 }
272 FunctionTarget::Namespace(ns) => {
273 let ns_lower = ns.to_lowercase();
274 let ns_pattern = if ns_lower.ends_with('\\') { ns_lower } else { format!("{ns_lower}\\") };
275 self.function_assertion_namespace.push((ascii_lowercase_atom(&ns_pattern), index));
276 }
277 }
278
279 self.function_assertion_providers.push(Box::new(provider));
280 }
281
282 pub fn register_method_assertion_provider<P: MethodAssertionProvider + 'static>(&mut self, provider: P) {
283 let index = self.method_assertion_providers.len();
284 let targets = P::targets();
285
286 let mut has_wildcards = false;
287 let mut wildcard_targets = Vec::new();
288
289 for target in targets {
290 if let Some(key) = target.index_key() {
291 self.method_assertion_exact.entry(key).or_default().push(index);
292 } else {
293 has_wildcards = true;
294 wildcard_targets.push(*target);
295 }
296 }
297
298 if has_wildcards {
299 self.method_assertion_wildcard.push((wildcard_targets, index));
300 }
301
302 self.method_assertion_providers.push(Box::new(provider));
303 }
304
305 pub fn register_expression_throw_provider<P: ExpressionThrowTypeProvider + 'static>(&mut self, provider: P) {
306 self.expression_throw_providers.push(Box::new(provider));
307 }
308
309 pub fn register_function_throw_provider<P: FunctionThrowTypeProvider + 'static>(&mut self, provider: P) {
310 let index = self.function_throw_providers.len();
311
312 match P::targets() {
313 FunctionTarget::Exact(name) => {
314 self.function_throw_exact.entry(ascii_lowercase_atom(name)).or_default().push(index);
315 }
316 FunctionTarget::ExactMultiple(names) => {
317 for name in names {
318 self.function_throw_exact.entry(ascii_lowercase_atom(name)).or_default().push(index);
319 }
320 }
321 FunctionTarget::Prefix(prefix) => {
322 self.function_throw_prefix.push((ascii_lowercase_atom(prefix), index));
323 }
324 FunctionTarget::Namespace(ns) => {
325 let ns_lower = ns.to_lowercase();
326 let ns_pattern = if ns_lower.ends_with('\\') { ns_lower } else { format!("{ns_lower}\\") };
327 self.function_throw_namespace.push((ascii_lowercase_atom(&ns_pattern), index));
328 }
329 }
330
331 self.function_throw_providers.push(Box::new(provider));
332 }
333
334 pub fn register_method_throw_provider<P: MethodThrowTypeProvider + 'static>(&mut self, provider: P) {
335 let index = self.method_throw_providers.len();
336 let targets = P::targets();
337
338 let mut has_wildcards = false;
339 let mut wildcard_targets = Vec::new();
340
341 for target in targets {
342 if let Some(key) = target.index_key() {
343 self.method_throw_exact.entry(key).or_default().push(index);
344 } else {
345 has_wildcards = true;
346 wildcard_targets.push(*target);
347 }
348 }
349
350 if has_wildcards {
351 self.method_throw_wildcard.push((wildcard_targets, index));
352 }
353
354 self.method_throw_providers.push(Box::new(provider));
355 }
356
357 #[inline]
358 #[must_use]
359 pub fn has_program_hooks(&self) -> bool {
360 !self.program_hooks.is_empty()
361 }
362
363 #[inline]
364 #[must_use]
365 pub fn has_statement_hooks(&self) -> bool {
366 !self.statement_hooks.is_empty()
367 }
368
369 #[inline]
370 #[must_use]
371 pub fn has_expression_hooks(&self) -> bool {
372 !self.expression_hooks.is_empty()
373 }
374
375 #[inline]
376 #[must_use]
377 pub fn has_function_call_hooks(&self) -> bool {
378 !self.function_call_hooks.is_empty()
379 }
380
381 #[inline]
382 #[must_use]
383 pub fn has_method_call_hooks(&self) -> bool {
384 !self.method_call_hooks.is_empty()
385 }
386
387 #[inline]
388 #[must_use]
389 pub fn has_static_method_call_hooks(&self) -> bool {
390 !self.static_method_call_hooks.is_empty()
391 }
392
393 #[inline]
394 #[must_use]
395 pub fn has_nullsafe_method_call_hooks(&self) -> bool {
396 !self.nullsafe_method_call_hooks.is_empty()
397 }
398
399 #[inline]
400 #[must_use]
401 pub fn has_class_hooks(&self) -> bool {
402 !self.class_hooks.is_empty()
403 }
404
405 #[inline]
406 #[must_use]
407 pub fn has_interface_hooks(&self) -> bool {
408 !self.interface_hooks.is_empty()
409 }
410
411 #[inline]
412 #[must_use]
413 pub fn has_trait_hooks(&self) -> bool {
414 !self.trait_hooks.is_empty()
415 }
416
417 #[inline]
418 #[must_use]
419 pub fn has_enum_hooks(&self) -> bool {
420 !self.enum_hooks.is_empty()
421 }
422
423 #[inline]
424 #[must_use]
425 pub fn has_function_decl_hooks(&self) -> bool {
426 !self.function_decl_hooks.is_empty()
427 }
428
429 #[inline]
430 #[must_use]
431 pub fn has_property_initialization_providers(&self) -> bool {
432 !self.property_initialization_providers.is_empty()
433 }
434
435 #[inline]
436 #[must_use]
437 pub fn has_issue_filter_hooks(&self) -> bool {
438 !self.issue_filter_hooks.is_empty()
439 }
440
441 #[inline]
442 #[must_use]
443 pub fn has_function_assertion_providers(&self) -> bool {
444 !self.function_assertion_providers.is_empty()
445 }
446
447 #[inline]
448 #[must_use]
449 pub fn has_method_assertion_providers(&self) -> bool {
450 !self.method_assertion_providers.is_empty()
451 }
452
453 #[inline]
454 #[must_use]
455 pub fn has_expression_throw_providers(&self) -> bool {
456 !self.expression_throw_providers.is_empty()
457 }
458
459 #[inline]
460 #[must_use]
461 pub fn has_function_throw_providers(&self) -> bool {
462 !self.function_throw_providers.is_empty()
463 }
464
465 #[inline]
466 #[must_use]
467 pub fn has_method_throw_providers(&self) -> bool {
468 !self.method_throw_providers.is_empty()
469 }
470
471 pub fn before_program(
472 &self,
473 file: &File,
474 program: &Program<'_>,
475 context: &mut HookContext<'_, '_>,
476 ) -> PluginResult<HookAction> {
477 for hook in &self.program_hooks {
478 if let HookAction::Skip = hook.before_program(file, program, context)? {
479 return Ok(HookAction::Skip);
480 }
481 }
482 Ok(HookAction::Continue)
483 }
484
485 pub fn after_program(
486 &self,
487 file: &File,
488 program: &Program<'_>,
489 context: &mut HookContext<'_, '_>,
490 ) -> PluginResult<()> {
491 for hook in &self.program_hooks {
492 hook.after_program(file, program, context)?;
493 }
494 Ok(())
495 }
496
497 pub fn before_statement(
498 &self,
499 stmt: &Statement<'_>,
500 context: &mut HookContext<'_, '_>,
501 ) -> PluginResult<HookAction> {
502 for hook in &self.statement_hooks {
503 if let HookAction::Skip = hook.before_statement(stmt, context)? {
504 return Ok(HookAction::Skip);
505 }
506 }
507 Ok(HookAction::Continue)
508 }
509
510 pub fn after_statement(&self, stmt: &Statement<'_>, context: &mut HookContext<'_, '_>) -> PluginResult<()> {
511 for hook in &self.statement_hooks {
512 hook.after_statement(stmt, context)?;
513 }
514 Ok(())
515 }
516
517 pub fn before_expression(
518 &self,
519 expr: &Expression<'_>,
520 context: &mut HookContext<'_, '_>,
521 ) -> PluginResult<ExpressionHookResult> {
522 for hook in &self.expression_hooks {
523 let result = hook.before_expression(expr, context)?;
524 if result.should_skip() {
525 return Ok(result);
526 }
527 }
528 Ok(ExpressionHookResult::Continue)
529 }
530
531 pub fn after_expression(&self, expr: &Expression<'_>, context: &mut HookContext<'_, '_>) -> PluginResult<()> {
532 for hook in &self.expression_hooks {
533 hook.after_expression(expr, context)?;
534 }
535 Ok(())
536 }
537
538 pub fn before_function_call(
539 &self,
540 call: &FunctionCall<'_>,
541 context: &mut HookContext<'_, '_>,
542 ) -> PluginResult<ExpressionHookResult> {
543 for hook in &self.function_call_hooks {
544 let result = hook.before_function_call(call, context)?;
545 if result.should_skip() {
546 return Ok(result);
547 }
548 }
549 Ok(ExpressionHookResult::Continue)
550 }
551
552 pub fn after_function_call(&self, call: &FunctionCall<'_>, context: &mut HookContext<'_, '_>) -> PluginResult<()> {
553 for hook in &self.function_call_hooks {
554 hook.after_function_call(call, context)?;
555 }
556 Ok(())
557 }
558
559 pub fn before_method_call(
560 &self,
561 call: &MethodCall<'_>,
562 context: &mut HookContext<'_, '_>,
563 ) -> PluginResult<ExpressionHookResult> {
564 for hook in &self.method_call_hooks {
565 let result = hook.before_method_call(call, context)?;
566 if result.should_skip() {
567 return Ok(result);
568 }
569 }
570 Ok(ExpressionHookResult::Continue)
571 }
572
573 pub fn after_method_call(&self, call: &MethodCall<'_>, context: &mut HookContext<'_, '_>) -> PluginResult<()> {
574 for hook in &self.method_call_hooks {
575 hook.after_method_call(call, context)?;
576 }
577 Ok(())
578 }
579
580 pub fn before_static_method_call(
581 &self,
582 call: &StaticMethodCall<'_>,
583 context: &mut HookContext<'_, '_>,
584 ) -> PluginResult<ExpressionHookResult> {
585 for hook in &self.static_method_call_hooks {
586 let result = hook.before_static_method_call(call, context)?;
587 if result.should_skip() {
588 return Ok(result);
589 }
590 }
591 Ok(ExpressionHookResult::Continue)
592 }
593
594 pub fn after_static_method_call(
595 &self,
596 call: &StaticMethodCall<'_>,
597 context: &mut HookContext<'_, '_>,
598 ) -> PluginResult<()> {
599 for hook in &self.static_method_call_hooks {
600 hook.after_static_method_call(call, context)?;
601 }
602 Ok(())
603 }
604
605 pub fn before_nullsafe_method_call(
606 &self,
607 call: &NullSafeMethodCall<'_>,
608 context: &mut HookContext<'_, '_>,
609 ) -> PluginResult<ExpressionHookResult> {
610 for hook in &self.nullsafe_method_call_hooks {
611 let result = hook.before_nullsafe_method_call(call, context)?;
612 if result.should_skip() {
613 return Ok(result);
614 }
615 }
616 Ok(ExpressionHookResult::Continue)
617 }
618
619 pub fn after_nullsafe_method_call(
620 &self,
621 call: &NullSafeMethodCall<'_>,
622 context: &mut HookContext<'_, '_>,
623 ) -> PluginResult<()> {
624 for hook in &self.nullsafe_method_call_hooks {
625 hook.after_nullsafe_method_call(call, context)?;
626 }
627 Ok(())
628 }
629
630 pub fn on_enter_class(
631 &self,
632 class: &Class<'_>,
633 metadata: &ClassLikeMetadata,
634 context: &mut HookContext<'_, '_>,
635 ) -> PluginResult<()> {
636 for hook in &self.class_hooks {
637 hook.on_enter_class(class, metadata, context)?;
638 }
639 Ok(())
640 }
641
642 pub fn on_leave_class(
643 &self,
644 class: &Class<'_>,
645 metadata: &ClassLikeMetadata,
646 context: &mut HookContext<'_, '_>,
647 ) -> PluginResult<()> {
648 for hook in &self.class_hooks {
649 hook.on_leave_class(class, metadata, context)?;
650 }
651 Ok(())
652 }
653
654 pub fn on_enter_interface(
655 &self,
656 interface: &Interface<'_>,
657 metadata: &ClassLikeMetadata,
658 context: &mut HookContext<'_, '_>,
659 ) -> PluginResult<()> {
660 for hook in &self.interface_hooks {
661 hook.on_enter_interface(interface, metadata, context)?;
662 }
663 Ok(())
664 }
665
666 pub fn on_leave_interface(
667 &self,
668 interface: &Interface<'_>,
669 metadata: &ClassLikeMetadata,
670 context: &mut HookContext<'_, '_>,
671 ) -> PluginResult<()> {
672 for hook in &self.interface_hooks {
673 hook.on_leave_interface(interface, metadata, context)?;
674 }
675 Ok(())
676 }
677
678 pub fn on_enter_trait(
679 &self,
680 trait_: &Trait<'_>,
681 metadata: &ClassLikeMetadata,
682 context: &mut HookContext<'_, '_>,
683 ) -> PluginResult<()> {
684 for hook in &self.trait_hooks {
685 hook.on_enter_trait(trait_, metadata, context)?;
686 }
687 Ok(())
688 }
689
690 pub fn on_leave_trait(
691 &self,
692 trait_: &Trait<'_>,
693 metadata: &ClassLikeMetadata,
694 context: &mut HookContext<'_, '_>,
695 ) -> PluginResult<()> {
696 for hook in &self.trait_hooks {
697 hook.on_leave_trait(trait_, metadata, context)?;
698 }
699 Ok(())
700 }
701
702 pub fn on_enter_enum(
703 &self,
704 enum_: &Enum<'_>,
705 metadata: &ClassLikeMetadata,
706 context: &mut HookContext<'_, '_>,
707 ) -> PluginResult<()> {
708 for hook in &self.enum_hooks {
709 hook.on_enter_enum(enum_, metadata, context)?;
710 }
711 Ok(())
712 }
713
714 pub fn on_leave_enum(
715 &self,
716 enum_: &Enum<'_>,
717 metadata: &ClassLikeMetadata,
718 context: &mut HookContext<'_, '_>,
719 ) -> PluginResult<()> {
720 for hook in &self.enum_hooks {
721 hook.on_leave_enum(enum_, metadata, context)?;
722 }
723 Ok(())
724 }
725
726 pub fn on_enter_function(
727 &self,
728 function: &Function<'_>,
729 metadata: &FunctionLikeMetadata,
730 context: &mut HookContext<'_, '_>,
731 ) -> PluginResult<()> {
732 for hook in &self.function_decl_hooks {
733 hook.on_enter_function(function, metadata, context)?;
734 }
735 Ok(())
736 }
737
738 pub fn on_leave_function(
739 &self,
740 function: &Function<'_>,
741 metadata: &FunctionLikeMetadata,
742 context: &mut HookContext<'_, '_>,
743 ) -> PluginResult<()> {
744 for hook in &self.function_decl_hooks {
745 hook.on_leave_function(function, metadata, context)?;
746 }
747 Ok(())
748 }
749
750 fn get_function_provider_indices(&self, name: &str) -> Vec<usize> {
751 let lower_name = ascii_lowercase_atom(name);
752 let mut indices = Vec::new();
753
754 if let Some(idxs) = self.function_exact.get(&lower_name) {
755 indices.extend(idxs.iter().copied());
756 }
757
758 for (prefix, idx) in &self.function_prefix {
759 if lower_name.as_str().starts_with(prefix.as_str()) && !indices.contains(idx) {
760 indices.push(*idx);
761 }
762 }
763
764 for (ns, idx) in &self.function_namespace {
765 if lower_name.as_str().starts_with(ns.as_str()) && !indices.contains(idx) {
766 indices.push(*idx);
767 }
768 }
769
770 indices
771 }
772
773 fn get_method_provider_indices(&self, class_name: &str, method_name: &str) -> Vec<usize> {
774 use mago_atom::concat_atom;
775 let key =
776 concat_atom!(ascii_lowercase_atom(class_name).as_str(), "::", ascii_lowercase_atom(method_name).as_str());
777 let mut indices = Vec::new();
778
779 if let Some(idxs) = self.method_exact.get(&key) {
780 indices.extend(idxs.iter().copied());
781 }
782
783 for (targets, idx) in &self.method_wildcard {
784 if !indices.contains(idx) {
785 for target in targets {
786 if target.matches(class_name, method_name) {
787 indices.push(*idx);
788 break;
789 }
790 }
791 }
792 }
793
794 indices
795 }
796
797 #[must_use]
798 pub fn get_function_like_return_type<'ctx>(
799 &self,
800 codebase: &'ctx CodebaseMetadata,
801 block_context: &BlockContext<'ctx>,
802 artifacts: &AnalysisArtifacts,
803 function_like: &FunctionLikeIdentifier,
804 invocation: &Invocation<'ctx, '_, '_>,
805 ) -> Option<ProviderResult> {
806 match function_like {
807 FunctionLikeIdentifier::Function(name) => {
808 Some(self.get_function_return_type(codebase, block_context, artifacts, name, invocation))
809 }
810 FunctionLikeIdentifier::Method(class_name, method_name) => Some(self.get_method_return_type(
811 codebase,
812 block_context,
813 artifacts,
814 class_name,
815 method_name,
816 invocation,
817 )),
818 _ => None,
819 }
820 }
821
822 #[must_use]
823 pub fn get_function_return_type<'ctx>(
824 &self,
825 codebase: &'ctx CodebaseMetadata,
826 block_context: &BlockContext<'ctx>,
827 artifacts: &AnalysisArtifacts,
828 function_name: &str,
829 invocation: &Invocation<'ctx, '_, '_>,
830 ) -> ProviderResult {
831 let indices = self.get_function_provider_indices(function_name);
832 let mut all_issues = Vec::new();
833
834 for idx in indices {
835 let provider_context = ProviderContext::new(codebase, block_context, artifacts);
836 let invocation_info = InvocationInfo::new(invocation);
837
838 if let Some(ty) = self.function_providers[idx].get_return_type(&provider_context, &invocation_info) {
839 all_issues.extend(provider_context.take_issues());
840 return ProviderResult { return_type: Some(ty), issues: all_issues };
841 }
842
843 all_issues.extend(provider_context.take_issues());
844 }
845
846 ProviderResult { return_type: None, issues: all_issues }
847 }
848
849 #[must_use]
850 pub fn get_method_return_type<'ctx>(
851 &self,
852 codebase: &'ctx CodebaseMetadata,
853 block_context: &BlockContext<'ctx>,
854 artifacts: &AnalysisArtifacts,
855 class_name: &str,
856 method_name: &str,
857 invocation: &Invocation<'ctx, '_, '_>,
858 ) -> ProviderResult {
859 let indices = self.get_method_provider_indices(class_name, method_name);
860 let mut all_issues = Vec::new();
861
862 for idx in indices {
863 let provider_context = ProviderContext::new(codebase, block_context, artifacts);
864 let invocation_info = InvocationInfo::new(invocation);
865
866 if let Some(ty) =
867 self.method_providers[idx].get_return_type(&provider_context, class_name, method_name, &invocation_info)
868 {
869 all_issues.extend(provider_context.take_issues());
870 return ProviderResult { return_type: Some(ty), issues: all_issues };
871 }
872
873 all_issues.extend(provider_context.take_issues());
874 }
875
876 ProviderResult { return_type: None, issues: all_issues }
877 }
878
879 #[inline]
880 #[must_use]
881 pub fn function_provider_count(&self) -> usize {
882 self.function_providers.len()
883 }
884
885 #[inline]
886 #[must_use]
887 pub fn method_provider_count(&self) -> usize {
888 self.method_providers.len()
889 }
890
891 #[must_use]
895 pub fn is_property_initialized(
896 &self,
897 class_metadata: &ClassLikeMetadata,
898 property_metadata: &PropertyMetadata,
899 ) -> bool {
900 for provider in &self.property_initialization_providers {
901 if provider.is_property_initialized(class_metadata, property_metadata) {
902 return true;
903 }
904 }
905
906 false
907 }
908
909 fn get_function_assertion_provider_indices(&self, name: &str) -> Vec<usize> {
910 if self.function_assertion_exact.is_empty()
911 && self.function_assertion_prefix.is_empty()
912 && self.function_assertion_namespace.is_empty()
913 {
914 return Vec::new();
915 }
916
917 let lower_name = ascii_lowercase_atom(name);
918 let mut indices = Vec::new();
919
920 if let Some(idxs) = self.function_assertion_exact.get(&lower_name) {
921 indices.extend(idxs.iter().copied());
922 }
923
924 for (prefix, idx) in &self.function_assertion_prefix {
925 if lower_name.as_str().starts_with(prefix.as_str()) && !indices.contains(idx) {
926 indices.push(*idx);
927 }
928 }
929
930 for (ns, idx) in &self.function_assertion_namespace {
931 if lower_name.as_str().starts_with(ns.as_str()) && !indices.contains(idx) {
932 indices.push(*idx);
933 }
934 }
935
936 indices
937 }
938
939 fn get_method_assertion_provider_indices(&self, class_name: &str, method_name: &str) -> Vec<usize> {
940 if self.method_assertion_exact.is_empty() && self.method_assertion_wildcard.is_empty() {
941 return Vec::new();
942 }
943
944 use mago_atom::concat_atom;
945 let key =
946 concat_atom!(ascii_lowercase_atom(class_name).as_str(), "::", ascii_lowercase_atom(method_name).as_str());
947 let mut indices = Vec::new();
948
949 if let Some(idxs) = self.method_assertion_exact.get(&key) {
950 indices.extend(idxs.iter().copied());
951 }
952
953 for (targets, idx) in &self.method_assertion_wildcard {
954 if !indices.contains(idx) {
955 for target in targets {
956 if target.matches(class_name, method_name) {
957 indices.push(*idx);
958 break;
959 }
960 }
961 }
962 }
963
964 indices
965 }
966
967 #[must_use]
968 pub fn get_function_like_assertions<'ctx>(
969 &self,
970 codebase: &'ctx CodebaseMetadata,
971 block_context: &BlockContext<'ctx>,
972 artifacts: &AnalysisArtifacts,
973 function_like: &FunctionLikeIdentifier,
974 invocation: &Invocation<'ctx, '_, '_>,
975 ) -> Option<InvocationAssertions> {
976 match function_like {
977 FunctionLikeIdentifier::Function(name) => {
978 self.get_function_assertions(codebase, block_context, artifacts, name, invocation)
979 }
980 FunctionLikeIdentifier::Method(class_name, method_name) => {
981 self.get_method_assertions(codebase, block_context, artifacts, class_name, method_name, invocation)
982 }
983 _ => None,
984 }
985 }
986
987 #[must_use]
989 pub fn get_function_assertions<'ctx>(
990 &self,
991 codebase: &'ctx CodebaseMetadata,
992 block_context: &BlockContext<'ctx>,
993 artifacts: &AnalysisArtifacts,
994 function_name: &str,
995 invocation: &Invocation<'ctx, '_, '_>,
996 ) -> Option<InvocationAssertions> {
997 if self.function_assertion_providers.is_empty() {
998 return None;
999 }
1000
1001 let indices = self.get_function_assertion_provider_indices(function_name);
1002
1003 for idx in indices {
1004 let provider_context = ProviderContext::new(codebase, block_context, artifacts);
1005 let invocation_info = InvocationInfo::new(invocation);
1006
1007 if let Some(assertions) =
1008 self.function_assertion_providers[idx].get_assertions(&provider_context, &invocation_info)
1009 && !assertions.is_empty()
1010 {
1011 return Some(assertions);
1012 }
1013 }
1014
1015 None
1016 }
1017
1018 #[must_use]
1020 pub fn get_method_assertions<'ctx>(
1021 &self,
1022 codebase: &'ctx CodebaseMetadata,
1023 block_context: &BlockContext<'ctx>,
1024 artifacts: &AnalysisArtifacts,
1025 class_name: &str,
1026 method_name: &str,
1027 invocation: &Invocation<'ctx, '_, '_>,
1028 ) -> Option<InvocationAssertions> {
1029 if self.method_assertion_providers.is_empty() {
1030 return None;
1031 }
1032
1033 let indices = self.get_method_assertion_provider_indices(class_name, method_name);
1034
1035 for idx in indices {
1036 let provider_context = ProviderContext::new(codebase, block_context, artifacts);
1037 let invocation_info = InvocationInfo::new(invocation);
1038
1039 if let Some(assertions) = self.method_assertion_providers[idx].get_assertions(
1040 &provider_context,
1041 class_name,
1042 method_name,
1043 &invocation_info,
1044 ) && !assertions.is_empty()
1045 {
1046 return Some(assertions);
1047 }
1048 }
1049
1050 None
1051 }
1052
1053 fn get_function_throw_provider_indices(&self, name: &str) -> Vec<usize> {
1054 if self.function_throw_exact.is_empty()
1055 && self.function_throw_prefix.is_empty()
1056 && self.function_throw_namespace.is_empty()
1057 {
1058 return Vec::new();
1059 }
1060
1061 let lower_name = ascii_lowercase_atom(name);
1062 let mut indices = Vec::new();
1063
1064 if let Some(idxs) = self.function_throw_exact.get(&lower_name) {
1065 indices.extend(idxs.iter().copied());
1066 }
1067
1068 for (prefix, idx) in &self.function_throw_prefix {
1069 if lower_name.as_str().starts_with(prefix.as_str()) && !indices.contains(idx) {
1070 indices.push(*idx);
1071 }
1072 }
1073
1074 for (ns, idx) in &self.function_throw_namespace {
1075 if lower_name.as_str().starts_with(ns.as_str()) && !indices.contains(idx) {
1076 indices.push(*idx);
1077 }
1078 }
1079
1080 indices
1081 }
1082
1083 fn get_method_throw_provider_indices(&self, class_name: &str, method_name: &str) -> Vec<usize> {
1084 if self.method_throw_providers.is_empty()
1085 && self.method_throw_exact.is_empty()
1086 && self.method_throw_wildcard.is_empty()
1087 {
1088 return Vec::new();
1089 }
1090
1091 use mago_atom::concat_atom;
1092 let key =
1093 concat_atom!(ascii_lowercase_atom(class_name).as_str(), "::", ascii_lowercase_atom(method_name).as_str());
1094 let mut indices = Vec::new();
1095
1096 if let Some(idxs) = self.method_throw_exact.get(&key) {
1097 indices.extend(idxs.iter().copied());
1098 }
1099
1100 for (targets, idx) in &self.method_throw_wildcard {
1101 if !indices.contains(idx) {
1102 for target in targets {
1103 if target.matches(class_name, method_name) {
1104 indices.push(*idx);
1105 break;
1106 }
1107 }
1108 }
1109 }
1110
1111 indices
1112 }
1113
1114 #[must_use]
1116 pub fn get_expression_thrown_exceptions<'ctx>(
1117 &self,
1118 codebase: &'ctx CodebaseMetadata,
1119 block_context: &BlockContext<'ctx>,
1120 artifacts: &AnalysisArtifacts,
1121 expression: &mago_syntax::ast::Expression<'_>,
1122 ) -> AtomSet {
1123 let mut exceptions = AtomSet::default();
1124
1125 for provider in &self.expression_throw_providers {
1126 let provider_context = ProviderContext::new(codebase, block_context, artifacts);
1127 exceptions.extend(provider.get_thrown_exceptions(&provider_context, expression));
1128 }
1129
1130 exceptions
1131 }
1132
1133 #[must_use]
1135 pub fn get_function_thrown_exceptions<'ctx>(
1136 &self,
1137 codebase: &'ctx CodebaseMetadata,
1138 block_context: &BlockContext<'ctx>,
1139 artifacts: &AnalysisArtifacts,
1140 function_name: &str,
1141 invocation: &Invocation<'ctx, '_, '_>,
1142 ) -> AtomSet {
1143 let mut exceptions = AtomSet::default();
1144 let indices = self.get_function_throw_provider_indices(function_name);
1145
1146 for idx in indices {
1147 let provider_context = ProviderContext::new(codebase, block_context, artifacts);
1148 let invocation_info = InvocationInfo::new(invocation);
1149 exceptions
1150 .extend(self.function_throw_providers[idx].get_thrown_exceptions(&provider_context, &invocation_info));
1151 }
1152
1153 exceptions
1154 }
1155
1156 #[must_use]
1158 pub fn get_method_thrown_exceptions<'ctx>(
1159 &self,
1160 codebase: &'ctx CodebaseMetadata,
1161 block_context: &BlockContext<'ctx>,
1162 artifacts: &AnalysisArtifacts,
1163 class_name: &str,
1164 method_name: &str,
1165 invocation: &Invocation<'ctx, '_, '_>,
1166 ) -> AtomSet {
1167 let mut exceptions = AtomSet::default();
1168 let indices = self.get_method_throw_provider_indices(class_name, method_name);
1169
1170 for idx in indices {
1171 let provider_context = ProviderContext::new(codebase, block_context, artifacts);
1172 let invocation_info = InvocationInfo::new(invocation);
1173 exceptions.extend(self.method_throw_providers[idx].get_thrown_exceptions(
1174 &provider_context,
1175 class_name,
1176 method_name,
1177 &invocation_info,
1178 ));
1179 }
1180
1181 exceptions
1182 }
1183
1184 #[must_use]
1188 pub fn filter_issues(&self, file: &File, issues: IssueCollection) -> IssueCollection {
1189 if self.issue_filter_hooks.is_empty() {
1190 return issues;
1191 }
1192
1193 let mut filtered = IssueCollection::default();
1194
1195 for issue in issues {
1196 let mut keep = true;
1197 for hook in &self.issue_filter_hooks {
1198 if let Ok(IssueFilterDecision::Remove) = hook.filter_issue(file, &issue) {
1199 keep = false;
1200 break;
1201 }
1202 }
1203
1204 if keep {
1205 filtered.push(issue);
1206 }
1207 }
1208
1209 filtered
1210 }
1211}
1212
1213#[cfg(test)]
1214mod tests {
1215 use super::*;
1216 use crate::plugin::provider::Provider;
1217 use crate::plugin::provider::ProviderMeta;
1218
1219 static TEST_META: ProviderMeta = ProviderMeta::new("test::provider", "Test Provider", "A test provider");
1220
1221 struct TestFunctionProvider;
1222
1223 impl Provider for TestFunctionProvider {
1224 fn meta() -> &'static ProviderMeta {
1225 &TEST_META
1226 }
1227 }
1228
1229 impl FunctionReturnTypeProvider for TestFunctionProvider {
1230 fn targets() -> FunctionTarget {
1231 FunctionTarget::Exact("test_func")
1232 }
1233
1234 fn get_return_type(
1235 &self,
1236 _context: &ProviderContext<'_, '_, '_>,
1237 _invocation: &InvocationInfo<'_, '_, '_>,
1238 ) -> Option<TUnion> {
1239 None
1240 }
1241 }
1242
1243 #[test]
1244 fn test_register_function_provider() {
1245 let mut registry = PluginRegistry::new();
1246 registry.register_function_provider(TestFunctionProvider);
1247
1248 assert_eq!(registry.function_provider_count(), 1);
1249 let indices = registry.get_function_provider_indices("test_func");
1250 assert_eq!(indices.len(), 1);
1251 }
1252
1253 #[test]
1254 fn test_function_exact_match() {
1255 let mut registry = PluginRegistry::new();
1256 registry.register_function_provider(TestFunctionProvider);
1257
1258 let indices = registry.get_function_provider_indices("test_func");
1259 assert_eq!(indices.len(), 1);
1260
1261 let indices = registry.get_function_provider_indices("TEST_FUNC");
1262 assert_eq!(indices.len(), 1);
1263
1264 let indices = registry.get_function_provider_indices("other_func");
1265 assert!(indices.is_empty());
1266 }
1267}