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 NotAnInterface { name: String },
92
93 NullArgument { param: String, fn_name: String },
97 NullPropertyFetch { property: String },
100 NullMethodCall { method: String },
103 NullArrayAccess,
106 PossiblyNullArgument { param: String, fn_name: String },
109 PossiblyInvalidArgument {
112 param: String,
113 fn_name: String,
114 expected: String,
115 actual: String,
116 },
117 PossiblyNullPropertyFetch { property: String },
120 PossiblyNullMethodCall { method: String },
123 PossiblyNullArrayAccess,
126 NullableReturnStatement { expected: String, actual: String },
129
130 InvalidReturnType { expected: String, actual: String },
134 InvalidArgument {
137 param: String,
138 fn_name: String,
139 expected: String,
140 actual: String,
141 },
142 TooFewArguments {
145 fn_name: String,
146 expected: usize,
147 actual: usize,
148 },
149 TooManyArguments {
152 fn_name: String,
153 expected: usize,
154 actual: usize,
155 },
156 InvalidNamedArgument { fn_name: String, name: String },
159 InvalidNamedArguments { fn_name: String },
162 InvalidPassByReference { fn_name: String, param: String },
165 InvalidPropertyFetch { ty: String },
168 InvalidArrayAccess { ty: String },
171 PossiblyInvalidArrayAccess { ty: String },
174 InvalidArrayAssignment { ty: String },
177 InvalidPropertyAssignment {
180 property: String,
181 expected: String,
182 actual: String,
183 },
184 InvalidCast { from: String, to: String },
187 InvalidStaticInvocation { class: String, method: String },
190 InvalidOperand {
194 op: String,
195 left: String,
196 right: String,
197 },
198 PossiblyInvalidOperand {
201 op: String,
202 left: String,
203 right: String,
204 },
205 PossiblyNullOperand { op: String, ty: String },
208 DivisionByZero { op: String },
212 RawObjectIteration { ty: String },
215 PossiblyRawObjectIteration { ty: String },
218 MismatchingDocblockReturnType { declared: String, inferred: String },
221 MismatchingDocblockParamType {
224 param: String,
225 declared: String,
226 inferred: String,
227 },
228 TypeCheckMismatch {
231 var: String,
232 expected: String,
233 actual: String,
234 },
235
236 Trace { variable: String, type_info: String },
239
240 InvalidArrayOffset { expected: String, actual: String },
244 NonExistentArrayOffset { key: String },
248 PossiblyInvalidArrayOffset { expected: String, actual: String },
251 DuplicateArrayKey { key: String },
256
257 RedundantCondition { ty: String },
261 RedundantCast { from: String, to: String },
264 UnnecessaryVarAnnotation { var: String },
267 TypeDoesNotContainType { left: String, right: String },
270 ParadoxicalCondition { value: String },
273 DocblockTypeContradiction { expr: String, declared: String },
278 ImpossibleIdenticalComparison {
284 op: String,
285 left: String,
286 right: String,
287 },
288 ImpossibleLooseComparison {
295 op: String,
296 left: String,
297 right: String,
298 },
299 UnevaluatedCode { reason: String },
306
307 UnusedVariable { name: String },
311 UnusedParam { name: String },
314 UnreachableCode,
317 UnhandledMatchCondition { detail: String },
320 UnusedMethod { class: String, method: String },
323 UnusedProperty { class: String, property: String },
326 UnusedFunction { name: String },
329 UnusedForeachValue { name: String },
332 UnusedClass { class: String },
335 UnusedSuppress { kind: String },
339
340 ArgumentTypeCoercion {
343 param: String,
344 fn_name: String,
345 expected: String,
346 actual: String,
347 },
348
349 PropertyTypeCoercion {
352 property: String,
353 expected: String,
354 actual: String,
355 },
356
357 ImpurePropertyAssignment { property: String },
360 ImpureMethodCall { method: String },
362 ImpureGlobalVariable { variable: String },
364 ImpureStaticVariable { variable: String },
366 ImpureStaticPropertyAssignment { class: String, property: String },
370 ImpureFunctionCall { fn_name: String },
374 ImmutablePropertyModification { property: String },
378
379 ReadonlyPropertyAssignment { class: String, property: String },
383
384 UnimplementedAbstractMethod { class: String, method: String },
388 UnimplementedInterfaceMethod {
391 class: String,
392 interface: String,
393 method: String,
394 },
395 MethodSignatureMismatch {
398 class: String,
399 method: String,
400 detail: String,
401 },
402 OverriddenMethodAccess { class: String, method: String },
405 OverriddenPropertyAccess { class: String, property: String },
408 PropertyTypeRedeclarationMismatch {
411 class: String,
412 property: String,
413 expected: String,
414 actual: String,
415 },
416 ReadonlyPropertyRedeclarationMismatch {
419 parent_class: String,
420 class: String,
421 property: String,
422 parent_readonly: bool,
425 },
426 StaticPropertyRedeclarationMismatch {
429 parent_class: String,
430 class: String,
431 property: String,
432 parent_static: bool,
435 },
436 BackedEnumCaseTypeMismatch {
439 enum_name: String,
440 case_name: String,
441 expected: String,
442 actual: String,
443 },
444 DirectConstructorCall { class: String },
447 InvalidExtendClass { parent: String, child: String },
450 FinalMethodOverridden {
453 class: String,
454 method: String,
455 parent: String,
456 },
457 AbstractInstantiation { class: String },
460 AbstractMethodCall { class: String, method: String },
463 InterfaceInstantiation { class: String },
466 InvalidOverride {
470 class: String,
471 method: String,
472 detail: String,
473 },
474
475 TaintedInput { sink: String },
479 TaintedHtml,
482 TaintedSql,
485 TaintedShell,
488 TaintedLlmPrompt,
492
493 InvalidTemplateParam {
497 name: String,
498 expected_bound: String,
499 actual: String,
500 },
501 ShadowedTemplateParam { name: String },
504 IfThisIsMismatch {
509 class: String,
510 method: String,
511 expected: String,
512 actual: String,
513 },
514
515 DeprecatedCall {
519 name: String,
520 message: Option<Arc<str>>,
521 },
522 DeprecatedProperty {
525 class: String,
526 property: String,
527 message: Option<Arc<str>>,
528 },
529 DeprecatedConstant {
532 class: String,
533 constant: String,
534 message: Option<Arc<str>>,
535 },
536 DeprecatedInterface {
539 name: String,
540 message: Option<Arc<str>>,
541 },
542 DeprecatedTrait {
545 name: String,
546 message: Option<Arc<str>>,
547 },
548 DeprecatedMethodCall {
551 class: String,
552 method: String,
553 message: Option<Arc<str>>,
554 },
555 DeprecatedMethod {
558 class: String,
559 method: String,
560 message: Option<Arc<str>>,
561 },
562 DeprecatedClass {
565 name: String,
566 message: Option<Arc<str>>,
567 },
568 InternalMethod { class: String, method: String },
571 MissingReturnType { fn_name: String },
574 MissingClosureReturnType,
577 MissingParamType { fn_name: String, param: String },
580 MissingPropertyType { class: String, property: String },
583 InvalidThrow { ty: String },
586 InvalidCatch { ty: String },
589 UnreachableCatch { ty: String, shadowed_by: String },
594 MissingThrowsDocblock { class: String },
597 ImplicitToStringCast { class: String },
600 ImplicitFloatToIntCast { from: String },
603 ParseError { message: String },
606 InvalidDocblock { message: String },
609 MixedArgument { param: String, fn_name: String },
612 MixedAssignment { var: String },
615 MixedMethodCall { method: String },
618 UnsupportedReferenceUsage,
621 NoInterfaceProperties { property: String },
625 UndefinedDocblockClass { name: String },
629 MissingConstructor { class: String },
632 MixedFunctionCall,
635 MixedReturnStatement { declared: String },
638 MixedPropertyFetch { property: String },
641 MixedPropertyAssignment { property: String },
644 MixedArrayAccess,
647 MixedArrayOffset,
650 MixedClone,
653 InvalidClone { ty: String },
657 PossiblyInvalidClone { ty: String },
661 InvalidToString { class: String },
665 CircularInheritance { class: String },
668
669 InvalidTraitUse { trait_name: String, reason: String },
673 ForbiddenCode { message: String },
676
677 InvalidAttribute { message: String },
681 UndefinedAttributeClass { name: String },
684
685 WrongCaseFunction { used: String, canonical: String },
689 WrongCaseMethod {
692 class: String,
693 used: String,
694 canonical: String,
695 },
696 WrongCaseClass { used: String, canonical: String },
699 DuplicateClass { name: String },
702 DuplicateInterface { name: String },
705 DuplicateTrait { name: String },
708 DuplicateEnum { name: String },
711 DuplicateFunction { name: String },
714}
715
716fn append_deprecation_message(base: String, message: &Option<Arc<str>>) -> String {
717 match message.as_deref().filter(|m| !m.is_empty()) {
718 Some(msg) => format!("{base}: {msg}"),
719 None => base,
720 }
721}
722
723impl IssueKind {
724 pub fn default_severity(&self) -> Severity {
726 match self {
727 IssueKind::NonStaticSelfCall { .. }
729 | IssueKind::DirectConstructorCall { .. }
730 | IssueKind::InvalidScope { .. }
731 | IssueKind::UndefinedVariable { .. }
732 | IssueKind::UndefinedFunction { .. }
733 | IssueKind::UndefinedMethod { .. }
734 | IssueKind::UndefinedClass { .. }
735 | IssueKind::NotAnInterface { .. }
736 | IssueKind::UndefinedConstant { .. }
737 | IssueKind::InaccessibleClassConstant { .. }
738 | IssueKind::InvalidReturnType { .. }
739 | IssueKind::InvalidArgument { .. }
740 | IssueKind::TooFewArguments { .. }
741 | IssueKind::TooManyArguments { .. }
742 | IssueKind::InvalidNamedArgument { .. }
743 | IssueKind::InvalidNamedArguments { .. }
744 | IssueKind::InvalidPassByReference { .. }
745 | IssueKind::InvalidThrow { .. }
746 | IssueKind::InvalidCatch { .. }
747 | IssueKind::InvalidStaticInvocation { .. }
748 | IssueKind::UnimplementedAbstractMethod { .. }
749 | IssueKind::UnimplementedInterfaceMethod { .. }
750 | IssueKind::MethodSignatureMismatch { .. }
751 | IssueKind::InvalidExtendClass { .. }
752 | IssueKind::FinalMethodOverridden { .. }
753 | IssueKind::AbstractInstantiation { .. }
754 | IssueKind::AbstractMethodCall { .. }
755 | IssueKind::InterfaceInstantiation { .. }
756 | IssueKind::InvalidOverride { .. }
757 | IssueKind::InvalidTemplateParam { .. }
758 | IssueKind::ReadonlyPropertyAssignment { .. }
759 | IssueKind::ParseError { .. }
760 | IssueKind::TaintedInput { .. }
761 | IssueKind::TaintedHtml
762 | IssueKind::TaintedSql
763 | IssueKind::TaintedShell
764 | IssueKind::TaintedLlmPrompt
765 | IssueKind::CircularInheritance { .. }
766 | IssueKind::InvalidTraitUse { .. }
767 | IssueKind::UndefinedTrait { .. }
768 | IssueKind::InvalidClone { .. }
769 | IssueKind::InvalidToString { .. }
770 | IssueKind::TypeCheckMismatch { .. }
771 | IssueKind::PropertyTypeRedeclarationMismatch { .. }
772 | IssueKind::ReadonlyPropertyRedeclarationMismatch { .. }
773 | IssueKind::StaticPropertyRedeclarationMismatch { .. }
774 | IssueKind::BackedEnumCaseTypeMismatch { .. }
775 | IssueKind::DivisionByZero { .. }
776 | IssueKind::ParentNotFound => Severity::Error,
777 IssueKind::Trace { .. } => Severity::Info,
778
779 IssueKind::NullArgument { .. }
781 | IssueKind::NullPropertyFetch { .. }
782 | IssueKind::NullMethodCall { .. }
783 | IssueKind::NullArrayAccess
784 | IssueKind::NullableReturnStatement { .. }
785 | IssueKind::InvalidPropertyFetch { .. }
786 | IssueKind::InvalidArrayAccess { .. }
787 | IssueKind::InvalidArrayAssignment { .. }
788 | IssueKind::InvalidPropertyAssignment { .. }
789 | IssueKind::InvalidArrayOffset { .. }
790 | IssueKind::NonExistentArrayOffset { .. }
791 | IssueKind::PossiblyInvalidArrayOffset { .. }
792 | IssueKind::UndefinedProperty { .. }
793 | IssueKind::InvalidOperand { .. }
794 | IssueKind::OverriddenMethodAccess { .. }
795 | IssueKind::OverriddenPropertyAccess { .. }
796 | IssueKind::ImplicitToStringCast { .. }
797 | IssueKind::ImplicitFloatToIntCast { .. }
798 | IssueKind::UnusedVariable { .. }
799 | IssueKind::UnusedForeachValue { .. }
800 | IssueKind::ImpurePropertyAssignment { .. }
801 | IssueKind::ImpureMethodCall { .. }
802 | IssueKind::ImpureGlobalVariable { .. }
803 | IssueKind::ImpureStaticVariable { .. }
804 | IssueKind::ImpureStaticPropertyAssignment { .. }
805 | IssueKind::ImpureFunctionCall { .. }
806 | IssueKind::ImmutablePropertyModification { .. }
807 | IssueKind::UnsupportedReferenceUsage
808 | IssueKind::ParadoxicalCondition { .. }
809 | IssueKind::UnhandledMatchCondition { .. }
810 | IssueKind::InvalidStringClass { .. }
811 | IssueKind::ImpossibleIdenticalComparison { .. }
812 | IssueKind::ImpossibleLooseComparison { .. }
813 | IssueKind::DuplicateArrayKey { .. }
814 | IssueKind::ForbiddenCode { .. } => Severity::Warning,
815
816 IssueKind::PossiblyUndefinedVariable { .. } => Severity::Warning,
818
819 IssueKind::PossiblyNullArgument { .. }
821 | IssueKind::PossiblyInvalidArgument { .. }
822 | IssueKind::PossiblyNullPropertyFetch { .. }
823 | IssueKind::PossiblyNullMethodCall { .. }
824 | IssueKind::PossiblyNullArrayAccess
825 | IssueKind::PossiblyInvalidArrayAccess { .. }
826 | IssueKind::PossiblyInvalidClone { .. }
827 | IssueKind::PossiblyInvalidOperand { .. }
828 | IssueKind::PossiblyNullOperand { .. }
829 | IssueKind::PossiblyRawObjectIteration { .. } => Severity::Info,
830
831 IssueKind::RawObjectIteration { .. } => Severity::Warning,
832
833 IssueKind::RedundantCondition { .. }
835 | IssueKind::RedundantCast { .. }
836 | IssueKind::UnnecessaryVarAnnotation { .. }
837 | IssueKind::TypeDoesNotContainType { .. }
838 | IssueKind::DocblockTypeContradiction { .. }
839 | IssueKind::UnevaluatedCode { .. }
840 | IssueKind::IfThisIsMismatch { .. }
841 | IssueKind::UnusedParam { .. }
842 | IssueKind::UnreachableCode
843 | IssueKind::UnreachableCatch { .. }
844 | IssueKind::UnusedMethod { .. }
845 | IssueKind::UnusedProperty { .. }
846 | IssueKind::UnusedFunction { .. }
847 | IssueKind::UnusedClass { .. }
848 | IssueKind::UnusedSuppress { .. }
849 | IssueKind::ArgumentTypeCoercion { .. }
850 | IssueKind::PropertyTypeCoercion { .. }
851 | IssueKind::DeprecatedCall { .. }
852 | IssueKind::DeprecatedProperty { .. }
853 | IssueKind::DeprecatedConstant { .. }
854 | IssueKind::DeprecatedInterface { .. }
855 | IssueKind::DeprecatedTrait { .. }
856 | IssueKind::DeprecatedMethodCall { .. }
857 | IssueKind::DeprecatedMethod { .. }
858 | IssueKind::DeprecatedClass { .. }
859 | IssueKind::InternalMethod { .. }
860 | IssueKind::MissingReturnType { .. }
861 | IssueKind::MissingClosureReturnType
862 | IssueKind::MissingParamType { .. }
863 | IssueKind::MissingPropertyType { .. }
864 | IssueKind::MismatchingDocblockReturnType { .. }
865 | IssueKind::MismatchingDocblockParamType { .. }
866 | IssueKind::InvalidDocblock { .. }
867 | IssueKind::InvalidCast { .. }
868 | IssueKind::MixedArgument { .. }
869 | IssueKind::MixedAssignment { .. }
870 | IssueKind::MixedMethodCall { .. }
871 | IssueKind::NoInterfaceProperties { .. }
872 | IssueKind::UndefinedDocblockClass { .. }
873 | IssueKind::MissingConstructor { .. }
874 | IssueKind::MixedFunctionCall
875 | IssueKind::MixedReturnStatement { .. }
876 | IssueKind::MixedPropertyFetch { .. }
877 | IssueKind::MixedPropertyAssignment { .. }
878 | IssueKind::MixedArrayAccess
879 | IssueKind::MixedArrayOffset
880 | IssueKind::MixedClone
881 | IssueKind::ShadowedTemplateParam { .. }
882 | IssueKind::MissingThrowsDocblock { .. }
883 | IssueKind::WrongCaseFunction { .. }
884 | IssueKind::WrongCaseMethod { .. }
885 | IssueKind::WrongCaseClass { .. }
886 | IssueKind::InvalidAttribute { .. }
887 | IssueKind::UndefinedAttributeClass { .. } => Severity::Info,
888 IssueKind::DuplicateClass { .. }
889 | IssueKind::DuplicateInterface { .. }
890 | IssueKind::DuplicateTrait { .. }
891 | IssueKind::DuplicateEnum { .. }
892 | IssueKind::DuplicateFunction { .. } => Severity::Error,
893 }
894 }
895
896 pub fn code(&self) -> &'static str {
924 match self {
925 IssueKind::NonStaticSelfCall { .. } => "MIR0216",
927 IssueKind::DirectConstructorCall { .. } => "MIR0217",
928 IssueKind::InvalidScope { .. } => "MIR0001",
929 IssueKind::UndefinedVariable { .. } => "MIR0002",
930 IssueKind::UndefinedFunction { .. } => "MIR0003",
931 IssueKind::UndefinedMethod { .. } => "MIR0004",
932 IssueKind::UndefinedClass { .. } => "MIR0005",
933 IssueKind::UndefinedProperty { .. } => "MIR0006",
934 IssueKind::UndefinedConstant { .. } => "MIR0007",
935 IssueKind::InaccessibleClassConstant { .. } => "MIR0011",
936 IssueKind::PossiblyUndefinedVariable { .. } => "MIR0008",
937 IssueKind::UndefinedTrait { .. } => "MIR0009",
938 IssueKind::ParentNotFound => "MIR0010",
939
940 IssueKind::NullArgument { .. } => "MIR0100",
942 IssueKind::NullPropertyFetch { .. } => "MIR0101",
943 IssueKind::NullMethodCall { .. } => "MIR0102",
944 IssueKind::NullArrayAccess => "MIR0103",
945 IssueKind::PossiblyNullArgument { .. } => "MIR0104",
946 IssueKind::PossiblyInvalidArgument { .. } => "MIR0105",
947 IssueKind::PossiblyNullPropertyFetch { .. } => "MIR0106",
948 IssueKind::PossiblyNullMethodCall { .. } => "MIR0107",
949 IssueKind::PossiblyNullArrayAccess => "MIR0108",
950 IssueKind::NullableReturnStatement { .. } => "MIR0109",
951
952 IssueKind::InvalidReturnType { .. } => "MIR0200",
954 IssueKind::InvalidArgument { .. } => "MIR0201",
955 IssueKind::TooFewArguments { .. } => "MIR0202",
956 IssueKind::TooManyArguments { .. } => "MIR0203",
957 IssueKind::InvalidNamedArgument { .. } => "MIR0204",
958 IssueKind::InvalidNamedArguments { .. } => "MIR0224",
959 IssueKind::InvalidPassByReference { .. } => "MIR0205",
960 IssueKind::InvalidPropertyFetch { .. } => "MIR0218",
961 IssueKind::InvalidArrayAccess { .. } => "MIR0219",
962 IssueKind::PossiblyInvalidArrayAccess { .. } => "MIR0227",
963 IssueKind::InvalidArrayAssignment { .. } => "MIR0220",
964 IssueKind::InvalidPropertyAssignment { .. } => "MIR0206",
965 IssueKind::InvalidCast { .. } => "MIR0207",
966 IssueKind::InvalidStaticInvocation { .. } => "MIR0215",
967 IssueKind::InvalidOperand { .. } => "MIR0208",
968 IssueKind::PossiblyInvalidOperand { .. } => "MIR0213",
969 IssueKind::PossiblyNullOperand { .. } => "MIR0214",
970 IssueKind::DivisionByZero { .. } => "MIR0229",
971 IssueKind::RawObjectIteration { .. } => "MIR0222",
972 IssueKind::PossiblyRawObjectIteration { .. } => "MIR0223",
973 IssueKind::MismatchingDocblockReturnType { .. } => "MIR0209",
974 IssueKind::MismatchingDocblockParamType { .. } => "MIR0210",
975 IssueKind::InvalidStringClass { .. } => "MIR0211",
976 IssueKind::NotAnInterface { .. } => "MIR0228",
977 IssueKind::TypeCheckMismatch { .. } => "MIR0212",
978 IssueKind::Trace { .. } => "MIR0221",
979 IssueKind::ArgumentTypeCoercion { .. } => "MIR0225",
980 IssueKind::PropertyTypeCoercion { .. } => "MIR0226",
981
982 IssueKind::InvalidArrayOffset { .. } => "MIR0300",
984 IssueKind::NonExistentArrayOffset { .. } => "MIR0301",
985 IssueKind::PossiblyInvalidArrayOffset { .. } => "MIR0302",
986 IssueKind::DuplicateArrayKey { .. } => "MIR0303",
987
988 IssueKind::RedundantCondition { .. } => "MIR0400",
990 IssueKind::RedundantCast { .. } => "MIR0401",
991 IssueKind::UnnecessaryVarAnnotation { .. } => "MIR0402",
992 IssueKind::TypeDoesNotContainType { .. } => "MIR0403",
993 IssueKind::ParadoxicalCondition { .. } => "MIR0404",
994 IssueKind::UnhandledMatchCondition { .. } => "MIR0405",
995 IssueKind::DocblockTypeContradiction { .. } => "MIR0406",
996 IssueKind::UnevaluatedCode { .. } => "MIR0407",
997 IssueKind::ImpossibleIdenticalComparison { .. } => "MIR0408",
998 IssueKind::ImpossibleLooseComparison { .. } => "MIR0409",
999
1000 IssueKind::UnusedVariable { .. } => "MIR0500",
1002 IssueKind::UnusedParam { .. } => "MIR0501",
1003 IssueKind::UnreachableCode => "MIR0502",
1004 IssueKind::UnusedMethod { .. } => "MIR0503",
1005 IssueKind::UnusedProperty { .. } => "MIR0504",
1006 IssueKind::UnusedFunction { .. } => "MIR0505",
1007 IssueKind::UnusedForeachValue { .. } => "MIR0506",
1008 IssueKind::UnusedClass { .. } => "MIR0507",
1009 IssueKind::UnusedSuppress { .. } => "MIR0508",
1010
1011 IssueKind::ImpurePropertyAssignment { .. } => "MIR1700",
1013 IssueKind::ImpureMethodCall { .. } => "MIR1701",
1014 IssueKind::ImpureGlobalVariable { .. } => "MIR1702",
1015 IssueKind::ImpureStaticVariable { .. } => "MIR1703",
1016 IssueKind::ImpureStaticPropertyAssignment { .. } => "MIR1706",
1017 IssueKind::ImpureFunctionCall { .. } => "MIR1704",
1018 IssueKind::ImmutablePropertyModification { .. } => "MIR1705",
1019 IssueKind::UnsupportedReferenceUsage => "MIR1506",
1020 IssueKind::NoInterfaceProperties { .. } => "MIR1504",
1021 IssueKind::UndefinedDocblockClass { .. } => "MIR1505",
1022 IssueKind::MissingConstructor { .. } => "MIR1507",
1023 IssueKind::MixedFunctionCall => "MIR1211",
1024 IssueKind::MixedReturnStatement { .. } => "MIR1212",
1025
1026 IssueKind::ReadonlyPropertyAssignment { .. } => "MIR0600",
1028
1029 IssueKind::UnimplementedAbstractMethod { .. } => "MIR0700",
1031 IssueKind::UnimplementedInterfaceMethod { .. } => "MIR0701",
1032 IssueKind::MethodSignatureMismatch { .. } => "MIR0702",
1033 IssueKind::OverriddenMethodAccess { .. } => "MIR0703",
1034 IssueKind::OverriddenPropertyAccess { .. } => "MIR0710",
1035 IssueKind::PropertyTypeRedeclarationMismatch { .. } => "MIR0712",
1036 IssueKind::BackedEnumCaseTypeMismatch { .. } => "MIR0713",
1037 IssueKind::ReadonlyPropertyRedeclarationMismatch { .. } => "MIR0714",
1038 IssueKind::StaticPropertyRedeclarationMismatch { .. } => "MIR0715",
1039 IssueKind::InvalidExtendClass { .. } => "MIR0704",
1040 IssueKind::FinalMethodOverridden { .. } => "MIR0705",
1041 IssueKind::AbstractInstantiation { .. } => "MIR0706",
1042 IssueKind::AbstractMethodCall { .. } => "MIR0711",
1043 IssueKind::InterfaceInstantiation { .. } => "MIR0709",
1044 IssueKind::CircularInheritance { .. } => "MIR0707",
1045 IssueKind::InvalidOverride { .. } => "MIR0708",
1046
1047 IssueKind::TaintedInput { .. } => "MIR0800",
1049 IssueKind::TaintedHtml => "MIR0801",
1050 IssueKind::TaintedSql => "MIR0802",
1051 IssueKind::TaintedShell => "MIR0803",
1052 IssueKind::TaintedLlmPrompt => "MIR0804",
1053
1054 IssueKind::InvalidTemplateParam { .. } => "MIR0900",
1056 IssueKind::ShadowedTemplateParam { .. } => "MIR0901",
1057 IssueKind::IfThisIsMismatch { .. } => "MIR0902",
1058
1059 IssueKind::DeprecatedCall { .. } => "MIR1000",
1061 IssueKind::WrongCaseFunction { .. } => "MIR1009",
1062 IssueKind::WrongCaseMethod { .. } => "MIR1010",
1063 IssueKind::WrongCaseClass { .. } => "MIR1011",
1064 IssueKind::DeprecatedProperty { .. } => "MIR1005",
1065 IssueKind::DeprecatedInterface { .. } => "MIR1006",
1066 IssueKind::DeprecatedTrait { .. } => "MIR1007",
1067 IssueKind::DeprecatedConstant { .. } => "MIR1008",
1068 IssueKind::DeprecatedMethodCall { .. } => "MIR1001",
1069 IssueKind::DeprecatedMethod { .. } => "MIR1002",
1070 IssueKind::DeprecatedClass { .. } => "MIR1003",
1071 IssueKind::InternalMethod { .. } => "MIR1004",
1072
1073 IssueKind::MissingReturnType { .. } => "MIR1100",
1075 IssueKind::MissingParamType { .. } => "MIR1101",
1076 IssueKind::MissingPropertyType { .. } => "MIR1104",
1077 IssueKind::MissingClosureReturnType => "MIR1105",
1078 IssueKind::MissingThrowsDocblock { .. } => "MIR1102",
1079 IssueKind::InvalidDocblock { .. } => "MIR1103",
1080
1081 IssueKind::MixedArgument { .. } => "MIR1200",
1083 IssueKind::MixedAssignment { .. } => "MIR1201",
1084 IssueKind::MixedMethodCall { .. } => "MIR1202",
1085 IssueKind::MixedPropertyFetch { .. } => "MIR1203",
1086 IssueKind::MixedPropertyAssignment { .. } => "MIR1208",
1087 IssueKind::MixedArrayAccess => "MIR1209",
1088 IssueKind::MixedArrayOffset => "MIR1210",
1089 IssueKind::MixedClone => "MIR1204",
1090 IssueKind::InvalidClone { .. } => "MIR1205",
1091 IssueKind::PossiblyInvalidClone { .. } => "MIR1206",
1092 IssueKind::InvalidToString { .. } => "MIR1207",
1093
1094 IssueKind::InvalidTraitUse { .. } => "MIR1300",
1096 IssueKind::ForbiddenCode { .. } => "MIR1301",
1097
1098 IssueKind::ParseError { .. } => "MIR1400",
1100
1101 IssueKind::InvalidAttribute { .. } => "MIR1600",
1103 IssueKind::UndefinedAttributeClass { .. } => "MIR1601",
1104 IssueKind::DuplicateClass { .. } => "MIR1602",
1105 IssueKind::DuplicateInterface { .. } => "MIR1603",
1106 IssueKind::DuplicateTrait { .. } => "MIR1604",
1107 IssueKind::DuplicateEnum { .. } => "MIR1605",
1108 IssueKind::DuplicateFunction { .. } => "MIR1606",
1109
1110 IssueKind::InvalidThrow { .. } => "MIR1500",
1112 IssueKind::InvalidCatch { .. } => "MIR1503",
1113 IssueKind::UnreachableCatch { .. } => "MIR1508",
1114 IssueKind::ImplicitToStringCast { .. } => "MIR1501",
1115 IssueKind::ImplicitFloatToIntCast { .. } => "MIR1502",
1116 }
1117 }
1118
1119 pub fn default_severity_for_code(code: &str) -> Option<Severity> {
1125 match code {
1126 "MIR0001" | "MIR0002" | "MIR0003" | "MIR0004" | "MIR0005" | "MIR0007" | "MIR0009"
1128 | "MIR0010" | "MIR0011" | "MIR0200" | "MIR0201" | "MIR0202" | "MIR0203" | "MIR0204"
1129 | "MIR0205" | "MIR0212" | "MIR0215" | "MIR0216" | "MIR0217" | "MIR0224" | "MIR0600"
1130 | "MIR0700" | "MIR0701" | "MIR0702" | "MIR0704" | "MIR0705" | "MIR0706" | "MIR0707"
1131 | "MIR0708" | "MIR0709" | "MIR0711" | "MIR0712" | "MIR0713" | "MIR0714" | "MIR0715"
1132 | "MIR0228" | "MIR0229" | "MIR0800" | "MIR0801" | "MIR0802" | "MIR0803" | "MIR0804"
1133 | "MIR0900" | "MIR1205" | "MIR1207" | "MIR1300" | "MIR1400" | "MIR1500" | "MIR1503"
1134 | "MIR1602" | "MIR1603" | "MIR1604" | "MIR1605" | "MIR1606" => Some(Severity::Error),
1135
1136 "MIR0006" | "MIR0008" | "MIR0100" | "MIR0101" | "MIR0102" | "MIR0103" | "MIR0109"
1138 | "MIR0206" | "MIR0208" | "MIR0211" | "MIR0218" | "MIR0219" | "MIR0220" | "MIR0222"
1139 | "MIR0300" | "MIR0301" | "MIR0302" | "MIR0303" | "MIR0404" | "MIR0405" | "MIR0408"
1140 | "MIR0500" | "MIR0506" | "MIR0703" | "MIR0710" | "MIR1301" | "MIR1501" | "MIR1502"
1141 | "MIR1700" | "MIR1701" | "MIR1702" | "MIR1703" | "MIR1704" | "MIR1705" | "MIR1706"
1142 | "MIR1506" => Some(Severity::Warning),
1143
1144 "MIR0104" | "MIR0105" | "MIR0106" | "MIR0107" | "MIR0108" | "MIR0207" | "MIR0209"
1146 | "MIR0210" | "MIR0213" | "MIR0214" | "MIR0221" | "MIR0223" | "MIR0400" | "MIR0401"
1147 | "MIR0402" | "MIR0403" | "MIR0501" | "MIR0502" | "MIR0503" | "MIR0504" | "MIR0505"
1148 | "MIR0507" | "MIR0508" | "MIR0901" | "MIR1000" | "MIR1001" | "MIR1002" | "MIR1003"
1149 | "MIR1004" | "MIR1005" | "MIR1006" | "MIR1007" | "MIR1008" | "MIR1009" | "MIR1010"
1150 | "MIR1011" | "MIR1100" | "MIR1101" | "MIR1102" | "MIR1103" | "MIR1104" | "MIR1105"
1151 | "MIR1200" | "MIR1201" | "MIR1202" | "MIR1203" | "MIR1204" | "MIR1206" | "MIR1208"
1152 | "MIR1209" | "MIR1210" | "MIR1211" | "MIR1212" | "MIR1504" | "MIR1505" | "MIR1507"
1153 | "MIR1508" | "MIR1600" | "MIR1601" | "MIR0225" | "MIR0226" | "MIR0227" | "MIR0406"
1154 | "MIR0407" | "MIR0902" => Some(Severity::Info),
1155
1156 _ => None,
1157 }
1158 }
1159
1160 pub fn name(&self) -> &'static str {
1162 match self {
1163 IssueKind::NonStaticSelfCall { .. } => "NonStaticSelfCall",
1164 IssueKind::DirectConstructorCall { .. } => "DirectConstructorCall",
1165 IssueKind::InvalidScope { .. } => "InvalidScope",
1166 IssueKind::UndefinedVariable { .. } => "UndefinedVariable",
1167 IssueKind::UndefinedFunction { .. } => "UndefinedFunction",
1168 IssueKind::UndefinedMethod { .. } => "UndefinedMethod",
1169 IssueKind::UndefinedClass { .. } => "UndefinedClass",
1170 IssueKind::UndefinedProperty { .. } => "UndefinedProperty",
1171 IssueKind::UndefinedConstant { .. } => "UndefinedConstant",
1172 IssueKind::InaccessibleClassConstant { .. } => "InaccessibleClassConstant",
1173 IssueKind::PossiblyUndefinedVariable { .. } => "PossiblyUndefinedVariable",
1174 IssueKind::UndefinedTrait { .. } => "UndefinedTrait",
1175 IssueKind::ParentNotFound => "ParentNotFound",
1176 IssueKind::InvalidStringClass { .. } => "InvalidStringClass",
1177 IssueKind::NotAnInterface { .. } => "NotAnInterface",
1178 IssueKind::NullArgument { .. } => "NullArgument",
1179 IssueKind::NullPropertyFetch { .. } => "NullPropertyFetch",
1180 IssueKind::NullMethodCall { .. } => "NullMethodCall",
1181 IssueKind::NullArrayAccess => "NullArrayAccess",
1182 IssueKind::PossiblyNullArgument { .. } => "PossiblyNullArgument",
1183 IssueKind::PossiblyInvalidArgument { .. } => "PossiblyInvalidArgument",
1184 IssueKind::PossiblyNullPropertyFetch { .. } => "PossiblyNullPropertyFetch",
1185 IssueKind::PossiblyNullMethodCall { .. } => "PossiblyNullMethodCall",
1186 IssueKind::PossiblyNullArrayAccess => "PossiblyNullArrayAccess",
1187 IssueKind::NullableReturnStatement { .. } => "NullableReturnStatement",
1188 IssueKind::InvalidReturnType { .. } => "InvalidReturnType",
1189 IssueKind::InvalidArgument { .. } => "InvalidArgument",
1190 IssueKind::TooFewArguments { .. } => "TooFewArguments",
1191 IssueKind::TooManyArguments { .. } => "TooManyArguments",
1192 IssueKind::InvalidNamedArgument { .. } => "InvalidNamedArgument",
1193 IssueKind::InvalidNamedArguments { .. } => "InvalidNamedArguments",
1194 IssueKind::InvalidPassByReference { .. } => "InvalidPassByReference",
1195 IssueKind::InvalidPropertyFetch { .. } => "InvalidPropertyFetch",
1196 IssueKind::InvalidArrayAccess { .. } => "InvalidArrayAccess",
1197 IssueKind::PossiblyInvalidArrayAccess { .. } => "PossiblyInvalidArrayAccess",
1198 IssueKind::InvalidArrayAssignment { .. } => "InvalidArrayAssignment",
1199 IssueKind::InvalidPropertyAssignment { .. } => "InvalidPropertyAssignment",
1200 IssueKind::InvalidCast { .. } => "InvalidCast",
1201 IssueKind::InvalidStaticInvocation { .. } => "InvalidStaticInvocation",
1202 IssueKind::InvalidOperand { .. } => "InvalidOperand",
1203 IssueKind::PossiblyInvalidOperand { .. } => "PossiblyInvalidOperand",
1204 IssueKind::PossiblyNullOperand { .. } => "PossiblyNullOperand",
1205 IssueKind::DivisionByZero { .. } => "DivisionByZero",
1206 IssueKind::RawObjectIteration { .. } => "RawObjectIteration",
1207 IssueKind::PossiblyRawObjectIteration { .. } => "PossiblyRawObjectIteration",
1208 IssueKind::MismatchingDocblockReturnType { .. } => "MismatchingDocblockReturnType",
1209 IssueKind::MismatchingDocblockParamType { .. } => "MismatchingDocblockParamType",
1210 IssueKind::TypeCheckMismatch { .. } => "TypeCheckMismatch",
1211 IssueKind::DocblockTypeContradiction { .. } => "DocblockTypeContradiction",
1212 IssueKind::ImpossibleIdenticalComparison { .. } => "ImpossibleIdenticalComparison",
1213 IssueKind::ImpossibleLooseComparison { .. } => "ImpossibleLooseComparison",
1214 IssueKind::UnevaluatedCode { .. } => "UnevaluatedCode",
1215 IssueKind::IfThisIsMismatch { .. } => "IfThisIsMismatch",
1216 IssueKind::Trace { .. } => "Trace",
1217 IssueKind::InvalidArrayOffset { .. } => "InvalidArrayOffset",
1218 IssueKind::NonExistentArrayOffset { .. } => "NonExistentArrayOffset",
1219 IssueKind::PossiblyInvalidArrayOffset { .. } => "PossiblyInvalidArrayOffset",
1220 IssueKind::DuplicateArrayKey { .. } => "DuplicateArrayKey",
1221 IssueKind::RedundantCondition { .. } => "RedundantCondition",
1222 IssueKind::RedundantCast { .. } => "RedundantCast",
1223 IssueKind::UnnecessaryVarAnnotation { .. } => "UnnecessaryVarAnnotation",
1224 IssueKind::TypeDoesNotContainType { .. } => "TypeDoesNotContainType",
1225 IssueKind::ParadoxicalCondition { .. } => "ParadoxicalCondition",
1226 IssueKind::UnhandledMatchCondition { .. } => "UnhandledMatchCondition",
1227 IssueKind::UnusedVariable { .. } => "UnusedVariable",
1228 IssueKind::UnusedParam { .. } => "UnusedParam",
1229 IssueKind::UnreachableCode => "UnreachableCode",
1230 IssueKind::UnusedMethod { .. } => "UnusedMethod",
1231 IssueKind::UnusedProperty { .. } => "UnusedProperty",
1232 IssueKind::UnusedFunction { .. } => "UnusedFunction",
1233 IssueKind::UnusedForeachValue { .. } => "UnusedForeachValue",
1234 IssueKind::UnusedClass { .. } => "UnusedClass",
1235 IssueKind::UnusedSuppress { .. } => "UnusedSuppress",
1236 IssueKind::ArgumentTypeCoercion { .. } => "ArgumentTypeCoercion",
1237 IssueKind::PropertyTypeCoercion { .. } => "PropertyTypeCoercion",
1238 IssueKind::ImpurePropertyAssignment { .. } => "ImpurePropertyAssignment",
1239 IssueKind::ImpureMethodCall { .. } => "ImpureMethodCall",
1240 IssueKind::ImpureGlobalVariable { .. } => "ImpureGlobalVariable",
1241 IssueKind::ImpureStaticVariable { .. } => "ImpureStaticVariable",
1242 IssueKind::ImpureStaticPropertyAssignment { .. } => "ImpureStaticPropertyAssignment",
1243 IssueKind::ImpureFunctionCall { .. } => "ImpureFunctionCall",
1244 IssueKind::ImmutablePropertyModification { .. } => "ImmutablePropertyModification",
1245 IssueKind::UnsupportedReferenceUsage => "UnsupportedReferenceUsage",
1246 IssueKind::NoInterfaceProperties { .. } => "NoInterfaceProperties",
1247 IssueKind::UndefinedDocblockClass { .. } => "UndefinedDocblockClass",
1248 IssueKind::MissingConstructor { .. } => "MissingConstructor",
1249 IssueKind::MixedFunctionCall => "MixedFunctionCall",
1250 IssueKind::MixedReturnStatement { .. } => "MixedReturnStatement",
1251 IssueKind::UnimplementedAbstractMethod { .. } => "UnimplementedAbstractMethod",
1252 IssueKind::UnimplementedInterfaceMethod { .. } => "UnimplementedInterfaceMethod",
1253 IssueKind::MethodSignatureMismatch { .. } => "MethodSignatureMismatch",
1254 IssueKind::OverriddenMethodAccess { .. } => "OverriddenMethodAccess",
1255 IssueKind::OverriddenPropertyAccess { .. } => "OverriddenPropertyAccess",
1256 IssueKind::PropertyTypeRedeclarationMismatch { .. } => {
1257 "PropertyTypeRedeclarationMismatch"
1258 }
1259 IssueKind::ReadonlyPropertyRedeclarationMismatch { .. } => {
1260 "ReadonlyPropertyRedeclarationMismatch"
1261 }
1262 IssueKind::StaticPropertyRedeclarationMismatch { .. } => {
1263 "StaticPropertyRedeclarationMismatch"
1264 }
1265 IssueKind::BackedEnumCaseTypeMismatch { .. } => "BackedEnumCaseTypeMismatch",
1266 IssueKind::InvalidExtendClass { .. } => "InvalidExtendClass",
1267 IssueKind::FinalMethodOverridden { .. } => "FinalMethodOverridden",
1268 IssueKind::AbstractInstantiation { .. } => "AbstractInstantiation",
1269 IssueKind::AbstractMethodCall { .. } => "AbstractMethodCall",
1270 IssueKind::InterfaceInstantiation { .. } => "InterfaceInstantiation",
1271 IssueKind::InvalidOverride { .. } => "InvalidOverride",
1272 IssueKind::ReadonlyPropertyAssignment { .. } => "ReadonlyPropertyAssignment",
1273 IssueKind::InvalidTemplateParam { .. } => "InvalidTemplateParam",
1274 IssueKind::ShadowedTemplateParam { .. } => "ShadowedTemplateParam",
1275 IssueKind::TaintedInput { .. } => "TaintedInput",
1276 IssueKind::TaintedHtml => "TaintedHtml",
1277 IssueKind::TaintedSql => "TaintedSql",
1278 IssueKind::TaintedShell => "TaintedShell",
1279 IssueKind::TaintedLlmPrompt => "TaintedLlmPrompt",
1280 IssueKind::DeprecatedCall { .. } => "DeprecatedCall",
1281 IssueKind::DeprecatedProperty { .. } => "DeprecatedProperty",
1282 IssueKind::DeprecatedConstant { .. } => "DeprecatedConstant",
1283 IssueKind::DeprecatedInterface { .. } => "DeprecatedInterface",
1284 IssueKind::DeprecatedTrait { .. } => "DeprecatedTrait",
1285 IssueKind::DeprecatedMethodCall { .. } => "DeprecatedMethodCall",
1286 IssueKind::DeprecatedMethod { .. } => "DeprecatedMethod",
1287 IssueKind::DeprecatedClass { .. } => "DeprecatedClass",
1288 IssueKind::InternalMethod { .. } => "InternalMethod",
1289 IssueKind::MissingReturnType { .. } => "MissingReturnType",
1290 IssueKind::MissingClosureReturnType => "MissingClosureReturnType",
1291 IssueKind::MissingParamType { .. } => "MissingParamType",
1292 IssueKind::MissingPropertyType { .. } => "MissingPropertyType",
1293 IssueKind::InvalidThrow { .. } => "InvalidThrow",
1294 IssueKind::InvalidCatch { .. } => "InvalidCatch",
1295 IssueKind::UnreachableCatch { .. } => "UnreachableCatch",
1296 IssueKind::MissingThrowsDocblock { .. } => "MissingThrowsDocblock",
1297 IssueKind::ImplicitToStringCast { .. } => "ImplicitToStringCast",
1298 IssueKind::ImplicitFloatToIntCast { .. } => "ImplicitFloatToIntCast",
1299 IssueKind::ParseError { .. } => "ParseError",
1300 IssueKind::InvalidDocblock { .. } => "InvalidDocblock",
1301 IssueKind::MixedArgument { .. } => "MixedArgument",
1302 IssueKind::MixedAssignment { .. } => "MixedAssignment",
1303 IssueKind::MixedMethodCall { .. } => "MixedMethodCall",
1304 IssueKind::MixedPropertyFetch { .. } => "MixedPropertyFetch",
1305 IssueKind::MixedPropertyAssignment { .. } => "MixedPropertyAssignment",
1306 IssueKind::MixedArrayAccess => "MixedArrayAccess",
1307 IssueKind::MixedArrayOffset => "MixedArrayOffset",
1308 IssueKind::MixedClone => "MixedClone",
1309 IssueKind::InvalidClone { .. } => "InvalidClone",
1310 IssueKind::PossiblyInvalidClone { .. } => "PossiblyInvalidClone",
1311 IssueKind::InvalidToString { .. } => "InvalidToString",
1312 IssueKind::CircularInheritance { .. } => "CircularInheritance",
1313 IssueKind::InvalidTraitUse { .. } => "InvalidTraitUse",
1314 IssueKind::ForbiddenCode { .. } => "ForbiddenCode",
1315 IssueKind::WrongCaseFunction { .. } => "WrongCaseFunction",
1316 IssueKind::WrongCaseMethod { .. } => "WrongCaseMethod",
1317 IssueKind::WrongCaseClass { .. } => "WrongCaseClass",
1318 IssueKind::InvalidAttribute { .. } => "InvalidAttribute",
1319 IssueKind::UndefinedAttributeClass { .. } => "UndefinedAttributeClass",
1320 IssueKind::DuplicateClass { .. } => "DuplicateClass",
1321 IssueKind::DuplicateInterface { .. } => "DuplicateInterface",
1322 IssueKind::DuplicateTrait { .. } => "DuplicateTrait",
1323 IssueKind::DuplicateEnum { .. } => "DuplicateEnum",
1324 IssueKind::DuplicateFunction { .. } => "DuplicateFunction",
1325 }
1326 }
1327
1328 pub fn message(&self) -> String {
1330 match self {
1331 IssueKind::NonStaticSelfCall { class, method } => {
1332 format!("Non-static method {class}::{method}() cannot be called statically")
1333 }
1334 IssueKind::DirectConstructorCall { class } => {
1335 format!("Cannot call constructor of {class} directly")
1336 }
1337 IssueKind::InvalidScope { in_class } => {
1338 if *in_class {
1339 "$this cannot be used in a static method".to_string()
1340 } else {
1341 "$this cannot be used outside of a class".to_string()
1342 }
1343 }
1344 IssueKind::UndefinedVariable { name } => format!("Variable ${name} is not defined"),
1345 IssueKind::UndefinedFunction { name } => format!("Function {name}() is not defined"),
1346 IssueKind::UndefinedMethod { class, method } => {
1347 format!("Method {class}::{method}() does not exist")
1348 }
1349 IssueKind::UndefinedClass { name } => format!("Class {name} does not exist"),
1350 IssueKind::UndefinedProperty { class, property } => {
1351 format!("Property {class}::${property} does not exist")
1352 }
1353 IssueKind::UndefinedConstant { name } => format!("Constant {name} is not defined"),
1354 IssueKind::InaccessibleClassConstant { class, constant } => {
1355 format!("Cannot access constant {class}::{constant}")
1356 }
1357 IssueKind::PossiblyUndefinedVariable { name } => {
1358 format!("Variable ${name} might not be defined")
1359 }
1360 IssueKind::UndefinedTrait { name } => format!("Trait {name} does not exist"),
1361 IssueKind::ParentNotFound => {
1362 "Cannot use parent:: when current class has no parent".to_string()
1363 }
1364 IssueKind::InvalidStringClass { actual } => {
1365 format!("Dynamic class instantiation requires string or class-string type, got '{actual}'")
1366 }
1367 IssueKind::NotAnInterface { name } => {
1368 format!("{name} is not an interface")
1369 }
1370
1371 IssueKind::NullArgument { param, fn_name } => {
1372 format!("Argument ${param} of {fn_name}() cannot be null")
1373 }
1374 IssueKind::NullPropertyFetch { property } => {
1375 format!("Cannot access property ${property} on null")
1376 }
1377 IssueKind::NullMethodCall { method } => {
1378 format!("Cannot call method {method}() on null")
1379 }
1380 IssueKind::NullArrayAccess => "Cannot access array on null".to_string(),
1381 IssueKind::PossiblyNullArgument { param, fn_name } => {
1382 format!("Argument ${param} of {fn_name}() might be null")
1383 }
1384 IssueKind::PossiblyInvalidArgument {
1385 param,
1386 fn_name,
1387 expected,
1388 actual,
1389 } => {
1390 format!("Argument ${param} of {fn_name}() expects '{expected}', possibly different type '{actual}' provided")
1391 }
1392 IssueKind::PossiblyNullPropertyFetch { property } => {
1393 format!("Cannot access property ${property} on possibly null value")
1394 }
1395 IssueKind::PossiblyNullMethodCall { method } => {
1396 format!("Cannot call method {method}() on possibly null value")
1397 }
1398 IssueKind::PossiblyNullArrayAccess => {
1399 "Cannot access array on possibly null value".to_string()
1400 }
1401 IssueKind::NullableReturnStatement { expected, actual } => {
1402 format!("Return type '{actual}' is not compatible with declared '{expected}'")
1403 }
1404
1405 IssueKind::InvalidReturnType { expected, actual } => {
1406 format!("Return type '{actual}' is not compatible with declared '{expected}'")
1407 }
1408 IssueKind::InvalidArgument {
1409 param,
1410 fn_name,
1411 expected,
1412 actual,
1413 } => {
1414 format!("Argument ${param} of {fn_name}() expects '{expected}', got '{actual}'")
1415 }
1416 IssueKind::TooFewArguments {
1417 fn_name,
1418 expected,
1419 actual,
1420 } => {
1421 format!(
1422 "Too few arguments for {}(): expected {}, got {}",
1423 fn_name, expected, actual
1424 )
1425 }
1426 IssueKind::TooManyArguments {
1427 fn_name,
1428 expected,
1429 actual,
1430 } => {
1431 format!(
1432 "Too many arguments for {}(): expected {}, got {}",
1433 fn_name, expected, actual
1434 )
1435 }
1436 IssueKind::InvalidNamedArgument { fn_name, name } => {
1437 format!("{}() has no parameter named ${}", fn_name, name)
1438 }
1439 IssueKind::InvalidNamedArguments { fn_name } => {
1440 format!("{}() does not accept named arguments", fn_name)
1441 }
1442 IssueKind::InvalidPassByReference { fn_name, param } => {
1443 format!(
1444 "Argument ${} of {}() must be passed by reference",
1445 param, fn_name
1446 )
1447 }
1448 IssueKind::InvalidPropertyFetch { ty } => {
1449 format!("Cannot fetch property on non-object type '{ty}'")
1450 }
1451 IssueKind::InvalidArrayAccess { ty } => {
1452 format!("Cannot use [] operator on non-array type '{ty}'")
1453 }
1454 IssueKind::PossiblyInvalidArrayAccess { ty } => {
1455 format!("Possibly invalid array access: '{ty}' might not support []")
1456 }
1457 IssueKind::InvalidArrayAssignment { ty } => {
1458 format!("Cannot use [] assignment on non-array type '{ty}'")
1459 }
1460 IssueKind::InvalidPropertyAssignment {
1461 property,
1462 expected,
1463 actual,
1464 } => {
1465 format!("Property ${property} expects '{expected}', cannot assign '{actual}'")
1466 }
1467 IssueKind::InvalidCast { from, to } => {
1468 format!("Cannot cast '{from}' to '{to}'")
1469 }
1470 IssueKind::InvalidStaticInvocation { class, method } => {
1471 format!("Non-static method {class}::{method}() cannot be called statically")
1472 }
1473 IssueKind::InvalidOperand { op, left, right } => {
1474 format!("Operator '{op}' not supported between '{left}' and '{right}'")
1475 }
1476 IssueKind::PossiblyInvalidOperand { op, left, right } => {
1477 format!("Operator '{op}' might not be supported between '{left}' and '{right}'")
1478 }
1479 IssueKind::PossiblyNullOperand { op, ty } => {
1480 format!("Operator '{op}' operand '{ty}' might be null")
1481 }
1482 IssueKind::DivisionByZero { op } => {
1483 format!("Division by zero: right operand of '{op}' is always 0")
1484 }
1485 IssueKind::RawObjectIteration { ty } => {
1486 format!("Cannot iterate over non-iterable object '{ty}'")
1487 }
1488 IssueKind::PossiblyRawObjectIteration { ty } => {
1489 format!("Cannot iterate over possibly non-iterable object '{ty}'")
1490 }
1491 IssueKind::MismatchingDocblockReturnType { declared, inferred } => {
1492 format!("Docblock return type '{declared}' does not match inferred '{inferred}'")
1493 }
1494 IssueKind::MismatchingDocblockParamType {
1495 param,
1496 declared,
1497 inferred,
1498 } => {
1499 format!(
1500 "Docblock type '{declared}' for ${param} does not match inferred '{inferred}'"
1501 )
1502 }
1503 IssueKind::TypeCheckMismatch {
1504 var,
1505 expected,
1506 actual,
1507 } => {
1508 format!("Type of ${var} is expected to be {expected}, got {actual}")
1509 }
1510 IssueKind::Trace {
1511 variable,
1512 type_info,
1513 } => {
1514 format!("Type of ${variable} is {type_info}")
1515 }
1516
1517 IssueKind::InvalidArrayOffset { expected, actual } => {
1518 format!("Array offset expects '{expected}', got '{actual}'")
1519 }
1520 IssueKind::NonExistentArrayOffset { key } => {
1521 format!("Array offset '{key}' does not exist")
1522 }
1523 IssueKind::PossiblyInvalidArrayOffset { expected, actual } => {
1524 format!("Array offset might be invalid: expects '{expected}', got '{actual}'")
1525 }
1526 IssueKind::DuplicateArrayKey { key } => {
1527 format!("Array key {key} is duplicated — the earlier entry is silently overwritten")
1528 }
1529
1530 IssueKind::RedundantCondition { ty } => {
1531 format!("Condition is always true/false for type '{ty}'")
1532 }
1533 IssueKind::RedundantCast { from, to } => {
1534 format!("Casting '{from}' to '{to}' is redundant")
1535 }
1536 IssueKind::UnnecessaryVarAnnotation { var } => {
1537 format!("@var annotation for ${var} is unnecessary")
1538 }
1539 IssueKind::TypeDoesNotContainType { left, right } => {
1540 format!("Type '{left}' can never contain type '{right}'")
1541 }
1542 IssueKind::ParadoxicalCondition { value } => {
1543 format!("Value {value} is duplicated; this branch can never be reached")
1544 }
1545 IssueKind::UnhandledMatchCondition { detail } => {
1546 format!("Unhandled match condition: {detail}")
1547 }
1548 IssueKind::DocblockTypeContradiction { expr, declared } => {
1549 format!("Type '{declared}' makes '{expr}' impossible — this can never hold")
1550 }
1551 IssueKind::ImpossibleIdenticalComparison { op, left, right } => {
1552 let result = if op == "===" { "false" } else { "true" };
1553 format!("'{op}' between '{left}' and '{right}' is always {result} — these types can never be identical")
1554 }
1555 IssueKind::ImpossibleLooseComparison { op, left, right } => {
1556 let result = if op == "==" { "false" } else { "true" };
1557 format!("'{op}' between '{left}' and '{right}' is always {result} — these types can never be loosely equal")
1558 }
1559 IssueKind::UnevaluatedCode { reason } => {
1560 format!("Unevaluated code: {reason}")
1561 }
1562 IssueKind::IfThisIsMismatch {
1563 class,
1564 method,
1565 expected,
1566 actual,
1567 } => {
1568 format!(
1569 "Cannot call {class}::{method}() — @if-this-is requires $this to be '{expected}', but it is '{actual}'"
1570 )
1571 }
1572
1573 IssueKind::UnusedVariable { name } => format!("Variable ${name} is never read"),
1574 IssueKind::UnusedParam { name } => format!("Parameter ${name} is never used"),
1575 IssueKind::UnreachableCode => "Unreachable code detected".to_string(),
1576 IssueKind::UnusedMethod { class, method } => {
1577 format!("Private method {class}::{method}() is never called")
1578 }
1579 IssueKind::UnusedProperty { class, property } => {
1580 format!("Private property {class}::${property} is never read")
1581 }
1582 IssueKind::UnusedFunction { name } => {
1583 format!("Function {name}() is never called")
1584 }
1585 IssueKind::UnusedForeachValue { name } => {
1586 format!("Foreach value ${name} is never read")
1587 }
1588 IssueKind::UnusedClass { class } => {
1589 format!("Class {class} is never referenced")
1590 }
1591 IssueKind::UnusedSuppress { kind } => {
1592 format!("Suppress annotation for '{kind}' is never used")
1593 }
1594 IssueKind::ArgumentTypeCoercion {
1595 param,
1596 fn_name,
1597 expected,
1598 actual,
1599 } => {
1600 format!("Argument ${param} of {fn_name}() expects '{expected}', got '{actual}' — coercion may fail at runtime")
1601 }
1602 IssueKind::PropertyTypeCoercion {
1603 property,
1604 expected,
1605 actual,
1606 } => {
1607 format!("Property ${property} expects '{expected}', cannot assign '{actual}' — coercion may fail at runtime")
1608 }
1609 IssueKind::ImpurePropertyAssignment { property } => {
1610 format!("Assigning to property {property} of a parameter in a pure or external-mutation-free context")
1611 }
1612 IssueKind::ImpureMethodCall { method } => {
1613 format!("Calling impure method {method}() in a pure or immutable context")
1614 }
1615 IssueKind::ImpureGlobalVariable { variable } => {
1616 format!("Using global variable ${variable} in a @pure function")
1617 }
1618 IssueKind::ImpureStaticVariable { variable } => {
1619 format!("Using static variable ${variable} in a @pure function")
1620 }
1621 IssueKind::ImpureStaticPropertyAssignment { class, property } => {
1622 format!("Assigning to static property {class}::${property} in a @pure function")
1623 }
1624 IssueKind::ImpureFunctionCall { fn_name } => {
1625 format!("Calling impure function {fn_name}() in a @pure function")
1626 }
1627 IssueKind::ImmutablePropertyModification { property } => {
1628 format!("Assigning to property {property} of $this in an immutable context (@psalm-immutable class or @psalm-mutation-free method)")
1629 }
1630
1631 IssueKind::UnimplementedAbstractMethod { class, method } => {
1632 format!("Class {class} must implement abstract method {method}()")
1633 }
1634 IssueKind::UnimplementedInterfaceMethod {
1635 class,
1636 interface,
1637 method,
1638 } => {
1639 format!("Class {class} must implement {interface}::{method}() from interface")
1640 }
1641 IssueKind::MethodSignatureMismatch {
1642 class,
1643 method,
1644 detail,
1645 } => {
1646 format!("Method {class}::{method}() signature mismatch: {detail}")
1647 }
1648 IssueKind::OverriddenMethodAccess { class, method } => {
1649 format!("Method {class}::{method}() overrides with less visibility")
1650 }
1651 IssueKind::OverriddenPropertyAccess { class, property } => {
1652 format!("Property {class}::${property} overrides with less visibility")
1653 }
1654 IssueKind::PropertyTypeRedeclarationMismatch {
1655 class,
1656 property,
1657 expected,
1658 actual,
1659 } => {
1660 format!(
1661 "Type of {class}::${property} must be {expected} (as in parent class), {actual} given"
1662 )
1663 }
1664 IssueKind::ReadonlyPropertyRedeclarationMismatch {
1665 parent_class,
1666 class,
1667 property,
1668 parent_readonly,
1669 } => {
1670 if *parent_readonly {
1671 format!(
1672 "Cannot redeclare readonly property {parent_class}::${property} as non-readonly {class}::${property}"
1673 )
1674 } else {
1675 format!(
1676 "Cannot redeclare non-readonly property {parent_class}::${property} as readonly {class}::${property}"
1677 )
1678 }
1679 }
1680 IssueKind::StaticPropertyRedeclarationMismatch {
1681 parent_class,
1682 class,
1683 property,
1684 parent_static,
1685 } => {
1686 if *parent_static {
1687 format!(
1688 "Cannot redeclare static property {parent_class}::${property} as non-static {class}::${property}"
1689 )
1690 } else {
1691 format!(
1692 "Cannot redeclare non-static property {parent_class}::${property} as static {class}::${property}"
1693 )
1694 }
1695 }
1696 IssueKind::BackedEnumCaseTypeMismatch {
1697 enum_name,
1698 case_name,
1699 expected,
1700 actual,
1701 } => {
1702 format!(
1703 "Backed enum case {enum_name}::{case_name} has value of type {actual}, but backing type is {expected}"
1704 )
1705 }
1706 IssueKind::ReadonlyPropertyAssignment { class, property } => {
1707 format!(
1708 "Cannot assign to readonly property {class}::${property} outside of constructor"
1709 )
1710 }
1711 IssueKind::InvalidExtendClass { parent, child } => {
1712 format!("Class {child} cannot extend final class {parent}")
1713 }
1714 IssueKind::InvalidTemplateParam {
1715 name,
1716 expected_bound,
1717 actual,
1718 } => {
1719 format!(
1720 "Template type '{name}' inferred as '{actual}' does not satisfy bound '{expected_bound}'"
1721 )
1722 }
1723 IssueKind::ShadowedTemplateParam { name } => {
1724 format!(
1725 "Method template parameter '{name}' shadows class-level template parameter with the same name"
1726 )
1727 }
1728 IssueKind::FinalMethodOverridden {
1729 class,
1730 method,
1731 parent,
1732 } => {
1733 format!("Method {class}::{method}() cannot override final method from {parent}")
1734 }
1735 IssueKind::AbstractInstantiation { class } => {
1736 format!("Cannot instantiate abstract class {class}")
1737 }
1738 IssueKind::AbstractMethodCall { class, method } => {
1739 format!("Cannot call abstract method {class}::{method}()")
1740 }
1741 IssueKind::InterfaceInstantiation { class } => {
1742 format!("Cannot instantiate interface {class}")
1743 }
1744 IssueKind::InvalidOverride {
1745 class,
1746 method,
1747 detail,
1748 } => {
1749 format!("Method {class}::{method}() has #[Override] but {detail}")
1750 }
1751
1752 IssueKind::TaintedInput { sink } => format!("Tainted input reaching sink '{sink}'"),
1753 IssueKind::TaintedHtml => "Tainted HTML output — possible XSS".to_string(),
1754 IssueKind::TaintedSql => "Tainted SQL query — possible SQL injection".to_string(),
1755 IssueKind::TaintedShell => {
1756 "Tainted shell command — possible command injection".to_string()
1757 }
1758 IssueKind::TaintedLlmPrompt => {
1759 "Tainted LLM prompt — possible prompt injection".to_string()
1760 }
1761
1762 IssueKind::DeprecatedCall { name, message } => {
1763 let base = format!("Call to deprecated function {name}");
1764 append_deprecation_message(base, message)
1765 }
1766 IssueKind::DeprecatedProperty {
1767 class,
1768 property,
1769 message,
1770 } => {
1771 let base = format!("Property {class}::${property} is deprecated");
1772 append_deprecation_message(base, message)
1773 }
1774 IssueKind::DeprecatedConstant {
1775 class,
1776 constant,
1777 message,
1778 } => {
1779 let base = format!("Constant {class}::{constant} is deprecated");
1780 append_deprecation_message(base, message)
1781 }
1782 IssueKind::DeprecatedInterface { name, message } => {
1783 let base = format!("Interface {name} is deprecated");
1784 append_deprecation_message(base, message)
1785 }
1786 IssueKind::DeprecatedTrait { name, message } => {
1787 let base = format!("Trait {name} is deprecated");
1788 append_deprecation_message(base, message)
1789 }
1790 IssueKind::DeprecatedMethodCall {
1791 class,
1792 method,
1793 message,
1794 } => {
1795 let base = format!("Call to deprecated method {class}::{method}");
1796 append_deprecation_message(base, message)
1797 }
1798 IssueKind::DeprecatedMethod {
1799 class,
1800 method,
1801 message,
1802 } => {
1803 let base = format!("Method {class}::{method}() is deprecated");
1804 append_deprecation_message(base, message)
1805 }
1806 IssueKind::DeprecatedClass { name, message } => {
1807 let base = format!("Class {name} is deprecated");
1808 append_deprecation_message(base, message)
1809 }
1810 IssueKind::InternalMethod { class, method } => {
1811 format!("Method {class}::{method}() is marked @internal")
1812 }
1813 IssueKind::MissingReturnType { fn_name } => {
1814 format!("Function {fn_name}() has no return type annotation")
1815 }
1816 IssueKind::MissingClosureReturnType => {
1817 "Closure has no return type annotation".to_string()
1818 }
1819 IssueKind::MissingParamType { fn_name, param } => {
1820 format!("Parameter ${param} of {fn_name}() has no type annotation")
1821 }
1822 IssueKind::MissingPropertyType { class, property } => {
1823 format!("Property {class}::${property} has no type annotation")
1824 }
1825 IssueKind::InvalidThrow { ty } => {
1826 format!("Thrown type '{ty}' does not extend Throwable")
1827 }
1828 IssueKind::InvalidCatch { ty } => {
1829 format!("Caught type '{ty}' does not extend Throwable")
1830 }
1831 IssueKind::UnreachableCatch { ty, shadowed_by } => {
1832 format!("Catch block for '{ty}' is unreachable — already caught by '{shadowed_by}'")
1833 }
1834 IssueKind::MissingThrowsDocblock { class } => {
1835 format!("Exception {class} is thrown but not declared in @throws")
1836 }
1837 IssueKind::ImplicitToStringCast { class } => {
1838 format!("Class {class} is implicitly cast to string")
1839 }
1840 IssueKind::ImplicitFloatToIntCast { from } => {
1841 format!("Implicit cast from {from} to int truncates the fractional part")
1842 }
1843 IssueKind::ParseError { message } => format!("Parse error: {message}"),
1844 IssueKind::InvalidDocblock { message } => format!("Invalid docblock: {message}"),
1845 IssueKind::MixedArgument { param, fn_name } => {
1846 format!("Argument ${param} of {fn_name}() is mixed")
1847 }
1848 IssueKind::MixedAssignment { var } => {
1849 format!("Variable ${var} is assigned a mixed type")
1850 }
1851 IssueKind::MixedMethodCall { method } => {
1852 format!("Method {method}() called on mixed type")
1853 }
1854 IssueKind::UnsupportedReferenceUsage => {
1855 "Reference assignment is not supported".to_string()
1856 }
1857 IssueKind::NoInterfaceProperties { property } => {
1858 format!("Property ${property} is not defined on sealed interface")
1859 }
1860 IssueKind::UndefinedDocblockClass { name } => {
1861 format!("Docblock type '{name}' does not exist")
1862 }
1863 IssueKind::MissingConstructor { class } => {
1864 format!("Class {class} has uninitialized properties but no constructor")
1865 }
1866 IssueKind::MixedFunctionCall => "Cannot call mixed type as a function".to_string(),
1867 IssueKind::MixedReturnStatement { declared } => {
1868 format!("Cannot return a mixed type from function with declared return type '{declared}'")
1869 }
1870 IssueKind::MixedPropertyFetch { property } => {
1871 format!("Property ${property} fetched on mixed type")
1872 }
1873 IssueKind::MixedPropertyAssignment { property } => {
1874 format!("Property ${property} assigned on mixed type")
1875 }
1876 IssueKind::MixedArrayAccess => "Array access on mixed type".to_string(),
1877 IssueKind::MixedArrayOffset => "Mixed type used as array offset".to_string(),
1878 IssueKind::MixedClone => "cannot clone mixed".to_string(),
1879 IssueKind::InvalidClone { ty } => format!("cannot clone non-object {ty}"),
1880 IssueKind::PossiblyInvalidClone { ty } => {
1881 format!("cannot clone possibly non-object {ty}")
1882 }
1883 IssueKind::InvalidToString { class } => {
1884 format!("Method {class}::__toString() must return a string")
1885 }
1886 IssueKind::CircularInheritance { class } => {
1887 format!("Class {class} has a circular inheritance chain")
1888 }
1889 IssueKind::InvalidTraitUse { trait_name, reason } => {
1890 format!("Trait {trait_name} used incorrectly: {reason}")
1891 }
1892 IssueKind::WrongCaseFunction { used, canonical } => {
1893 format!("Function name '{used}' has incorrect casing; use '{canonical}'")
1894 }
1895 IssueKind::WrongCaseMethod {
1896 class,
1897 used,
1898 canonical,
1899 } => {
1900 format!("Method name '{class}::{used}' has incorrect casing; use '{canonical}'")
1901 }
1902 IssueKind::WrongCaseClass { used, canonical } => {
1903 format!("Class name '{used}' has incorrect casing; use '{canonical}'")
1904 }
1905 IssueKind::InvalidAttribute { message } => message.clone(),
1906 IssueKind::UndefinedAttributeClass { name } => {
1907 format!("Attribute class {name} does not exist")
1908 }
1909 IssueKind::ForbiddenCode { message } => message.clone(),
1910 IssueKind::DuplicateClass { name } => {
1911 format!("Class {name} has already been defined")
1912 }
1913 IssueKind::DuplicateInterface { name } => {
1914 format!("Interface {name} has already been defined")
1915 }
1916 IssueKind::DuplicateTrait { name } => {
1917 format!("Trait {name} has already been defined")
1918 }
1919 IssueKind::DuplicateEnum { name } => {
1920 format!("Enum {name} has already been defined")
1921 }
1922 IssueKind::DuplicateFunction { name } => {
1923 format!("Function {name}() has already been defined")
1924 }
1925 }
1926 }
1927}
1928
1929#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1934pub struct Issue {
1935 pub kind: IssueKind,
1936 pub severity: Severity,
1937 pub location: Location,
1938 pub snippet: Option<String>,
1939 pub suppressed: bool,
1940}
1941
1942impl Issue {
1943 pub fn new(kind: IssueKind, location: Location) -> Self {
1944 let severity = kind.default_severity();
1945 Self {
1946 severity,
1947 kind,
1948 location,
1949 snippet: None,
1950 suppressed: false,
1951 }
1952 }
1953
1954 pub fn with_snippet(mut self, snippet: impl Into<String>) -> Self {
1955 self.snippet = Some(snippet.into());
1956 self
1957 }
1958
1959 pub fn suppress(mut self) -> Self {
1960 self.suppressed = true;
1961 self
1962 }
1963}
1964
1965impl fmt::Display for Issue {
1966 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1967 let sev = match self.severity {
1968 Severity::Error => "error".red().to_string(),
1969 Severity::Warning => "warning".yellow().to_string(),
1970 Severity::Info => "info".blue().to_string(),
1971 };
1972 write!(
1973 f,
1974 "{} {}[{}] {}: {}",
1975 self.location.bright_black(),
1976 sev,
1977 self.kind.code().bright_black(),
1978 self.kind.name().bold(),
1979 self.kind.message()
1980 )
1981 }
1982}
1983
1984#[derive(Debug, Default)]
1989pub struct IssueBuffer {
1990 issues: Vec<Issue>,
1991 seen: HashSet<(&'static str, Arc<str>, u32, u16)>,
1992 file_suppressions: Vec<String>,
1994}
1995
1996impl IssueBuffer {
1997 pub fn new() -> Self {
1998 Self::default()
1999 }
2000
2001 pub fn add(&mut self, issue: Issue) {
2002 let key = (
2003 issue.kind.name(),
2004 issue.location.file.clone(),
2005 issue.location.line,
2006 issue.location.col_start,
2007 );
2008 if self.seen.insert(key) {
2009 self.issues.push(issue);
2010 }
2011 }
2012
2013 pub fn add_suppression(&mut self, name: impl Into<String>) {
2014 self.file_suppressions.push(name.into());
2015 }
2016
2017 pub fn into_issues(self) -> Vec<Issue> {
2019 self.issues
2020 .into_iter()
2021 .filter(|i| !i.suppressed)
2022 .filter(|i| !self.file_suppressions.contains(&i.kind.name().to_string()))
2023 .collect()
2024 }
2025
2026 pub fn into_all_issues(self) -> Vec<Issue> {
2031 self.issues
2032 .into_iter()
2033 .map(|mut i| {
2034 if self.file_suppressions.contains(&i.kind.name().to_string()) {
2035 i.suppressed = true;
2036 }
2037 i
2038 })
2039 .collect()
2040 }
2041
2042 pub fn suppress_range(&mut self, from: usize, suppressions: &[String]) {
2045 if suppressions.is_empty() {
2046 return;
2047 }
2048 for issue in self.issues[from..].iter_mut() {
2049 if suppressions.iter().any(|s| s == issue.kind.name()) {
2050 issue.suppressed = true;
2051 }
2052 }
2053 }
2054
2055 pub fn issue_count(&self) -> usize {
2058 self.issues.len()
2059 }
2060
2061 pub fn truncate_to(&mut self, mark: usize) {
2067 for issue in self.issues.drain(mark..) {
2068 let key = (
2069 issue.kind.name(),
2070 issue.location.file.clone(),
2071 issue.location.line,
2072 issue.location.col_start,
2073 );
2074 self.seen.remove(&key);
2075 }
2076 }
2077
2078 pub fn is_empty(&self) -> bool {
2079 self.issues.is_empty()
2080 }
2081
2082 pub fn len(&self) -> usize {
2083 self.issues.len()
2084 }
2085
2086 pub fn error_count(&self) -> usize {
2087 self.issues
2088 .iter()
2089 .filter(|i| !i.suppressed && i.severity == Severity::Error)
2090 .count()
2091 }
2092
2093 pub fn warning_count(&self) -> usize {
2094 self.issues
2095 .iter()
2096 .filter(|i| !i.suppressed && i.severity == Severity::Warning)
2097 .count()
2098 }
2099}
2100
2101#[cfg(test)]
2102mod code_tests {
2103 use super::*;
2104 use std::collections::HashSet;
2105
2106 fn one_of_each() -> Vec<IssueKind> {
2112 let s = || String::new();
2113 vec![
2114 IssueKind::InvalidScope { in_class: false },
2115 IssueKind::NonStaticSelfCall {
2116 class: s(),
2117 method: s(),
2118 },
2119 IssueKind::DirectConstructorCall { class: s() },
2120 IssueKind::UndefinedVariable { name: s() },
2121 IssueKind::UndefinedFunction { name: s() },
2122 IssueKind::UndefinedMethod {
2123 class: s(),
2124 method: s(),
2125 },
2126 IssueKind::UndefinedClass { name: s() },
2127 IssueKind::NotAnInterface { name: s() },
2128 IssueKind::UndefinedProperty {
2129 class: s(),
2130 property: s(),
2131 },
2132 IssueKind::UndefinedConstant { name: s() },
2133 IssueKind::InaccessibleClassConstant {
2134 class: s(),
2135 constant: s(),
2136 },
2137 IssueKind::PossiblyUndefinedVariable { name: s() },
2138 IssueKind::UndefinedTrait { name: s() },
2139 IssueKind::ParentNotFound,
2140 IssueKind::NullArgument {
2141 param: s(),
2142 fn_name: s(),
2143 },
2144 IssueKind::NullPropertyFetch { property: s() },
2145 IssueKind::NullMethodCall { method: s() },
2146 IssueKind::NullArrayAccess,
2147 IssueKind::PossiblyNullArgument {
2148 param: s(),
2149 fn_name: s(),
2150 },
2151 IssueKind::PossiblyInvalidArgument {
2152 param: s(),
2153 fn_name: s(),
2154 expected: s(),
2155 actual: s(),
2156 },
2157 IssueKind::PossiblyNullPropertyFetch { property: s() },
2158 IssueKind::PossiblyNullMethodCall { method: s() },
2159 IssueKind::PossiblyNullArrayAccess,
2160 IssueKind::NullableReturnStatement {
2161 expected: s(),
2162 actual: s(),
2163 },
2164 IssueKind::InvalidReturnType {
2165 expected: s(),
2166 actual: s(),
2167 },
2168 IssueKind::InvalidArgument {
2169 param: s(),
2170 fn_name: s(),
2171 expected: s(),
2172 actual: s(),
2173 },
2174 IssueKind::TooFewArguments {
2175 fn_name: s(),
2176 expected: 0,
2177 actual: 0,
2178 },
2179 IssueKind::TooManyArguments {
2180 fn_name: s(),
2181 expected: 0,
2182 actual: 0,
2183 },
2184 IssueKind::InvalidNamedArgument {
2185 fn_name: s(),
2186 name: s(),
2187 },
2188 IssueKind::InvalidNamedArguments { fn_name: s() },
2189 IssueKind::InvalidPassByReference {
2190 fn_name: s(),
2191 param: s(),
2192 },
2193 IssueKind::InvalidPropertyFetch { ty: s() },
2194 IssueKind::InvalidArrayAccess { ty: s() },
2195 IssueKind::PossiblyInvalidArrayAccess { ty: s() },
2196 IssueKind::InvalidArrayAssignment { ty: s() },
2197 IssueKind::InvalidPropertyAssignment {
2198 property: s(),
2199 expected: s(),
2200 actual: s(),
2201 },
2202 IssueKind::InvalidCast { from: s(), to: s() },
2203 IssueKind::InvalidStaticInvocation {
2204 class: s(),
2205 method: s(),
2206 },
2207 IssueKind::InvalidOperand {
2208 op: s(),
2209 left: s(),
2210 right: s(),
2211 },
2212 IssueKind::PossiblyInvalidOperand {
2213 op: s(),
2214 left: s(),
2215 right: s(),
2216 },
2217 IssueKind::PossiblyNullOperand { op: s(), ty: s() },
2218 IssueKind::DivisionByZero { op: s() },
2219 IssueKind::RawObjectIteration { ty: s() },
2220 IssueKind::PossiblyRawObjectIteration { ty: s() },
2221 IssueKind::MismatchingDocblockReturnType {
2222 declared: s(),
2223 inferred: s(),
2224 },
2225 IssueKind::MismatchingDocblockParamType {
2226 param: s(),
2227 declared: s(),
2228 inferred: s(),
2229 },
2230 IssueKind::TypeCheckMismatch {
2231 var: s(),
2232 expected: s(),
2233 actual: s(),
2234 },
2235 IssueKind::Trace {
2236 variable: s(),
2237 type_info: s(),
2238 },
2239 IssueKind::InvalidArrayOffset {
2240 expected: s(),
2241 actual: s(),
2242 },
2243 IssueKind::NonExistentArrayOffset { key: s() },
2244 IssueKind::PossiblyInvalidArrayOffset {
2245 expected: s(),
2246 actual: s(),
2247 },
2248 IssueKind::DuplicateArrayKey { key: s() },
2249 IssueKind::RedundantCondition { ty: s() },
2250 IssueKind::RedundantCast { from: s(), to: s() },
2251 IssueKind::UnnecessaryVarAnnotation { var: s() },
2252 IssueKind::TypeDoesNotContainType {
2253 left: s(),
2254 right: s(),
2255 },
2256 IssueKind::UnusedVariable { name: s() },
2257 IssueKind::UnusedParam { name: s() },
2258 IssueKind::UnreachableCode,
2259 IssueKind::UnhandledMatchCondition { detail: s() },
2260 IssueKind::UnusedMethod {
2261 class: s(),
2262 method: s(),
2263 },
2264 IssueKind::UnusedProperty {
2265 class: s(),
2266 property: s(),
2267 },
2268 IssueKind::UnusedFunction { name: s() },
2269 IssueKind::UnusedForeachValue { name: s() },
2270 IssueKind::UnusedClass { class: s() },
2271 IssueKind::UnusedSuppress { kind: s() },
2272 IssueKind::ArgumentTypeCoercion {
2273 param: s(),
2274 fn_name: s(),
2275 expected: s(),
2276 actual: s(),
2277 },
2278 IssueKind::PropertyTypeCoercion {
2279 property: s(),
2280 expected: s(),
2281 actual: s(),
2282 },
2283 IssueKind::ImpurePropertyAssignment { property: s() },
2284 IssueKind::ImpureMethodCall { method: s() },
2285 IssueKind::ImpureGlobalVariable { variable: s() },
2286 IssueKind::ImpureStaticVariable { variable: s() },
2287 IssueKind::ImpureStaticPropertyAssignment {
2288 class: s(),
2289 property: s(),
2290 },
2291 IssueKind::ImpureFunctionCall { fn_name: s() },
2292 IssueKind::ImmutablePropertyModification { property: s() },
2293 IssueKind::ReadonlyPropertyAssignment {
2294 class: s(),
2295 property: s(),
2296 },
2297 IssueKind::UnimplementedAbstractMethod {
2298 class: s(),
2299 method: s(),
2300 },
2301 IssueKind::UnimplementedInterfaceMethod {
2302 class: s(),
2303 interface: s(),
2304 method: s(),
2305 },
2306 IssueKind::MethodSignatureMismatch {
2307 class: s(),
2308 method: s(),
2309 detail: s(),
2310 },
2311 IssueKind::OverriddenMethodAccess {
2312 class: s(),
2313 method: s(),
2314 },
2315 IssueKind::OverriddenPropertyAccess {
2316 class: s(),
2317 property: s(),
2318 },
2319 IssueKind::PropertyTypeRedeclarationMismatch {
2320 class: s(),
2321 property: s(),
2322 expected: s(),
2323 actual: s(),
2324 },
2325 IssueKind::ReadonlyPropertyRedeclarationMismatch {
2326 parent_class: s(),
2327 class: s(),
2328 property: s(),
2329 parent_readonly: true,
2330 },
2331 IssueKind::StaticPropertyRedeclarationMismatch {
2332 parent_class: s(),
2333 class: s(),
2334 property: s(),
2335 parent_static: true,
2336 },
2337 IssueKind::BackedEnumCaseTypeMismatch {
2338 enum_name: s(),
2339 case_name: s(),
2340 expected: s(),
2341 actual: s(),
2342 },
2343 IssueKind::InvalidExtendClass {
2344 parent: s(),
2345 child: s(),
2346 },
2347 IssueKind::FinalMethodOverridden {
2348 class: s(),
2349 method: s(),
2350 parent: s(),
2351 },
2352 IssueKind::AbstractInstantiation { class: s() },
2353 IssueKind::AbstractMethodCall {
2354 class: s(),
2355 method: s(),
2356 },
2357 IssueKind::InterfaceInstantiation { class: s() },
2358 IssueKind::InvalidOverride {
2359 class: s(),
2360 method: s(),
2361 detail: s(),
2362 },
2363 IssueKind::CircularInheritance { class: s() },
2364 IssueKind::TaintedInput { sink: s() },
2365 IssueKind::TaintedHtml,
2366 IssueKind::TaintedSql,
2367 IssueKind::TaintedShell,
2368 IssueKind::TaintedLlmPrompt,
2369 IssueKind::InvalidTemplateParam {
2370 name: s(),
2371 expected_bound: s(),
2372 actual: s(),
2373 },
2374 IssueKind::ShadowedTemplateParam { name: s() },
2375 IssueKind::DeprecatedCall {
2376 name: s(),
2377 message: None,
2378 },
2379 IssueKind::DeprecatedProperty {
2380 class: s(),
2381 property: s(),
2382 message: None,
2383 },
2384 IssueKind::DeprecatedConstant {
2385 class: s(),
2386 constant: s(),
2387 message: None,
2388 },
2389 IssueKind::DeprecatedInterface {
2390 name: s(),
2391 message: None,
2392 },
2393 IssueKind::DeprecatedTrait {
2394 name: s(),
2395 message: None,
2396 },
2397 IssueKind::DeprecatedMethodCall {
2398 class: s(),
2399 method: s(),
2400 message: None,
2401 },
2402 IssueKind::DeprecatedMethod {
2403 class: s(),
2404 method: s(),
2405 message: None,
2406 },
2407 IssueKind::DeprecatedClass {
2408 name: s(),
2409 message: None,
2410 },
2411 IssueKind::InternalMethod {
2412 class: s(),
2413 method: s(),
2414 },
2415 IssueKind::MissingReturnType { fn_name: s() },
2416 IssueKind::MissingClosureReturnType,
2417 IssueKind::MissingParamType {
2418 fn_name: s(),
2419 param: s(),
2420 },
2421 IssueKind::MissingPropertyType {
2422 class: s(),
2423 property: s(),
2424 },
2425 IssueKind::MissingThrowsDocblock { class: s() },
2426 IssueKind::InvalidDocblock { message: s() },
2427 IssueKind::MixedArgument {
2428 param: s(),
2429 fn_name: s(),
2430 },
2431 IssueKind::MixedAssignment { var: s() },
2432 IssueKind::MixedMethodCall { method: s() },
2433 IssueKind::UnsupportedReferenceUsage,
2434 IssueKind::NoInterfaceProperties { property: s() },
2435 IssueKind::UndefinedDocblockClass { name: s() },
2436 IssueKind::MissingConstructor { class: s() },
2437 IssueKind::MixedFunctionCall,
2438 IssueKind::MixedReturnStatement { declared: s() },
2439 IssueKind::MixedPropertyFetch { property: s() },
2440 IssueKind::MixedPropertyAssignment { property: s() },
2441 IssueKind::MixedArrayAccess,
2442 IssueKind::MixedArrayOffset,
2443 IssueKind::MixedClone,
2444 IssueKind::InvalidClone { ty: s() },
2445 IssueKind::PossiblyInvalidClone { ty: s() },
2446 IssueKind::InvalidToString { class: s() },
2447 IssueKind::InvalidTraitUse {
2448 trait_name: s(),
2449 reason: s(),
2450 },
2451 IssueKind::ParseError { message: s() },
2452 IssueKind::InvalidThrow { ty: s() },
2453 IssueKind::InvalidCatch { ty: s() },
2454 IssueKind::UnreachableCatch {
2455 ty: s(),
2456 shadowed_by: s(),
2457 },
2458 IssueKind::ImplicitToStringCast { class: s() },
2459 IssueKind::ImplicitFloatToIntCast { from: s() },
2460 IssueKind::WrongCaseFunction {
2461 used: s(),
2462 canonical: s(),
2463 },
2464 IssueKind::WrongCaseMethod {
2465 class: s(),
2466 used: s(),
2467 canonical: s(),
2468 },
2469 IssueKind::WrongCaseClass {
2470 used: s(),
2471 canonical: s(),
2472 },
2473 IssueKind::InvalidAttribute { message: s() },
2474 IssueKind::UndefinedAttributeClass { name: s() },
2475 IssueKind::ForbiddenCode { message: s() },
2476 IssueKind::DuplicateClass { name: s() },
2477 IssueKind::DuplicateInterface { name: s() },
2478 IssueKind::DuplicateTrait { name: s() },
2479 IssueKind::DuplicateEnum { name: s() },
2480 IssueKind::DuplicateFunction { name: s() },
2481 ]
2482 }
2483
2484 #[test]
2485 fn codes_have_expected_shape() {
2486 for kind in one_of_each() {
2487 let code = kind.code();
2488 assert!(
2489 code.len() == 7
2490 && code.starts_with("MIR")
2491 && code[3..].chars().all(|c| c.is_ascii_digit()),
2492 "code {code:?} for {} does not match MIR####",
2493 kind.name(),
2494 );
2495 }
2496 }
2497
2498 #[test]
2499 fn codes_are_unique() {
2500 let kinds = one_of_each();
2501 let mut seen: HashSet<&'static str> = HashSet::new();
2502 for kind in &kinds {
2503 assert!(
2504 seen.insert(kind.code()),
2505 "duplicate code {} (variant {})",
2506 kind.code(),
2507 kind.name(),
2508 );
2509 }
2510 }
2511
2512 #[test]
2513 fn display_includes_code() {
2514 let issue = Issue::new(
2515 IssueKind::UndefinedClass {
2516 name: "Foo".to_string(),
2517 },
2518 Location {
2519 file: Arc::from("src/x.php"),
2520 line: 1,
2521 line_end: 1,
2522 col_start: 0,
2523 col_end: 3,
2524 },
2525 );
2526 let raw = format!("{issue}");
2529 let stripped: String = {
2530 let mut out = String::new();
2531 let mut chars = raw.chars();
2532 while let Some(c) = chars.next() {
2533 if c == '\u{1b}' {
2534 for c2 in chars.by_ref() {
2535 if c2 == 'm' {
2536 break;
2537 }
2538 }
2539 } else {
2540 out.push(c);
2541 }
2542 }
2543 out
2544 };
2545 assert!(
2546 stripped.contains("error[MIR0005] UndefinedClass:"),
2547 "Display output missing code/name segment: {stripped:?}",
2548 );
2549 }
2550
2551 #[test]
2552 fn default_severity_for_code_round_trips() {
2553 for kind in one_of_each() {
2554 let code = kind.code();
2555 assert_eq!(
2556 IssueKind::default_severity_for_code(code),
2557 Some(kind.default_severity()),
2558 "severity mismatch for {code} (variant {})",
2559 kind.name(),
2560 );
2561 }
2562 }
2563
2564 #[test]
2565 fn default_severity_for_code_unknown_returns_none() {
2566 assert_eq!(IssueKind::default_severity_for_code("MIR9999"), None);
2567 assert_eq!(IssueKind::default_severity_for_code(""), None);
2568 assert_eq!(IssueKind::default_severity_for_code("mir0001"), None);
2569 }
2570
2571 #[test]
2574 fn one_of_each_has_every_variant() {
2575 assert_eq!(one_of_each().len(), 149);
2578 }
2579}