1use std::collections::HashSet;
2use std::fmt;
3use std::sync::Arc;
4
5use owo_colors::OwoColorize;
6use serde::{Deserialize, Serialize};
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
13pub enum Severity {
14 Info,
16 Warning,
18 Error,
20}
21
22impl fmt::Display for Severity {
23 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
24 match self {
25 Severity::Info => write!(f, "info"),
26 Severity::Warning => write!(f, "warning"),
27 Severity::Error => write!(f, "error"),
28 }
29 }
30}
31
32pub use mir_types::Location;
37
38#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
43#[non_exhaustive]
44pub enum IssueKind {
45 NonStaticSelfCall { class: String, method: String },
51 InvalidScope {
52 in_class: bool,
54 },
55 UndefinedVariable { name: String },
58 UndefinedFunction { name: String },
61 UndefinedMethod { class: String, method: String },
64 UndefinedClass { name: String },
67 UndefinedProperty { class: String, property: String },
70 UndefinedConstant { name: String },
73 InaccessibleClassConstant { class: String, constant: String },
76 PossiblyUndefinedVariable { name: String },
79 UndefinedTrait { name: String },
82 ParentNotFound,
85 InvalidStringClass { actual: String },
88
89 NullArgument { param: String, fn_name: String },
93 NullPropertyFetch { property: String },
96 NullMethodCall { method: String },
99 NullArrayAccess,
102 PossiblyNullArgument { param: String, fn_name: String },
105 PossiblyInvalidArgument {
108 param: String,
109 fn_name: String,
110 expected: String,
111 actual: String,
112 },
113 PossiblyNullPropertyFetch { property: String },
116 PossiblyNullMethodCall { method: String },
119 PossiblyNullArrayAccess,
122 NullableReturnStatement { expected: String, actual: String },
125
126 InvalidReturnType { expected: String, actual: String },
130 InvalidArgument {
133 param: String,
134 fn_name: String,
135 expected: String,
136 actual: String,
137 },
138 TooFewArguments {
141 fn_name: String,
142 expected: usize,
143 actual: usize,
144 },
145 TooManyArguments {
148 fn_name: String,
149 expected: usize,
150 actual: usize,
151 },
152 InvalidNamedArgument { fn_name: String, name: String },
155 InvalidNamedArguments { fn_name: String },
158 InvalidPassByReference { fn_name: String, param: String },
161 InvalidPropertyFetch { ty: String },
164 InvalidArrayAccess { ty: String },
167 PossiblyInvalidArrayAccess { ty: String },
170 InvalidArrayAssignment { ty: String },
173 InvalidPropertyAssignment {
176 property: String,
177 expected: String,
178 actual: String,
179 },
180 InvalidCast { from: String, to: String },
183 InvalidStaticInvocation { class: String, method: String },
186 InvalidOperand {
190 op: String,
191 left: String,
192 right: String,
193 },
194 PossiblyInvalidOperand {
197 op: String,
198 left: String,
199 right: String,
200 },
201 PossiblyNullOperand { op: String, ty: String },
204 RawObjectIteration { ty: String },
207 PossiblyRawObjectIteration { ty: String },
210 MismatchingDocblockReturnType { declared: String, inferred: String },
213 MismatchingDocblockParamType {
216 param: String,
217 declared: String,
218 inferred: String,
219 },
220 TypeCheckMismatch {
223 var: String,
224 expected: String,
225 actual: String,
226 },
227
228 Trace { variable: String, type_info: String },
231
232 InvalidArrayOffset { expected: String, actual: String },
236 NonExistentArrayOffset { key: String },
240 PossiblyInvalidArrayOffset { expected: String, actual: String },
243
244 RedundantCondition { ty: String },
248 RedundantCast { from: String, to: String },
251 UnnecessaryVarAnnotation { var: String },
254 TypeDoesNotContainType { left: String, right: String },
257 ParadoxicalCondition { value: String },
260 DocblockTypeContradiction { expr: String, declared: String },
265 UnevaluatedCode { reason: String },
272
273 UnusedVariable { name: String },
277 UnusedParam { name: String },
280 UnreachableCode,
283 UnhandledMatchCondition { detail: String },
286 UnusedMethod { class: String, method: String },
289 UnusedProperty { class: String, property: String },
292 UnusedFunction { name: String },
295 UnusedForeachValue { name: String },
298 UnusedClass { class: String },
301 UnusedPsalmSuppress { kind: String },
305
306 ArgumentTypeCoercion {
309 param: String,
310 fn_name: String,
311 expected: String,
312 actual: String,
313 },
314
315 PropertyTypeCoercion {
318 property: String,
319 expected: String,
320 actual: String,
321 },
322
323 ImpurePropertyAssignment { property: String },
326 ImpureMethodCall { method: String },
328 ImpureGlobalVariable { variable: String },
330 ImpureStaticVariable { variable: String },
332 ImpureFunctionCall { fn_name: String },
336
337 ReadonlyPropertyAssignment { class: String, property: String },
341
342 UnimplementedAbstractMethod { class: String, method: String },
346 UnimplementedInterfaceMethod {
349 class: String,
350 interface: String,
351 method: String,
352 },
353 MethodSignatureMismatch {
356 class: String,
357 method: String,
358 detail: String,
359 },
360 OverriddenMethodAccess { class: String, method: String },
363 OverriddenPropertyAccess { class: String, property: String },
366 DirectConstructorCall { class: String },
369 InvalidExtendClass { parent: String, child: String },
372 FinalMethodOverridden {
375 class: String,
376 method: String,
377 parent: String,
378 },
379 AbstractInstantiation { class: String },
382 AbstractMethodCall { class: String, method: String },
385 InterfaceInstantiation { class: String },
388 InvalidOverride {
392 class: String,
393 method: String,
394 detail: String,
395 },
396
397 TaintedInput { sink: String },
401 TaintedHtml,
404 TaintedSql,
407 TaintedShell,
410 TaintedLlmPrompt,
414
415 InvalidTemplateParam {
419 name: String,
420 expected_bound: String,
421 actual: String,
422 },
423 ShadowedTemplateParam { name: String },
426 IfThisIsMismatch {
431 class: String,
432 method: String,
433 expected: String,
434 actual: String,
435 },
436
437 DeprecatedCall {
441 name: String,
442 message: Option<Arc<str>>,
443 },
444 DeprecatedProperty {
447 class: String,
448 property: String,
449 message: Option<Arc<str>>,
450 },
451 DeprecatedConstant {
454 class: String,
455 constant: String,
456 message: Option<Arc<str>>,
457 },
458 DeprecatedInterface {
461 name: String,
462 message: Option<Arc<str>>,
463 },
464 DeprecatedTrait {
467 name: String,
468 message: Option<Arc<str>>,
469 },
470 DeprecatedMethodCall {
473 class: String,
474 method: String,
475 message: Option<Arc<str>>,
476 },
477 DeprecatedMethod {
480 class: String,
481 method: String,
482 message: Option<Arc<str>>,
483 },
484 DeprecatedClass {
487 name: String,
488 message: Option<Arc<str>>,
489 },
490 InternalMethod { class: String, method: String },
493 MissingReturnType { fn_name: String },
496 MissingClosureReturnType,
499 MissingParamType { fn_name: String, param: String },
502 MissingPropertyType { class: String, property: String },
505 InvalidThrow { ty: String },
508 InvalidCatch { ty: String },
511 MissingThrowsDocblock { class: String },
514 ImplicitToStringCast { class: String },
517 ImplicitFloatToIntCast { from: String },
520 ParseError { message: String },
523 InvalidDocblock { message: String },
526 MixedArgument { param: String, fn_name: String },
529 MixedAssignment { var: String },
532 MixedMethodCall { method: String },
535 UnsupportedReferenceUsage,
538 NoInterfaceProperties { property: String },
542 UndefinedDocblockClass { name: String },
546 MissingConstructor { class: String },
549 MixedFunctionCall,
552 MixedReturnStatement { declared: String },
555 MixedPropertyFetch { property: String },
558 MixedPropertyAssignment { property: String },
561 MixedArrayAccess,
564 MixedArrayOffset,
567 MixedClone,
570 InvalidClone { ty: String },
574 PossiblyInvalidClone { ty: String },
578 InvalidToString { class: String },
582 CircularInheritance { class: String },
585
586 InvalidTraitUse { trait_name: String, reason: String },
590 ForbiddenCode { message: String },
593
594 InvalidAttribute { message: String },
598 UndefinedAttributeClass { name: String },
601
602 WrongCaseFunction { used: String, canonical: String },
606 WrongCaseMethod {
609 class: String,
610 used: String,
611 canonical: String,
612 },
613 WrongCaseClass { used: String, canonical: String },
616 DuplicateClass { name: String },
619 DuplicateInterface { name: String },
622 DuplicateTrait { name: String },
625 DuplicateEnum { name: String },
628 DuplicateFunction { name: String },
631}
632
633fn append_deprecation_message(base: String, message: &Option<Arc<str>>) -> String {
634 match message.as_deref().filter(|m| !m.is_empty()) {
635 Some(msg) => format!("{base}: {msg}"),
636 None => base,
637 }
638}
639
640impl IssueKind {
641 pub fn default_severity(&self) -> Severity {
643 match self {
644 IssueKind::NonStaticSelfCall { .. }
646 | IssueKind::DirectConstructorCall { .. }
647 | IssueKind::InvalidScope { .. }
648 | IssueKind::UndefinedVariable { .. }
649 | IssueKind::UndefinedFunction { .. }
650 | IssueKind::UndefinedMethod { .. }
651 | IssueKind::UndefinedClass { .. }
652 | IssueKind::UndefinedConstant { .. }
653 | IssueKind::InaccessibleClassConstant { .. }
654 | IssueKind::InvalidReturnType { .. }
655 | IssueKind::InvalidArgument { .. }
656 | IssueKind::TooFewArguments { .. }
657 | IssueKind::TooManyArguments { .. }
658 | IssueKind::InvalidNamedArgument { .. }
659 | IssueKind::InvalidNamedArguments { .. }
660 | IssueKind::InvalidPassByReference { .. }
661 | IssueKind::InvalidThrow { .. }
662 | IssueKind::InvalidCatch { .. }
663 | IssueKind::InvalidStaticInvocation { .. }
664 | IssueKind::UnimplementedAbstractMethod { .. }
665 | IssueKind::UnimplementedInterfaceMethod { .. }
666 | IssueKind::MethodSignatureMismatch { .. }
667 | IssueKind::InvalidExtendClass { .. }
668 | IssueKind::FinalMethodOverridden { .. }
669 | IssueKind::AbstractInstantiation { .. }
670 | IssueKind::AbstractMethodCall { .. }
671 | IssueKind::InterfaceInstantiation { .. }
672 | IssueKind::InvalidOverride { .. }
673 | IssueKind::InvalidTemplateParam { .. }
674 | IssueKind::ReadonlyPropertyAssignment { .. }
675 | IssueKind::ParseError { .. }
676 | IssueKind::TaintedInput { .. }
677 | IssueKind::TaintedHtml
678 | IssueKind::TaintedSql
679 | IssueKind::TaintedShell
680 | IssueKind::TaintedLlmPrompt
681 | IssueKind::CircularInheritance { .. }
682 | IssueKind::InvalidTraitUse { .. }
683 | IssueKind::UndefinedTrait { .. }
684 | IssueKind::InvalidClone { .. }
685 | IssueKind::InvalidToString { .. }
686 | IssueKind::TypeCheckMismatch { .. }
687 | IssueKind::ParentNotFound => Severity::Error,
688 IssueKind::Trace { .. } => Severity::Info,
689
690 IssueKind::NullArgument { .. }
692 | IssueKind::NullPropertyFetch { .. }
693 | IssueKind::NullMethodCall { .. }
694 | IssueKind::NullArrayAccess
695 | IssueKind::NullableReturnStatement { .. }
696 | IssueKind::InvalidPropertyFetch { .. }
697 | IssueKind::InvalidArrayAccess { .. }
698 | IssueKind::InvalidArrayAssignment { .. }
699 | IssueKind::InvalidPropertyAssignment { .. }
700 | IssueKind::InvalidArrayOffset { .. }
701 | IssueKind::NonExistentArrayOffset { .. }
702 | IssueKind::PossiblyInvalidArrayOffset { .. }
703 | IssueKind::UndefinedProperty { .. }
704 | IssueKind::InvalidOperand { .. }
705 | IssueKind::OverriddenMethodAccess { .. }
706 | IssueKind::OverriddenPropertyAccess { .. }
707 | IssueKind::ImplicitToStringCast { .. }
708 | IssueKind::ImplicitFloatToIntCast { .. }
709 | IssueKind::UnusedVariable { .. }
710 | IssueKind::UnusedForeachValue { .. }
711 | IssueKind::ImpurePropertyAssignment { .. }
712 | IssueKind::ImpureMethodCall { .. }
713 | IssueKind::ImpureGlobalVariable { .. }
714 | IssueKind::ImpureStaticVariable { .. }
715 | IssueKind::ImpureFunctionCall { .. }
716 | IssueKind::UnsupportedReferenceUsage
717 | IssueKind::ParadoxicalCondition { .. }
718 | IssueKind::UnhandledMatchCondition { .. }
719 | IssueKind::InvalidStringClass { .. }
720 | IssueKind::ForbiddenCode { .. } => Severity::Warning,
721
722 IssueKind::PossiblyUndefinedVariable { .. } => Severity::Warning,
724
725 IssueKind::PossiblyNullArgument { .. }
727 | IssueKind::PossiblyInvalidArgument { .. }
728 | IssueKind::PossiblyNullPropertyFetch { .. }
729 | IssueKind::PossiblyNullMethodCall { .. }
730 | IssueKind::PossiblyNullArrayAccess
731 | IssueKind::PossiblyInvalidArrayAccess { .. }
732 | IssueKind::PossiblyInvalidClone { .. }
733 | IssueKind::PossiblyInvalidOperand { .. }
734 | IssueKind::PossiblyNullOperand { .. }
735 | IssueKind::PossiblyRawObjectIteration { .. } => Severity::Info,
736
737 IssueKind::RawObjectIteration { .. } => Severity::Warning,
738
739 IssueKind::RedundantCondition { .. }
741 | IssueKind::RedundantCast { .. }
742 | IssueKind::UnnecessaryVarAnnotation { .. }
743 | IssueKind::TypeDoesNotContainType { .. }
744 | IssueKind::DocblockTypeContradiction { .. }
745 | IssueKind::UnevaluatedCode { .. }
746 | IssueKind::IfThisIsMismatch { .. }
747 | IssueKind::UnusedParam { .. }
748 | IssueKind::UnreachableCode
749 | IssueKind::UnusedMethod { .. }
750 | IssueKind::UnusedProperty { .. }
751 | IssueKind::UnusedFunction { .. }
752 | IssueKind::UnusedClass { .. }
753 | IssueKind::UnusedPsalmSuppress { .. }
754 | IssueKind::ArgumentTypeCoercion { .. }
755 | IssueKind::PropertyTypeCoercion { .. }
756 | IssueKind::DeprecatedCall { .. }
757 | IssueKind::DeprecatedProperty { .. }
758 | IssueKind::DeprecatedConstant { .. }
759 | IssueKind::DeprecatedInterface { .. }
760 | IssueKind::DeprecatedTrait { .. }
761 | IssueKind::DeprecatedMethodCall { .. }
762 | IssueKind::DeprecatedMethod { .. }
763 | IssueKind::DeprecatedClass { .. }
764 | IssueKind::InternalMethod { .. }
765 | IssueKind::MissingReturnType { .. }
766 | IssueKind::MissingClosureReturnType
767 | IssueKind::MissingParamType { .. }
768 | IssueKind::MissingPropertyType { .. }
769 | IssueKind::MismatchingDocblockReturnType { .. }
770 | IssueKind::MismatchingDocblockParamType { .. }
771 | IssueKind::InvalidDocblock { .. }
772 | IssueKind::InvalidCast { .. }
773 | IssueKind::MixedArgument { .. }
774 | IssueKind::MixedAssignment { .. }
775 | IssueKind::MixedMethodCall { .. }
776 | IssueKind::NoInterfaceProperties { .. }
777 | IssueKind::UndefinedDocblockClass { .. }
778 | IssueKind::MissingConstructor { .. }
779 | IssueKind::MixedFunctionCall
780 | IssueKind::MixedReturnStatement { .. }
781 | IssueKind::MixedPropertyFetch { .. }
782 | IssueKind::MixedPropertyAssignment { .. }
783 | IssueKind::MixedArrayAccess
784 | IssueKind::MixedArrayOffset
785 | IssueKind::MixedClone
786 | IssueKind::ShadowedTemplateParam { .. }
787 | IssueKind::MissingThrowsDocblock { .. }
788 | IssueKind::WrongCaseFunction { .. }
789 | IssueKind::WrongCaseMethod { .. }
790 | IssueKind::WrongCaseClass { .. }
791 | IssueKind::InvalidAttribute { .. }
792 | IssueKind::UndefinedAttributeClass { .. } => Severity::Info,
793 IssueKind::DuplicateClass { .. }
794 | IssueKind::DuplicateInterface { .. }
795 | IssueKind::DuplicateTrait { .. }
796 | IssueKind::DuplicateEnum { .. }
797 | IssueKind::DuplicateFunction { .. } => Severity::Error,
798 }
799 }
800
801 pub fn code(&self) -> &'static str {
829 match self {
830 IssueKind::NonStaticSelfCall { .. } => "MIR0216",
832 IssueKind::DirectConstructorCall { .. } => "MIR0217",
833 IssueKind::InvalidScope { .. } => "MIR0001",
834 IssueKind::UndefinedVariable { .. } => "MIR0002",
835 IssueKind::UndefinedFunction { .. } => "MIR0003",
836 IssueKind::UndefinedMethod { .. } => "MIR0004",
837 IssueKind::UndefinedClass { .. } => "MIR0005",
838 IssueKind::UndefinedProperty { .. } => "MIR0006",
839 IssueKind::UndefinedConstant { .. } => "MIR0007",
840 IssueKind::InaccessibleClassConstant { .. } => "MIR0011",
841 IssueKind::PossiblyUndefinedVariable { .. } => "MIR0008",
842 IssueKind::UndefinedTrait { .. } => "MIR0009",
843 IssueKind::ParentNotFound => "MIR0010",
844
845 IssueKind::NullArgument { .. } => "MIR0100",
847 IssueKind::NullPropertyFetch { .. } => "MIR0101",
848 IssueKind::NullMethodCall { .. } => "MIR0102",
849 IssueKind::NullArrayAccess => "MIR0103",
850 IssueKind::PossiblyNullArgument { .. } => "MIR0104",
851 IssueKind::PossiblyInvalidArgument { .. } => "MIR0105",
852 IssueKind::PossiblyNullPropertyFetch { .. } => "MIR0106",
853 IssueKind::PossiblyNullMethodCall { .. } => "MIR0107",
854 IssueKind::PossiblyNullArrayAccess => "MIR0108",
855 IssueKind::NullableReturnStatement { .. } => "MIR0109",
856
857 IssueKind::InvalidReturnType { .. } => "MIR0200",
859 IssueKind::InvalidArgument { .. } => "MIR0201",
860 IssueKind::TooFewArguments { .. } => "MIR0202",
861 IssueKind::TooManyArguments { .. } => "MIR0203",
862 IssueKind::InvalidNamedArgument { .. } => "MIR0204",
863 IssueKind::InvalidNamedArguments { .. } => "MIR0224",
864 IssueKind::InvalidPassByReference { .. } => "MIR0205",
865 IssueKind::InvalidPropertyFetch { .. } => "MIR0218",
866 IssueKind::InvalidArrayAccess { .. } => "MIR0219",
867 IssueKind::PossiblyInvalidArrayAccess { .. } => "MIR0227",
868 IssueKind::InvalidArrayAssignment { .. } => "MIR0220",
869 IssueKind::InvalidPropertyAssignment { .. } => "MIR0206",
870 IssueKind::InvalidCast { .. } => "MIR0207",
871 IssueKind::InvalidStaticInvocation { .. } => "MIR0215",
872 IssueKind::InvalidOperand { .. } => "MIR0208",
873 IssueKind::PossiblyInvalidOperand { .. } => "MIR0213",
874 IssueKind::PossiblyNullOperand { .. } => "MIR0214",
875 IssueKind::RawObjectIteration { .. } => "MIR0222",
876 IssueKind::PossiblyRawObjectIteration { .. } => "MIR0223",
877 IssueKind::MismatchingDocblockReturnType { .. } => "MIR0209",
878 IssueKind::MismatchingDocblockParamType { .. } => "MIR0210",
879 IssueKind::InvalidStringClass { .. } => "MIR0211",
880 IssueKind::TypeCheckMismatch { .. } => "MIR0212",
881 IssueKind::Trace { .. } => "MIR0221",
882 IssueKind::ArgumentTypeCoercion { .. } => "MIR0225",
883 IssueKind::PropertyTypeCoercion { .. } => "MIR0226",
884
885 IssueKind::InvalidArrayOffset { .. } => "MIR0300",
887 IssueKind::NonExistentArrayOffset { .. } => "MIR0301",
888 IssueKind::PossiblyInvalidArrayOffset { .. } => "MIR0302",
889
890 IssueKind::RedundantCondition { .. } => "MIR0400",
892 IssueKind::RedundantCast { .. } => "MIR0401",
893 IssueKind::UnnecessaryVarAnnotation { .. } => "MIR0402",
894 IssueKind::TypeDoesNotContainType { .. } => "MIR0403",
895 IssueKind::ParadoxicalCondition { .. } => "MIR0404",
896 IssueKind::UnhandledMatchCondition { .. } => "MIR0405",
897 IssueKind::DocblockTypeContradiction { .. } => "MIR0406",
898 IssueKind::UnevaluatedCode { .. } => "MIR0407",
899
900 IssueKind::UnusedVariable { .. } => "MIR0500",
902 IssueKind::UnusedParam { .. } => "MIR0501",
903 IssueKind::UnreachableCode => "MIR0502",
904 IssueKind::UnusedMethod { .. } => "MIR0503",
905 IssueKind::UnusedProperty { .. } => "MIR0504",
906 IssueKind::UnusedFunction { .. } => "MIR0505",
907 IssueKind::UnusedForeachValue { .. } => "MIR0506",
908 IssueKind::UnusedClass { .. } => "MIR0507",
909 IssueKind::UnusedPsalmSuppress { .. } => "MIR0508",
910
911 IssueKind::ImpurePropertyAssignment { .. } => "MIR1700",
913 IssueKind::ImpureMethodCall { .. } => "MIR1701",
914 IssueKind::ImpureGlobalVariable { .. } => "MIR1702",
915 IssueKind::ImpureStaticVariable { .. } => "MIR1703",
916 IssueKind::ImpureFunctionCall { .. } => "MIR1704",
917 IssueKind::UnsupportedReferenceUsage => "MIR1506",
918 IssueKind::NoInterfaceProperties { .. } => "MIR1504",
919 IssueKind::UndefinedDocblockClass { .. } => "MIR1505",
920 IssueKind::MissingConstructor { .. } => "MIR1507",
921 IssueKind::MixedFunctionCall => "MIR1211",
922 IssueKind::MixedReturnStatement { .. } => "MIR1212",
923
924 IssueKind::ReadonlyPropertyAssignment { .. } => "MIR0600",
926
927 IssueKind::UnimplementedAbstractMethod { .. } => "MIR0700",
929 IssueKind::UnimplementedInterfaceMethod { .. } => "MIR0701",
930 IssueKind::MethodSignatureMismatch { .. } => "MIR0702",
931 IssueKind::OverriddenMethodAccess { .. } => "MIR0703",
932 IssueKind::OverriddenPropertyAccess { .. } => "MIR0710",
933 IssueKind::InvalidExtendClass { .. } => "MIR0704",
934 IssueKind::FinalMethodOverridden { .. } => "MIR0705",
935 IssueKind::AbstractInstantiation { .. } => "MIR0706",
936 IssueKind::AbstractMethodCall { .. } => "MIR0711",
937 IssueKind::InterfaceInstantiation { .. } => "MIR0709",
938 IssueKind::CircularInheritance { .. } => "MIR0707",
939 IssueKind::InvalidOverride { .. } => "MIR0708",
940
941 IssueKind::TaintedInput { .. } => "MIR0800",
943 IssueKind::TaintedHtml => "MIR0801",
944 IssueKind::TaintedSql => "MIR0802",
945 IssueKind::TaintedShell => "MIR0803",
946 IssueKind::TaintedLlmPrompt => "MIR0804",
947
948 IssueKind::InvalidTemplateParam { .. } => "MIR0900",
950 IssueKind::ShadowedTemplateParam { .. } => "MIR0901",
951 IssueKind::IfThisIsMismatch { .. } => "MIR0902",
952
953 IssueKind::DeprecatedCall { .. } => "MIR1000",
955 IssueKind::WrongCaseFunction { .. } => "MIR1009",
956 IssueKind::WrongCaseMethod { .. } => "MIR1010",
957 IssueKind::WrongCaseClass { .. } => "MIR1011",
958 IssueKind::DeprecatedProperty { .. } => "MIR1005",
959 IssueKind::DeprecatedInterface { .. } => "MIR1006",
960 IssueKind::DeprecatedTrait { .. } => "MIR1007",
961 IssueKind::DeprecatedConstant { .. } => "MIR1008",
962 IssueKind::DeprecatedMethodCall { .. } => "MIR1001",
963 IssueKind::DeprecatedMethod { .. } => "MIR1002",
964 IssueKind::DeprecatedClass { .. } => "MIR1003",
965 IssueKind::InternalMethod { .. } => "MIR1004",
966
967 IssueKind::MissingReturnType { .. } => "MIR1100",
969 IssueKind::MissingParamType { .. } => "MIR1101",
970 IssueKind::MissingPropertyType { .. } => "MIR1104",
971 IssueKind::MissingClosureReturnType => "MIR1105",
972 IssueKind::MissingThrowsDocblock { .. } => "MIR1102",
973 IssueKind::InvalidDocblock { .. } => "MIR1103",
974
975 IssueKind::MixedArgument { .. } => "MIR1200",
977 IssueKind::MixedAssignment { .. } => "MIR1201",
978 IssueKind::MixedMethodCall { .. } => "MIR1202",
979 IssueKind::MixedPropertyFetch { .. } => "MIR1203",
980 IssueKind::MixedPropertyAssignment { .. } => "MIR1208",
981 IssueKind::MixedArrayAccess => "MIR1209",
982 IssueKind::MixedArrayOffset => "MIR1210",
983 IssueKind::MixedClone => "MIR1204",
984 IssueKind::InvalidClone { .. } => "MIR1205",
985 IssueKind::PossiblyInvalidClone { .. } => "MIR1206",
986 IssueKind::InvalidToString { .. } => "MIR1207",
987
988 IssueKind::InvalidTraitUse { .. } => "MIR1300",
990 IssueKind::ForbiddenCode { .. } => "MIR1301",
991
992 IssueKind::ParseError { .. } => "MIR1400",
994
995 IssueKind::InvalidAttribute { .. } => "MIR1600",
997 IssueKind::UndefinedAttributeClass { .. } => "MIR1601",
998 IssueKind::DuplicateClass { .. } => "MIR1602",
999 IssueKind::DuplicateInterface { .. } => "MIR1603",
1000 IssueKind::DuplicateTrait { .. } => "MIR1604",
1001 IssueKind::DuplicateEnum { .. } => "MIR1605",
1002 IssueKind::DuplicateFunction { .. } => "MIR1606",
1003
1004 IssueKind::InvalidThrow { .. } => "MIR1500",
1006 IssueKind::InvalidCatch { .. } => "MIR1503",
1007 IssueKind::ImplicitToStringCast { .. } => "MIR1501",
1008 IssueKind::ImplicitFloatToIntCast { .. } => "MIR1502",
1009 }
1010 }
1011
1012 pub fn default_severity_for_code(code: &str) -> Option<Severity> {
1018 match code {
1019 "MIR0001" | "MIR0002" | "MIR0003" | "MIR0004" | "MIR0005" | "MIR0007" | "MIR0009"
1021 | "MIR0010" | "MIR0011" | "MIR0200" | "MIR0201" | "MIR0202" | "MIR0203" | "MIR0204"
1022 | "MIR0205" | "MIR0212" | "MIR0215" | "MIR0216" | "MIR0217" | "MIR0224" | "MIR0600"
1023 | "MIR0700" | "MIR0701" | "MIR0702" | "MIR0704" | "MIR0705" | "MIR0706" | "MIR0707"
1024 | "MIR0708" | "MIR0709" | "MIR0711" | "MIR0800" | "MIR0801" | "MIR0802" | "MIR0803"
1025 | "MIR0804" | "MIR0900" | "MIR1205" | "MIR1207" | "MIR1300" | "MIR1400" | "MIR1500"
1026 | "MIR1503" | "MIR1602" | "MIR1603" | "MIR1604" | "MIR1605" | "MIR1606" => {
1027 Some(Severity::Error)
1028 }
1029
1030 "MIR0006" | "MIR0008" | "MIR0100" | "MIR0101" | "MIR0102" | "MIR0103" | "MIR0109"
1032 | "MIR0206" | "MIR0208" | "MIR0211" | "MIR0218" | "MIR0219" | "MIR0220" | "MIR0222"
1033 | "MIR0300" | "MIR0301" | "MIR0302" | "MIR0404" | "MIR0405" | "MIR0500" | "MIR0506"
1034 | "MIR0703" | "MIR0710" | "MIR1301" | "MIR1501" | "MIR1502" | "MIR1700" | "MIR1701"
1035 | "MIR1702" | "MIR1703" | "MIR1704" | "MIR1506" => Some(Severity::Warning),
1036
1037 "MIR0104" | "MIR0105" | "MIR0106" | "MIR0107" | "MIR0108" | "MIR0207" | "MIR0209"
1039 | "MIR0210" | "MIR0213" | "MIR0214" | "MIR0221" | "MIR0223" | "MIR0400" | "MIR0401"
1040 | "MIR0402" | "MIR0403" | "MIR0501" | "MIR0502" | "MIR0503" | "MIR0504" | "MIR0505"
1041 | "MIR0507" | "MIR0508" | "MIR0901" | "MIR1000" | "MIR1001" | "MIR1002" | "MIR1003"
1042 | "MIR1004" | "MIR1005" | "MIR1006" | "MIR1007" | "MIR1008" | "MIR1009" | "MIR1010"
1043 | "MIR1011" | "MIR1100" | "MIR1101" | "MIR1102" | "MIR1103" | "MIR1104" | "MIR1105"
1044 | "MIR1200" | "MIR1201" | "MIR1202" | "MIR1203" | "MIR1204" | "MIR1206" | "MIR1208"
1045 | "MIR1209" | "MIR1210" | "MIR1211" | "MIR1212" | "MIR1504" | "MIR1505" | "MIR1507"
1046 | "MIR1600" | "MIR1601" | "MIR0225" | "MIR0226" | "MIR0227" | "MIR0406" | "MIR0407"
1047 | "MIR0902" => Some(Severity::Info),
1048
1049 _ => None,
1050 }
1051 }
1052
1053 pub fn name(&self) -> &'static str {
1055 match self {
1056 IssueKind::NonStaticSelfCall { .. } => "NonStaticSelfCall",
1057 IssueKind::DirectConstructorCall { .. } => "DirectConstructorCall",
1058 IssueKind::InvalidScope { .. } => "InvalidScope",
1059 IssueKind::UndefinedVariable { .. } => "UndefinedVariable",
1060 IssueKind::UndefinedFunction { .. } => "UndefinedFunction",
1061 IssueKind::UndefinedMethod { .. } => "UndefinedMethod",
1062 IssueKind::UndefinedClass { .. } => "UndefinedClass",
1063 IssueKind::UndefinedProperty { .. } => "UndefinedProperty",
1064 IssueKind::UndefinedConstant { .. } => "UndefinedConstant",
1065 IssueKind::InaccessibleClassConstant { .. } => "InaccessibleClassConstant",
1066 IssueKind::PossiblyUndefinedVariable { .. } => "PossiblyUndefinedVariable",
1067 IssueKind::UndefinedTrait { .. } => "UndefinedTrait",
1068 IssueKind::ParentNotFound => "ParentNotFound",
1069 IssueKind::InvalidStringClass { .. } => "InvalidStringClass",
1070 IssueKind::NullArgument { .. } => "NullArgument",
1071 IssueKind::NullPropertyFetch { .. } => "NullPropertyFetch",
1072 IssueKind::NullMethodCall { .. } => "NullMethodCall",
1073 IssueKind::NullArrayAccess => "NullArrayAccess",
1074 IssueKind::PossiblyNullArgument { .. } => "PossiblyNullArgument",
1075 IssueKind::PossiblyInvalidArgument { .. } => "PossiblyInvalidArgument",
1076 IssueKind::PossiblyNullPropertyFetch { .. } => "PossiblyNullPropertyFetch",
1077 IssueKind::PossiblyNullMethodCall { .. } => "PossiblyNullMethodCall",
1078 IssueKind::PossiblyNullArrayAccess => "PossiblyNullArrayAccess",
1079 IssueKind::NullableReturnStatement { .. } => "NullableReturnStatement",
1080 IssueKind::InvalidReturnType { .. } => "InvalidReturnType",
1081 IssueKind::InvalidArgument { .. } => "InvalidArgument",
1082 IssueKind::TooFewArguments { .. } => "TooFewArguments",
1083 IssueKind::TooManyArguments { .. } => "TooManyArguments",
1084 IssueKind::InvalidNamedArgument { .. } => "InvalidNamedArgument",
1085 IssueKind::InvalidNamedArguments { .. } => "InvalidNamedArguments",
1086 IssueKind::InvalidPassByReference { .. } => "InvalidPassByReference",
1087 IssueKind::InvalidPropertyFetch { .. } => "InvalidPropertyFetch",
1088 IssueKind::InvalidArrayAccess { .. } => "InvalidArrayAccess",
1089 IssueKind::PossiblyInvalidArrayAccess { .. } => "PossiblyInvalidArrayAccess",
1090 IssueKind::InvalidArrayAssignment { .. } => "InvalidArrayAssignment",
1091 IssueKind::InvalidPropertyAssignment { .. } => "InvalidPropertyAssignment",
1092 IssueKind::InvalidCast { .. } => "InvalidCast",
1093 IssueKind::InvalidStaticInvocation { .. } => "InvalidStaticInvocation",
1094 IssueKind::InvalidOperand { .. } => "InvalidOperand",
1095 IssueKind::PossiblyInvalidOperand { .. } => "PossiblyInvalidOperand",
1096 IssueKind::PossiblyNullOperand { .. } => "PossiblyNullOperand",
1097 IssueKind::RawObjectIteration { .. } => "RawObjectIteration",
1098 IssueKind::PossiblyRawObjectIteration { .. } => "PossiblyRawObjectIteration",
1099 IssueKind::MismatchingDocblockReturnType { .. } => "MismatchingDocblockReturnType",
1100 IssueKind::MismatchingDocblockParamType { .. } => "MismatchingDocblockParamType",
1101 IssueKind::TypeCheckMismatch { .. } => "TypeCheckMismatch",
1102 IssueKind::DocblockTypeContradiction { .. } => "DocblockTypeContradiction",
1103 IssueKind::UnevaluatedCode { .. } => "UnevaluatedCode",
1104 IssueKind::IfThisIsMismatch { .. } => "IfThisIsMismatch",
1105 IssueKind::Trace { .. } => "Trace",
1106 IssueKind::InvalidArrayOffset { .. } => "InvalidArrayOffset",
1107 IssueKind::NonExistentArrayOffset { .. } => "NonExistentArrayOffset",
1108 IssueKind::PossiblyInvalidArrayOffset { .. } => "PossiblyInvalidArrayOffset",
1109 IssueKind::RedundantCondition { .. } => "RedundantCondition",
1110 IssueKind::RedundantCast { .. } => "RedundantCast",
1111 IssueKind::UnnecessaryVarAnnotation { .. } => "UnnecessaryVarAnnotation",
1112 IssueKind::TypeDoesNotContainType { .. } => "TypeDoesNotContainType",
1113 IssueKind::ParadoxicalCondition { .. } => "ParadoxicalCondition",
1114 IssueKind::UnhandledMatchCondition { .. } => "UnhandledMatchCondition",
1115 IssueKind::UnusedVariable { .. } => "UnusedVariable",
1116 IssueKind::UnusedParam { .. } => "UnusedParam",
1117 IssueKind::UnreachableCode => "UnreachableCode",
1118 IssueKind::UnusedMethod { .. } => "UnusedMethod",
1119 IssueKind::UnusedProperty { .. } => "UnusedProperty",
1120 IssueKind::UnusedFunction { .. } => "UnusedFunction",
1121 IssueKind::UnusedForeachValue { .. } => "UnusedForeachValue",
1122 IssueKind::UnusedClass { .. } => "UnusedClass",
1123 IssueKind::UnusedPsalmSuppress { .. } => "UnusedPsalmSuppress",
1124 IssueKind::ArgumentTypeCoercion { .. } => "ArgumentTypeCoercion",
1125 IssueKind::PropertyTypeCoercion { .. } => "PropertyTypeCoercion",
1126 IssueKind::ImpurePropertyAssignment { .. } => "ImpurePropertyAssignment",
1127 IssueKind::ImpureMethodCall { .. } => "ImpureMethodCall",
1128 IssueKind::ImpureGlobalVariable { .. } => "ImpureGlobalVariable",
1129 IssueKind::ImpureStaticVariable { .. } => "ImpureStaticVariable",
1130 IssueKind::ImpureFunctionCall { .. } => "ImpureFunctionCall",
1131 IssueKind::UnsupportedReferenceUsage => "UnsupportedReferenceUsage",
1132 IssueKind::NoInterfaceProperties { .. } => "NoInterfaceProperties",
1133 IssueKind::UndefinedDocblockClass { .. } => "UndefinedDocblockClass",
1134 IssueKind::MissingConstructor { .. } => "MissingConstructor",
1135 IssueKind::MixedFunctionCall => "MixedFunctionCall",
1136 IssueKind::MixedReturnStatement { .. } => "MixedReturnStatement",
1137 IssueKind::UnimplementedAbstractMethod { .. } => "UnimplementedAbstractMethod",
1138 IssueKind::UnimplementedInterfaceMethod { .. } => "UnimplementedInterfaceMethod",
1139 IssueKind::MethodSignatureMismatch { .. } => "MethodSignatureMismatch",
1140 IssueKind::OverriddenMethodAccess { .. } => "OverriddenMethodAccess",
1141 IssueKind::OverriddenPropertyAccess { .. } => "OverriddenPropertyAccess",
1142 IssueKind::InvalidExtendClass { .. } => "InvalidExtendClass",
1143 IssueKind::FinalMethodOverridden { .. } => "FinalMethodOverridden",
1144 IssueKind::AbstractInstantiation { .. } => "AbstractInstantiation",
1145 IssueKind::AbstractMethodCall { .. } => "AbstractMethodCall",
1146 IssueKind::InterfaceInstantiation { .. } => "InterfaceInstantiation",
1147 IssueKind::InvalidOverride { .. } => "InvalidOverride",
1148 IssueKind::ReadonlyPropertyAssignment { .. } => "ReadonlyPropertyAssignment",
1149 IssueKind::InvalidTemplateParam { .. } => "InvalidTemplateParam",
1150 IssueKind::ShadowedTemplateParam { .. } => "ShadowedTemplateParam",
1151 IssueKind::TaintedInput { .. } => "TaintedInput",
1152 IssueKind::TaintedHtml => "TaintedHtml",
1153 IssueKind::TaintedSql => "TaintedSql",
1154 IssueKind::TaintedShell => "TaintedShell",
1155 IssueKind::TaintedLlmPrompt => "TaintedLlmPrompt",
1156 IssueKind::DeprecatedCall { .. } => "DeprecatedCall",
1157 IssueKind::DeprecatedProperty { .. } => "DeprecatedProperty",
1158 IssueKind::DeprecatedConstant { .. } => "DeprecatedConstant",
1159 IssueKind::DeprecatedInterface { .. } => "DeprecatedInterface",
1160 IssueKind::DeprecatedTrait { .. } => "DeprecatedTrait",
1161 IssueKind::DeprecatedMethodCall { .. } => "DeprecatedMethodCall",
1162 IssueKind::DeprecatedMethod { .. } => "DeprecatedMethod",
1163 IssueKind::DeprecatedClass { .. } => "DeprecatedClass",
1164 IssueKind::InternalMethod { .. } => "InternalMethod",
1165 IssueKind::MissingReturnType { .. } => "MissingReturnType",
1166 IssueKind::MissingClosureReturnType => "MissingClosureReturnType",
1167 IssueKind::MissingParamType { .. } => "MissingParamType",
1168 IssueKind::MissingPropertyType { .. } => "MissingPropertyType",
1169 IssueKind::InvalidThrow { .. } => "InvalidThrow",
1170 IssueKind::InvalidCatch { .. } => "InvalidCatch",
1171 IssueKind::MissingThrowsDocblock { .. } => "MissingThrowsDocblock",
1172 IssueKind::ImplicitToStringCast { .. } => "ImplicitToStringCast",
1173 IssueKind::ImplicitFloatToIntCast { .. } => "ImplicitFloatToIntCast",
1174 IssueKind::ParseError { .. } => "ParseError",
1175 IssueKind::InvalidDocblock { .. } => "InvalidDocblock",
1176 IssueKind::MixedArgument { .. } => "MixedArgument",
1177 IssueKind::MixedAssignment { .. } => "MixedAssignment",
1178 IssueKind::MixedMethodCall { .. } => "MixedMethodCall",
1179 IssueKind::MixedPropertyFetch { .. } => "MixedPropertyFetch",
1180 IssueKind::MixedPropertyAssignment { .. } => "MixedPropertyAssignment",
1181 IssueKind::MixedArrayAccess => "MixedArrayAccess",
1182 IssueKind::MixedArrayOffset => "MixedArrayOffset",
1183 IssueKind::MixedClone => "MixedClone",
1184 IssueKind::InvalidClone { .. } => "InvalidClone",
1185 IssueKind::PossiblyInvalidClone { .. } => "PossiblyInvalidClone",
1186 IssueKind::InvalidToString { .. } => "InvalidToString",
1187 IssueKind::CircularInheritance { .. } => "CircularInheritance",
1188 IssueKind::InvalidTraitUse { .. } => "InvalidTraitUse",
1189 IssueKind::ForbiddenCode { .. } => "ForbiddenCode",
1190 IssueKind::WrongCaseFunction { .. } => "WrongCaseFunction",
1191 IssueKind::WrongCaseMethod { .. } => "WrongCaseMethod",
1192 IssueKind::WrongCaseClass { .. } => "WrongCaseClass",
1193 IssueKind::InvalidAttribute { .. } => "InvalidAttribute",
1194 IssueKind::UndefinedAttributeClass { .. } => "UndefinedAttributeClass",
1195 IssueKind::DuplicateClass { .. } => "DuplicateClass",
1196 IssueKind::DuplicateInterface { .. } => "DuplicateInterface",
1197 IssueKind::DuplicateTrait { .. } => "DuplicateTrait",
1198 IssueKind::DuplicateEnum { .. } => "DuplicateEnum",
1199 IssueKind::DuplicateFunction { .. } => "DuplicateFunction",
1200 }
1201 }
1202
1203 pub fn message(&self) -> String {
1205 match self {
1206 IssueKind::NonStaticSelfCall { class, method } => {
1207 format!("Non-static method {class}::{method}() cannot be called statically")
1208 }
1209 IssueKind::DirectConstructorCall { class } => {
1210 format!("Cannot call constructor of {class} directly")
1211 }
1212 IssueKind::InvalidScope { in_class } => {
1213 if *in_class {
1214 "$this cannot be used in a static method".to_string()
1215 } else {
1216 "$this cannot be used outside of a class".to_string()
1217 }
1218 }
1219 IssueKind::UndefinedVariable { name } => format!("Variable ${name} is not defined"),
1220 IssueKind::UndefinedFunction { name } => format!("Function {name}() is not defined"),
1221 IssueKind::UndefinedMethod { class, method } => {
1222 format!("Method {class}::{method}() does not exist")
1223 }
1224 IssueKind::UndefinedClass { name } => format!("Class {name} does not exist"),
1225 IssueKind::UndefinedProperty { class, property } => {
1226 format!("Property {class}::${property} does not exist")
1227 }
1228 IssueKind::UndefinedConstant { name } => format!("Constant {name} is not defined"),
1229 IssueKind::InaccessibleClassConstant { class, constant } => {
1230 format!("Cannot access constant {class}::{constant}")
1231 }
1232 IssueKind::PossiblyUndefinedVariable { name } => {
1233 format!("Variable ${name} might not be defined")
1234 }
1235 IssueKind::UndefinedTrait { name } => format!("Trait {name} does not exist"),
1236 IssueKind::ParentNotFound => {
1237 "Cannot use parent:: when current class has no parent".to_string()
1238 }
1239 IssueKind::InvalidStringClass { actual } => {
1240 format!("Dynamic class instantiation requires string or class-string type, got '{actual}'")
1241 }
1242
1243 IssueKind::NullArgument { param, fn_name } => {
1244 format!("Argument ${param} of {fn_name}() cannot be null")
1245 }
1246 IssueKind::NullPropertyFetch { property } => {
1247 format!("Cannot access property ${property} on null")
1248 }
1249 IssueKind::NullMethodCall { method } => {
1250 format!("Cannot call method {method}() on null")
1251 }
1252 IssueKind::NullArrayAccess => "Cannot access array on null".to_string(),
1253 IssueKind::PossiblyNullArgument { param, fn_name } => {
1254 format!("Argument ${param} of {fn_name}() might be null")
1255 }
1256 IssueKind::PossiblyInvalidArgument {
1257 param,
1258 fn_name,
1259 expected,
1260 actual,
1261 } => {
1262 format!("Argument ${param} of {fn_name}() expects '{expected}', possibly different type '{actual}' provided")
1263 }
1264 IssueKind::PossiblyNullPropertyFetch { property } => {
1265 format!("Cannot access property ${property} on possibly null value")
1266 }
1267 IssueKind::PossiblyNullMethodCall { method } => {
1268 format!("Cannot call method {method}() on possibly null value")
1269 }
1270 IssueKind::PossiblyNullArrayAccess => {
1271 "Cannot access array on possibly null value".to_string()
1272 }
1273 IssueKind::NullableReturnStatement { expected, actual } => {
1274 format!("Return type '{actual}' is not compatible with declared '{expected}'")
1275 }
1276
1277 IssueKind::InvalidReturnType { expected, actual } => {
1278 format!("Return type '{actual}' is not compatible with declared '{expected}'")
1279 }
1280 IssueKind::InvalidArgument {
1281 param,
1282 fn_name,
1283 expected,
1284 actual,
1285 } => {
1286 format!("Argument ${param} of {fn_name}() expects '{expected}', got '{actual}'")
1287 }
1288 IssueKind::TooFewArguments {
1289 fn_name,
1290 expected,
1291 actual,
1292 } => {
1293 format!(
1294 "Too few arguments for {}(): expected {}, got {}",
1295 fn_name, expected, actual
1296 )
1297 }
1298 IssueKind::TooManyArguments {
1299 fn_name,
1300 expected,
1301 actual,
1302 } => {
1303 format!(
1304 "Too many arguments for {}(): expected {}, got {}",
1305 fn_name, expected, actual
1306 )
1307 }
1308 IssueKind::InvalidNamedArgument { fn_name, name } => {
1309 format!("{}() has no parameter named ${}", fn_name, name)
1310 }
1311 IssueKind::InvalidNamedArguments { fn_name } => {
1312 format!("{}() does not accept named arguments", fn_name)
1313 }
1314 IssueKind::InvalidPassByReference { fn_name, param } => {
1315 format!(
1316 "Argument ${} of {}() must be passed by reference",
1317 param, fn_name
1318 )
1319 }
1320 IssueKind::InvalidPropertyFetch { ty } => {
1321 format!("Cannot fetch property on non-object type '{ty}'")
1322 }
1323 IssueKind::InvalidArrayAccess { ty } => {
1324 format!("Cannot use [] operator on non-array type '{ty}'")
1325 }
1326 IssueKind::PossiblyInvalidArrayAccess { ty } => {
1327 format!("Possibly invalid array access: '{ty}' might not support []")
1328 }
1329 IssueKind::InvalidArrayAssignment { ty } => {
1330 format!("Cannot use [] assignment on non-array type '{ty}'")
1331 }
1332 IssueKind::InvalidPropertyAssignment {
1333 property,
1334 expected,
1335 actual,
1336 } => {
1337 format!("Property ${property} expects '{expected}', cannot assign '{actual}'")
1338 }
1339 IssueKind::InvalidCast { from, to } => {
1340 format!("Cannot cast '{from}' to '{to}'")
1341 }
1342 IssueKind::InvalidStaticInvocation { class, method } => {
1343 format!("Non-static method {class}::{method}() cannot be called statically")
1344 }
1345 IssueKind::InvalidOperand { op, left, right } => {
1346 format!("Operator '{op}' not supported between '{left}' and '{right}'")
1347 }
1348 IssueKind::PossiblyInvalidOperand { op, left, right } => {
1349 format!("Operator '{op}' might not be supported between '{left}' and '{right}'")
1350 }
1351 IssueKind::PossiblyNullOperand { op, ty } => {
1352 format!("Operator '{op}' operand '{ty}' might be null")
1353 }
1354 IssueKind::RawObjectIteration { ty } => {
1355 format!("Cannot iterate over non-iterable object '{ty}'")
1356 }
1357 IssueKind::PossiblyRawObjectIteration { ty } => {
1358 format!("Cannot iterate over possibly non-iterable object '{ty}'")
1359 }
1360 IssueKind::MismatchingDocblockReturnType { declared, inferred } => {
1361 format!("Docblock return type '{declared}' does not match inferred '{inferred}'")
1362 }
1363 IssueKind::MismatchingDocblockParamType {
1364 param,
1365 declared,
1366 inferred,
1367 } => {
1368 format!(
1369 "Docblock type '{declared}' for ${param} does not match inferred '{inferred}'"
1370 )
1371 }
1372 IssueKind::TypeCheckMismatch {
1373 var,
1374 expected,
1375 actual,
1376 } => {
1377 format!("Type of ${var} is expected to be {expected}, got {actual}")
1378 }
1379 IssueKind::Trace {
1380 variable,
1381 type_info,
1382 } => {
1383 format!("Type of ${variable} is {type_info}")
1384 }
1385
1386 IssueKind::InvalidArrayOffset { expected, actual } => {
1387 format!("Array offset expects '{expected}', got '{actual}'")
1388 }
1389 IssueKind::NonExistentArrayOffset { key } => {
1390 format!("Array offset '{key}' does not exist")
1391 }
1392 IssueKind::PossiblyInvalidArrayOffset { expected, actual } => {
1393 format!("Array offset might be invalid: expects '{expected}', got '{actual}'")
1394 }
1395
1396 IssueKind::RedundantCondition { ty } => {
1397 format!("Condition is always true/false for type '{ty}'")
1398 }
1399 IssueKind::RedundantCast { from, to } => {
1400 format!("Casting '{from}' to '{to}' is redundant")
1401 }
1402 IssueKind::UnnecessaryVarAnnotation { var } => {
1403 format!("@var annotation for ${var} is unnecessary")
1404 }
1405 IssueKind::TypeDoesNotContainType { left, right } => {
1406 format!("Type '{left}' can never contain type '{right}'")
1407 }
1408 IssueKind::ParadoxicalCondition { value } => {
1409 format!("Value {value} is duplicated; this branch can never be reached")
1410 }
1411 IssueKind::UnhandledMatchCondition { detail } => {
1412 format!("Unhandled match condition: {detail}")
1413 }
1414 IssueKind::DocblockTypeContradiction { expr, declared } => {
1415 format!("Type '{declared}' makes '{expr}' impossible — this can never hold")
1416 }
1417 IssueKind::UnevaluatedCode { reason } => {
1418 format!("Unevaluated code: {reason}")
1419 }
1420 IssueKind::IfThisIsMismatch {
1421 class,
1422 method,
1423 expected,
1424 actual,
1425 } => {
1426 format!(
1427 "Cannot call {class}::{method}() — @if-this-is requires $this to be '{expected}', but it is '{actual}'"
1428 )
1429 }
1430
1431 IssueKind::UnusedVariable { name } => format!("Variable ${name} is never read"),
1432 IssueKind::UnusedParam { name } => format!("Parameter ${name} is never used"),
1433 IssueKind::UnreachableCode => "Unreachable code detected".to_string(),
1434 IssueKind::UnusedMethod { class, method } => {
1435 format!("Private method {class}::{method}() is never called")
1436 }
1437 IssueKind::UnusedProperty { class, property } => {
1438 format!("Private property {class}::${property} is never read")
1439 }
1440 IssueKind::UnusedFunction { name } => {
1441 format!("Function {name}() is never called")
1442 }
1443 IssueKind::UnusedForeachValue { name } => {
1444 format!("Foreach value ${name} is never read")
1445 }
1446 IssueKind::UnusedClass { class } => {
1447 format!("Class {class} is never referenced")
1448 }
1449 IssueKind::UnusedPsalmSuppress { kind } => {
1450 format!("Suppress annotation for '{kind}' is never used")
1451 }
1452 IssueKind::ArgumentTypeCoercion {
1453 param,
1454 fn_name,
1455 expected,
1456 actual,
1457 } => {
1458 format!("Argument ${param} of {fn_name}() expects '{expected}', got '{actual}' — coercion may fail at runtime")
1459 }
1460 IssueKind::PropertyTypeCoercion {
1461 property,
1462 expected,
1463 actual,
1464 } => {
1465 format!("Property ${property} expects '{expected}', cannot assign '{actual}' — coercion may fail at runtime")
1466 }
1467 IssueKind::ImpurePropertyAssignment { property } => {
1468 format!("Assigning to property {property} of a parameter in a @pure function")
1469 }
1470 IssueKind::ImpureMethodCall { method } => {
1471 format!("Calling impure method {method}() in a @pure function")
1472 }
1473 IssueKind::ImpureGlobalVariable { variable } => {
1474 format!("Using global variable ${variable} in a @pure function")
1475 }
1476 IssueKind::ImpureStaticVariable { variable } => {
1477 format!("Using static variable ${variable} in a @pure function")
1478 }
1479 IssueKind::ImpureFunctionCall { fn_name } => {
1480 format!("Calling impure function {fn_name}() in a @pure function")
1481 }
1482
1483 IssueKind::UnimplementedAbstractMethod { class, method } => {
1484 format!("Class {class} must implement abstract method {method}()")
1485 }
1486 IssueKind::UnimplementedInterfaceMethod {
1487 class,
1488 interface,
1489 method,
1490 } => {
1491 format!("Class {class} must implement {interface}::{method}() from interface")
1492 }
1493 IssueKind::MethodSignatureMismatch {
1494 class,
1495 method,
1496 detail,
1497 } => {
1498 format!("Method {class}::{method}() signature mismatch: {detail}")
1499 }
1500 IssueKind::OverriddenMethodAccess { class, method } => {
1501 format!("Method {class}::{method}() overrides with less visibility")
1502 }
1503 IssueKind::OverriddenPropertyAccess { class, property } => {
1504 format!("Property {class}::${property} overrides with less visibility")
1505 }
1506 IssueKind::ReadonlyPropertyAssignment { class, property } => {
1507 format!(
1508 "Cannot assign to readonly property {class}::${property} outside of constructor"
1509 )
1510 }
1511 IssueKind::InvalidExtendClass { parent, child } => {
1512 format!("Class {child} cannot extend final class {parent}")
1513 }
1514 IssueKind::InvalidTemplateParam {
1515 name,
1516 expected_bound,
1517 actual,
1518 } => {
1519 format!(
1520 "Template type '{name}' inferred as '{actual}' does not satisfy bound '{expected_bound}'"
1521 )
1522 }
1523 IssueKind::ShadowedTemplateParam { name } => {
1524 format!(
1525 "Method template parameter '{name}' shadows class-level template parameter with the same name"
1526 )
1527 }
1528 IssueKind::FinalMethodOverridden {
1529 class,
1530 method,
1531 parent,
1532 } => {
1533 format!("Method {class}::{method}() cannot override final method from {parent}")
1534 }
1535 IssueKind::AbstractInstantiation { class } => {
1536 format!("Cannot instantiate abstract class {class}")
1537 }
1538 IssueKind::AbstractMethodCall { class, method } => {
1539 format!("Cannot call abstract method {class}::{method}()")
1540 }
1541 IssueKind::InterfaceInstantiation { class } => {
1542 format!("Cannot instantiate interface {class}")
1543 }
1544 IssueKind::InvalidOverride {
1545 class,
1546 method,
1547 detail,
1548 } => {
1549 format!("Method {class}::{method}() has #[Override] but {detail}")
1550 }
1551
1552 IssueKind::TaintedInput { sink } => format!("Tainted input reaching sink '{sink}'"),
1553 IssueKind::TaintedHtml => "Tainted HTML output — possible XSS".to_string(),
1554 IssueKind::TaintedSql => "Tainted SQL query — possible SQL injection".to_string(),
1555 IssueKind::TaintedShell => {
1556 "Tainted shell command — possible command injection".to_string()
1557 }
1558 IssueKind::TaintedLlmPrompt => {
1559 "Tainted LLM prompt — possible prompt injection".to_string()
1560 }
1561
1562 IssueKind::DeprecatedCall { name, message } => {
1563 let base = format!("Call to deprecated function {name}");
1564 append_deprecation_message(base, message)
1565 }
1566 IssueKind::DeprecatedProperty {
1567 class,
1568 property,
1569 message,
1570 } => {
1571 let base = format!("Property {class}::${property} is deprecated");
1572 append_deprecation_message(base, message)
1573 }
1574 IssueKind::DeprecatedConstant {
1575 class,
1576 constant,
1577 message,
1578 } => {
1579 let base = format!("Constant {class}::{constant} is deprecated");
1580 append_deprecation_message(base, message)
1581 }
1582 IssueKind::DeprecatedInterface { name, message } => {
1583 let base = format!("Interface {name} is deprecated");
1584 append_deprecation_message(base, message)
1585 }
1586 IssueKind::DeprecatedTrait { name, message } => {
1587 let base = format!("Trait {name} is deprecated");
1588 append_deprecation_message(base, message)
1589 }
1590 IssueKind::DeprecatedMethodCall {
1591 class,
1592 method,
1593 message,
1594 } => {
1595 let base = format!("Call to deprecated method {class}::{method}");
1596 append_deprecation_message(base, message)
1597 }
1598 IssueKind::DeprecatedMethod {
1599 class,
1600 method,
1601 message,
1602 } => {
1603 let base = format!("Method {class}::{method}() is deprecated");
1604 append_deprecation_message(base, message)
1605 }
1606 IssueKind::DeprecatedClass { name, message } => {
1607 let base = format!("Class {name} is deprecated");
1608 append_deprecation_message(base, message)
1609 }
1610 IssueKind::InternalMethod { class, method } => {
1611 format!("Method {class}::{method}() is marked @internal")
1612 }
1613 IssueKind::MissingReturnType { fn_name } => {
1614 format!("Function {fn_name}() has no return type annotation")
1615 }
1616 IssueKind::MissingClosureReturnType => {
1617 "Closure has no return type annotation".to_string()
1618 }
1619 IssueKind::MissingParamType { fn_name, param } => {
1620 format!("Parameter ${param} of {fn_name}() has no type annotation")
1621 }
1622 IssueKind::MissingPropertyType { class, property } => {
1623 format!("Property {class}::${property} has no type annotation")
1624 }
1625 IssueKind::InvalidThrow { ty } => {
1626 format!("Thrown type '{ty}' does not extend Throwable")
1627 }
1628 IssueKind::InvalidCatch { ty } => {
1629 format!("Caught type '{ty}' does not extend Throwable")
1630 }
1631 IssueKind::MissingThrowsDocblock { class } => {
1632 format!("Exception {class} is thrown but not declared in @throws")
1633 }
1634 IssueKind::ImplicitToStringCast { class } => {
1635 format!("Class {class} is implicitly cast to string")
1636 }
1637 IssueKind::ImplicitFloatToIntCast { from } => {
1638 format!("Implicit cast from {from} to int truncates the fractional part")
1639 }
1640 IssueKind::ParseError { message } => format!("Parse error: {message}"),
1641 IssueKind::InvalidDocblock { message } => format!("Invalid docblock: {message}"),
1642 IssueKind::MixedArgument { param, fn_name } => {
1643 format!("Argument ${param} of {fn_name}() is mixed")
1644 }
1645 IssueKind::MixedAssignment { var } => {
1646 format!("Variable ${var} is assigned a mixed type")
1647 }
1648 IssueKind::MixedMethodCall { method } => {
1649 format!("Method {method}() called on mixed type")
1650 }
1651 IssueKind::UnsupportedReferenceUsage => {
1652 "Reference assignment is not supported".to_string()
1653 }
1654 IssueKind::NoInterfaceProperties { property } => {
1655 format!("Property ${property} is not defined on sealed interface")
1656 }
1657 IssueKind::UndefinedDocblockClass { name } => {
1658 format!("Docblock type '{name}' does not exist")
1659 }
1660 IssueKind::MissingConstructor { class } => {
1661 format!("Class {class} has uninitialized properties but no constructor")
1662 }
1663 IssueKind::MixedFunctionCall => "Cannot call mixed type as a function".to_string(),
1664 IssueKind::MixedReturnStatement { declared } => {
1665 format!("Cannot return a mixed type from function with declared return type '{declared}'")
1666 }
1667 IssueKind::MixedPropertyFetch { property } => {
1668 format!("Property ${property} fetched on mixed type")
1669 }
1670 IssueKind::MixedPropertyAssignment { property } => {
1671 format!("Property ${property} assigned on mixed type")
1672 }
1673 IssueKind::MixedArrayAccess => "Array access on mixed type".to_string(),
1674 IssueKind::MixedArrayOffset => "Mixed type used as array offset".to_string(),
1675 IssueKind::MixedClone => "cannot clone mixed".to_string(),
1676 IssueKind::InvalidClone { ty } => format!("cannot clone non-object {ty}"),
1677 IssueKind::PossiblyInvalidClone { ty } => {
1678 format!("cannot clone possibly non-object {ty}")
1679 }
1680 IssueKind::InvalidToString { class } => {
1681 format!("Method {class}::__toString() must return a string")
1682 }
1683 IssueKind::CircularInheritance { class } => {
1684 format!("Class {class} has a circular inheritance chain")
1685 }
1686 IssueKind::InvalidTraitUse { trait_name, reason } => {
1687 format!("Trait {trait_name} used incorrectly: {reason}")
1688 }
1689 IssueKind::WrongCaseFunction { used, canonical } => {
1690 format!("Function name '{used}' has incorrect casing; use '{canonical}'")
1691 }
1692 IssueKind::WrongCaseMethod {
1693 class,
1694 used,
1695 canonical,
1696 } => {
1697 format!("Method name '{class}::{used}' has incorrect casing; use '{canonical}'")
1698 }
1699 IssueKind::WrongCaseClass { used, canonical } => {
1700 format!("Class name '{used}' has incorrect casing; use '{canonical}'")
1701 }
1702 IssueKind::InvalidAttribute { message } => message.clone(),
1703 IssueKind::UndefinedAttributeClass { name } => {
1704 format!("Attribute class {name} does not exist")
1705 }
1706 IssueKind::ForbiddenCode { message } => message.clone(),
1707 IssueKind::DuplicateClass { name } => {
1708 format!("Class {name} has already been defined")
1709 }
1710 IssueKind::DuplicateInterface { name } => {
1711 format!("Interface {name} has already been defined")
1712 }
1713 IssueKind::DuplicateTrait { name } => {
1714 format!("Trait {name} has already been defined")
1715 }
1716 IssueKind::DuplicateEnum { name } => {
1717 format!("Enum {name} has already been defined")
1718 }
1719 IssueKind::DuplicateFunction { name } => {
1720 format!("Function {name}() has already been defined")
1721 }
1722 }
1723 }
1724}
1725
1726#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1731pub struct Issue {
1732 pub kind: IssueKind,
1733 pub severity: Severity,
1734 pub location: Location,
1735 pub snippet: Option<String>,
1736 pub suppressed: bool,
1737}
1738
1739impl Issue {
1740 pub fn new(kind: IssueKind, location: Location) -> Self {
1741 let severity = kind.default_severity();
1742 Self {
1743 severity,
1744 kind,
1745 location,
1746 snippet: None,
1747 suppressed: false,
1748 }
1749 }
1750
1751 pub fn with_snippet(mut self, snippet: impl Into<String>) -> Self {
1752 self.snippet = Some(snippet.into());
1753 self
1754 }
1755
1756 pub fn suppress(mut self) -> Self {
1757 self.suppressed = true;
1758 self
1759 }
1760}
1761
1762impl fmt::Display for Issue {
1763 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1764 let sev = match self.severity {
1765 Severity::Error => "error".red().to_string(),
1766 Severity::Warning => "warning".yellow().to_string(),
1767 Severity::Info => "info".blue().to_string(),
1768 };
1769 write!(
1770 f,
1771 "{} {}[{}] {}: {}",
1772 self.location.bright_black(),
1773 sev,
1774 self.kind.code().bright_black(),
1775 self.kind.name().bold(),
1776 self.kind.message()
1777 )
1778 }
1779}
1780
1781#[derive(Debug, Default)]
1786pub struct IssueBuffer {
1787 issues: Vec<Issue>,
1788 seen: HashSet<(&'static str, Arc<str>, u32, u16)>,
1789 file_suppressions: Vec<String>,
1791}
1792
1793impl IssueBuffer {
1794 pub fn new() -> Self {
1795 Self::default()
1796 }
1797
1798 pub fn add(&mut self, issue: Issue) {
1799 let key = (
1800 issue.kind.name(),
1801 issue.location.file.clone(),
1802 issue.location.line,
1803 issue.location.col_start,
1804 );
1805 if self.seen.insert(key) {
1806 self.issues.push(issue);
1807 }
1808 }
1809
1810 pub fn add_suppression(&mut self, name: impl Into<String>) {
1811 self.file_suppressions.push(name.into());
1812 }
1813
1814 pub fn into_issues(self) -> Vec<Issue> {
1816 self.issues
1817 .into_iter()
1818 .filter(|i| !i.suppressed)
1819 .filter(|i| !self.file_suppressions.contains(&i.kind.name().to_string()))
1820 .collect()
1821 }
1822
1823 pub fn into_all_issues(self) -> Vec<Issue> {
1828 self.issues
1829 .into_iter()
1830 .map(|mut i| {
1831 if self.file_suppressions.contains(&i.kind.name().to_string()) {
1832 i.suppressed = true;
1833 }
1834 i
1835 })
1836 .collect()
1837 }
1838
1839 pub fn suppress_range(&mut self, from: usize, suppressions: &[String]) {
1842 if suppressions.is_empty() {
1843 return;
1844 }
1845 for issue in self.issues[from..].iter_mut() {
1846 if suppressions.iter().any(|s| s == issue.kind.name()) {
1847 issue.suppressed = true;
1848 }
1849 }
1850 }
1851
1852 pub fn issue_count(&self) -> usize {
1855 self.issues.len()
1856 }
1857
1858 pub fn is_empty(&self) -> bool {
1859 self.issues.is_empty()
1860 }
1861
1862 pub fn len(&self) -> usize {
1863 self.issues.len()
1864 }
1865
1866 pub fn error_count(&self) -> usize {
1867 self.issues
1868 .iter()
1869 .filter(|i| !i.suppressed && i.severity == Severity::Error)
1870 .count()
1871 }
1872
1873 pub fn warning_count(&self) -> usize {
1874 self.issues
1875 .iter()
1876 .filter(|i| !i.suppressed && i.severity == Severity::Warning)
1877 .count()
1878 }
1879}
1880
1881#[cfg(test)]
1882mod code_tests {
1883 use super::*;
1884 use std::collections::HashSet;
1885
1886 fn one_of_each() -> Vec<IssueKind> {
1892 let s = || String::new();
1893 vec![
1894 IssueKind::InvalidScope { in_class: false },
1895 IssueKind::NonStaticSelfCall {
1896 class: s(),
1897 method: s(),
1898 },
1899 IssueKind::DirectConstructorCall { class: s() },
1900 IssueKind::UndefinedVariable { name: s() },
1901 IssueKind::UndefinedFunction { name: s() },
1902 IssueKind::UndefinedMethod {
1903 class: s(),
1904 method: s(),
1905 },
1906 IssueKind::UndefinedClass { name: s() },
1907 IssueKind::UndefinedProperty {
1908 class: s(),
1909 property: s(),
1910 },
1911 IssueKind::UndefinedConstant { name: s() },
1912 IssueKind::InaccessibleClassConstant {
1913 class: s(),
1914 constant: s(),
1915 },
1916 IssueKind::PossiblyUndefinedVariable { name: s() },
1917 IssueKind::UndefinedTrait { name: s() },
1918 IssueKind::ParentNotFound,
1919 IssueKind::NullArgument {
1920 param: s(),
1921 fn_name: s(),
1922 },
1923 IssueKind::NullPropertyFetch { property: s() },
1924 IssueKind::NullMethodCall { method: s() },
1925 IssueKind::NullArrayAccess,
1926 IssueKind::PossiblyNullArgument {
1927 param: s(),
1928 fn_name: s(),
1929 },
1930 IssueKind::PossiblyInvalidArgument {
1931 param: s(),
1932 fn_name: s(),
1933 expected: s(),
1934 actual: s(),
1935 },
1936 IssueKind::PossiblyNullPropertyFetch { property: s() },
1937 IssueKind::PossiblyNullMethodCall { method: s() },
1938 IssueKind::PossiblyNullArrayAccess,
1939 IssueKind::NullableReturnStatement {
1940 expected: s(),
1941 actual: s(),
1942 },
1943 IssueKind::InvalidReturnType {
1944 expected: s(),
1945 actual: s(),
1946 },
1947 IssueKind::InvalidArgument {
1948 param: s(),
1949 fn_name: s(),
1950 expected: s(),
1951 actual: s(),
1952 },
1953 IssueKind::TooFewArguments {
1954 fn_name: s(),
1955 expected: 0,
1956 actual: 0,
1957 },
1958 IssueKind::TooManyArguments {
1959 fn_name: s(),
1960 expected: 0,
1961 actual: 0,
1962 },
1963 IssueKind::InvalidNamedArgument {
1964 fn_name: s(),
1965 name: s(),
1966 },
1967 IssueKind::InvalidNamedArguments { fn_name: s() },
1968 IssueKind::InvalidPassByReference {
1969 fn_name: s(),
1970 param: s(),
1971 },
1972 IssueKind::InvalidPropertyFetch { ty: s() },
1973 IssueKind::InvalidArrayAccess { ty: s() },
1974 IssueKind::PossiblyInvalidArrayAccess { ty: s() },
1975 IssueKind::InvalidArrayAssignment { ty: s() },
1976 IssueKind::InvalidPropertyAssignment {
1977 property: s(),
1978 expected: s(),
1979 actual: s(),
1980 },
1981 IssueKind::InvalidCast { from: s(), to: s() },
1982 IssueKind::InvalidStaticInvocation {
1983 class: s(),
1984 method: s(),
1985 },
1986 IssueKind::InvalidOperand {
1987 op: s(),
1988 left: s(),
1989 right: s(),
1990 },
1991 IssueKind::PossiblyInvalidOperand {
1992 op: s(),
1993 left: s(),
1994 right: s(),
1995 },
1996 IssueKind::PossiblyNullOperand { op: s(), ty: s() },
1997 IssueKind::RawObjectIteration { ty: s() },
1998 IssueKind::PossiblyRawObjectIteration { ty: s() },
1999 IssueKind::MismatchingDocblockReturnType {
2000 declared: s(),
2001 inferred: s(),
2002 },
2003 IssueKind::MismatchingDocblockParamType {
2004 param: s(),
2005 declared: s(),
2006 inferred: s(),
2007 },
2008 IssueKind::TypeCheckMismatch {
2009 var: s(),
2010 expected: s(),
2011 actual: s(),
2012 },
2013 IssueKind::Trace {
2014 variable: s(),
2015 type_info: s(),
2016 },
2017 IssueKind::InvalidArrayOffset {
2018 expected: s(),
2019 actual: s(),
2020 },
2021 IssueKind::NonExistentArrayOffset { key: s() },
2022 IssueKind::PossiblyInvalidArrayOffset {
2023 expected: s(),
2024 actual: s(),
2025 },
2026 IssueKind::RedundantCondition { ty: s() },
2027 IssueKind::RedundantCast { from: s(), to: s() },
2028 IssueKind::UnnecessaryVarAnnotation { var: s() },
2029 IssueKind::TypeDoesNotContainType {
2030 left: s(),
2031 right: s(),
2032 },
2033 IssueKind::UnusedVariable { name: s() },
2034 IssueKind::UnusedParam { name: s() },
2035 IssueKind::UnreachableCode,
2036 IssueKind::UnhandledMatchCondition { detail: s() },
2037 IssueKind::UnusedMethod {
2038 class: s(),
2039 method: s(),
2040 },
2041 IssueKind::UnusedProperty {
2042 class: s(),
2043 property: s(),
2044 },
2045 IssueKind::UnusedFunction { name: s() },
2046 IssueKind::UnusedForeachValue { name: s() },
2047 IssueKind::UnusedClass { class: s() },
2048 IssueKind::UnusedPsalmSuppress { kind: s() },
2049 IssueKind::ArgumentTypeCoercion {
2050 param: s(),
2051 fn_name: s(),
2052 expected: s(),
2053 actual: s(),
2054 },
2055 IssueKind::PropertyTypeCoercion {
2056 property: s(),
2057 expected: s(),
2058 actual: s(),
2059 },
2060 IssueKind::ImpurePropertyAssignment { property: s() },
2061 IssueKind::ImpureMethodCall { method: s() },
2062 IssueKind::ImpureGlobalVariable { variable: s() },
2063 IssueKind::ImpureStaticVariable { variable: s() },
2064 IssueKind::ImpureFunctionCall { fn_name: s() },
2065 IssueKind::ReadonlyPropertyAssignment {
2066 class: s(),
2067 property: s(),
2068 },
2069 IssueKind::UnimplementedAbstractMethod {
2070 class: s(),
2071 method: s(),
2072 },
2073 IssueKind::UnimplementedInterfaceMethod {
2074 class: s(),
2075 interface: s(),
2076 method: s(),
2077 },
2078 IssueKind::MethodSignatureMismatch {
2079 class: s(),
2080 method: s(),
2081 detail: s(),
2082 },
2083 IssueKind::OverriddenMethodAccess {
2084 class: s(),
2085 method: s(),
2086 },
2087 IssueKind::OverriddenPropertyAccess {
2088 class: s(),
2089 property: s(),
2090 },
2091 IssueKind::InvalidExtendClass {
2092 parent: s(),
2093 child: s(),
2094 },
2095 IssueKind::FinalMethodOverridden {
2096 class: s(),
2097 method: s(),
2098 parent: s(),
2099 },
2100 IssueKind::AbstractInstantiation { class: s() },
2101 IssueKind::AbstractMethodCall {
2102 class: s(),
2103 method: s(),
2104 },
2105 IssueKind::InterfaceInstantiation { class: s() },
2106 IssueKind::InvalidOverride {
2107 class: s(),
2108 method: s(),
2109 detail: s(),
2110 },
2111 IssueKind::CircularInheritance { class: s() },
2112 IssueKind::TaintedInput { sink: s() },
2113 IssueKind::TaintedHtml,
2114 IssueKind::TaintedSql,
2115 IssueKind::TaintedShell,
2116 IssueKind::TaintedLlmPrompt,
2117 IssueKind::InvalidTemplateParam {
2118 name: s(),
2119 expected_bound: s(),
2120 actual: s(),
2121 },
2122 IssueKind::ShadowedTemplateParam { name: s() },
2123 IssueKind::DeprecatedCall {
2124 name: s(),
2125 message: None,
2126 },
2127 IssueKind::DeprecatedProperty {
2128 class: s(),
2129 property: s(),
2130 message: None,
2131 },
2132 IssueKind::DeprecatedConstant {
2133 class: s(),
2134 constant: s(),
2135 message: None,
2136 },
2137 IssueKind::DeprecatedInterface {
2138 name: s(),
2139 message: None,
2140 },
2141 IssueKind::DeprecatedTrait {
2142 name: s(),
2143 message: None,
2144 },
2145 IssueKind::DeprecatedMethodCall {
2146 class: s(),
2147 method: s(),
2148 message: None,
2149 },
2150 IssueKind::DeprecatedMethod {
2151 class: s(),
2152 method: s(),
2153 message: None,
2154 },
2155 IssueKind::DeprecatedClass {
2156 name: s(),
2157 message: None,
2158 },
2159 IssueKind::InternalMethod {
2160 class: s(),
2161 method: s(),
2162 },
2163 IssueKind::MissingReturnType { fn_name: s() },
2164 IssueKind::MissingClosureReturnType,
2165 IssueKind::MissingParamType {
2166 fn_name: s(),
2167 param: s(),
2168 },
2169 IssueKind::MissingPropertyType {
2170 class: s(),
2171 property: s(),
2172 },
2173 IssueKind::MissingThrowsDocblock { class: s() },
2174 IssueKind::InvalidDocblock { message: s() },
2175 IssueKind::MixedArgument {
2176 param: s(),
2177 fn_name: s(),
2178 },
2179 IssueKind::MixedAssignment { var: s() },
2180 IssueKind::MixedMethodCall { method: s() },
2181 IssueKind::UnsupportedReferenceUsage,
2182 IssueKind::NoInterfaceProperties { property: s() },
2183 IssueKind::UndefinedDocblockClass { name: s() },
2184 IssueKind::MissingConstructor { class: s() },
2185 IssueKind::MixedFunctionCall,
2186 IssueKind::MixedReturnStatement { declared: s() },
2187 IssueKind::MixedPropertyFetch { property: s() },
2188 IssueKind::MixedPropertyAssignment { property: s() },
2189 IssueKind::MixedArrayAccess,
2190 IssueKind::MixedArrayOffset,
2191 IssueKind::MixedClone,
2192 IssueKind::InvalidClone { ty: s() },
2193 IssueKind::PossiblyInvalidClone { ty: s() },
2194 IssueKind::InvalidToString { class: s() },
2195 IssueKind::InvalidTraitUse {
2196 trait_name: s(),
2197 reason: s(),
2198 },
2199 IssueKind::ParseError { message: s() },
2200 IssueKind::InvalidThrow { ty: s() },
2201 IssueKind::InvalidCatch { ty: s() },
2202 IssueKind::ImplicitToStringCast { class: s() },
2203 IssueKind::ImplicitFloatToIntCast { from: s() },
2204 IssueKind::WrongCaseFunction {
2205 used: s(),
2206 canonical: s(),
2207 },
2208 IssueKind::WrongCaseMethod {
2209 class: s(),
2210 used: s(),
2211 canonical: s(),
2212 },
2213 IssueKind::WrongCaseClass {
2214 used: s(),
2215 canonical: s(),
2216 },
2217 IssueKind::InvalidAttribute { message: s() },
2218 IssueKind::UndefinedAttributeClass { name: s() },
2219 IssueKind::ForbiddenCode { message: s() },
2220 IssueKind::DuplicateClass { name: s() },
2221 IssueKind::DuplicateInterface { name: s() },
2222 IssueKind::DuplicateTrait { name: s() },
2223 IssueKind::DuplicateEnum { name: s() },
2224 IssueKind::DuplicateFunction { name: s() },
2225 ]
2226 }
2227
2228 #[test]
2229 fn codes_have_expected_shape() {
2230 for kind in one_of_each() {
2231 let code = kind.code();
2232 assert!(
2233 code.len() == 7
2234 && code.starts_with("MIR")
2235 && code[3..].chars().all(|c| c.is_ascii_digit()),
2236 "code {code:?} for {} does not match MIR####",
2237 kind.name(),
2238 );
2239 }
2240 }
2241
2242 #[test]
2243 fn codes_are_unique() {
2244 let kinds = one_of_each();
2245 let mut seen: HashSet<&'static str> = HashSet::new();
2246 for kind in &kinds {
2247 assert!(
2248 seen.insert(kind.code()),
2249 "duplicate code {} (variant {})",
2250 kind.code(),
2251 kind.name(),
2252 );
2253 }
2254 }
2255
2256 #[test]
2257 fn display_includes_code() {
2258 let issue = Issue::new(
2259 IssueKind::UndefinedClass {
2260 name: "Foo".to_string(),
2261 },
2262 Location {
2263 file: Arc::from("src/x.php"),
2264 line: 1,
2265 line_end: 1,
2266 col_start: 0,
2267 col_end: 3,
2268 },
2269 );
2270 let raw = format!("{issue}");
2273 let stripped: String = {
2274 let mut out = String::new();
2275 let mut chars = raw.chars();
2276 while let Some(c) = chars.next() {
2277 if c == '\u{1b}' {
2278 for c2 in chars.by_ref() {
2279 if c2 == 'm' {
2280 break;
2281 }
2282 }
2283 } else {
2284 out.push(c);
2285 }
2286 }
2287 out
2288 };
2289 assert!(
2290 stripped.contains("error[MIR0005] UndefinedClass:"),
2291 "Display output missing code/name segment: {stripped:?}",
2292 );
2293 }
2294
2295 #[test]
2296 fn default_severity_for_code_round_trips() {
2297 for kind in one_of_each() {
2298 let code = kind.code();
2299 assert_eq!(
2300 IssueKind::default_severity_for_code(code),
2301 Some(kind.default_severity()),
2302 "severity mismatch for {code} (variant {})",
2303 kind.name(),
2304 );
2305 }
2306 }
2307
2308 #[test]
2309 fn default_severity_for_code_unknown_returns_none() {
2310 assert_eq!(IssueKind::default_severity_for_code("MIR9999"), None);
2311 assert_eq!(IssueKind::default_severity_for_code(""), None);
2312 assert_eq!(IssueKind::default_severity_for_code("mir0001"), None);
2313 }
2314
2315 #[test]
2318 fn one_of_each_has_every_variant() {
2319 assert_eq!(one_of_each().len(), 139);
2322 }
2323}