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