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