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