1use std::collections::HashSet;
2use std::fmt;
3use std::sync::Arc;
4
5use serde::{Deserialize, Serialize};
6
7mod psalm_levels;
8pub use psalm_levels::psalm_error_level;
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
15pub enum Severity {
16 Info,
18 Warning,
20 Error,
22}
23
24impl fmt::Display for Severity {
25 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
26 match self {
27 Severity::Info => write!(f, "info"),
28 Severity::Warning => write!(f, "warning"),
29 Severity::Error => write!(f, "error"),
30 }
31 }
32}
33
34pub use mir_types::Location;
39
40#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
45#[non_exhaustive]
46pub enum IssueKind {
47 NonStaticSelfCall { class: String, method: String },
53 InvalidScope {
54 in_class: bool,
56 },
57 UndefinedVariable { name: String },
60 UndefinedFunction { name: String },
63 UndefinedMethod { class: String, method: String },
66 UndefinedClass { name: String },
69 UndefinedProperty { class: String, property: String },
72 InaccessibleProperty { class: String, property: String },
77 UndefinedConstant { name: String },
80 InaccessibleClassConstant { class: String, constant: String },
83 PossiblyUndefinedVariable { name: String },
86 UndefinedTrait { name: String },
89 TraitConstantAccessedDirectly {
96 trait_name: String,
97 constant: String,
98 },
99 UndefinedTraitAliasMethod {
107 trait_name: Option<String>,
108 method: String,
109 },
110 ParentNotFound,
113 InvalidStringClass { actual: String },
116 NotAnInterface { name: String },
120
121 NullArgument { param: String, fn_name: String },
125 NullPropertyFetch { property: String },
128 NullMethodCall { method: String },
131 NullArrayAccess,
134 PossiblyNullArgument { param: String, fn_name: String },
137 PossiblyInvalidArgument {
140 param: String,
141 fn_name: String,
142 expected: String,
143 actual: String,
144 },
145 PossiblyNullPropertyFetch { property: String },
148 PossiblyNullMethodCall { method: String },
151 PossiblyNullArrayAccess,
154 NullableReturnStatement { expected: String, actual: String },
157
158 InvalidReturnType { expected: String, actual: String },
162 InvalidArgument {
165 param: String,
166 fn_name: String,
167 expected: String,
168 actual: String,
169 },
170 TooFewArguments {
173 fn_name: String,
174 expected: usize,
175 actual: usize,
176 },
177 TooManyArguments {
180 fn_name: String,
181 expected: usize,
182 actual: usize,
183 },
184 InvalidNamedArgument {
187 fn_name: String,
188 name: String,
189 overwrites: bool,
190 },
191 InvalidNamedArguments { fn_name: String },
194 InvalidPassByReference { fn_name: String, param: String },
197 InvalidPropertyFetch { ty: String },
200 InvalidArrayAccess { ty: String },
203 PossiblyInvalidArrayAccess { ty: String },
206 InvalidArrayAssignment { ty: String },
209 InvalidPropertyAssignment {
212 property: String,
213 expected: String,
214 actual: String,
215 },
216 InvalidCast { from: String, to: String },
219 InvalidStaticInvocation { class: String, method: String },
222 InvalidOperand {
226 op: String,
227 left: String,
228 right: String,
229 },
230 PossiblyInvalidOperand {
233 op: String,
234 left: String,
235 right: String,
236 },
237 PossiblyNullOperand { op: String, ty: String },
240 DivisionByZero { op: String },
244 RawObjectIteration { ty: String },
247 PossiblyRawObjectIteration { ty: String },
250 MismatchingDocblockReturnType { declared: String, inferred: String },
253 MismatchingDocblockParamType {
256 param: String,
257 declared: String,
258 inferred: String,
259 },
260 TypeCheckMismatch {
266 var: String,
267 expected: String,
268 actual: String,
269 },
270
271 Trace { variable: String, type_info: String },
274
275 InvalidArrayOffset { expected: String, actual: String },
279 NonExistentArrayOffset { key: String },
283 PossiblyInvalidArrayOffset { expected: String, actual: String },
286 DuplicateArrayKey { key: String },
291
292 RedundantCondition {
296 always_true: bool,
297 unreachable: Option<String>,
299 },
300 RedundantCast { from: String, to: String },
303 UnnecessaryVarAnnotation { var: String },
306 TypeDoesNotContainType { left: String, right: String },
309 ParadoxicalCondition { value: String },
312 DocblockTypeContradiction { expr: String, declared: String },
317 ImpossibleIdenticalComparison {
323 op: String,
324 left: String,
325 right: String,
326 },
327 ImpossibleLooseComparison {
334 op: String,
335 left: String,
336 right: String,
337 },
338 UnevaluatedCode { reason: String },
345
346 UnusedVariable { name: String },
350 UnusedParam { name: String },
353 UnreachableCode,
356 UnhandledMatchCondition { detail: String },
359 UnusedMethod { class: String, method: String },
362 UnusedProperty { class: String, property: String },
365 UnusedFunction { name: String },
368 UnusedForeachValue { name: String },
371 UnusedClass { class: String },
374 UnusedSuppress { kind: String },
378
379 ArgumentTypeCoercion {
382 param: String,
383 fn_name: String,
384 expected: String,
385 actual: String,
386 },
387
388 PropertyTypeCoercion {
391 property: String,
392 expected: String,
393 actual: String,
394 },
395
396 ImpurePropertyAssignment { property: String },
399 ImpureMethodCall { method: String },
401 ImpureGlobalVariable { variable: String },
403 ImpureStaticVariable { variable: String },
405 ImpureStaticPropertyAssignment { class: String, property: String },
409 ImpureStaticPropertyAccess { class: String, property: String },
414 ImpureByRefAssignment { variable: String },
419 ImpureFunctionCall { fn_name: String },
423 ImmutablePropertyModification { receiver: String, property: String },
429
430 ReadonlyPropertyAssignment { class: String, property: String },
434 ReadonlyPropertyAlreadyInitialized { class: String, property: String },
441
442 UnimplementedAbstractMethod { class: String, method: String },
446 UnimplementedInterfaceMethod {
449 class: String,
450 interface: String,
451 method: String,
452 },
453 MethodSignatureMismatch {
456 class: String,
457 method: String,
458 detail: String,
459 },
460 OverriddenMethodAccess { class: String, method: String },
463 OverriddenPropertyAccess { class: String, property: String },
466 PropertyTypeRedeclarationMismatch {
469 class: String,
470 property: String,
471 expected: String,
472 actual: String,
473 },
474 ReadonlyPropertyRedeclarationMismatch {
477 parent_class: String,
478 class: String,
479 property: String,
480 parent_readonly: bool,
483 },
484 StaticPropertyRedeclarationMismatch {
487 parent_class: String,
488 class: String,
489 property: String,
490 parent_static: bool,
493 },
494 ReadonlyClassExtendsMismatch {
500 parent: String,
501 child: String,
502 parent_readonly: bool,
505 },
506 InvalidReadonlyPropertyDeclaration { class: String, property: String },
512 BackedEnumCaseTypeMismatch {
515 enum_name: String,
516 case_name: String,
517 expected: String,
518 actual: String,
519 },
520 DirectConstructorCall { class: String },
523 InvalidExtendClass { parent: String, child: String },
526 FinalMethodOverridden {
529 class: String,
530 method: String,
531 parent: String,
532 },
533 AbstractInstantiation { class: String },
536 AbstractMethodCall { class: String, method: String },
539 InterfaceInstantiation { class: String },
542 InvalidOverride {
546 class: String,
547 method: String,
548 detail: String,
549 },
550
551 TaintedInput { sink: String },
555 TaintedHtml,
558 TaintedSql,
561 TaintedShell,
564 TaintedLlmPrompt,
568 TaintedHeader,
571 TaintedCookie,
574
575 InvalidTemplateParam {
579 name: String,
580 expected_bound: String,
581 actual: String,
582 },
583 ShadowedTemplateParam { name: String },
586 IfThisIsMismatch {
591 class: String,
592 method: String,
593 expected: String,
594 actual: String,
595 },
596
597 DeprecatedCall {
601 name: String,
602 message: Option<Arc<str>>,
603 },
604 DeprecatedProperty {
607 class: String,
608 property: String,
609 message: Option<Arc<str>>,
610 },
611 DeprecatedConstant {
614 class: String,
615 constant: String,
616 message: Option<Arc<str>>,
617 },
618 DeprecatedInterface {
621 name: String,
622 message: Option<Arc<str>>,
623 },
624 DeprecatedTrait {
627 name: String,
628 message: Option<Arc<str>>,
629 },
630 DeprecatedMethodCall {
633 class: String,
634 method: String,
635 message: Option<Arc<str>>,
636 },
637 DeprecatedMethod {
640 class: String,
641 method: String,
642 message: Option<Arc<str>>,
643 },
644 DeprecatedClass {
647 name: String,
648 message: Option<Arc<str>>,
649 },
650 InternalMethod { class: String, method: String },
653 MissingReturnType { fn_name: String },
656 MissingClosureReturnType,
659 MissingParamType { fn_name: String, param: String },
662 MissingPropertyType { class: String, property: String },
665 InvalidThrow { ty: String },
668 InvalidCatch { ty: String },
671 UnreachableCatch { ty: String, shadowed_by: String },
676 MissingThrowsDocblock { class: String },
679 UndefinedThrowsDocblock { name: String },
684 ImplicitToStringCast { class: String },
687 ImplicitFloatToIntCast { from: String },
690 ParseError { message: String },
693 InvalidDocblock { message: String },
696 InvalidDocblockType { message: String },
703 MixedArgument { param: String, fn_name: String },
706 MixedAssignment { var: String },
709 MixedMethodCall { method: String },
712 UnsupportedReferenceUsage,
715 NoInterfaceProperties { property: String },
719 UndefinedDocblockClass { name: String },
723 MissingConstructor { class: String },
726 PropertyPossiblyUninitialized { class: String, property: String },
738 MixedFunctionCall,
741 MixedReturnStatement { declared: String },
744 MixedPropertyFetch { property: String },
747 MixedPropertyAssignment { property: String },
750 MixedArrayAccess,
753 MixedArrayOffset,
756 MixedClone,
759 InvalidClone { ty: String },
763 PossiblyInvalidClone { ty: String },
767 InvalidToString { class: String },
771 CircularInheritance { class: String },
774
775 InvalidTraitUse { trait_name: String, reason: String },
779 ForbiddenCode { message: String },
782
783 InvalidAttribute { message: String },
787 UndefinedAttributeClass { name: String },
790
791 WrongCaseFunction { used: String, canonical: String },
795 WrongCaseMethod {
798 class: String,
799 used: String,
800 canonical: String,
801 },
802 WrongCaseClass { used: String, canonical: String },
805 DuplicateClass { name: String },
808 DuplicateInterface { name: String },
811 DuplicateTrait { name: String },
814 DuplicateEnum { name: String },
817 DuplicateFunction { name: String },
820
821 PluginIssue { name: String, message: String },
825}
826
827fn append_deprecation_message(base: String, message: &Option<Arc<str>>) -> String {
828 match message.as_deref().filter(|m| !m.is_empty()) {
829 Some(msg) => format!("{base}: {msg}"),
830 None => base,
831 }
832}
833
834impl IssueKind {
835 pub fn default_severity(&self) -> Severity {
837 match self {
838 IssueKind::NonStaticSelfCall { .. }
840 | IssueKind::DirectConstructorCall { .. }
841 | IssueKind::InvalidScope { .. }
842 | IssueKind::UndefinedVariable { .. }
843 | IssueKind::UndefinedFunction { .. }
844 | IssueKind::UndefinedMethod { .. }
845 | IssueKind::UndefinedClass { .. }
846 | IssueKind::NotAnInterface { .. }
847 | IssueKind::UndefinedConstant { .. }
848 | IssueKind::InaccessibleClassConstant { .. }
849 | IssueKind::InaccessibleProperty { .. }
850 | IssueKind::InvalidReturnType { .. }
851 | IssueKind::InvalidArgument { .. }
852 | IssueKind::TooFewArguments { .. }
853 | IssueKind::TooManyArguments { .. }
854 | IssueKind::InvalidNamedArgument { .. }
855 | IssueKind::InvalidNamedArguments { .. }
856 | IssueKind::InvalidPassByReference { .. }
857 | IssueKind::InvalidThrow { .. }
858 | IssueKind::InvalidCatch { .. }
859 | IssueKind::InvalidStaticInvocation { .. }
860 | IssueKind::UnimplementedAbstractMethod { .. }
861 | IssueKind::UnimplementedInterfaceMethod { .. }
862 | IssueKind::MethodSignatureMismatch { .. }
863 | IssueKind::InvalidExtendClass { .. }
864 | IssueKind::FinalMethodOverridden { .. }
865 | IssueKind::AbstractInstantiation { .. }
866 | IssueKind::AbstractMethodCall { .. }
867 | IssueKind::InterfaceInstantiation { .. }
868 | IssueKind::InvalidOverride { .. }
869 | IssueKind::InvalidTemplateParam { .. }
870 | IssueKind::ReadonlyPropertyAssignment { .. }
871 | IssueKind::ReadonlyPropertyAlreadyInitialized { .. }
872 | IssueKind::ParseError { .. }
873 | IssueKind::TaintedInput { .. }
874 | IssueKind::TaintedHtml
875 | IssueKind::TaintedSql
876 | IssueKind::TaintedShell
877 | IssueKind::TaintedLlmPrompt
878 | IssueKind::TaintedHeader
879 | IssueKind::TaintedCookie
880 | IssueKind::CircularInheritance { .. }
881 | IssueKind::InvalidTraitUse { .. }
882 | IssueKind::UndefinedTrait { .. }
883 | IssueKind::TraitConstantAccessedDirectly { .. }
884 | IssueKind::UndefinedTraitAliasMethod { .. }
885 | IssueKind::InvalidClone { .. }
886 | IssueKind::InvalidToString { .. }
887 | IssueKind::TypeCheckMismatch { .. }
888 | IssueKind::PropertyTypeRedeclarationMismatch { .. }
889 | IssueKind::ReadonlyPropertyRedeclarationMismatch { .. }
890 | IssueKind::StaticPropertyRedeclarationMismatch { .. }
891 | IssueKind::ReadonlyClassExtendsMismatch { .. }
892 | IssueKind::InvalidReadonlyPropertyDeclaration { .. }
893 | IssueKind::BackedEnumCaseTypeMismatch { .. }
894 | IssueKind::DivisionByZero { .. }
895 | IssueKind::ParentNotFound => Severity::Error,
896 IssueKind::Trace { .. } => Severity::Info,
897
898 IssueKind::NullArgument { .. }
900 | IssueKind::NullPropertyFetch { .. }
901 | IssueKind::NullMethodCall { .. }
902 | IssueKind::NullArrayAccess
903 | IssueKind::NullableReturnStatement { .. }
904 | IssueKind::InvalidPropertyFetch { .. }
905 | IssueKind::InvalidArrayAccess { .. }
906 | IssueKind::InvalidArrayAssignment { .. }
907 | IssueKind::InvalidPropertyAssignment { .. }
908 | IssueKind::InvalidArrayOffset { .. }
909 | IssueKind::NonExistentArrayOffset { .. }
910 | IssueKind::PossiblyInvalidArrayOffset { .. }
911 | IssueKind::UndefinedProperty { .. }
912 | IssueKind::InvalidOperand { .. }
913 | IssueKind::OverriddenMethodAccess { .. }
914 | IssueKind::OverriddenPropertyAccess { .. }
915 | IssueKind::ImplicitToStringCast { .. }
916 | IssueKind::ImplicitFloatToIntCast { .. }
917 | IssueKind::UnusedVariable { .. }
918 | IssueKind::UnusedForeachValue { .. }
919 | IssueKind::ImpurePropertyAssignment { .. }
920 | IssueKind::ImpureMethodCall { .. }
921 | IssueKind::ImpureGlobalVariable { .. }
922 | IssueKind::ImpureStaticVariable { .. }
923 | IssueKind::ImpureStaticPropertyAssignment { .. }
924 | IssueKind::ImpureStaticPropertyAccess { .. }
925 | IssueKind::ImpureFunctionCall { .. }
926 | IssueKind::ImpureByRefAssignment { .. }
927 | IssueKind::ImmutablePropertyModification { .. }
928 | IssueKind::UnsupportedReferenceUsage
929 | IssueKind::ParadoxicalCondition { .. }
930 | IssueKind::UnhandledMatchCondition { .. }
931 | IssueKind::InvalidStringClass { .. }
932 | IssueKind::ImpossibleIdenticalComparison { .. }
933 | IssueKind::ImpossibleLooseComparison { .. }
934 | IssueKind::DuplicateArrayKey { .. }
935 | IssueKind::ForbiddenCode { .. }
936 | IssueKind::UndefinedThrowsDocblock { .. }
937 | IssueKind::InvalidDocblockType { .. } => Severity::Warning,
938
939 IssueKind::PossiblyUndefinedVariable { .. } => Severity::Warning,
941
942 IssueKind::PossiblyNullArgument { .. }
944 | IssueKind::PossiblyInvalidArgument { .. }
945 | IssueKind::PossiblyNullPropertyFetch { .. }
946 | IssueKind::PossiblyNullMethodCall { .. }
947 | IssueKind::PossiblyNullArrayAccess
948 | IssueKind::PossiblyInvalidArrayAccess { .. }
949 | IssueKind::PossiblyInvalidClone { .. }
950 | IssueKind::PossiblyInvalidOperand { .. }
951 | IssueKind::PossiblyNullOperand { .. }
952 | IssueKind::PossiblyRawObjectIteration { .. } => Severity::Info,
953
954 IssueKind::RawObjectIteration { .. } => Severity::Warning,
955
956 IssueKind::RedundantCondition { .. }
958 | IssueKind::RedundantCast { .. }
959 | IssueKind::UnnecessaryVarAnnotation { .. }
960 | IssueKind::TypeDoesNotContainType { .. }
961 | IssueKind::DocblockTypeContradiction { .. }
962 | IssueKind::UnevaluatedCode { .. }
963 | IssueKind::IfThisIsMismatch { .. }
964 | IssueKind::UnusedParam { .. }
965 | IssueKind::UnreachableCode
966 | IssueKind::UnreachableCatch { .. }
967 | IssueKind::UnusedMethod { .. }
968 | IssueKind::UnusedProperty { .. }
969 | IssueKind::UnusedFunction { .. }
970 | IssueKind::UnusedClass { .. }
971 | IssueKind::UnusedSuppress { .. }
972 | IssueKind::ArgumentTypeCoercion { .. }
973 | IssueKind::PropertyTypeCoercion { .. }
974 | IssueKind::DeprecatedCall { .. }
975 | IssueKind::DeprecatedProperty { .. }
976 | IssueKind::DeprecatedConstant { .. }
977 | IssueKind::DeprecatedInterface { .. }
978 | IssueKind::DeprecatedTrait { .. }
979 | IssueKind::DeprecatedMethodCall { .. }
980 | IssueKind::DeprecatedMethod { .. }
981 | IssueKind::DeprecatedClass { .. }
982 | IssueKind::InternalMethod { .. }
983 | IssueKind::MissingReturnType { .. }
984 | IssueKind::MissingClosureReturnType
985 | IssueKind::MissingParamType { .. }
986 | IssueKind::MissingPropertyType { .. }
987 | IssueKind::MismatchingDocblockReturnType { .. }
988 | IssueKind::MismatchingDocblockParamType { .. }
989 | IssueKind::InvalidDocblock { .. }
990 | IssueKind::InvalidCast { .. }
991 | IssueKind::MixedArgument { .. }
992 | IssueKind::MixedAssignment { .. }
993 | IssueKind::MixedMethodCall { .. }
994 | IssueKind::NoInterfaceProperties { .. }
995 | IssueKind::UndefinedDocblockClass { .. }
996 | IssueKind::MissingConstructor { .. }
997 | IssueKind::PropertyPossiblyUninitialized { .. }
998 | IssueKind::MixedFunctionCall
999 | IssueKind::MixedReturnStatement { .. }
1000 | IssueKind::MixedPropertyFetch { .. }
1001 | IssueKind::MixedPropertyAssignment { .. }
1002 | IssueKind::MixedArrayAccess
1003 | IssueKind::MixedArrayOffset
1004 | IssueKind::MixedClone
1005 | IssueKind::ShadowedTemplateParam { .. }
1006 | IssueKind::MissingThrowsDocblock { .. }
1007 | IssueKind::WrongCaseFunction { .. }
1008 | IssueKind::WrongCaseMethod { .. }
1009 | IssueKind::WrongCaseClass { .. }
1010 | IssueKind::InvalidAttribute { .. }
1011 | IssueKind::UndefinedAttributeClass { .. } => Severity::Info,
1012 IssueKind::DuplicateClass { .. }
1013 | IssueKind::DuplicateInterface { .. }
1014 | IssueKind::DuplicateTrait { .. }
1015 | IssueKind::DuplicateEnum { .. }
1016 | IssueKind::DuplicateFunction { .. } => Severity::Error,
1017 IssueKind::PluginIssue { .. } => Severity::Error,
1020 }
1021 }
1022
1023 pub fn code(&self) -> &'static str {
1053 match self {
1054 IssueKind::NonStaticSelfCall { .. } => "MIR0216",
1056 IssueKind::DirectConstructorCall { .. } => "MIR0217",
1057 IssueKind::InvalidScope { .. } => "MIR0001",
1058 IssueKind::UndefinedVariable { .. } => "MIR0002",
1059 IssueKind::UndefinedFunction { .. } => "MIR0003",
1060 IssueKind::UndefinedMethod { .. } => "MIR0004",
1061 IssueKind::UndefinedClass { .. } => "MIR0005",
1062 IssueKind::UndefinedProperty { .. } => "MIR0006",
1063 IssueKind::UndefinedConstant { .. } => "MIR0007",
1064 IssueKind::InaccessibleClassConstant { .. } => "MIR0011",
1065 IssueKind::InaccessibleProperty { .. } => "MIR0014",
1066 IssueKind::TraitConstantAccessedDirectly { .. } => "MIR0012",
1067 IssueKind::UndefinedTraitAliasMethod { .. } => "MIR0013",
1068 IssueKind::PossiblyUndefinedVariable { .. } => "MIR0008",
1069 IssueKind::UndefinedTrait { .. } => "MIR0009",
1070 IssueKind::ParentNotFound => "MIR0010",
1071
1072 IssueKind::NullArgument { .. } => "MIR0100",
1074 IssueKind::NullPropertyFetch { .. } => "MIR0101",
1075 IssueKind::NullMethodCall { .. } => "MIR0102",
1076 IssueKind::NullArrayAccess => "MIR0103",
1077 IssueKind::PossiblyNullArgument { .. } => "MIR0104",
1078 IssueKind::PossiblyInvalidArgument { .. } => "MIR0105",
1079 IssueKind::PossiblyNullPropertyFetch { .. } => "MIR0106",
1080 IssueKind::PossiblyNullMethodCall { .. } => "MIR0107",
1081 IssueKind::PossiblyNullArrayAccess => "MIR0108",
1082 IssueKind::NullableReturnStatement { .. } => "MIR0109",
1083
1084 IssueKind::InvalidReturnType { .. } => "MIR0200",
1086 IssueKind::InvalidArgument { .. } => "MIR0201",
1087 IssueKind::TooFewArguments { .. } => "MIR0202",
1088 IssueKind::TooManyArguments { .. } => "MIR0203",
1089 IssueKind::InvalidNamedArgument { .. } => "MIR0204",
1090 IssueKind::InvalidNamedArguments { .. } => "MIR0224",
1091 IssueKind::InvalidPassByReference { .. } => "MIR0205",
1092 IssueKind::InvalidPropertyFetch { .. } => "MIR0218",
1093 IssueKind::InvalidArrayAccess { .. } => "MIR0219",
1094 IssueKind::PossiblyInvalidArrayAccess { .. } => "MIR0227",
1095 IssueKind::InvalidArrayAssignment { .. } => "MIR0220",
1096 IssueKind::InvalidPropertyAssignment { .. } => "MIR0206",
1097 IssueKind::InvalidCast { .. } => "MIR0207",
1098 IssueKind::InvalidStaticInvocation { .. } => "MIR0215",
1099 IssueKind::InvalidOperand { .. } => "MIR0208",
1100 IssueKind::PossiblyInvalidOperand { .. } => "MIR0213",
1101 IssueKind::PossiblyNullOperand { .. } => "MIR0214",
1102 IssueKind::DivisionByZero { .. } => "MIR0229",
1103 IssueKind::RawObjectIteration { .. } => "MIR0222",
1104 IssueKind::PossiblyRawObjectIteration { .. } => "MIR0223",
1105 IssueKind::MismatchingDocblockReturnType { .. } => "MIR0209",
1106 IssueKind::MismatchingDocblockParamType { .. } => "MIR0210",
1107 IssueKind::InvalidStringClass { .. } => "MIR0211",
1108 IssueKind::NotAnInterface { .. } => "MIR0228",
1109 IssueKind::TypeCheckMismatch { .. } => "MIR0212",
1110 IssueKind::Trace { .. } => "MIR0221",
1111 IssueKind::ArgumentTypeCoercion { .. } => "MIR0225",
1112 IssueKind::PropertyTypeCoercion { .. } => "MIR0226",
1113
1114 IssueKind::InvalidArrayOffset { .. } => "MIR0300",
1116 IssueKind::NonExistentArrayOffset { .. } => "MIR0301",
1117 IssueKind::PossiblyInvalidArrayOffset { .. } => "MIR0302",
1118 IssueKind::DuplicateArrayKey { .. } => "MIR0303",
1119
1120 IssueKind::RedundantCondition { .. } => "MIR0400",
1122 IssueKind::RedundantCast { .. } => "MIR0401",
1123 IssueKind::UnnecessaryVarAnnotation { .. } => "MIR0402",
1124 IssueKind::TypeDoesNotContainType { .. } => "MIR0403",
1125 IssueKind::ParadoxicalCondition { .. } => "MIR0404",
1126 IssueKind::UnhandledMatchCondition { .. } => "MIR0405",
1127 IssueKind::DocblockTypeContradiction { .. } => "MIR0406",
1128 IssueKind::UnevaluatedCode { .. } => "MIR0407",
1129 IssueKind::ImpossibleIdenticalComparison { .. } => "MIR0408",
1130 IssueKind::ImpossibleLooseComparison { .. } => "MIR0409",
1131
1132 IssueKind::UnusedVariable { .. } => "MIR0500",
1134 IssueKind::UnusedParam { .. } => "MIR0501",
1135 IssueKind::UnreachableCode => "MIR0502",
1136 IssueKind::UnusedMethod { .. } => "MIR0503",
1137 IssueKind::UnusedProperty { .. } => "MIR0504",
1138 IssueKind::UnusedFunction { .. } => "MIR0505",
1139 IssueKind::UnusedForeachValue { .. } => "MIR0506",
1140 IssueKind::UnusedClass { .. } => "MIR0507",
1141 IssueKind::UnusedSuppress { .. } => "MIR0508",
1142
1143 IssueKind::ImpurePropertyAssignment { .. } => "MIR1700",
1145 IssueKind::ImpureMethodCall { .. } => "MIR1701",
1146 IssueKind::ImpureGlobalVariable { .. } => "MIR1702",
1147 IssueKind::ImpureStaticVariable { .. } => "MIR1703",
1148 IssueKind::ImpureStaticPropertyAssignment { .. } => "MIR1706",
1149 IssueKind::ImpureByRefAssignment { .. } => "MIR1707",
1150 IssueKind::ImpureStaticPropertyAccess { .. } => "MIR1708",
1151 IssueKind::ImpureFunctionCall { .. } => "MIR1704",
1152 IssueKind::ImmutablePropertyModification { .. } => "MIR1705",
1153 IssueKind::UnsupportedReferenceUsage => "MIR1506",
1154 IssueKind::NoInterfaceProperties { .. } => "MIR1504",
1155 IssueKind::UndefinedDocblockClass { .. } => "MIR1505",
1156 IssueKind::MissingConstructor { .. } => "MIR1507",
1157 IssueKind::PropertyPossiblyUninitialized { .. } => "MIR1510",
1158 IssueKind::MixedFunctionCall => "MIR1211",
1159 IssueKind::MixedReturnStatement { .. } => "MIR1212",
1160
1161 IssueKind::ReadonlyPropertyAssignment { .. } => "MIR0600",
1163 IssueKind::ReadonlyPropertyAlreadyInitialized { .. } => "MIR0601",
1164
1165 IssueKind::UnimplementedAbstractMethod { .. } => "MIR0700",
1167 IssueKind::UnimplementedInterfaceMethod { .. } => "MIR0701",
1168 IssueKind::MethodSignatureMismatch { .. } => "MIR0702",
1169 IssueKind::OverriddenMethodAccess { .. } => "MIR0703",
1170 IssueKind::OverriddenPropertyAccess { .. } => "MIR0710",
1171 IssueKind::PropertyTypeRedeclarationMismatch { .. } => "MIR0712",
1172 IssueKind::BackedEnumCaseTypeMismatch { .. } => "MIR0713",
1173 IssueKind::ReadonlyPropertyRedeclarationMismatch { .. } => "MIR0714",
1174 IssueKind::StaticPropertyRedeclarationMismatch { .. } => "MIR0715",
1175 IssueKind::ReadonlyClassExtendsMismatch { .. } => "MIR0716",
1176 IssueKind::InvalidReadonlyPropertyDeclaration { .. } => "MIR0717",
1177 IssueKind::InvalidExtendClass { .. } => "MIR0704",
1178 IssueKind::FinalMethodOverridden { .. } => "MIR0705",
1179 IssueKind::AbstractInstantiation { .. } => "MIR0706",
1180 IssueKind::AbstractMethodCall { .. } => "MIR0711",
1181 IssueKind::InterfaceInstantiation { .. } => "MIR0709",
1182 IssueKind::CircularInheritance { .. } => "MIR0707",
1183 IssueKind::InvalidOverride { .. } => "MIR0708",
1184
1185 IssueKind::TaintedInput { .. } => "MIR0800",
1187 IssueKind::TaintedHtml => "MIR0801",
1188 IssueKind::TaintedSql => "MIR0802",
1189 IssueKind::TaintedShell => "MIR0803",
1190 IssueKind::TaintedLlmPrompt => "MIR0804",
1191 IssueKind::TaintedHeader => "MIR0805",
1192 IssueKind::TaintedCookie => "MIR0806",
1193
1194 IssueKind::InvalidTemplateParam { .. } => "MIR0900",
1196 IssueKind::ShadowedTemplateParam { .. } => "MIR0901",
1197 IssueKind::IfThisIsMismatch { .. } => "MIR0902",
1198
1199 IssueKind::DeprecatedCall { .. } => "MIR1000",
1201 IssueKind::WrongCaseFunction { .. } => "MIR1009",
1202 IssueKind::WrongCaseMethod { .. } => "MIR1010",
1203 IssueKind::WrongCaseClass { .. } => "MIR1011",
1204 IssueKind::DeprecatedProperty { .. } => "MIR1005",
1205 IssueKind::DeprecatedInterface { .. } => "MIR1006",
1206 IssueKind::DeprecatedTrait { .. } => "MIR1007",
1207 IssueKind::DeprecatedConstant { .. } => "MIR1008",
1208 IssueKind::DeprecatedMethodCall { .. } => "MIR1001",
1209 IssueKind::DeprecatedMethod { .. } => "MIR1002",
1210 IssueKind::DeprecatedClass { .. } => "MIR1003",
1211 IssueKind::InternalMethod { .. } => "MIR1004",
1212
1213 IssueKind::MissingReturnType { .. } => "MIR1100",
1215 IssueKind::MissingParamType { .. } => "MIR1101",
1216 IssueKind::MissingPropertyType { .. } => "MIR1104",
1217 IssueKind::MissingClosureReturnType => "MIR1105",
1218 IssueKind::MissingThrowsDocblock { .. } => "MIR1102",
1219 IssueKind::InvalidDocblock { .. } => "MIR1103",
1220 IssueKind::UndefinedThrowsDocblock { .. } => "MIR1106",
1221 IssueKind::InvalidDocblockType { .. } => "MIR1107",
1222
1223 IssueKind::MixedArgument { .. } => "MIR1200",
1225 IssueKind::MixedAssignment { .. } => "MIR1201",
1226 IssueKind::MixedMethodCall { .. } => "MIR1202",
1227 IssueKind::MixedPropertyFetch { .. } => "MIR1203",
1228 IssueKind::MixedPropertyAssignment { .. } => "MIR1208",
1229 IssueKind::MixedArrayAccess => "MIR1209",
1230 IssueKind::MixedArrayOffset => "MIR1210",
1231 IssueKind::MixedClone => "MIR1204",
1232 IssueKind::InvalidClone { .. } => "MIR1205",
1233 IssueKind::PossiblyInvalidClone { .. } => "MIR1206",
1234 IssueKind::InvalidToString { .. } => "MIR1207",
1235
1236 IssueKind::InvalidTraitUse { .. } => "MIR1300",
1238 IssueKind::ForbiddenCode { .. } => "MIR1301",
1239
1240 IssueKind::ParseError { .. } => "MIR1400",
1242
1243 IssueKind::InvalidAttribute { .. } => "MIR1600",
1245 IssueKind::UndefinedAttributeClass { .. } => "MIR1601",
1246 IssueKind::DuplicateClass { .. } => "MIR1602",
1247 IssueKind::DuplicateInterface { .. } => "MIR1603",
1248 IssueKind::DuplicateTrait { .. } => "MIR1604",
1249 IssueKind::DuplicateEnum { .. } => "MIR1605",
1250 IssueKind::DuplicateFunction { .. } => "MIR1606",
1251
1252 IssueKind::InvalidThrow { .. } => "MIR1500",
1254 IssueKind::InvalidCatch { .. } => "MIR1503",
1255 IssueKind::UnreachableCatch { .. } => "MIR1508",
1256 IssueKind::PluginIssue { .. } => "MIR1509",
1257 IssueKind::ImplicitToStringCast { .. } => "MIR1501",
1258 IssueKind::ImplicitFloatToIntCast { .. } => "MIR1502",
1259 }
1260 }
1261
1262 pub fn default_severity_for_code(code: &str) -> Option<Severity> {
1268 match code {
1269 "MIR0001" | "MIR0002" | "MIR0003" | "MIR0004" | "MIR0005" | "MIR0007" | "MIR0009"
1271 | "MIR0010" | "MIR0011" | "MIR0012" | "MIR0013" | "MIR0014" | "MIR0200" | "MIR0201"
1272 | "MIR0202" | "MIR0203" | "MIR0204" | "MIR0205" | "MIR0212" | "MIR0215" | "MIR0216"
1273 | "MIR0217" | "MIR0224" | "MIR0600" | "MIR0601" | "MIR0700" | "MIR0701" | "MIR0702"
1274 | "MIR0704" | "MIR0705" | "MIR0706" | "MIR0707" | "MIR0708" | "MIR0709" | "MIR0711"
1275 | "MIR0712" | "MIR0713" | "MIR0714" | "MIR0715" | "MIR0716" | "MIR0717" | "MIR0228"
1276 | "MIR0229" | "MIR0800" | "MIR0801" | "MIR0802" | "MIR0803" | "MIR0804" | "MIR0805"
1277 | "MIR0806" | "MIR0900" | "MIR1205" | "MIR1207" | "MIR1300" | "MIR1400" | "MIR1500"
1278 | "MIR1503" | "MIR1602" | "MIR1603" | "MIR1604" | "MIR1605" | "MIR1606" => {
1279 Some(Severity::Error)
1280 }
1281
1282 "MIR0006" | "MIR0008" | "MIR0100" | "MIR0101" | "MIR0102" | "MIR0103" | "MIR0109"
1284 | "MIR0206" | "MIR0208" | "MIR0211" | "MIR0218" | "MIR0219" | "MIR0220" | "MIR0222"
1285 | "MIR0300" | "MIR0301" | "MIR0302" | "MIR0303" | "MIR0404" | "MIR0405" | "MIR0408"
1286 | "MIR0500" | "MIR0506" | "MIR0703" | "MIR0710" | "MIR1301" | "MIR1501" | "MIR1502"
1287 | "MIR1700" | "MIR1701" | "MIR1702" | "MIR1703" | "MIR1704" | "MIR1705" | "MIR1706"
1288 | "MIR1707" | "MIR1708" | "MIR1506" | "MIR1106" | "MIR1107" => Some(Severity::Warning),
1289
1290 "MIR0104" | "MIR0105" | "MIR0106" | "MIR0107" | "MIR0108" | "MIR0207" | "MIR0209"
1292 | "MIR0210" | "MIR0213" | "MIR0214" | "MIR0221" | "MIR0223" | "MIR0400" | "MIR0401"
1293 | "MIR0402" | "MIR0403" | "MIR0501" | "MIR0502" | "MIR0503" | "MIR0504" | "MIR0505"
1294 | "MIR0507" | "MIR0508" | "MIR0901" | "MIR1000" | "MIR1001" | "MIR1002" | "MIR1003"
1295 | "MIR1004" | "MIR1005" | "MIR1006" | "MIR1007" | "MIR1008" | "MIR1009" | "MIR1010"
1296 | "MIR1011" | "MIR1100" | "MIR1101" | "MIR1102" | "MIR1103" | "MIR1104" | "MIR1105"
1297 | "MIR1200" | "MIR1201" | "MIR1202" | "MIR1203" | "MIR1204" | "MIR1206" | "MIR1208"
1298 | "MIR1209" | "MIR1210" | "MIR1211" | "MIR1212" | "MIR1504" | "MIR1505" | "MIR1507"
1299 | "MIR1508" | "MIR1600" | "MIR1601" | "MIR0225" | "MIR0226" | "MIR0227" | "MIR0406"
1300 | "MIR0407" | "MIR0902" | "MIR1510" => Some(Severity::Info),
1301
1302 _ => None,
1303 }
1304 }
1305
1306 pub fn name(&self) -> &'static str {
1308 match self {
1309 IssueKind::NonStaticSelfCall { .. } => "NonStaticSelfCall",
1310 IssueKind::DirectConstructorCall { .. } => "DirectConstructorCall",
1311 IssueKind::InvalidScope { .. } => "InvalidScope",
1312 IssueKind::UndefinedVariable { .. } => "UndefinedVariable",
1313 IssueKind::UndefinedFunction { .. } => "UndefinedFunction",
1314 IssueKind::UndefinedMethod { .. } => "UndefinedMethod",
1315 IssueKind::UndefinedClass { .. } => "UndefinedClass",
1316 IssueKind::UndefinedProperty { .. } => "UndefinedProperty",
1317 IssueKind::UndefinedConstant { .. } => "UndefinedConstant",
1318 IssueKind::InaccessibleClassConstant { .. } => "InaccessibleClassConstant",
1319 IssueKind::InaccessibleProperty { .. } => "InaccessibleProperty",
1320 IssueKind::TraitConstantAccessedDirectly { .. } => "TraitConstantAccessedDirectly",
1321 IssueKind::UndefinedTraitAliasMethod { .. } => "UndefinedTraitAliasMethod",
1322 IssueKind::PossiblyUndefinedVariable { .. } => "PossiblyUndefinedVariable",
1323 IssueKind::UndefinedTrait { .. } => "UndefinedTrait",
1324 IssueKind::ParentNotFound => "ParentNotFound",
1325 IssueKind::InvalidStringClass { .. } => "InvalidStringClass",
1326 IssueKind::NotAnInterface { .. } => "NotAnInterface",
1327 IssueKind::NullArgument { .. } => "NullArgument",
1328 IssueKind::NullPropertyFetch { .. } => "NullPropertyFetch",
1329 IssueKind::NullMethodCall { .. } => "NullMethodCall",
1330 IssueKind::NullArrayAccess => "NullArrayAccess",
1331 IssueKind::PossiblyNullArgument { .. } => "PossiblyNullArgument",
1332 IssueKind::PossiblyInvalidArgument { .. } => "PossiblyInvalidArgument",
1333 IssueKind::PossiblyNullPropertyFetch { .. } => "PossiblyNullPropertyFetch",
1334 IssueKind::PossiblyNullMethodCall { .. } => "PossiblyNullMethodCall",
1335 IssueKind::PossiblyNullArrayAccess => "PossiblyNullArrayAccess",
1336 IssueKind::NullableReturnStatement { .. } => "NullableReturnStatement",
1337 IssueKind::InvalidReturnType { .. } => "InvalidReturnType",
1338 IssueKind::InvalidArgument { .. } => "InvalidArgument",
1339 IssueKind::TooFewArguments { .. } => "TooFewArguments",
1340 IssueKind::TooManyArguments { .. } => "TooManyArguments",
1341 IssueKind::InvalidNamedArgument { .. } => "InvalidNamedArgument",
1342 IssueKind::InvalidNamedArguments { .. } => "InvalidNamedArguments",
1343 IssueKind::InvalidPassByReference { .. } => "InvalidPassByReference",
1344 IssueKind::InvalidPropertyFetch { .. } => "InvalidPropertyFetch",
1345 IssueKind::InvalidArrayAccess { .. } => "InvalidArrayAccess",
1346 IssueKind::PossiblyInvalidArrayAccess { .. } => "PossiblyInvalidArrayAccess",
1347 IssueKind::InvalidArrayAssignment { .. } => "InvalidArrayAssignment",
1348 IssueKind::InvalidPropertyAssignment { .. } => "InvalidPropertyAssignment",
1349 IssueKind::InvalidCast { .. } => "InvalidCast",
1350 IssueKind::InvalidStaticInvocation { .. } => "InvalidStaticInvocation",
1351 IssueKind::InvalidOperand { .. } => "InvalidOperand",
1352 IssueKind::PossiblyInvalidOperand { .. } => "PossiblyInvalidOperand",
1353 IssueKind::PossiblyNullOperand { .. } => "PossiblyNullOperand",
1354 IssueKind::DivisionByZero { .. } => "DivisionByZero",
1355 IssueKind::RawObjectIteration { .. } => "RawObjectIteration",
1356 IssueKind::PossiblyRawObjectIteration { .. } => "PossiblyRawObjectIteration",
1357 IssueKind::MismatchingDocblockReturnType { .. } => "MismatchingDocblockReturnType",
1358 IssueKind::MismatchingDocblockParamType { .. } => "MismatchingDocblockParamType",
1359 IssueKind::TypeCheckMismatch { .. } => "TypeCheckMismatch",
1360 IssueKind::DocblockTypeContradiction { .. } => "DocblockTypeContradiction",
1361 IssueKind::ImpossibleIdenticalComparison { .. } => "ImpossibleIdenticalComparison",
1362 IssueKind::ImpossibleLooseComparison { .. } => "ImpossibleLooseComparison",
1363 IssueKind::UnevaluatedCode { .. } => "UnevaluatedCode",
1364 IssueKind::IfThisIsMismatch { .. } => "IfThisIsMismatch",
1365 IssueKind::Trace { .. } => "Trace",
1366 IssueKind::InvalidArrayOffset { .. } => "InvalidArrayOffset",
1367 IssueKind::NonExistentArrayOffset { .. } => "NonExistentArrayOffset",
1368 IssueKind::PossiblyInvalidArrayOffset { .. } => "PossiblyInvalidArrayOffset",
1369 IssueKind::DuplicateArrayKey { .. } => "DuplicateArrayKey",
1370 IssueKind::RedundantCondition { .. } => "RedundantCondition",
1371 IssueKind::RedundantCast { .. } => "RedundantCast",
1372 IssueKind::UnnecessaryVarAnnotation { .. } => "UnnecessaryVarAnnotation",
1373 IssueKind::TypeDoesNotContainType { .. } => "TypeDoesNotContainType",
1374 IssueKind::ParadoxicalCondition { .. } => "ParadoxicalCondition",
1375 IssueKind::UnhandledMatchCondition { .. } => "UnhandledMatchCondition",
1376 IssueKind::UnusedVariable { .. } => "UnusedVariable",
1377 IssueKind::UnusedParam { .. } => "UnusedParam",
1378 IssueKind::UnreachableCode => "UnreachableCode",
1379 IssueKind::UnusedMethod { .. } => "UnusedMethod",
1380 IssueKind::UnusedProperty { .. } => "UnusedProperty",
1381 IssueKind::UnusedFunction { .. } => "UnusedFunction",
1382 IssueKind::UnusedForeachValue { .. } => "UnusedForeachValue",
1383 IssueKind::UnusedClass { .. } => "UnusedClass",
1384 IssueKind::UnusedSuppress { .. } => "UnusedSuppress",
1385 IssueKind::ArgumentTypeCoercion { .. } => "ArgumentTypeCoercion",
1386 IssueKind::PropertyTypeCoercion { .. } => "PropertyTypeCoercion",
1387 IssueKind::ImpurePropertyAssignment { .. } => "ImpurePropertyAssignment",
1388 IssueKind::ImpureMethodCall { .. } => "ImpureMethodCall",
1389 IssueKind::ImpureGlobalVariable { .. } => "ImpureGlobalVariable",
1390 IssueKind::ImpureStaticVariable { .. } => "ImpureStaticVariable",
1391 IssueKind::ImpureByRefAssignment { .. } => "ImpureByRefAssignment",
1392 IssueKind::ImpureStaticPropertyAssignment { .. } => "ImpureStaticPropertyAssignment",
1393 IssueKind::ImpureStaticPropertyAccess { .. } => "ImpureStaticPropertyAccess",
1394 IssueKind::ImpureFunctionCall { .. } => "ImpureFunctionCall",
1395 IssueKind::ImmutablePropertyModification { .. } => "ImmutablePropertyModification",
1396 IssueKind::UnsupportedReferenceUsage => "UnsupportedReferenceUsage",
1397 IssueKind::NoInterfaceProperties { .. } => "NoInterfaceProperties",
1398 IssueKind::UndefinedDocblockClass { .. } => "UndefinedDocblockClass",
1399 IssueKind::MissingConstructor { .. } => "MissingConstructor",
1400 IssueKind::PropertyPossiblyUninitialized { .. } => "PropertyPossiblyUninitialized",
1401 IssueKind::MixedFunctionCall => "MixedFunctionCall",
1402 IssueKind::MixedReturnStatement { .. } => "MixedReturnStatement",
1403 IssueKind::UnimplementedAbstractMethod { .. } => "UnimplementedAbstractMethod",
1404 IssueKind::UnimplementedInterfaceMethod { .. } => "UnimplementedInterfaceMethod",
1405 IssueKind::MethodSignatureMismatch { .. } => "MethodSignatureMismatch",
1406 IssueKind::OverriddenMethodAccess { .. } => "OverriddenMethodAccess",
1407 IssueKind::OverriddenPropertyAccess { .. } => "OverriddenPropertyAccess",
1408 IssueKind::PropertyTypeRedeclarationMismatch { .. } => {
1409 "PropertyTypeRedeclarationMismatch"
1410 }
1411 IssueKind::ReadonlyPropertyRedeclarationMismatch { .. } => {
1412 "ReadonlyPropertyRedeclarationMismatch"
1413 }
1414 IssueKind::StaticPropertyRedeclarationMismatch { .. } => {
1415 "StaticPropertyRedeclarationMismatch"
1416 }
1417 IssueKind::ReadonlyClassExtendsMismatch { .. } => "ReadonlyClassExtendsMismatch",
1418 IssueKind::InvalidReadonlyPropertyDeclaration { .. } => {
1419 "InvalidReadonlyPropertyDeclaration"
1420 }
1421 IssueKind::BackedEnumCaseTypeMismatch { .. } => "BackedEnumCaseTypeMismatch",
1422 IssueKind::InvalidExtendClass { .. } => "InvalidExtendClass",
1423 IssueKind::FinalMethodOverridden { .. } => "FinalMethodOverridden",
1424 IssueKind::AbstractInstantiation { .. } => "AbstractInstantiation",
1425 IssueKind::AbstractMethodCall { .. } => "AbstractMethodCall",
1426 IssueKind::InterfaceInstantiation { .. } => "InterfaceInstantiation",
1427 IssueKind::InvalidOverride { .. } => "InvalidOverride",
1428 IssueKind::ReadonlyPropertyAssignment { .. } => "ReadonlyPropertyAssignment",
1429 IssueKind::ReadonlyPropertyAlreadyInitialized { .. } => {
1430 "ReadonlyPropertyAlreadyInitialized"
1431 }
1432 IssueKind::InvalidTemplateParam { .. } => "InvalidTemplateParam",
1433 IssueKind::ShadowedTemplateParam { .. } => "ShadowedTemplateParam",
1434 IssueKind::TaintedInput { .. } => "TaintedInput",
1435 IssueKind::TaintedHtml => "TaintedHtml",
1436 IssueKind::TaintedSql => "TaintedSql",
1437 IssueKind::TaintedShell => "TaintedShell",
1438 IssueKind::TaintedLlmPrompt => "TaintedLlmPrompt",
1439 IssueKind::TaintedHeader => "TaintedHeader",
1440 IssueKind::TaintedCookie => "TaintedCookie",
1441 IssueKind::DeprecatedCall { .. } => "DeprecatedCall",
1442 IssueKind::DeprecatedProperty { .. } => "DeprecatedProperty",
1443 IssueKind::DeprecatedConstant { .. } => "DeprecatedConstant",
1444 IssueKind::DeprecatedInterface { .. } => "DeprecatedInterface",
1445 IssueKind::DeprecatedTrait { .. } => "DeprecatedTrait",
1446 IssueKind::DeprecatedMethodCall { .. } => "DeprecatedMethodCall",
1447 IssueKind::DeprecatedMethod { .. } => "DeprecatedMethod",
1448 IssueKind::DeprecatedClass { .. } => "DeprecatedClass",
1449 IssueKind::InternalMethod { .. } => "InternalMethod",
1450 IssueKind::MissingReturnType { .. } => "MissingReturnType",
1451 IssueKind::MissingClosureReturnType => "MissingClosureReturnType",
1452 IssueKind::MissingParamType { .. } => "MissingParamType",
1453 IssueKind::MissingPropertyType { .. } => "MissingPropertyType",
1454 IssueKind::InvalidThrow { .. } => "InvalidThrow",
1455 IssueKind::InvalidCatch { .. } => "InvalidCatch",
1456 IssueKind::UnreachableCatch { .. } => "UnreachableCatch",
1457 IssueKind::MissingThrowsDocblock { .. } => "MissingThrowsDocblock",
1458 IssueKind::ImplicitToStringCast { .. } => "ImplicitToStringCast",
1459 IssueKind::ImplicitFloatToIntCast { .. } => "ImplicitFloatToIntCast",
1460 IssueKind::ParseError { .. } => "ParseError",
1461 IssueKind::InvalidDocblock { .. } => "InvalidDocblock",
1462 IssueKind::UndefinedThrowsDocblock { .. } => "UndefinedThrowsDocblock",
1463 IssueKind::InvalidDocblockType { .. } => "InvalidDocblockType",
1464 IssueKind::MixedArgument { .. } => "MixedArgument",
1465 IssueKind::MixedAssignment { .. } => "MixedAssignment",
1466 IssueKind::MixedMethodCall { .. } => "MixedMethodCall",
1467 IssueKind::MixedPropertyFetch { .. } => "MixedPropertyFetch",
1468 IssueKind::MixedPropertyAssignment { .. } => "MixedPropertyAssignment",
1469 IssueKind::MixedArrayAccess => "MixedArrayAccess",
1470 IssueKind::MixedArrayOffset => "MixedArrayOffset",
1471 IssueKind::MixedClone => "MixedClone",
1472 IssueKind::InvalidClone { .. } => "InvalidClone",
1473 IssueKind::PossiblyInvalidClone { .. } => "PossiblyInvalidClone",
1474 IssueKind::InvalidToString { .. } => "InvalidToString",
1475 IssueKind::CircularInheritance { .. } => "CircularInheritance",
1476 IssueKind::InvalidTraitUse { .. } => "InvalidTraitUse",
1477 IssueKind::ForbiddenCode { .. } => "ForbiddenCode",
1478 IssueKind::WrongCaseFunction { .. } => "WrongCaseFunction",
1479 IssueKind::WrongCaseMethod { .. } => "WrongCaseMethod",
1480 IssueKind::WrongCaseClass { .. } => "WrongCaseClass",
1481 IssueKind::InvalidAttribute { .. } => "InvalidAttribute",
1482 IssueKind::UndefinedAttributeClass { .. } => "UndefinedAttributeClass",
1483 IssueKind::DuplicateClass { .. } => "DuplicateClass",
1484 IssueKind::DuplicateInterface { .. } => "DuplicateInterface",
1485 IssueKind::DuplicateTrait { .. } => "DuplicateTrait",
1486 IssueKind::DuplicateEnum { .. } => "DuplicateEnum",
1487 IssueKind::DuplicateFunction { .. } => "DuplicateFunction",
1488 IssueKind::PluginIssue { .. } => "PluginIssue",
1489 }
1490 }
1491
1492 pub fn display_name(&self) -> &str {
1496 match self {
1497 IssueKind::PluginIssue { name, .. } => name,
1498 other => other.name(),
1499 }
1500 }
1501
1502 pub fn message(&self) -> String {
1504 match self {
1505 IssueKind::NonStaticSelfCall { class, method } => {
1506 format!("Non-static method {class}::{method}() cannot be called statically")
1507 }
1508 IssueKind::DirectConstructorCall { class } => {
1509 format!("Cannot call constructor of {class} directly")
1510 }
1511 IssueKind::InvalidScope { in_class } => {
1512 if *in_class {
1513 "$this cannot be used in a static method".to_string()
1514 } else {
1515 "$this cannot be used outside of a class".to_string()
1516 }
1517 }
1518 IssueKind::UndefinedVariable { name } => format!("Variable ${name} is not defined"),
1519 IssueKind::UndefinedFunction { name } => format!("Function {name}() is not defined"),
1520 IssueKind::UndefinedMethod { class, method } => {
1521 format!("Method {class}::{method}() does not exist")
1522 }
1523 IssueKind::UndefinedClass { name } => format!("Class {name} does not exist"),
1524 IssueKind::UndefinedProperty { class, property } => {
1525 format!("Property {class}::${property} does not exist")
1526 }
1527 IssueKind::UndefinedConstant { name } => format!("Constant {name} is not defined"),
1528 IssueKind::InaccessibleClassConstant { class, constant } => {
1529 format!("Cannot access constant {class}::{constant}")
1530 }
1531 IssueKind::InaccessibleProperty { class, property } => {
1532 format!("Cannot access property {class}::${property}")
1533 }
1534 IssueKind::PossiblyUndefinedVariable { name } => {
1535 format!("Variable ${name} might not be defined")
1536 }
1537 IssueKind::UndefinedTrait { name } => format!("Trait {name} does not exist"),
1538 IssueKind::TraitConstantAccessedDirectly {
1539 trait_name,
1540 constant,
1541 } => {
1542 format!("Cannot access trait constant {trait_name}::{constant} directly")
1543 }
1544 IssueKind::UndefinedTraitAliasMethod { trait_name, method } => match trait_name {
1545 Some(t) => {
1546 format!("An alias was defined for {t}::{method} but this method does not exist")
1547 }
1548 None => format!("An alias was defined for {method} but this method does not exist"),
1549 },
1550 IssueKind::ParentNotFound => {
1551 "Cannot use parent:: when current class has no parent".to_string()
1552 }
1553 IssueKind::InvalidStringClass { actual } => {
1554 format!("Dynamic class instantiation requires string or class-string type, got '{actual}'")
1555 }
1556 IssueKind::NotAnInterface { name } => {
1557 format!("{name} is not an interface")
1558 }
1559
1560 IssueKind::NullArgument { param, fn_name } => {
1561 format!("Argument ${param} of {fn_name}() cannot be null")
1562 }
1563 IssueKind::NullPropertyFetch { property } => {
1564 format!("Cannot access property ${property} on null")
1565 }
1566 IssueKind::NullMethodCall { method } => {
1567 format!("Cannot call method {method}() on null")
1568 }
1569 IssueKind::NullArrayAccess => "Cannot access array on null".to_string(),
1570 IssueKind::PossiblyNullArgument { param, fn_name } => {
1571 format!("Argument ${param} of {fn_name}() might be null")
1572 }
1573 IssueKind::PossiblyInvalidArgument {
1574 param,
1575 fn_name,
1576 expected,
1577 actual,
1578 } => {
1579 format!("Argument ${param} of {fn_name}() expects '{expected}', possibly different type '{actual}' provided")
1580 }
1581 IssueKind::PossiblyNullPropertyFetch { property } => {
1582 format!("Cannot access property ${property} on possibly null value")
1583 }
1584 IssueKind::PossiblyNullMethodCall { method } => {
1585 format!("Cannot call method {method}() on possibly null value")
1586 }
1587 IssueKind::PossiblyNullArrayAccess => {
1588 "Cannot access array on possibly null value".to_string()
1589 }
1590 IssueKind::NullableReturnStatement { expected, actual } => {
1591 format!("Return type '{actual}' is not compatible with declared '{expected}'")
1592 }
1593
1594 IssueKind::InvalidReturnType { expected, actual } => {
1595 format!("Return type '{actual}' is not compatible with declared '{expected}'")
1596 }
1597 IssueKind::InvalidArgument {
1598 param,
1599 fn_name,
1600 expected,
1601 actual,
1602 } => {
1603 format!("Argument ${param} of {fn_name}() expects '{expected}', got '{actual}'")
1604 }
1605 IssueKind::TooFewArguments {
1606 fn_name,
1607 expected,
1608 actual,
1609 } => {
1610 format!(
1611 "Too few arguments for {}(): expected {}, got {}",
1612 fn_name, expected, actual
1613 )
1614 }
1615 IssueKind::TooManyArguments {
1616 fn_name,
1617 expected,
1618 actual,
1619 } => {
1620 format!(
1621 "Too many arguments for {}(): expected {}, got {}",
1622 fn_name, expected, actual
1623 )
1624 }
1625 IssueKind::InvalidNamedArgument {
1626 fn_name,
1627 name,
1628 overwrites: true,
1629 } => {
1630 format!(
1631 "{}() argument ${} overwrites a previous argument",
1632 fn_name, name
1633 )
1634 }
1635 IssueKind::InvalidNamedArgument { fn_name, name, .. } => {
1636 format!("{}() has no parameter named ${}", fn_name, name)
1637 }
1638 IssueKind::InvalidNamedArguments { fn_name } => {
1639 format!("{}() does not accept named arguments", fn_name)
1640 }
1641 IssueKind::InvalidPassByReference { fn_name, param } => {
1642 format!(
1643 "Argument ${} of {}() must be passed by reference",
1644 param, fn_name
1645 )
1646 }
1647 IssueKind::InvalidPropertyFetch { ty } => {
1648 format!("Cannot fetch property on non-object type '{ty}'")
1649 }
1650 IssueKind::InvalidArrayAccess { ty } => {
1651 format!("Cannot use [] operator on non-array type '{ty}'")
1652 }
1653 IssueKind::PossiblyInvalidArrayAccess { ty } => {
1654 format!("Possibly invalid array access: '{ty}' might not support []")
1655 }
1656 IssueKind::InvalidArrayAssignment { ty } => {
1657 format!("Cannot use [] assignment on non-array type '{ty}'")
1658 }
1659 IssueKind::InvalidPropertyAssignment {
1660 property,
1661 expected,
1662 actual,
1663 } => {
1664 format!("Property ${property} expects '{expected}', cannot assign '{actual}'")
1665 }
1666 IssueKind::InvalidCast { from, to } => {
1667 format!("Cannot cast '{from}' to '{to}'")
1668 }
1669 IssueKind::InvalidStaticInvocation { class, method } => {
1670 format!("Non-static method {class}::{method}() cannot be called statically")
1671 }
1672 IssueKind::InvalidOperand { op, left, right } => {
1673 if right.is_empty() {
1674 format!("Operator '{op}' not supported for operand of type '{left}'")
1675 } else {
1676 format!("Operator '{op}' not supported between '{left}' and '{right}'")
1677 }
1678 }
1679 IssueKind::PossiblyInvalidOperand { op, left, right } => {
1680 format!("Operator '{op}' might not be supported between '{left}' and '{right}'")
1681 }
1682 IssueKind::PossiblyNullOperand { op, ty } => {
1683 format!("Operator '{op}' operand '{ty}' might be null")
1684 }
1685 IssueKind::DivisionByZero { op } => {
1686 format!("Division by zero: right operand of '{op}' is always 0")
1687 }
1688 IssueKind::RawObjectIteration { ty } => {
1689 format!("Cannot iterate over non-iterable object '{ty}'")
1690 }
1691 IssueKind::PossiblyRawObjectIteration { ty } => {
1692 format!("Cannot iterate over possibly non-iterable object '{ty}'")
1693 }
1694 IssueKind::MismatchingDocblockReturnType { declared, inferred } => {
1695 format!("Docblock return type '{declared}' does not match inferred '{inferred}'")
1696 }
1697 IssueKind::MismatchingDocblockParamType {
1698 param,
1699 declared,
1700 inferred,
1701 } => {
1702 format!(
1703 "Docblock type '{declared}' for ${param} does not match inferred '{inferred}'"
1704 )
1705 }
1706 IssueKind::TypeCheckMismatch {
1707 var,
1708 expected,
1709 actual,
1710 } => {
1711 format!("Type of {var} is expected to be {expected}, got {actual}")
1712 }
1713 IssueKind::Trace {
1714 variable,
1715 type_info,
1716 } => {
1717 format!("Type of ${variable} is {type_info}")
1718 }
1719
1720 IssueKind::InvalidArrayOffset { expected, actual } => {
1721 format!("Array offset expects '{expected}', got '{actual}'")
1722 }
1723 IssueKind::NonExistentArrayOffset { key } => {
1724 format!("Array offset '{key}' does not exist")
1725 }
1726 IssueKind::PossiblyInvalidArrayOffset { expected, actual } => {
1727 format!("Array offset might be invalid: expects '{expected}', got '{actual}'")
1728 }
1729 IssueKind::DuplicateArrayKey { key } => {
1730 format!("Array key {key} is duplicated — the earlier entry is silently overwritten")
1731 }
1732
1733 IssueKind::RedundantCondition {
1734 always_true,
1735 unreachable,
1736 } => match unreachable {
1737 Some(branch) => {
1738 format!("Condition is always {always_true}, so the {branch} is never reached")
1739 }
1740 None => format!("Condition is always {always_true}, so the check is redundant"),
1741 },
1742 IssueKind::RedundantCast { from, to } => {
1743 format!("Casting '{from}' to '{to}' is redundant")
1744 }
1745 IssueKind::UnnecessaryVarAnnotation { var } => {
1746 format!("@var annotation for ${var} is unnecessary")
1747 }
1748 IssueKind::TypeDoesNotContainType { left, right } => {
1749 format!("Type '{left}' can never contain type '{right}'")
1750 }
1751 IssueKind::ParadoxicalCondition { value } => {
1752 format!("Value {value} is duplicated; this branch can never be reached")
1753 }
1754 IssueKind::UnhandledMatchCondition { detail } => {
1755 format!("Unhandled match condition: {detail}")
1756 }
1757 IssueKind::DocblockTypeContradiction { expr, declared } => {
1758 format!("Type '{declared}' makes '{expr}' impossible — this can never hold")
1759 }
1760 IssueKind::ImpossibleIdenticalComparison { op, left, right } => {
1761 let result = if op == "===" { "false" } else { "true" };
1762 format!("'{op}' between '{left}' and '{right}' is always {result} — these types can never be identical")
1763 }
1764 IssueKind::ImpossibleLooseComparison { op, left, right } => {
1765 let result = if op == "==" { "false" } else { "true" };
1766 format!("'{op}' between '{left}' and '{right}' is always {result} — these types can never be loosely equal")
1767 }
1768 IssueKind::UnevaluatedCode { reason } => {
1769 format!("Unevaluated code: {reason}")
1770 }
1771 IssueKind::IfThisIsMismatch {
1772 class,
1773 method,
1774 expected,
1775 actual,
1776 } => {
1777 format!(
1778 "Cannot call {class}::{method}() — @if-this-is requires $this to be '{expected}', but it is '{actual}'"
1779 )
1780 }
1781
1782 IssueKind::UnusedVariable { name } => format!("Variable ${name} is never read"),
1783 IssueKind::UnusedParam { name } => format!("Parameter ${name} is never used"),
1784 IssueKind::UnreachableCode => "Unreachable code detected".to_string(),
1785 IssueKind::UnusedMethod { class, method } => {
1786 format!("Private method {class}::{method}() is never called")
1787 }
1788 IssueKind::UnusedProperty { class, property } => {
1789 format!("Private property {class}::${property} is never read")
1790 }
1791 IssueKind::UnusedFunction { name } => {
1792 format!("Function {name}() is never called")
1793 }
1794 IssueKind::UnusedForeachValue { name } => {
1795 format!("Foreach value ${name} is never read")
1796 }
1797 IssueKind::UnusedClass { class } => {
1798 format!("Class {class} is never referenced")
1799 }
1800 IssueKind::UnusedSuppress { kind } => {
1801 format!("Suppress annotation for '{kind}' is never used")
1802 }
1803 IssueKind::ArgumentTypeCoercion {
1804 param,
1805 fn_name,
1806 expected,
1807 actual,
1808 } => {
1809 format!("Argument ${param} of {fn_name}() expects '{expected}', got '{actual}' — coercion may fail at runtime")
1810 }
1811 IssueKind::PropertyTypeCoercion {
1812 property,
1813 expected,
1814 actual,
1815 } => {
1816 format!("Property ${property} expects '{expected}', cannot assign '{actual}' — coercion may fail at runtime")
1817 }
1818 IssueKind::ImpurePropertyAssignment { property } => {
1819 format!("Assigning to property {property} of a parameter in a pure or external-mutation-free context")
1820 }
1821 IssueKind::ImpureMethodCall { method } => {
1822 format!("Calling impure method {method}() in a pure or immutable context")
1823 }
1824 IssueKind::ImpureGlobalVariable { variable } => {
1825 format!("Using global variable ${variable} in a @pure function")
1826 }
1827 IssueKind::ImpureStaticVariable { variable } => {
1828 format!("Using static variable ${variable} in a @pure function")
1829 }
1830 IssueKind::ImpureByRefAssignment { variable } => {
1831 format!("Assigning to by-reference parameter ${variable} in a @pure function")
1832 }
1833 IssueKind::ImpureStaticPropertyAssignment { class, property } => {
1834 format!("Assigning to static property {class}::${property} in a @pure function")
1835 }
1836 IssueKind::ImpureStaticPropertyAccess { class, property } => {
1837 format!("Reading static property {class}::${property} in a @pure function")
1838 }
1839 IssueKind::ImpureFunctionCall { fn_name } => {
1840 format!("Calling impure function {fn_name}() in a @pure function")
1841 }
1842 IssueKind::ImmutablePropertyModification { receiver, property } => {
1843 format!("Assigning to property {property} of {receiver} in an immutable context (@psalm-immutable class or @psalm-mutation-free method)")
1844 }
1845
1846 IssueKind::UnimplementedAbstractMethod { class, method } => {
1847 format!("Class {class} must implement abstract method {method}()")
1848 }
1849 IssueKind::UnimplementedInterfaceMethod {
1850 class,
1851 interface,
1852 method,
1853 } => {
1854 format!("Class {class} must implement {interface}::{method}() from interface")
1855 }
1856 IssueKind::MethodSignatureMismatch {
1857 class,
1858 method,
1859 detail,
1860 } => {
1861 format!("Method {class}::{method}() signature mismatch: {detail}")
1862 }
1863 IssueKind::OverriddenMethodAccess { class, method } => {
1864 format!("Method {class}::{method}() overrides with less visibility")
1865 }
1866 IssueKind::OverriddenPropertyAccess { class, property } => {
1867 format!("Property {class}::${property} overrides with less visibility")
1868 }
1869 IssueKind::PropertyTypeRedeclarationMismatch {
1870 class,
1871 property,
1872 expected,
1873 actual,
1874 } => {
1875 format!(
1876 "Type of {class}::${property} must be {expected} (as in parent class), {actual} given"
1877 )
1878 }
1879 IssueKind::ReadonlyPropertyRedeclarationMismatch {
1880 parent_class,
1881 class,
1882 property,
1883 parent_readonly,
1884 } => {
1885 if *parent_readonly {
1886 format!(
1887 "Cannot redeclare readonly property {parent_class}::${property} as non-readonly {class}::${property}"
1888 )
1889 } else {
1890 format!(
1891 "Cannot redeclare non-readonly property {parent_class}::${property} as readonly {class}::${property}"
1892 )
1893 }
1894 }
1895 IssueKind::StaticPropertyRedeclarationMismatch {
1896 parent_class,
1897 class,
1898 property,
1899 parent_static,
1900 } => {
1901 if *parent_static {
1902 format!(
1903 "Cannot redeclare static property {parent_class}::${property} as non-static {class}::${property}"
1904 )
1905 } else {
1906 format!(
1907 "Cannot redeclare non-static property {parent_class}::${property} as static {class}::${property}"
1908 )
1909 }
1910 }
1911 IssueKind::BackedEnumCaseTypeMismatch {
1912 enum_name,
1913 case_name,
1914 expected,
1915 actual,
1916 } => {
1917 format!(
1918 "Backed enum case {enum_name}::{case_name} has value of type {actual}, but backing type is {expected}"
1919 )
1920 }
1921 IssueKind::ReadonlyClassExtendsMismatch {
1922 parent,
1923 child,
1924 parent_readonly,
1925 } => {
1926 if *parent_readonly {
1927 format!("Non-readonly class {child} cannot extend readonly class {parent}")
1928 } else {
1929 format!("Readonly class {child} cannot extend non-readonly class {parent}")
1930 }
1931 }
1932 IssueKind::InvalidReadonlyPropertyDeclaration { class, property } => {
1933 format!("Readonly property {class}::${property} cannot have a default value")
1934 }
1935 IssueKind::ReadonlyPropertyAssignment { class, property } => {
1936 format!(
1937 "Cannot assign to readonly property {class}::${property} outside of constructor"
1938 )
1939 }
1940 IssueKind::ReadonlyPropertyAlreadyInitialized { class, property } => {
1941 format!(
1942 "Cannot modify readonly property {class}::${property} — already initialized"
1943 )
1944 }
1945 IssueKind::InvalidExtendClass { parent, child } => {
1946 format!("Class {child} cannot extend final class {parent}")
1947 }
1948 IssueKind::InvalidTemplateParam {
1949 name,
1950 expected_bound,
1951 actual,
1952 } => {
1953 if actual.split('|').any(|part| part == "false") {
1954 format!(
1955 "Template type '{name}' inferred as '{actual}' can be false and therefore does not satisfy bound '{expected_bound}'"
1956 )
1957 } else {
1958 format!(
1959 "Template type '{name}' inferred as '{actual}' does not satisfy bound '{expected_bound}'"
1960 )
1961 }
1962 }
1963 IssueKind::ShadowedTemplateParam { name } => {
1964 format!(
1965 "Method template parameter '{name}' shadows class-level template parameter with the same name"
1966 )
1967 }
1968 IssueKind::FinalMethodOverridden {
1969 class,
1970 method,
1971 parent,
1972 } => {
1973 format!("Method {class}::{method}() cannot override final method from {parent}")
1974 }
1975 IssueKind::AbstractInstantiation { class } => {
1976 format!("Cannot instantiate abstract class {class}")
1977 }
1978 IssueKind::AbstractMethodCall { class, method } => {
1979 format!("Cannot call abstract method {class}::{method}()")
1980 }
1981 IssueKind::InterfaceInstantiation { class } => {
1982 format!("Cannot instantiate interface {class}")
1983 }
1984 IssueKind::InvalidOverride {
1985 class,
1986 method,
1987 detail,
1988 } => {
1989 format!("Method {class}::{method}() has #[Override] but {detail}")
1990 }
1991
1992 IssueKind::TaintedInput { sink } => format!("Tainted input reaching sink '{sink}'"),
1993 IssueKind::TaintedHtml => "Tainted HTML output — possible XSS".to_string(),
1994 IssueKind::TaintedSql => "Tainted SQL query — possible SQL injection".to_string(),
1995 IssueKind::TaintedShell => {
1996 "Tainted shell command — possible command injection".to_string()
1997 }
1998 IssueKind::TaintedLlmPrompt => {
1999 "Tainted LLM prompt — possible prompt injection".to_string()
2000 }
2001 IssueKind::TaintedHeader => {
2002 "Tainted HTTP header — possible header injection or open redirect".to_string()
2003 }
2004 IssueKind::TaintedCookie => "Tainted cookie — possible cookie injection".to_string(),
2005
2006 IssueKind::DeprecatedCall { name, message } => {
2007 let base = format!("Call to deprecated function {name}");
2008 append_deprecation_message(base, message)
2009 }
2010 IssueKind::DeprecatedProperty {
2011 class,
2012 property,
2013 message,
2014 } => {
2015 let base = format!("Property {class}::${property} is deprecated");
2016 append_deprecation_message(base, message)
2017 }
2018 IssueKind::DeprecatedConstant {
2019 class,
2020 constant,
2021 message,
2022 } => {
2023 let base = format!("Constant {class}::{constant} is deprecated");
2024 append_deprecation_message(base, message)
2025 }
2026 IssueKind::DeprecatedInterface { name, message } => {
2027 let base = format!("Interface {name} is deprecated");
2028 append_deprecation_message(base, message)
2029 }
2030 IssueKind::DeprecatedTrait { name, message } => {
2031 let base = format!("Trait {name} is deprecated");
2032 append_deprecation_message(base, message)
2033 }
2034 IssueKind::DeprecatedMethodCall {
2035 class,
2036 method,
2037 message,
2038 } => {
2039 let base = format!("Call to deprecated method {class}::{method}");
2040 append_deprecation_message(base, message)
2041 }
2042 IssueKind::DeprecatedMethod {
2043 class,
2044 method,
2045 message,
2046 } => {
2047 let base = format!("Method {class}::{method}() is deprecated");
2048 append_deprecation_message(base, message)
2049 }
2050 IssueKind::DeprecatedClass { name, message } => {
2051 let base = format!("Class {name} is deprecated");
2052 append_deprecation_message(base, message)
2053 }
2054 IssueKind::InternalMethod { class, method } => {
2055 format!("Method {class}::{method}() is marked @internal")
2056 }
2057 IssueKind::MissingReturnType { fn_name } => {
2058 format!("Function {fn_name}() has no return type annotation")
2059 }
2060 IssueKind::MissingClosureReturnType => {
2061 "Closure has no return type annotation".to_string()
2062 }
2063 IssueKind::MissingParamType { fn_name, param } => {
2064 format!("Parameter ${param} of {fn_name}() has no type annotation")
2065 }
2066 IssueKind::MissingPropertyType { class, property } => {
2067 format!("Property {class}::${property} has no type annotation")
2068 }
2069 IssueKind::InvalidThrow { ty } => {
2070 format!("Thrown type '{ty}' does not extend Throwable")
2071 }
2072 IssueKind::InvalidCatch { ty } => {
2073 format!("Caught type '{ty}' does not extend Throwable")
2074 }
2075 IssueKind::UnreachableCatch { ty, shadowed_by } => {
2076 format!("Catch block for '{ty}' is unreachable — already caught by '{shadowed_by}'")
2077 }
2078 IssueKind::MissingThrowsDocblock { class } => {
2079 format!("Exception {class} is thrown but not declared in @throws")
2080 }
2081 IssueKind::ImplicitToStringCast { class } => {
2082 format!("Class {class} is implicitly cast to string")
2083 }
2084 IssueKind::ImplicitFloatToIntCast { from } => {
2085 format!("Implicit cast from {from} to int truncates the fractional part")
2086 }
2087 IssueKind::ParseError { message } => format!("Parse error: {message}"),
2088 IssueKind::InvalidDocblock { message } => format!("Invalid docblock: {message}"),
2089 IssueKind::UndefinedThrowsDocblock { name } => {
2090 format!("@throws class '{name}' does not exist")
2091 }
2092 IssueKind::InvalidDocblockType { message } => {
2093 format!("Invalid docblock type: {message}")
2094 }
2095 IssueKind::MixedArgument { param, fn_name } => {
2096 format!("Argument ${param} of {fn_name}() is mixed")
2097 }
2098 IssueKind::MixedAssignment { var } => {
2099 format!("Variable ${var} is assigned a mixed type")
2100 }
2101 IssueKind::MixedMethodCall { method } => {
2102 format!("Method {method}() called on mixed type")
2103 }
2104 IssueKind::UnsupportedReferenceUsage => {
2105 "Reference assignment is not supported".to_string()
2106 }
2107 IssueKind::NoInterfaceProperties { property } => {
2108 format!("Property ${property} is not defined on this interface")
2109 }
2110 IssueKind::UndefinedDocblockClass { name } => {
2111 format!("Docblock type '{name}' does not exist")
2112 }
2113 IssueKind::MissingConstructor { class } => {
2114 format!("Class {class} has uninitialized properties but no constructor")
2115 }
2116 IssueKind::PropertyPossiblyUninitialized { class, property } => {
2117 format!(
2118 "Property {class}::${property} may be left uninitialized by the constructor"
2119 )
2120 }
2121 IssueKind::MixedFunctionCall => "Cannot call mixed type as a function".to_string(),
2122 IssueKind::MixedReturnStatement { declared } => {
2123 format!("Cannot return a mixed type from function with declared return type '{declared}'")
2124 }
2125 IssueKind::MixedPropertyFetch { property } => {
2126 format!("Property ${property} fetched on mixed type")
2127 }
2128 IssueKind::MixedPropertyAssignment { property } => {
2129 format!("Property ${property} assigned on mixed type")
2130 }
2131 IssueKind::MixedArrayAccess => "Array access on mixed type".to_string(),
2132 IssueKind::MixedArrayOffset => "Mixed type used as array offset".to_string(),
2133 IssueKind::MixedClone => "cannot clone mixed".to_string(),
2134 IssueKind::InvalidClone { ty } => format!("cannot clone non-object {ty}"),
2135 IssueKind::PossiblyInvalidClone { ty } => {
2136 format!("cannot clone possibly non-object {ty}")
2137 }
2138 IssueKind::InvalidToString { class } => {
2139 format!("Method {class}::__toString() must return a string")
2140 }
2141 IssueKind::CircularInheritance { class } => {
2142 format!("Class {class} has a circular inheritance chain")
2143 }
2144 IssueKind::InvalidTraitUse { trait_name, reason } => {
2145 format!("Trait {trait_name} used incorrectly: {reason}")
2146 }
2147 IssueKind::WrongCaseFunction { used, canonical } => {
2148 format!("Function name '{used}' has incorrect casing; use '{canonical}'")
2149 }
2150 IssueKind::WrongCaseMethod {
2151 class,
2152 used,
2153 canonical,
2154 } => {
2155 format!("Method name '{class}::{used}' has incorrect casing; use '{canonical}'")
2156 }
2157 IssueKind::WrongCaseClass { used, canonical } => {
2158 format!("Class name '{used}' has incorrect casing; use '{canonical}'")
2159 }
2160 IssueKind::InvalidAttribute { message } => message.clone(),
2161 IssueKind::UndefinedAttributeClass { name } => {
2162 format!("Attribute class {name} does not exist")
2163 }
2164 IssueKind::ForbiddenCode { message } => message.clone(),
2165 IssueKind::DuplicateClass { name } => {
2166 format!("Class {name} has already been defined")
2167 }
2168 IssueKind::DuplicateInterface { name } => {
2169 format!("Interface {name} has already been defined")
2170 }
2171 IssueKind::DuplicateTrait { name } => {
2172 format!("Trait {name} has already been defined")
2173 }
2174 IssueKind::DuplicateEnum { name } => {
2175 format!("Enum {name} has already been defined")
2176 }
2177 IssueKind::DuplicateFunction { name } => {
2178 format!("Function {name}() has already been defined")
2179 }
2180 IssueKind::PluginIssue { message, .. } => message.clone(),
2181 }
2182 }
2183}
2184
2185#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
2190pub struct Issue {
2191 pub kind: IssueKind,
2192 pub severity: Severity,
2193 pub location: Location,
2194 pub snippet: Option<String>,
2195 pub suppressed: bool,
2196}
2197
2198impl Issue {
2199 pub fn new(kind: IssueKind, location: Location) -> Self {
2200 let severity = kind.default_severity();
2201 Self {
2202 severity,
2203 kind,
2204 location,
2205 snippet: None,
2206 suppressed: false,
2207 }
2208 }
2209
2210 pub fn with_snippet(mut self, snippet: impl Into<String>) -> Self {
2211 self.snippet = Some(snippet.into());
2212 self
2213 }
2214
2215 pub fn suppress(mut self) -> Self {
2216 self.suppressed = true;
2217 self
2218 }
2219}
2220
2221#[derive(Debug, Default)]
2226pub struct IssueBuffer {
2227 issues: Vec<Issue>,
2228 seen: HashSet<(&'static str, Arc<str>, u32, u16)>,
2229 file_suppressions: Vec<String>,
2231 discard: bool,
2232}
2233
2234impl IssueBuffer {
2235 pub fn new() -> Self {
2236 Self::default()
2237 }
2238
2239 pub fn discarding(mut self) -> Self {
2240 self.discard = true;
2241 self
2242 }
2243
2244 pub fn add(&mut self, issue: Issue) {
2245 if self.discard {
2246 return;
2247 }
2248 let key = (
2249 issue.kind.name(),
2250 issue.location.file.clone(),
2251 issue.location.line,
2252 issue.location.col_start,
2253 );
2254 if self.seen.insert(key) {
2255 self.issues.push(issue);
2256 }
2257 }
2258
2259 pub fn has_impossible_comparison_within(&self, span: &Location) -> bool {
2262 self.issues.iter().any(|i| {
2263 matches!(
2264 i.kind,
2265 IssueKind::ImpossibleIdenticalComparison { .. }
2266 | IssueKind::ImpossibleLooseComparison { .. }
2267 | IssueKind::DocblockTypeContradiction { .. }
2268 | IssueKind::TypeDoesNotContainType { .. }
2269 ) && i.location.file == span.file
2270 && (i.location.line, i.location.col_start) >= (span.line, span.col_start)
2271 && (i.location.line, i.location.col_start) < (span.line_end, span.col_end)
2272 })
2273 }
2274
2275 pub fn add_suppression(&mut self, name: impl Into<String>) {
2276 self.file_suppressions.push(name.into());
2277 }
2278
2279 pub fn into_issues(self) -> Vec<Issue> {
2281 self.issues
2282 .into_iter()
2283 .filter(|i| !i.suppressed)
2284 .filter(|i| {
2285 !self
2286 .file_suppressions
2287 .iter()
2288 .any(|s| s == i.kind.display_name())
2289 })
2290 .collect()
2291 }
2292
2293 pub fn into_all_issues(self) -> Vec<Issue> {
2298 self.issues
2299 .into_iter()
2300 .map(|mut i| {
2301 if self
2302 .file_suppressions
2303 .iter()
2304 .any(|s| s == i.kind.display_name())
2305 {
2306 i.suppressed = true;
2307 }
2308 i
2309 })
2310 .collect()
2311 }
2312
2313 pub fn suppress_range(&mut self, from: usize, suppressions: &[String]) {
2316 if suppressions.is_empty() {
2317 return;
2318 }
2319 for issue in self.issues[from..].iter_mut() {
2320 if suppressions.iter().any(|s| s == issue.kind.display_name()) {
2321 issue.suppressed = true;
2322 }
2323 }
2324 }
2325
2326 pub fn issue_count(&self) -> usize {
2329 self.issues.len()
2330 }
2331
2332 pub fn truncate_to(&mut self, mark: usize) {
2338 for issue in self.issues.drain(mark..) {
2339 let key = (
2340 issue.kind.name(),
2341 issue.location.file.clone(),
2342 issue.location.line,
2343 issue.location.col_start,
2344 );
2345 self.seen.remove(&key);
2346 }
2347 }
2348
2349 pub fn is_empty(&self) -> bool {
2350 self.issues.is_empty()
2351 }
2352
2353 pub fn len(&self) -> usize {
2354 self.issues.len()
2355 }
2356
2357 pub fn error_count(&self) -> usize {
2358 self.issues
2359 .iter()
2360 .filter(|i| !i.suppressed && i.severity == Severity::Error)
2361 .count()
2362 }
2363
2364 pub fn warning_count(&self) -> usize {
2365 self.issues
2366 .iter()
2367 .filter(|i| !i.suppressed && i.severity == Severity::Warning)
2368 .count()
2369 }
2370}
2371
2372#[cfg(test)]
2373mod code_tests {
2374 use super::*;
2375 use std::collections::HashSet;
2376
2377 fn one_of_each() -> Vec<IssueKind> {
2383 let s = || String::new();
2384 vec![
2385 IssueKind::InvalidScope { in_class: false },
2386 IssueKind::NonStaticSelfCall {
2387 class: s(),
2388 method: s(),
2389 },
2390 IssueKind::DirectConstructorCall { class: s() },
2391 IssueKind::UndefinedVariable { name: s() },
2392 IssueKind::UndefinedFunction { name: s() },
2393 IssueKind::UndefinedMethod {
2394 class: s(),
2395 method: s(),
2396 },
2397 IssueKind::UndefinedClass { name: s() },
2398 IssueKind::NotAnInterface { name: s() },
2399 IssueKind::UndefinedProperty {
2400 class: s(),
2401 property: s(),
2402 },
2403 IssueKind::UndefinedConstant { name: s() },
2404 IssueKind::InaccessibleClassConstant {
2405 class: s(),
2406 constant: s(),
2407 },
2408 IssueKind::InaccessibleProperty {
2409 class: s(),
2410 property: s(),
2411 },
2412 IssueKind::PossiblyUndefinedVariable { name: s() },
2413 IssueKind::UndefinedTrait { name: s() },
2414 IssueKind::TraitConstantAccessedDirectly {
2415 trait_name: s(),
2416 constant: s(),
2417 },
2418 IssueKind::UndefinedTraitAliasMethod {
2419 trait_name: Some(s()),
2420 method: s(),
2421 },
2422 IssueKind::ParentNotFound,
2423 IssueKind::NullArgument {
2424 param: s(),
2425 fn_name: s(),
2426 },
2427 IssueKind::NullPropertyFetch { property: s() },
2428 IssueKind::NullMethodCall { method: s() },
2429 IssueKind::NullArrayAccess,
2430 IssueKind::PossiblyNullArgument {
2431 param: s(),
2432 fn_name: s(),
2433 },
2434 IssueKind::PossiblyInvalidArgument {
2435 param: s(),
2436 fn_name: s(),
2437 expected: s(),
2438 actual: s(),
2439 },
2440 IssueKind::PossiblyNullPropertyFetch { property: s() },
2441 IssueKind::PossiblyNullMethodCall { method: s() },
2442 IssueKind::PossiblyNullArrayAccess,
2443 IssueKind::NullableReturnStatement {
2444 expected: s(),
2445 actual: s(),
2446 },
2447 IssueKind::InvalidReturnType {
2448 expected: s(),
2449 actual: s(),
2450 },
2451 IssueKind::InvalidArgument {
2452 param: s(),
2453 fn_name: s(),
2454 expected: s(),
2455 actual: s(),
2456 },
2457 IssueKind::TooFewArguments {
2458 fn_name: s(),
2459 expected: 0,
2460 actual: 0,
2461 },
2462 IssueKind::TooManyArguments {
2463 fn_name: s(),
2464 expected: 0,
2465 actual: 0,
2466 },
2467 IssueKind::InvalidNamedArgument {
2468 fn_name: s(),
2469 name: s(),
2470 overwrites: false,
2471 },
2472 IssueKind::InvalidNamedArguments { fn_name: s() },
2473 IssueKind::InvalidPassByReference {
2474 fn_name: s(),
2475 param: s(),
2476 },
2477 IssueKind::InvalidPropertyFetch { ty: s() },
2478 IssueKind::InvalidArrayAccess { ty: s() },
2479 IssueKind::PossiblyInvalidArrayAccess { ty: s() },
2480 IssueKind::InvalidArrayAssignment { ty: s() },
2481 IssueKind::InvalidPropertyAssignment {
2482 property: s(),
2483 expected: s(),
2484 actual: s(),
2485 },
2486 IssueKind::InvalidCast { from: s(), to: s() },
2487 IssueKind::InvalidStaticInvocation {
2488 class: s(),
2489 method: s(),
2490 },
2491 IssueKind::InvalidOperand {
2492 op: s(),
2493 left: s(),
2494 right: s(),
2495 },
2496 IssueKind::PossiblyInvalidOperand {
2497 op: s(),
2498 left: s(),
2499 right: s(),
2500 },
2501 IssueKind::PossiblyNullOperand { op: s(), ty: s() },
2502 IssueKind::DivisionByZero { op: s() },
2503 IssueKind::RawObjectIteration { ty: s() },
2504 IssueKind::PossiblyRawObjectIteration { ty: s() },
2505 IssueKind::MismatchingDocblockReturnType {
2506 declared: s(),
2507 inferred: s(),
2508 },
2509 IssueKind::MismatchingDocblockParamType {
2510 param: s(),
2511 declared: s(),
2512 inferred: s(),
2513 },
2514 IssueKind::TypeCheckMismatch {
2515 var: s(),
2516 expected: s(),
2517 actual: s(),
2518 },
2519 IssueKind::Trace {
2520 variable: s(),
2521 type_info: s(),
2522 },
2523 IssueKind::InvalidArrayOffset {
2524 expected: s(),
2525 actual: s(),
2526 },
2527 IssueKind::NonExistentArrayOffset { key: s() },
2528 IssueKind::PossiblyInvalidArrayOffset {
2529 expected: s(),
2530 actual: s(),
2531 },
2532 IssueKind::DuplicateArrayKey { key: s() },
2533 IssueKind::RedundantCondition {
2534 always_true: true,
2535 unreachable: None,
2536 },
2537 IssueKind::RedundantCast { from: s(), to: s() },
2538 IssueKind::UnnecessaryVarAnnotation { var: s() },
2539 IssueKind::TypeDoesNotContainType {
2540 left: s(),
2541 right: s(),
2542 },
2543 IssueKind::UnusedVariable { name: s() },
2544 IssueKind::UnusedParam { name: s() },
2545 IssueKind::UnreachableCode,
2546 IssueKind::UnhandledMatchCondition { detail: s() },
2547 IssueKind::UnusedMethod {
2548 class: s(),
2549 method: s(),
2550 },
2551 IssueKind::UnusedProperty {
2552 class: s(),
2553 property: s(),
2554 },
2555 IssueKind::UnusedFunction { name: s() },
2556 IssueKind::UnusedForeachValue { name: s() },
2557 IssueKind::UnusedClass { class: s() },
2558 IssueKind::UnusedSuppress { kind: s() },
2559 IssueKind::ArgumentTypeCoercion {
2560 param: s(),
2561 fn_name: s(),
2562 expected: s(),
2563 actual: s(),
2564 },
2565 IssueKind::PropertyTypeCoercion {
2566 property: s(),
2567 expected: s(),
2568 actual: s(),
2569 },
2570 IssueKind::ImpurePropertyAssignment { property: s() },
2571 IssueKind::ImpureMethodCall { method: s() },
2572 IssueKind::ImpureGlobalVariable { variable: s() },
2573 IssueKind::ImpureStaticVariable { variable: s() },
2574 IssueKind::ImpureByRefAssignment { variable: s() },
2575 IssueKind::ImpureStaticPropertyAssignment {
2576 class: s(),
2577 property: s(),
2578 },
2579 IssueKind::ImpureStaticPropertyAccess {
2580 class: s(),
2581 property: s(),
2582 },
2583 IssueKind::ImpureFunctionCall { fn_name: s() },
2584 IssueKind::ImmutablePropertyModification {
2585 receiver: s(),
2586 property: s(),
2587 },
2588 IssueKind::ReadonlyPropertyAssignment {
2589 class: s(),
2590 property: s(),
2591 },
2592 IssueKind::ReadonlyPropertyAlreadyInitialized {
2593 class: s(),
2594 property: s(),
2595 },
2596 IssueKind::UnimplementedAbstractMethod {
2597 class: s(),
2598 method: s(),
2599 },
2600 IssueKind::UnimplementedInterfaceMethod {
2601 class: s(),
2602 interface: s(),
2603 method: s(),
2604 },
2605 IssueKind::MethodSignatureMismatch {
2606 class: s(),
2607 method: s(),
2608 detail: s(),
2609 },
2610 IssueKind::OverriddenMethodAccess {
2611 class: s(),
2612 method: s(),
2613 },
2614 IssueKind::OverriddenPropertyAccess {
2615 class: s(),
2616 property: s(),
2617 },
2618 IssueKind::PropertyTypeRedeclarationMismatch {
2619 class: s(),
2620 property: s(),
2621 expected: s(),
2622 actual: s(),
2623 },
2624 IssueKind::ReadonlyPropertyRedeclarationMismatch {
2625 parent_class: s(),
2626 class: s(),
2627 property: s(),
2628 parent_readonly: true,
2629 },
2630 IssueKind::StaticPropertyRedeclarationMismatch {
2631 parent_class: s(),
2632 class: s(),
2633 property: s(),
2634 parent_static: true,
2635 },
2636 IssueKind::ReadonlyClassExtendsMismatch {
2637 parent: s(),
2638 child: s(),
2639 parent_readonly: true,
2640 },
2641 IssueKind::InvalidReadonlyPropertyDeclaration {
2642 class: s(),
2643 property: s(),
2644 },
2645 IssueKind::BackedEnumCaseTypeMismatch {
2646 enum_name: s(),
2647 case_name: s(),
2648 expected: s(),
2649 actual: s(),
2650 },
2651 IssueKind::InvalidExtendClass {
2652 parent: s(),
2653 child: s(),
2654 },
2655 IssueKind::FinalMethodOverridden {
2656 class: s(),
2657 method: s(),
2658 parent: s(),
2659 },
2660 IssueKind::AbstractInstantiation { class: s() },
2661 IssueKind::AbstractMethodCall {
2662 class: s(),
2663 method: s(),
2664 },
2665 IssueKind::InterfaceInstantiation { class: s() },
2666 IssueKind::InvalidOverride {
2667 class: s(),
2668 method: s(),
2669 detail: s(),
2670 },
2671 IssueKind::CircularInheritance { class: s() },
2672 IssueKind::TaintedInput { sink: s() },
2673 IssueKind::TaintedHtml,
2674 IssueKind::TaintedSql,
2675 IssueKind::TaintedShell,
2676 IssueKind::TaintedLlmPrompt,
2677 IssueKind::TaintedHeader,
2678 IssueKind::TaintedCookie,
2679 IssueKind::InvalidTemplateParam {
2680 name: s(),
2681 expected_bound: s(),
2682 actual: s(),
2683 },
2684 IssueKind::ShadowedTemplateParam { name: s() },
2685 IssueKind::DeprecatedCall {
2686 name: s(),
2687 message: None,
2688 },
2689 IssueKind::DeprecatedProperty {
2690 class: s(),
2691 property: s(),
2692 message: None,
2693 },
2694 IssueKind::DeprecatedConstant {
2695 class: s(),
2696 constant: s(),
2697 message: None,
2698 },
2699 IssueKind::DeprecatedInterface {
2700 name: s(),
2701 message: None,
2702 },
2703 IssueKind::DeprecatedTrait {
2704 name: s(),
2705 message: None,
2706 },
2707 IssueKind::DeprecatedMethodCall {
2708 class: s(),
2709 method: s(),
2710 message: None,
2711 },
2712 IssueKind::DeprecatedMethod {
2713 class: s(),
2714 method: s(),
2715 message: None,
2716 },
2717 IssueKind::DeprecatedClass {
2718 name: s(),
2719 message: None,
2720 },
2721 IssueKind::InternalMethod {
2722 class: s(),
2723 method: s(),
2724 },
2725 IssueKind::MissingReturnType { fn_name: s() },
2726 IssueKind::MissingClosureReturnType,
2727 IssueKind::MissingParamType {
2728 fn_name: s(),
2729 param: s(),
2730 },
2731 IssueKind::MissingPropertyType {
2732 class: s(),
2733 property: s(),
2734 },
2735 IssueKind::MissingThrowsDocblock { class: s() },
2736 IssueKind::InvalidDocblock { message: s() },
2737 IssueKind::UndefinedThrowsDocblock { name: s() },
2738 IssueKind::InvalidDocblockType { message: s() },
2739 IssueKind::MixedArgument {
2740 param: s(),
2741 fn_name: s(),
2742 },
2743 IssueKind::MixedAssignment { var: s() },
2744 IssueKind::MixedMethodCall { method: s() },
2745 IssueKind::UnsupportedReferenceUsage,
2746 IssueKind::NoInterfaceProperties { property: s() },
2747 IssueKind::UndefinedDocblockClass { name: s() },
2748 IssueKind::MissingConstructor { class: s() },
2749 IssueKind::PropertyPossiblyUninitialized {
2750 class: s(),
2751 property: s(),
2752 },
2753 IssueKind::MixedFunctionCall,
2754 IssueKind::MixedReturnStatement { declared: s() },
2755 IssueKind::MixedPropertyFetch { property: s() },
2756 IssueKind::MixedPropertyAssignment { property: s() },
2757 IssueKind::MixedArrayAccess,
2758 IssueKind::MixedArrayOffset,
2759 IssueKind::MixedClone,
2760 IssueKind::InvalidClone { ty: s() },
2761 IssueKind::PossiblyInvalidClone { ty: s() },
2762 IssueKind::InvalidToString { class: s() },
2763 IssueKind::InvalidTraitUse {
2764 trait_name: s(),
2765 reason: s(),
2766 },
2767 IssueKind::ParseError { message: s() },
2768 IssueKind::InvalidThrow { ty: s() },
2769 IssueKind::InvalidCatch { ty: s() },
2770 IssueKind::UnreachableCatch {
2771 ty: s(),
2772 shadowed_by: s(),
2773 },
2774 IssueKind::ImplicitToStringCast { class: s() },
2775 IssueKind::ImplicitFloatToIntCast { from: s() },
2776 IssueKind::WrongCaseFunction {
2777 used: s(),
2778 canonical: s(),
2779 },
2780 IssueKind::WrongCaseMethod {
2781 class: s(),
2782 used: s(),
2783 canonical: s(),
2784 },
2785 IssueKind::WrongCaseClass {
2786 used: s(),
2787 canonical: s(),
2788 },
2789 IssueKind::InvalidAttribute { message: s() },
2790 IssueKind::UndefinedAttributeClass { name: s() },
2791 IssueKind::ForbiddenCode { message: s() },
2792 IssueKind::DuplicateClass { name: s() },
2793 IssueKind::DuplicateInterface { name: s() },
2794 IssueKind::DuplicateTrait { name: s() },
2795 IssueKind::DuplicateEnum { name: s() },
2796 IssueKind::DuplicateFunction { name: s() },
2797 ]
2798 }
2799
2800 #[test]
2801 fn codes_have_expected_shape() {
2802 for kind in one_of_each() {
2803 let code = kind.code();
2804 assert!(
2805 code.len() == 7
2806 && code.starts_with("MIR")
2807 && code[3..].chars().all(|c| c.is_ascii_digit()),
2808 "code {code:?} for {} does not match MIR####",
2809 kind.name(),
2810 );
2811 }
2812 }
2813
2814 #[test]
2815 fn codes_are_unique() {
2816 let kinds = one_of_each();
2817 let mut seen: HashSet<&'static str> = HashSet::new();
2818 for kind in &kinds {
2819 assert!(
2820 seen.insert(kind.code()),
2821 "duplicate code {} (variant {})",
2822 kind.code(),
2823 kind.name(),
2824 );
2825 }
2826 }
2827
2828 #[test]
2829 fn default_severity_for_code_round_trips() {
2830 for kind in one_of_each() {
2831 let code = kind.code();
2832 assert_eq!(
2833 IssueKind::default_severity_for_code(code),
2834 Some(kind.default_severity()),
2835 "severity mismatch for {code} (variant {})",
2836 kind.name(),
2837 );
2838 }
2839 }
2840
2841 #[test]
2842 fn default_severity_for_code_unknown_returns_none() {
2843 assert_eq!(IssueKind::default_severity_for_code("MIR9999"), None);
2844 assert_eq!(IssueKind::default_severity_for_code(""), None);
2845 assert_eq!(IssueKind::default_severity_for_code("mir0001"), None);
2846 }
2847
2848 #[test]
2851 fn one_of_each_has_every_variant() {
2852 assert_eq!(one_of_each().len(), 162);
2855 }
2856}