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